diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 448011016e..d5ab1c1704 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -2,10 +2,11 @@ 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, 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 @@ def get_booking_dates(doc, item, posting_date=None): 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 @@ def get_booking_dates(doc, item, posting_date=None): 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 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a 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 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): 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 @@ def send_mail(deferred_process): 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 353ff77223..e4b96ae67e 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 af2aa65e6b..4573c50134 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 caaf30f11f..7360b39942 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.utils import get_balance_on, get_account_currency 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 @@ class JournalEntry(AccountsController): # 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 @@ class JournalEntry(AccountsController): # 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 @@ class JournalEntry(AccountsController): "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 ff3533a679..ad0ecc4a0d 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 975c60cf91..2800c195ce 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 @@ frappe.ui.form.on('Process Deferred Accounting', { } }; }); + + 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 6170005061..b5955ca258 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 unittest 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 @@ class TestPurchaseInvoice(unittest.TestCase): 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 6cdf9b57d1..311cc12dd8 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 @@ class TestSalesInvoice(unittest.TestCase): 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 @@ class TestSalesInvoice(unittest.TestCase): 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 928c0ab9d8..17fbcc2190 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -699,6 +699,7 @@ erpnext.patches.v13_0.delete_old_purchase_reports 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 0000000000..a7d82077b7 --- /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