From 0f03b19109ec0c47243574f8a976d55f638ce339 Mon Sep 17 00:00:00 2001 From: hrwx Date: Mon, 4 Oct 2021 18:48:48 +0100 Subject: [PATCH 1/5] fix: reorder updation of end date --- .../doctype/subscription/subscription.py | 6 ++- .../doctype/subscription/test_subscription.py | 39 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 8171b3b019..092002fb15 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -542,8 +542,7 @@ class Subscription(Document): else: self.set_status_grace_period() - if getdate() > getdate(self.current_invoice_end): - self.update_subscription_period(add_days(self.current_invoice_end, 1)) + update_subscription = True if getdate() > getdate(self.current_invoice_end) else False # Generate invoices periodically even if current invoice are unpaid if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() @@ -551,6 +550,9 @@ class Subscription(Document): prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) + if update_subscription: + self.update_subscription_period(add_days(self.current_invoice_end, 1)) + @staticmethod def is_paid(invoice): """ diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index e2cf4d5a44..ec8b257455 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -20,6 +20,43 @@ from erpnext.accounts.doctype.subscription.subscription import get_prorata_facto def create_plan(): + + if not frappe.db.exists('UOM', {"name": '_Test UOM'}): + frappe.get_doc({ + "doctype": "UOM", + "uom_name": "_Test UOM" + }).insert() + + if not frappe.db.exists('Item Group', {"name": '_Test Item Group Desktops'}): + frappe.get_doc({ + "doctype": "Item Group", + "is_group": 0, + "item_group_name": "_Test Item Group Desktops", + "parent_item_group": "All Item Groups" + }).insert() + + if not frappe.db.exists('Item', {"item_name": '_Test Non Stock Item'}): + frappe.get_doc({ + "description": "_Test Non Stock Item 7", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 0, + "is_sub_contracted_item": 0, + "item_code": "_Test Non Stock Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Non Stock Item", + "stock_uom": "_Test UOM", + "gst_hsn_code": "999800", + "item_defaults": [{ + "company": "_Test Company", + "default_warehouse": "Finished Goods - _TC", + "expense_account": "Gegenkonto zu Konto 9260 - 9268 - _TC", + "income_account": "Kurzfristige Rückstellungen - _TC" + }] + }).insert() + if not frappe.db.exists('Subscription Plan', '_Test Plan Name'): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name' @@ -619,7 +656,7 @@ class TestSubscription(unittest.TestCase): # subscription subscription.process() - self.assertEqual(len(subscription.invoices), 2) + self.assertEqual(len(subscription.invoices), 1) def test_subscription_without_generate_invoice_past_due(self): subscription = frappe.new_doc('Subscription') From fc375c5bdebe187e63a2d20892be049966741ed0 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 6 Oct 2021 18:04:54 +0530 Subject: [PATCH 2/5] fix: remove newline --- erpnext/accounts/doctype/subscription/test_subscription.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index ec8b257455..3a96d849ff 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -20,7 +20,6 @@ from erpnext.accounts.doctype.subscription.subscription import get_prorata_facto def create_plan(): - if not frappe.db.exists('UOM', {"name": '_Test UOM'}): frappe.get_doc({ "doctype": "UOM", From 656015d99d8d98ef614abef45914b5088c866476 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 6 Oct 2021 18:35:48 +0530 Subject: [PATCH 3/5] test: use `test_dependencies` instead of duplication --- .../doctype/subscription/test_subscription.py | 38 +------------------ 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 3a96d849ff..a01363be6e 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -18,44 +18,9 @@ from frappe.utils.data import ( from erpnext.accounts.doctype.subscription.subscription import get_prorata_factor +test_dependencies = ("UOM", "Item Group", "Item") def create_plan(): - if not frappe.db.exists('UOM', {"name": '_Test UOM'}): - frappe.get_doc({ - "doctype": "UOM", - "uom_name": "_Test UOM" - }).insert() - - if not frappe.db.exists('Item Group', {"name": '_Test Item Group Desktops'}): - frappe.get_doc({ - "doctype": "Item Group", - "is_group": 0, - "item_group_name": "_Test Item Group Desktops", - "parent_item_group": "All Item Groups" - }).insert() - - if not frappe.db.exists('Item', {"item_name": '_Test Non Stock Item'}): - frappe.get_doc({ - "description": "_Test Non Stock Item 7", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 0, - "is_sub_contracted_item": 0, - "item_code": "_Test Non Stock Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Non Stock Item", - "stock_uom": "_Test UOM", - "gst_hsn_code": "999800", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "Finished Goods - _TC", - "expense_account": "Gegenkonto zu Konto 9260 - 9268 - _TC", - "income_account": "Kurzfristige Rückstellungen - _TC" - }] - }).insert() - if not frappe.db.exists('Subscription Plan', '_Test Plan Name'): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name' @@ -104,7 +69,6 @@ def create_plan(): supplier.insert() class TestSubscription(unittest.TestCase): - def setUp(self): create_plan() From ae657c7e4ee7510705513948c329968e9330e24f Mon Sep 17 00:00:00 2001 From: hrwx Date: Wed, 6 Oct 2021 16:49:19 +0100 Subject: [PATCH 4/5] fix: create past invoices --- .../doctype/subscription/subscription.py | 81 +++++++++++-------- .../doctype/subscription/test_subscription.py | 2 +- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 092002fb15..01a5363697 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -33,7 +33,7 @@ class Subscription(Document): # update start just before the subscription doc is created self.update_subscription_period(self.start_date) - def update_subscription_period(self, date=None): + def update_subscription_period(self, date=None, return_date=False): """ Subscription period is the period to be billed. This method updates the beginning of the billing period and end of the billing period. @@ -41,28 +41,41 @@ class Subscription(Document): The beginning of the billing period is represented in the doctype as `current_invoice_start` and the end of the billing period is represented as `current_invoice_end`. - """ - self.set_current_invoice_start(date) - self.set_current_invoice_end() - def set_current_invoice_start(self, date=None): + If return_date is True, it wont update the start and end dates. + This is implemented to get the dates to check if is_current_invoice_generated """ - This sets the date of the beginning of the current billing period. + _current_invoice_start = self.get_current_invoice_start(date) + _current_invoice_end = self.get_current_invoice_end(_current_invoice_start) + + if return_date: + return _current_invoice_start, _current_invoice_end + + self.current_invoice_start = _current_invoice_start + self.current_invoice_end = _current_invoice_end + + def get_current_invoice_start(self, date=None): + """ + This returns the date of the beginning of the current billing period. If the `date` parameter is not given , it will be automatically set as today's date. """ - if self.is_new_subscription() and self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date): - self.current_invoice_start = add_days(self.trial_period_end, 1) - elif self.trial_period_start and self.is_trialling(): - self.current_invoice_start = self.trial_period_start - elif date: - self.current_invoice_start = date - else: - self.current_invoice_start = nowdate() + _current_invoice_start = None - def set_current_invoice_end(self): + if self.is_new_subscription() and self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date): + _current_invoice_start = add_days(self.trial_period_end, 1) + elif self.trial_period_start and self.is_trialling(): + _current_invoice_start = self.trial_period_start + elif date: + _current_invoice_start = date + else: + _current_invoice_start = nowdate() + + return _current_invoice_start + + def get_current_invoice_end(self, date=None): """ - This sets the date of the end of the current billing period. + This returns the date of the end of the current billing period. If the subscription is in trial period, it will be set as the end of the trial period. @@ -71,44 +84,47 @@ class Subscription(Document): current billing period where `x` is the billing interval from the `Subscription Plan` in the `Subscription`. """ - if self.is_trialling() and getdate(self.current_invoice_start) < getdate(self.trial_period_end): - self.current_invoice_end = self.trial_period_end + _current_invoice_end = None + + if self.is_trialling() and getdate(date) < getdate(self.trial_period_end): + _current_invoice_end = self.trial_period_end else: billing_cycle_info = self.get_billing_cycle_data() if billing_cycle_info: - if self.is_new_subscription() and getdate(self.start_date) < getdate(self.current_invoice_start): - self.current_invoice_end = add_to_date(self.start_date, **billing_cycle_info) + if self.is_new_subscription() and getdate(self.start_date) < getdate(date): + _current_invoice_end = add_to_date(self.start_date, **billing_cycle_info) # For cases where trial period is for an entire billing interval - if getdate(self.current_invoice_end) < getdate(self.current_invoice_start): - self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info) + if getdate(self.current_invoice_end) < getdate(date): + _current_invoice_end = add_to_date(date, **billing_cycle_info) else: - self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info) + _current_invoice_end = add_to_date(date, **billing_cycle_info) else: - self.current_invoice_end = get_last_day(self.current_invoice_start) + _current_invoice_end = get_last_day(date) if self.follow_calendar_months: billing_info = self.get_billing_cycle_and_interval() billing_interval_count = billing_info[0]['billing_interval_count'] calendar_months = get_calendar_months(billing_interval_count) calendar_month = 0 - current_invoice_end_month = getdate(self.current_invoice_end).month - current_invoice_end_year = getdate(self.current_invoice_end).year + current_invoice_end_month = getdate(_current_invoice_end).month + current_invoice_end_year = getdate(_current_invoice_end).year for month in calendar_months: if month <= current_invoice_end_month: calendar_month = month if cint(calendar_month - billing_interval_count) <= 0 and \ - getdate(self.current_invoice_start).month != 1: + getdate(date).month != 1: calendar_month = 12 current_invoice_end_year -= 1 - self.current_invoice_end = get_last_day(cstr(current_invoice_end_year) + '-' \ - + cstr(calendar_month) + '-01') + _current_invoice_end = get_last_day(cstr(current_invoice_end_year) + '-' + cstr(calendar_month) + '-01') - if self.end_date and getdate(self.current_invoice_end) > getdate(self.end_date): - self.current_invoice_end = self.end_date + if self.end_date and getdate(_current_invoice_end) > getdate(self.end_date): + _current_invoice_end = self.end_date + + return _current_invoice_end @staticmethod def validate_plans_billing_cycle(billing_cycle_data): @@ -488,8 +504,9 @@ class Subscription(Document): def is_current_invoice_generated(self): invoice = self.get_current_invoice() + _current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True) - if invoice and getdate(self.current_invoice_start) <= getdate(invoice.posting_date) <= getdate(self.current_invoice_end): + if invoice and getdate(_current_start_date) <= getdate(invoice.posting_date) <= getdate(_current_end_date): return True return False diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index a01363be6e..0f7a0a86a4 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -619,7 +619,7 @@ class TestSubscription(unittest.TestCase): # subscription subscription.process() - self.assertEqual(len(subscription.invoices), 1) + self.assertEqual(len(subscription.invoices), 2) def test_subscription_without_generate_invoice_past_due(self): subscription = frappe.new_doc('Subscription') From 44306bd0e593887e1287b0558da0e47e2c7be40a Mon Sep 17 00:00:00 2001 From: Himanshu Date: Sat, 9 Oct 2021 09:37:54 +0100 Subject: [PATCH 5/5] Update subscription.py --- erpnext/accounts/doctype/subscription/subscription.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 01a5363697..de9550233f 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -559,15 +559,13 @@ class Subscription(Document): else: self.set_status_grace_period() - update_subscription = True if getdate() > getdate(self.current_invoice_end) else False - # Generate invoices periodically even if current invoice are unpaid if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) - if update_subscription: + if getdate() > getdate(self.current_invoice_end): self.update_subscription_period(add_days(self.current_invoice_end, 1)) @staticmethod