Move subscription module to accounts (#10772)

This commit is contained in:
rohitwaghchaure 2017-09-15 16:16:36 +05:30 committed by Rohit Waghchaure
parent e859671eb3
commit 850eaa73b0
25 changed files with 183 additions and 42 deletions

View File

@ -483,9 +483,14 @@ class PaymentEntry(AccountsController):
doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc)
def on_recurring(self, reference_doc, subscription_doc):
self.reference_no = reference_doc.name
self.reference_date = nowdate()
@frappe.whitelist()
def get_outstanding_reference_documents(args):
args = json.loads(args)
if isinstance(args, basestring):
args = json.loads(args)
party_account_currency = get_account_currency(args.get("party_account"))
company_currency = frappe.db.get_value("Company", args.get("company"), "default_currency")

View File

@ -40,6 +40,38 @@ frappe.ui.form.on('Subscription', {
frappe.set_route("List", frm.doc.reference_doctype);
}
);
if(frm.doc.status != 'Stopped') {
frm.add_custom_button(__("Stop"),
function() {
frm.events.stop_resume_subscription(frm, "Stopped");
}
);
}
if(frm.doc.status == 'Stopped') {
frm.add_custom_button(__("Resume"),
function() {
frm.events.stop_resume_subscription(frm, "Resumed");
}
);
}
}
},
stop_resume_subscription: function(frm, status) {
frappe.call({
method: "erpnext.accounts.doctype.subscription.subscription.stop_resume_subscription",
args: {
subscription: frm.doc.name,
status: status
},
callback: function(r) {
if(r.message) {
frm.set_value("status", r.message);
frm.reload_doc();
}
}
});
}
});

View File

@ -148,7 +148,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Disabled",
"length": 0,
@ -619,24 +619,24 @@
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Draft",
"fieldname": "status",
"fieldtype": "Select",
"hidden": 1,
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "\nDraft\nSubmitted\nCancelled\nCompleted",
"options": "\nDraft\nStopped\nSubmitted\nCancelled\nCompleted",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@ -690,9 +690,9 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-08-29 15:45:16.157643",
"modified": "2017-09-14 12:09:38.471458",
"modified_by": "Administrator",
"module": "Subscription",
"module": "Accounts",
"name": "Subscription",
"name_case": "",
"owner": "Administrator",
@ -700,7 +700,7 @@
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 1,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
@ -716,6 +716,46 @@
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 0,

View File

@ -71,13 +71,16 @@ class Subscription(Document):
doc.db_set('subscription', self.name)
def update_status(self):
def update_status(self, status=None):
self.status = {
'0': 'Draft',
'1': 'Submitted',
'2': 'Cancelled'
}[cstr(self.docstatus or 0)]
if status and status != 'Resumed':
self.status = status
def get_next_schedule_date(start_date, frequency, repeat_on_day):
mcount = month_map.get(frequency)
if mcount:
@ -93,11 +96,10 @@ def make_subscription_entry(date=None):
schedule_date = getdate(data.next_schedule_date)
while schedule_date <= getdate(today()):
create_documents(data, schedule_date)
schedule_date = get_next_schedule_date(schedule_date,
data.frequency, data.repeat_on_day)
if schedule_date:
if schedule_date and not frappe.db.get_value('Subscription', data.name, 'disabled'):
frappe.db.set_value('Subscription', data.name, 'next_schedule_date', schedule_date)
def get_subscription_entries(date):
@ -105,23 +107,29 @@ def get_subscription_entries(date):
where docstatus = 1 and next_schedule_date <=%s
and reference_document is not null and reference_document != ''
and next_schedule_date <= ifnull(end_date, '2199-12-31')
and ifnull(disabled, 0) = 0""", (date), as_dict=1)
and ifnull(disabled, 0) = 0 and status != 'Stopped' """, (date), as_dict=1)
def create_documents(data, schedule_date):
try:
doc = make_new_document(data, schedule_date)
if data.notify_by_email:
send_notification(doc, data.print_format, data.recipients)
if data.notify_by_email and data.recipients:
print_format = data.print_format or "Standard"
send_notification(doc, print_format, data.recipients)
frappe.db.commit()
except Exception:
frappe.db.rollback()
frappe.db.begin()
frappe.log_error(frappe.get_traceback())
disabled_subscription(data)
frappe.db.commit()
if data.reference_document and not frappe.flags.in_test:
notify_error_to_user(data)
def disabled_subscription(data):
subscription = frappe.get_doc('Subscription', data.name)
subscription.db_set('disabled', 1)
def notify_error_to_user(data):
party = ''
party_type = ''
@ -134,7 +142,7 @@ def notify_error_to_user(data):
if party_type:
party = frappe.db.get_value(data.reference_doctype, data.reference_document, party_type)
notify_errors(data.reference_document, data.reference_doctype, party, data.owner)
notify_errors(data.reference_document, data.reference_doctype, party, data.owner, data.name)
def make_new_document(args, schedule_date):
doc = frappe.get_doc(args.reference_doctype, args.reference_document)
@ -168,32 +176,32 @@ def get_next_date(dt, mcount, day=None):
def send_notification(new_rv, print_format='Standard', recipients=None):
"""Notify concerned persons about recurring document generation"""
recipients = recipients or new_rv.notification_email_address
print_format = print_format or new_rv.recurring_print_format
print_format = print_format
frappe.sendmail(recipients,
subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name),
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name),
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=print_format)])
def notify_errors(doc, doctype, party, owner):
def notify_errors(doc, doctype, party, owner, name):
recipients = get_system_managers(only_name=True)
frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")],
subject="[Urgent] Error while creating recurring %s for %s" % (doctype, doc),
subject=_("[Urgent] Error while creating recurring %s for %s" % (doctype, doc)),
message = frappe.get_template("templates/emails/recurring_document_failed.html").render({
"type": doctype,
"type": _(doctype),
"name": doc,
"party": party or ""
"party": party or "",
"subscription": name
}))
assign_task_to_owner(doc, doctype, "Recurring Invoice Failed", recipients)
assign_task_to_owner(name, "Recurring Documents Failed", recipients)
def assign_task_to_owner(doc, doctype, msg, users):
def assign_task_to_owner(name, msg, users):
for d in users:
args = {
'doctype' : 'Subscription',
'assign_to' : d,
'doctype' : doctype,
'name' : doc,
'name' : name,
'description' : msg,
'priority' : 'High'
}
@ -205,3 +213,16 @@ def make_subscription(doctype, docname):
doc.reference_doctype = doctype
doc.reference_document = docname
return doc
@frappe.whitelist()
def stop_resume_subscription(subscription, status):
doc = frappe.get_doc('Subscription', subscription)
frappe.msgprint(_("Subscription has been {0}").format(status))
if status == 'Resumed':
doc.next_schedule_date = get_next_schedule_date(today(),
doc.frequency, doc.repeat_on_day)
doc.update_status(status)
doc.save()
return doc.status

