Commonify Recurring Sales Order/Invoice
This commit is contained in:
parent
21b8d30c80
commit
e8331d40f3
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
121
erpnext/controllers/recurring_document.py
Normal file
121
erpnext/controllers/recurring_document.py
Normal file
@ -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
|
||||
}))
|
@ -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",
|
||||
|
@ -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):
|
||||
|
@ -1,12 +1,12 @@
|
||||
<h2>Recurring Invoice Failed</h2>
|
||||
<h2>Recurring {{ type }} Failed</h2>
|
||||
|
||||
<p>An error occured while creating recurring invoice <b>{{ name }}</b> for <b>{{ customer }}</b>.</p>
|
||||
<p>This could be because of some invalid email ids in the invoice.</p>
|
||||
<p>An error occured while creating recurring {{ type }} <b>{{ name }}</b> for <b>{{ customer }}</b>.</p>
|
||||
<p>This could be because of some invalid email ids in the {{ type }}.</p>
|
||||
<p>To stop sending repetitive error notifications from the system, we have unchecked
|
||||
"Convert into Recurring" field in the invoice {{ name }}.</p>
|
||||
<p><b>Please correct the invoice and make the invoice recurring again.</b></p>
|
||||
"Convert into Recurring" field in the {{ type }} {{ name }}.</p>
|
||||
<p><b>Please correct the {{ type }} and make the {{ type }} recurring again.</b></p>
|
||||
<hr>
|
||||
<p><b>It is necessary to take this action today itself for the above mentioned recurring invoice
|
||||
<p><b>It is necessary to take this action today itself for the above mentioned recurring {{ type }}
|
||||
to be generated. If delayed, you will have to manually change the "Repeat on Day of Month" field
|
||||
of this invoice for generating the recurring invoice.</b></p>
|
||||
of this {{ type }} for generating the recurring {{ type }}.</b></p>
|
||||
<p>[This email is autogenerated]</p>
|
||||
|
Loading…
x
Reference in New Issue
Block a user