From e8331d40f3ba1f7d31f7f0cd03b03d1c4b761af9 Mon Sep 17 00:00:00 2001 From: ankitjavalkarwork Date: Mon, 25 Aug 2014 18:00:12 +0530 Subject: [PATCH 1/6] Commonify Recurring Sales Order/Invoice --- .../doctype/sales_invoice/sales_invoice.js | 14 +- .../doctype/sales_invoice/sales_invoice.json | 23 +- .../doctype/sales_invoice/sales_invoice.py | 252 +++++++++--------- .../sales_invoice/test_sales_invoice.py | 36 +-- erpnext/controllers/accounts_controller.py | 51 ++++ erpnext/controllers/recurring_document.py | 121 +++++++++ .../doctype/sales_order/sales_order.json | 131 ++++++++- .../doctype/sales_order/sales_order.py | 8 + .../emails/recurring_invoice_failed.html | 14 +- 9 files changed, 479 insertions(+), 171 deletions(-) create mode 100644 erpnext/controllers/recurring_document.py diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 76092ed30d..5228b0e383 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -228,7 +228,7 @@ cur_frm.cscript.hide_fields = function(doc) { par_flds = ['project_name', 'due_date', 'is_opening', 'source', 'total_advance', 'gross_profit', 'gross_profit_percent', 'get_advances_received', 'advance_adjustment_details', 'sales_partner', 'commission_rate', - 'total_commission', 'advances', 'invoice_period_from_date', 'invoice_period_to_date']; + 'total_commission', 'advances', 'period_from', 'period_to']; item_flds_normal = ['sales_order', 'delivery_note'] @@ -414,18 +414,18 @@ cur_frm.cscript.convert_into_recurring_invoice = function(doc, dt, dn) { refresh_many(["notification_email_address", "repeat_on_day_of_month"]); } -cur_frm.cscript.invoice_period_from_date = function(doc, dt, dn) { - // set invoice_period_to_date - if(doc.invoice_period_from_date) { +cur_frm.cscript.period_from = function(doc, dt, dn) { + // set period_to + if(doc.period_from) { var recurring_type_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}; var months = recurring_type_map[doc.recurring_type]; if(months) { - var to_date = frappe.datetime.add_months(doc.invoice_period_from_date, + var to_date = frappe.datetime.add_months(doc.period_from, months); - doc.invoice_period_to_date = frappe.datetime.add_days(to_date, -1); - refresh_field('invoice_period_to_date'); + doc.period_to = frappe.datetime.add_days(to_date, -1); + refresh_field('period_to'); } } } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index ff256dc777..7cab4c24f0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1,5 +1,6 @@ { - "allow_import": 1, + "allow_attach": 1, + "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-24 19:29:05", "default_print_format": "Standard", @@ -172,7 +173,7 @@ "allow_on_submit": 1, "depends_on": "", "description": "Start date of current invoice's period", - "fieldname": "invoice_period_from_date", + "fieldname": "period_from", "fieldtype": "Date", "label": "Invoice Period From", "no_copy": 1, @@ -184,7 +185,7 @@ "allow_on_submit": 1, "depends_on": "", "description": "End date of current invoice's period", - "fieldname": "invoice_period_to_date", + "fieldname": "period_to", "fieldtype": "Date", "label": "Invoice Period To", "no_copy": 1, @@ -1087,7 +1088,7 @@ "allow_on_submit": 1, "depends_on": "eval:doc.docstatus<2", "description": "Check if recurring invoice, uncheck to stop recurring or put proper End Date", - "fieldname": "convert_into_recurring_invoice", + "fieldname": "convert_into_recurring", "fieldtype": "Check", "label": "Convert into Recurring Invoice", "no_copy": 1, @@ -1097,7 +1098,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring_invoice==1", + "depends_on": "eval:doc.convert_into_recurring==1", "description": "Select the period when the invoice will be generated automatically", "fieldname": "recurring_type", "fieldtype": "Select", @@ -1110,7 +1111,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring_invoice==1", + "depends_on": "eval:doc.convert_into_recurring==1", "description": "The day of the month on which auto invoice will be generated e.g. 05, 28 etc ", "fieldname": "repeat_on_day_of_month", "fieldtype": "Int", @@ -1121,7 +1122,7 @@ "read_only": 0 }, { - "depends_on": "eval:doc.convert_into_recurring_invoice==1", + "depends_on": "eval:doc.convert_into_recurring==1", "description": "The date on which next invoice will be generated. It is generated on submit.\n", "fieldname": "next_date", "fieldtype": "Date", @@ -1133,7 +1134,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring_invoice==1", + "depends_on": "eval:doc.convert_into_recurring==1", "description": "The date on which recurring invoice will be stop", "fieldname": "end_date", "fieldtype": "Date", @@ -1153,7 +1154,7 @@ "width": "50%" }, { - "depends_on": "eval:doc.convert_into_recurring_invoice==1", + "depends_on": "eval:doc.convert_into_recurring==1", "description": "The unique id for tracking all recurring invoices.\u00a0It is generated on submit.", "fieldname": "recurring_id", "fieldtype": "Data", @@ -1165,7 +1166,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring_invoice==1", + "depends_on": "eval:doc.convert_into_recurring==1", "description": "Enter email id separated by commas, invoice will be mailed automatically on particular date", "fieldname": "notification_email_address", "fieldtype": "Small Text", @@ -1192,7 +1193,7 @@ "icon": "icon-file-text", "idx": 1, "is_submittable": 1, - "modified": "2014-08-14 02:13:09.673510", + "modified": "2014-08-25 17:41:35.367233", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 481ae098b3..69a7def900 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -75,7 +75,7 @@ class SalesInvoice(SellingController): self.set_against_income_account() self.validate_c_form() self.validate_time_logs_are_submitted() - self.validate_recurring_invoice() + self.validate_recurring_document() self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "delivery_note_details") @@ -103,7 +103,7 @@ class SalesInvoice(SellingController): self.update_c_form() self.update_time_log_batch(self.name) - self.convert_to_recurring() + self.convert_to_recurring("RECINV.#####", self.transaction_date) def before_cancel(self): self.update_time_log_batch(None) @@ -144,8 +144,8 @@ class SalesInvoice(SellingController): }) def on_update_after_submit(self): - self.validate_recurring_invoice() - self.convert_to_recurring() + self.validate_recurring_document() + self.convert_to_recurring("RECINV.#####", self.transaction_date) def get_portal_page(self): return "invoice" if self.docstatus==1 else None @@ -592,157 +592,157 @@ class SalesInvoice(SellingController): grand_total = %s where invoice_no = %s and parent = %s""", (self.name, self.amended_from, self.c_form_no)) - def validate_recurring_invoice(self): - if self.convert_into_recurring_invoice: - self.validate_notification_email_id() +# def validate_recurring_invoice(self): +# if self.convert_into_recurring_invoice: +# self.validate_notification_email_id() - if not self.recurring_type: - msgprint(_("Please select {0}").format(self.meta.get_label("recurring_type")), - raise_exception=1) +# if not self.recurring_type: +# msgprint(_("Please select {0}").format(self.meta.get_label("recurring_type")), +# raise_exception=1) - elif not (self.invoice_period_from_date and \ - self.invoice_period_to_date): - throw(_("Invoice Period From and Invoice Period To dates mandatory for recurring invoice")) +# elif not (self.period_from and \ +# self.period_to): +# throw(_("Invoice Period From and Invoice Period To dates mandatory for recurring invoice")) - def convert_to_recurring(self): - if self.convert_into_recurring_invoice: - if not self.recurring_id: - frappe.db.set(self, "recurring_id", - make_autoname("RECINV/.#####")) +# def convert_to_recurring(self): +# if self.convert_into_recurring_invoice: +# if not self.recurring_id: +# frappe.db.set(self, "recurring_id", +# make_autoname("RECINV/.#####")) - self.set_next_date() +# self.set_next_date() - elif self.recurring_id: - frappe.db.sql("""update `tabSales Invoice` - set convert_into_recurring_invoice = 0 - where recurring_id = %s""", (self.recurring_id,)) +# elif self.recurring_id: +# frappe.db.sql("""update `tabSales Invoice` +# set convert_into_recurring_invoice = 0 +# where recurring_id = %s""", (self.recurring_id,)) - def validate_notification_email_id(self): - if self.notification_email_address: - email_list = filter(None, [cstr(email).strip() for email in - self.notification_email_address.replace("\n", "").split(",")]) +# def validate_notification_email_id(self): +# if self.notification_email_address: +# email_list = filter(None, [cstr(email).strip() for email in +# self.notification_email_address.replace("\n", "").split(",")]) - from frappe.utils import validate_email_add - for email in email_list: - if not validate_email_add(email): - throw(_("{0} is an invalid email address in 'Notification Email Address'").format(email)) +# from frappe.utils import validate_email_add +# for email in email_list: +# if not validate_email_add(email): +# throw(_("{0} is an invalid email address in 'Notification Email Address'").format(email)) - else: - throw(_("'Notification Email Addresses' not specified for recurring invoice")) +# else: +# throw(_("'Notification Email Addresses' not specified for recurring invoice")) - def set_next_date(self): - """ Set next date on which auto invoice will be created""" - if not self.repeat_on_day_of_month: - msgprint(_("Please enter 'Repeat on Day of Month' field value"), raise_exception=1) +# def set_next_date(self): +# """ Set next date on which auto invoice will be created""" +# if not self.repeat_on_day_of_month: +# msgprint(_("Please enter 'Repeat on Day of Month' field value"), raise_exception=1) - next_date = get_next_date(self.posting_date, - month_map[self.recurring_type], cint(self.repeat_on_day_of_month)) +# next_date = get_next_date(self.posting_date, +# month_map[self.recurring_type], cint(self.repeat_on_day_of_month)) - frappe.db.set(self, 'next_date', next_date) +# frappe.db.set(self, 'next_date', next_date) -def get_next_date(dt, mcount, day=None): - dt = getdate(dt) +# def get_next_date(dt, mcount, day=None): +# dt = getdate(dt) - from dateutil.relativedelta import relativedelta - dt += relativedelta(months=mcount, day=day) +# from dateutil.relativedelta import relativedelta +# dt += relativedelta(months=mcount, day=day) - return dt +# return dt -def manage_recurring_invoices(next_date=None, commit=True): - """ - Create recurring invoices on specific date by copying the original one - and notify the concerned people - """ - next_date = next_date or nowdate() - recurring_invoices = frappe.db.sql("""select name, recurring_id - from `tabSales Invoice` where ifnull(convert_into_recurring_invoice, 0)=1 - and docstatus=1 and next_date=%s - and next_date <= ifnull(end_date, '2199-12-31')""", next_date) +# def manage_recurring_invoices(next_date=None, commit=True): +# """ +# Create recurring invoices on specific date by copying the original one +# and notify the concerned people +# """ +# next_date = next_date or nowdate() +# recurring_invoices = frappe.db.sql("""select name, recurring_id +# from `tabSales Invoice` where ifnull(convert_into_recurring_invoice, 0)=1 +# and docstatus=1 and next_date=%s +# and next_date <= ifnull(end_date, '2199-12-31')""", next_date) - exception_list = [] - for ref_invoice, recurring_id in recurring_invoices: - if not frappe.db.sql("""select name from `tabSales Invoice` - where posting_date=%s and recurring_id=%s and docstatus=1""", - (next_date, recurring_id)): - try: - ref_wrapper = frappe.get_doc('Sales Invoice', ref_invoice) - new_invoice_wrapper = make_new_invoice(ref_wrapper, next_date) - send_notification(new_invoice_wrapper) - if commit: - frappe.db.commit() - except: - if commit: - frappe.db.rollback() +# exception_list = [] +# for ref_invoice, recurring_id in recurring_invoices: +# if not frappe.db.sql("""select name from `tabSales Invoice` +# where posting_date=%s and recurring_id=%s and docstatus=1""", +# (next_date, recurring_id)): +# try: +# ref_wrapper = frappe.get_doc('Sales Invoice', ref_invoice) +# new_invoice_wrapper = make_new_invoice(ref_wrapper, next_date) +# send_notification(new_invoice_wrapper) +# if commit: +# frappe.db.commit() +# except: +# if commit: +# frappe.db.rollback() - frappe.db.begin() - frappe.db.sql("update `tabSales Invoice` set \ - convert_into_recurring_invoice = 0 where name = %s", ref_invoice) - notify_errors(ref_invoice, ref_wrapper.customer, ref_wrapper.owner) - frappe.db.commit() +# frappe.db.begin() +# frappe.db.sql("update `tabSales Invoice` set \ +# convert_into_recurring_invoice = 0 where name = %s", ref_invoice) +# notify_errors(ref_invoice, ref_wrapper.customer, ref_wrapper.owner) +# frappe.db.commit() - exception_list.append(frappe.get_traceback()) - finally: - if commit: - frappe.db.begin() +# exception_list.append(frappe.get_traceback()) +# finally: +# if commit: +# frappe.db.begin() - if exception_list: - exception_message = "\n\n".join([cstr(d) for d in exception_list]) - frappe.throw(exception_message) +# if exception_list: +# exception_message = "\n\n".join([cstr(d) for d in exception_list]) +# frappe.throw(exception_message) -def make_new_invoice(ref_wrapper, posting_date): - from erpnext.accounts.utils import get_fiscal_year - new_invoice = frappe.copy_doc(ref_wrapper) +# def make_new_invoice(ref_wrapper, posting_date): +# from erpnext.accounts.utils import get_fiscal_year +# new_invoice = frappe.copy_doc(ref_wrapper) - mcount = month_map[ref_wrapper.recurring_type] +# mcount = month_map[ref_wrapper.recurring_type] - invoice_period_from_date = get_next_date(ref_wrapper.invoice_period_from_date, mcount) +# period_from = get_next_date(ref_wrapper.period_from, mcount) - # get last day of the month to maintain period if the from date is first day of its own month - # and to date is the last day of its own month - if (cstr(get_first_day(ref_wrapper.invoice_period_from_date)) == \ - cstr(ref_wrapper.invoice_period_from_date)) and \ - (cstr(get_last_day(ref_wrapper.invoice_period_to_date)) == \ - cstr(ref_wrapper.invoice_period_to_date)): - invoice_period_to_date = get_last_day(get_next_date(ref_wrapper.invoice_period_to_date, - mcount)) - else: - invoice_period_to_date = get_next_date(ref_wrapper.invoice_period_to_date, mcount) +# # get last day of the month to maintain period if the from date is first day of its own month +# # and to date is the last day of its own month +# if (cstr(get_first_day(ref_wrapper.period_from)) == \ +# cstr(ref_wrapper.period_from)) and \ +# (cstr(get_last_day(ref_wrapper.period_to)) == \ +# cstr(ref_wrapper.period_to)): +# period_to = get_last_day(get_next_date(ref_wrapper.period_to, +# mcount)) +# else: +# period_to = get_next_date(ref_wrapper.period_to, mcount) - new_invoice.update({ - "posting_date": posting_date, - "aging_date": posting_date, - "due_date": add_days(posting_date, cint(date_diff(ref_wrapper.due_date, - ref_wrapper.posting_date))), - "invoice_period_from_date": invoice_period_from_date, - "invoice_period_to_date": invoice_period_to_date, - "fiscal_year": get_fiscal_year(posting_date)[0], - "owner": ref_wrapper.owner, - }) +# new_invoice.update({ +# "posting_date": posting_date, +# "aging_date": posting_date, +# "due_date": add_days(posting_date, cint(date_diff(ref_wrapper.due_date, +# ref_wrapper.posting_date))), +# "period_from": period_from, +# "period_to": period_to, +# "fiscal_year": get_fiscal_year(posting_date)[0], +# "owner": ref_wrapper.owner, +# }) - new_invoice.submit() +# new_invoice.submit() - return new_invoice +# return new_invoice -def send_notification(new_rv): - """Notify concerned persons about recurring invoice generation""" - frappe.sendmail(new_rv.notification_email_address, - subject="New Invoice : " + new_rv.name, - message = _("Please find attached Sales Invoice #{0}").format(new_rv.name), - attachments = [{ - "fname": new_rv.name + ".pdf", - "fcontent": frappe.get_print_format(new_rv.doctype, new_rv.name, as_pdf=True) - }]) +# def send_notification(new_rv): +# """Notify concerned persons about recurring invoice generation""" +# frappe.sendmail(new_rv.notification_email_address, +# subject="New Invoice : " + new_rv.name, +# message = _("Please find attached Sales Invoice #{0}").format(new_rv.name), +# attachments = [{ +# "fname": new_rv.name + ".pdf", +# "fcontent": frappe.get_print_format(new_rv.doctype, new_rv.name, as_pdf=True) +# }]) -def notify_errors(inv, customer, owner): - from frappe.utils.user import get_system_managers - recipients=get_system_managers(only_name=True) +# def notify_errors(inv, customer, owner): +# from frappe.utils.user import get_system_managers +# recipients=get_system_managers(only_name=True) - frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")], - subject="[Urgent] Error while creating recurring invoice for %s" % inv, - message = frappe.get_template("templates/emails/recurring_invoice_failed.html").render({ - "name": inv, - "customer": customer - })) +# frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")], +# subject="[Urgent] Error while creating recurring invoice for %s" % inv, +# message = frappe.get_template("templates/emails/recurring_invoice_failed.html").render({ +# "name": inv, +# "customer": customer +# })) assign_task_to_owner(inv, "Recurring Invoice Failed", recipients) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index ab361d83ad..44bd451ed8 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -677,8 +677,8 @@ class TestSalesInvoice(unittest.TestCase): "posting_date": today, "due_date": None, "fiscal_year": get_fiscal_year(today)[0], - "invoice_period_from_date": get_first_day(today), - "invoice_period_to_date": get_last_day(today) + "period_from": get_first_day(today), + "period_to": get_last_day(today) }) # monthly @@ -690,8 +690,8 @@ class TestSalesInvoice(unittest.TestCase): # monthly without a first and last day period si2 = frappe.copy_doc(base_si) si2.update({ - "invoice_period_from_date": today, - "invoice_period_to_date": add_to_date(today, days=30) + "period_from": today, + "period_to": add_to_date(today, days=30) }) si2.insert() si2.submit() @@ -701,8 +701,8 @@ class TestSalesInvoice(unittest.TestCase): si3 = frappe.copy_doc(base_si) si3.update({ "recurring_type": "Quarterly", - "invoice_period_from_date": get_first_day(today), - "invoice_period_to_date": get_last_day(add_to_date(today, months=3)) + "period_from": get_first_day(today), + "period_to": get_last_day(add_to_date(today, months=3)) }) si3.insert() si3.submit() @@ -712,8 +712,8 @@ class TestSalesInvoice(unittest.TestCase): si4 = frappe.copy_doc(base_si) si4.update({ "recurring_type": "Quarterly", - "invoice_period_from_date": today, - "invoice_period_to_date": add_to_date(today, months=3) + "period_from": today, + "period_to": add_to_date(today, months=3) }) si4.insert() si4.submit() @@ -723,8 +723,8 @@ class TestSalesInvoice(unittest.TestCase): si5 = frappe.copy_doc(base_si) si5.update({ "recurring_type": "Yearly", - "invoice_period_from_date": get_first_day(today), - "invoice_period_to_date": get_last_day(add_to_date(today, years=1)) + "period_from": get_first_day(today), + "period_to": get_last_day(add_to_date(today, years=1)) }) si5.insert() si5.submit() @@ -734,8 +734,8 @@ class TestSalesInvoice(unittest.TestCase): si6 = frappe.copy_doc(base_si) si6.update({ "recurring_type": "Yearly", - "invoice_period_from_date": today, - "invoice_period_to_date": add_to_date(today, years=1) + "period_from": today, + "period_to": add_to_date(today, years=1) }) si6.insert() si6.submit() @@ -784,16 +784,16 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(new_si.posting_date, unicode(next_date)) - self.assertEquals(new_si.invoice_period_from_date, - unicode(add_months(base_si.invoice_period_from_date, no_of_months))) + self.assertEquals(new_si.period_from, + unicode(add_months(base_si.period_from, no_of_months))) if first_and_last_day: - self.assertEquals(new_si.invoice_period_to_date, - unicode(get_last_day(add_months(base_si.invoice_period_to_date, + self.assertEquals(new_si.period_to, + unicode(get_last_day(add_months(base_si.period_to, no_of_months)))) else: - self.assertEquals(new_si.invoice_period_to_date, - unicode(add_months(base_si.invoice_period_to_date, no_of_months))) + self.assertEquals(new_si.period_to, + unicode(add_months(base_si.period_to, no_of_months))) return new_si diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 59a49afb22..9aa93ac8a0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -444,6 +444,57 @@ class AccountsController(TransactionBase): if total_outstanding: frappe.get_doc('Account', account).check_credit_limit(total_outstanding) + def validate_recurring_document(self): + if self.convert_into_recurring: + self.validate_notification_email_id() + + if not self.recurring_type: + msgprint(_("Please select {0}").format(self.meta.get_label("recurring_type")), + raise_exception=1) + + elif not (self.period_from and self.period_to): + throw(_("Period From and Period To dates mandatory for recurring %s") % self.doctype) + + def convert_to_recurring(self, autoname, posting_date): + if self.convert_into_recurring: + if not self.recurring_id: + frappe.db.set(self, "recurring_id", + make_autoname(autoname)) + + self.set_next_date(posting_date) + + elif self.recurring_id: + frappe.db.sql("""update `tab%s` \ + set convert_into_recurring = 0 \ + where recurring_id = %s""", % (self.doctype, '%s'), (self.recurring_id)) + + def validate_notification_email_id(self): + if self.notification_email_address: + email_list = filter(None, [cstr(email).strip() for email in + self.notification_email_address.replace("\n", "").split(",")]) + + from frappe.utils import validate_email_add + for email in email_list: + if not validate_email_add(email): + throw(_("{0} is an invalid email address in 'Notification \ + Email Address'").format(email)) + + else: + frappe.throw(_("'Notification Email Addresses' not specified for recurring %s") \ + % self.doctype) + + def set_next_date(self, posting_date): + """ Set next date on which recurring document will be created""" + from erpnext.controllers.recurring_document import get_next_date + + if not self.repeat_on_day_of_month: + msgprint(_("Please enter 'Repeat on Day of Month' field value"), raise_exception=1) + + next_date = get_next_date(posting_date, month_map[self.recurring_type], + cint(self.repeat_on_day_of_month)) + + frappe.db.set(self, 'next_date', next_date) + @frappe.whitelist() def get_tax_rate(account_head): diff --git a/erpnext/controllers/recurring_document.py b/erpnext/controllers/recurring_document.py new file mode 100644 index 0000000000..ad32371b86 --- /dev/null +++ b/erpnext/controllers/recurring_document.py @@ -0,0 +1,121 @@ +from __future__ import unicode_literals +import frappe +import frappe.utils +import frappe.defaults + +from frappe.utils import add_days, cint, cstr, date_diff, flt, getdate, nowdate, \ + get_first_day, get_last_day, comma_and +from frappe.model.naming import make_autoname + +from frappe import _, msgprint, throw +from erpnext.accounts.party import get_party_account, get_due_date, get_party_details +from frappe.model.mapper import get_mapped_doc + +def manage_recurring_documents(doctype, next_date=None, commit=True): + """ + Create recurring documents on specific date by copying the original one + and notify the concerned people + """ + next_date = next_date or nowdate() + recurring_documents = frappe.db.sql("""select name, recurring_id + from `tab%s` where ifnull(convert_into_recurring, 0)=1 + and docstatus=1 and next_date=%s + and next_date <= ifnull(end_date, '2199-12-31')""", % (doctype, '%s'), (next_date)) + + exception_list = [] + for ref_document, recurring_id in recurring_documents: + if not frappe.db.sql("""select name from `tab%s` + where transaction_date=%s and recurring_id=%s and docstatus=1""", + % (doctype, '%s', '%s'), (next_date, recurring_id)): + try: + ref_wrapper = frappe.get_doc(doctype, ref_document) + new_document_wrapper = make_new_document(ref_wrapper, next_date) + send_notification(new_document_wrapper) + if commit: + frappe.db.commit() + except: + if commit: + frappe.db.rollback() + + frappe.db.begin() + frappe.db.sql("update `tab%s` \ + set convert_into_recurring = 0 where name = %s", % (doctype, '%s'), + (ref_document)) + notify_errors(ref_document, doctype, ref_wrapper.customer, ref_wrapper.owner) + frappe.db.commit() + + exception_list.append(frappe.get_traceback()) + finally: + if commit: + frappe.db.begin() + + if exception_list: + exception_message = "\n\n".join([cstr(d) for d in exception_list]) + frappe.throw(exception_message) + +def make_new_document(ref_wrapper, posting_date): + from erpnext.accounts.utils import get_fiscal_year + new_document = frappe.copy_doc(ref_wrapper) + + mcount = month_map[ref_wrapper.recurring_type] + + period_from = get_next_date(ref_wrapper.period_from, mcount) + + # get last day of the month to maintain period if the from date is first day of its own month + # and to date is the last day of its own month + if (cstr(get_first_day(ref_wrapper.period_from)) == \ + cstr(ref_wrapper.period_from)) and \ + (cstr(get_last_day(ref_wrapper.period_to)) == \ + cstr(ref_wrapper.period_to)): + period_to = get_last_day(get_next_date(ref_wrapper.period_to, + mcount)) + else: + period_to = get_next_date(ref_wrapper.period_to, mcount) + + new_document.update({ + "transaction_date": posting_date, + "period_from": period_from, + "period_to": period_to, + "fiscal_year": get_fiscal_year(posting_date)[0], + "owner": ref_wrapper.owner, + }) + + if ref_wrapper.doctype == "Sales Order": + new_document.update({ + "delivery_date": get_next_date(ref_wrapper.delivery_date, mcount, + cint(ref_wrapper.repeat_on_day_of_month)) + }) + + new_document.submit() + + return new_document + +def get_next_date(dt, mcount, day=None): + dt = getdate(dt) + + from dateutil.relativedelta import relativedelta + dt += relativedelta(months=mcount, day=day) + + return dt + +def send_notification(new_rv): + """Notify concerned persons about recurring document generation""" + frappe.sendmail(new_rv.notification_email_address, + 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 = [{ + "fname": new_rv.name + ".pdf", + "fcontent": frappe.get_print_format(new_rv.doctype, new_rv.name, as_pdf=True) + }]) + +def notify_errors(doc, doctype, customer, owner): + from frappe.utils.user import get_system_managers + 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), + message = frappe.get_template("templates/emails/recurring_sales_invoice_failed.html").render({ + "type": doctype, + "name": doc, + "customer": customer + })) \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index fb7e360940..e418ee91db 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1,5 +1,6 @@ { - "allow_import": 1, + "allow_attach": 1, + "allow_import": 1, "autoname": "naming_series:", "creation": "2013-06-18 12:39:59", "docstatus": 0, @@ -169,6 +170,24 @@ "search_index": 1, "width": "160px" }, + { + "allow_on_submit": 1, + "description": "Start date of current order's period", + "fieldname": "period_from", + "fieldtype": "Date", + "label": "Order Period From", + "no_copy": 1, + "permlevel": 0 + }, + { + "allow_on_submit": 1, + "description": "End date of current order's period", + "fieldname": "period_to", + "fieldtype": "Date", + "label": "Order Period To", + "no_copy": 1, + "permlevel": 0 + }, { "description": "Customer's Purchase Order Number", "fieldname": "po_no", @@ -888,13 +907,121 @@ "options": "Sales Team", "permlevel": 0, "print_hide": 1 + }, + { + "fieldname": "recurring_order", + "fieldtype": "Section Break", + "label": "Recurring Order", + "options": "icon-time", + "permlevel": 0 + }, + { + "fieldname": "column_break82", + "fieldtype": "Column Break", + "label": "Column Break", + "permlevel": 0 + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.docstatus<2", + "description": "Check if recurring order, uncheck to stop recurring or put proper End Date", + "fieldname": "convert_into_recurring", + "fieldtype": "Check", + "label": "Convert into Recurring Order", + "no_copy": 1, + "permlevel": 0, + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.convert_into_recurring==1", + "description": "Select the period when the invoice will be generated automatically", + "fieldname": "recurring_type", + "fieldtype": "Select", + "label": "Recurring Type", + "no_copy": 1, + "options": "\nMonthly\nQuarterly\nHalf-yearly\nYearly", + "permlevel": 0, + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.convert_into_recurring==1", + "description": "The day of the month on which auto order will be generated e.g. 05, 28 etc ", + "fieldname": "repeat_on_day_of_month", + "fieldtype": "Int", + "label": "Repeat on Day of Month", + "no_copy": 1, + "permlevel": 0, + "print_hide": 1 + }, + { + "depends_on": "eval:doc.convert_into_recurring==1", + "description": "The date on which next invoice will be generated. It is generated on submit.", + "fieldname": "next_date", + "fieldtype": "Date", + "label": "Next Date", + "no_copy": 1, + "permlevel": 0, + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.convert_into_recurring==1", + "description": "The date on which recurring order will be stop", + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date", + "no_copy": 1, + "permlevel": 0, + "print_hide": 1 + }, + { + "fieldname": "column_break83", + "fieldtype": "Column Break", + "label": "Column Break", + "permlevel": 0, + "print_hide": 1 + }, + { + "depends_on": "eval:doc.convert_into_recurring==1", + "fieldname": "recurring_id", + "fieldtype": "Data", + "label": "Recurring Id", + "no_copy": 1, + "permlevel": 0, + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.convert_into_recurring==1", + "description": "Enter email id separated by commas, order will be mailed automatically on particular date", + "fieldname": "notification_email_address", + "fieldtype": "Small Text", + "ignore_user_permissions": 0, + "label": "Notification Email Address", + "no_copy": 1, + "permlevel": 0, + "print_hide": 1 + }, + { + "fieldname": "against_income_account", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Against Income Account", + "no_copy": 1, + "permlevel": 0, + "print_hide": 1, + "report_hide": 1 } ], "icon": "icon-file-text", "idx": 1, "is_submittable": 1, "issingle": 0, - "modified": "2014-08-11 07:28:11.362232", + "modified": "2014-08-25 17:41:39.456399", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 37b26fdb48..37aca0a8ba 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -120,6 +120,8 @@ class SalesOrder(SellingController): if not self.billing_status: self.billing_status = 'Not Billed' if not self.delivery_status: self.delivery_status = 'Not Delivered' + self.validate_recurring_document() + def validate_warehouse(self): from erpnext.stock.utils import validate_warehouse_company @@ -161,6 +163,8 @@ class SalesOrder(SellingController): self.update_prevdoc_status('submit') frappe.db.set(self, 'status', 'Submitted') + + self.convert_to_recurring("SO/REC/.#####", self.transaction_date) def on_cancel(self): # Cannot cancel stopped SO @@ -249,6 +253,10 @@ class SalesOrder(SellingController): def get_portal_page(self): return "order" if self.docstatus==1 else None + def on_update_after_submit(self): + self.validate_recurring_document() + self.convert_to_recurring("SO/REC/.#####", self.transaction_date) + @frappe.whitelist() def make_material_request(source_name, target_doc=None): diff --git a/erpnext/templates/emails/recurring_invoice_failed.html b/erpnext/templates/emails/recurring_invoice_failed.html index 39690d8a85..a216e286a5 100644 --- a/erpnext/templates/emails/recurring_invoice_failed.html +++ b/erpnext/templates/emails/recurring_invoice_failed.html @@ -1,12 +1,12 @@ -

Recurring Invoice Failed

+

Recurring {{ type }} Failed

-

An error occured while creating recurring invoice {{ name }} for {{ customer }}.

-

This could be because of some invalid email ids in the invoice.

+

An error occured while creating recurring {{ type }} {{ name }} for {{ customer }}.

+

This could be because of some invalid email ids in the {{ type }}.

To stop sending repetitive error notifications from the system, we have unchecked -"Convert into Recurring" field in the invoice {{ name }}.

-

Please correct the invoice and make the invoice recurring again.

+"Convert into Recurring" field in the {{ type }} {{ name }}.

+

Please correct the {{ type }} and make the {{ type }} recurring again.


-

It is necessary to take this action today itself for the above mentioned recurring invoice +

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 invoice for generating the recurring invoice.

+of this {{ type }} for generating the recurring {{ type }}.

[This email is autogenerated]

From ac085e0f59d1f335af12f79393e1373c53a5950a Mon Sep 17 00:00:00 2001 From: ankitjavalkarwork Date: Tue, 26 Aug 2014 10:40:23 +0530 Subject: [PATCH 2/6] Add manage_recurring_documents and path to hooks, fix minor issues --- .../doctype/sales_invoice/sales_invoice.py | 170 +----------------- erpnext/controllers/accounts_controller.py | 6 +- erpnext/controllers/recurring_document.py | 16 +- erpnext/hooks.py | 2 +- 4 files changed, 21 insertions(+), 173 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 69a7def900..fc72562565 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -103,7 +103,7 @@ class SalesInvoice(SellingController): self.update_c_form() self.update_time_log_batch(self.name) - self.convert_to_recurring("RECINV.#####", self.transaction_date) + self.convert_to_recurring("RECINV.#####", self.posting_date) def before_cancel(self): self.update_time_log_batch(None) @@ -145,7 +145,7 @@ class SalesInvoice(SellingController): def on_update_after_submit(self): self.validate_recurring_document() - self.convert_to_recurring("RECINV.#####", self.transaction_date) + self.convert_to_recurring("RECINV.#####", self.posting_date) def get_portal_page(self): return "invoice" if self.docstatus==1 else None @@ -592,172 +592,6 @@ class SalesInvoice(SellingController): grand_total = %s where invoice_no = %s and parent = %s""", (self.name, self.amended_from, self.c_form_no)) -# def validate_recurring_invoice(self): -# if self.convert_into_recurring_invoice: -# self.validate_notification_email_id() - -# if not self.recurring_type: -# msgprint(_("Please select {0}").format(self.meta.get_label("recurring_type")), -# raise_exception=1) - -# elif not (self.period_from and \ -# self.period_to): -# throw(_("Invoice Period From and Invoice Period To dates mandatory for recurring invoice")) - -# def convert_to_recurring(self): -# if self.convert_into_recurring_invoice: -# if not self.recurring_id: -# frappe.db.set(self, "recurring_id", -# make_autoname("RECINV/.#####")) - -# self.set_next_date() - -# elif self.recurring_id: -# frappe.db.sql("""update `tabSales Invoice` -# set convert_into_recurring_invoice = 0 -# where recurring_id = %s""", (self.recurring_id,)) - -# def validate_notification_email_id(self): -# if self.notification_email_address: -# email_list = filter(None, [cstr(email).strip() for email in -# self.notification_email_address.replace("\n", "").split(",")]) - -# from frappe.utils import validate_email_add -# for email in email_list: -# if not validate_email_add(email): -# throw(_("{0} is an invalid email address in 'Notification Email Address'").format(email)) - -# else: -# throw(_("'Notification Email Addresses' not specified for recurring invoice")) - -# def set_next_date(self): -# """ Set next date on which auto invoice will be created""" -# if not self.repeat_on_day_of_month: -# msgprint(_("Please enter 'Repeat on Day of Month' field value"), raise_exception=1) - -# next_date = get_next_date(self.posting_date, -# month_map[self.recurring_type], cint(self.repeat_on_day_of_month)) - -# frappe.db.set(self, 'next_date', next_date) - -# def get_next_date(dt, mcount, day=None): -# dt = getdate(dt) - -# from dateutil.relativedelta import relativedelta -# dt += relativedelta(months=mcount, day=day) - -# return dt - -# def manage_recurring_invoices(next_date=None, commit=True): -# """ -# Create recurring invoices on specific date by copying the original one -# and notify the concerned people -# """ -# next_date = next_date or nowdate() -# recurring_invoices = frappe.db.sql("""select name, recurring_id -# from `tabSales Invoice` where ifnull(convert_into_recurring_invoice, 0)=1 -# and docstatus=1 and next_date=%s -# and next_date <= ifnull(end_date, '2199-12-31')""", next_date) - -# exception_list = [] -# for ref_invoice, recurring_id in recurring_invoices: -# if not frappe.db.sql("""select name from `tabSales Invoice` -# where posting_date=%s and recurring_id=%s and docstatus=1""", -# (next_date, recurring_id)): -# try: -# ref_wrapper = frappe.get_doc('Sales Invoice', ref_invoice) -# new_invoice_wrapper = make_new_invoice(ref_wrapper, next_date) -# send_notification(new_invoice_wrapper) -# if commit: -# frappe.db.commit() -# except: -# if commit: -# frappe.db.rollback() - -# frappe.db.begin() -# frappe.db.sql("update `tabSales Invoice` set \ -# convert_into_recurring_invoice = 0 where name = %s", ref_invoice) -# notify_errors(ref_invoice, ref_wrapper.customer, ref_wrapper.owner) -# frappe.db.commit() - -# exception_list.append(frappe.get_traceback()) -# finally: -# if commit: -# frappe.db.begin() - -# if exception_list: -# exception_message = "\n\n".join([cstr(d) for d in exception_list]) -# frappe.throw(exception_message) - -# def make_new_invoice(ref_wrapper, posting_date): -# from erpnext.accounts.utils import get_fiscal_year -# new_invoice = frappe.copy_doc(ref_wrapper) - -# mcount = month_map[ref_wrapper.recurring_type] - -# period_from = get_next_date(ref_wrapper.period_from, mcount) - -# # get last day of the month to maintain period if the from date is first day of its own month -# # and to date is the last day of its own month -# if (cstr(get_first_day(ref_wrapper.period_from)) == \ -# cstr(ref_wrapper.period_from)) and \ -# (cstr(get_last_day(ref_wrapper.period_to)) == \ -# cstr(ref_wrapper.period_to)): -# period_to = get_last_day(get_next_date(ref_wrapper.period_to, -# mcount)) -# else: -# period_to = get_next_date(ref_wrapper.period_to, mcount) - -# new_invoice.update({ -# "posting_date": posting_date, -# "aging_date": posting_date, -# "due_date": add_days(posting_date, cint(date_diff(ref_wrapper.due_date, -# ref_wrapper.posting_date))), -# "period_from": period_from, -# "period_to": period_to, -# "fiscal_year": get_fiscal_year(posting_date)[0], -# "owner": ref_wrapper.owner, -# }) - -# new_invoice.submit() - -# return new_invoice - -# def send_notification(new_rv): -# """Notify concerned persons about recurring invoice generation""" -# frappe.sendmail(new_rv.notification_email_address, -# subject="New Invoice : " + new_rv.name, -# message = _("Please find attached Sales Invoice #{0}").format(new_rv.name), -# attachments = [{ -# "fname": new_rv.name + ".pdf", -# "fcontent": frappe.get_print_format(new_rv.doctype, new_rv.name, as_pdf=True) -# }]) - -# def notify_errors(inv, customer, owner): -# from frappe.utils.user import get_system_managers -# recipients=get_system_managers(only_name=True) - -# frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")], -# subject="[Urgent] Error while creating recurring invoice for %s" % inv, -# message = frappe.get_template("templates/emails/recurring_invoice_failed.html").render({ -# "name": inv, -# "customer": customer -# })) - - assign_task_to_owner(inv, "Recurring Invoice Failed", recipients) - -def assign_task_to_owner(inv, msg, users): - for d in users: - from frappe.widgets.form import assign_to - args = { - 'assign_to' : d, - 'doctype' : 'Sales Invoice', - 'name' : inv, - 'description' : msg, - 'priority' : 'High' - } - assign_to.add(args) - @frappe.whitelist() def get_bank_cash_account(mode_of_payment): val = frappe.db.get_value("Mode of Payment", mode_of_payment, "default_account") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9aa93ac8a0..d9705c25ec 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -464,9 +464,9 @@ class AccountsController(TransactionBase): self.set_next_date(posting_date) elif self.recurring_id: - frappe.db.sql("""update `tab%s` \ - set convert_into_recurring = 0 \ - where recurring_id = %s""", % (self.doctype, '%s'), (self.recurring_id)) + frappe.db.sql("""update `tab%s` + set convert_into_recurring = 0 + where recurring_id = %s""" % (self.doctype, '%s'), (self.recurring_id)) def validate_notification_email_id(self): if self.notification_email_address: diff --git a/erpnext/controllers/recurring_document.py b/erpnext/controllers/recurring_document.py index ad32371b86..24e3845205 100644 --- a/erpnext/controllers/recurring_document.py +++ b/erpnext/controllers/recurring_document.py @@ -118,4 +118,18 @@ def notify_errors(doc, doctype, customer, owner): "type": doctype, "name": doc, "customer": customer - })) \ No newline at end of file + })) + + assign_task_to_owner(doc, doctype, "Recurring Invoice Failed", recipients) + +def assign_task_to_owner(doc, doctype, msg, users): + for d in users: + from frappe.widgets.form import assign_to + args = { + 'assign_to' : d, + 'doctype' : doctype, + 'name' : doc, + 'description' : msg, + 'priority' : 'High' + } + assign_to.add(args) \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index df15916f7a..5466f2a0d8 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -64,7 +64,7 @@ scheduler_events = { "erpnext.selling.doctype.lead.get_leads.get_leads" ], "daily": [ - "erpnext.accounts.doctype.sales_invoice.sales_invoice.manage_recurring_invoices", + "erpnext.controllers.recurring_document.manage_recurring_documents" "erpnext.stock.utils.reorder_item", "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.support.doctype.support_ticket.support_ticket.auto_close_tickets" From e60822b094bb5cd369f17a2da7e63a082b2df572 Mon Sep 17 00:00:00 2001 From: ankitjavalkarwork Date: Tue, 26 Aug 2014 14:29:06 +0530 Subject: [PATCH 3/6] Add tests for Recurring Document, Sales Inv, Sales Order, fix minor errors --- .../sales_invoice/test_sales_invoice.py | 233 +++++++++--------- erpnext/controllers/accounts_controller.py | 38 +-- erpnext/controllers/recurring_document.py | 23 +- erpnext/controllers/tests/__init__.py | 1 + .../tests/test_recurring_document.py | 165 +++++++++++++ .../doctype/sales_order/test_sales_order.py | 5 + 6 files changed, 326 insertions(+), 139 deletions(-) create mode 100644 erpnext/controllers/tests/__init__.py create mode 100644 erpnext/controllers/tests/test_recurring_document.py diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 44bd451ed8..c84d172129 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -665,143 +665,148 @@ class TestSalesInvoice(unittest.TestCase): where against_invoice=%s""", si.name)) def test_recurring_invoice(self): - from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate - from erpnext.accounts.utils import get_fiscal_year - today = nowdate() - base_si = frappe.copy_doc(test_records[0]) - base_si.update({ - "convert_into_recurring_invoice": 1, - "recurring_type": "Monthly", - "notification_email_address": "test@example.com, test1@example.com, test2@example.com", - "repeat_on_day_of_month": getdate(today).day, - "posting_date": today, - "due_date": None, - "fiscal_year": get_fiscal_year(today)[0], - "period_from": get_first_day(today), - "period_to": get_last_day(today) - }) + from erpnext.controllers.tests.test_recurring_document import test_recurring_document - # monthly - si1 = frappe.copy_doc(base_si) - si1.insert() - si1.submit() - self._test_recurring_invoice(si1, True) + test_recurring_document(self, test_records) - # monthly without a first and last day period - si2 = frappe.copy_doc(base_si) - si2.update({ - "period_from": today, - "period_to": add_to_date(today, days=30) - }) - si2.insert() - si2.submit() - self._test_recurring_invoice(si2, False) + # def test_recurring_invoice(self): + # from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate + # from erpnext.accounts.utils import get_fiscal_year + # today = nowdate() + # base_si = frappe.copy_doc(test_records[0]) + # base_si.update({ + # "convert_into_recurring_invoice": 1, + # "recurring_type": "Monthly", + # "notification_email_address": "test@example.com, test1@example.com, test2@example.com", + # "repeat_on_day_of_month": getdate(today).day, + # "posting_date": today, + # "due_date": None, + # "fiscal_year": get_fiscal_year(today)[0], + # "period_from": get_first_day(today), + # "period_to": get_last_day(today) + # }) - # quarterly - si3 = frappe.copy_doc(base_si) - si3.update({ - "recurring_type": "Quarterly", - "period_from": get_first_day(today), - "period_to": get_last_day(add_to_date(today, months=3)) - }) - si3.insert() - si3.submit() - self._test_recurring_invoice(si3, True) + # # monthly + # si1 = frappe.copy_doc(base_si) + # si1.insert() + # si1.submit() + # self._test_recurring_invoice(si1, True) - # quarterly without a first and last day period - si4 = frappe.copy_doc(base_si) - si4.update({ - "recurring_type": "Quarterly", - "period_from": today, - "period_to": add_to_date(today, months=3) - }) - si4.insert() - si4.submit() - self._test_recurring_invoice(si4, False) + # # monthly without a first and last day period + # si2 = frappe.copy_doc(base_si) + # si2.update({ + # "period_from": today, + # "period_to": add_to_date(today, days=30) + # }) + # si2.insert() + # si2.submit() + # self._test_recurring_invoice(si2, False) - # yearly - si5 = frappe.copy_doc(base_si) - si5.update({ - "recurring_type": "Yearly", - "period_from": get_first_day(today), - "period_to": get_last_day(add_to_date(today, years=1)) - }) - si5.insert() - si5.submit() - self._test_recurring_invoice(si5, True) + # # quarterly + # si3 = frappe.copy_doc(base_si) + # si3.update({ + # "recurring_type": "Quarterly", + # "period_from": get_first_day(today), + # "period_to": get_last_day(add_to_date(today, months=3)) + # }) + # si3.insert() + # si3.submit() + # self._test_recurring_invoice(si3, True) - # yearly without a first and last day period - si6 = frappe.copy_doc(base_si) - si6.update({ - "recurring_type": "Yearly", - "period_from": today, - "period_to": add_to_date(today, years=1) - }) - si6.insert() - si6.submit() - self._test_recurring_invoice(si6, False) + # # quarterly without a first and last day period + # si4 = frappe.copy_doc(base_si) + # si4.update({ + # "recurring_type": "Quarterly", + # "period_from": today, + # "period_to": add_to_date(today, months=3) + # }) + # si4.insert() + # si4.submit() + # self._test_recurring_invoice(si4, False) - # change posting date but keep recuring day to be today - si7 = frappe.copy_doc(base_si) - si7.update({ - "posting_date": add_to_date(today, days=-1) - }) - si7.insert() - si7.submit() + # # yearly + # si5 = frappe.copy_doc(base_si) + # si5.update({ + # "recurring_type": "Yearly", + # "period_from": get_first_day(today), + # "period_to": get_last_day(add_to_date(today, years=1)) + # }) + # si5.insert() + # si5.submit() + # self._test_recurring_invoice(si5, True) - # setting so that _test function works - si7.posting_date = today - self._test_recurring_invoice(si7, True) + # # yearly without a first and last day period + # si6 = frappe.copy_doc(base_si) + # si6.update({ + # "recurring_type": "Yearly", + # "period_from": today, + # "period_to": add_to_date(today, years=1) + # }) + # si6.insert() + # si6.submit() + # self._test_recurring_invoice(si6, False) - def _test_recurring_invoice(self, base_si, first_and_last_day): - from frappe.utils import add_months, get_last_day - from erpnext.accounts.doctype.sales_invoice.sales_invoice \ - import manage_recurring_invoices, get_next_date + # # change posting date but keep recuring day to be today + # si7 = frappe.copy_doc(base_si) + # si7.update({ + # "posting_date": add_to_date(today, days=-1) + # }) + # si7.insert() + # si7.submit() - no_of_months = ({"Monthly": 1, "Quarterly": 3, "Yearly": 12})[base_si.recurring_type] + # # setting so that _test function works + # si7.posting_date = today + # self._test_recurring_invoice(si7, True) - def _test(i): - self.assertEquals(i+1, frappe.db.sql("""select count(*) from `tabSales Invoice` - where recurring_id=%s and docstatus=1""", base_si.recurring_id)[0][0]) + # def _test_recurring_invoice(self, base_si, first_and_last_day): + # from frappe.utils import add_months, get_last_day + # from erpnext.accounts.doctype.sales_invoice.sales_invoice \ + # import manage_recurring_invoices, get_next_date - next_date = get_next_date(base_si.posting_date, no_of_months, - base_si.repeat_on_day_of_month) + # no_of_months = ({"Monthly": 1, "Quarterly": 3, "Yearly": 12})[base_si.recurring_type] - manage_recurring_invoices(next_date=next_date, commit=False) + # def _test(i): + # self.assertEquals(i+1, frappe.db.sql("""select count(*) from `tabSales Invoice` + # where recurring_id=%s and docstatus=1""", base_si.recurring_id)[0][0]) - recurred_invoices = frappe.db.sql("""select name from `tabSales Invoice` - where recurring_id=%s and docstatus=1 order by name desc""", - base_si.recurring_id) + # next_date = get_next_date(base_si.posting_date, no_of_months, + # base_si.repeat_on_day_of_month) - self.assertEquals(i+2, len(recurred_invoices)) + # manage_recurring_invoices(next_date=next_date, commit=False) - new_si = frappe.get_doc("Sales Invoice", recurred_invoices[0][0]) + # recurred_invoices = frappe.db.sql("""select name from `tabSales Invoice` + # where recurring_id=%s and docstatus=1 order by name desc""", + # base_si.recurring_id) - for fieldname in ["convert_into_recurring_invoice", "recurring_type", - "repeat_on_day_of_month", "notification_email_address"]: - self.assertEquals(base_si.get(fieldname), - new_si.get(fieldname)) + # self.assertEquals(i+2, len(recurred_invoices)) - self.assertEquals(new_si.posting_date, unicode(next_date)) + # new_si = frappe.get_doc("Sales Invoice", recurred_invoices[0][0]) - self.assertEquals(new_si.period_from, - unicode(add_months(base_si.period_from, no_of_months))) + # for fieldname in ["convert_into_recurring_invoice", "recurring_type", + # "repeat_on_day_of_month", "notification_email_address"]: + # self.assertEquals(base_si.get(fieldname), + # new_si.get(fieldname)) - if first_and_last_day: - self.assertEquals(new_si.period_to, - unicode(get_last_day(add_months(base_si.period_to, - no_of_months)))) - else: - self.assertEquals(new_si.period_to, - unicode(add_months(base_si.period_to, no_of_months))) + # self.assertEquals(new_si.posting_date, unicode(next_date)) + + # self.assertEquals(new_si.period_from, + # unicode(add_months(base_si.period_from, no_of_months))) + + # if first_and_last_day: + # self.assertEquals(new_si.period_to, + # unicode(get_last_day(add_months(base_si.period_to, + # no_of_months)))) + # else: + # self.assertEquals(new_si.period_to, + # unicode(add_months(base_si.period_to, no_of_months))) - return new_si + # return new_si - # if yearly, test 1 repetition, else test 5 repetitions - count = 1 if (no_of_months == 12) else 5 - for i in xrange(count): - base_si = _test(i) + # # if yearly, test 1 repetition, else test 5 repetitions + # count = 1 if (no_of_months == 12) else 5 + # for i in xrange(count): + # base_si = _test(i) def clear_stock_account_balance(self): frappe.db.sql("delete from `tabStock Ledger Entry`") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d9705c25ec..4a23673630 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -4,7 +4,9 @@ from __future__ import unicode_literals import frappe from frappe import _, throw -from frappe.utils import flt, cint, today +from frappe.utils import add_days, cint, cstr, today, date_diff, flt, getdate, nowdate, \ + get_first_day, get_last_day +from frappe.model.naming import make_autoname from erpnext.setup.utils import get_company_currency, get_exchange_rate from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year from erpnext.utilities.transaction_base import TransactionBase @@ -428,22 +430,6 @@ class AccountsController(TransactionBase): return stock_items - @property - def company_abbr(self): - if not hasattr(self, "_abbr"): - self._abbr = frappe.db.get_value("Company", self.company, "abbr") - - return self._abbr - - def check_credit_limit(self, account): - total_outstanding = frappe.db.sql(""" - select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) - from `tabGL Entry` where account = %s""", account) - - total_outstanding = total_outstanding[0][0] if total_outstanding else 0 - if total_outstanding: - frappe.get_doc('Account', account).check_credit_limit(total_outstanding) - def validate_recurring_document(self): if self.convert_into_recurring: self.validate_notification_email_id() @@ -468,6 +454,22 @@ class AccountsController(TransactionBase): set convert_into_recurring = 0 where recurring_id = %s""" % (self.doctype, '%s'), (self.recurring_id)) + @property + def company_abbr(self): + if not hasattr(self, "_abbr"): + self._abbr = frappe.db.get_value("Company", self.company, "abbr") + + return self._abbr + + def check_credit_limit(self, account): + total_outstanding = frappe.db.sql(""" + select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) + from `tabGL Entry` where account = %s""", account) + + total_outstanding = total_outstanding[0][0] if total_outstanding else 0 + if total_outstanding: + frappe.get_doc('Account', account).check_credit_limit(total_outstanding) + def validate_notification_email_id(self): if self.notification_email_address: email_list = filter(None, [cstr(email).strip() for email in @@ -487,6 +489,8 @@ class AccountsController(TransactionBase): """ Set next date on which recurring document will be created""" from erpnext.controllers.recurring_document import get_next_date + month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12} + if not self.repeat_on_day_of_month: msgprint(_("Please enter 'Repeat on Day of Month' field value"), raise_exception=1) diff --git a/erpnext/controllers/recurring_document.py b/erpnext/controllers/recurring_document.py index 24e3845205..d3c7809614 100644 --- a/erpnext/controllers/recurring_document.py +++ b/erpnext/controllers/recurring_document.py @@ -11,25 +11,33 @@ from frappe import _, msgprint, throw from erpnext.accounts.party import get_party_account, get_due_date, get_party_details from frappe.model.mapper import get_mapped_doc +month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12} + def manage_recurring_documents(doctype, next_date=None, commit=True): """ Create recurring documents on specific date by copying the original one and notify the concerned people """ next_date = next_date or nowdate() + + if doctype == "Sales Order": + date_field = "transaction_date" + elif doctype == "Sales Invoice": + date_field = "posting_date" + recurring_documents = frappe.db.sql("""select name, recurring_id from `tab%s` where ifnull(convert_into_recurring, 0)=1 and docstatus=1 and next_date=%s - and next_date <= ifnull(end_date, '2199-12-31')""", % (doctype, '%s'), (next_date)) + and next_date <= ifnull(end_date, '2199-12-31')""" % (doctype, '%s'), (next_date)) exception_list = [] for ref_document, recurring_id in recurring_documents: if not frappe.db.sql("""select name from `tab%s` - where transaction_date=%s and recurring_id=%s and docstatus=1""", - % (doctype, '%s', '%s'), (next_date, recurring_id)): + where %s=%s and recurring_id=%s and docstatus=1""" + % (doctype, date_field, '%s', '%s'), (next_date, recurring_id)): try: ref_wrapper = frappe.get_doc(doctype, ref_document) - new_document_wrapper = make_new_document(ref_wrapper, next_date) + new_document_wrapper = make_new_document(ref_wrapper, date_field, next_date) send_notification(new_document_wrapper) if commit: frappe.db.commit() @@ -39,7 +47,7 @@ def manage_recurring_documents(doctype, next_date=None, commit=True): frappe.db.begin() frappe.db.sql("update `tab%s` \ - set convert_into_recurring = 0 where name = %s", % (doctype, '%s'), + set convert_into_recurring = 0 where name = %s" % (doctype, '%s'), (ref_document)) notify_errors(ref_document, doctype, ref_wrapper.customer, ref_wrapper.owner) frappe.db.commit() @@ -53,10 +61,9 @@ def manage_recurring_documents(doctype, next_date=None, commit=True): exception_message = "\n\n".join([cstr(d) for d in exception_list]) frappe.throw(exception_message) -def make_new_document(ref_wrapper, posting_date): +def make_new_document(ref_wrapper, date_field, posting_date): from erpnext.accounts.utils import get_fiscal_year new_document = frappe.copy_doc(ref_wrapper) - mcount = month_map[ref_wrapper.recurring_type] period_from = get_next_date(ref_wrapper.period_from, mcount) @@ -73,7 +80,7 @@ def make_new_document(ref_wrapper, posting_date): period_to = get_next_date(ref_wrapper.period_to, mcount) new_document.update({ - "transaction_date": posting_date, + date_field: posting_date, "period_from": period_from, "period_to": period_to, "fiscal_year": get_fiscal_year(posting_date)[0], diff --git a/erpnext/controllers/tests/__init__.py b/erpnext/controllers/tests/__init__.py new file mode 100644 index 0000000000..60bec4fbec --- /dev/null +++ b/erpnext/controllers/tests/__init__.py @@ -0,0 +1 @@ +from erpnext.__version__ import __version__ diff --git a/erpnext/controllers/tests/test_recurring_document.py b/erpnext/controllers/tests/test_recurring_document.py new file mode 100644 index 0000000000..d31f6324bb --- /dev/null +++ b/erpnext/controllers/tests/test_recurring_document.py @@ -0,0 +1,165 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +import unittest, json, copy +from frappe.utils import flt +import frappe.permissions +from erpnext.accounts.utils import get_stock_and_account_difference +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory +from erpnext.projects.doctype.time_log_batch.test_time_log_batch import * + +def test_recurring_document(obj, test_records): + from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate, add_days + from erpnext.accounts.utils import get_fiscal_year + today = nowdate() + base_doc = frappe.copy_doc(test_records[0]) + + base_doc.update({ + "convert_into_recurring": 1, + "recurring_type": "Monthly", + "notification_email_address": "test@example.com, test1@example.com, test2@example.com", + "repeat_on_day_of_month": getdate(today).day, + "due_date": None, + "fiscal_year": get_fiscal_year(today)[0], + "period_from": get_first_day(today), + "period_to": get_last_day(today) + }) + + if base_doc.doctype == "Sales Order": + base_doc.update({ + "transaction_date": today, + "delivery_date": add_days(today, 15) + }) + elif base_doc.doctype == "Sales Invoice": + base_doc.update({ + "posting_date": today + }) + + if base_doc.doctype == "Sales Order": + date_field = "transaction_date" + elif base_doc.doctype == "Sales Invoice": + date_field = "posting_date" + + # monthly + doc1 = frappe.copy_doc(base_doc) + doc1.insert() + doc1.submit() + _test_recurring_document(obj, doc1, date_field, True) + + # monthly without a first and last day period + doc2 = frappe.copy_doc(base_doc) + doc2.update({ + "period_from": today, + "period_to": add_to_date(today, days=30) + }) + doc2.insert() + doc2.submit() + _test_recurring_document(obj, doc2, date_field, False) + + # quarterly + doc3 = frappe.copy_doc(base_doc) + doc3.update({ + "recurring_type": "Quarterly", + "period_from": get_first_day(today), + "period_to": get_last_day(add_to_date(today, months=3)) + }) + doc3.insert() + doc3.submit() + _test_recurring_document(obj, doc3, date_field, True) + + # quarterly without a first and last day period + doc4 = frappe.copy_doc(base_doc) + doc4.update({ + "recurring_type": "Quarterly", + "period_from": today, + "period_to": add_to_date(today, months=3) + }) + doc4.insert() + doc4.submit() + _test_recurring_document(obj, doc4, date_field, False) + + # yearly + doc5 = frappe.copy_doc(base_doc) + doc5.update({ + "recurring_type": "Yearly", + "period_from": get_first_day(today), + "period_to": get_last_day(add_to_date(today, years=1)) + }) + doc5.insert() + doc5.submit() + _test_recurring_document(obj, doc5, date_field, True) + + # yearly without a first and last day period + doc6 = frappe.copy_doc(base_doc) + doc6.update({ + "recurring_type": "Yearly", + "period_from": today, + "period_to": add_to_date(today, years=1) + }) + doc6.insert() + doc6.submit() + _test_recurring_document(obj, doc6, date_field, False) + + # change date field but keep recurring day to be today + doc7 = frappe.copy_doc(base_doc) + doc7.update({ + date_field: add_to_date(today, days=-1) + }) + doc7.insert() + doc7.submit() + + # setting so that _test function works + doc7.set(date_field, today) + _test_recurring_document(obj, doc7, date_field, True) + +def _test_recurring_document(obj, base_doc, date_field, first_and_last_day): + from frappe.utils import add_months, get_last_day + from erpnext.controllers.recurring_document import manage_recurring_documents, \ + get_next_date + + no_of_months = ({"Monthly": 1, "Quarterly": 3, "Yearly": 12})[base_doc.recurring_type] + + def _test(i): + obj.assertEquals(i+1, frappe.db.sql("""select count(*) from `tab%s` + where recurring_id=%s and docstatus=1""" % (base_doc.doctype, '%s'), + (base_doc.recurring_id))[0][0]) + + next_date = get_next_date(base_doc.get(date_field), no_of_months, + base_doc.repeat_on_day_of_month) + + manage_recurring_documents(base_doc.doctype, next_date=next_date, commit=False) + + recurred_documents = frappe.db.sql("""select name from `tab%s` + where recurring_id=%s and docstatus=1 order by name desc""" + % (base_doc.doctype, '%s'), (base_doc.recurring_id)) + + obj.assertEquals(i+2, len(recurred_documents)) + + new_doc = frappe.get_doc(base_doc.doctype, recurred_documents[0][0]) + + for fieldname in ["convert_into_recurring", "recurring_type", + "repeat_on_day_of_month", "notification_email_address"]: + obj.assertEquals(base_doc.get(fieldname), + new_doc.get(fieldname)) + + obj.assertEquals(new_doc.get(date_field), unicode(next_date)) + + obj.assertEquals(new_doc.period_from, + unicode(add_months(base_doc.period_from, no_of_months))) + + if first_and_last_day: + obj.assertEquals(new_doc.period_to, + unicode(get_last_day(add_months(base_doc.period_to, + no_of_months)))) + else: + obj.assertEquals(new_doc.period_to, + unicode(add_months(base_doc.period_to, no_of_months))) + + + return new_doc + + # if yearly, test 1 repetition, else test 5 repetitions + count = 1 if (no_of_months == 12) else 5 + for i in xrange(count): + base_doc = _test(i) \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 128c5774d9..c55b7b383f 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -331,6 +331,11 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.CancelledLinkError, delivery_note.submit) + def test_recurring_order(self): + from erpnext.controllers.tests.test_recurring_document import test_recurring_document + + test_recurring_document(self, test_records) + test_dependencies = ["Sales BOM", "Currency Exchange"] test_records = frappe.get_test_records('Sales Order') From 28a975dd322cd7c0ca1d3405c0860e990bcfc6b0 Mon Sep 17 00:00:00 2001 From: ankitjavalkarwork Date: Tue, 26 Aug 2014 15:04:56 +0530 Subject: [PATCH 4/6] Add patch for field name change in SI, rename email template --- .../sales_invoice/test_sales_invoice.py | 139 ------------------ erpnext/patches.txt | 4 + .../update_sales_order_invoice_field_name.py | 6 + ...ed.html => recurring_document_failed.html} | 0 4 files changed, 10 insertions(+), 139 deletions(-) create mode 100644 erpnext/patches/v4_2/update_sales_order_invoice_field_name.py rename erpnext/templates/emails/{recurring_invoice_failed.html => recurring_document_failed.html} (100%) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index c84d172129..8231650167 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -669,145 +669,6 @@ class TestSalesInvoice(unittest.TestCase): test_recurring_document(self, test_records) - # def test_recurring_invoice(self): - # from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate - # from erpnext.accounts.utils import get_fiscal_year - # today = nowdate() - # base_si = frappe.copy_doc(test_records[0]) - # base_si.update({ - # "convert_into_recurring_invoice": 1, - # "recurring_type": "Monthly", - # "notification_email_address": "test@example.com, test1@example.com, test2@example.com", - # "repeat_on_day_of_month": getdate(today).day, - # "posting_date": today, - # "due_date": None, - # "fiscal_year": get_fiscal_year(today)[0], - # "period_from": get_first_day(today), - # "period_to": get_last_day(today) - # }) - - # # monthly - # si1 = frappe.copy_doc(base_si) - # si1.insert() - # si1.submit() - # self._test_recurring_invoice(si1, True) - - # # monthly without a first and last day period - # si2 = frappe.copy_doc(base_si) - # si2.update({ - # "period_from": today, - # "period_to": add_to_date(today, days=30) - # }) - # si2.insert() - # si2.submit() - # self._test_recurring_invoice(si2, False) - - # # quarterly - # si3 = frappe.copy_doc(base_si) - # si3.update({ - # "recurring_type": "Quarterly", - # "period_from": get_first_day(today), - # "period_to": get_last_day(add_to_date(today, months=3)) - # }) - # si3.insert() - # si3.submit() - # self._test_recurring_invoice(si3, True) - - # # quarterly without a first and last day period - # si4 = frappe.copy_doc(base_si) - # si4.update({ - # "recurring_type": "Quarterly", - # "period_from": today, - # "period_to": add_to_date(today, months=3) - # }) - # si4.insert() - # si4.submit() - # self._test_recurring_invoice(si4, False) - - # # yearly - # si5 = frappe.copy_doc(base_si) - # si5.update({ - # "recurring_type": "Yearly", - # "period_from": get_first_day(today), - # "period_to": get_last_day(add_to_date(today, years=1)) - # }) - # si5.insert() - # si5.submit() - # self._test_recurring_invoice(si5, True) - - # # yearly without a first and last day period - # si6 = frappe.copy_doc(base_si) - # si6.update({ - # "recurring_type": "Yearly", - # "period_from": today, - # "period_to": add_to_date(today, years=1) - # }) - # si6.insert() - # si6.submit() - # self._test_recurring_invoice(si6, False) - - # # change posting date but keep recuring day to be today - # si7 = frappe.copy_doc(base_si) - # si7.update({ - # "posting_date": add_to_date(today, days=-1) - # }) - # si7.insert() - # si7.submit() - - # # setting so that _test function works - # si7.posting_date = today - # self._test_recurring_invoice(si7, True) - - # def _test_recurring_invoice(self, base_si, first_and_last_day): - # from frappe.utils import add_months, get_last_day - # from erpnext.accounts.doctype.sales_invoice.sales_invoice \ - # import manage_recurring_invoices, get_next_date - - # no_of_months = ({"Monthly": 1, "Quarterly": 3, "Yearly": 12})[base_si.recurring_type] - - # def _test(i): - # self.assertEquals(i+1, frappe.db.sql("""select count(*) from `tabSales Invoice` - # where recurring_id=%s and docstatus=1""", base_si.recurring_id)[0][0]) - - # next_date = get_next_date(base_si.posting_date, no_of_months, - # base_si.repeat_on_day_of_month) - - # manage_recurring_invoices(next_date=next_date, commit=False) - - # recurred_invoices = frappe.db.sql("""select name from `tabSales Invoice` - # where recurring_id=%s and docstatus=1 order by name desc""", - # base_si.recurring_id) - - # self.assertEquals(i+2, len(recurred_invoices)) - - # new_si = frappe.get_doc("Sales Invoice", recurred_invoices[0][0]) - - # for fieldname in ["convert_into_recurring_invoice", "recurring_type", - # "repeat_on_day_of_month", "notification_email_address"]: - # self.assertEquals(base_si.get(fieldname), - # new_si.get(fieldname)) - - # self.assertEquals(new_si.posting_date, unicode(next_date)) - - # self.assertEquals(new_si.period_from, - # unicode(add_months(base_si.period_from, no_of_months))) - - # if first_and_last_day: - # self.assertEquals(new_si.period_to, - # unicode(get_last_day(add_months(base_si.period_to, - # no_of_months)))) - # else: - # self.assertEquals(new_si.period_to, - # unicode(add_months(base_si.period_to, no_of_months))) - - - # return new_si - - # # if yearly, test 1 repetition, else test 5 repetitions - # count = 1 if (no_of_months == 12) else 5 - # for i in xrange(count): - # base_si = _test(i) - def clear_stock_account_balance(self): frappe.db.sql("delete from `tabStock Ledger Entry`") frappe.db.sql("delete from tabBin") diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d744fee784..cde43f347e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -78,4 +78,8 @@ erpnext.patches.v4_2.update_project_milestones erpnext.patches.v4_2.add_currency_turkish_lira #2014-08-08 execute:frappe.delete_doc("DocType", "Landed Cost Wizard") erpnext.patches.v4_2.default_website_style +<<<<<<< HEAD erpnext.patches.v4_2.set_company_country +======= +erpnext.patches.v4_2.update_sales_order_invoice_field_name +>>>>>>> Add patch for field name change in SI, rename email template diff --git a/erpnext/patches/v4_2/update_sales_order_invoice_field_name.py b/erpnext/patches/v4_2/update_sales_order_invoice_field_name.py new file mode 100644 index 0000000000..1ae3eb0764 --- /dev/null +++ b/erpnext/patches/v4_2/update_sales_order_invoice_field_name.py @@ -0,0 +1,6 @@ +import frappe + +def execute(): + frappe.reload_doc('selling', 'doctype', 'sales_order') + frappe.db.sql("""update `tabSales Invoice` set period_from = order_period_from, + period_to = order_period_to, convert_into_recurring = convert_into_recurring_order""") diff --git a/erpnext/templates/emails/recurring_invoice_failed.html b/erpnext/templates/emails/recurring_document_failed.html similarity index 100% rename from erpnext/templates/emails/recurring_invoice_failed.html rename to erpnext/templates/emails/recurring_document_failed.html From aaac7c17b8e210d6cb4fbf8054f6db1520adcc05 Mon Sep 17 00:00:00 2001 From: ankitjavalkarwork Date: Wed, 27 Aug 2014 19:10:10 +0530 Subject: [PATCH 5/6] Fix minor errors, fix patch, call in hooks, move from account_controller to recurring_document --- .../doctype/sales_invoice/sales_invoice.js | 18 ++-- .../doctype/sales_invoice/sales_invoice.json | 26 +++--- .../doctype/sales_invoice/sales_invoice.py | 10 +-- erpnext/controllers/accounts_controller.py | 54 ------------ erpnext/controllers/recurring_document.py | 87 +++++++++++++++---- .../tests/test_recurring_document.py | 40 ++++----- erpnext/hooks.py | 2 +- .../update_sales_order_invoice_field_name.py | 6 +- .../doctype/sales_order/sales_order.js | 31 +++++++ .../doctype/sales_order/sales_order.json | 26 +++--- .../doctype/sales_order/sales_order.py | 10 ++- 11 files changed, 172 insertions(+), 138 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 5228b0e383..73832cec65 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -228,7 +228,7 @@ cur_frm.cscript.hide_fields = function(doc) { par_flds = ['project_name', 'due_date', 'is_opening', 'source', 'total_advance', 'gross_profit', 'gross_profit_percent', 'get_advances_received', 'advance_adjustment_details', 'sales_partner', 'commission_rate', - 'total_commission', 'advances', 'period_from', 'period_to']; + 'total_commission', 'advances', 'from_date', 'to_date']; item_flds_normal = ['sales_order', 'delivery_note'] @@ -399,9 +399,9 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) { }) } -cur_frm.cscript.convert_into_recurring_invoice = function(doc, dt, dn) { +cur_frm.cscript.is_recurring = function(doc, dt, dn) { // set default values for recurring invoices - if(doc.convert_into_recurring_invoice) { + if(doc.is_recurring) { var owner_email = doc.owner=="Administrator" ? frappe.user_info("Administrator").email : doc.owner; @@ -414,18 +414,18 @@ cur_frm.cscript.convert_into_recurring_invoice = function(doc, dt, dn) { refresh_many(["notification_email_address", "repeat_on_day_of_month"]); } -cur_frm.cscript.period_from = function(doc, dt, dn) { - // set period_to - if(doc.period_from) { +cur_frm.cscript.from_date = function(doc, dt, dn) { + // set to_date + if(doc.from_date) { var recurring_type_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}; var months = recurring_type_map[doc.recurring_type]; if(months) { - var to_date = frappe.datetime.add_months(doc.period_from, + var to_date = frappe.datetime.add_months(doc.from_date, months); - doc.period_to = frappe.datetime.add_days(to_date, -1); - refresh_field('period_to'); + doc.to_date = frappe.datetime.add_days(to_date, -1); + refresh_field('to_date'); } } } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 7cab4c24f0..c26583b737 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -173,9 +173,9 @@ "allow_on_submit": 1, "depends_on": "", "description": "Start date of current invoice's period", - "fieldname": "period_from", + "fieldname": "from_date", "fieldtype": "Date", - "label": "Invoice Period From", + "label": "From", "no_copy": 1, "permlevel": 0, "print_hide": 0, @@ -185,9 +185,9 @@ "allow_on_submit": 1, "depends_on": "", "description": "End date of current invoice's period", - "fieldname": "period_to", + "fieldname": "to_date", "fieldtype": "Date", - "label": "Invoice Period To", + "label": "To", "no_copy": 1, "permlevel": 0, "print_hide": 0, @@ -1088,9 +1088,9 @@ "allow_on_submit": 1, "depends_on": "eval:doc.docstatus<2", "description": "Check if recurring invoice, uncheck to stop recurring or put proper End Date", - "fieldname": "convert_into_recurring", + "fieldname": "is_recurring", "fieldtype": "Check", - "label": "Convert into Recurring Invoice", + "label": "Is Recurring", "no_copy": 1, "permlevel": 0, "print_hide": 1, @@ -1098,7 +1098,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "description": "Select the period when the invoice will be generated automatically", "fieldname": "recurring_type", "fieldtype": "Select", @@ -1111,7 +1111,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "description": "The day of the month on which auto invoice will be generated e.g. 05, 28 etc ", "fieldname": "repeat_on_day_of_month", "fieldtype": "Int", @@ -1122,7 +1122,7 @@ "read_only": 0 }, { - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "description": "The date on which next invoice will be generated. It is generated on submit.\n", "fieldname": "next_date", "fieldtype": "Date", @@ -1134,7 +1134,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "description": "The date on which recurring invoice will be stop", "fieldname": "end_date", "fieldtype": "Date", @@ -1154,7 +1154,7 @@ "width": "50%" }, { - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "description": "The unique id for tracking all recurring invoices.\u00a0It is generated on submit.", "fieldname": "recurring_id", "fieldtype": "Data", @@ -1166,7 +1166,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "description": "Enter email id separated by commas, invoice will be mailed automatically on particular date", "fieldname": "notification_email_address", "fieldtype": "Small Text", @@ -1193,7 +1193,7 @@ "icon": "icon-file-text", "idx": 1, "is_submittable": 1, - "modified": "2014-08-25 17:41:35.367233", + "modified": "2014-08-28 11:21:00.726344", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index fc72562565..a20d906b8c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -14,7 +14,7 @@ from erpnext.accounts.party import get_party_account, get_due_date from erpnext.controllers.stock_controller import update_gl_entries_after from frappe.model.mapper import get_mapped_doc -month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12} +from erpnext.controllers.recurring_document import * from erpnext.controllers.selling_controller import SellingController @@ -75,7 +75,7 @@ class SalesInvoice(SellingController): self.set_against_income_account() self.validate_c_form() self.validate_time_logs_are_submitted() - self.validate_recurring_document() + validate_recurring_document(self) self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "delivery_note_details") @@ -103,7 +103,7 @@ class SalesInvoice(SellingController): self.update_c_form() self.update_time_log_batch(self.name) - self.convert_to_recurring("RECINV.#####", self.posting_date) + convert_to_recurring(self, "RECINV.#####", self.posting_date) def before_cancel(self): self.update_time_log_batch(None) @@ -144,8 +144,8 @@ class SalesInvoice(SellingController): }) def on_update_after_submit(self): - self.validate_recurring_document() - self.convert_to_recurring("RECINV.#####", self.posting_date) + validate_recurring_document(self) + convert_to_recurring(self, "RECINV.#####", self.posting_date) def get_portal_page(self): return "invoice" if self.docstatus==1 else None diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4a23673630..4af9f5ed8a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -430,30 +430,6 @@ class AccountsController(TransactionBase): return stock_items - def validate_recurring_document(self): - if self.convert_into_recurring: - self.validate_notification_email_id() - - if not self.recurring_type: - msgprint(_("Please select {0}").format(self.meta.get_label("recurring_type")), - raise_exception=1) - - elif not (self.period_from and self.period_to): - throw(_("Period From and Period To dates mandatory for recurring %s") % self.doctype) - - def convert_to_recurring(self, autoname, posting_date): - if self.convert_into_recurring: - if not self.recurring_id: - frappe.db.set(self, "recurring_id", - make_autoname(autoname)) - - self.set_next_date(posting_date) - - elif self.recurring_id: - frappe.db.sql("""update `tab%s` - set convert_into_recurring = 0 - where recurring_id = %s""" % (self.doctype, '%s'), (self.recurring_id)) - @property def company_abbr(self): if not hasattr(self, "_abbr"): @@ -470,36 +446,6 @@ class AccountsController(TransactionBase): if total_outstanding: frappe.get_doc('Account', account).check_credit_limit(total_outstanding) - def validate_notification_email_id(self): - if self.notification_email_address: - email_list = filter(None, [cstr(email).strip() for email in - self.notification_email_address.replace("\n", "").split(",")]) - - from frappe.utils import validate_email_add - for email in email_list: - if not validate_email_add(email): - throw(_("{0} is an invalid email address in 'Notification \ - Email Address'").format(email)) - - else: - frappe.throw(_("'Notification Email Addresses' not specified for recurring %s") \ - % self.doctype) - - def set_next_date(self, posting_date): - """ Set next date on which recurring document will be created""" - from erpnext.controllers.recurring_document import get_next_date - - month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12} - - if not self.repeat_on_day_of_month: - msgprint(_("Please enter 'Repeat on Day of Month' field value"), raise_exception=1) - - next_date = get_next_date(posting_date, month_map[self.recurring_type], - cint(self.repeat_on_day_of_month)) - - frappe.db.set(self, 'next_date', next_date) - - @frappe.whitelist() def get_tax_rate(account_head): return frappe.db.get_value("Account", account_head, "tax_rate") diff --git a/erpnext/controllers/recurring_document.py b/erpnext/controllers/recurring_document.py index d3c7809614..d9326792f3 100644 --- a/erpnext/controllers/recurring_document.py +++ b/erpnext/controllers/recurring_document.py @@ -13,6 +13,10 @@ from frappe.model.mapper import get_mapped_doc month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12} +def create_recurring_documents(): + manage_recurring_documents("Sales Order") + manage_recurring_documents("Sales Invoice") + def manage_recurring_documents(doctype, next_date=None, commit=True): """ Create recurring documents on specific date by copying the original one @@ -26,9 +30,9 @@ def manage_recurring_documents(doctype, next_date=None, commit=True): date_field = "posting_date" recurring_documents = frappe.db.sql("""select name, recurring_id - from `tab%s` where ifnull(convert_into_recurring, 0)=1 - and docstatus=1 and next_date=%s - and next_date <= ifnull(end_date, '2199-12-31')""" % (doctype, '%s'), (next_date)) + from `tab{}` where ifnull(is_recurring, 0)=1 + and docstatus=1 and next_date='{}' + and next_date <= ifnull(end_date, '2199-12-31')""".format(doctype, next_date)) exception_list = [] for ref_document, recurring_id in recurring_documents: @@ -47,7 +51,7 @@ def manage_recurring_documents(doctype, next_date=None, commit=True): frappe.db.begin() frappe.db.sql("update `tab%s` \ - set convert_into_recurring = 0 where name = %s" % (doctype, '%s'), + set is_recurring = 0 where name = %s" % (doctype, '%s'), (ref_document)) notify_errors(ref_document, doctype, ref_wrapper.customer, ref_wrapper.owner) frappe.db.commit() @@ -66,23 +70,23 @@ def make_new_document(ref_wrapper, date_field, posting_date): new_document = frappe.copy_doc(ref_wrapper) mcount = month_map[ref_wrapper.recurring_type] - period_from = get_next_date(ref_wrapper.period_from, mcount) + from_date = get_next_date(ref_wrapper.from_date, mcount) # get last day of the month to maintain period if the from date is first day of its own month # and to date is the last day of its own month - if (cstr(get_first_day(ref_wrapper.period_from)) == \ - cstr(ref_wrapper.period_from)) and \ - (cstr(get_last_day(ref_wrapper.period_to)) == \ - cstr(ref_wrapper.period_to)): - period_to = get_last_day(get_next_date(ref_wrapper.period_to, + if (cstr(get_first_day(ref_wrapper.from_date)) == \ + cstr(ref_wrapper.from_date)) and \ + (cstr(get_last_day(ref_wrapper.to_date)) == \ + cstr(ref_wrapper.to_date)): + to_date = get_last_day(get_next_date(ref_wrapper.to_date, mcount)) else: - period_to = get_next_date(ref_wrapper.period_to, mcount) + to_date = get_next_date(ref_wrapper.to_date, mcount) new_document.update({ date_field: posting_date, - "period_from": period_from, - "period_to": period_to, + "from_date": from_date, + "to_date": to_date, "fiscal_year": get_fiscal_year(posting_date)[0], "owner": ref_wrapper.owner, }) @@ -112,7 +116,7 @@ def send_notification(new_rv): message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name), attachments = [{ "fname": new_rv.name + ".pdf", - "fcontent": frappe.get_print_format(new_rv.doctype, new_rv.name, as_pdf=True) + "fcontent": frappe.get_print_format(new_rv.doctype, new_rv.name, as_pdf=True).encode('utf-8') }]) def notify_errors(doc, doctype, customer, owner): @@ -121,7 +125,7 @@ def notify_errors(doc, doctype, customer, owner): frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")], subject="[Urgent] Error while creating recurring %s for %s" % (doctype, doc), - message = frappe.get_template("templates/emails/recurring_sales_invoice_failed.html").render({ + message = frappe.get_template("templates/emails/recurring_document_failed.html").render({ "type": doctype, "name": doc, "customer": customer @@ -139,4 +143,55 @@ def assign_task_to_owner(doc, doctype, msg, users): 'description' : msg, 'priority' : 'High' } - assign_to.add(args) \ No newline at end of file + assign_to.add(args) + +def validate_recurring_document(doc): + if doc.is_recurring: + validate_notification_email_id(doc) + + if not doc.recurring_type: + msgprint(_("Please select {0}").format(doc.meta.get_label("recurring_type")), + raise_exception=1) + + elif not (doc.from_date and doc.to_date): + throw(_("Period From and Period To dates mandatory for recurring %s") % doc.doctype) + +def convert_to_recurring(doc, autoname, posting_date): + if doc.is_recurring: + if not doc.recurring_id: + frappe.db.set(doc, "recurring_id", + make_autoname(autoname)) + + set_next_date(doc, posting_date) + + elif doc.recurring_id: + frappe.db.sql("""update `tab%s` + set is_recurring = 0 + where recurring_id = %s""" % (doc.doctype, '%s'), (doc.recurring_id)) + +def validate_notification_email_id(doc): + if doc.notification_email_address: + email_list = filter(None, [cstr(email).strip() for email in + doc.notification_email_address.replace("\n", "").split(",")]) + + from frappe.utils import validate_email_add + for email in email_list: + if not validate_email_add(email): + throw(_("{0} is an invalid email address in 'Notification \ + Email Address'").format(email)) + + else: + frappe.throw(_("'Notification Email Addresses' not specified for recurring %s") \ + % doc.doctype) + +def set_next_date(doc, posting_date): + """ Set next date on which recurring document will be created""" + from erpnext.controllers.recurring_document import get_next_date + + if not doc.repeat_on_day_of_month: + msgprint(_("Please enter 'Repeat on Day of Month' field value"), raise_exception=1) + + next_date = get_next_date(posting_date, month_map[doc.recurring_type], + cint(doc.repeat_on_day_of_month)) + + frappe.db.set(doc, 'next_date', next_date) \ No newline at end of file diff --git a/erpnext/controllers/tests/test_recurring_document.py b/erpnext/controllers/tests/test_recurring_document.py index d31f6324bb..5332ae0541 100644 --- a/erpnext/controllers/tests/test_recurring_document.py +++ b/erpnext/controllers/tests/test_recurring_document.py @@ -16,14 +16,14 @@ def test_recurring_document(obj, test_records): base_doc = frappe.copy_doc(test_records[0]) base_doc.update({ - "convert_into_recurring": 1, + "is_recurring": 1, "recurring_type": "Monthly", "notification_email_address": "test@example.com, test1@example.com, test2@example.com", "repeat_on_day_of_month": getdate(today).day, "due_date": None, "fiscal_year": get_fiscal_year(today)[0], - "period_from": get_first_day(today), - "period_to": get_last_day(today) + "from_date": get_first_day(today), + "to_date": get_last_day(today) }) if base_doc.doctype == "Sales Order": @@ -50,8 +50,8 @@ def test_recurring_document(obj, test_records): # monthly without a first and last day period doc2 = frappe.copy_doc(base_doc) doc2.update({ - "period_from": today, - "period_to": add_to_date(today, days=30) + "from_date": today, + "to_date": add_to_date(today, days=30) }) doc2.insert() doc2.submit() @@ -61,8 +61,8 @@ def test_recurring_document(obj, test_records): doc3 = frappe.copy_doc(base_doc) doc3.update({ "recurring_type": "Quarterly", - "period_from": get_first_day(today), - "period_to": get_last_day(add_to_date(today, months=3)) + "from_date": get_first_day(today), + "to_date": get_last_day(add_to_date(today, months=3)) }) doc3.insert() doc3.submit() @@ -72,8 +72,8 @@ def test_recurring_document(obj, test_records): doc4 = frappe.copy_doc(base_doc) doc4.update({ "recurring_type": "Quarterly", - "period_from": today, - "period_to": add_to_date(today, months=3) + "from_date": today, + "to_date": add_to_date(today, months=3) }) doc4.insert() doc4.submit() @@ -83,8 +83,8 @@ def test_recurring_document(obj, test_records): doc5 = frappe.copy_doc(base_doc) doc5.update({ "recurring_type": "Yearly", - "period_from": get_first_day(today), - "period_to": get_last_day(add_to_date(today, years=1)) + "from_date": get_first_day(today), + "to_date": get_last_day(add_to_date(today, years=1)) }) doc5.insert() doc5.submit() @@ -94,8 +94,8 @@ def test_recurring_document(obj, test_records): doc6 = frappe.copy_doc(base_doc) doc6.update({ "recurring_type": "Yearly", - "period_from": today, - "period_to": add_to_date(today, years=1) + "from_date": today, + "to_date": add_to_date(today, years=1) }) doc6.insert() doc6.submit() @@ -138,23 +138,23 @@ def _test_recurring_document(obj, base_doc, date_field, first_and_last_day): new_doc = frappe.get_doc(base_doc.doctype, recurred_documents[0][0]) - for fieldname in ["convert_into_recurring", "recurring_type", + for fieldname in ["is_recurring", "recurring_type", "repeat_on_day_of_month", "notification_email_address"]: obj.assertEquals(base_doc.get(fieldname), new_doc.get(fieldname)) obj.assertEquals(new_doc.get(date_field), unicode(next_date)) - obj.assertEquals(new_doc.period_from, - unicode(add_months(base_doc.period_from, no_of_months))) + obj.assertEquals(new_doc.from_date, + unicode(add_months(base_doc.from_date, no_of_months))) if first_and_last_day: - obj.assertEquals(new_doc.period_to, - unicode(get_last_day(add_months(base_doc.period_to, + obj.assertEquals(new_doc.to_date, + unicode(get_last_day(add_months(base_doc.to_date, no_of_months)))) else: - obj.assertEquals(new_doc.period_to, - unicode(add_months(base_doc.period_to, no_of_months))) + obj.assertEquals(new_doc.to_date, + unicode(add_months(base_doc.to_date, no_of_months))) return new_doc diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5466f2a0d8..b9e8451f18 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -64,7 +64,7 @@ scheduler_events = { "erpnext.selling.doctype.lead.get_leads.get_leads" ], "daily": [ - "erpnext.controllers.recurring_document.manage_recurring_documents" + "erpnext.controllers.recurring_document.create_recurring_documents" "erpnext.stock.utils.reorder_item", "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.support.doctype.support_ticket.support_ticket.auto_close_tickets" diff --git a/erpnext/patches/v4_2/update_sales_order_invoice_field_name.py b/erpnext/patches/v4_2/update_sales_order_invoice_field_name.py index 1ae3eb0764..a8303a0aae 100644 --- a/erpnext/patches/v4_2/update_sales_order_invoice_field_name.py +++ b/erpnext/patches/v4_2/update_sales_order_invoice_field_name.py @@ -1,6 +1,6 @@ import frappe def execute(): - frappe.reload_doc('selling', 'doctype', 'sales_order') - frappe.db.sql("""update `tabSales Invoice` set period_from = order_period_from, - period_to = order_period_to, convert_into_recurring = convert_into_recurring_order""") + frappe.reload_doc('accounts', 'doctype', 'sales_invoice') + frappe.db.sql("""update `tabSales Invoice` set from_date = invoice_period_from_date, + to_date = invoice_period_to_date, is_recurring = convert_into_recurring_invoice""") diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 628e43e1df..4797230ad0 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -195,6 +195,37 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) { } }; +cur_frm.cscript.is_recurring = function(doc, dt, dn) { + // set default values for recurring orders + if(doc.is_recurring) { + var owner_email = doc.owner=="Administrator" + ? frappe.user_info("Administrator").email + : doc.owner; + + doc.notification_email_address = $.map([cstr(owner_email), + cstr(doc.contact_email)], function(v) { return v || null; }).join(", "); + doc.repeat_on_day_of_month = frappe.datetime.str_to_obj(doc.posting_date).getDate(); + } + + refresh_many(["notification_email_address", "repeat_on_day_of_month"]); +} + +cur_frm.cscript.from_date = function(doc, dt, dn) { + // set to_date + if(doc.from_date) { + var recurring_type_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, + 'Yearly': 12}; + + var months = recurring_type_map[doc.recurring_type]; + if(months) { + var to_date = frappe.datetime.add_months(doc.from_date, + months); + doc.to_date = frappe.datetime.add_days(to_date, -1); + refresh_field('to_date'); + } + } +} + cur_frm.cscript.send_sms = function() { frappe.require("assets/erpnext/js/sms_manager.js"); var sms_man = new SMSManager(cur_frm.doc); diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index e418ee91db..a4b00ff8b6 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -173,18 +173,18 @@ { "allow_on_submit": 1, "description": "Start date of current order's period", - "fieldname": "period_from", + "fieldname": "from_date", "fieldtype": "Date", - "label": "Order Period From", + "label": "From", "no_copy": 1, "permlevel": 0 }, { "allow_on_submit": 1, "description": "End date of current order's period", - "fieldname": "period_to", + "fieldname": "to_date", "fieldtype": "Date", - "label": "Order Period To", + "label": "To", "no_copy": 1, "permlevel": 0 }, @@ -925,16 +925,16 @@ "allow_on_submit": 1, "depends_on": "eval:doc.docstatus<2", "description": "Check if recurring order, uncheck to stop recurring or put proper End Date", - "fieldname": "convert_into_recurring", + "fieldname": "is_recurring", "fieldtype": "Check", - "label": "Convert into Recurring Order", + "label": "Is Recurring", "no_copy": 1, "permlevel": 0, "print_hide": 1 }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "description": "Select the period when the invoice will be generated automatically", "fieldname": "recurring_type", "fieldtype": "Select", @@ -946,7 +946,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "description": "The day of the month on which auto order will be generated e.g. 05, 28 etc ", "fieldname": "repeat_on_day_of_month", "fieldtype": "Int", @@ -956,7 +956,7 @@ "print_hide": 1 }, { - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "description": "The date on which next invoice will be generated. It is generated on submit.", "fieldname": "next_date", "fieldtype": "Date", @@ -968,7 +968,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "description": "The date on which recurring order will be stop", "fieldname": "end_date", "fieldtype": "Date", @@ -985,7 +985,7 @@ "print_hide": 1 }, { - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "fieldname": "recurring_id", "fieldtype": "Data", "label": "Recurring Id", @@ -996,7 +996,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.convert_into_recurring==1", + "depends_on": "eval:doc.is_recurring==1", "description": "Enter email id separated by commas, order will be mailed automatically on particular date", "fieldname": "notification_email_address", "fieldtype": "Small Text", @@ -1021,7 +1021,7 @@ "idx": 1, "is_submittable": 1, "issingle": 0, - "modified": "2014-08-25 17:41:39.456399", + "modified": "2014-08-28 11:22:10.959416", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 37aca0a8ba..ff14f9d0c1 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -10,6 +10,8 @@ from frappe.utils import cstr, flt, getdate, comma_and from frappe import _ from frappe.model.mapper import get_mapped_doc +from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document + from erpnext.controllers.selling_controller import SellingController form_grid_templates = { @@ -120,7 +122,7 @@ class SalesOrder(SellingController): if not self.billing_status: self.billing_status = 'Not Billed' if not self.delivery_status: self.delivery_status = 'Not Delivered' - self.validate_recurring_document() + validate_recurring_document(self) def validate_warehouse(self): from erpnext.stock.utils import validate_warehouse_company @@ -164,7 +166,7 @@ class SalesOrder(SellingController): self.update_prevdoc_status('submit') frappe.db.set(self, 'status', 'Submitted') - self.convert_to_recurring("SO/REC/.#####", self.transaction_date) + convert_to_recurring(self, "SO/REC/.#####", self.transaction_date) def on_cancel(self): # Cannot cancel stopped SO @@ -254,8 +256,8 @@ class SalesOrder(SellingController): return "order" if self.docstatus==1 else None def on_update_after_submit(self): - self.validate_recurring_document() - self.convert_to_recurring("SO/REC/.#####", self.transaction_date) + validate_recurring_document(self) + convert_to_recurring(self, "SO/REC/.#####", self.transaction_date) @frappe.whitelist() From 737d8e4d9fa61aadc815071d5b6100dd0c881563 Mon Sep 17 00:00:00 2001 From: ankitjavalkarwork Date: Mon, 1 Sep 2014 16:18:44 +0530 Subject: [PATCH 6/6] fix minor issue and set default value send as pdf --- erpnext/controllers/recurring_document.py | 10 ++++++---- erpnext/controllers/tests/test_recurring_document.py | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/recurring_document.py b/erpnext/controllers/recurring_document.py index d9326792f3..729c6f7f9c 100644 --- a/erpnext/controllers/recurring_document.py +++ b/erpnext/controllers/recurring_document.py @@ -111,12 +111,13 @@ def get_next_date(dt, mcount, day=None): def send_notification(new_rv): """Notify concerned persons about recurring document generation""" + frappe.sendmail(new_rv.notification_email_address, 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 = [{ "fname": new_rv.name + ".pdf", - "fcontent": frappe.get_print_format(new_rv.doctype, new_rv.name, as_pdf=True).encode('utf-8') + "fcontent": frappe.get_print_format(new_rv.doctype, new_rv.name, as_pdf=True) }]) def notify_errors(doc, doctype, customer, owner): @@ -186,12 +187,13 @@ def validate_notification_email_id(doc): def set_next_date(doc, posting_date): """ Set next date on which recurring document will be created""" - from erpnext.controllers.recurring_document import get_next_date - + if not doc.repeat_on_day_of_month: msgprint(_("Please enter 'Repeat on Day of Month' field value"), raise_exception=1) next_date = get_next_date(posting_date, month_map[doc.recurring_type], cint(doc.repeat_on_day_of_month)) - frappe.db.set(doc, 'next_date', next_date) \ No newline at end of file + frappe.db.set(doc, 'next_date', next_date) + + msgprint(_("Next Recurring {0} will be created on {1}").format(doc.doctype, next_date)) diff --git a/erpnext/controllers/tests/test_recurring_document.py b/erpnext/controllers/tests/test_recurring_document.py index 5332ae0541..0e7cb1bc5e 100644 --- a/erpnext/controllers/tests/test_recurring_document.py +++ b/erpnext/controllers/tests/test_recurring_document.py @@ -12,6 +12,7 @@ from erpnext.projects.doctype.time_log_batch.test_time_log_batch import * def test_recurring_document(obj, test_records): from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate, add_days from erpnext.accounts.utils import get_fiscal_year + frappe.db.set_value("Print Settings", "Print Settings", "send_print_as_pdf", 1) today = nowdate() base_doc = frappe.copy_doc(test_records[0]) @@ -104,13 +105,13 @@ def test_recurring_document(obj, test_records): # change date field but keep recurring day to be today doc7 = frappe.copy_doc(base_doc) doc7.update({ - date_field: add_to_date(today, days=-1) + date_field: today, }) doc7.insert() doc7.submit() # setting so that _test function works - doc7.set(date_field, today) + # doc7.set(date_field, today) _test_recurring_document(obj, doc7, date_field, True) def _test_recurring_document(obj, base_doc, date_field, first_and_last_day): @@ -162,4 +163,4 @@ def _test_recurring_document(obj, base_doc, date_field, first_and_last_day): # if yearly, test 1 repetition, else test 5 repetitions count = 1 if (no_of_months == 12) else 5 for i in xrange(count): - base_doc = _test(i) \ No newline at end of file + base_doc = _test(i)