add ability to cancel, restart and refresh subscription
diff --git a/erpnext/accounts/doctype/subscriptions/subscriptions.js b/erpnext/accounts/doctype/subscriptions/subscriptions.js
index 14eb9b6..ae572d2 100644
--- a/erpnext/accounts/doctype/subscriptions/subscriptions.js
+++ b/erpnext/accounts/doctype/subscriptions/subscriptions.js
@@ -3,6 +3,75 @@
frappe.ui.form.on('Subscriptions', {
refresh: function(frm) {
+ if(!frm.is_new()){
+ if(frm.doc.status !== 'Canceled'){
+ frm.add_custom_button(
+ __('Cancel Subscription'),
+ () => frm.events.cancel_this_subscription(frm)
+ );
+ frm.add_custom_button(
+ __('Fetch Subscription Updates'),
+ () => frm.events.get_subscription_updates(frm)
+ );
+ }
+ else if(frm.doc.status === 'Canceled'){
+ frm.add_custom_button(
+ __('Restart Subscription'),
+ () => frm.events.renew_this_subscription(frm)
+ );
+ }
+ }
+ },
+ cancel_this_subscription: function(frm) {
+ const doc = frm.doc;
+ frappe.confirm(
+ __('This action will stop future billing. Are you sure you want to cancel this subscription?'),
+ function() {
+ frappe.call({
+ method:
+ "erpnext.accounts.doctype.subscriptions.subscriptions.cancel_subscription",
+ args: {name: doc.name},
+ callback: function(data){
+ if(!data.exc){
+ frm.reload_doc();
+ }
+ }
+ });
+ }
+ );
+ },
+
+ renew_this_subscription: function(frm) {
+ const doc = frm.doc;
+ frappe.confirm(
+ __('You will lose records of previously generated invoices. Are you sure you want to restart this subscription?'),
+ function() {
+ frappe.call({
+ method:
+ "erpnext.accounts.doctype.subscriptions.subscriptions.restart_subscription",
+ args: {name: doc.name},
+ callback: function(data){
+ if(!data.exc){
+ frm.reload_doc();
+ }
+ }
+ });
+ }
+ );
+ },
+
+ get_subscription_updates: function(frm) {
+ const doc = frm.doc;
+ frappe.call({
+ method:
+ "erpnext.accounts.doctype.subscriptions.subscriptions.get_subscription_updates",
+ args: {name: doc.name},
+ callback: function(data){
+ if(!data.exc){
+ frm.reload_doc();
+ }
+ }
+ });
}
});
diff --git a/erpnext/accounts/doctype/subscriptions/subscriptions.py b/erpnext/accounts/doctype/subscriptions/subscriptions.py
index 123cd32..cd43a9c 100644
--- a/erpnext/accounts/doctype/subscriptions/subscriptions.py
+++ b/erpnext/accounts/doctype/subscriptions/subscriptions.py
@@ -14,8 +14,8 @@
# update start just before the subscription doc is created
self.update_subscription_period()
- def update_subscription_period(self):
- self.set_current_invoice_start()
+ def update_subscription_period(self, date=None):
+ self.set_current_invoice_start(date)
self.set_current_invoice_end()
def set_current_invoice_start(self, date=None):
@@ -228,16 +228,19 @@
"""
if self.status == 'Active':
self.process_for_active()
- elif self.status == 'Past Due Date':
+ elif self.status in ['Past Due Date', 'Unpaid']:
self.process_for_past_due_date()
- self.save()
- # process_for_unpaid()
+
+ if self.status != 'Canceled':
+ self.save()
def process_for_active(self):
if getdate(nowdate()) > getdate(self.current_invoice_end) and not self.has_outstanding_invoice():
self.generate_invoice()
+ if self.current_invoice_is_past_due():
+ self.status = 'Past Due Date'
- if self.current_invoice_is_past_due():
+ if self.current_invoice_is_past_due() and getdate(nowdate()) > getdate(self.current_invoice_end):
self.status = 'Past Due Date'
def process_for_past_due_date(self):
@@ -247,7 +250,7 @@
else:
if self.is_not_outstanding(current_invoice):
self.status = 'Active'
- self.update_subscription_period()
+ self.update_subscription_period(nowdate())
else:
self.set_status_grace_period()
@@ -261,3 +264,41 @@
else:
return not self.is_not_outstanding(current_invoice)
return True
+
+ def cancel_subscription(self):
+ """
+ This sets the subscription as cancelled. It will stop invoices from being generated
+ but it will not affect already created invoices.
+ """
+ self.status = 'Canceled'
+ self.cancelation_date = nowdate()
+ self.save()
+
+ def restart_subscription(self):
+ """
+ This sets the subscription as active. The subscription will be made to be like a new
+ subscription but new trial periods will not be allowed.
+ """
+ self.status = 'Active'
+ self.cancelation_date = None
+ self.update_subscription_period(nowdate())
+ self.invoices = []
+ self.save()
+
+
+@frappe.whitelist()
+def cancel_subscription(name):
+ subscription = frappe.get_doc('Subscriptions', name)
+ subscription.cancel_subscription()
+
+
+@frappe.whitelist()
+def restart_subscription(name):
+ subscription = frappe.get_doc('Subscriptions', name)
+ subscription.restart_subscription()
+
+
+@frappe.whitelist()
+def get_subscription_updates(name):
+ subscription = frappe.get_doc('Subscriptions', name)
+ subscription.process()
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/subscriptions/test_subscriptions.py b/erpnext/accounts/doctype/subscriptions/test_subscriptions.py
index a3413c7..a0f9400 100644
--- a/erpnext/accounts/doctype/subscriptions/test_subscriptions.py
+++ b/erpnext/accounts/doctype/subscriptions/test_subscriptions.py
@@ -267,5 +267,123 @@
subscription.delete()
+ def test_subcription_cancelation(self):
+ subscription = frappe.new_doc('Subscriptions')
+ subscription.subscriber = '_Test Customer'
+ subscription.append('plans', {'plan': '_Test Plan Name'})
+ subscription.save()
+ subscription.cancel_subscription()
+
+ self.assertEqual(subscription.status, 'Canceled')
+
+ subscription.delete()
+
+ def test_subcription_cancelation_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('Subscriptions')
+ subscription.subscriber = '_Test Customer'
+ subscription.append('plans', {'plan': '_Test Plan Name'})
+ subscription.insert()
+ subscription.set_current_invoice_start('2018-01-01')
+ subscription.set_current_invoice_end()
+ subscription.process() # generate first invoice
+ invoices = len(subscription.invoices)
+
+ self.assertEqual(subscription.status, 'Past Due Date')
+ self.assertEqual(len(subscription.invoices), invoices)
+
+ subscription.cancel_subscription()
+ self.assertEqual(subscription.status, 'Canceled')
+ self.assertEqual(len(subscription.invoices), invoices)
+
+ subscription.process()
+ self.assertEqual(subscription.status, 'Canceled')
+ self.assertEqual(len(subscription.invoices), invoices)
+
+ subscription.process()
+ self.assertEqual(subscription.status, 'Canceled')
+ self.assertEqual(len(subscription.invoices), invoices)
+
+ settings.cancel_after_grace = default_grace_period_action
+ settings.save()
+ subscription.delete()
+
+ def test_subscription_restart_and_process(self):
+ settings = frappe.get_single('Subscription Settings')
+ default_grace_period_action = settings.cancel_after_grace
+ settings.grace_period = 0
+ settings.cancel_after_grace = 0
+ settings.save()
+
+ subscription = frappe.new_doc('Subscriptions')
+ subscription.subscriber = '_Test Customer'
+ subscription.append('plans', {'plan': '_Test Plan Name'})
+ subscription.insert()
+ subscription.set_current_invoice_start('2018-01-01')
+ subscription.set_current_invoice_end()
+ subscription.process() # generate first invoice
+
+ self.assertEqual(subscription.status, 'Past Due Date')
+
+ subscription.process()
+ self.assertEqual(subscription.status, 'Unpaid')
+
+ subscription.cancel_subscription()
+ self.assertEqual(subscription.status, 'Canceled')
+
+ subscription.restart_subscription()
+ self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(len(subscription.invoices), 0)
+
+ subscription.process()
+ self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(len(subscription.invoices), 0)
+
+ subscription.process()
+ self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(len(subscription.invoices), 0)
+
+ settings.cancel_after_grace = default_grace_period_action
+ settings.save()
+ subscription.delete()
+
+ def test_subscription_unpaid_back_to_active(self):
+ settings = frappe.get_single('Subscription Settings')
+ default_grace_period_action = settings.cancel_after_grace
+ settings.cancel_after_grace = 0
+ settings.save()
+
+ subscription = frappe.new_doc('Subscriptions')
+ subscription.subscriber = '_Test Customer'
+ subscription.append('plans', {'plan': '_Test Plan Name'})
+ subscription.insert()
+ subscription.set_current_invoice_start('2018-01-01')
+ subscription.set_current_invoice_end()
+ subscription.process() # generate first invoice
+
+ self.assertEqual(subscription.status, 'Past Due Date')
+
+ subscription.process()
+ # This should change status to Canceled since grace period is 0
+ self.assertEqual(subscription.status, 'Unpaid')
+
+ invoice = subscription.get_current_invoice()
+ invoice.db_set('outstanding_amount', 0)
+ invoice.db_set('status', 'Paid')
+
+ subscription.process()
+ self.assertEqual(subscription.status, 'Active')
+
+ subscription.process()
+ self.assertEqual(subscription.status, 'Active')
+
+ settings.cancel_after_grace = default_grace_period_action
+ settings.save()
+ subscription.delete()
+
def test_subscription_creation_with_multiple_plans(self):
pass