more test cases and bug fixes
diff --git a/erpnext/accounts/doctype/subscriptions/subscriptions.py b/erpnext/accounts/doctype/subscriptions/subscriptions.py
index e5c2cc5..9a130f3 100644
--- a/erpnext/accounts/doctype/subscriptions/subscriptions.py
+++ b/erpnext/accounts/doctype/subscriptions/subscriptions.py
@@ -13,9 +13,6 @@
class Subscriptions(Document):
- def before_save(self):
- self.set_status()
-
def before_insert(self):
# update start just before the subscription doc is created
self.update_subscription_period()
@@ -29,6 +26,8 @@
self.current_invoice_start = self.trial_period_start
elif not date:
self.current_invoice_start = nowdate()
+ elif date:
+ self.current_invoice_start = date
def set_current_invoice_end(self):
if self.is_trialling():
@@ -81,15 +80,19 @@
return data
- def set_status(self):
+ def set_status_grace_period(self):
+ if self.status == 'Past Due Date' and self.is_past_grace_period():
+ self.status = 'Canceled' if cint(SUBSCRIPTION_SETTINGS.cancel_after_grace) else 'Unpaid'
+
+ def set_subscription_status(self):
if self.is_trialling():
self.status = 'Trialling'
- elif self.status == 'Past Due' and self.is_past_grace_period():
+ elif self.status == 'Past Due Date' and self.is_past_grace_period():
self.status = 'Canceled' if cint(SUBSCRIPTION_SETTINGS.cancel_after_grace) else 'Unpaid'
- elif self.status == 'Past Due' and not self.has_outstanding_invoice():
+ elif self.status == 'Past Due Date' and not self.has_outstanding_invoice():
self.status = 'Active'
elif self.current_invoice_is_past_due():
- self.status = 'Past Due'
+ self.status = 'Past Due Date'
elif self.is_new_subscription():
self.status = 'Active'
# todo: then generate new invoice
@@ -110,7 +113,7 @@
if self.current_invoice_is_past_due(current_invoice):
grace_period = cint(SUBSCRIPTION_SETTINGS.grace_period)
- return nowdate() > add_days(current_invoice.due_date, grace_period)
+ return getdate(nowdate()) > add_days(current_invoice.due_date, grace_period)
def current_invoice_is_past_due(self, current_invoice=None):
if not current_invoice:
@@ -124,8 +127,11 @@
def get_current_invoice(self):
if len(self.invoices):
current = self.invoices[-1]
- doc = frappe.get_doc('Sales Invoice', current.invoice)
- return doc
+ if frappe.db.exists('Sales Invoice', current.invoice):
+ doc = frappe.get_doc('Sales Invoice', current.invoice)
+ return doc
+ else:
+ frappe.throw(_('Invoice {0} no longer exists'.format(invoice.invoice)))
def is_new_subscription(self):
return len(self.invoices) == 0
@@ -144,7 +150,7 @@
def after_insert(self):
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
- pass
+ self.set_subscription_status()
def generate_invoice(self):
invoice = self.create_invoice()
@@ -156,6 +162,8 @@
def create_invoice(self):
invoice = frappe.new_doc('Sales Invoice')
+ invoice.set_posting_time = 1
+ invoice.posting_date = self.current_invoice_start
invoice.customer = self.get_customer(self.subscriber)
# Subscription is better suited for service items. I won't update `update_stock`
@@ -220,28 +228,36 @@
"""
if self.status == 'Active':
self.process_for_active()
- elif self.status == 'Past Due':
+ elif self.status == 'Past Due Date':
self.process_for_past_due_date()
+ self.save()
# process_for_unpaid()
def process_for_active(self):
- print 'processing for active'
if nowdate() > self.current_invoice_end and not self.has_outstanding_invoice():
- print 'generating invoice'
self.generate_invoice()
- print 'invoice generated'
if self.current_invoice_is_past_due():
- print 'current invoice is past due'
- self.status = 'Past Due'
+ self.status = 'Past Due Date'
def process_for_past_due_date(self):
- if not self.has_outstanding_invoice():
- self.status = 'Active'
- self.update_subscription_period()
+ current_invoice = self.get_current_invoice()
+ if not current_invoice:
+ frappe.throw('Current invoice is missing')
else:
- self.set_status()
+ if self.is_not_outstanding(current_invoice):
+ self.status = 'Active'
+ self.update_subscription_period()
+ else:
+ self.set_status_grace_period()
+
+ def is_not_outstanding(self, invoice):
+ return invoice.status == 'Paid'
def has_outstanding_invoice(self):
current_invoice = self.get_current_invoice()
- return current_invoice.posting_date != self.current_invoice_start
+ if not current_invoice:
+ return False
+ else:
+ return not self.is_not_outstanding(current_invoice)
+ return True
diff --git a/erpnext/accounts/doctype/subscriptions/test_subscriptions.py b/erpnext/accounts/doctype/subscriptions/test_subscriptions.py
index abd2f3f..63177e4 100644
--- a/erpnext/accounts/doctype/subscriptions/test_subscriptions.py
+++ b/erpnext/accounts/doctype/subscriptions/test_subscriptions.py
@@ -5,7 +5,8 @@
import frappe
import unittest
-from frappe.utils.data import nowdate, add_days, get_last_day, cint, getdate, add_to_date
+from frappe.utils.data import nowdate, add_days, get_last_day, cint, getdate, add_to_date, get_datetime_str
+from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
class TestSubscriptions(unittest.TestCase):
@@ -14,7 +15,7 @@
plan = frappe.new_doc('Subscription Plan')
plan.plan_name = '_Test Plan Name'
plan.item = '_Test Non Stock Item'
- plan.cost = 999.99
+ plan.cost = 900
plan.billing_interval = 'Month'
plan.billing_interval_count = 1
plan.insert()
@@ -23,7 +24,7 @@
plan = frappe.new_doc('Subscription Plan')
plan.plan_name = '_Test Plan Name 2'
plan.item = '_Test Non Stock Item'
- plan.cost = 1999.99
+ plan.cost = 1999
plan.billing_interval = 'Month'
plan.billing_interval_count = 1
plan.insert()
@@ -32,7 +33,7 @@
plan = frappe.new_doc('Subscription Plan')
plan.plan_name = '_Test Plan Name 3'
plan.item = '_Test Non Stock Item'
- plan.cost = 1999.99
+ plan.cost = 1999
plan.billing_interval = 'Day'
plan.billing_interval_count = 14
plan.insert()
@@ -89,6 +90,7 @@
subscription.append('plans', {'plan': '_Test Plan Name'})
self.assertRaises(frappe.ValidationError, subscription.save)
+ subscription.delete()
def test_create_subscription_multi_with_different_billing_fails(self):
subscription = frappe.new_doc('Subscriptions')
@@ -99,22 +101,92 @@
subscription.append('plans', {'plan': '_Test Plan Name 3'})
self.assertRaises(frappe.ValidationError, subscription.save)
+ subscription.delete()
- # def test_subscription_invoice_days_until_due(self):
- # subscription = frappe.new_doc('Subscriptions')
- # subscription.subscriber = '_Test Customer'
- # subscription.append('plans', {'plan': '_Test Plan Name'})
- # subscription.save()
+ def test_invoice_is_generated_at_end_of_billing_period(self):
+ subscription = frappe.new_doc('Subscriptions')
+ subscription.subscriber = '_Test Customer'
+ subscription.append('plans', {'plan': '_Test Plan Name'})
+ subscription.insert()
- # generated_invoice_name = subscription.invoices[-1].invoice
- # invoice = frappe.get_doc('Sales Invoice', generated_invoice_name)
+ self.assertEqual(subscription.status, 'Active')
- # self.assertEqual(
- # invoice.due_date,
- # add_days(subscription.current_invoice_end, cint(subscription.days_until_due))
- # )
+ subscription.set_current_invoice_start('2018-01-01')
+ subscription.set_current_invoice_end()
- # subscription.delete()
+ self.assertEqual(subscription.current_invoice_start, '2018-01-01')
+ self.assertEqual(subscription.current_invoice_end, '2018-01-31')
+ subscription.process()
+
+ self.assertEqual(len(subscription.invoices), 1)
+ self.assertEqual(subscription.current_invoice_start, nowdate())
+ self.assertEqual(subscription.status, 'Past Due Date')
+ subscription.delete()
+
+ def test_status_goes_back_to_active_after_invoice_is_paid(self):
+ 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(len(subscription.invoices), 1)
+ self.assertEqual(subscription.status, 'Past Due Date')
+
+ subscription.get_current_invoice()
+ current_invoice = subscription.get_current_invoice()
+
+ self.assertIsNotNone(current_invoice)
+
+ current_invoice.db_set('outstanding_amount', 0)
+ current_invoice.db_set('status', 'Paid')
+ subscription.process()
+
+ self.assertEqual(subscription.status, 'Active')
+ self.assertEqual(subscription.current_invoice_start, nowdate())
+ self.assertEqual(len(subscription.invoices), 1)
+
+ subscription.delete()
+
+ def test_subscription_cancel_after_grace_period(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
+
+ self.assertEqual(subscription.status, 'Past Due Date')
+
+ subscription.process()
+ # This should change status to Canceled since grace period is 0
+ self.assertEqual(subscription.status, 'Canceled')
+
+ settings.cancel_after_grace = default_grace_period_action
+ settings.save()
+ subscription.delete()
+
+
+ def test_subscription_invoice_days_until_due(self):
+ subscription = frappe.new_doc('Subscriptions')
+ subscription.subscriber = '_Test Customer'
+ subscription.append('plans', {'plan': '_Test Plan Name'})
+ subscription.days_until_due = 10
+ subscription.insert()
+ subscription.set_current_invoice_start(get_datetime_str(add_to_date(nowdate(), months=-1)))
+ subscription.set_current_invoice_end()
+ subscription.process() # generate first invoice
+ self.assertEqual(len(subscription.invoices), 1)
+ self.assertEqual(subscription.status, 'Active')
+
+ subscription.delete()
def test_subscription_creation_with_multiple_plans(self):
pass