From 2f7861a9b838ba40593d86139b51ef93b34258ed Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Sat, 2 May 2020 20:09:33 +0530 Subject: [PATCH] feat: Process deferred accounting entry (#19658) * feat: process deferred accounting * feat: maintain entry for deferred accounting * feat: add check for automatic deferred accounting entry * feat: add build conditions for company and account * fix: create record for automatic processing of deferred accounting * feat: add custom naming series * fix: change the deferred revenue creation via hooks * fix: add client side validations * test: creation of gl entries on submission of process deferred accounting * fix: add multiple validations * patch(accounts-settings): set automatically process deferred accounting entry * fix: On cancel function for deferred entry * fix: Send email per process instead of per invoice * fix: Test cases * fix: Label * fix: Process deferred accounting fixes * fix: Error flag Co-authored-by: Nabin Hait Co-authored-by: deepeshgarg007 Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- erpnext/accounts/deferred_revenue.py | 107 ++++- .../accounts_settings/accounts_settings.json | 432 +++++++++--------- .../accounts/doctype/gl_entry/gl_entry.json | 1 + .../process_deferred_accounting/__init__.py | 0 .../process_deferred_accounting.js | 39 ++ .../process_deferred_accounting.json | 128 ++++++ .../process_deferred_accounting.py | 34 ++ .../test_process_deferred_accounting.py | 48 ++ .../sales_invoice/test_sales_invoice.py | 93 +++- erpnext/config/accounts.py | 4 + erpnext/patches.txt | 1 + ...eferred_accounting_in_accounts_settings.py | 7 + 12 files changed, 644 insertions(+), 250 deletions(-) create mode 100644 erpnext/accounts/doctype/process_deferred_accounting/__init__.py create mode 100644 erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js create mode 100644 erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json create mode 100644 erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py create mode 100644 erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py create mode 100644 erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 3b6a5881ca..b0210e5fd4 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -2,9 +2,10 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day +from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day, cint, get_link_to_form from erpnext.accounts.utils import get_account_currency from frappe.email import sendmail_to_system_managers +from frappe.utils.background_jobs import enqueue def validate_service_stop_date(doc): ''' Validates service_stop_date for Purchase Invoice and Sales Invoice ''' @@ -32,8 +33,20 @@ def validate_service_stop_date(doc): if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name): frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx)) -def convert_deferred_expense_to_expense(start_date=None, end_date=None): +def build_conditions(process_type, account, company): + conditions='' + deferred_account = "item.deferred_revenue_account" if process_type=="Income" else "item.deferred_expense_account" + + if account: + conditions += "AND %s='%s'"%(deferred_account, account) + elif company: + conditions += "AND p.company='%s'"%(company) + + return conditions + +def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=''): # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM + if not start_date: start_date = add_months(today(), -1) if not end_date: @@ -41,18 +54,25 @@ def convert_deferred_expense_to_expense(start_date=None, end_date=None): # check for the purchase invoice for which GL entries has to be done invoices = frappe.db.sql_list(''' - select distinct parent from `tabPurchase Invoice Item` - where service_start_date<=%s and service_end_date>=%s - and enable_deferred_expense = 1 and docstatus = 1 and ifnull(amount, 0) > 0 - ''', (end_date, start_date)) + select distinct item.parent + from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p + where item.service_start_date<=%s and item.service_end_date>=%s + and item.enable_deferred_expense = 1 and item.parent=p.name + and item.docstatus = 1 and ifnull(item.amount, 0) > 0 + {0} + '''.format(conditions), (end_date, start_date)) #nosec # For each invoice, book deferred expense for invoice in invoices: doc = frappe.get_doc("Purchase Invoice", invoice) - book_deferred_income_or_expense(doc, end_date) + book_deferred_income_or_expense(doc, deferred_process, end_date) -def convert_deferred_revenue_to_income(start_date=None, end_date=None): + if frappe.flags.deferred_accounting_error: + send_mail(deferred_process) + +def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=''): # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM + if not start_date: start_date = add_months(today(), -1) if not end_date: @@ -60,14 +80,20 @@ def convert_deferred_revenue_to_income(start_date=None, end_date=None): # check for the sales invoice for which GL entries has to be done invoices = frappe.db.sql_list(''' - select distinct parent from `tabSales Invoice Item` - where service_start_date<=%s and service_end_date>=%s - and enable_deferred_revenue = 1 and docstatus = 1 and ifnull(amount, 0) > 0 - ''', (end_date, start_date)) + select distinct item.parent + from `tabSales Invoice Item` item, `tabSales Invoice` p + where item.service_start_date<=%s and item.service_end_date>=%s + and item.enable_deferred_revenue = 1 and item.parent=p.name + and item.docstatus = 1 and ifnull(item.amount, 0) > 0 + {0} + '''.format(conditions), (end_date, start_date)) #nosec for invoice in invoices: doc = frappe.get_doc("Sales Invoice", invoice) - book_deferred_income_or_expense(doc, end_date) + book_deferred_income_or_expense(doc, deferred_process, end_date) + + if frappe.flags.deferred_accounting_error: + send_mail(deferred_process) def get_booking_dates(doc, item, posting_date=None): if not posting_date: @@ -136,7 +162,7 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a return amount, base_amount -def book_deferred_income_or_expense(doc, posting_date=None): +def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): enable_check = "enable_deferred_revenue" \ if doc.doctype=="Sales Invoice" else "enable_deferred_expense" @@ -159,7 +185,11 @@ def book_deferred_income_or_expense(doc, posting_date=None): total_days, total_booking_days, account_currency) make_gl_entries(doc, credit_account, debit_account, against, - amount, base_amount, end_date, project, account_currency, item.cost_center, item.name) + amount, base_amount, end_date, project, account_currency, item.cost_center, item.name, deferred_process) + + # Returned in case of any errors because it tries to submit the same record again and again in case of errors + if frappe.flags.deferred_accounting_error: + return if getdate(end_date) < getdate(posting_date) and not last_gl_entry: _book_deferred_revenue_or_expense(item) @@ -169,8 +199,30 @@ def book_deferred_income_or_expense(doc, posting_date=None): if item.get(enable_check): _book_deferred_revenue_or_expense(item) +def process_deferred_accounting(posting_date=today()): + ''' Converts deferred income/expense into income/expense + Executed via background jobs on every month end ''' + + if not cint(frappe.db.get_singles_value('Accounts Settings', 'automatically_process_deferred_accounting_entry')): + return + + start_date = add_months(today(), -1) + end_date = add_days(today(), -1) + + for record_type in ('Income', 'Expense'): + doc = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date=posting_date, + start_date=start_date, + end_date=end_date, + type=record_type + )) + + doc.insert() + doc.submit() + def make_gl_entries(doc, credit_account, debit_account, against, - amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no): + amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no, deferred_process=None): # GL Entry for crediting the amount in the deferred expense from erpnext.accounts.general_ledger import make_gl_entries @@ -186,7 +238,9 @@ def make_gl_entries(doc, credit_account, debit_account, against, "cost_center": cost_center, "voucher_detail_no": voucher_detail_no, 'posting_date': posting_date, - 'project': project + 'project': project, + 'against_voucher_type': 'Process Deferred Accounting', + 'against_voucher': deferred_process }, account_currency) ) # GL Entry to debit the amount from the expense @@ -199,7 +253,9 @@ def make_gl_entries(doc, credit_account, debit_account, against, "cost_center": cost_center, "voucher_detail_no": voucher_detail_no, 'posting_date': posting_date, - 'project': project + 'project': project, + 'against_voucher_type': 'Process Deferred Accounting', + 'against_voucher': deferred_process }, account_currency) ) @@ -209,7 +265,16 @@ def make_gl_entries(doc, credit_account, debit_account, against, frappe.db.commit() except: frappe.db.rollback() - title = _("Error while processing deferred accounting for {0}").format(doc.name) traceback = frappe.get_traceback() - frappe.log_error(message=traceback , title=title) - sendmail_to_system_managers(title, traceback) \ No newline at end of file + frappe.log_error(message=traceback) + + frappe.flags.deferred_accounting_error = True + +def send_mail(deferred_process): + title = _("Error while processing deferred accounting for {0}".format(deferred_process)) + content = _(""" + Deferred accounting failed for some invoices: + Please check Process Deferred Accounting {0} + and submit manually after resolving errors + """).format(get_link_to_form('Process Deferred Accounting', deferred_process)) + sendmail_to_system_managers(title, content) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 4ff4212920..353ff77223 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -1,210 +1,226 @@ { - "creation": "2013-06-24 15:49:57", - "description": "Settings for Accounts", - "doctype": "DocType", - "document_type": "Other", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "auto_accounting_for_stock", - "acc_frozen_upto", - "frozen_accounts_modifier", - "determine_address_tax_category_from", - "over_billing_allowance", - "column_break_4", - "credit_controller", - "check_supplier_invoice_uniqueness", - "make_payment_via_journal_entry", - "unlink_payment_on_cancellation_of_invoice", - "unlink_advance_payment_on_cancelation_of_order", - "book_asset_depreciation_entry_automatically", - "allow_cost_center_in_entry_of_bs_account", - "add_taxes_from_item_tax_template", - "automatically_fetch_payment_terms", - "print_settings", - "show_inclusive_tax_in_print", - "column_break_12", - "show_payment_schedule_in_print", - "currency_exchange_section", - "allow_stale", - "stale_days", - "report_settings_sb", - "use_custom_cash_flow" - ], - "fields": [ - { - "default": "1", - "description": "If enabled, the system will post accounting entries for inventory automatically.", - "fieldname": "auto_accounting_for_stock", - "fieldtype": "Check", - "hidden": 1, - "in_list_view": 1, - "label": "Make Accounting Entry For Every Stock Movement" - }, - { - "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", - "fieldname": "acc_frozen_upto", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Accounts Frozen Upto" - }, - { - "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", - "fieldname": "frozen_accounts_modifier", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", - "options": "Role" - }, - { - "default": "Billing Address", - "description": "Address used to determine Tax Category in transactions.", - "fieldname": "determine_address_tax_category_from", - "fieldtype": "Select", - "label": "Determine Address Tax Category From", - "options": "Billing Address\nShipping Address" - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "description": "Role that is allowed to submit transactions that exceed credit limits set.", - "fieldname": "credit_controller", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Credit Controller", - "options": "Role" - }, - { - "fieldname": "check_supplier_invoice_uniqueness", - "fieldtype": "Check", - "label": "Check Supplier Invoice Number Uniqueness" - }, - { - "fieldname": "make_payment_via_journal_entry", - "fieldtype": "Check", - "label": "Make Payment via Journal Entry" - }, - { - "default": "1", - "fieldname": "unlink_payment_on_cancellation_of_invoice", - "fieldtype": "Check", - "label": "Unlink Payment on Cancellation of Invoice" - }, - { - "default": "1", - "fieldname": "unlink_advance_payment_on_cancelation_of_order", - "fieldtype": "Check", - "label": "Unlink Advance Payment on Cancelation of Order" - }, - { - "default": "1", - "fieldname": "book_asset_depreciation_entry_automatically", - "fieldtype": "Check", - "label": "Book Asset Depreciation Entry Automatically" - }, - { - "fieldname": "allow_cost_center_in_entry_of_bs_account", - "fieldtype": "Check", - "label": "Allow Cost Center In Entry of Balance Sheet Account" - }, - { - "default": "1", - "fieldname": "add_taxes_from_item_tax_template", - "fieldtype": "Check", - "label": "Automatically Add Taxes and Charges from Item Tax Template" - }, - { - "fieldname": "print_settings", - "fieldtype": "Section Break", - "label": "Print Settings" - }, - { - "fieldname": "show_inclusive_tax_in_print", - "fieldtype": "Check", - "label": "Show Inclusive Tax In Print" - }, - { - "fieldname": "column_break_12", - "fieldtype": "Column Break" - }, - { - "fieldname": "show_payment_schedule_in_print", - "fieldtype": "Check", - "label": "Show Payment Schedule in Print" - }, - { - "fieldname": "currency_exchange_section", - "fieldtype": "Section Break", - "label": "Currency Exchange Settings" - }, - { - "default": "1", - "fieldname": "allow_stale", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Allow Stale Exchange Rates" - }, - { - "default": "1", - "depends_on": "eval:doc.allow_stale==0", - "fieldname": "stale_days", - "fieldtype": "Int", - "label": "Stale Days" - }, - { - "fieldname": "report_settings_sb", - "fieldtype": "Section Break", - "label": "Report Settings" - }, - { - "default": "0", - "description": "Only select if you have setup Cash Flow Mapper documents", - "fieldname": "use_custom_cash_flow", - "fieldtype": "Check", - "label": "Use Custom Cash Flow Format" - }, - { - "fieldname": "automatically_fetch_payment_terms", - "fieldtype": "Check", - "label": "Automatically Fetch Payment Terms" - }, - { - "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.", - "fieldname": "over_billing_allowance", - "fieldtype": "Currency", - "label": "Over Billing Allowance (%)" - } - ], - "icon": "icon-cog", - "idx": 1, - "issingle": 1, - "modified": "2019-07-04 18:20:55.789946", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Accounts Settings", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "Accounts Manager", - "share": 1, - "write": 1 - }, - { - "read": 1, - "role": "Sales User" - }, - { - "read": 1, - "role": "Purchase User" - } - ], - "quick_entry": 1, - "sort_order": "ASC", - "track_changes": 1 + "actions": [], + "creation": "2013-06-24 15:49:57", + "description": "Settings for Accounts", + "doctype": "DocType", + "document_type": "Other", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "auto_accounting_for_stock", + "acc_frozen_upto", + "frozen_accounts_modifier", + "determine_address_tax_category_from", + "over_billing_allowance", + "column_break_4", + "credit_controller", + "check_supplier_invoice_uniqueness", + "make_payment_via_journal_entry", + "unlink_payment_on_cancellation_of_invoice", + "unlink_advance_payment_on_cancelation_of_order", + "book_asset_depreciation_entry_automatically", + "allow_cost_center_in_entry_of_bs_account", + "add_taxes_from_item_tax_template", + "automatically_fetch_payment_terms", + "automatically_process_deferred_accounting_entry", + "print_settings", + "show_inclusive_tax_in_print", + "column_break_12", + "show_payment_schedule_in_print", + "currency_exchange_section", + "allow_stale", + "stale_days", + "report_settings_sb", + "use_custom_cash_flow" + ], + "fields": [ + { + "default": "1", + "description": "If enabled, the system will post accounting entries for inventory automatically.", + "fieldname": "auto_accounting_for_stock", + "fieldtype": "Check", + "hidden": 1, + "in_list_view": 1, + "label": "Make Accounting Entry For Every Stock Movement" + }, + { + "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", + "fieldname": "acc_frozen_upto", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Accounts Frozen Upto" + }, + { + "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", + "fieldname": "frozen_accounts_modifier", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", + "options": "Role" + }, + { + "default": "Billing Address", + "description": "Address used to determine Tax Category in transactions.", + "fieldname": "determine_address_tax_category_from", + "fieldtype": "Select", + "label": "Determine Address Tax Category From", + "options": "Billing Address\nShipping Address" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "description": "Role that is allowed to submit transactions that exceed credit limits set.", + "fieldname": "credit_controller", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Credit Controller", + "options": "Role" + }, + { + "default": "0", + "fieldname": "check_supplier_invoice_uniqueness", + "fieldtype": "Check", + "label": "Check Supplier Invoice Number Uniqueness" + }, + { + "default": "0", + "fieldname": "make_payment_via_journal_entry", + "fieldtype": "Check", + "label": "Make Payment via Journal Entry" + }, + { + "default": "1", + "fieldname": "unlink_payment_on_cancellation_of_invoice", + "fieldtype": "Check", + "label": "Unlink Payment on Cancellation of Invoice" + }, + { + "default": "1", + "fieldname": "unlink_advance_payment_on_cancelation_of_order", + "fieldtype": "Check", + "label": "Unlink Advance Payment on Cancelation of Order" + }, + { + "default": "1", + "fieldname": "book_asset_depreciation_entry_automatically", + "fieldtype": "Check", + "label": "Book Asset Depreciation Entry Automatically" + }, + { + "default": "0", + "fieldname": "allow_cost_center_in_entry_of_bs_account", + "fieldtype": "Check", + "label": "Allow Cost Center In Entry of Balance Sheet Account" + }, + { + "default": "1", + "fieldname": "add_taxes_from_item_tax_template", + "fieldtype": "Check", + "label": "Automatically Add Taxes and Charges from Item Tax Template" + }, + { + "fieldname": "print_settings", + "fieldtype": "Section Break", + "label": "Print Settings" + }, + { + "default": "0", + "fieldname": "show_inclusive_tax_in_print", + "fieldtype": "Check", + "label": "Show Inclusive Tax In Print" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "show_payment_schedule_in_print", + "fieldtype": "Check", + "label": "Show Payment Schedule in Print" + }, + { + "fieldname": "currency_exchange_section", + "fieldtype": "Section Break", + "label": "Currency Exchange Settings" + }, + { + "default": "1", + "fieldname": "allow_stale", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Allow Stale Exchange Rates" + }, + { + "default": "1", + "depends_on": "eval:doc.allow_stale==0", + "fieldname": "stale_days", + "fieldtype": "Int", + "label": "Stale Days" + }, + { + "fieldname": "report_settings_sb", + "fieldtype": "Section Break", + "label": "Report Settings" + }, + { + "default": "0", + "description": "Only select if you have setup Cash Flow Mapper documents", + "fieldname": "use_custom_cash_flow", + "fieldtype": "Check", + "label": "Use Custom Cash Flow Format" + }, + { + "default": "0", + "fieldname": "automatically_fetch_payment_terms", + "fieldtype": "Check", + "label": "Automatically Fetch Payment Terms" + }, + { + "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.", + "fieldname": "over_billing_allowance", + "fieldtype": "Currency", + "label": "Over Billing Allowance (%)" + }, + { + "default": "1", + "fieldname": "automatically_process_deferred_accounting_entry", + "fieldtype": "Check", + "label": "Automatically Process Deferred Accounting Entry" } + ], + "icon": "icon-cog", + "idx": 1, + "issingle": 1, + "links": [], + "modified": "2019-12-19 16:58:17.395595", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Accounts Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "read": 1, + "role": "Sales User" + }, + { + "read": 1, + "role": "Purchase User" + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 0d75329039..e6d97a1fb2 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "ACC-GLE-.YYYY.-.#####", "creation": "2013-01-10 16:34:06", "doctype": "DocType", diff --git a/erpnext/accounts/doctype/process_deferred_accounting/__init__.py b/erpnext/accounts/doctype/process_deferred_accounting/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js new file mode 100644 index 0000000000..975c60cf91 --- /dev/null +++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js @@ -0,0 +1,39 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Process Deferred Accounting', { + setup: function(frm) { + frm.set_query("document_type", function() { + return { + filters: { + 'name': ['in', ['Sales Invoice', 'Purchase Invoice']] + } + }; + }); + }, + + validate: function() { + return new Promise((resolve) => { + return frappe.db.get_single_value('Accounts Settings', 'automatically_process_deferred_accounting_entry') + .then(value => { + if(value) { + frappe.throw(__('Manual entry cannot be created! Disable automatic entry for deferred accounting in accounts settings and try again')); + } + resolve(value); + }); + }); + }, + + end_date: function(frm) { + if (frm.doc.end_date && frm.doc.end_date < frm.doc.start_date) { + frappe.throw(__("End date cannot be before start date")); + } + }, + + onload: function(frm) { + if (frm.doc.posting_date && frm.doc.docstatus === 0) { + frm.set_value('start_date', frappe.datetime.add_months(frm.doc.posting_date, -1)); + frm.set_value('end_date', frm.doc.posting_date); + } + } +}); diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json new file mode 100644 index 0000000000..4daafef3ec --- /dev/null +++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json @@ -0,0 +1,128 @@ +{ + "actions": [], + "autoname": "ACC-PDA-.#####", + "creation": "2019-11-04 18:01:23.454775", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "type", + "account", + "column_break_3", + "posting_date", + "start_date", + "end_date", + "amended_from" + ], + "fields": [ + { + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "options": "\nIncome\nExpense", + "reqd": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Process Deferred Accounting", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Service Start Date", + "reqd": 1 + }, + { + "fieldname": "end_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Service End Date", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "fieldname": "account", + "fieldtype": "Link", + "label": "Account", + "options": "Account" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-02-06 18:18:09.852844", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Process Deferred Accounting", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py new file mode 100644 index 0000000000..0eac73236e --- /dev/null +++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import erpnext +from frappe import _ +from frappe.model.document import Document +from erpnext.accounts.general_ledger import make_reverse_gl_entries +from erpnext.accounts.deferred_revenue import convert_deferred_expense_to_expense, \ + convert_deferred_revenue_to_income, build_conditions + +class ProcessDeferredAccounting(Document): + def validate(self): + if self.end_date < self.start_date: + frappe.throw(_("End date cannot be before start date")) + + def on_submit(self): + conditions = build_conditions(self.type, self.account, self.company) + if self.type == 'Income': + convert_deferred_revenue_to_income(self.name, self.start_date, self.end_date, conditions) + else: + convert_deferred_expense_to_expense(self.name, self.start_date, self.end_date, conditions) + + def on_cancel(self): + self.ignore_linked_doctypes = ['GL Entry'] + gl_entries = frappe.get_all('GL Entry', fields = ['*'], + filters={ + 'against_voucher_type': self.doctype, + 'against_voucher': self.name + }) + + make_reverse_gl_entries(gl_entries=gl_entries) \ No newline at end of file diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py new file mode 100644 index 0000000000..31356c6e8b --- /dev/null +++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from erpnext.accounts.doctype.account.test_account import create_account +from erpnext.stock.doctype.item.test_item import create_item +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice, check_gl_entries + +class TestProcessDeferredAccounting(unittest.TestCase): + def test_creation_of_ledger_entry_on_submit(self): + ''' test creation of gl entries on submission of document ''' + deferred_account = create_account(account_name="Deferred Revenue", + parent_account="Current Liabilities - _TC", company="_Test Company") + + item = create_item("_Test Item for Deferred Accounting") + item.enable_deferred_revenue = 1 + item.deferred_revenue_account = deferred_account + item.no_of_months = 12 + item.save() + + si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True) + si.items[0].enable_deferred_revenue = 1 + si.items[0].service_start_date = "2019-01-10" + si.items[0].service_end_date = "2019-03-15" + si.items[0].deferred_revenue_account = deferred_account + si.save() + si.submit() + + process_deferred_accounting = doc = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date="2019-01-01", + start_date="2019-01-01", + end_date="2019-01-31", + type="Income" + )) + + process_deferred_accounting.insert() + process_deferred_accounting.submit() + + expected_gle = [ + [deferred_account, 33.85, 0.0, "2019-01-31"], + ["Sales - _TC", 0.0, 33.85, "2019-01-31"] + ] + + check_gl_entries(self, si.name, expected_gle, "2019-01-10") \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index dd727a49b0..c82a249843 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest, copy, time -from frappe.utils import nowdate, flt, getdate, cint, add_days +from frappe.utils import nowdate, flt, getdate, cint, add_days, add_months from frappe.model.dynamic_links import get_dynamic_link_map from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice @@ -1721,37 +1721,76 @@ class TestSalesInvoice(unittest.TestCase): si.submit() from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income - convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-01-31") + + pda1 = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date=nowdate(), + start_date="2019-01-01", + end_date="2019-03-31", + type="Income", + company="_Test Company" + )) + + pda1.insert() + pda1.submit() expected_gle = [ [deferred_account, 33.85, 0.0, "2019-01-31"], - ["Sales - _TC", 0.0, 33.85, "2019-01-31"] - ] - - self.check_gl_entries(si.name, expected_gle, "2019-01-10") - - convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-03-31") - - expected_gle = [ + ["Sales - _TC", 0.0, 33.85, "2019-01-31"], [deferred_account, 43.08, 0.0, "2019-02-28"], ["Sales - _TC", 0.0, 43.08, "2019-02-28"], [deferred_account, 23.07, 0.0, "2019-03-15"], ["Sales - _TC", 0.0, 23.07, "2019-03-15"] ] - self.check_gl_entries(si.name, expected_gle, "2019-01-31") + check_gl_entries(self, si.name, expected_gle, "2019-01-30") - def check_gl_entries(self, voucher_no, expected_gle, posting_date): - gl_entries = frappe.db.sql("""select account, debit, credit, posting_date - from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s - order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1) + def test_deferred_error_email(self): + deferred_account = create_account(account_name="Deferred Revenue", + parent_account="Current Liabilities - _TC", company="_Test Company") - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_gle[i][0], gle.account) - self.assertEqual(expected_gle[i][1], gle.debit) - self.assertEqual(expected_gle[i][2], gle.credit) - self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) + item = create_item("_Test Item for Deferred Accounting") + item.enable_deferred_revenue = 1 + item.deferred_revenue_account = deferred_account + item.no_of_months = 12 + item.save() + + si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True) + si.items[0].enable_deferred_revenue = 1 + si.items[0].service_start_date = "2019-01-10" + si.items[0].service_end_date = "2019-03-15" + si.items[0].deferred_revenue_account = deferred_account + si.save() + si.submit() + + from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income + + acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') + acc_settings.acc_frozen_upto = '2019-01-31' + acc_settings.save() + + pda = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date=nowdate(), + start_date="2019-01-01", + end_date="2019-03-31", + type="Income", + company="_Test Company" + )) + + pda.insert() + pda.submit() + + email = frappe.db.sql(""" select name from `tabEmail Queue` + where message like %(txt)s """, { + 'txt': "%%%s%%" % "Error while processing deferred accounting for {0}".format(pda.name) + }) + + self.assertTrue(email) + + acc_settings.load_from_db() + acc_settings.acc_frozen_upto = None + acc_settings.save() def test_inter_company_transaction(self): @@ -1912,6 +1951,18 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234') self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000) +def check_gl_entries(doc, voucher_no, expected_gle, posting_date): + gl_entries = frappe.db.sql("""select account, debit, credit, posting_date + from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s + order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1) + + for i, gle in enumerate(gl_entries): + doc.assertEqual(expected_gle[i][0], gle.account) + doc.assertEqual(expected_gle[i][1], gle.debit) + doc.assertEqual(expected_gle[i][2], gle.credit) + doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) + def test_item_tax_validity(self): item = frappe.get_doc("Item", "_Test Item 2") diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index b9b0da48c9..839c4ad84a 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -245,6 +245,10 @@ def get_data(): "name": "Supplier Ledger Summary", "doctype": "Sales Invoice", "is_query_report": True, + }, + { + "type": "doctype", + "name": "Process Deferred Accounting" } ] }, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 783fee1a2b..5295399695 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -643,6 +643,7 @@ erpnext.patches.v12_0.set_default_shopify_app_type erpnext.patches.v12_0.set_cwip_and_delete_asset_settings erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings +erpnext.patches.v12_0.set_automatically_process_deferred_accounting_in_accounts_settings erpnext.patches.v12_0.set_payment_entry_status erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields erpnext.patches.v12_0.add_export_type_field_in_party_master diff --git a/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py new file mode 100644 index 0000000000..5ee75be499 --- /dev/null +++ b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("accounts", "doctype", "accounts_settings") + + frappe.db.set_value("Accounts Settings", None, "automatically_process_deferred_accounting_entry", 1) \ No newline at end of file