feat: Add provision for prebilled subscription invoices
(cherry picked from commit db33e6304d3167d0a2c0c9cee4a9405f5833120e)
diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json
index 39ad0d4..aa29907 100644
--- a/erpnext/accounts/doctype/subscription/subscription.json
+++ b/erpnext/accounts/doctype/subscription/subscription.json
@@ -440,6 +440,38 @@
{
"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,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 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 fe39161..7fb6b7a 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -321,6 +321,23 @@
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 @@
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 @@
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 c42b8e8..a5285ea 100644
--- a/erpnext/accounts/doctype/subscription/test_subscription.py
+++ b/erpnext/accounts/doctype/subscription/test_subscription.py
@@ -500,3 +500,51 @@
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()