From a4fdc84fdaf7f2ab761d64eb7586db58b73fa165 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 6 Dec 2011 15:04:47 +0530 Subject: [PATCH] Introduced recurring invoices --- .../accounts/doctype/gl_control/gl_control.py | 47 +++++-- .../receivable_voucher/receivable_voucher.js | 9 ++ .../receivable_voucher/receivable_voucher.py | 61 ++++++++- .../receivable_voucher/receivable_voucher.txt | 116 +++++++++++++++++- 4 files changed, 217 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/gl_control/gl_control.py b/erpnext/accounts/doctype/gl_control/gl_control.py index 44a9e8d8c9..585892075f 100644 --- a/erpnext/accounts/doctype/gl_control/gl_control.py +++ b/erpnext/accounts/doctype/gl_control/gl_control.py @@ -4,8 +4,8 @@ import webnotes from webnotes.utils import add_days, add_months, add_years, cint, cstr, date_diff, default_fields, flt, fmt_money, formatdate, generate_hash, getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common, month_name, now, nowdate, replace_newlines, sendmail, set_default, str_esc_quote, user_format, validate_email_add from webnotes.model import db_exists from webnotes.model.doc import Document, addchild, removechild, getchildren, make_autoname, SuperDocType -from webnotes.model.doclist import getlist, copy_doclist -from webnotes.model.code import get_obj, get_server_obj, run_server_obj, updatedb, check_syntax +from webnotes.model.doclist import getlist, copy_doclist, clone +from webnotes.model.code import get_obj from webnotes import session, form, is_testing, msgprint, errprint sql = webnotes.conn.sql @@ -24,14 +24,8 @@ class DocType: # Get Company List # ---------------- def get_companies(self,arg=''): - #d = get_defaults() ret = sql("select name, abbr from tabCompany where docstatus != 2") - #pl = {} - #for r in ret: - # inc = get_value('Account','Income - '+r[1], 'balance') - # exp = get_value('Account','Expenses - '+r[1], 'balance') - # pl[r[0]] = flt(flt(inc) - flt(exp)) - return {'cl':[r[0] for r in ret]}#, 'pl':pl} + return {'cl':[r[0] for r in ret]} def get_company_currency(self,arg=''): dcc = TransactionBase().get_company_currency(arg) @@ -506,3 +500,38 @@ In Account := %s User := %s has Repaired Outstanding Amount For %s : %s and foll for a in set(ac_list): fy_obj.repost(a) + +def manage_recurring_invoices(): + """ + Create recurring invoices on specific date by copying the original one + and notify the concerned people + """ + rv = sql("""select name, recurring_id from `tabReceivable Voucher` where ifnull(convert_into_recurring_invoice, 0) = 1 + and next_date = %s and next_date <= end_date""", nowdate()) + for d in rv: + if not sql("""select name from `tabReceivable Voucher` where posting_date = %s and recurring_id = %s""", (nowdate, d[1])): + prev_rv = get_obj('Receivable Voucher', d[0], with_children=1) + new_rv = create_new_invoice(prev_rv) + + send_notification(new_rv) + +def create_new_invoice(prev_rv): + # clone rv + new_rv = clone(prev_rv) + + # update new rv + new_rv.doc.voucher_date = new_rv.doc.next_date + new_rv.doc.posting_date = new_rv.doc.next_date + new_rv.doc.aging_date = new_rv.doc.next_date + new_rv.doc.due_date = add_days(new_rv.doc.next_date, cint(date_diff(prev_rv.doc.due_date, prev_rv.doc.posting_date))) + new_rv.doc.save() + + # submit and after submit + new_rv.submit() + new_rv.update_after_submit() + + return new_rv + +def send_notification(new_rv): + """Notify concerned persons about recurring invoice generation""" + pass diff --git a/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.js b/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.js index bb19681b98..363da2ec57 100644 --- a/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.js +++ b/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.js @@ -433,3 +433,12 @@ cur_frm.cscript['View Ledger Entry'] = function(){ } loadreport('GL Entry','General Ledger', callback); } + +// Default values for recurring invoices +cur_frm.cscript.convert_into_recurring_invoice = function(doc) { + if (doc.convert_into_recurring_invoice) { + doc.repeat_on_day_of_month = doc.posting_date.split('-')[2]; + doc.notification_email_address = doc.owner + ', ' + doc.contact_email; + refresh_field(['repeat_on_day_of_month', 'notification_email_address']); + } +} diff --git a/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.py b/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.py index 6d1f80adb9..13ed8d3c5e 100644 --- a/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.py +++ b/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.py @@ -1,12 +1,13 @@ # Please edit this list and import only required elements import webnotes -from webnotes.utils import add_days, add_months, add_years, cint, cstr, date_diff, default_fields, flt, fmt_money, formatdate, generate_hash, getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common, month_name, now, nowdate, replace_newlines, sendmail, set_default, str_esc_quote, user_format, validate_email_add +from webnotes.utils import add_days, add_months, add_years, cint, cstr,date_diff, default_fields, flt, fmt_money, formatdate, generate_hash,getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common,month_name, now, nowdate, replace_newlines, sendmail, set_default,str_esc_quote, user_format, validate_email_add from webnotes.model import db_exists from webnotes.model.doc import Document, addchild, removechild, getchildren, make_autoname, SuperDocType from webnotes.model.doclist import getlist, copy_doclist from webnotes.model.code import get_obj, get_server_obj, run_server_obj, updatedb, check_syntax from webnotes import session, form, is_testing, msgprint, errprint +from webnotes.utils.scheduler import set_event, cancel_event, Scheduler set = webnotes.conn.set sql = webnotes.conn.sql @@ -528,7 +529,6 @@ class DocType(TransactionBase): def make_gl_entries(self, is_cancel=0): mapper = self.doc.is_pos and self.doc.write_off_account and 'POS with write off' or self.doc.is_pos and not self.doc.write_off_account and 'POS' or '' update_outstanding = self.doc.is_pos and self.doc.write_off_account and 'No' or 'Yes' - get_obj(dt='GL Control').make_gl_entries(self.doc, self.doclist,cancel = is_cancel, use_mapper = mapper, update_outstanding = update_outstanding, merge_entries = cint(self.doc.is_pos) != 1 and 1 or 0) @@ -546,7 +546,8 @@ class DocType(TransactionBase): get_obj("Sales Common").update_prevdoc_detail(1,self) # Check for Approving Authority - get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self) + if not self.doc.recurring_id: + get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self) # this sequence because outstanding may get -ve self.make_gl_entries() @@ -618,9 +619,61 @@ class DocType(TransactionBase): set(self.doc,'outstanding_amount',flt(self.doc.grand_total) - flt(self.doc.total_advance) - flt(self.doc.paid_amount) - flt(self.doc.write_off_amount)) - ######################################################################## # Repair Outstanding ####################################################################### def repair_rv_outstanding(self): get_obj(dt = 'GL Control').repair_voucher_outstanding(self) + + def on_update_after_submit(self): + self.convert_into_recurring() + + + def convert_into_recurring(self): + if self.doc.convert_into_recurring_invoice: + event = 'accounts.doctype.gl_control.gl_control.manage_recurring_invoices' + self.set_next_date() + if not self.doc.recurring_id: + set(self.doc, 'recurring_id', make_autoname('RECINV/.#####')) + + if sql("select name from `tabReceivable Voucher` where ifnull(convert_into_recurring_invoice, 0) = 1 and next_date <= end_date"): + if not self.check_event_exists(event): + set_event(event, interval = 60*60, recurring = 1) + else: + cancel_event(event) + + elif self.doc.recurring_id: + sql("""update `tabReceivable Voucher` set convert_into_recurring_invoice = 0 where recurring_id = %s""", self.doc.recurring_id) + + + def check_event_exists(self, event): + try: + ev = Scheduler().get_events() + except: + msgprint("Scheduler database not exists. Please mail to support@erpnext.com", raise_exception=1) + + if event in [d['event'] for d in ev]: + return 1 + + + def set_next_date(self): + """ Set next date on which auto invoice will be created""" + + if not self.doc.repeat_on_day_of_month: + msgprint("""Please enter 'Repeat on Day of Month' field value. \nThe day of the month on which auto invoice + will be generated e.g. 05, 28 etc.""", raise_exception=1) + + import datetime + m = getdate(self.doc.posting_date).month + 1 + y = getdate(self.doc.posting_date).year + if m > 12: + m, y = 1, y+1 + try: + next_date = datetime.date(y, m, cint(self.doc.repeat_on_day_of_month)) + except: + import calendar + last_day = calendar.monthrange(y, m)[1] + next_date = datetime.date(y, m, last_day) + next_date = next_date.strftime("%Y-%m-%d") + + set(self.doc, 'next_date', next_date) diff --git a/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.txt b/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.txt index 1abd6a725b..e2fc0c6416 100644 --- a/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.txt +++ b/erpnext/accounts/doctype/receivable_voucher/receivable_voucher.txt @@ -5,7 +5,7 @@ { 'creation': '2010-08-08 17:09:18', 'docstatus': 0, - 'modified': '2011-10-19 16:31:54', + 'modified': '2011-12-06 13:17:26', 'modified_by': 'Administrator', 'owner': 'Administrator' }, @@ -21,7 +21,7 @@ # These values are common for all DocType { - '_last_update': '1319014846', + '_last_update': '1323156733', 'change_log': '1. Change in pull_details method dt.-26-06-2009', 'colour': 'White:FFF', 'default_print_format': 'Standard', @@ -34,7 +34,7 @@ 'server_code_error': ' ', 'show_in_menu': 0, 'subject': 'To %(customer_name)s worth %(currency)s %(grand_total_export)s due on %(due_date)s | %(outstanding_amount)s outstanding', - 'version': 363 + 'version': 383 }, # These values are common for all DocFormat @@ -1343,5 +1343,115 @@ 'permlevel': 0, 'print_hide': 1, 'report_hide': 1 + }, + + # DocField + { + 'depends_on': 'eval:doc.docstatus==1', + 'doctype': 'DocField', + 'fieldtype': 'Section Break', + 'label': 'Recurring Invoice', + 'permlevel': 0 + }, + + # DocField + { + 'doctype': 'DocField', + 'fieldtype': 'Column Break', + 'permlevel': 0, + 'width': '50%' + }, + + # DocField + { + 'allow_on_submit': 1, + 'colour': 'White:FFF', + 'depends_on': 'eval:doc.docstatus==1', + 'description': 'Check if recurring invoice, uncheck to stop recurring or put proper End Date', + 'doctype': 'DocField', + 'fieldname': 'convert_into_recurring_invoice', + 'fieldtype': 'Check', + 'label': 'Convert into Recurring Invoice', + 'no_copy': 1, + 'permlevel': 0, + 'print_hide': 1, + 'trigger': 'Client' + }, + + # DocField + { + 'allow_on_submit': 1, + 'depends_on': 'eval:doc.convert_into_recurring_invoice==1', + 'description': 'The day of the month on which auto invoice will be generated e.g. 05, 28 etc ', + 'doctype': 'DocField', + 'fieldname': 'repeat_on_day_of_month', + 'fieldtype': 'Data', + 'label': 'Repeat on Day of Month', + 'no_copy': 1, + 'permlevel': 0, + 'print_hide': 1 + }, + + # DocField + { + 'allow_on_submit': 1, + 'depends_on': 'eval:doc.convert_into_recurring_invoice==1', + 'description': 'The date on which recurring invoice will be stop', + 'doctype': 'DocField', + 'fieldname': 'end_date', + 'fieldtype': 'Date', + 'label': 'End Date', + 'no_copy': 1, + 'permlevel': 0, + 'print_hide': 1 + }, + + # DocField + { + 'doctype': 'DocField', + 'fieldtype': 'Column Break', + 'no_copy': 0, + 'permlevel': 0, + 'width': '50%' + }, + + # DocField + { + 'allow_on_submit': 1, + 'depends_on': 'eval:doc.convert_into_recurring_invoice==1', + 'description': 'Enter email id separated by commas, invoice will be mailed automatically on particular date', + 'doctype': 'DocField', + 'fieldname': 'notification_email_address', + 'fieldtype': 'Small Text', + 'label': 'Notification Email Address', + 'no_copy': 1, + 'permlevel': 0, + 'print_hide': 1 + }, + + # DocField + { + 'depends_on': 'eval:doc.convert_into_recurring_invoice==1', + 'description': 'The unique id for tracking all recurring invoices ', + 'doctype': 'DocField', + 'fieldname': 'recurring_id', + 'fieldtype': 'Data', + 'label': 'Recurring Id', + 'no_copy': 1, + 'permlevel': 1, + 'print_hide': 1 + }, + + # DocField + { + 'depends_on': 'eval:doc.convert_into_recurring_invoice==1', + 'description': 'The date on which next invoice will be generated ', + 'doctype': 'DocField', + 'fieldname': 'next_date', + 'fieldtype': 'Date', + 'label': 'Next Date', + 'no_copy': 1, + 'permlevel': 1, + 'print_hide': 1 } ] \ No newline at end of file