feat: Add a process document for Subscription (#37126)
* feat: Add a process document for Subscription * chore: Remove print statements * feat: Input for generating invoice before currenc invoice date * chore: patch for backward compatability * refactor: Unit tests for subscription * chore: set status on insert
This commit is contained in:
parent
03f0abf6de
commit
e19e04b050
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Process Subscription", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
@ -0,0 +1,90 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2023-09-17 15:40:59.724177",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"posting_date",
|
||||
"subscription",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Process Subscription",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Posting Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "subscription",
|
||||
"fieldtype": "Link",
|
||||
"label": "Subscription",
|
||||
"options": "Subscription"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-17 17:33:37.974166",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Subscription",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate
|
||||
|
||||
from erpnext.accounts.doctype.subscription.subscription import process_all
|
||||
|
||||
|
||||
class ProcessSubscription(Document):
|
||||
def on_submit(self):
|
||||
process_all(subscription=self.subscription, posting_date=self.posting_date)
|
||||
|
||||
|
||||
def create_subscription_process(
|
||||
subscription: str | None, posting_date: Union[str, datetime.date] | None
|
||||
):
|
||||
"""Create a new Process Subscription document"""
|
||||
doc = frappe.new_doc("Process Subscription")
|
||||
doc.subscription = subscription
|
||||
doc.posting_date = getdate(posting_date)
|
||||
doc.insert(ignore_permissions=True)
|
||||
doc.submit()
|
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestProcessSubscription(FrappeTestCase):
|
||||
pass
|
@ -24,8 +24,9 @@
|
||||
"current_invoice_start",
|
||||
"current_invoice_end",
|
||||
"days_until_due",
|
||||
"generate_invoice_at",
|
||||
"number_of_days",
|
||||
"cancel_at_period_end",
|
||||
"generate_invoice_at_period_start",
|
||||
"sb_4",
|
||||
"plans",
|
||||
"sb_1",
|
||||
@ -86,12 +87,14 @@
|
||||
"fieldname": "current_invoice_start",
|
||||
"fieldtype": "Date",
|
||||
"label": "Current Invoice Start Date",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "current_invoice_end",
|
||||
"fieldtype": "Date",
|
||||
"label": "Current Invoice End Date",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -107,12 +110,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Cancel At End Of Period"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "generate_invoice_at_period_start",
|
||||
"fieldtype": "Check",
|
||||
"label": "Generate Invoice At Beginning Of Period"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "sb_4",
|
||||
@ -240,6 +237,21 @@
|
||||
"fieldname": "submit_invoice",
|
||||
"fieldtype": "Check",
|
||||
"label": "Submit Generated Invoices"
|
||||
},
|
||||
{
|
||||
"default": "End of the current subscription period",
|
||||
"fieldname": "generate_invoice_at",
|
||||
"fieldtype": "Select",
|
||||
"label": "Generate Invoice At",
|
||||
"options": "End of the current subscription period\nBeginning of the current subscription period\nDays before the current subscription period",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.generate_invoice_at === \"Days before the current subscription period\"",
|
||||
"fieldname": "number_of_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Number of Days",
|
||||
"mandatory_depends_on": "eval:doc.generate_invoice_at === \"Days before the current subscription period\""
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
@ -255,7 +267,7 @@
|
||||
"link_fieldname": "subscription"
|
||||
}
|
||||
],
|
||||
"modified": "2022-02-18 23:24:57.185054",
|
||||
"modified": "2023-09-18 17:48:21.900252",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Subscription",
|
||||
|
@ -36,12 +36,15 @@ class InvoiceNotCancelled(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
DateTimeLikeObject = Union[str, datetime.date]
|
||||
|
||||
|
||||
class Subscription(Document):
|
||||
def before_insert(self):
|
||||
# update start just before the subscription doc is created
|
||||
self.update_subscription_period(self.start_date)
|
||||
|
||||
def update_subscription_period(self, date: Optional[Union[datetime.date, str]] = None):
|
||||
def update_subscription_period(self, date: Optional["DateTimeLikeObject"] = None):
|
||||
"""
|
||||
Subscription period is the period to be billed. This method updates the
|
||||
beginning of the billing period and end of the billing period.
|
||||
@ -52,14 +55,14 @@ class Subscription(Document):
|
||||
self.current_invoice_start = self.get_current_invoice_start(date)
|
||||
self.current_invoice_end = self.get_current_invoice_end(self.current_invoice_start)
|
||||
|
||||
def _get_subscription_period(self, date: Optional[Union[datetime.date, str]] = None):
|
||||
def _get_subscription_period(self, date: Optional["DateTimeLikeObject"] = None):
|
||||
_current_invoice_start = self.get_current_invoice_start(date)
|
||||
_current_invoice_end = self.get_current_invoice_end(_current_invoice_start)
|
||||
|
||||
return _current_invoice_start, _current_invoice_end
|
||||
|
||||
def get_current_invoice_start(
|
||||
self, date: Optional[Union[datetime.date, str]] = None
|
||||
self, date: Optional["DateTimeLikeObject"] = None
|
||||
) -> Union[datetime.date, str]:
|
||||
"""
|
||||
This returns the date of the beginning of the current billing period.
|
||||
@ -84,7 +87,7 @@ class Subscription(Document):
|
||||
return _current_invoice_start
|
||||
|
||||
def get_current_invoice_end(
|
||||
self, date: Optional[Union[datetime.date, str]] = None
|
||||
self, date: Optional["DateTimeLikeObject"] = None
|
||||
) -> Union[datetime.date, str]:
|
||||
"""
|
||||
This returns the date of the end of the current billing period.
|
||||
@ -179,30 +182,24 @@ class Subscription(Document):
|
||||
|
||||
return data
|
||||
|
||||
def set_subscription_status(self) -> None:
|
||||
def set_subscription_status(self, posting_date: Optional["DateTimeLikeObject"] = None) -> None:
|
||||
"""
|
||||
Sets the status of the `Subscription`
|
||||
"""
|
||||
if self.is_trialling():
|
||||
self.status = "Trialling"
|
||||
elif (
|
||||
self.status == "Active"
|
||||
and self.end_date
|
||||
and getdate(frappe.flags.current_date) > getdate(self.end_date)
|
||||
self.status == "Active" and self.end_date and getdate(posting_date) > getdate(self.end_date)
|
||||
):
|
||||
self.status = "Completed"
|
||||
elif self.is_past_grace_period():
|
||||
self.status = self.get_status_for_past_grace_period()
|
||||
self.cancelation_date = (
|
||||
getdate(frappe.flags.current_date) if self.status == "Cancelled" else None
|
||||
)
|
||||
self.cancelation_date = getdate(posting_date) if self.status == "Cancelled" else None
|
||||
elif self.current_invoice_is_past_due() and not self.is_past_grace_period():
|
||||
self.status = "Past Due Date"
|
||||
elif not self.has_outstanding_invoice() or self.is_new_subscription():
|
||||
self.status = "Active"
|
||||
|
||||
self.save()
|
||||
|
||||
def is_trialling(self) -> bool:
|
||||
"""
|
||||
Returns `True` if the `Subscription` is in trial period.
|
||||
@ -210,7 +207,9 @@ class Subscription(Document):
|
||||
return not self.period_has_passed(self.trial_period_end) and self.is_new_subscription()
|
||||
|
||||
@staticmethod
|
||||
def period_has_passed(end_date: Union[str, datetime.date]) -> bool:
|
||||
def period_has_passed(
|
||||
end_date: Union[str, datetime.date], posting_date: Optional["DateTimeLikeObject"] = None
|
||||
) -> bool:
|
||||
"""
|
||||
Returns true if the given `end_date` has passed
|
||||
"""
|
||||
@ -218,7 +217,7 @@ class Subscription(Document):
|
||||
if not end_date:
|
||||
return True
|
||||
|
||||
return getdate(frappe.flags.current_date) > getdate(end_date)
|
||||
return getdate(posting_date) > getdate(end_date)
|
||||
|
||||
def get_status_for_past_grace_period(self) -> str:
|
||||
cancel_after_grace = cint(frappe.get_value("Subscription Settings", None, "cancel_after_grace"))
|
||||
@ -229,7 +228,7 @@ class Subscription(Document):
|
||||
|
||||
return status
|
||||
|
||||
def is_past_grace_period(self) -> bool:
|
||||
def is_past_grace_period(self, posting_date: Optional["DateTimeLikeObject"] = None) -> bool:
|
||||
"""
|
||||
Returns `True` if the grace period for the `Subscription` has passed
|
||||
"""
|
||||
@ -237,18 +236,18 @@ class Subscription(Document):
|
||||
return
|
||||
|
||||
grace_period = cint(frappe.get_value("Subscription Settings", None, "grace_period"))
|
||||
return getdate(frappe.flags.current_date) >= getdate(
|
||||
add_days(self.current_invoice.due_date, grace_period)
|
||||
)
|
||||
return getdate(posting_date) >= getdate(add_days(self.current_invoice.due_date, grace_period))
|
||||
|
||||
def current_invoice_is_past_due(self) -> bool:
|
||||
def current_invoice_is_past_due(
|
||||
self, posting_date: Optional["DateTimeLikeObject"] = None
|
||||
) -> bool:
|
||||
"""
|
||||
Returns `True` if the current generated invoice is overdue
|
||||
"""
|
||||
if not self.current_invoice or self.is_paid(self.current_invoice):
|
||||
return False
|
||||
|
||||
return getdate(frappe.flags.current_date) >= getdate(self.current_invoice.due_date)
|
||||
return getdate(posting_date) >= getdate(self.current_invoice.due_date)
|
||||
|
||||
@property
|
||||
def invoice_document_type(self) -> str:
|
||||
@ -270,6 +269,9 @@ class Subscription(Document):
|
||||
if not self.cost_center:
|
||||
self.cost_center = get_default_cost_center(self.get("company"))
|
||||
|
||||
if self.is_new():
|
||||
self.set_subscription_status()
|
||||
|
||||
def validate_trial_period(self) -> None:
|
||||
"""
|
||||
Runs sanity checks on trial period dates for the `Subscription`
|
||||
@ -305,10 +307,6 @@ class Subscription(Document):
|
||||
if billing_info[0]["billing_interval"] != "Month":
|
||||
frappe.throw(_("Billing Interval in Subscription Plan must be Month to follow calendar months"))
|
||||
|
||||
def after_insert(self) -> None:
|
||||
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
|
||||
self.set_subscription_status()
|
||||
|
||||
def generate_invoice(
|
||||
self,
|
||||
from_date: Optional[Union[str, datetime.date]] = None,
|
||||
@ -344,7 +342,7 @@ class Subscription(Document):
|
||||
invoice.set_posting_time = 1
|
||||
invoice.posting_date = (
|
||||
self.current_invoice_start
|
||||
if self.generate_invoice_at_period_start
|
||||
if self.generate_invoice_at == "Beginning of the current subscription period"
|
||||
else self.current_invoice_end
|
||||
)
|
||||
|
||||
@ -438,7 +436,7 @@ class Subscription(Document):
|
||||
prorate_factor = get_prorata_factor(
|
||||
self.current_invoice_end,
|
||||
self.current_invoice_start,
|
||||
cint(self.generate_invoice_at_period_start),
|
||||
cint(self.generate_invoice_at == "Beginning of the current subscription period"),
|
||||
)
|
||||
|
||||
items = []
|
||||
@ -503,42 +501,45 @@ class Subscription(Document):
|
||||
return items
|
||||
|
||||
@frappe.whitelist()
|
||||
def process(self) -> bool:
|
||||
def process(self, posting_date: Optional["DateTimeLikeObject"] = None) -> bool:
|
||||
"""
|
||||
To be called by task periodically. It checks the subscription and takes appropriate action
|
||||
as need be. It calls either of these methods depending the `Subscription` status:
|
||||
1. `process_for_active`
|
||||
2. `process_for_past_due`
|
||||
"""
|
||||
if (
|
||||
not self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end)
|
||||
and self.can_generate_new_invoice()
|
||||
):
|
||||
if not self.is_current_invoice_generated(
|
||||
self.current_invoice_start, self.current_invoice_end
|
||||
) and self.can_generate_new_invoice(posting_date):
|
||||
self.generate_invoice()
|
||||
self.update_subscription_period(add_days(self.current_invoice_end, 1))
|
||||
|
||||
if self.cancel_at_period_end and (
|
||||
getdate(frappe.flags.current_date) >= getdate(self.current_invoice_end)
|
||||
or getdate(frappe.flags.current_date) >= getdate(self.end_date)
|
||||
getdate(posting_date) >= getdate(self.current_invoice_end)
|
||||
or getdate(posting_date) >= getdate(self.end_date)
|
||||
):
|
||||
self.cancel_subscription()
|
||||
|
||||
self.set_subscription_status()
|
||||
self.set_subscription_status(posting_date=posting_date)
|
||||
|
||||
self.save()
|
||||
|
||||
def can_generate_new_invoice(self) -> bool:
|
||||
def can_generate_new_invoice(self, posting_date: Optional["DateTimeLikeObject"] = None) -> bool:
|
||||
if self.cancelation_date:
|
||||
return False
|
||||
elif self.generate_invoice_at_period_start and (
|
||||
getdate(frappe.flags.current_date) == getdate(self.current_invoice_start)
|
||||
or self.is_new_subscription()
|
||||
|
||||
if self.has_outstanding_invoice() and not self.generate_new_invoices_past_due_date:
|
||||
return False
|
||||
|
||||
if self.generate_invoice_at == "Beginning of the current subscription period" and (
|
||||
getdate(posting_date) == getdate(self.current_invoice_start) or self.is_new_subscription()
|
||||
):
|
||||
return True
|
||||
elif getdate(frappe.flags.current_date) == getdate(self.current_invoice_end):
|
||||
if self.has_outstanding_invoice() and not self.generate_new_invoices_past_due_date:
|
||||
return False
|
||||
|
||||
elif self.generate_invoice_at == "Days before the current subscription period" and (
|
||||
getdate(posting_date) == getdate(add_days(self.current_invoice_start, -1 * self.number_of_days))
|
||||
):
|
||||
return True
|
||||
elif getdate(posting_date) == getdate(self.current_invoice_end):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@ -628,7 +629,10 @@ class Subscription(Document):
|
||||
frappe.throw(_("subscription is already cancelled."), InvoiceCancelled)
|
||||
|
||||
to_generate_invoice = (
|
||||
True if self.status == "Active" and not self.generate_invoice_at_period_start else False
|
||||
True
|
||||
if self.status == "Active"
|
||||
and not self.generate_invoice_at == "Beginning of the current subscription period"
|
||||
else False
|
||||
)
|
||||
self.status = "Cancelled"
|
||||
self.cancelation_date = nowdate()
|
||||
@ -639,7 +643,7 @@ class Subscription(Document):
|
||||
self.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def restart_subscription(self) -> None:
|
||||
def restart_subscription(self, posting_date: Optional["DateTimeLikeObject"] = None) -> None:
|
||||
"""
|
||||
This sets the subscription as active. The subscription will be made to be like a new
|
||||
subscription and the `Subscription` will lose all the history of generated invoices
|
||||
@ -650,7 +654,7 @@ class Subscription(Document):
|
||||
|
||||
self.status = "Active"
|
||||
self.cancelation_date = None
|
||||
self.update_subscription_period(frappe.flags.current_date or nowdate())
|
||||
self.update_subscription_period(posting_date or nowdate())
|
||||
self.save()
|
||||
|
||||
|
||||
@ -671,14 +675,21 @@ def get_prorata_factor(
|
||||
return diff / plan_days
|
||||
|
||||
|
||||
def process_all() -> None:
|
||||
def process_all(
|
||||
subscription: str | None, posting_date: Optional["DateTimeLikeObject"] = None
|
||||
) -> None:
|
||||
"""
|
||||
Task to updates the status of all `Subscription` apart from those that are cancelled
|
||||
"""
|
||||
for subscription in frappe.get_all("Subscription", {"status": ("!=", "Cancelled")}, pluck="name"):
|
||||
filters = {"status": ("!=", "Cancelled")}
|
||||
|
||||
if subscription:
|
||||
filters["name"] = subscription
|
||||
|
||||
for subscription in frappe.get_all("Subscription", filters, pluck="name"):
|
||||
try:
|
||||
subscription = frappe.get_doc("Subscription", subscription)
|
||||
subscription.process()
|
||||
subscription.process(posting_date)
|
||||
frappe.db.commit()
|
||||
except frappe.ValidationError:
|
||||
frappe.db.rollback()
|
||||
|
@ -8,6 +8,7 @@ from frappe.utils.data import (
|
||||
add_days,
|
||||
add_months,
|
||||
add_to_date,
|
||||
cint,
|
||||
date_diff,
|
||||
flt,
|
||||
get_date_str,
|
||||
@ -20,99 +21,16 @@ from erpnext.accounts.doctype.subscription.subscription import get_prorata_facto
|
||||
test_dependencies = ("UOM", "Item Group", "Item")
|
||||
|
||||
|
||||
def create_plan():
|
||||
if not frappe.db.exists("Subscription Plan", "_Test Plan Name"):
|
||||
plan = frappe.new_doc("Subscription Plan")
|
||||
plan.plan_name = "_Test Plan Name"
|
||||
plan.item = "_Test Non Stock Item"
|
||||
plan.price_determination = "Fixed Rate"
|
||||
plan.cost = 900
|
||||
plan.billing_interval = "Month"
|
||||
plan.billing_interval_count = 1
|
||||
plan.insert()
|
||||
|
||||
if not frappe.db.exists("Subscription Plan", "_Test Plan Name 2"):
|
||||
plan = frappe.new_doc("Subscription Plan")
|
||||
plan.plan_name = "_Test Plan Name 2"
|
||||
plan.item = "_Test Non Stock Item"
|
||||
plan.price_determination = "Fixed Rate"
|
||||
plan.cost = 1999
|
||||
plan.billing_interval = "Month"
|
||||
plan.billing_interval_count = 1
|
||||
plan.insert()
|
||||
|
||||
if not frappe.db.exists("Subscription Plan", "_Test Plan Name 3"):
|
||||
plan = frappe.new_doc("Subscription Plan")
|
||||
plan.plan_name = "_Test Plan Name 3"
|
||||
plan.item = "_Test Non Stock Item"
|
||||
plan.price_determination = "Fixed Rate"
|
||||
plan.cost = 1999
|
||||
plan.billing_interval = "Day"
|
||||
plan.billing_interval_count = 14
|
||||
plan.insert()
|
||||
|
||||
# Defined a quarterly Subscription Plan
|
||||
if not frappe.db.exists("Subscription Plan", "_Test Plan Name 4"):
|
||||
plan = frappe.new_doc("Subscription Plan")
|
||||
plan.plan_name = "_Test Plan Name 4"
|
||||
plan.item = "_Test Non Stock Item"
|
||||
plan.price_determination = "Monthly Rate"
|
||||
plan.cost = 20000
|
||||
plan.billing_interval = "Month"
|
||||
plan.billing_interval_count = 3
|
||||
plan.insert()
|
||||
|
||||
if not frappe.db.exists("Subscription Plan", "_Test Plan Multicurrency"):
|
||||
plan = frappe.new_doc("Subscription Plan")
|
||||
plan.plan_name = "_Test Plan Multicurrency"
|
||||
plan.item = "_Test Non Stock Item"
|
||||
plan.price_determination = "Fixed Rate"
|
||||
plan.cost = 50
|
||||
plan.currency = "USD"
|
||||
plan.billing_interval = "Month"
|
||||
plan.billing_interval_count = 1
|
||||
plan.insert()
|
||||
|
||||
|
||||
def create_parties():
|
||||
if not frappe.db.exists("Supplier", "_Test Supplier"):
|
||||
supplier = frappe.new_doc("Supplier")
|
||||
supplier.supplier_name = "_Test Supplier"
|
||||
supplier.supplier_group = "All Supplier Groups"
|
||||
supplier.insert()
|
||||
|
||||
if not frappe.db.exists("Customer", "_Test Subscription Customer"):
|
||||
customer = frappe.new_doc("Customer")
|
||||
customer.customer_name = "_Test Subscription Customer"
|
||||
customer.billing_currency = "USD"
|
||||
customer.append(
|
||||
"accounts", {"company": "_Test Company", "account": "_Test Receivable USD - _TC"}
|
||||
)
|
||||
customer.insert()
|
||||
|
||||
|
||||
def reset_settings():
|
||||
settings = frappe.get_single("Subscription Settings")
|
||||
settings.grace_period = 0
|
||||
settings.cancel_after_grace = 0
|
||||
settings.save()
|
||||
|
||||
|
||||
class TestSubscription(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_plan()
|
||||
make_plans()
|
||||
create_parties()
|
||||
reset_settings()
|
||||
|
||||
def test_create_subscription_with_trial_with_correct_period(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.trial_period_start = nowdate()
|
||||
subscription.trial_period_end = add_months(nowdate(), 1)
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
|
||||
subscription = create_subscription(
|
||||
trial_period_start=nowdate(), trial_period_end=add_months(nowdate(), 1)
|
||||
)
|
||||
self.assertEqual(subscription.trial_period_start, nowdate())
|
||||
self.assertEqual(subscription.trial_period_end, add_months(nowdate(), 1))
|
||||
self.assertEqual(
|
||||
@ -126,12 +44,7 @@ class TestSubscription(unittest.TestCase):
|
||||
self.assertEqual(subscription.status, "Trialling")
|
||||
|
||||
def test_create_subscription_without_trial_with_correct_period(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
|
||||
subscription = create_subscription()
|
||||
self.assertEqual(subscription.trial_period_start, None)
|
||||
self.assertEqual(subscription.trial_period_end, None)
|
||||
self.assertEqual(subscription.current_invoice_start, nowdate())
|
||||
@ -141,55 +54,28 @@ class TestSubscription(unittest.TestCase):
|
||||
self.assertEqual(subscription.status, "Active")
|
||||
|
||||
def test_create_subscription_trial_with_wrong_dates(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.trial_period_end = nowdate()
|
||||
subscription.trial_period_start = add_days(nowdate(), 30)
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, subscription.save)
|
||||
|
||||
def test_create_subscription_multi_with_different_billing_fails(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.trial_period_end = nowdate()
|
||||
subscription.trial_period_start = add_days(nowdate(), 30)
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.append("plans", {"plan": "_Test Plan Name 3", "qty": 1})
|
||||
|
||||
subscription = create_subscription(
|
||||
trial_period_start=add_days(nowdate(), 30), trial_period_end=nowdate(), do_not_save=True
|
||||
)
|
||||
self.assertRaises(frappe.ValidationError, subscription.save)
|
||||
|
||||
def test_invoice_is_generated_at_end_of_billing_period(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.start_date = "2018-01-01"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.insert()
|
||||
|
||||
subscription = create_subscription(start_date="2018-01-01")
|
||||
self.assertEqual(subscription.status, "Active")
|
||||
self.assertEqual(subscription.current_invoice_start, "2018-01-01")
|
||||
self.assertEqual(subscription.current_invoice_end, "2018-01-31")
|
||||
frappe.flags.current_date = "2018-01-31"
|
||||
subscription.process()
|
||||
|
||||
subscription.process(posting_date="2018-01-31")
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
self.assertEqual(subscription.current_invoice_start, "2018-02-01")
|
||||
self.assertEqual(subscription.current_invoice_end, "2018-02-28")
|
||||
self.assertEqual(subscription.status, "Unpaid")
|
||||
|
||||
def test_status_goes_back_to_active_after_invoice_is_paid(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.start_date = "2018-01-01"
|
||||
subscription.generate_invoice_at_period_start = True
|
||||
subscription.insert()
|
||||
frappe.flags.current_date = "2018-01-01"
|
||||
subscription.process() # generate first invoice
|
||||
subscription = create_subscription(
|
||||
start_date="2018-01-01", generate_invoice_at="Beginning of the current subscription period"
|
||||
)
|
||||
subscription.process(posting_date="2018-01-01") # generate first invoice
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
|
||||
# Status is unpaid as Days until Due is zero and grace period is Zero
|
||||
@ -213,18 +99,10 @@ class TestSubscription(unittest.TestCase):
|
||||
settings.cancel_after_grace = 1
|
||||
settings.save()
|
||||
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
# subscription.generate_invoice_at_period_start = True
|
||||
subscription.start_date = "2018-01-01"
|
||||
subscription.insert()
|
||||
|
||||
subscription = create_subscription(start_date="2018-01-01")
|
||||
self.assertEqual(subscription.status, "Active")
|
||||
|
||||
frappe.flags.current_date = "2018-01-31"
|
||||
subscription.process() # generate first invoice
|
||||
subscription.process(posting_date="2018-01-31") # generate first invoice
|
||||
# This should change status to Cancelled since grace period is 0
|
||||
# And is backdated subscription so subscription will be cancelled after processing
|
||||
self.assertEqual(subscription.status, "Cancelled")
|
||||
@ -235,13 +113,8 @@ class TestSubscription(unittest.TestCase):
|
||||
settings.cancel_after_grace = 0
|
||||
settings.save()
|
||||
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.start_date = "2018-01-01"
|
||||
subscription.insert()
|
||||
subscription.process() # generate first invoice
|
||||
subscription = create_subscription(start_date="2018-01-01")
|
||||
subscription.process(posting_date="2018-01-31") # generate first invoice
|
||||
|
||||
# Status is unpaid as Days until Due is zero and grace period is Zero
|
||||
self.assertEqual(subscription.status, "Unpaid")
|
||||
@ -251,21 +124,9 @@ class TestSubscription(unittest.TestCase):
|
||||
|
||||
def test_subscription_invoice_days_until_due(self):
|
||||
_date = add_months(nowdate(), -1)
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.days_until_due = 10
|
||||
subscription.start_date = _date
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.insert()
|
||||
subscription = create_subscription(start_date=_date, days_until_due=10)
|
||||
|
||||
frappe.flags.current_date = subscription.current_invoice_end
|
||||
|
||||
subscription.process() # generate first invoice
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
self.assertEqual(subscription.status, "Active")
|
||||
|
||||
frappe.flags.current_date = add_days(subscription.current_invoice_end, 3)
|
||||
subscription.process(posting_date=subscription.current_invoice_end) # generate first invoice
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
self.assertEqual(subscription.status, "Active")
|
||||
|
||||
@ -275,16 +136,9 @@ class TestSubscription(unittest.TestCase):
|
||||
settings.grace_period = 1000
|
||||
settings.save()
|
||||
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.start_date = add_days(nowdate(), -1000)
|
||||
subscription.insert()
|
||||
|
||||
frappe.flags.current_date = subscription.current_invoice_end
|
||||
subscription.process() # generate first invoice
|
||||
subscription = create_subscription(start_date=add_days(nowdate(), -1000))
|
||||
|
||||
subscription.process(posting_date=subscription.current_invoice_end) # generate first invoice
|
||||
self.assertEqual(subscription.status, "Past Due Date")
|
||||
|
||||
subscription.process()
|
||||
@ -301,12 +155,7 @@ class TestSubscription(unittest.TestCase):
|
||||
settings.save()
|
||||
|
||||
def test_subscription_remains_active_during_invoice_period(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
subscription.process() # no changes expected
|
||||
subscription = create_subscription() # no changes expected
|
||||
|
||||
self.assertEqual(subscription.status, "Active")
|
||||
self.assertEqual(subscription.current_invoice_start, nowdate())
|
||||
@ -325,12 +174,8 @@ class TestSubscription(unittest.TestCase):
|
||||
self.assertEqual(subscription.current_invoice_end, add_to_date(nowdate(), months=1, days=-1))
|
||||
self.assertEqual(len(subscription.invoices), 0)
|
||||
|
||||
def test_subscription_cancelation(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
def test_subscription_cancellation(self):
|
||||
subscription = create_subscription()
|
||||
subscription.cancel_subscription()
|
||||
|
||||
self.assertEqual(subscription.status, "Cancelled")
|
||||
@ -341,11 +186,7 @@ class TestSubscription(unittest.TestCase):
|
||||
settings.prorate = 1
|
||||
settings.save()
|
||||
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
subscription = create_subscription()
|
||||
|
||||
self.assertEqual(subscription.status, "Active")
|
||||
|
||||
@ -365,7 +206,7 @@ class TestSubscription(unittest.TestCase):
|
||||
get_prorata_factor(
|
||||
subscription.current_invoice_end,
|
||||
subscription.current_invoice_start,
|
||||
subscription.generate_invoice_at_period_start,
|
||||
cint(subscription.generate_invoice_at == "Beginning of the current subscription period"),
|
||||
),
|
||||
2,
|
||||
),
|
||||
@ -383,11 +224,7 @@ class TestSubscription(unittest.TestCase):
|
||||
settings.prorate = 0
|
||||
settings.save()
|
||||
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
subscription = create_subscription()
|
||||
subscription.cancel_subscription()
|
||||
invoice = subscription.get_current_invoice()
|
||||
|
||||
@ -402,11 +239,7 @@ class TestSubscription(unittest.TestCase):
|
||||
settings.prorate = 1
|
||||
settings.save()
|
||||
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
subscription = create_subscription()
|
||||
subscription.cancel_subscription()
|
||||
|
||||
invoice = subscription.get_current_invoice()
|
||||
@ -421,18 +254,13 @@ class TestSubscription(unittest.TestCase):
|
||||
settings.prorate = to_prorate
|
||||
settings.save()
|
||||
|
||||
def test_subcription_cancellation_and_process(self):
|
||||
def test_subscription_cancellation_and_process(self):
|
||||
settings = frappe.get_single("Subscription Settings")
|
||||
default_grace_period_action = settings.cancel_after_grace
|
||||
settings.cancel_after_grace = 1
|
||||
settings.save()
|
||||
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.start_date = "2018-01-01"
|
||||
subscription.insert()
|
||||
subscription = create_subscription(start_date="2018-01-01")
|
||||
subscription.process() # generate first invoice
|
||||
|
||||
# Generate an invoice for the cancelled period
|
||||
@ -458,14 +286,8 @@ class TestSubscription(unittest.TestCase):
|
||||
settings.cancel_after_grace = 0
|
||||
settings.save()
|
||||
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.start_date = "2018-01-01"
|
||||
subscription.insert()
|
||||
frappe.flags.current_date = "2018-01-31"
|
||||
subscription.process() # generate first invoice
|
||||
subscription = create_subscription(start_date="2018-01-01")
|
||||
subscription.process(posting_date="2018-01-31") # generate first invoice
|
||||
|
||||
# Status is unpaid as Days until Due is zero and grace period is Zero
|
||||
self.assertEqual(subscription.status, "Unpaid")
|
||||
@ -494,17 +316,10 @@ class TestSubscription(unittest.TestCase):
|
||||
settings.cancel_after_grace = 0
|
||||
settings.save()
|
||||
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.start_date = "2018-01-01"
|
||||
subscription.generate_invoice_at_period_start = True
|
||||
subscription.insert()
|
||||
|
||||
frappe.flags.current_date = subscription.current_invoice_start
|
||||
|
||||
subscription.process() # generate first invoice
|
||||
subscription = create_subscription(
|
||||
start_date="2018-01-01", generate_invoice_at="Beginning of the current subscription period"
|
||||
)
|
||||
subscription.process(subscription.current_invoice_start) # generate first invoice
|
||||
# This should change status to Unpaid since grace period is 0
|
||||
self.assertEqual(subscription.status, "Unpaid")
|
||||
|
||||
@ -516,29 +331,18 @@ class TestSubscription(unittest.TestCase):
|
||||
self.assertEqual(subscription.status, "Active")
|
||||
|
||||
# A new invoice is generated
|
||||
frappe.flags.current_date = subscription.current_invoice_start
|
||||
subscription.process()
|
||||
subscription.process(posting_date=subscription.current_invoice_start)
|
||||
self.assertEqual(subscription.status, "Unpaid")
|
||||
|
||||
settings.cancel_after_grace = default_grace_period_action
|
||||
settings.save()
|
||||
|
||||
def test_restart_active_subscription(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
|
||||
subscription = create_subscription()
|
||||
self.assertRaises(frappe.ValidationError, subscription.restart_subscription)
|
||||
|
||||
def test_subscription_invoice_discount_percentage(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.additional_discount_percentage = 10
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
subscription = create_subscription(additional_discount_percentage=10)
|
||||
subscription.cancel_subscription()
|
||||
|
||||
invoice = subscription.get_current_invoice()
|
||||
@ -547,12 +351,7 @@ class TestSubscription(unittest.TestCase):
|
||||
self.assertEqual(invoice.apply_discount_on, "Grand Total")
|
||||
|
||||
def test_subscription_invoice_discount_amount(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.additional_discount_amount = 11
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
subscription = create_subscription(additional_discount_amount=11)
|
||||
subscription.cancel_subscription()
|
||||
|
||||
invoice = subscription.get_current_invoice()
|
||||
@ -563,18 +362,13 @@ class TestSubscription(unittest.TestCase):
|
||||
def test_prepaid_subscriptions(self):
|
||||
# Create a non pre-billed subscription, processing should not create
|
||||
# invoices.
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
subscription = create_subscription()
|
||||
subscription.process()
|
||||
|
||||
self.assertEqual(len(subscription.invoices), 0)
|
||||
|
||||
# Change the subscription type to prebilled and process it.
|
||||
# Prepaid invoice should be generated
|
||||
subscription.generate_invoice_at_period_start = True
|
||||
subscription.generate_invoice_at = "Beginning of the current subscription period"
|
||||
subscription.save()
|
||||
subscription.process()
|
||||
|
||||
@ -586,12 +380,9 @@ class TestSubscription(unittest.TestCase):
|
||||
settings.prorate = 1
|
||||
settings.save()
|
||||
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Customer"
|
||||
subscription.generate_invoice_at_period_start = True
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.save()
|
||||
subscription = create_subscription(
|
||||
generate_invoice_at="Beginning of the current subscription period"
|
||||
)
|
||||
subscription.process()
|
||||
subscription.cancel_subscription()
|
||||
|
||||
@ -609,9 +400,10 @@ class TestSubscription(unittest.TestCase):
|
||||
|
||||
def test_subscription_with_follow_calendar_months(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.company = "_Test Company"
|
||||
subscription.party_type = "Supplier"
|
||||
subscription.party = "_Test Supplier"
|
||||
subscription.generate_invoice_at_period_start = 1
|
||||
subscription.generate_invoice_at = "Beginning of the current subscription period"
|
||||
subscription.follow_calendar_months = 1
|
||||
|
||||
# select subscription start date as "2018-01-15"
|
||||
@ -625,39 +417,33 @@ class TestSubscription(unittest.TestCase):
|
||||
self.assertEqual(get_date_str(subscription.current_invoice_end), "2018-03-31")
|
||||
|
||||
def test_subscription_generate_invoice_past_due(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Supplier"
|
||||
subscription.party = "_Test Supplier"
|
||||
subscription.generate_invoice_at_period_start = 1
|
||||
subscription.generate_new_invoices_past_due_date = 1
|
||||
# select subscription start date as "2018-01-15"
|
||||
subscription.start_date = "2018-01-01"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name 4", "qty": 1})
|
||||
subscription.save()
|
||||
subscription = create_subscription(
|
||||
start_date="2018-01-01",
|
||||
party_type="Supplier",
|
||||
party="_Test Supplier",
|
||||
generate_invoice_at="Beginning of the current subscription period",
|
||||
generate_new_invoices_past_due_date=1,
|
||||
plans=[{"plan": "_Test Plan Name 4", "qty": 1}],
|
||||
)
|
||||
|
||||
frappe.flags.current_date = "2018-01-01"
|
||||
# Process subscription and create first invoice
|
||||
# Subscription status will be unpaid since due date has already passed
|
||||
subscription.process()
|
||||
subscription.process(posting_date="2018-01-01")
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
self.assertEqual(subscription.status, "Unpaid")
|
||||
|
||||
# Now the Subscription is unpaid
|
||||
# Even then new invoice should be created as we have enabled `generate_new_invoices_past_due_date` in
|
||||
# subscription and the interval between the subscriptions is 3 months
|
||||
frappe.flags.current_date = "2018-04-01"
|
||||
subscription.process()
|
||||
subscription.process(posting_date="2018-04-01")
|
||||
self.assertEqual(len(subscription.invoices), 2)
|
||||
|
||||
def test_subscription_without_generate_invoice_past_due(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Supplier"
|
||||
subscription.party = "_Test Supplier"
|
||||
subscription.generate_invoice_at_period_start = 1
|
||||
# select subscription start date as "2018-01-15"
|
||||
subscription.start_date = "2018-01-01"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name 4", "qty": 1})
|
||||
subscription.save()
|
||||
subscription = create_subscription(
|
||||
start_date="2018-01-01",
|
||||
generate_invoice_at="Beginning of the current subscription period",
|
||||
plans=[{"plan": "_Test Plan Name 4", "qty": 1}],
|
||||
)
|
||||
|
||||
# Process subscription and create first invoice
|
||||
# Subscription status will be unpaid since due date has already passed
|
||||
@ -668,16 +454,13 @@ class TestSubscription(unittest.TestCase):
|
||||
subscription.process()
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
|
||||
def test_multicurrency_subscription(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Subscription Customer"
|
||||
subscription.generate_invoice_at_period_start = 1
|
||||
subscription.company = "_Test Company"
|
||||
# select subscription start date as "2018-01-15"
|
||||
subscription.start_date = "2018-01-01"
|
||||
subscription.append("plans", {"plan": "_Test Plan Multicurrency", "qty": 1})
|
||||
subscription.save()
|
||||
def test_multi_currency_subscription(self):
|
||||
subscription = create_subscription(
|
||||
start_date="2018-01-01",
|
||||
generate_invoice_at="Beginning of the current subscription period",
|
||||
plans=[{"plan": "_Test Plan Multicurrency", "qty": 1}],
|
||||
party="_Test Subscription Customer",
|
||||
)
|
||||
|
||||
subscription.process()
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
@ -689,42 +472,135 @@ class TestSubscription(unittest.TestCase):
|
||||
|
||||
def test_subscription_recovery(self):
|
||||
"""Test if Subscription recovers when start/end date run out of sync with created invoices."""
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Customer"
|
||||
subscription.party = "_Test Subscription Customer"
|
||||
subscription.company = "_Test Company"
|
||||
subscription.start_date = "2021-12-01"
|
||||
subscription.generate_new_invoices_past_due_date = 1
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
subscription.submit_invoice = 0
|
||||
subscription.save()
|
||||
subscription = create_subscription(
|
||||
start_date="2021-01-01",
|
||||
submit_invoice=0,
|
||||
generate_new_invoices_past_due_date=1,
|
||||
party="_Test Subscription Customer",
|
||||
)
|
||||
|
||||
# create invoices for the first two moths
|
||||
frappe.flags.current_date = "2021-12-31"
|
||||
subscription.process()
|
||||
subscription.process(posting_date="2021-01-31")
|
||||
|
||||
frappe.flags.current_date = "2022-01-31"
|
||||
subscription.process()
|
||||
subscription.process(posting_date="2021-02-28")
|
||||
|
||||
self.assertEqual(len(subscription.invoices), 2)
|
||||
self.assertEqual(
|
||||
getdate(frappe.db.get_value("Sales Invoice", subscription.invoices[0].name, "from_date")),
|
||||
getdate("2021-12-01"),
|
||||
getdate("2021-01-01"),
|
||||
)
|
||||
self.assertEqual(
|
||||
getdate(frappe.db.get_value("Sales Invoice", subscription.invoices[1].name, "from_date")),
|
||||
getdate("2022-01-01"),
|
||||
getdate("2021-02-01"),
|
||||
)
|
||||
|
||||
# recreate most recent invoice
|
||||
subscription.process()
|
||||
subscription.process(posting_date="2022-01-31")
|
||||
|
||||
self.assertEqual(len(subscription.invoices), 2)
|
||||
self.assertEqual(
|
||||
getdate(frappe.db.get_value("Sales Invoice", subscription.invoices[0].name, "from_date")),
|
||||
getdate("2021-12-01"),
|
||||
getdate("2021-01-01"),
|
||||
)
|
||||
self.assertEqual(
|
||||
getdate(frappe.db.get_value("Sales Invoice", subscription.invoices[1].name, "from_date")),
|
||||
getdate("2022-01-01"),
|
||||
getdate("2021-02-01"),
|
||||
)
|
||||
|
||||
def test_subscription_invoice_generation_before_days(self):
|
||||
subscription = create_subscription(
|
||||
start_date="2023-01-01",
|
||||
generate_invoice_at="Days before the current subscription period",
|
||||
number_of_days=10,
|
||||
generate_new_invoices_past_due_date=1,
|
||||
)
|
||||
|
||||
subscription.process(posting_date="2022-12-22")
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
|
||||
subscription.process(posting_date="2023-01-22")
|
||||
self.assertEqual(len(subscription.invoices), 2)
|
||||
|
||||
|
||||
def make_plans():
|
||||
create_plan(plan_name="_Test Plan Name", cost=900)
|
||||
create_plan(plan_name="_Test Plan Name 2", cost=1999)
|
||||
create_plan(
|
||||
plan_name="_Test Plan Name 3", cost=1999, billing_interval="Day", billing_interval_count=14
|
||||
)
|
||||
create_plan(
|
||||
plan_name="_Test Plan Name 4", cost=20000, billing_interval="Month", billing_interval_count=3
|
||||
)
|
||||
create_plan(
|
||||
plan_name="_Test Plan Multicurrency", cost=50, billing_interval="Month", currency="USD"
|
||||
)
|
||||
|
||||
|
||||
def create_plan(**kwargs):
|
||||
if not frappe.db.exists("Subscription Plan", kwargs.get("plan_name")):
|
||||
plan = frappe.new_doc("Subscription Plan")
|
||||
plan.plan_name = kwargs.get("plan_name") or "_Test Plan Name"
|
||||
plan.item = kwargs.get("item") or "_Test Non Stock Item"
|
||||
plan.price_determination = kwargs.get("price_determination") or "Fixed Rate"
|
||||
plan.cost = kwargs.get("cost") or 1000
|
||||
plan.billing_interval = kwargs.get("billing_interval") or "Month"
|
||||
plan.billing_interval_count = kwargs.get("billing_interval_count") or 1
|
||||
plan.currency = kwargs.get("currency")
|
||||
plan.insert()
|
||||
|
||||
|
||||
def create_parties():
|
||||
if not frappe.db.exists("Supplier", "_Test Supplier"):
|
||||
supplier = frappe.new_doc("Supplier")
|
||||
supplier.supplier_name = "_Test Supplier"
|
||||
supplier.supplier_group = "All Supplier Groups"
|
||||
supplier.insert()
|
||||
|
||||
if not frappe.db.exists("Customer", "_Test Subscription Customer"):
|
||||
customer = frappe.new_doc("Customer")
|
||||
customer.customer_name = "_Test Subscription Customer"
|
||||
customer.billing_currency = "USD"
|
||||
customer.append(
|
||||
"accounts", {"company": "_Test Company", "account": "_Test Receivable USD - _TC"}
|
||||
)
|
||||
customer.insert()
|
||||
|
||||
|
||||
def reset_settings():
|
||||
settings = frappe.get_single("Subscription Settings")
|
||||
settings.grace_period = 0
|
||||
settings.cancel_after_grace = 0
|
||||
settings.save()
|
||||
|
||||
|
||||
def create_subscription(**kwargs):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = (kwargs.get("party_type") or "Customer",)
|
||||
subscription.company = kwargs.get("company") or "_Test Company"
|
||||
subscription.party = kwargs.get("party") or "_Test Customer"
|
||||
subscription.trial_period_start = kwargs.get("trial_period_start")
|
||||
subscription.trial_period_end = kwargs.get("trial_period_end")
|
||||
subscription.start_date = kwargs.get("start_date")
|
||||
subscription.generate_invoice_at = kwargs.get("generate_invoice_at")
|
||||
subscription.additional_discount_percentage = kwargs.get("additional_discount_percentage")
|
||||
subscription.additional_discount_amount = kwargs.get("additional_discount_amount")
|
||||
subscription.follow_calendar_months = kwargs.get("follow_calendar_months")
|
||||
subscription.generate_new_invoices_past_due_date = kwargs.get(
|
||||
"generate_new_invoices_past_due_date"
|
||||
)
|
||||
subscription.submit_invoice = kwargs.get("submit_invoice")
|
||||
subscription.days_until_due = kwargs.get("days_until_due")
|
||||
subscription.number_of_days = kwargs.get("number_of_days")
|
||||
|
||||
if not kwargs.get("plans"):
|
||||
subscription.append("plans", {"plan": "_Test Plan Name", "qty": 1})
|
||||
else:
|
||||
for plan in kwargs.get("plans"):
|
||||
subscription.append("plans", plan)
|
||||
|
||||
if kwargs.get("do_not_save"):
|
||||
return subscription
|
||||
|
||||
subscription.save()
|
||||
|
||||
return subscription
|
||||
|
@ -430,7 +430,7 @@ scheduler_events = {
|
||||
"erpnext.projects.doctype.project.project.collect_project_status",
|
||||
],
|
||||
"hourly_long": [
|
||||
"erpnext.accounts.doctype.subscription.subscription.process_all",
|
||||
"erpnext.accounts.doctype.process_subscription.process_subscription.create_subscription_process",
|
||||
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
||||
"erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
|
||||
],
|
||||
|
@ -342,6 +342,7 @@ execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for
|
||||
erpnext.patches.v15_0.correct_asset_value_if_je_with_workflow
|
||||
erpnext.patches.v15_0.delete_woocommerce_settings_doctype
|
||||
erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults
|
||||
erpnext.patches.v14_0.update_invoicing_period_in_subscription
|
||||
execute:frappe.delete_doc("Page", "welcome-to-erpnext")
|
||||
# below migration patch should always run last
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
|
@ -0,0 +1,8 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
subscription = frappe.qb.DocType("Subscription")
|
||||
frappe.qb.update(subscription).set(
|
||||
subscription.generate_invoice_at, "Beginning of the currency subscription period"
|
||||
).where(subscription.generate_invoice_at_period_start == 1).run()
|
Loading…
x
Reference in New Issue
Block a user