diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 2b01e2411d66ac765b577f2c0b3ffc3fe7234ea9..d29e7b9969f0860a5b475fdb130811f57f7a48af 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -11,6 +11,10 @@ from frappe.model import core_doctypes_list from frappe.model.document import Document from frappe.utils import cstr +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + get_allowed_types_from_settings, +) + class AccountingDimension(Document): def before_insert(self): @@ -86,6 +90,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None): doc_count = len(get_accounting_dimensions()) count = 0 + repostable_doctypes = get_allowed_types_from_settings() for doctype in doclist: @@ -101,6 +106,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None): "options": doc.document_type, "insert_after": insert_after_field, "owner": "Administrator", + "allow_on_submit": 1 if doctype in repostable_doctypes else 0, } meta = frappe.get_meta(doctype, cached=False) diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index 84ee592d4c0d6e4de9731eab576a9911cc6669ba..72d5cbcfdae474a49d40ae4513d93e5bc0c7e830 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -44,6 +44,7 @@ def create_bank_account( return bank_account.name + def create_gl_account(gl_account_name="_Test Bank - _TC"): gl_account = frappe.get_doc( { @@ -137,6 +138,7 @@ def add_payments(): pass pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe.reference_no = "Conrad Oct 18" pe.reference_date = "2018-10-24" @@ -227,6 +229,7 @@ def add_payments(): mode_of_payment.append( "accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"} ) + mode_of_payment.save() si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index c2fc905d96f8be5f05f5d5adc48556bab2e00f6e..eb872be1cff33788184b95d5ec30efe34f989a0a 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -43,6 +43,25 @@ frappe.ui.form.on("Journal Entry", { refresh: function(frm) { erpnext.toggle_naming_series(); + if (frm.doc.repost_required && frm.doc.docstatus===1) { + frm.set_intro(__("Accounting entries for this Journal Entry need to be reposted. Please click on 'Repost' button to update.")); + frm.add_custom_button(__('Repost Accounting Entries'), + () => { + frm.call({ + doc: frm.doc, + method: 'repost_accounting_entries', + freeze: true, + freeze_message: __('Reposting...'), + callback: (r) => { + if (!r.exc) { + frappe.msgprint(__('Accounting Entries are reposted.')); + frm.refresh(); + } + } + }); + }).removeClass('btn-default').addClass('btn-warning'); + } + if(frm.doc.docstatus > 0) { frm.add_custom_button(__('Ledger'), function() { frappe.route_options = { @@ -246,7 +265,6 @@ var update_jv_details = function(doc, r) { $.each(r, function(i, d) { var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts"); frappe.model.set_value(row.doctype, row.name, "account", d.account) - frappe.model.set_value(row.doctype, row.name, "balance", d.balance) }); refresh_field("accounts"); } @@ -255,7 +273,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro onload() { this.load_defaults(); this.setup_queries(); - this.setup_balance_formatter(); erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype); } @@ -359,19 +376,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro } - setup_balance_formatter() { - const formatter = function(value, df, options, doc) { - var currency = frappe.meta.get_field_currency(df, doc); - var dr_or_cr = value ? ('') : ""; - return "
" - + ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency)) - + " " + dr_or_cr - + "
"; - }; - this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter); - this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter); - } - reference_name(doc, cdt, cdn) { var d = frappe.get_doc(cdt, cdn); @@ -467,23 +471,22 @@ frappe.ui.form.on("Journal Entry Account", { if(!d.account && d.party_type && d.party) { if(!frm.doc.company) frappe.throw(__("Please select Company")); return frm.call({ - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance", + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_currency", child: d, args: { company: frm.doc.company, party_type: d.party_type, party: d.party, - cost_center: d.cost_center } }); } }, cost_center: function(frm, dt, dn) { - erpnext.journal_entry.set_account_balance(frm, dt, dn); + erpnext.journal_entry.set_account_details(frm, dt, dn); }, account: function(frm, dt, dn) { - erpnext.journal_entry.set_account_balance(frm, dt, dn); + erpnext.journal_entry.set_account_details(frm, dt, dn); }, debit_in_account_currency: function(frm, cdt, cdn) { @@ -672,14 +675,14 @@ $.extend(erpnext.journal_entry, { }); $.extend(erpnext.journal_entry, { - set_account_balance: function(frm, dt, dn) { + set_account_details: function(frm, dt, dn) { var d = locals[dt][dn]; if(d.account) { if(!frm.doc.company) frappe.throw(__("Please select Company first")); if(!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first")); return frappe.call({ - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type", + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_details_and_party_type", args: { account: d.account, date: frm.doc.posting_date, @@ -687,7 +690,6 @@ $.extend(erpnext.journal_entry, { debit: flt(d.debit_in_account_currency), credit: flt(d.credit_in_account_currency), exchange_rate: d.exchange_rate, - cost_center: d.cost_center }, callback: function(r) { if(r.message) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index fd9b218069a627de9d0afd64292a1ea829a211d9..915a5f79868dec50ab2107e0bc262b1a305ae45a 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -66,7 +66,8 @@ "stock_entry", "subscription_section", "auto_repeat", - "amended_from" + "amended_from", + "repost_required" ], "fields": [ { @@ -565,6 +566,15 @@ "label": "Is System Generated", "no_copy": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "repost_required", + "fieldtype": "Check", + "hidden": 1, + "label": "Repost Required", + "print_hide": 1, + "read_only": 1 } ], "icon": "uil uil-file-alt", @@ -580,7 +590,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2023-12-28 17:19:46.544879", + "modified": "2023-10-12 12:32:34.234167", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 49579c51526ebaf736db9677733d2f46705b2f38..12db5753a1f3c114eaaaa333f30b4feb9604c5bb 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -13,6 +13,10 @@ from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import ( get_party_account_based_on_invoice_discounting, ) +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + validate_docs_for_deferred_accounting, + validate_docs_for_voucher_types, +) from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) @@ -143,7 +147,6 @@ class JournalEntry(AccountsController): self.set_print_format_fields() self.validate_credit_debit_note() self.validate_empty_accounts_table() - self.set_account_and_party_balance() self.validate_inter_company_accounts() self.validate_accounting_journals() self.validate_depr_entry_voucher_type() @@ -154,6 +157,24 @@ class JournalEntry(AccountsController): if not self.title: self.title = self.get_title() + def validate_for_repost(self): + validate_docs_for_voucher_types(["Journal Entry"]) + validate_docs_for_deferred_accounting([self.name], []) + + def submit(self): + if len(self.accounts) > 100: + msgprint(_("The task has been enqueued as a background job."), alert=True) + self.queue_action("submit", timeout=4600) + else: + return self._submit() + + def cancel(self): + if len(self.accounts) > 100: + msgprint(_("The task has been enqueued as a background job."), alert=True) + self.queue_action("cancel", timeout=4600) + else: + return self._cancel() + def on_submit(self): self.validate_cheque_info() self.check_credit_limit() @@ -164,6 +185,15 @@ class JournalEntry(AccountsController): self.update_invoice_discounting() self.update_unreconciled_amount() + def on_update_after_submit(self): + if hasattr(self, "repost_required"): + self.needs_repost = self.check_if_fields_updated( + fields_to_check=[], child_tables={"accounts": []} + ) + if self.needs_repost: + self.validate_for_repost() + self.db_set("repost_required", self.needs_repost) + def on_cancel(self): # References for this Journal are removed on the `on_cancel` event in accounts_controller super(JournalEntry, self).on_cancel() @@ -1149,21 +1179,6 @@ class JournalEntry(AccountsController): if not self.get("accounts"): frappe.throw(_("Accounts table cannot be blank.")) - def set_account_and_party_balance(self): - account_balance = {} - party_balance = {} - for d in self.get("accounts"): - if d.account not in account_balance: - account_balance[d.account] = get_balance_on(account=d.account, date=self.posting_date) - - if (d.party_type, d.party) not in party_balance: - party_balance[(d.party_type, d.party)] = get_balance_on( - party_type=d.party_type, party=d.party, date=self.posting_date, company=self.company - ) - - d.account_balance = account_balance[d.account] - d.party_balance = party_balance[(d.party_type, d.party)] - def update_unreconciled_amount(self): amount = 0 cash_bank_accounts = [ @@ -1366,8 +1381,6 @@ def get_payment_entry(ref_doc, args): "account_type": frappe.get_cached_value("Account", args.get("party_account"), "account_type"), "account_currency": args.get("party_account_currency") or get_account_currency(args.get("party_account")), - "balance": get_balance_on(args.get("party_account")), - "party_balance": get_balance_on(party=args.get("party"), party_type=args.get("party_type")), "exchange_rate": exchange_rate, args.get("amount_field_party"): args.get("amount"), "is_advance": args.get("is_advance"), @@ -1515,30 +1528,23 @@ def get_outstanding(args): @frappe.whitelist() -def get_party_account_and_balance(company, party_type, party, cost_center=None): +def get_party_account_and_currency(company, party_type, party): if not frappe.has_permission("Account"): frappe.msgprint(_("No Permission"), raise_exception=1) account = get_party_account(party_type, party, company) - account_balance = get_balance_on(account=account, cost_center=cost_center) - party_balance = get_balance_on( - party_type=party_type, party=party, company=company, cost_center=cost_center - ) - return { "account": account, - "balance": account_balance, - "party_balance": party_balance, "account_currency": frappe.get_cached_value("Account", account, "account_currency"), } @frappe.whitelist() -def get_account_balance_and_party_type( - account, date, company, debit=None, credit=None, exchange_rate=None, cost_center=None +def get_account_details_and_party_type( + account, date, company, debit=None, credit=None, exchange_rate=None ): - """Returns dict of account balance and party type to be set in Journal Entry on selection of account.""" + """Returns dict of account details and party type to be set in Journal Entry on selection of account.""" if not frappe.has_permission("Account"): frappe.msgprint(_("No Permission"), raise_exception=1) @@ -1558,7 +1564,6 @@ def get_account_balance_and_party_type( party_type = "" grid_values = { - "balance": get_balance_on(account, date, cost_center=cost_center), "party_type": party_type, "account_type": account_details.account_type, "account_currency": account_details.account_currency or company_currency, diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index a6e920b7ef6aa1988c1bdef9f4ac36bb0905254c..bf1c8a9a239aa4ee99c3615642e90b526a1e7c18 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -166,43 +166,37 @@ class TestJournalEntry(unittest.TestCase): jv.get("accounts")[1].credit_in_account_currency = 5000 jv.submit() - gl_entries = frappe.db.sql( - """select account, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - jv.name, - as_dict=1, - ) + self.voucher_no = jv.name - self.assertTrue(gl_entries) + self.fields = [ + "account", + "account_currency", + "debit", + "debit_in_account_currency", + "credit", + "credit_in_account_currency", + ] - expected_values = { - "_Test Bank USD - _TC": { - "account_currency": "USD", - "debit": 5000, - "debit_in_account_currency": 100, - "credit": 0, - "credit_in_account_currency": 0, - }, - "_Test Bank - _TC": { + self.expected_gle = [ + { + "account": "_Test Bank - _TC", "account_currency": "INR", "debit": 0, "debit_in_account_currency": 0, "credit": 5000, "credit_in_account_currency": 5000, }, - } + { + "account": "_Test Bank USD - _TC", + "account_currency": "USD", + "debit": 5000, + "debit_in_account_currency": 100, + "credit": 0, + "credit_in_account_currency": 0, + }, + ] - for field in ( - "account_currency", - "debit", - "debit_in_account_currency", - "credit", - "credit_in_account_currency", - ): - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_values[gle.account][field], gle[field]) + self.check_gl_entries() # cancel jv.cancel() @@ -228,43 +222,37 @@ class TestJournalEntry(unittest.TestCase): rjv.posting_date = nowdate() rjv.submit() - gl_entries = frappe.db.sql( - """select account, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - rjv.name, - as_dict=1, - ) + self.voucher_no = rjv.name - self.assertTrue(gl_entries) + self.fields = [ + "account", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ] - expected_values = { - "_Test Bank USD - _TC": { + self.expected_gle = [ + { + "account": "_Test Bank USD - _TC", "account_currency": "USD", "debit": 0, "debit_in_account_currency": 0, "credit": 5000, "credit_in_account_currency": 100, }, - "Sales - _TC": { + { + "account": "Sales - _TC", "account_currency": "INR", "debit": 5000, "debit_in_account_currency": 5000, "credit": 0, "credit_in_account_currency": 0, }, - } + ] - for field in ( - "account_currency", - "debit", - "debit_in_account_currency", - "credit", - "credit_in_account_currency", - ): - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_values[gle.account][field], gle[field]) + self.check_gl_entries() def test_disallow_change_in_account_currency_for_a_party(self): # create jv in USD @@ -344,23 +332,25 @@ class TestJournalEntry(unittest.TestCase): jv.insert() jv.submit() - expected_values = { - "_Test Cash - _TC": {"cost_center": cost_center}, - "_Test Bank - _TC": {"cost_center": cost_center}, - } + self.voucher_no = jv.name - gl_entries = frappe.db.sql( - """select account, cost_center, debit, credit - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - jv.name, - as_dict=1, - ) + self.fields = [ + "account", + "cost_center", + ] - self.assertTrue(gl_entries) + self.expected_gle = [ + { + "account": "_Test Bank - _TC", + "cost_center": cost_center, + }, + { + "account": "_Test Cash - _TC", + "cost_center": cost_center, + }, + ] - for gle in gl_entries: - self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + self.check_gl_entries() def test_jv_with_project(self): from erpnext.projects.doctype.project.test_project import make_project @@ -387,23 +377,22 @@ class TestJournalEntry(unittest.TestCase): jv.insert() jv.submit() - expected_values = { - "_Test Cash - _TC": {"project": project_name}, - "_Test Bank - _TC": {"project": project_name}, - } + self.voucher_no = jv.name - gl_entries = frappe.db.sql( - """select account, project, debit, credit - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - jv.name, - as_dict=1, - ) + self.fields = ["account", "project"] - self.assertTrue(gl_entries) + self.expected_gle = [ + { + "account": "_Test Bank - _TC", + "project": project_name, + }, + { + "account": "_Test Cash - _TC", + "project": project_name, + }, + ] - for gle in gl_entries: - self.assertEqual(expected_values[gle.account]["project"], gle.project) + self.check_gl_entries() def test_jv_account_and_party_balance_with_cost_centre(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center @@ -426,6 +415,80 @@ class TestJournalEntry(unittest.TestCase): account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center) self.assertEqual(expected_account_balance, account_balance) + def test_repost_accounting_entries(self): + from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center + + # Configure Repost Accounting Ledger for JVs + settings = frappe.get_doc("Repost Accounting Ledger Settings") + if not [x for x in settings.allowed_types if x.document_type == "Journal Entry"]: + settings.append("allowed_types", {"document_type": "Journal Entry", "allowed": True}) + settings.save() + + # Create JV with defaut cost center - _Test Cost Center + jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False) + jv.multi_currency = 0 + jv.submit() + + # Check GL entries before reposting + self.voucher_no = jv.name + + self.fields = [ + "account", + "debit_in_account_currency", + "credit_in_account_currency", + "cost_center", + ] + + self.expected_gle = [ + { + "account": "_Test Bank - _TC", + "debit_in_account_currency": 0, + "credit_in_account_currency": 100, + "cost_center": "_Test Cost Center - _TC", + }, + { + "account": "_Test Cash - _TC", + "debit_in_account_currency": 100, + "credit_in_account_currency": 0, + "cost_center": "_Test Cost Center - _TC", + }, + ] + + self.check_gl_entries() + + jv.reload() # @dokos + # Change cost center for bank account - _Test Cost Center for BS Account + create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") + jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC" + jv.save() + + # Check if repost flag gets set on update after submit + self.assertTrue(jv.repost_required) + jv.repost_accounting_entries() + + # Check GL entries after reposting + jv.load_from_db() + self.expected_gle[0]["cost_center"] = "_Test Cost Center for BS Account - _TC" + self.check_gl_entries() + + def check_gl_entries(self): + gl = frappe.qb.DocType("GL Entry") + query = frappe.qb.from_(gl) + for field in self.fields: + query = query.select(gl[field]) + + query = query.where( + (gl.voucher_type == "Journal Entry") + & (gl.voucher_no == self.voucher_no) + & (gl.is_cancelled == 0) + ).orderby(gl.account) + + gl_entries = query.run(as_dict=True) + + for i in range(len(self.expected_gle)): + for field in self.fields: + self.assertEqual(self.expected_gle[i][field], gl_entries[i][field]) + def make_journal_entry( account1, 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 66425cc9a2fdd3dd8234256e3db2680aeb8953ef..2db65f48a985b1d859d3b65145cb9d36881be3ff 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -10,12 +10,10 @@ "account", "accounting_journal", "account_type", - "balance", "col_break1", "bank_account", "party_type", "party", - "party_balance", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -66,17 +64,7 @@ "print_hide": 1 }, { - "fieldname": "balance", - "fieldtype": "Currency", - "label": "Account Balance", - "no_copy": 1, - "oldfieldname": "balance", - "oldfieldtype": "Data", - "options": "account_currency", - "print_hide": 1, - "read_only": 1 - }, - { + "allow_on_submit": 1, "default": ":Company", "description": "If Income or Expense", "fieldname": "cost_center", @@ -109,14 +97,6 @@ "label": "Party", "options": "party_type" }, - { - "fieldname": "party_balance", - "fieldtype": "Currency", - "label": "Party Balance", - "options": "account_currency", - "print_hide": 1, - "read_only": 1 - }, { "fieldname": "currency_section", "fieldtype": "Section Break", @@ -225,6 +205,7 @@ "no_copy": 1 }, { + "allow_on_submit": 1, "fieldname": "project", "fieldtype": "Link", "label": "Project", @@ -304,7 +285,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-23 11:44:25.841187", + "modified": "2024-02-05 01:10:50.224840", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 8fea0dbe83b7252e1f172ae3d395959c781a02c5..47b979ff17391bcc6d6bc4c828c6f72d7f3154b1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -753,6 +753,7 @@ class PurchaseInvoice(BuyingController): "cash_bank_account", "write_off_account", "unrealized_profit_loss_account", + "is_opening", ] child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 3647d7a00da1bdcb181f5962bf9edf954962b6f6..fa77b8ffcdbf22d7006140c521195a2c22fea442 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -744,6 +744,7 @@ class SalesInvoice(SellingController): "write_off_account", "loyalty_redemption_account", "unrealized_profit_loss_account", + "is_opening", ] child_tables = { "items": ("income_account", "expense_account", "discount_account"), diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3d60e0a7719290efcea75dfecd6307c6f7f24b8c..ce18d005ebea7059df5c7bafe0524dd69c02e2a5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2472,27 +2472,20 @@ class AccountsController(TransactionBase): doc_before_update = self.get_doc_before_save() accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"] - # Check if opening entry check updated - needs_repost = doc_before_update.get("is_opening") != self.is_opening - - if not needs_repost: - # Parent Level Accounts excluding party account - fields_to_check += accounting_dimensions - for field in fields_to_check: - if doc_before_update.get(field) != self.get(field): - needs_repost = 1 - break + # Parent Level Accounts excluding party account + fields_to_check += accounting_dimensions + for field in fields_to_check: + if doc_before_update.get(field) != self.get(field): + return True - if not needs_repost: - # Check for child tables - for table in child_tables: - needs_repost = check_if_child_table_updated( - doc_before_update.get(table), self.get(table), child_tables[table] - ) - if needs_repost: - break + # Check for child tables + for table in child_tables: + if check_if_child_table_updated( + doc_before_update.get(table), self.get(table), child_tables[table] + ): + return True - return needs_repost + return False @frappe.whitelist() def repost_accounting_entries(self): @@ -3624,15 +3617,12 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil def check_if_child_table_updated( child_table_before_update, child_table_after_update, fields_to_check ): - accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"] + fields_to_check = list(fields_to_check) + get_accounting_dimensions() + ["cost_center", "project"] + # Check if any field affecting accounting entry is altered - for index, item in enumerate(child_table_after_update): + for index, item in enumerate(child_table_before_update): for field in fields_to_check: - if child_table_before_update[index].get(field) != item.get(field): - return True - - for dimension in accounting_dimensions: - if child_table_before_update[index].get(dimension) != item.get(dimension): + if child_table_after_update[index].get(field) != item.get(field): return True return False diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2f05ec36413155af38835804db6546cc68969cce..a3fbdda0e9e28bd6b82a021d25df2192d4257dbf 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -159,9 +159,6 @@ class StockController(AccountsController): row.serial_no = clean_serial_no_string(row.serial_no) def make_bundle_using_old_serial_batch_fields(self, table_name=None): - from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos - from erpnext.stock.serial_batch_bundle import SerialBatchCreation - if self.get("_action") == "update_after_submit": return @@ -199,31 +196,21 @@ class StockController(AccountsController): "voucher_detail_no": row.name, "company": self.company, "is_rejected": 1 if row.get("rejected_warehouse") else 0, - "serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None, - "batch_no": row.batch_no, "use_serial_batch_fields": row.use_serial_batch_fields, "do_not_submit": True, } - self.update_bundle_details(bundle_details, table_name, row) - sn_doc = SerialBatchCreation(bundle_details).make_serial_and_batch_bundle() + if row.qty: + self.update_bundle_details(bundle_details, table_name, row) + self.create_serial_batch_bundle(bundle_details, row) - if sn_doc.is_rejected: - row.rejected_serial_and_batch_bundle = sn_doc.name - row.db_set( - { - "rejected_serial_and_batch_bundle": sn_doc.name, - } - ) - else: - row.serial_and_batch_bundle = sn_doc.name - row.db_set( - { - "serial_and_batch_bundle": sn_doc.name, - } - ) + if row.get("rejected_qty"): + self.update_bundle_details(bundle_details, table_name, row, is_rejected=True) + self.create_serial_batch_bundle(bundle_details, row) + + def update_bundle_details(self, bundle_details, table_name, row, is_rejected=False): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos - def update_bundle_details(self, bundle_details, table_name, row): # Since qty field is different for different doctypes qty = row.get("qty") warehouse = row.get("warehouse") @@ -242,15 +229,37 @@ class StockController(AccountsController): qty = row.transfer_qty warehouse = row.s_warehouse or row.t_warehouse + serial_nos = row.serial_no + if is_rejected: + serial_nos = row.get("rejected_serial_no") + type_of_transaction = "Inward" if not self.is_return else "Outward" + qty = row.get("rejected_qty") + warehouse = row.get("rejected_warehouse") + bundle_details.update( { "qty": qty, + "is_rejected": is_rejected, "type_of_transaction": type_of_transaction, "warehouse": warehouse, "batches": frappe._dict({row.batch_no: qty}) if row.batch_no else None, + "serial_nos": get_serial_nos(serial_nos) if serial_nos else None, + "batch_no": row.batch_no, } ) + def create_serial_batch_bundle(self, bundle_details, row): + from erpnext.stock.serial_batch_bundle import SerialBatchCreation + + sn_doc = SerialBatchCreation(bundle_details).make_serial_and_batch_bundle() + + field = "serial_and_batch_bundle" + if bundle_details.get("is_rejected"): + field = "rejected_serial_and_batch_bundle" + + row.set(field, sn_doc.name) + row.db_set({field: sn_doc.name}) + def validate_serial_nos_and_batches_with_bundle(self, row): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 15ee0ffb1d8821f1df1f319ac34d6ee5f6fec9fa..ee4fc0d908416c4192f56216954d478dc2332d97 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -355,6 +355,10 @@ erpnext.patches.v14_0.update_zero_asset_quantity_field execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction") execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format")) erpnext.patches.v14_0.update_total_asset_cost_field +erpnext.patches.v15_0.create_advance_payment_status +erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes +erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool + # @dokos erpnext.patches.dokos.v3_0.add_expiration_date_to_booking_legder @@ -383,4 +387,4 @@ erpnext.accounts.doctype.mode_of_payment.patches.migrate_fees_and_cost_center_to # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 -erpnext.patches.v14_0.set_maintain_stock_for_bom_item +erpnext.patches.v14_0.set_maintain_stock_for_bom_item \ No newline at end of file diff --git a/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py b/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py new file mode 100644 index 0000000000000000000000000000000000000000..e75610d0a537080fea9595078bf8d7254ed9fc55 --- /dev/null +++ b/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py @@ -0,0 +1,14 @@ +import frappe + +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + get_allowed_types_from_settings, +) + + +def execute(): + for dt in get_allowed_types_from_settings(): + for dimension in get_accounting_dimensions(): + frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py index 84b256f6c8f13c373b072bfe6e0ec134ab1755a0..271cbbc007fbd51291a49c14c679a87261fac3c2 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py @@ -1,6 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt + from typing import TYPE_CHECKING, Optional, overload import frappe diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index c8f4b77d1a7884045e7a34c14d4f6922223e9e79..b88ae4c8479998cf2c865bc94893a76686839477 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,7 +5,7 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, add_to_date, flt, nowdate, nowtime, today +from frappe.utils import add_days, flt, nowdate, nowtime, today from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import (