diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json index 39ad0d4a80..aa29907d06 100644 --- a/erpnext/accounts/doctype/subscription/subscription.json +++ b/erpnext/accounts/doctype/subscription/subscription.json @@ -437,6 +437,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "generate_invoice_at_period_start", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Generate Invoice At Beginning Of Period", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -814,7 +846,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 16:15:44.533482", + "modified": "2018-10-04 10:29:03.338893", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription", diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index fe39161960..7fb6b7a096 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -321,6 +321,23 @@ class Subscription(Document): self.save() + @property + def is_postpaid_to_invoice(self): + return getdate(nowdate()) > getdate(self.current_invoice_end) or \ + (getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and \ + not self.has_outstanding_invoice() + + @property + def is_prepaid_to_invoice(self): + if not self.generate_invoice_at_period_start: + return False + + if self.is_new_subscription(): + return True + + # Check invoice dates and make sure it doesn't have outstanding invoices + return getdate(nowdate()) >= getdate(self.current_invoice_start) and not self.has_outstanding_invoice() + def process_for_active(self): """ Called by `process` if the status of the `Subscription` is 'Active'. @@ -330,7 +347,7 @@ class Subscription(Document): 2. Change the `Subscription` status to 'Past Due Date' 3. Change the `Subscription` status to 'Cancelled' """ - if getdate(nowdate()) > getdate(self.current_invoice_end) or (getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and not self.has_outstanding_invoice(): + if self.is_postpaid_to_invoice or self.is_prepaid_to_invoice: self.generate_invoice() if self.current_invoice_is_past_due(): self.status = 'Past Due Date' @@ -338,7 +355,7 @@ class Subscription(Document): if self.current_invoice_is_past_due() and getdate(nowdate()) > getdate(self.current_invoice_end): self.status = 'Past Due Date' - if self.cancel_at_period_end and getdate(nowdate()) > self.current_invoice_end: + if self.cancel_at_period_end and getdate(nowdate()) > getdate(self.current_invoice_end): self.cancel_subscription_at_period_end() def cancel_subscription_at_period_end(self): diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index c42b8e824b..a5285ea16a 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -500,3 +500,51 @@ class TestSubscription(unittest.TestCase): self.assertEqual(invoice.apply_discount_on, 'Grand Total') subscription.delete() + + def test_prepaid_subscriptions(self): + # Create a non pre-billed subscription, processing should not create + # invoices. + subscription = frappe.new_doc('Subscription') + subscription.subscriber = '_Test Customer' + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) + subscription.save() + 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.save() + subscription.process() + + self.assertEqual(len(subscription.invoices), 1) + + def test_prepaid_subscriptions_with_prorate_true(self): + settings = frappe.get_single('Subscription Settings') + to_prorate = settings.prorate + settings.prorate = 1 + settings.save() + + subscription = frappe.new_doc('Subscription') + subscription.subscriber = '_Test Customer' + subscription.generate_invoice_at_period_start = True + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) + subscription.save() + subscription.cancel_subscription() + + self.assertEqual(len(subscription.invoices), 1) + + current_inv = subscription.get_current_invoice() + self.assertEqual(current_inv.status, "Unpaid") + + diff = flt(date_diff(nowdate(), subscription.current_invoice_start) + 1) + plan_days = flt(date_diff(subscription.current_invoice_end, subscription.current_invoice_start) + 1) + prorate_factor = flt(diff / plan_days) + + self.assertEqual(flt(current_inv.grand_total, 2), flt(prorate_factor * 900, 2)) + + settings.prorate = to_prorate + settings.save() + + subscription.delete()