From 4dc5d9a6cad5f353867a96ccd3b4d6eaee34dcf9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:49:22 +0530 Subject: [PATCH 01/26] fix: Exchange rate on MR to PO creation for muticurrency POs (#39646) fix: Exchange rate on MR to PO creation for muticurrency POs (#39646) (cherry picked from commit cfd1666181ffdff8ae79bbbb7863e1b12b3c6090) Co-authored-by: Deepesh Garg --- erpnext/stock/doctype/material_request/material_request.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index b84ccf770b2..b0a0158abf4 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -417,6 +417,7 @@ def make_purchase_order(source_name, target_doc=None, args=None): postprocess, ) + doclist.set_onload("load_after_mapping", False) return doclist From 350b2cdde39c2c318f286bc01c54ebdb681e8a2d Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:13:29 +0530 Subject: [PATCH 02/26] fix: correctly calculate diff amount for included taxes (#39655) (cherry picked from commit 772f540bef28117c008512ead6558db801d395cd) --- .../doctype/payment_entry/payment_entry.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 66c82be596c..b89854b172a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -874,19 +874,19 @@ class PaymentEntry(AccountsController): ) base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount) - - if self.payment_type == "Receive": - self.difference_amount = base_party_amount - self.base_received_amount - elif self.payment_type == "Pay": - self.difference_amount = self.base_paid_amount - base_party_amount - else: - self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) - - total_deductions = sum(flt(d.amount) for d in self.get("deductions")) included_taxes = self.get_included_taxes() + if self.payment_type == "Receive": + self.difference_amount = base_party_amount - self.base_received_amount + included_taxes + elif self.payment_type == "Pay": + self.difference_amount = self.base_paid_amount - base_party_amount - included_taxes + else: + self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) - included_taxes + + total_deductions = sum(flt(d.amount) for d in self.get("deductions")) + self.difference_amount = flt( - self.difference_amount - total_deductions - included_taxes, self.precision("difference_amount") + self.difference_amount - total_deductions, self.precision("difference_amount") ) def get_included_taxes(self): From 2fbd11d646a9a3b1f18ff2ea0b250957f3aa216f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 1 Feb 2024 14:53:01 +0530 Subject: [PATCH 03/26] refactor: move ignore ERR filters from SOA to General Ledger (cherry picked from commit c077eda64e8b57011c42fbf7b4fe41af9ab2bc2f) # Conflicts: # erpnext/accounts/report/general_ledger/general_ledger.py --- .../process_statement_of_accounts.py | 16 ++-------------- .../report/general_ledger/general_ledger.js | 6 ++++++ .../report/general_ledger/general_ledger.py | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index d4b4b37b4ee..222c9628018 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -64,18 +64,6 @@ def get_statement_dict(doc, get_statement_dict=False): statement_dict = {} ageing = "" - err_journals = None - if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals: - err_journals = frappe.db.get_all( - "Journal Entry", - filters={ - "company": doc.company, - "docstatus": 1, - "voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]), - }, - as_list=True, - ) - for entry in doc.customers: if doc.include_ageing: ageing = set_ageing(doc, entry) @@ -88,8 +76,8 @@ def get_statement_dict(doc, get_statement_dict=False): ) filters = get_common_filters(doc) - if err_journals: - filters.update({"voucher_no_not_in": [x[0] for x in err_journals]}) + if doc.ignore_exchange_rate_revaluation_journals: + filters.update({"ignore_err": True}) if doc.report == "General Ledger": filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency)) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index f0ac3d0ffdb..2b5abcb7240 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -193,8 +193,14 @@ frappe.query_reports["General Ledger"] = { "fieldname": "show_remarks", "label": __("Show Remarks"), "fieldtype": "Check" + }, + { + "fieldname": "ignore_err", + "label": __("Ignore Exchange Rate Revaluation Journals"), + "fieldtype": "Check" } + ] } diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 95397452b01..ae772377f30 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -231,6 +231,25 @@ def get_conditions(filters): if filters.get("voucher_no"): conditions.append("voucher_no=%(voucher_no)s") +<<<<<<< HEAD +======= + if filters.get("against_voucher_no"): + conditions.append("against_voucher=%(against_voucher_no)s") + + if filters.get("ignore_err"): + err_journals = frappe.db.get_all( + "Journal Entry", + filters={ + "company": filters.get("company"), + "docstatus": 1, + "voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]), + }, + as_list=True, + ) + if err_journals: + filters.update({"voucher_no_not_in": [x[0] for x in err_journals]}) + +>>>>>>> c077eda64e (refactor: move ignore ERR filters from SOA to General Ledger) if filters.get("voucher_no_not_in"): conditions.append("voucher_no not in %(voucher_no_not_in)s") From 686da470fa37674633b80c533b4c9f42f42929b7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 1 Feb 2024 15:03:02 +0530 Subject: [PATCH 04/26] test: ignore_err filter out in General Ledger (cherry picked from commit affca3a519c626a2c3273a82f00ee1579853b0e1) --- .../general_ledger/test_general_ledger.py | 104 +++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index a8c362e78c1..c3ed7f2a23f 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -3,7 +3,7 @@ import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import today +from frappe.utils import flt, today from erpnext.accounts.report.general_ledger.general_ledger import execute @@ -148,3 +148,105 @@ class TestGeneralLedger(FrappeTestCase): self.assertEqual(data[2]["credit"], 900) self.assertEqual(data[3]["debit"], 100) self.assertEqual(data[3]["credit"], 100) + + def test_ignore_exchange_rate_journals_filter(self): + # create a new account with USD currency + account_name = "Test Debtors USD" + company = "_Test Company" + account = frappe.get_doc( + { + "account_name": account_name, + "is_group": 0, + "company": company, + "root_type": "Asset", + "report_type": "Balance Sheet", + "account_currency": "USD", + "parent_account": "Accounts Receivable - _TC", + "account_type": "Receivable", + "doctype": "Account", + } + ) + account.insert(ignore_if_duplicate=True) + # create a JV to debit 1000 USD at 75 exchange rate + jv = frappe.new_doc("Journal Entry") + jv.posting_date = today() + jv.company = company + jv.multi_currency = 1 + jv.cost_center = "_Test Cost Center - _TC" + jv.set( + "accounts", + [ + { + "account": account.name, + "party_type": "Customer", + "party": "_Test Customer", + "debit_in_account_currency": 1000, + "credit_in_account_currency": 0, + "exchange_rate": 75, + "cost_center": "_Test Cost Center - _TC", + }, + { + "account": "Cash - _TC", + "debit_in_account_currency": 0, + "credit_in_account_currency": 75000, + "cost_center": "_Test Cost Center - _TC", + }, + ], + ) + jv.save() + jv.submit() + + revaluation = frappe.new_doc("Exchange Rate Revaluation") + revaluation.posting_date = today() + revaluation.company = company + accounts = revaluation.get_accounts_data() + revaluation.extend("accounts", accounts) + row = revaluation.accounts[0] + row.new_exchange_rate = 83 + row.new_balance_in_base_currency = flt( + row.new_exchange_rate * flt(row.balance_in_account_currency) + ) + row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency) + revaluation.set_total_gain_loss() + revaluation = revaluation.save().submit() + + # post journal entry for Revaluation doc + frappe.db.set_value( + "Company", company, "unrealized_exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC" + ) + revaluation_jv = revaluation.make_jv_for_revaluation() + revaluation_jv.cost_center = "_Test Cost Center - _TC" + for acc in revaluation_jv.get("accounts"): + acc.cost_center = "_Test Cost Center - _TC" + revaluation_jv.save() + revaluation_jv.submit() + + # With ignore_err enabled + columns, data = execute( + frappe._dict( + { + "company": company, + "from_date": today(), + "to_date": today(), + "account": [account.name], + "group_by": "Group by Voucher (Consolidated)", + "ignore_err": True, + } + ) + ) + self.assertNotIn(revaluation_jv.name, set([x.voucher_no for x in data])) + + # Without ignore_err enabled + columns, data = execute( + frappe._dict( + { + "company": company, + "from_date": today(), + "to_date": today(), + "account": [account.name], + "group_by": "Group by Voucher (Consolidated)", + "ignore_err": False, + } + ) + ) + self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data])) From 6284553f23fab6c3d2152680e6a530ffb1a03722 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 1 Feb 2024 15:46:57 +0530 Subject: [PATCH 05/26] refactor(test): use party with USD billing currency (cherry picked from commit beff566c8267104b97f7e4b80c36715e4eb91832) --- erpnext/accounts/report/general_ledger/test_general_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index c3ed7f2a23f..75f94309bcc 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -179,7 +179,7 @@ class TestGeneralLedger(FrappeTestCase): { "account": account.name, "party_type": "Customer", - "party": "_Test Customer", + "party": "_Test Customer USD", "debit_in_account_currency": 1000, "credit_in_account_currency": 0, "exchange_rate": 75, From 68c5a6e86f38522681837ff45a816555d6c923bf Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 1 Feb 2024 16:23:26 +0530 Subject: [PATCH 06/26] chore: resolve conflict --- erpnext/accounts/report/general_ledger/general_ledger.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index ae772377f30..269d25b99db 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -231,11 +231,6 @@ def get_conditions(filters): if filters.get("voucher_no"): conditions.append("voucher_no=%(voucher_no)s") -<<<<<<< HEAD -======= - if filters.get("against_voucher_no"): - conditions.append("against_voucher=%(against_voucher_no)s") - if filters.get("ignore_err"): err_journals = frappe.db.get_all( "Journal Entry", @@ -249,7 +244,6 @@ def get_conditions(filters): if err_journals: filters.update({"voucher_no_not_in": [x[0] for x in err_journals]}) ->>>>>>> c077eda64e (refactor: move ignore ERR filters from SOA to General Ledger) if filters.get("voucher_no_not_in"): conditions.append("voucher_no not in %(voucher_no_not_in)s") From 8035f5b951db8c9eee392340afd8a21e380df7c6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 1 Feb 2024 16:05:27 +0530 Subject: [PATCH 07/26] refactor: use pop up to inform of possible data issue and leave a comment in communcation trail as well (cherry picked from commit 78483e2ee6fdef9e36b63408f6796f0a1e78dac1) --- erpnext/accounts/doctype/account/account.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 22ddc2ffae3..283e9d2f4ef 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -58,6 +58,7 @@ class Account(NestedSet): self.validate_balance_must_be_debit_or_credit() self.validate_account_currency() self.validate_root_company_and_sync_account_to_children() + self.validate_receivable_payable_account_type() def validate_parent(self): """Fetch Parent Details and validate parent account""" @@ -114,6 +115,24 @@ class Account(NestedSet): "Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss" ) + def validate_receivable_payable_account_type(self): + doc_before_save = self.get_doc_before_save() + receivable_payable_types = ["Receivable", "Payable"] + if ( + doc_before_save + and doc_before_save.account_type in receivable_payable_types + and doc_before_save.account_type != self.account_type + ): + # check for ledger entries + if frappe.db.get_all("GL Entry", filters={"account": self.name, "is_cancelled": 0}, limit=1): + msg = _( + "There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report" + ).format( + frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type + ) + frappe.msgprint(msg) + self.add_comment("Comment", msg) + def validate_root_details(self): # does not exists parent if frappe.db.exists("Account", self.name): From f8ca5c5c8bf0783ffd525571d157d4e2d597c8e4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 2 Feb 2024 13:23:54 +0530 Subject: [PATCH 08/26] chore: flag for barcode scanner --- erpnext/public/js/utils/barcode_scanner.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index f1b53cb0721..aadbb24cc04 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -1,6 +1,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { constructor(opts) { this.frm = opts.frm; + // frappe.flags.trigger_from_barcode_scanner is used for custom scripts // field from which to capture input of scanned data this.scan_field_name = opts.scan_field_name || "scan_barcode"; @@ -84,6 +85,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { update_table(data) { return new Promise(resolve => { let cur_grid = this.frm.fields_dict[this.items_table_name].grid; + frappe.flags.trigger_from_barcode_scanner = true; const {item_code, barcode, batch_no, serial_no, uom} = data; @@ -143,12 +145,14 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { revert_selector_flag() { frappe.flags.hide_serial_batch_dialog = false; + frappe.flags.trigger_from_barcode_scanner = false; } set_item(row, item_code, barcode, batch_no, serial_no) { return new Promise(resolve => { const increment = async (value = 1) => { const item_data = {item_code: item_code}; + frappe.flags.trigger_from_barcode_scanner = true; item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value); await frappe.model.set_value(row.doctype, row.name, item_data); return value; From 58ea7f41058ebad832e37e131cad84d4e71a57d0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 2 Feb 2024 17:50:19 +0530 Subject: [PATCH 09/26] refactor: add 'disabled' field to Bank Account --- erpnext/accounts/doctype/bank_account/bank_account.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index 41d79479ca5..b1d53dc1e70 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -13,6 +13,7 @@ "account_type", "account_subtype", "column_break_7", + "disabled", "is_default", "is_company_account", "company", @@ -199,10 +200,16 @@ "fieldtype": "Data", "in_global_search": 1, "label": "Branch Code" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" } ], "links": [], - "modified": "2022-05-04 15:49:42.620630", + "modified": "2024-02-02 17:50:09.768835", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", From 3808ddbf860d9b5e88f544bb6a2108b22aaaca5c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 3 Feb 2024 09:10:28 +0530 Subject: [PATCH 10/26] feat: New financial views - Growth and margin views for P&L and balance sheet (backport #39588) (#39601) feat: New financial views - Growth and margin views for P&L and balance sheet (cherry picked from commit 92649de5c647b1a03689fd77e6a01c29175c65a9) # Conflicts: # erpnext/accounts/report/balance_sheet/balance_sheet.js # erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js Co-authored-by: nitmit --- .../report/balance_sheet/balance_sheet.js | 29 +++++++++++ .../profit_and_loss_statement.js | 23 +++++++++ erpnext/public/js/financial_statements.js | 51 +++++++++++++++++++ 3 files changed, 103 insertions(+) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index f1f8e5f6e7c..1d5d870cbfc 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -6,6 +6,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { erpnext.utils.add_dimensions('Balance Sheet', 10); +<<<<<<< HEAD frappe.query_reports["Balance Sheet"]["filters"].push({ "fieldname": "accumulated_values", "label": __("Accumulated Values"), @@ -19,4 +20,32 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldtype": "Check", "default": 1 }); +======= +frappe.query_reports["Balance Sheet"]["filters"].push( + { + "fieldname": "selected_view", + "label": __("Select View"), + "fieldtype": "Select", + "options": [ + { "value": "Report", "label": __("Report View") }, + { "value": "Growth", "label": __("Growth View") } + ], + "default": "Report", + "reqd": 1 + }, +); + +frappe.query_reports["Balance Sheet"]["filters"].push({ + fieldname: "accumulated_values", + label: __("Accumulated Values"), + fieldtype: "Check", + default: 1, +}); + +frappe.query_reports["Balance Sheet"]["filters"].push({ + fieldname: "include_default_book_entries", + label: __("Include Default FB Entries"), + fieldtype: "Check", + default: 1, +>>>>>>> 92649de5c6 (Adding growth and margin views for P&L and balance sheet financial reports in collaboration with Sapcon Instruments Pvt Ltd) }); diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js index e794f270c2b..d2a2c4b46f5 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -6,6 +6,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { frappe.query_reports["Profit and Loss Statement"] = $.extend({}, erpnext.financial_statements); +<<<<<<< HEAD erpnext.utils.add_dimensions('Profit and Loss Statement', 10); frappe.query_reports["Profit and Loss Statement"]["filters"].push( @@ -16,4 +17,26 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "default": 1 } ); +======= +frappe.query_reports["Profit and Loss Statement"]["filters"].push( + { + "fieldname": "selected_view", + "label": __("Select View"), + "fieldtype": "Select", + "options": [ + { "value": "Report", "label": __("Report View") }, + { "value": "Growth", "label": __("Growth View") }, + { "value": "Margin", "label": __("Margin View") }, + ], + "default": "Report", + "reqd": 1 + }, +); + +frappe.query_reports["Profit and Loss Statement"]["filters"].push({ + fieldname: "accumulated_values", + label: __("Accumulated Values"), + fieldtype: "Check", + default: 1, +>>>>>>> 92649de5c6 (Adding growth and margin views for P&L and balance sheet financial reports in collaboration with Sapcon Instruments Pvt Ltd) }); diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 5e1974299ee..14b99aa6308 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -2,7 +2,58 @@ frappe.provide("erpnext.financial_statements"); erpnext.financial_statements = { "filters": get_filters(), + "baseData": null, "formatter": function(value, row, column, data, default_formatter, filter) { + if(frappe.query_report.get_filter_value("selected_view") == "Growth" && data && column.colIndex >= 3){ + //Assuming that the first three columns are s.no, account name and the very first year of the accounting values, to calculate the relative percentage values of the successive columns. + const lastAnnualValue = row[column.colIndex - 1].content; + const currentAnnualvalue = data[column.fieldname]; + if(currentAnnualvalue == undefined) return 'NA'; //making this not applicable for undefined/null values + let annualGrowth = 0; + if(lastAnnualValue == 0 && currentAnnualvalue > 0){ + //If the previous year value is 0 and the current value is greater than 0 + annualGrowth = 1; + } + else if(lastAnnualValue > 0){ + annualGrowth = (currentAnnualvalue - lastAnnualValue) / lastAnnualValue; + } + + const growthPercent = (Math.round(annualGrowth*10000)/100); //calculating the rounded off percentage + + value = $(`${((growthPercent >=0)? '+':'' )+growthPercent+'%'}`); + if(growthPercent < 0){ + value = $(value).addClass("text-danger"); + } + else{ + value = $(value).addClass("text-success"); + } + value = $(value).wrap("

").parent().html(); + + return value; + } + else if(frappe.query_report.get_filter_value("selected_view") == "Margin" && data){ + if(column.fieldname =="account" && data.account_name == __("Income")){ + //Taking the total income from each column (for all the financial years) as the base (100%) + this.baseData = row; + } + if(column.colIndex >= 2){ + //Assuming that the first two columns are s.no and account name, to calculate the relative percentage values of the successive columns. + const currentAnnualvalue = data[column.fieldname]; + const baseValue = this.baseData[column.colIndex].content; + if(currentAnnualvalue == undefined || baseValue <= 0) return 'NA'; + const marginPercent = Math.round((currentAnnualvalue/baseValue)*10000)/100; + + value = $(`${marginPercent+'%'}`); + if(marginPercent < 0) + value = $(value).addClass("text-danger"); + else + value = $(value).addClass("text-success"); + value = $(value).wrap("

").parent().html(); + return value; + } + + } + if (data && column.fieldname=="account") { value = data.account_name || value; From f01308b97288ac630bf9221df897f13071183b24 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 3 Feb 2024 09:11:12 +0530 Subject: [PATCH 11/26] Revert "feat: New financial views - Growth and margin views for P&L and balance sheet (#39588)" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "feat: New financial views - Growth and margin views for P&L and balan…" This reverts commit 3808ddbf860d9b5e88f544bb6a2108b22aaaca5c. --- .../report/balance_sheet/balance_sheet.js | 29 ----------- .../profit_and_loss_statement.js | 23 --------- erpnext/public/js/financial_statements.js | 51 ------------------- 3 files changed, 103 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index 1d5d870cbfc..f1f8e5f6e7c 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -6,7 +6,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { erpnext.utils.add_dimensions('Balance Sheet', 10); -<<<<<<< HEAD frappe.query_reports["Balance Sheet"]["filters"].push({ "fieldname": "accumulated_values", "label": __("Accumulated Values"), @@ -20,32 +19,4 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldtype": "Check", "default": 1 }); -======= -frappe.query_reports["Balance Sheet"]["filters"].push( - { - "fieldname": "selected_view", - "label": __("Select View"), - "fieldtype": "Select", - "options": [ - { "value": "Report", "label": __("Report View") }, - { "value": "Growth", "label": __("Growth View") } - ], - "default": "Report", - "reqd": 1 - }, -); - -frappe.query_reports["Balance Sheet"]["filters"].push({ - fieldname: "accumulated_values", - label: __("Accumulated Values"), - fieldtype: "Check", - default: 1, -}); - -frappe.query_reports["Balance Sheet"]["filters"].push({ - fieldname: "include_default_book_entries", - label: __("Include Default FB Entries"), - fieldtype: "Check", - default: 1, ->>>>>>> 92649de5c6 (Adding growth and margin views for P&L and balance sheet financial reports in collaboration with Sapcon Instruments Pvt Ltd) }); diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js index d2a2c4b46f5..e794f270c2b 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -6,7 +6,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { frappe.query_reports["Profit and Loss Statement"] = $.extend({}, erpnext.financial_statements); -<<<<<<< HEAD erpnext.utils.add_dimensions('Profit and Loss Statement', 10); frappe.query_reports["Profit and Loss Statement"]["filters"].push( @@ -17,26 +16,4 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "default": 1 } ); -======= -frappe.query_reports["Profit and Loss Statement"]["filters"].push( - { - "fieldname": "selected_view", - "label": __("Select View"), - "fieldtype": "Select", - "options": [ - { "value": "Report", "label": __("Report View") }, - { "value": "Growth", "label": __("Growth View") }, - { "value": "Margin", "label": __("Margin View") }, - ], - "default": "Report", - "reqd": 1 - }, -); - -frappe.query_reports["Profit and Loss Statement"]["filters"].push({ - fieldname: "accumulated_values", - label: __("Accumulated Values"), - fieldtype: "Check", - default: 1, ->>>>>>> 92649de5c6 (Adding growth and margin views for P&L and balance sheet financial reports in collaboration with Sapcon Instruments Pvt Ltd) }); diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 14b99aa6308..5e1974299ee 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -2,58 +2,7 @@ frappe.provide("erpnext.financial_statements"); erpnext.financial_statements = { "filters": get_filters(), - "baseData": null, "formatter": function(value, row, column, data, default_formatter, filter) { - if(frappe.query_report.get_filter_value("selected_view") == "Growth" && data && column.colIndex >= 3){ - //Assuming that the first three columns are s.no, account name and the very first year of the accounting values, to calculate the relative percentage values of the successive columns. - const lastAnnualValue = row[column.colIndex - 1].content; - const currentAnnualvalue = data[column.fieldname]; - if(currentAnnualvalue == undefined) return 'NA'; //making this not applicable for undefined/null values - let annualGrowth = 0; - if(lastAnnualValue == 0 && currentAnnualvalue > 0){ - //If the previous year value is 0 and the current value is greater than 0 - annualGrowth = 1; - } - else if(lastAnnualValue > 0){ - annualGrowth = (currentAnnualvalue - lastAnnualValue) / lastAnnualValue; - } - - const growthPercent = (Math.round(annualGrowth*10000)/100); //calculating the rounded off percentage - - value = $(`${((growthPercent >=0)? '+':'' )+growthPercent+'%'}`); - if(growthPercent < 0){ - value = $(value).addClass("text-danger"); - } - else{ - value = $(value).addClass("text-success"); - } - value = $(value).wrap("

").parent().html(); - - return value; - } - else if(frappe.query_report.get_filter_value("selected_view") == "Margin" && data){ - if(column.fieldname =="account" && data.account_name == __("Income")){ - //Taking the total income from each column (for all the financial years) as the base (100%) - this.baseData = row; - } - if(column.colIndex >= 2){ - //Assuming that the first two columns are s.no and account name, to calculate the relative percentage values of the successive columns. - const currentAnnualvalue = data[column.fieldname]; - const baseValue = this.baseData[column.colIndex].content; - if(currentAnnualvalue == undefined || baseValue <= 0) return 'NA'; - const marginPercent = Math.round((currentAnnualvalue/baseValue)*10000)/100; - - value = $(`${marginPercent+'%'}`); - if(marginPercent < 0) - value = $(value).addClass("text-danger"); - else - value = $(value).addClass("text-success"); - value = $(value).wrap("

").parent().html(); - return value; - } - - } - if (data && column.fieldname=="account") { value = data.account_name || value; From e49f8d5f55b0cafda012475e94d48e11577a86e4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:05:47 +0530 Subject: [PATCH 12/26] fix: don't overwrite existing terms in transaction (#39519) * fix: don't overwrite existing terms in transaction (cherry picked from commit 77b044f1a6e7273b21ce5a884429875b10d3bb2e) * refactor: keep the diff small --------- Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> --- erpnext/public/js/controllers/transaction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 48bde9dbc98..d5bc7647647 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -746,14 +746,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"]; if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") && - selling_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) { + selling_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) { me.frm.set_value("tc_name", company_doc.default_selling_terms); } let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order", "Material Request", "Purchase Receipt"]; // Purchase Invoice is excluded as per issue #3345 if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") && - buying_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) { + buying_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) { me.frm.set_value("tc_name", company_doc.default_buying_terms); } From 4a609d8fa8b8f0688e883382181c80063a7cdc1f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 1 Feb 2024 19:29:06 +0530 Subject: [PATCH 13/26] fix: incorrect landed cost voucher amount (cherry picked from commit d78a1e78148a3702990ae347a2b1c31b580c308e) --- .../doctype/landed_cost_voucher/landed_cost_voucher.py | 7 +++++++ .../stock/doctype/purchase_receipt/purchase_receipt.py | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 3511cec2fd7..c73dc65f8a8 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -122,6 +122,13 @@ class LandedCostVoucher(Document): self.get("items")[item_count - 1].applicable_charges += diff def validate_applicable_charges_for_item(self): + if self.distribute_charges_based_on == "Distribute Manually" and len(self.taxes) > 1: + frappe.throw( + _( + "Please keep one Applicable Charges, when 'Distribute Charges Based On' is 'Distribute Manually'. For more charges, please create another Landed Cost Voucher." + ) + ) + based_on = self.distribute_charges_based_on.lower() if based_on != "distribute manually": diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d44bb4d26d8..274073b638e 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1200,16 +1200,16 @@ def get_item_account_wise_additional_cost(purchase_document): for lcv in landed_cost_vouchers: landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent) + based_on_field = None # Use amount field for total item cost for manually cost distributed LCVs - if landed_cost_voucher_doc.distribute_charges_based_on == "Distribute Manually": - based_on_field = "amount" - else: + if landed_cost_voucher_doc.distribute_charges_based_on != "Distribute Manually": based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on) total_item_cost = 0 - for item in landed_cost_voucher_doc.items: - total_item_cost += item.get(based_on_field) + if based_on_field: + for item in landed_cost_voucher_doc.items: + total_item_cost += item.get(based_on_field) for item in landed_cost_voucher_doc.items: if item.receipt_document == purchase_document: From 85e6b39e23ae468e88eb49af9f5981ac854a5ac3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 31 Jan 2024 11:32:17 +0530 Subject: [PATCH 14/26] perf: timeout for auto material request through reorder level (cherry picked from commit 951023f434a36ef03f874b3dcbd4f995168b7b5a) # Conflicts: # erpnext/stock/doctype/stock_entry/test_stock_entry.py --- erpnext/controllers/selling_controller.py | 2 +- .../doctype/stock_entry/test_stock_entry.py | 84 +++++++++ erpnext/stock/get_item_details.py | 7 +- erpnext/stock/reorder_item.py | 163 +++++++++++++----- 4 files changed, 214 insertions(+), 42 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index d55aeeacc08..c9b12f4f723 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -586,7 +586,7 @@ class SellingController(StockController): if self.doctype in ["Sales Order", "Quotation"]: for item in self.items: item.gross_profit = flt( - ((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item) + ((item.base_rate - flt(item.valuation_rate)) * item.stock_qty), self.precision("amount", item) ) def set_customer_address(self): diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 8afe23a1d7a..92430861c05 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -1734,6 +1734,90 @@ class TestStockEntry(FrappeTestCase): self.assertFalse(doc.is_enqueue_action()) frappe.flags.in_test = True +<<<<<<< HEAD +======= + def test_negative_batch(self): + item_code = "Test Negative Batch Item - 001" + make_item( + item_code, + {"has_batch_no": 1, "create_new_batch": 1, "batch_naming_series": "Test-BCH-NNS.#####"}, + ) + + se1 = make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + qty=100, + target="_Test Warehouse - _TC", + ) + + se1.reload() + + batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle) + + se2 = make_stock_entry( + item_code=item_code, + purpose="Material Issue", + batch_no=batch_no, + qty=10, + source="_Test Warehouse - _TC", + ) + + se2.reload() + + se3 = make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + qty=100, + target="_Test Warehouse - _TC", + ) + + se3.reload() + + self.assertRaises(frappe.ValidationError, se1.cancel) + + def test_auto_reorder_level(self): + from erpnext.stock.reorder_item import reorder_item + + item_doc = make_item( + "Test Auto Reorder Item - 001", + properties={"stock_uom": "Kg", "purchase_uom": "Nos", "is_stock_item": 1}, + uoms=[{"uom": "Nos", "conversion_factor": 5}], + ) + + if not frappe.db.exists("Item Reorder", {"parent": item_doc.name}): + item_doc.append( + "reorder_levels", + { + "warehouse_reorder_level": 0, + "warehouse_reorder_qty": 10, + "warehouse": "_Test Warehouse - _TC", + "material_request_type": "Purchase", + }, + ) + + item_doc.save(ignore_permissions=True) + + frappe.db.set_single_value("Stock Settings", "auto_indent", 1) + + mr_list = reorder_item() + + frappe.db.set_single_value("Stock Settings", "auto_indent", 0) + mrs = frappe.get_all( + "Material Request Item", + fields=["qty", "stock_uom", "stock_qty"], + filters={"item_code": item_doc.name, "uom": "Nos"}, + ) + + for mri in mrs: + self.assertEqual(mri.stock_uom, "Kg") + self.assertEqual(mri.stock_qty, 10) + self.assertEqual(mri.qty, 2) + + for mr in mr_list: + mr.cancel() + mr.delete() + +>>>>>>> 951023f434 (perf: timeout for auto material request through reorder level) def make_serialized_item(**args): args = frappe._dict(args) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 10d3ef4b7a9..37a6a0d6892 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -87,7 +87,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru get_party_item_code(args, item, out) - set_valuation_rate(out, args) + if args.get("doctype") in ["Sales Order", "Quotation"]: + set_valuation_rate(out, args) update_party_blanket_order(args, out) @@ -303,7 +304,9 @@ def get_basic_details(args, item, overwrite_warehouse=True): if not item: item = frappe.get_doc("Item", args.get("item_code")) - if item.variant_of and not item.taxes: + if ( + item.variant_of and not item.taxes and frappe.db.exists("Item Tax", {"parent": item.variant_of}) + ): item.update_template_tables() item_defaults = get_item_defaults(item.name, args.company) diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index e172cecb236..de4242893c6 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -34,73 +34,157 @@ def _reorder_item(): erpnext.get_default_company() or frappe.db.sql("""select name from tabCompany limit 1""")[0][0] ) - items_to_consider = frappe.db.sql_list( - """select name from `tabItem` item - where is_stock_item=1 and has_variants=0 - and disabled=0 - and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s) - and (exists (select name from `tabItem Reorder` ir where ir.parent=item.name) - or (variant_of is not null and variant_of != '' - and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of)) - )""", - {"today": nowdate()}, - ) + items_to_consider = get_items_for_reorder() if not items_to_consider: return item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider) - def add_to_material_request( - item_code, warehouse, reorder_level, reorder_qty, material_request_type, warehouse_group=None - ): - if warehouse not in warehouse_company: + def add_to_material_request(**kwargs): + if isinstance(kwargs, dict): + kwargs = frappe._dict(kwargs) + + if kwargs.warehouse not in warehouse_company: # a disabled warehouse return - reorder_level = flt(reorder_level) - reorder_qty = flt(reorder_qty) + reorder_level = flt(kwargs.reorder_level) + reorder_qty = flt(kwargs.reorder_qty) # projected_qty will be 0 if Bin does not exist - if warehouse_group: - projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse_group)) + if kwargs.warehouse_group: + projected_qty = flt( + item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse_group) + ) else: - projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse)) + projected_qty = flt( + item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse) + ) if (reorder_level or reorder_qty) and projected_qty < reorder_level: deficiency = reorder_level - projected_qty if deficiency > reorder_qty: reorder_qty = deficiency - company = warehouse_company.get(warehouse) or default_company + company = warehouse_company.get(kwargs.warehouse) or default_company - material_requests[material_request_type].setdefault(company, []).append( - {"item_code": item_code, "warehouse": warehouse, "reorder_qty": reorder_qty} + material_requests[kwargs.material_request_type].setdefault(company, []).append( + { + "item_code": kwargs.item_code, + "warehouse": kwargs.warehouse, + "reorder_qty": reorder_qty, + "item_details": kwargs.item_details, + } ) - for item_code in items_to_consider: - item = frappe.get_doc("Item", item_code) + for item_code, reorder_levels in items_to_consider.items(): + for d in reorder_levels: + if d.has_variants: + continue - if item.variant_of and not item.get("reorder_levels"): - item.update_template_tables() - - if item.get("reorder_levels"): - for d in item.get("reorder_levels"): - add_to_material_request( - item_code, - d.warehouse, - d.warehouse_reorder_level, - d.warehouse_reorder_qty, - d.material_request_type, - warehouse_group=d.warehouse_group, - ) + add_to_material_request( + item_code=item_code, + warehouse=d.warehouse, + reorder_level=d.warehouse_reorder_level, + reorder_qty=d.warehouse_reorder_qty, + material_request_type=d.material_request_type, + warehouse_group=d.warehouse_group, + item_details=frappe._dict( + { + "item_code": item_code, + "name": item_code, + "item_name": d.item_name, + "item_group": d.item_group, + "brand": d.brand, + "description": d.description, + "stock_uom": d.stock_uom, + "purchase_uom": d.purchase_uom, + } + ), + ) if material_requests: return create_material_request(material_requests) +def get_items_for_reorder() -> dict[str, list]: + reorder_table = frappe.qb.DocType("Item Reorder") + item_table = frappe.qb.DocType("Item") + + query = ( + frappe.qb.from_(reorder_table) + .inner_join(item_table) + .on(reorder_table.parent == item_table.name) + .select( + reorder_table.warehouse, + reorder_table.warehouse_group, + reorder_table.material_request_type, + reorder_table.warehouse_reorder_level, + reorder_table.warehouse_reorder_qty, + item_table.name, + item_table.stock_uom, + item_table.purchase_uom, + item_table.description, + item_table.item_name, + item_table.item_group, + item_table.brand, + item_table.variant_of, + item_table.has_variants, + ) + .where( + (item_table.disabled == 0) + & (item_table.is_stock_item == 1) + & ( + (item_table.end_of_life.isnull()) + | (item_table.end_of_life > nowdate()) + | (item_table.end_of_life == "0000-00-00") + ) + ) + ) + + data = query.run(as_dict=True) + itemwise_reorder = frappe._dict({}) + for d in data: + itemwise_reorder.setdefault(d.name, []).append(d) + + itemwise_reorder = get_reorder_levels_for_variants(itemwise_reorder) + + return itemwise_reorder + + +def get_reorder_levels_for_variants(itemwise_reorder): + item_table = frappe.qb.DocType("Item") + + query = ( + frappe.qb.from_(item_table) + .select( + item_table.name, + item_table.variant_of, + ) + .where( + (item_table.disabled == 0) + & (item_table.is_stock_item == 1) + & ( + (item_table.end_of_life.isnull()) + | (item_table.end_of_life > nowdate()) + | (item_table.end_of_life == "0000-00-00") + ) + & (item_table.variant_of.notnull()) + ) + ) + + variants_item = query.run(as_dict=True) + for row in variants_item: + if not itemwise_reorder.get(row.name) and itemwise_reorder.get(row.variant_of): + itemwise_reorder.setdefault(row.name, []).extend(itemwise_reorder.get(row.variant_of, [])) + + return itemwise_reorder + + def get_item_warehouse_projected_qty(items_to_consider): item_warehouse_projected_qty = {} + items_to_consider = list(items_to_consider.keys()) for item_code, warehouse, projected_qty in frappe.db.sql( """select item_code, warehouse, projected_qty @@ -164,7 +248,7 @@ def create_material_request(material_requests): for d in items: d = frappe._dict(d) - item = frappe.get_doc("Item", d.item_code) + item = d.get("item_details") uom = item.stock_uom conversion_factor = 1.0 @@ -190,6 +274,7 @@ def create_material_request(material_requests): "item_code": d.item_code, "schedule_date": add_days(nowdate(), cint(item.lead_time_days)), "qty": qty, + "conversion_factor": conversion_factor, "uom": uom, "stock_uom": item.stock_uom, "warehouse": d.warehouse, From b32848d69dfb6614b013eca0df328e7923523a21 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 5 Feb 2024 11:46:39 +0530 Subject: [PATCH 15/26] perf: memory consumption for the stock balance report (#39626) (cherry picked from commit b70f3de16be169a723842a3a99c046a3809d0768) # Conflicts: # erpnext/stock/report/stock_balance/stock_balance.py --- .../report/stock_balance/stock_balance.py | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 7a5a8615d0c..4e6f9865aca 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -90,8 +90,7 @@ class StockBalanceReport(object): self.opening_data.setdefault(group_by_key, entry) def prepare_new_data(self): - if not self.sle_entries: - return + self.item_warehouse_map = self.get_item_warehouse_map() if self.filters.get("show_stock_ageing_data"): self.filters["show_warehouse_wise_stock"] = True @@ -99,7 +98,13 @@ class StockBalanceReport(object): _func = itemgetter(1) +<<<<<<< HEAD self.item_warehouse_map = self.get_item_warehouse_map() +======= + del self.sle_entries + + sre_details = self.get_sre_reserved_qty_details() +>>>>>>> b70f3de16b (perf: memory consumption for the stock balance report (#39626)) variant_values = {} if self.filters.get("show_variant_attributes"): @@ -139,15 +144,22 @@ class StockBalanceReport(object): item_warehouse_map = {} self.opening_vouchers = self.get_opening_vouchers() - for entry in self.sle_entries: - group_by_key = self.get_group_by_key(entry) - if group_by_key not in item_warehouse_map: - self.initialize_data(item_warehouse_map, group_by_key, entry) + if self.filters.get("show_stock_ageing_data"): + self.sle_entries = self.sle_query.run(as_dict=True) - self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key) + with frappe.db.unbuffered_cursor(): + if not self.filters.get("show_stock_ageing_data"): + self.sle_entries = self.sle_query.run(as_dict=True, as_iterator=True) - if self.opening_data.get(group_by_key): - del self.opening_data[group_by_key] + for entry in self.sle_entries: + group_by_key = self.get_group_by_key(entry) + if group_by_key not in item_warehouse_map: + self.initialize_data(item_warehouse_map, group_by_key, entry) + + self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key) + + if self.opening_data.get(group_by_key): + del self.opening_data[group_by_key] for group_by_key, entry in self.opening_data.items(): if group_by_key not in item_warehouse_map: @@ -236,7 +248,8 @@ class StockBalanceReport(object): .where( (table.docstatus == 1) & (table.company == self.filters.company) - & ((table.to_date <= self.from_date)) + & (table.to_date <= self.from_date) + & (table.status == "Completed") ) .orderby(table.to_date, order=Order.desc) .limit(1) @@ -289,7 +302,7 @@ class StockBalanceReport(object): if self.filters.get("company"): query = query.where(sle.company == self.filters.get("company")) - self.sle_entries = query.run(as_dict=True) + self.sle_query = query def apply_inventory_dimensions_filters(self, query, sle) -> str: inventory_dimension_fields = self.get_inventory_dimension_fields() From 26dfbb7a641a4a3342030daad6e1c21ff3175103 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 5 Feb 2024 12:13:14 +0530 Subject: [PATCH 16/26] chore: fix conflicts --- erpnext/stock/report/stock_balance/stock_balance.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 4e6f9865aca..32a2b302d7b 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -98,14 +98,8 @@ class StockBalanceReport(object): _func = itemgetter(1) -<<<<<<< HEAD - self.item_warehouse_map = self.get_item_warehouse_map() -======= del self.sle_entries - sre_details = self.get_sre_reserved_qty_details() ->>>>>>> b70f3de16b (perf: memory consumption for the stock balance report (#39626)) - variant_values = {} if self.filters.get("show_variant_attributes"): variant_values = self.get_variant_values_for() From e38bb836a5c4b58f8973e5389b63348006a49f86 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 5 Feb 2024 13:20:03 +0530 Subject: [PATCH 17/26] chore: fix conflicts --- .../doctype/stock_entry/test_stock_entry.py | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 92430861c05..1d7e4da26d5 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -1734,47 +1734,6 @@ class TestStockEntry(FrappeTestCase): self.assertFalse(doc.is_enqueue_action()) frappe.flags.in_test = True -<<<<<<< HEAD -======= - def test_negative_batch(self): - item_code = "Test Negative Batch Item - 001" - make_item( - item_code, - {"has_batch_no": 1, "create_new_batch": 1, "batch_naming_series": "Test-BCH-NNS.#####"}, - ) - - se1 = make_stock_entry( - item_code=item_code, - purpose="Material Receipt", - qty=100, - target="_Test Warehouse - _TC", - ) - - se1.reload() - - batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle) - - se2 = make_stock_entry( - item_code=item_code, - purpose="Material Issue", - batch_no=batch_no, - qty=10, - source="_Test Warehouse - _TC", - ) - - se2.reload() - - se3 = make_stock_entry( - item_code=item_code, - purpose="Material Receipt", - qty=100, - target="_Test Warehouse - _TC", - ) - - se3.reload() - - self.assertRaises(frappe.ValidationError, se1.cancel) - def test_auto_reorder_level(self): from erpnext.stock.reorder_item import reorder_item @@ -1817,7 +1776,6 @@ class TestStockEntry(FrappeTestCase): mr.cancel() mr.delete() ->>>>>>> 951023f434 (perf: timeout for auto material request through reorder level) def make_serialized_item(**args): args = frappe._dict(args) From 7952bf43187c88b1c5ce5fd4a84ec45f526d72a2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:05:24 +0530 Subject: [PATCH 18/26] feat: copy emails from lead to customer (#38647) feat: copy emails from lead to customer (cherry picked from commit 906ac093e37694c4616cffb961b131ce9002315b) Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> --- erpnext/selling/doctype/customer/customer.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index b7fcadb1eca..74f68ea6922 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -171,6 +171,7 @@ class Customer(TransactionBase): if self.flags.is_new_doc: self.link_lead_address_and_contact() + self.copy_communication() self.update_customer_groups() @@ -224,6 +225,17 @@ class Customer(TransactionBase): linked_doc.append("links", dict(link_doctype="Customer", link_name=self.name)) linked_doc.save(ignore_permissions=self.flags.ignore_permissions) + def copy_communication(self): + if not self.lead_name or not frappe.db.get_single_value( + "CRM Settings", "carry_forward_communication_and_comments" + ): + return + + from erpnext.crm.utils import copy_comments, link_communications + + copy_comments("Lead", self.lead_name, self) + link_communications("Lead", self.lead_name, self) + def validate_name_with_customer_group(self): if frappe.db.exists("Customer Group", self.name): frappe.throw( From 7691256f4df404ca94f826feb2bc05f280b2857a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:34:16 +0530 Subject: [PATCH 19/26] perf: Move dimension validation out of GL Entry doctype (#39730) perf: Move dimension validation out of GL Entry doctype (#39730) (cherry picked from commit b834ed10d6b6faa1f76e1343ca8b347ca393b7dd) Co-authored-by: Deepesh Garg --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 46 +------------------ erpnext/accounts/general_ledger.py | 42 +++++++++++++++++ 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 3a564825b55..6e07b0ec430 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -13,16 +13,9 @@ import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_checks_for_pl_and_bs_accounts, ) -from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import ( - get_dimension_filter_map, -) from erpnext.accounts.party import validate_party_frozen_disabled, validate_party_gle_currency from erpnext.accounts.utils import get_account_currency, get_fiscal_year -from erpnext.exceptions import ( - InvalidAccountCurrency, - InvalidAccountDimensionError, - MandatoryAccountDimensionError, -) +from erpnext.exceptions import InvalidAccountCurrency exclude_from_linked_with = True @@ -54,7 +47,6 @@ class GLEntry(Document): if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher": self.validate_account_details(adv_adj) self.validate_dimensions_for_pl_and_bs() - self.validate_allowed_dimensions() validate_balance_type(self.account, adv_adj) validate_frozen_account(self.account, adv_adj) @@ -164,42 +156,6 @@ class GLEntry(Document): ) ) - def validate_allowed_dimensions(self): - dimension_filter_map = get_dimension_filter_map() - for key, value in dimension_filter_map.items(): - dimension = key[0] - account = key[1] - - if self.account == account: - if value["is_mandatory"] and not self.get(dimension): - frappe.throw( - _("{0} is mandatory for account {1}").format( - frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account) - ), - MandatoryAccountDimensionError, - ) - - if value["allow_or_restrict"] == "Allow": - if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]: - frappe.throw( - _("Invalid value {0} for {1} against account {2}").format( - frappe.bold(self.get(dimension)), - frappe.bold(frappe.unscrub(dimension)), - frappe.bold(self.account), - ), - InvalidAccountDimensionError, - ) - else: - if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]: - frappe.throw( - _("Invalid value {0} for {1} against account {2}").format( - frappe.bold(self.get(dimension)), - frappe.bold(frappe.unscrub(dimension)), - frappe.bold(self.account), - ), - InvalidAccountDimensionError, - ) - def check_pl_account(self): if ( self.is_opening == "Yes" diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 134ddddf9e0..38fcd976ad4 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -13,9 +13,13 @@ import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) +from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import ( + get_dimension_filter_map, +) from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.utils import create_payment_ledger_entry +from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError def make_gl_entries( @@ -354,6 +358,7 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): process_debit_credit_difference(gl_map) + dimension_filter_map = get_dimension_filter_map() if gl_map: check_freezing_date(gl_map[0]["posting_date"], adv_adj) is_opening = any(d.get("is_opening") == "Yes" for d in gl_map) @@ -361,6 +366,7 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"]) for entry in gl_map: + validate_allowed_dimensions(entry, dimension_filter_map) make_entry(entry, adv_adj, update_outstanding, from_repost) @@ -672,3 +678,39 @@ def set_as_cancel(voucher_type, voucher_no): where voucher_type=%s and voucher_no=%s and is_cancelled = 0""", (now(), frappe.session.user, voucher_type, voucher_no), ) + + +def validate_allowed_dimensions(gl_entry, dimension_filter_map): + for key, value in dimension_filter_map.items(): + dimension = key[0] + account = key[1] + + if gl_entry.account == account: + if value["is_mandatory"] and not gl_entry.get(dimension): + frappe.throw( + _("{0} is mandatory for account {1}").format( + frappe.bold(frappe.unscrub(dimension)), frappe.bold(gl_entry.account) + ), + MandatoryAccountDimensionError, + ) + + if value["allow_or_restrict"] == "Allow": + if gl_entry.get(dimension) and gl_entry.get(dimension) not in value["allowed_dimensions"]: + frappe.throw( + _("Invalid value {0} for {1} against account {2}").format( + frappe.bold(gl_entry.get(dimension)), + frappe.bold(frappe.unscrub(dimension)), + frappe.bold(gl_entry.account), + ), + InvalidAccountDimensionError, + ) + else: + if gl_entry.get(dimension) and gl_entry.get(dimension) in value["allowed_dimensions"]: + frappe.throw( + _("Invalid value {0} for {1} against account {2}").format( + frappe.bold(gl_entry.get(dimension)), + frappe.bold(frappe.unscrub(dimension)), + frappe.bold(gl_entry.account), + ), + InvalidAccountDimensionError, + ) From 46ac4f471423ff652510e582f7a3e59141db2f02 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 16:09:09 +0530 Subject: [PATCH 20/26] fix: remove applied pricing rule on qty change (backport #39688) (#39736) fix: remove pricing rule (cherry picked from commit 7c6a5a0f23b948953815870b726c30b0fd076338) Co-authored-by: s-aga-r --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a4597da8e27..a3f48141aac 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -671,7 +671,7 @@ class AccountsController(TransactionBase): if self.get("is_subcontracted"): args["is_subcontracted"] = self.is_subcontracted - ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False) + ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False) for fieldname, value in ret.items(): if item.meta.get_field(fieldname) and value is not None: From 44c09de7298005651c09e62ba578b7a8126e80ef Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 16:52:01 +0530 Subject: [PATCH 21/26] fix: Blanket Order Ordered Quantity (backport #39725) (#39738) * fix: disable no-copy for blanket order in PO (cherry picked from commit 5ce5c352e4ab2293b3f5b5dac9bc1a2a1912e620) # Conflicts: # erpnext/buying/doctype/purchase_order_item/purchase_order_item.json * fix: update BO Ordered Quantity on PO Close/Open (cherry picked from commit 61ded697a7384d8ef133a42424d8a14763bb6061) # Conflicts: # erpnext/buying/doctype/purchase_order/purchase_order.py * test: BO on PO Close/Open (cherry picked from commit 27d6c8b6d52ada292eef5c42506e95bcf933eec8) * chore: `conflicts` --------- Co-authored-by: s-aga-r --- .../doctype/purchase_order/purchase_order.py | 1 + .../purchase_order/test_purchase_order.py | 25 +++++++++++++++++++ .../purchase_order_item.json | 4 +-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 8131104b825..5d1dfafc3c9 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -317,6 +317,7 @@ class PurchaseOrder(BuyingController): self.update_requested_qty() self.update_ordered_qty() self.update_reserved_qty_for_subcontract() + self.update_blanket_order() self.notify_update() clear_doctype_notifications(self) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index b0bbc5d0c71..739a989c79e 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -814,6 +814,30 @@ class TestPurchaseOrder(FrappeTestCase): # To test if the PO does NOT have a Blanket Order self.assertEqual(po_doc.items[0].blanket_order, None) + def test_blanket_order_on_po_close_and_open(self): + # Step - 1: Create Blanket Order + bo = make_blanket_order(blanket_order_type="Purchasing", quantity=10, rate=10) + + # Step - 2: Create Purchase Order + po = create_purchase_order( + item_code="_Test Item", qty=5, against_blanket_order=1, against_blanket=bo.name + ) + + bo.load_from_db() + self.assertEqual(bo.items[0].ordered_qty, 5) + + # Step - 3: Close Purchase Order + po.update_status("Closed") + + bo.load_from_db() + self.assertEqual(bo.items[0].ordered_qty, 0) + + # Step - 4: Re-Open Purchase Order + po.update_status("Re-open") + + bo.load_from_db() + self.assertEqual(bo.items[0].ordered_qty, 5) + def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, @@ -1016,6 +1040,7 @@ def create_purchase_order(**args): "schedule_date": add_days(nowdate(), 1), "include_exploded_items": args.get("include_exploded_items", 1), "against_blanket_order": args.against_blanket_order, + "against_blanket": args.against_blanket, "material_request": args.material_request, "material_request_item": args.material_request_item, }, diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 1a9035c3327..feb1a9b8828 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -546,7 +546,6 @@ "fieldname": "blanket_order", "fieldtype": "Link", "label": "Blanket Order", - "no_copy": 1, "options": "Blanket Order" }, { @@ -554,7 +553,6 @@ "fieldname": "blanket_order_rate", "fieldtype": "Currency", "label": "Blanket Order Rate", - "no_copy": 1, "print_hide": 1, "read_only": 1 }, @@ -918,7 +916,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-24 19:07:34.921094", + "modified": "2024-02-05 11:23:24.859435", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", From a1a70bbae0723f3543f44daeaf675a0989061d76 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 5 Feb 2024 19:26:31 +0530 Subject: [PATCH 22/26] perf: timeout while submitting the purchase receipt entry (cherry picked from commit 1fa62333773a59feb5a0a39d274ebcae362ed286) --- erpnext/buying/doctype/purchase_order/purchase_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 5d1dfafc3c9..9d4846056ea 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -457,6 +457,7 @@ class PurchaseOrder(BuyingController): ) +@frappe.request_cache def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0): """get last purchase rate for an item""" From 0163e13aa30aaebd65a598ad38322226bbd4bdfa Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 2 Feb 2024 17:45:50 +0530 Subject: [PATCH 23/26] refactor: ensure unique accounts for each Bank Account's (cherry picked from commit 2caa2d677c09793f7097c3e76ebb4180a3f2a336) --- .../accounts/doctype/bank_account/bank_account.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index addcf62e5b6..ece27e77ee9 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -9,6 +9,7 @@ from frappe.contacts.address_and_contact import ( load_address_and_contact, ) from frappe.model.document import Document +from frappe.utils import comma_and, get_link_to_form class BankAccount(Document): @@ -25,6 +26,17 @@ class BankAccount(Document): def validate(self): self.validate_company() self.validate_iban() + self.validate_account() + + def validate_account(self): + if self.account: + if accounts := frappe.db.get_all("Bank Account", filters={"account": self.account}, as_list=1): + frappe.throw( + _("'{0}' account is already used by {1}. Use another account.").format( + frappe.bold(self.account), + frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])), + ) + ) def validate_company(self): if self.is_company_account and not self.company: From f64f0437aecbc23d319cafb2a282e56b6a5efc08 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 3 Feb 2024 10:58:31 +0530 Subject: [PATCH 24/26] refactor(test): generate uniq GL acc and Bank acc for each test case (cherry picked from commit a9a2ec81de73cde0995c837e12cd5dc79a584841) --- .../bank_transaction/test_bank_transaction.py | 72 ++++++++++++------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index eb0dc74825d..b35ed01cd9a 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -32,8 +32,16 @@ class TestBankTransaction(FrappeTestCase): frappe.db.delete(dt) make_pos_profile() - add_transactions() - add_vouchers() + + # generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error + uniq_identifier = frappe.generate_hash(length=10) + gl_account = create_gl_account("_Test Bank " + uniq_identifier) + bank_account = create_bank_account( + gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier + ) + + add_transactions(bank_account=bank_account) + add_vouchers(gl_account=gl_account) # This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. def test_linked_payments(self): @@ -213,7 +221,9 @@ class TestBankTransaction(FrappeTestCase): self.assertEqual(linked_payments[0][2], repayment_entry.name) -def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"): +def create_bank_account( + bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account" +): try: frappe.get_doc( { @@ -225,21 +235,35 @@ def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"): pass try: - frappe.get_doc( + bank_account = frappe.get_doc( { "doctype": "Bank Account", - "account_name": "Checking Account", + "account_name": bank_account_name, "bank": bank_name, - "account": account_name, + "account": gl_account, } ).insert(ignore_if_duplicate=True) except frappe.DuplicateEntryError: pass + return bank_account.name -def add_transactions(): - create_bank_account() +def create_gl_account(gl_account_name="_Test Bank - _TC"): + gl_account = frappe.get_doc( + { + "doctype": "Account", + "company": "_Test Company", + "parent_account": "Current Assets - _TC", + "account_type": "Bank", + "is_group": 0, + "account_name": gl_account_name, + } + ).insert() + return gl_account.name + + +def add_transactions(bank_account="_Test Bank - _TC"): doc = frappe.get_doc( { "doctype": "Bank Transaction", @@ -247,7 +271,7 @@ def add_transactions(): "date": "2018-10-23", "deposit": 1200, "currency": "INR", - "bank_account": "Checking Account - Citi Bank", + "bank_account": bank_account, } ).insert() doc.submit() @@ -259,7 +283,7 @@ def add_transactions(): "date": "2018-10-23", "deposit": 1700, "currency": "INR", - "bank_account": "Checking Account - Citi Bank", + "bank_account": bank_account, } ).insert() doc.submit() @@ -271,7 +295,7 @@ def add_transactions(): "date": "2018-10-26", "withdrawal": 690, "currency": "INR", - "bank_account": "Checking Account - Citi Bank", + "bank_account": bank_account, } ).insert() doc.submit() @@ -283,7 +307,7 @@ def add_transactions(): "date": "2018-10-27", "deposit": 3900, "currency": "INR", - "bank_account": "Checking Account - Citi Bank", + "bank_account": bank_account, } ).insert() doc.submit() @@ -295,13 +319,13 @@ def add_transactions(): "date": "2018-10-27", "withdrawal": 109080, "currency": "INR", - "bank_account": "Checking Account - Citi Bank", + "bank_account": bank_account, } ).insert() doc.submit() -def add_vouchers(): +def add_vouchers(gl_account="_Test Bank - _TC"): try: frappe.get_doc( { @@ -317,7 +341,7 @@ def add_vouchers(): 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 = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe.reference_no = "Conrad Oct 18" pe.reference_date = "2018-10-24" pe.insert() @@ -336,14 +360,14 @@ def add_vouchers(): pass pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200) - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe.reference_no = "Herr G Oct 18" pe.reference_date = "2018-10-24" pe.insert() pe.submit() pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700) - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe.reference_no = "Herr G Nov 18" pe.reference_date = "2018-11-01" pe.insert() @@ -374,10 +398,10 @@ def add_vouchers(): pass pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1) - pi.cash_bank_account = "_Test Bank - _TC" + pi.cash_bank_account = gl_account pi.insert() pi.submit() - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe.reference_no = "Poore Simon's Oct 18" pe.reference_date = "2018-10-28" pe.paid_amount = 690 @@ -386,7 +410,7 @@ def add_vouchers(): pe.submit() si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900) - pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") + pe = get_payment_entry("Sales Invoice", si.name, bank_account=gl_account) pe.reference_no = "Poore Simon's Oct 18" pe.reference_date = "2018-10-28" pe.insert() @@ -409,16 +433,12 @@ def add_vouchers(): if not frappe.db.get_value( "Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"} ): - mode_of_payment.append( - "accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"} - ) + mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account}) mode_of_payment.save() si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1) si.is_pos = 1 - si.append( - "payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080} - ) + si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080}) si.insert() si.submit() From 8267fee9b8666cecda4884a1c8743549ec406245 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 3 Feb 2024 12:08:01 +0530 Subject: [PATCH 25/26] refactor(test): make use of test fixtures in Payment Order (cherry picked from commit 322cdbaccf0b8697000aae4e56efa659a34fa8e5) --- .../payment_order/test_payment_order.py | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py index 0dcb1794b9a..60f288e1f07 100644 --- a/erpnext/accounts/doctype/payment_order/test_payment_order.py +++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py @@ -4,9 +4,13 @@ import unittest import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import getdate -from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account +from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import ( + create_bank_account, + create_gl_account, +) from erpnext.accounts.doctype.payment_entry.payment_entry import ( get_payment_entry, make_payment_order, @@ -14,28 +18,32 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import ( from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice -class TestPaymentOrder(unittest.TestCase): +class TestPaymentOrder(FrappeTestCase): def setUp(self): - create_bank_account() + # generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error + uniq_identifier = frappe.generate_hash(length=10) + self.gl_account = create_gl_account("_Test Bank " + uniq_identifier) + self.bank_account = create_bank_account( + gl_account=self.gl_account, bank_account_name="Checking Account " + uniq_identifier + ) def tearDown(self): - for bt in frappe.get_all("Payment Order"): - doc = frappe.get_doc("Payment Order", bt.name) - doc.cancel() - doc.delete() + frappe.db.rollback() def test_payment_order_creation_against_payment_entry(self): purchase_invoice = make_purchase_invoice() payment_entry = get_payment_entry( - "Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC" + "Purchase Invoice", purchase_invoice.name, bank_account=self.gl_account ) payment_entry.reference_no = "_Test_Payment_Order" payment_entry.reference_date = getdate() - payment_entry.party_bank_account = "Checking Account - Citi Bank" + payment_entry.party_bank_account = self.bank_account payment_entry.insert() payment_entry.submit() - doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry") + doc = create_payment_order_against_payment_entry( + payment_entry, "Payment Entry", self.bank_account + ) reference_doc = doc.get("references")[0] self.assertEqual(reference_doc.reference_name, payment_entry.name) self.assertEqual(reference_doc.reference_doctype, "Payment Entry") @@ -43,13 +51,13 @@ class TestPaymentOrder(unittest.TestCase): self.assertEqual(reference_doc.amount, 250) -def create_payment_order_against_payment_entry(ref_doc, order_type): +def create_payment_order_against_payment_entry(ref_doc, order_type, bank_account): payment_order = frappe.get_doc( dict( doctype="Payment Order", company="_Test Company", payment_order_type=order_type, - company_bank_account="Checking Account - Citi Bank", + company_bank_account=bank_account, ) ) doc = make_payment_order(ref_doc.name, payment_order) From a6067c623957123753dd64b5a1b6581280587a99 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 12:45:43 +0530 Subject: [PATCH 26/26] fix: show warehouse title field in sales docs (backport #39746) (#39754) fix: show warehouse title field in sales docs (cherry picked from commit ee14faaa39bcb0fb8d813aee7e00eedc8d3a38c1) Co-authored-by: s-aga-r --- erpnext/controllers/queries.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 06ea8336bd6..303548cca42 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -679,17 +679,24 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters): conditions, bin_conditions = [], [] filter_dict = get_doctype_wise_filters(filters) - query = """select `tabWarehouse`.name, + warehouse_field = "name" + meta = frappe.get_meta("Warehouse") + if meta.get("show_title_field_in_link") and meta.get("title_field"): + searchfield = meta.get("title_field") + warehouse_field = meta.get("title_field") + + query = """select `tabWarehouse`.`{warehouse_field}`, CONCAT_WS(' : ', 'Actual Qty', ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty from `tabWarehouse` left join `tabBin` on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions} where `tabWarehouse`.`{key}` like {txt} {fcond} {mcond} - order by ifnull(`tabBin`.actual_qty, 0) desc + order by ifnull(`tabBin`.actual_qty, 0) desc, `tabWarehouse`.`{warehouse_field}` asc limit {page_len} offset {start} """.format( + warehouse_field=warehouse_field, bin_conditions=get_filters_cond( doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True ),