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)