View File

@ -1,10 +1,14 @@
frappe.listview_settings['Subscription'] = {
add_fields: ["next_schedule_date"],
get_indicator: function(doc) {
if(doc.next_schedule_date >= frappe.datetime.get_today() ) {
if(doc.disabled) {
return [__("Disabled"), "red"];
} else if(doc.next_schedule_date >= frappe.datetime.get_today() && doc.status != 'Stopped') {
return [__("Active"), "green"];
} else if(doc.docstatus === 0) {
return [__("Draft"), "red", "docstatus,=,0"];
} else if(doc.status === 'Stopped') {
return [__("Stopped"), "red"];
} else {
return [__("Expired"), "darkgrey"];
}

View File

@ -10,7 +10,7 @@ from erpnext.accounts.utils import get_fiscal_year
from erpnext.accounts.report.financial_statements import get_months
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.subscription.doctype.subscription.subscription import make_subscription_entry
from erpnext.accounts.doctype.subscription.subscription import make_subscription_entry
class TestSubscription(unittest.TestCase):
def test_daily_subscription(self):

View File

@ -32,6 +32,12 @@ def get_data():
"label": _("POS"),
"description": _("Point of Sale")
},
{
"type": "doctype",
"name": "Subscription",
"label": _("Subscription"),
"description": _("To make recurring documents")
},
{
"type": "report",
"name": "Accounts Receivable",

View File

Before

Width:  |  Height:  |  Size: 818 KiB

After

Width:  |  Height:  |  Size: 818 KiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -6,6 +6,7 @@ purchase-invoice
payments
journal-entry
payment-entry
subscription
multi-currency-accounting
advance-payment-entry
payment-request

View File

@ -0,0 +1,24 @@
# Subscription
If you have a contract with the Customer where your organization gives bill to the Customer on a monthly, quarterly, half-yearly or annual basis, you can use subscription feature to make auto invoicing.
<img class="screenshot" alt="Subscription" src="/docs/assets/img/accounts/subscription.png">
#### Scenario
Subscription for your hosted ERPNext account requires yearly renewal. We use Sales Invoice for generating proforma invoices. To automate proforma invoicing for renewal, we set original Sales Invoice on the subscription form. Recurring proforma invoice is created automatically just before customer's account is about to expire, and requires renewal. This recurring Proforma Invoice is also emailed automatically to the customer.
To set the subscription for the sales invoice
Goto Subscription > select base doctype "Sales Invoice" > select base docname "Invoice No" > Save
<img class="screenshot" alt="Subscription" src="/docs/assets/img/accounts/subscription.gif">
**From Date and To Date**: This defines contract period with the customer.
**Repeat on Day**: If frequency is set as Monthly, then it will be day of the month on which recurring invoice will be generated.
**Notify By Email**: If you want to notify the user about auto recurring invoice.
**Print Format**: Select a print format to define document view which should be emailed to customer.
**Disabled**: It will stop to make auto recurring documents against the subscription

View File

@ -192,7 +192,7 @@ doc_events = {
scheduler_events = {
"hourly": [
"erpnext.subscription.doctype.subscription.subscription.make_subscription_entry",
"erpnext.accounts.doctype.subscription.subscription.make_subscription_entry",
'erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.trigger_emails'
],
"daily": [

View File

@ -15,5 +15,4 @@ Portal
Maintenance
Schools
Regional
Healthcare
Subscription
Healthcare

View File

@ -439,6 +439,7 @@ erpnext.patches.v8_7.set_offline_in_pos_settings #11-09-17
erpnext.patches.v8_9.add_setup_progress_actions #08-09-2017
erpnext.patches.v8_9.rename_company_sales_target_field
erpnext.patches.v8_8.set_bom_rate_as_per_uom
erpnext.patches.v9_0.remove_subscription_module
erpnext.patches.v8_7.make_subscription_from_recurring_data
erpnext.patches.v8_9.set_print_zero_amount_taxes
erpnext.patches.v8_9.set_default_customer_group

View File

@ -6,7 +6,7 @@ import frappe
from frappe.utils import today
def execute():
frappe.reload_doc('subscription', 'doctype', 'subscription')
frappe.reload_doc('accounts', 'doctype', 'subscription')
frappe.reload_doc('selling', 'doctype', 'sales_order')
frappe.reload_doc('buying', 'doctype', 'purchase_order')
frappe.reload_doc('accounts', 'doctype', 'sales_invoice')

View File

@ -0,0 +1 @@
from __future__ import unicode_literals

View File

@ -0,0 +1,9 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
if frappe.db.exists('Module Def', 'Subscription'):
frappe.db.sql(""" delete from `tabModule Def` where name = 'Subscription'""")

View File

@ -129,7 +129,7 @@ $.extend(erpnext.utils, {
make_subscription: function(doctype, docname) {
frappe.call({
method: "erpnext.subscription.doctype.subscription.subscription.make_subscription",
method: "erpnext.accounts.doctype.subscription.subscription.make_subscription",
args: {
doctype: doctype,
docname: docname

View File

@ -13,7 +13,7 @@ from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
from frappe.desk.notifications import clear_doctype_notifications
from frappe.contacts.doctype.address.address import get_company_address
from erpnext.controllers.selling_controller import SellingController
from erpnext.subscription.doctype.subscription.subscription import month_map, get_next_date
from erpnext.accounts.doctype.subscription.subscription import get_next_schedule_date
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@ -347,8 +347,7 @@ class SalesOrder(SellingController):
return items
def on_recurring(self, reference_doc, subscription_doc):
mcount = month_map[subscription_doc.frequency]
self.set("delivery_date", get_next_date(reference_doc.delivery_date, mcount,
self.set("delivery_date", get_next_schedule_date(reference_doc.delivery_date, subscription_doc.frequency,
cint(subscription_doc.repeat_on_day)))
for d in self.get("items"):
@ -356,7 +355,7 @@ class SalesOrder(SellingController):
{"parent": reference_doc.name, "item_code": d.item_code, "idx": d.idx}, "delivery_date")
d.set("delivery_date",
get_next_date(reference_delivery_date, mcount, cint(subscription_doc.repeat_on_day)))
get_next_schedule_date(reference_delivery_date, subscription_doc.frequency, cint(subscription_doc.repeat_on_day)))
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context

View File

@ -1,12 +1,11 @@
<h2>Recurring {{ type }} Failed</h2>
<h2>{{_("Recurring")}} {{ type }} {{ _("Failed")}}</h2>
<p>An error occured while creating recurring {{ type }} <b>{{ name }}</b> for <b>{{ party }}</b>.</p>
<p>This could be because of some invalid Email Addresses in the {{ type }}.</p>
<p>To stop sending repetitive error notifications from the system, we have unchecked
"Convert into Recurring" field in the {{ type }} {{ name }}.</p>
<p><b>Please correct the {{ type }} and make the {{ type }} recurring again.</b></p>
<p>To stop sending repetitive error notifications from the system, we have checked "Disabled" field in the subscription {{ subscription}} for the {{ type }} {{ name }}.</p>
<p><b>Please correct the {{ type }} and unchcked "Disabled" in the {{ subscription }} for making recurring again.</b></p>
<hr>
<p><b>It is necessary to take this action today itself for the above mentioned recurring {{ type }}
to be generated. If delayed, you will have to manually change the "Repeat on Day of Month" field
of this {{ type }} for generating the recurring {{ type }}.</b></p>
of this {{ type }} for generating the recurring {{ type }} in the subscription {{ subscription }}.</b></p>
<p>[This email is autogenerated]</p>