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 <nabinhait@gmail.com>
Co-authored-by: deepeshgarg007 <deepeshgarg6@gmail.com>
Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index 3b6a588..b0210e5 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -2,9 +2,10 @@
 
 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 @@
 		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 @@
 
 	# 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 @@
 
 	# 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 @@
 
 	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 @@
 			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 @@
 		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 @@
 			"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 @@
 			"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 @@
 			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 4ff4212..353ff77 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 0d75329..e6d97a1 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 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/process_deferred_accounting/__init__.py
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 0000000..975c60c
--- /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 0000000..4daafef
--- /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 0000000..0eac732
--- /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 0000000..31356c6
--- /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 dd727a4..c82a249 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 @@
 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 @@
 		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 @@
 		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 b9b0da4..839c4ad 100644
--- a/erpnext/config/accounts.py
+++ b/erpnext/config/accounts.py
@@ -245,6 +245,10 @@
 					"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 783fee1..5295399 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -643,6 +643,7 @@
 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 0000000..5ee75be
--- /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