feat: More controlled deferred revenue booking (#21671)

* feat: More controller deferred revenue booking

* fix: Query for last gl entry

* fix: Accounting Dimension for Deferred entries

* fix: Deferred revenue booking against paid invoices

* fix: Don not update outstanding on Deferred Entry submission

* fix: Naming fixes

* feat: Deferred revenue/expense booking based on months

* fix: Test case for fix monthly deferred revenue booking

* fix: Typo

* fix: Patch to update settings

* fix: Test case to book deferred expense via journal entry

* fix: Update field for better UX

* fix: Codacy

* fix: Change select field to checkbox
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index 4480110..d5ab1c1 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -2,10 +2,11 @@
 
 import frappe
 from frappe import _
-from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day, cint, get_link_to_form
+from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day, get_first_day, cint, get_link_to_form, rounded
 from erpnext.accounts.utils import get_account_currency
 from frappe.email import sendmail_to_system_managers
 from frappe.utils.background_jobs import enqueue
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
 
 def validate_service_stop_date(doc):
 	''' Validates service_stop_date for Purchase Invoice and Sales Invoice '''
@@ -109,6 +110,18 @@
 		order by posting_date desc limit 1
 	''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
 
+	prev_gl_via_je = frappe.db.sql('''
+		SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
+		WHERE p.name = c.parent and p.company=%s and c.account=%s
+		and c.reference_type=%s and c.reference_name=%s
+		and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
+	''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
+
+	if prev_gl_via_je:
+		if (not prev_gl_entry) or (prev_gl_entry and
+			prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date):
+			prev_gl_entry = prev_gl_via_je
+
 	if prev_gl_entry:
 		start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
 	else:
@@ -130,14 +143,48 @@
 	else:
 		return None, None, None
 
-def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
-	if doc.doctype == "Sales Invoice":
-		total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
-		deferred_account = "deferred_revenue_account"
-	else:
-		total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
-		deferred_account = "deferred_expense_account"
+def calculate_monthly_amount(doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency):
+	amount, base_amount = 0, 0
 
+	if not last_gl_entry:
+		total_months = (item.service_end_date.year - item.service_start_date.year) * 12 + \
+			(item.service_end_date.month - item.service_start_date.month) + 1
+
+		prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) \
+			/ flt(date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date)))
+
+		actual_months = rounded(total_months * prorate_factor, 1)
+
+		already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
+		base_amount = flt(item.base_net_amount / actual_months, item.precision("base_net_amount"))
+
+		if base_amount + already_booked_amount > item.base_net_amount:
+			base_amount = item.base_net_amount - already_booked_amount
+
+		if account_currency==doc.company_currency:
+			amount = base_amount
+		else:
+			amount = flt(item.net_amount/actual_months, item.precision("net_amount"))
+			if amount + already_booked_amount_in_account_currency > item.net_amount:
+				amount = item.net_amount - already_booked_amount_in_account_currency
+
+		if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
+			partial_month = flt(date_diff(end_date, start_date)) \
+				/ flt(date_diff(get_last_day(end_date), get_first_day(start_date)))
+
+			base_amount = rounded(partial_month, 1) * base_amount
+			amount = rounded(partial_month, 1) * amount
+	else:
+		already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
+		base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
+		if account_currency==doc.company_currency:
+			amount = base_amount
+		else:
+			amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
+
+	return amount, base_amount
+
+def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
 	amount, base_amount = 0, 0
 	if not last_gl_entry:
 		base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount"))
@@ -146,27 +193,55 @@
 		else:
 			amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount"))
 	else:
-		gl_entries_details = frappe.db.sql('''
-			select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
-			from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
-			group by voucher_detail_no
-		'''.format(total_credit_debit, total_credit_debit_currency),
-			(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
-		already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
+		already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
+
 		base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
 		if account_currency==doc.company_currency:
 			amount = base_amount
 		else:
-			already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
 			amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
 
 	return amount, base_amount
 
+def get_already_booked_amount(doc, item):
+	if doc.doctype == "Sales Invoice":
+		total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
+		deferred_account = "deferred_revenue_account"
+	else:
+		total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
+		deferred_account = "deferred_expense_account"
+
+	gl_entries_details = frappe.db.sql('''
+		select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
+		from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
+		group by voucher_detail_no
+	'''.format(total_credit_debit, total_credit_debit_currency),
+		(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
+
+	journal_entry_details = frappe.db.sql('''
+		SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
+		FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
+		p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
+		and p.docstatus < 2 group by reference_detail_no
+	'''.format(total_credit_debit, total_credit_debit_currency),
+		(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
+
+	already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
+	already_booked_amount += journal_entry_details[0].total_credit if journal_entry_details else 0
+
+	if doc.currency == doc.company_currency:
+		already_booked_amount_in_account_currency = already_booked_amount
+	else:
+		already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
+		already_booked_amount_in_account_currency += journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
+
+	return already_booked_amount, already_booked_amount_in_account_currency
+
 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"
 
-	def _book_deferred_revenue_or_expense(item):
+	def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on):
 		start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
 		if not (start_date and end_date): return
 
@@ -181,23 +256,34 @@
 		total_days = date_diff(item.service_end_date, item.service_start_date) + 1
 		total_booking_days = date_diff(end_date, start_date) + 1
 
-		amount, base_amount = calculate_amount(doc, item, last_gl_entry,
-			total_days, total_booking_days, account_currency)
+		if book_deferred_entries_based_on == 'Months':
+			amount, base_amount = calculate_monthly_amount(doc, item, last_gl_entry,
+				start_date, end_date, total_days, total_booking_days, account_currency)
+		else:
+			amount, base_amount = calculate_amount(doc, item, last_gl_entry,
+				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, deferred_process)
+		if via_journal_entry:
+			book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
+				base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
+		else:
+			make_gl_entries(doc, credit_account, debit_account, against,
+				amount, base_amount, end_date, project, account_currency, item.cost_center, item, 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)
+			_book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
 
+	via_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_via_journal_entry'))
+	submit_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'submit_journal_entries'))
+	book_deferred_entries_based_on = frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_based_on')
 
 	for item in doc.get('items'):
 		if item.get(enable_check):
-			_book_deferred_revenue_or_expense(item)
+			_book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
 
 def process_deferred_accounting(posting_date=None):
 	''' Converts deferred income/expense into income/expense
@@ -281,3 +367,83 @@
 		and submit manually after resolving errors
 	""").format(get_link_to_form('Process Deferred Accounting', deferred_process))
 	sendmail_to_system_managers(title, content)
+
+def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
+	amount, base_amount, posting_date, project, account_currency, cost_center, item,
+	deferred_process=None, submit='No'):
+
+	if amount == 0: return
+
+	journal_entry = frappe.new_doc('Journal Entry')
+	journal_entry.posting_date = posting_date
+	journal_entry.company = doc.company
+	journal_entry.voucher_type = 'Deferred Revenue' if doc.doctype == 'Sales Invoice' \
+		else 'Deferred Expense'
+
+	debit_entry = {
+		'account': credit_account,
+		'credit': base_amount,
+		'credit_in_account_currency': amount,
+		'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
+		'party': against,
+		'account_currency': account_currency,
+		'reference_name': doc.name,
+		'reference_type': doc.doctype,
+		'reference_detail_no': item.name,
+		'cost_center': cost_center,
+		'project': project,
+	}
+
+	credit_entry = {
+		'account': debit_account,
+		'debit': base_amount,
+		'debit_in_account_currency': amount,
+		'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
+		'party': against,
+		'account_currency': account_currency,
+		'reference_name': doc.name,
+		'reference_type': doc.doctype,
+		'reference_detail_no': item.name,
+		'cost_center': cost_center,
+		'project': project,
+	}
+
+	for dimension in get_accounting_dimensions():
+		debit_entry.update({
+			dimension: item.get(dimension)
+		})
+
+		credit_entry.update({
+			dimension: item.get(dimension)
+		})
+
+	journal_entry.append('accounts', debit_entry)
+	journal_entry.append('accounts', credit_entry)
+
+	try:
+		journal_entry.save()
+
+		if submit:
+			journal_entry.submit()
+	except:
+		frappe.db.rollback()
+		traceback = frappe.get_traceback()
+		frappe.log_error(message=traceback)
+
+		frappe.flags.deferred_accounting_error = True
+
+def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
+
+	if doctype == 'Sales Invoice':
+		credit_account, debit_account = frappe.db.get_value('Sales Invoice Item', {'name': voucher_detail_no},
+			['income_account', 'deferred_revenue_account'])
+	else:
+		credit_account, debit_account = frappe.db.get_value('Purchase Invoice Item', {'name': voucher_detail_no},
+			['deferred_expense_account', 'expense_account'])
+
+	if dr_or_cr == 'Debit':
+		return debit_account
+	else:
+		return credit_account
+
+
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 353ff77..e4b96ae 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -22,7 +22,12 @@
   "allow_cost_center_in_entry_of_bs_account",
   "add_taxes_from_item_tax_template",
   "automatically_fetch_payment_terms",
+  "deferred_accounting_settings_section",
   "automatically_process_deferred_accounting_entry",
+  "book_deferred_entries_based_on",
+  "column_break_18",
+  "book_deferred_entries_via_journal_entry",
+  "submit_journal_entries",
   "print_settings",
   "show_inclusive_tax_in_print",
   "column_break_12",
@@ -189,13 +194,45 @@
    "fieldname": "automatically_process_deferred_accounting_entry",
    "fieldtype": "Check",
    "label": "Automatically Process Deferred Accounting Entry"
+  },
+  {
+   "fieldname": "deferred_accounting_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Deferred Accounting Settings"
+  },
+  {
+   "fieldname": "column_break_18",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "description": "If this is unchecked direct GL Entries will be created to book Deferred Revenue/Expense",
+   "fieldname": "book_deferred_entries_via_journal_entry",
+   "fieldtype": "Check",
+   "label": "Book Deferred Entries Via Journal Entry"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.book_deferred_entries_via_journal_entry",
+   "description": "If this is unchecked Journal Entries will be saved in a Draft state and will have to be submitted manually",
+   "fieldname": "submit_journal_entries",
+   "fieldtype": "Check",
+   "label": "Submit Journal Entries"
+  },
+  {
+   "default": "Days",
+   "description": "If \"Months\" is selected then fixed amount will be booked as deferred revenue or expense for each month irrespective of number of days in a month. Will be prorated if deferred revenue or expense is not booked for an entire month.",
+   "fieldname": "book_deferred_entries_based_on",
+   "fieldtype": "Select",
+   "label": "Book Deferred Entries Based On",
+   "options": "Days\nMonths"
   }
  ],
  "icon": "icon-cog",
  "idx": 1,
  "issingle": 1,
  "links": [],
- "modified": "2019-12-19 16:58:17.395595",
+ "modified": "2020-06-22 20:13:26.043092",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index af2aa65..4573c50 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -83,7 +83,7 @@
    "label": "Entry Type",
    "oldfieldname": "voucher_type",
    "oldfieldtype": "Select",
-   "options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation",
+   "options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation\nDeferred Revenue\nDeferred Expense",
    "reqd": 1,
    "search_index": 1
   },
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index caaf30f..7360b39 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -10,6 +10,7 @@
 from erpnext.accounts.party import get_party_account
 from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
 from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
+from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
 
 from six import string_types, iteritems
 
@@ -265,7 +266,10 @@
 				# set totals
 				if not d.reference_name in self.reference_totals:
 					self.reference_totals[d.reference_name] = 0.0
-				self.reference_totals[d.reference_name] += flt(d.get(dr_or_cr))
+
+				if self.voucher_type not in ('Deferred Revenue', 'Deferred Expense'):
+					self.reference_totals[d.reference_name] += flt(d.get(dr_or_cr))
+
 				self.reference_types[d.reference_name] = d.reference_type
 				self.reference_accounts[d.reference_name] = d.account
 
@@ -277,10 +281,16 @@
 
 				# check if party and account match
 				if d.reference_type in ("Sales Invoice", "Purchase Invoice"):
-					if d.reference_type == "Sales Invoice":
-						party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
+					if self.voucher_type in ('Deferred Revenue', 'Deferred Expense') and d.reference_detail_no:
+						debit_or_credit = 'Debit' if d.debit else 'Credit'
+						party_account = get_deferred_booking_accounts(d.reference_type, d.reference_detail_no,
+							debit_or_credit)
 					else:
-						party_account = against_voucher[1]
+						if d.reference_type == "Sales Invoice":
+							party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
+						else:
+							party_account = against_voucher[1]
+
 					if (against_voucher[0] != d.party or party_account != d.account):
 						frappe.throw(_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}")
 							.format(d.idx, field_dict.get(d.reference_type)[0], field_dict.get(d.reference_type)[1],
@@ -513,14 +523,20 @@
 						"against_voucher_type": d.reference_type,
 						"against_voucher": d.reference_name,
 						"remarks": remarks,
+						"voucher_detail_no": d.reference_detail_no,
 						"cost_center": d.cost_center,
 						"project": d.project,
 						"finance_book": self.finance_book
 					}, item=d)
 				)
 
+		if self.voucher_type in ('Deferred Revenue', 'Deferred Expense'):
+			update_outstanding = 'No'
+		else:
+			update_outstanding = 'Yes'
+
 		if gl_map:
-			make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj)
+			make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding)
 
 	def get_balance(self):
 		if not self.get('accounts'):
diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
index ff3533a..ad0ecc4 100644
--- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
+++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
@@ -33,6 +33,7 @@
   "reference_type",
   "reference_name",
   "reference_due_date",
+  "reference_detail_no",
   "col_break3",
   "is_advance",
   "user_remark",
@@ -268,6 +269,12 @@
    "fieldtype": "Link",
    "label": "Bank Account",
    "options": "Bank Account"
+  },
+  {
+   "fieldname": "reference_detail_no",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Reference Detail No"
   }
  ],
  "idx": 1,
diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js
index 975c60c..2800c19 100644
--- a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js
+++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js
@@ -10,6 +10,18 @@
 				}
 			};
 		});
+
+		if (frm.doc.company) {
+			frm.set_query("account", function() {
+				return {
+					filters: {
+						'company': frm.doc.company,
+						'root_type': 'Liability',
+						'is_group': 0
+					}
+				};
+			});
+		}
 	},
 
 	validate: function() {
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 6170005..b5955ca 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -7,14 +7,15 @@
 import frappe, erpnext
 import frappe.model
 from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
-from frappe.utils import cint, flt, today, nowdate, add_days
+from frappe.utils import cint, flt, today, nowdate, add_days, getdate
 import frappe.defaults
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
 	test_records as pr_test_records, make_purchase_receipt, get_taxes
 from erpnext.controllers.accounts_controller import get_payment_terms
 from erpnext.exceptions import InvalidCurrency
 from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction
-from erpnext.accounts.doctype.account.test_account import get_inventory_account
+from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
+from erpnext.stock.doctype.item.test_item import create_item
 
 test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"]
 test_ignore = ["Serial No"]
@@ -866,6 +867,67 @@
 		for gle in gl_entries:
 			self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
 
+	def test_deferred_expense_via_journal_entry(self):
+		deferred_account = create_account(account_name="Deferred Expense",
+			parent_account="Current Assets - _TC", company="_Test Company")
+
+		acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+		acc_settings.book_deferred_entries_via_journal_entry = 1
+		acc_settings.submit_journal_entries = 1
+		acc_settings.save()
+
+		item = create_item("_Test Item for Deferred Accounting")
+		item.enable_deferred_expense = 1
+		item.deferred_expense_account = deferred_account
+		item.save()
+
+		pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True)
+		pi.set_posting_time = 1
+		pi.posting_date = '2019-03-15'
+		pi.items[0].enable_deferred_expense = 1
+		pi.items[0].service_start_date = "2019-01-10"
+		pi.items[0].service_end_date = "2019-03-15"
+		pi.items[0].deferred_expense_account = deferred_account
+		pi.save()
+		pi.submit()
+
+		pda1 = frappe.get_doc(dict(
+			doctype='Process Deferred Accounting',
+			posting_date=nowdate(),
+			start_date="2019-01-01",
+			end_date="2019-03-31",
+			type="Expense",
+			company="_Test Company"
+		))
+
+		pda1.insert()
+		pda1.submit()
+
+		expected_gle = [
+			["_Test Account Cost for Goods Sold - _TC", 0.0, 33.85, "2019-01-31"],
+			[deferred_account, 33.85, 0.0, "2019-01-31"],
+			["_Test Account Cost for Goods Sold - _TC", 0.0, 43.08, "2019-02-28"],
+			[deferred_account, 43.08, 0.0, "2019-02-28"],
+			["_Test Account Cost for Goods Sold - _TC", 0.0, 23.07, "2019-03-15"],
+			[deferred_account, 23.07, 0.0, "2019-03-15"]
+		]
+
+		gl_entries = gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
+			from `tabGL Entry`
+			where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
+			order by posting_date asc, account asc""", (pi.items[0].name, pi.posting_date), as_dict=1)
+
+		for i, gle in enumerate(gl_entries):
+			self.assertEqual(expected_gle[i][0], gle.account)
+			self.assertEqual(expected_gle[i][1], gle.credit)
+			self.assertEqual(expected_gle[i][2], gle.debit)
+			self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
+
+		acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+		acc_settings.book_deferred_entries_via_journal_entry = 0
+		acc_settings.submit_journal_entriessubmit_journal_entries = 0
+		acc_settings.save()
+
 
 def unlink_payment_on_cancel_of_invoice(enable=1):
 	accounts_settings = frappe.get_doc("Accounts Settings")
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 6cdf9b5..311cc12 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1720,8 +1720,6 @@
 		si.save()
 		si.submit()
 
-		from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income
-
 		pda1 = frappe.get_doc(dict(
 			doctype='Process Deferred Accounting',
 			posting_date=nowdate(),
@@ -1745,6 +1743,55 @@
 
 		check_gl_entries(self, si.name, expected_gle, "2019-01-30")
 
+	def test_fixed_deferred_revenue(self):
+		deferred_account = create_account(account_name="Deferred Revenue",
+			parent_account="Current Liabilities - _TC", company="_Test Company")
+
+		acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+		acc_settings.book_deferred_entries_based_on = 'Months'
+		acc_settings.save()
+
+		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-16", rate=50000, do_not_submit=True)
+		si.items[0].enable_deferred_revenue = 1
+		si.items[0].service_start_date = "2019-01-16"
+		si.items[0].service_end_date = "2019-03-31"
+		si.items[0].deferred_revenue_account = deferred_account
+		si.save()
+		si.submit()
+
+		pda1 = frappe.get_doc(dict(
+			doctype='Process Deferred Accounting',
+			posting_date='2019-03-31',
+			start_date="2019-01-01",
+			end_date="2019-03-31",
+			type="Income",
+			company="_Test Company"
+		))
+
+		pda1.insert()
+		pda1.submit()
+
+		expected_gle = [
+			[deferred_account, 10000.0, 0.0, "2019-01-31"],
+			["Sales - _TC", 0.0, 10000.0, "2019-01-31"],
+			[deferred_account, 20000.0, 0.0, "2019-02-28"],
+			["Sales - _TC", 0.0, 20000.0, "2019-02-28"],
+			[deferred_account, 20000.0, 0.0, "2019-03-31"],
+			["Sales - _TC", 0.0, 20000.0, "2019-03-31"]
+		]
+
+		check_gl_entries(self, si.name, expected_gle, "2019-01-30")
+
+		acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+		acc_settings.book_deferred_entries_based_on = 'Days'
+		acc_settings.save()
+
 	def test_inter_company_transaction(self):
 
 		if not frappe.db.exists("Customer", "_Test Internal Customer"):
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 928c0ab..17fbcc2 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -699,6 +699,7 @@
 erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
 erpnext.patches.v13_0.update_sla_enhancements
 erpnext.patches.v12_0.update_address_template_for_india
+erpnext.patches.v13_0.update_deferred_settings
 erpnext.patches.v12_0.set_multi_uom_in_rfq
 erpnext.patches.v13_0.delete_old_sales_reports
 execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation")
diff --git a/erpnext/patches/v13_0/update_deferred_settings.py b/erpnext/patches/v13_0/update_deferred_settings.py
new file mode 100644
index 0000000..a7d8207
--- /dev/null
+++ b/erpnext/patches/v13_0/update_deferred_settings.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+	accounts_settings.book_deferred_entries_based_on = 'Days'
+	accounts_settings.book_deferred_entries_via_journal_entry = 0
+	accounts_settings.submit_journal_entries = 0
+	accounts_settings.save()
\ No newline at end of file