From 2104d903aa65a5a09b5ecfbb6b937fa181fc33af Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:05:49 +0530 Subject: [PATCH 01/42] fix: correcting balance sheet calculation for zero liabilities and equity (#41497) * fix: correcting balance sheet calculation for zero liabilities and equity * fix: correcting balance sheet calculation for zero liabilities and equity (cherry picked from commit 3c3313594d94cf1fbd76d0ec6f2ce5d5bfc2603c) # Conflicts: # erpnext/accounts/report/balance_sheet/balance_sheet.py --- .../report/balance_sheet/balance_sheet.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 2106451bd1a..eaa49631167 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -109,7 +109,7 @@ def get_provisional_profit_loss( ): provisional_profit_loss = {} total_row = {} - if asset and (liability or equity): + if asset: total = total_row_total = 0 currency = currency or frappe.get_cached_value("Company", company, "default_currency") total_row = { @@ -122,6 +122,7 @@ def get_provisional_profit_loss( for period in period_list: key = period if consolidated else period.key +<<<<<<< HEAD effective_liability = 0.0 if liability: effective_liability += flt(liability[-2].get(key)) @@ -130,6 +131,22 @@ def get_provisional_profit_loss( provisional_profit_loss[key] = flt(asset[-2].get(key)) - effective_liability total_row[key] = effective_liability + provisional_profit_loss[key] +======= + total_assets = flt(asset[0].get(key)) + + if liability or equity: + effective_liability = 0.0 + if liability: + effective_liability += flt(liability[0].get(key)) + if equity: + effective_liability += flt(equity[0].get(key)) + + provisional_profit_loss[key] = total_assets - effective_liability + else: + provisional_profit_loss[key] = total_assets + + total_row[key] = provisional_profit_loss[key] +>>>>>>> 3c3313594d (fix: correcting balance sheet calculation for zero liabilities and equity (#41497)) if provisional_profit_loss[key]: has_value = True From a3444a07b7f597f57b89ed2119be2a9da5273e2a Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:26:14 +0530 Subject: [PATCH 02/42] fix: completed DC will not appear in a delivery trip (#41655) * fix: completed DC will not appear in a delivery trip * fix: completed DC will not appear in a delivery trip (cherry picked from commit 4f0214d00e6aab808597c7ce766b1de9ff8b2ec7) # Conflicts: # erpnext/stock/doctype/delivery_note/delivery_note.js --- erpnext/stock/doctype/delivery_note/delivery_note.js | 4 ++++ erpnext/stock/doctype/delivery_trip/delivery_trip.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 23d0adc5708..d564386016c 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -205,7 +205,11 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( ); } +<<<<<<< HEAD if (doc.docstatus == 1) { +======= + if (doc.docstatus == 1 && doc.status != "Completed" && frappe.model.can_create("Delivery Trip")) { +>>>>>>> 4f0214d00e (fix: completed DC will not appear in a delivery trip (#41655)) this.frm.add_custom_button( __("Delivery Trip"), function () { diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js index 4f8649c0bfa..2445d1f66ea 100755 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js @@ -58,10 +58,12 @@ frappe.ui.form.on("Delivery Trip", { date_field: "posting_date", setters: { company: frm.doc.company, + customer: null, }, get_query_filters: { docstatus: 1, company: frm.doc.company, + status: ["Not In", ["Completed", "Cancelled"]], }, }); }, From 0bab609a6fd502740bb3e7f9f8dfa5bc50ea9e6a Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:09:31 +0530 Subject: [PATCH 03/42] fix: completed DC will not appear in a delivery trip --- erpnext/stock/doctype/delivery_note/delivery_note.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index d564386016c..22e8869bd6d 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -205,11 +205,7 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( ); } -<<<<<<< HEAD - if (doc.docstatus == 1) { -======= if (doc.docstatus == 1 && doc.status != "Completed" && frappe.model.can_create("Delivery Trip")) { ->>>>>>> 4f0214d00e (fix: completed DC will not appear in a delivery trip (#41655)) this.frm.add_custom_button( __("Delivery Trip"), function () { From d48a2c9f8e3c7bd47e253cb2c88ffc2c75290ed8 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:14:59 +0530 Subject: [PATCH 04/42] fix: correcting balance sheet calculation for zero liabilities and equity --- .../accounts/report/balance_sheet/balance_sheet.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index eaa49631167..aa78f4b6204 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -122,16 +122,6 @@ def get_provisional_profit_loss( for period in period_list: key = period if consolidated else period.key -<<<<<<< HEAD - effective_liability = 0.0 - if liability: - effective_liability += flt(liability[-2].get(key)) - if equity: - effective_liability += flt(equity[-2].get(key)) - - provisional_profit_loss[key] = flt(asset[-2].get(key)) - effective_liability - total_row[key] = effective_liability + provisional_profit_loss[key] -======= total_assets = flt(asset[0].get(key)) if liability or equity: @@ -146,7 +136,6 @@ def get_provisional_profit_loss( provisional_profit_loss[key] = total_assets total_row[key] = provisional_profit_loss[key] ->>>>>>> 3c3313594d (fix: correcting balance sheet calculation for zero liabilities and equity (#41497)) if provisional_profit_loss[key]: has_value = True From a7b6530fde4ad7ae298f685d9917d9a701fcfd72 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:42:37 +0530 Subject: [PATCH 05/42] fix: manual pick allow to pick more than available stock (backport #42155) (#42159) fix: manual pick allow to pick more than available stock (#42155) (cherry picked from commit 938dd4b2aa4c3d85c9c29692331a9df54fed0cbc) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/pick_list/pick_list.py | 59 ++++++++++++++++++- .../stock/doctype/pick_list/test_pick_list.py | 42 +++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 6dd53f290c9..066dba95b1e 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -6,7 +6,7 @@ from collections import OrderedDict, defaultdict from itertools import groupby import frappe -from frappe import _ +from frappe import _, bold from frappe.model.document import Document from frappe.model.mapper import map_child_doc from frappe.query_builder import Case @@ -73,6 +73,9 @@ class PickList(Document): def validate(self): self.validate_for_qty() + if self.pick_manually and self.get("locations"): + self.validate_stock_qty() + self.check_serial_no_status() def before_save(self): self.update_status() @@ -82,6 +85,60 @@ class PickList(Document): if self.get("locations"): self.validate_sales_order_percentage() + def validate_stock_qty(self): + from erpnext.stock.doctype.batch.batch import get_batch_qty + + for row in self.get("locations"): + if row.batch_no and not row.qty: + batch_qty = get_batch_qty(row.batch_no, row.warehouse, row.item_code) + + if row.qty > batch_qty: + frappe.throw( + _( + "At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} for the batch {4} in the warehouse {5}." + ).format(row.idx, row.item_code, batch_qty, row.batch_no, bold(row.warehouse)), + title=_("Insufficient Stock"), + ) + + continue + + bin_qty = frappe.db.get_value( + "Bin", + {"item_code": row.item_code, "warehouse": row.warehouse}, + "actual_qty", + ) + + if row.qty > bin_qty: + frappe.throw( + _( + "At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} in the warehouse {4}." + ).format(row.idx, row.qty, bold(row.item_code), bin_qty, bold(row.warehouse)), + title=_("Insufficient Stock"), + ) + + def check_serial_no_status(self): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + for row in self.get("locations"): + if not row.serial_no: + continue + + picked_serial_nos = get_serial_nos(row.serial_no) + validated_serial_nos = frappe.get_all( + "Serial No", + filters={"name": ("in", picked_serial_nos), "warehouse": row.warehouse}, + pluck="name", + ) + + incorrect_serial_nos = set(picked_serial_nos) - set(validated_serial_nos) + if incorrect_serial_nos: + frappe.throw( + _("The Serial No at Row #{0}: {1} is not available in warehouse {2}.").format( + row.idx, ", ".join(incorrect_serial_nos), row.warehouse + ), + title=_("Incorrect Warehouse"), + ) + def validate_sales_order_percentage(self): # set percentage picked in SO for location in self.get("locations"): diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 116a0bd833d..3341b1fc6de 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -1132,3 +1132,45 @@ class TestPickList(FrappeTestCase): pl.save() self.assertEqual(pl.locations[0].qty, 80.0) + + def test_validate_picked_qty_with_manual_option(self): + warehouse = "_Test Warehouse - _TC" + non_serialized_item = make_item( + "Test Non Serialized Pick List Item For Manual Option", properties={"is_stock_item": 1} + ).name + + serialized_item = make_item( + "Test Serialized Pick List Item For Manual Option", + properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SN-HSNMSPLI-.####"}, + ).name + + batched_item = make_item( + "Test Batched Pick List Item For Manual Option", + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "batch_number_series": "SN-HBNMSPLI-.####", + "create_new_batch": 1, + }, + ).name + + make_stock_entry(item=non_serialized_item, to_warehouse=warehouse, qty=10, basic_rate=100) + make_stock_entry(item=serialized_item, to_warehouse=warehouse, qty=10, basic_rate=100) + make_stock_entry(item=batched_item, to_warehouse=warehouse, qty=10, basic_rate=100) + + so = make_sales_order( + item_code=non_serialized_item, qty=10, rate=100, do_not_save=True, warehouse=warehouse + ) + so.append("items", {"item_code": serialized_item, "qty": 10, "rate": 100, "warehouse": warehouse}) + so.append("items", {"item_code": batched_item, "qty": 10, "rate": 100, "warehouse": warehouse}) + so.set_missing_values() + so.save() + so.submit() + + pl = create_pick_list(so.name) + pl.pick_manually = 1 + + for row in pl.locations: + row.qty = row.qty + 10 + + self.assertRaises(frappe.ValidationError, pl.save) From 4f623c3b6671658c4a64870549b4e8568db35c5c Mon Sep 17 00:00:00 2001 From: Markus Lobedann Date: Wed, 3 Jul 2024 12:21:12 +0200 Subject: [PATCH 06/42] refactor: remove obsolete function call (#42162) (cherry picked from commit 45124328160862e4f2c1dd7523c474bb35db302d) --- .../doctype/import_supplier_invoice/import_supplier_invoice.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js index 7aa8012f0b6..80dd246eb8a 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -36,7 +36,6 @@ frappe.ui.form.on("Import Supplier Invoice", { toggle_read_only_fields: function (frm) { if (["File Import Completed", "Processing File Data"].includes(frm.doc.status)) { cur_frm.set_read_only(); - cur_frm.refresh_fields(); frm.set_df_property("import_invoices", "hidden", 1); } else { frm.set_df_property("import_invoices", "hidden", 0); From 5317418a53815f421296d3861a7d10aa25d31136 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:12:00 +0530 Subject: [PATCH 07/42] fix: path of automatically updates the status of asset maintenance log (cherry picked from commit 909aa8f359c77cb92c3607cb46fea6a5292730dc) --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index a080af9a827..028acc9cb11 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -442,7 +442,7 @@ scheduler_events = { "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email", "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily", "erpnext.accounts.utils.run_ledger_health_checks", - "erpnext.assets.doctype.asset.asset_maintenance_log.update_asset_maintenance_log_status", + "erpnext.assets.doctype.asset_maintenance_log.asset_maintenance_log.update_asset_maintenance_log_status", ], "weekly": [ "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly", From 93528631c3652edecfee3e712d94c4ace9d4b26a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 3 Jul 2024 15:58:19 +0530 Subject: [PATCH 08/42] fix: multiple free items on same Item Group (cherry picked from commit c4ae0d283fe24ef0bba45266df8c2f4d1cdf22ba) --- erpnext/public/js/controllers/transaction.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 950914953aa..dfb0ded8139 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1750,12 +1750,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe apply_product_discount(args) { const items = this.frm.doc.items.filter(d => (d.is_free_item)) || []; - const exist_items = items.map(row => (row.item_code, row.pricing_rules)); + const exist_items = items.map(row => { return {item_code: row.item_code, pricing_rules: row.pricing_rules};}); args.free_item_data.forEach(pr_row => { let row_to_modify = {}; - if (!items || !in_list(exist_items, (pr_row.item_code, pr_row.pricing_rules))) { + // If there are no free items, or if the current free item doesn't exist in the table, add it + if (!items || !exist_items.filter(e_row => { + return e_row.item_code == pr_row.item_code && e_row.pricing_rules == pr_row.pricing_rules; + }).length) { row_to_modify = frappe.model.add_child(this.frm.doc, this.frm.doc.doctype + ' Item', 'items'); From 4ecb02cb41783056c6365735180bd9edca367a32 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 2 Jul 2024 15:44:54 +0530 Subject: [PATCH 09/42] refactor: validation to prevent recursion with mixed conditions (cherry picked from commit 406dfd528f2cabc60ece0c917fb02092abed50cc) --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 5 +++++ .../doctype/promotional_scheme/promotional_scheme.py | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 420fd3bee9d..de29ec0f004 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -139,6 +139,7 @@ class PricingRule(Document): self.validate_price_list_with_currency() self.validate_dates() self.validate_condition() + self.validate_mixed_with_recursion() if not self.margin_type: self.margin_rate_or_amount = 0.0 @@ -308,6 +309,10 @@ class PricingRule(Document): ): frappe.throw(_("Invalid condition expression")) + def validate_mixed_with_recursion(self): + if self.mixed_conditions and self.is_recursive: + frappe.throw(_("Recursive Discounts with Mixed condition is not supported by the system")) + # -------------------------------------------------------------------------------- diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index ed4e3d2a8f3..6767594b10f 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -146,6 +146,7 @@ class PromotionalScheme(Document): self.validate_applicable_for() self.validate_pricing_rules() + self.validate_mixed_with_recursion() def validate_applicable_for(self): if self.applicable_for: @@ -177,6 +178,7 @@ class PromotionalScheme(Document): frappe.delete_doc("Pricing Rule", docname.name) def on_update(self): + self.validate() pricing_rules = ( frappe.get_all( "Pricing Rule", @@ -188,6 +190,15 @@ class PromotionalScheme(Document): ) self.update_pricing_rules(pricing_rules) + def validate_mixed_with_recursion(self): + if self.mixed_conditions: + if self.product_discount_slabs: + for slab in self.product_discount_slabs: + if slab.is_recursive: + frappe.throw( + _("Recursive Discounts with Mixed condition is not supported by the system") + ) + def update_pricing_rules(self, pricing_rules): rules = {} count = 0 From cfda5f6d0b1b6f9757b6010c98c675c768e0cbb5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 3 Jul 2024 17:05:13 +0530 Subject: [PATCH 10/42] fix: use standard method to get `_doc_before_save` (cherry picked from commit 9d7be293ae9d7d2a7cf9f81fa1aadcc071bc29e6) --- .../accounts/doctype/promotional_scheme/promotional_scheme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index 6767594b10f..71fe156dd7a 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -164,7 +164,7 @@ class PromotionalScheme(Document): docnames = [] # If user has changed applicable for - if self._doc_before_save.applicable_for == self.applicable_for: + if self.get_doc_before_save() and self.get_doc_before_save().applicable_for == self.applicable_for: return docnames = frappe.get_all("Pricing Rule", filters={"promotional_scheme": self.name}) From 0774607f522b769637aaaefedcc612016f02096a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 3 Jul 2024 17:11:32 +0530 Subject: [PATCH 11/42] test: validation on mixed condition with recursion (cherry picked from commit 9bd4e7b7094830f56fdb6fd492d1a0873bf5b977) --- .../test_promotional_scheme.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py index 9b40c98829c..74ba6cf923c 100644 --- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py @@ -129,6 +129,25 @@ class TestPromotionalScheme(unittest.TestCase): [pr.min_qty, pr.free_item, pr.free_qty, pr.recurse_for], [12, "_Test Item 2", 1, 12] ) + def test_validation_on_recurse_with_mixed_condition(self): + ps = make_promotional_scheme() + ps.set("price_discount_slabs", []) + ps.set( + "product_discount_slabs", + [ + { + "rule_description": "12+1", + "min_qty": 12, + "free_item": "_Test Item 2", + "free_qty": 1, + "is_recursive": 1, + "recurse_for": 12, + } + ], + ) + ps.mixed_conditions = True + self.assertRaises(frappe.ValidationError, ps.save) + def make_promotional_scheme(**args): args = frappe._dict(args) From 12bec3be9de575693b068f78661613dc137fe923 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 3 Jul 2024 17:54:41 +0530 Subject: [PATCH 12/42] test: validation on mixed condition and recursion on pricing rule (cherry picked from commit eb4af58bf0371437ba7bca36f345488c6c03331b) --- .../doctype/pricing_rule/test_pricing_rule.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 72961a6b6ec..87f6fa76bc8 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -1299,6 +1299,18 @@ class TestPricingRule(unittest.TestCase): item_group_rule.delete() item_code_rule.delete() + def test_validation_on_mixed_condition_with_recursion(self): + pricing_rule = make_pricing_rule( + discount_percentage=10, + selling=1, + priority=2, + min_qty=4, + title="_Test Pricing Rule with Min Qty - 2", + ) + pricing_rule.mixed_conditions = True + pricing_rule.is_recursive = True + self.assertRaises(frappe.ValidationError, pricing_rule.save) + test_dependencies = ["Campaign"] From a967d5984434029ed47fd1807a23cf82592c43e0 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:49:40 +0530 Subject: [PATCH 13/42] fix: group by in item-wise purchase register (cherry picked from commit 3fab00135b1391c5f505fee599dac7234b0e1992) --- .../item_wise_purchase_register/item_wise_purchase_register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 1cd9b872a9b..8bc05273092 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -309,7 +309,7 @@ def apply_conditions(query, pi, pii, filters): query = query.orderby(pi.posting_date, order=Order.desc) query = query.orderby(pii.item_group, order=Order.desc) else: - query = apply_group_by_conditions(filters, "Purchase Invoice") + query = apply_group_by_conditions(query, pi, pii, filters) return query From 5ac3b34a6fa85da41ec168037f520a503056df66 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 4 Jul 2024 15:01:35 +0530 Subject: [PATCH 14/42] fix: empty item-wise sales/purchase register reports on initial load (cherry picked from commit ee862126e4ba2a38a6eaa4eba8267360f9b04a04) --- .../item_wise_purchase_register/item_wise_purchase_register.js | 2 +- .../report/item_wise_sales_register/item_wise_sales_register.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js index e05591e49df..836ec7b456c 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js @@ -52,7 +52,7 @@ frappe.query_reports["Item-wise Purchase Register"] = { label: __("Group By"), fieldname: "group_by", fieldtype: "Select", - options: ["Supplier", "Item Group", "Item", "Invoice"], + options: ["", "Supplier", "Item Group", "Item", "Invoice"], }, ], formatter: function (value, row, column, data, default_formatter) { diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js index 16a97733393..6fb015f7b74 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js @@ -70,7 +70,7 @@ frappe.query_reports["Item-wise Sales Register"] = { label: __("Group By"), fieldname: "group_by", fieldtype: "Select", - options: ["Customer Group", "Customer", "Item Group", "Item", "Territory", "Invoice"], + options: ["", "Customer Group", "Customer", "Item Group", "Item", "Territory", "Invoice"], }, ], formatter: function (value, row, column, data, default_formatter) { From ca57fd42551dd3a4c6e8a2ffcd58ed75d89c532c Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 2 Jul 2024 17:38:40 +0530 Subject: [PATCH 15/42] fix: Multiple fixes for General Ledger Report (cherry picked from commit 97f02015c24bcdfc517a74880bb54e1cc3c88388) --- .../report/general_ledger/general_ledger.py | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index e884deb2a17..4b20746286a 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -2,10 +2,12 @@ # License: GNU General Public License v3. See license.txt +import copy from collections import OrderedDict import frappe from frappe import _, _dict +from frappe.query_builder import Criterion from frappe.utils import cstr, getdate from erpnext import get_company_currency, get_default_company @@ -17,9 +19,6 @@ from erpnext.accounts.report.financial_statements import get_cost_centers_with_c from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency from erpnext.accounts.utils import get_account_currency -# to cache translations -TRANSLATIONS = frappe._dict() - def execute(filters=None): if not filters: @@ -44,19 +43,11 @@ def execute(filters=None): columns = get_columns(filters) - update_translations() - res = get_result(filters, account_details) return columns, res -def update_translations(): - TRANSLATIONS.update( - dict(OPENING=_("Opening"), TOTAL=_("Total"), CLOSING_TOTAL=_("Closing (Opening + Total)")) - ) - - def validate_filters(filters, account_details): if not filters.get("company"): frappe.throw(_("{0} is mandatory").format(_("Company"))) @@ -319,26 +310,31 @@ def get_accounts_with_children(accounts): if not isinstance(accounts, list): accounts = [d.strip() for d in accounts.strip().split(",") if d] - all_accounts = [] - for d in accounts: - account = frappe.get_cached_doc("Account", d) - if account: - children = frappe.get_all( - "Account", filters={"lft": [">=", account.lft], "rgt": ["<=", account.rgt]} - ) - all_accounts += [c.name for c in children] - else: - frappe.throw(_("Account: {0} does not exist").format(d)) + if not accounts: + return - return list(set(all_accounts)) if all_accounts else None + doctype = frappe.qb.DocType("Account") + accounts_data = ( + frappe.qb.from_(doctype) + .select(doctype.lft, doctype.rgt) + .where(doctype.name.isin(accounts)) + .run(as_dict=True) + ) + + conditions = [] + for account in accounts_data: + conditions.append((doctype.lft >= account.lft) & (doctype.rgt <= account.rgt)) + + return frappe.qb.from_(doctype).select(doctype.name).where(Criterion.any(conditions)).run(pluck=True) def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries): data = [] + totals_dict = get_totals_dict() - gle_map = initialize_gle_map(gl_entries, filters) + gle_map = initialize_gle_map(gl_entries, filters, totals_dict) - totals, entries = get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map) + totals, entries = get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, totals_dict) # Opening for filtered account data.append(totals.opening) @@ -387,9 +383,9 @@ def get_totals_dict(): ) return _dict( - opening=_get_debit_credit_dict(TRANSLATIONS.OPENING), - total=_get_debit_credit_dict(TRANSLATIONS.TOTAL), - closing=_get_debit_credit_dict(TRANSLATIONS.CLOSING_TOTAL), + opening=_get_debit_credit_dict(_("Opening")), + total=_get_debit_credit_dict(_("Total")), + closing=_get_debit_credit_dict(_("Closing (Opening + Total)")), ) @@ -402,17 +398,16 @@ def group_by_field(group_by): return "voucher_no" -def initialize_gle_map(gl_entries, filters): +def initialize_gle_map(gl_entries, filters, totals_dict): gle_map = OrderedDict() group_by = group_by_field(filters.get("group_by")) for gle in gl_entries: - gle_map.setdefault(gle.get(group_by), _dict(totals=get_totals_dict(), entries=[])) + gle_map.setdefault(gle.get(group_by), _dict(totals=copy.deepcopy(totals_dict), entries=[])) return gle_map -def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): - totals = get_totals_dict() +def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, totals): entries = [] consolidated_gle = OrderedDict() group_by = group_by_field(filters.get("group_by")) From ec881ace76bd6c8b63dc6793cff29bde60b74795 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 11:02:02 +0530 Subject: [PATCH 16/42] fix: provision to enable do not use batch-wise valuation (backport #42186) (#42198) * fix: provision to enable do not use batch-wise valuation (#42186) fix: provision to enable do not use batchwise valuation (cherry picked from commit f06ba0cc36cc30744a9b1acefa72e967ca7628e4) # Conflicts: # erpnext/stock/doctype/stock_settings/stock_settings.json * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- erpnext/patches.txt | 1 + .../v15_0/do_not_use_batchwise_valuation.py | 15 +++++ erpnext/stock/doctype/batch/batch.py | 4 ++ .../delivery_note/test_delivery_note.py | 2 +- .../stock/doctype/pick_list/test_pick_list.py | 2 +- .../purchase_receipt/test_purchase_receipt.py | 59 +++++++++++++++++++ .../serial_and_batch_bundle.py | 10 ++++ .../test_serial_and_batch_bundle.py | 2 +- .../stock_settings/stock_settings.json | 11 +++- .../doctype/stock_settings/stock_settings.py | 17 ++++++ erpnext/stock/stock_ledger.py | 37 +++++++++++- erpnext/stock/utils.py | 11 +++- 12 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 erpnext/patches/v15_0/do_not_use_batchwise_valuation.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 66e98dde5b4..b661e6f99a4 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -369,3 +369,4 @@ erpnext.patches.v15_0.rename_number_of_depreciations_booked_to_opening_booked_de erpnext.patches.v15_0.update_warehouse_field_in_asset_repair_consumed_item_doctype erpnext.patches.v15_0.update_asset_repair_field_in_stock_entry erpnext.patches.v15_0.update_total_number_of_booked_depreciations +erpnext.patches.v15_0.do_not_use_batchwise_valuation diff --git a/erpnext/patches/v15_0/do_not_use_batchwise_valuation.py b/erpnext/patches/v15_0/do_not_use_batchwise_valuation.py new file mode 100644 index 00000000000..9e95a34acc7 --- /dev/null +++ b/erpnext/patches/v15_0/do_not_use_batchwise_valuation.py @@ -0,0 +1,15 @@ +import frappe + + +def execute(): + valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method") + if valuation_method in ["FIFO", "LIFO"]: + return + + if frappe.get_all("Batch", filters={"use_batchwise_valuation": 1}, limit=1): + return + + if frappe.get_all("Item", filters={"has_batch_no": 1, "valuation_method": "FIFO"}, limit=1): + return + + frappe.db.set_single_value("Stock Settings", "do_not_use_batchwise_valuation", 1) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index e490badfc40..c539c315747 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -158,6 +158,10 @@ class Batch(Document): def set_batchwise_valuation(self): if self.is_new(): + if frappe.db.get_single_value("Stock Settings", "do_not_use_batchwise_valuation"): + self.use_batchwise_valuation = 0 + return + self.use_batchwise_valuation = 1 def before_save(self): diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index f24c912e362..5f44548a873 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1654,7 +1654,7 @@ class TestDeliveryNote(FrappeTestCase): { "is_stock_item": 1, "has_batch_no": 1, - "batch_no_series": "BATCH-TESTSERIAL-.#####", + "batch_number_series": "BATCH-TESTSERIAL-.#####", "create_new_batch": 1, }, ) diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 3341b1fc6de..b1c03bf8453 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -648,7 +648,7 @@ class TestPickList(FrappeTestCase): def test_picklist_for_batch_item(self): warehouse = "_Test Warehouse - _TC" item = make_item( - properties={"is_stock_item": 1, "has_batch_no": 1, "batch_no_series": "PICKLT-.######"} + properties={"is_stock_item": 1, "has_batch_no": 1, "batch_number_series": "PICKLT-.######"} ).name # create batch diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 968cb68dac0..459f7f3f841 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3170,6 +3170,65 @@ class TestPurchaseReceipt(FrappeTestCase): lcv.save().submit() return lcv + def test_do_not_use_batchwise_valuation_rate(self): + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + item_code = "Test Item for Do Not Use Batchwise Valuation" + make_item( + item_code, + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TIDNBV-.#####", + "valuation_method": "Moving Average", + }, + ) + + # 1st pr for 100 rate + pr = make_purchase_receipt( + item_code=item_code, + qty=1, + rate=100, + posting_date=add_days(today(), -2), + ) + + make_purchase_receipt( + item_code=item_code, + qty=1, + rate=200, + posting_date=add_days(today(), -1), + ) + + dn = create_delivery_note( + item_code=item_code, + qty=1, + rate=300, + posting_date=today(), + use_serial_batch_fields=1, + batch_no=get_batch_from_bundle(pr.items[0].serial_and_batch_bundle), + ) + dn.reload() + bundle = dn.items[0].serial_and_batch_bundle + + valuation_rate = frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate") + self.assertEqual(valuation_rate, 100) + + doc = frappe.get_doc("Stock Settings") + doc.do_not_use_batchwise_valuation = 1 + doc.flags.ignore_validate = True + doc.save() + + pr.repost_future_sle_and_gle(force=True) + + valuation_rate = frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate") + self.assertEqual(valuation_rate, 150) + + doc = frappe.get_doc("Stock Settings") + doc.do_not_use_batchwise_valuation = 0 + doc.flags.ignore_validate = True + doc.save() + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 63e1e5084d6..b8b17cb4c81 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -228,6 +228,16 @@ class SerialandBatchBundle(Document): def get_serial_nos(self): return [d.serial_no for d in self.entries if d.serial_no] + def update_valuation_rate(self, valuation_rate=None, save=False): + for row in self.entries: + row.incoming_rate = valuation_rate + row.stock_value_difference = flt(row.qty) * flt(valuation_rate) + + if save: + row.db_set( + {"incoming_rate": row.incoming_rate, "stock_value_difference": row.stock_value_difference} + ) + def set_incoming_rate_for_outward_transaction(self, row=None, save=False, allow_negative_stock=False): sle = self.get_sle_for_outward_transaction() diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index 2913af4a724..32f3b4fb85b 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -653,7 +653,7 @@ class TestSerialandBatchBundle(FrappeTestCase): "is_stock_item": 1, "has_batch_no": 1, "create_new_batch": 1, - "batch_no_series": "PSNBI-TSNVL-.#####", + "batch_number_series": "PSNBI-TSNVL-.#####", "has_serial_no": 1, "serial_no_series": "SN-PSNBI-TSNVL-.#####", }, diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index f03cc49ef73..5d9ddaccedf 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -44,6 +44,7 @@ "auto_reserve_serial_and_batch", "serial_and_batch_item_settings_tab", "section_break_7", + "do_not_use_batchwise_valuation", "auto_create_serial_and_batch_bundle_for_outward", "pick_serial_and_batch_based_on", "column_break_mhzc", @@ -437,6 +438,14 @@ "fieldname": "do_not_update_serial_batch_on_creation_of_auto_bundle", "fieldtype": "Check", "label": "Do Not Update Serial / Batch on Creation of Auto Bundle" + }, + { + "default": "0", + "depends_on": "eval:doc.valuation_method === \"Moving Average\"", + "description": "If enabled, the system will use the moving average valuation method to calculate the valuation rate for the batched items and will not consider the individual batch-wise incoming rate.", + "fieldname": "do_not_use_batchwise_valuation", + "fieldtype": "Check", + "label": "Do Not Use Batch-wise Valuation" } ], "icon": "icon-cog", @@ -444,7 +453,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-02-25 16:32:01.084453", + "modified": "2024-07-04 12:45:09.811280", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index 24e8dccd17c..e786b1f67c6 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -40,6 +40,7 @@ class StockSettings(Document): default_warehouse: DF.Link | None disable_serial_no_and_batch_selector: DF.Check do_not_update_serial_batch_on_creation_of_auto_bundle: DF.Check + do_not_use_batchwise_valuation: DF.Check enable_stock_reservation: DF.Check item_group: DF.Link | None item_naming_by: DF.Literal["Item Code", "Naming Series"] @@ -98,6 +99,22 @@ class StockSettings(Document): self.validate_stock_reservation() self.change_precision_for_for_sales() self.change_precision_for_purchase() + self.validate_use_batch_wise_valuation() + + def validate_use_batch_wise_valuation(self): + if not self.do_not_use_batchwise_valuation: + return + + if self.valuation_method == "FIFO": + frappe.throw(_("Cannot disable batch wise valuation for FIFO valuation method.")) + + if frappe.get_all( + "Item", filters={"valuation_method": "FIFO", "is_stock_item": 1, "has_batch_no": 1}, limit=1 + ): + frappe.throw(_("Can't disable batch wise valuation for items with FIFO valuation method.")) + + if frappe.get_all("Batch", filters={"use_batchwise_valuation": 1}, limit=1): + frappe.throw(_("Can't disable batch wise valuation for active batches.")) def validate_warehouses(self): warehouse_fields = ["default_warehouse", "sample_retention_warehouse"] diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index d0195843c5f..ff4798515c3 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -530,6 +530,10 @@ class update_entries_after: self.allow_zero_rate = allow_zero_rate self.via_landed_cost_voucher = via_landed_cost_voucher self.item_code = args.get("item_code") + self.use_moving_avg_for_batch = frappe.db.get_single_value( + "Stock Settings", "do_not_use_batchwise_valuation" + ) + self.allow_negative_stock = allow_negative_stock or is_negative_stock_allowed( item_code=self.item_code ) @@ -745,7 +749,7 @@ class update_entries_after: if sle.get(dimension.get("fieldname")): has_dimensions = True - if sle.serial_and_batch_bundle: + if sle.serial_and_batch_bundle and (not self.use_moving_avg_for_batch or sle.has_serial_no): self.calculate_valuation_for_serial_batch_bundle(sle) elif sle.serial_no and not self.args.get("sle_id"): # Only run in reposting @@ -765,7 +769,12 @@ class update_entries_after: # Only run in reposting self.update_batched_values(sle) else: - if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no and not has_dimensions: + if ( + sle.voucher_type == "Stock Reconciliation" + and not sle.batch_no + and not sle.has_batch_no + and not has_dimensions + ): # assert self.wh_data.valuation_rate = sle.valuation_rate self.wh_data.qty_after_transaction = sle.qty_after_transaction @@ -806,6 +815,15 @@ class update_entries_after: sle.doctype = "Stock Ledger Entry" frappe.get_doc(sle).db_update() + if ( + sle.serial_and_batch_bundle + and self.valuation_method == "Moving Average" + and self.use_moving_avg_for_batch + and (sle.batch_no or sle.has_batch_no) + ): + valuation_rate = flt(stock_value_difference) / flt(sle.actual_qty) + self.update_valuation_rate_in_serial_and_batch_bundle(sle, valuation_rate) + if not self.args.get("sle_id") or ( sle.serial_and_batch_bundle and sle.auto_created_serial_and_batch_bundle ): @@ -916,6 +934,21 @@ class update_entries_after: self.wh_data.qty_after_transaction, precision ) + def update_valuation_rate_in_serial_and_batch_bundle(self, sle, valuation_rate): + # Only execute if the item has batch_no and the valuation method is moving average + if not frappe.db.exists("Serial and Batch Bundle", sle.serial_and_batch_bundle): + return + + doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle) + doc.update_valuation_rate(valuation_rate, save=True) + doc.calculate_qty_and_amount(save=True) + + def get_outgoing_rate_for_batched_item(self, sle): + if self.wh_data.qty_after_transaction == 0: + return 0 + + return flt(self.wh_data.stock_value) / flt(self.wh_data.qty_after_transaction) + def validate_negative_stock(self, sle): """ validate negative stock for entries current datetime onwards diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 4c502e106ec..51d2708be24 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -244,6 +244,8 @@ def get_incoming_rate(args, raise_error_if_no_rate=True): "Item", args.get("item_code"), ["has_serial_no", "has_batch_no"], as_dict=1 ) + use_moving_avg_for_batch = frappe.db.get_single_value("Stock Settings", "do_not_use_batchwise_valuation") + if isinstance(args, dict): args = frappe._dict(args) @@ -257,7 +259,12 @@ def get_incoming_rate(args, raise_error_if_no_rate=True): return sn_obj.get_incoming_rate() - elif item_details and item_details.has_batch_no and args.get("serial_and_batch_bundle"): + elif ( + item_details + and item_details.has_batch_no + and args.get("serial_and_batch_bundle") + and not use_moving_avg_for_batch + ): args.actual_qty = args.qty batch_obj = BatchNoValuation( sle=args, @@ -274,7 +281,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True): sn_obj = SerialNoValuation(sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code")) return sn_obj.get_incoming_rate() - elif args.get("batch_no") and not args.get("serial_and_batch_bundle"): + elif args.get("batch_no") and not args.get("serial_and_batch_bundle") and not use_moving_avg_for_batch: args.actual_qty = args.qty args.batch_nos = frappe._dict({args.batch_no: args}) From 2f89461ace6af52e17134cd2cb5ab535e3851985 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:34:39 +0200 Subject: [PATCH 17/42] fix: add missing german translations --- erpnext/translations/de.csv | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 5a53a5ff2bd..3a7911ae49a 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1231,6 +1231,8 @@ Is Default,Ist Standard, Is Existing Asset,Vermögenswert existiert bereits., Is Frozen,Ist gesperrt, Is Group,Ist Gruppe, +Is Group Warehouse,Ist Lagergruppe, +Is Rejected Warehouse,Ist Lager für abgelehnte Ware, Issue,Anfrage, Issue Material,Material ausgeben, Issued,Ausgestellt, @@ -1294,6 +1296,7 @@ Items for Raw Material Request,Artikel für Rohstoffanforderung, Job Card,Jobkarte, Job card {0} created,Jobkarte {0} erstellt, Join,Beitreten, +Joining,Beitritt, Journal Entries {0} are un-linked,Buchungssätze {0} sind nicht verknüpft, Journal Entry,Buchungssatz, Journal Entry {0} does not have account {1} or already matched against other voucher,Buchungssatz {0} gehört nicht zu Konto {1} oder ist bereits mit einem anderen Beleg abgeglichen, @@ -1993,6 +1996,7 @@ Product Search,Produkt Suche, Production,Produktion, Production Item,Produktions-Artikel, Products,Produkte, +Profile,Profil, Profit and Loss,Gewinn und Verlust, Profit for the year,Jahresüberschuss, Program,Programm, @@ -2054,6 +2058,9 @@ Qty To Manufacture,Herzustellende Menge, Qty Total,Gesamtmenge, Qty for {0},Menge für {0}, Qualification,Qualifikation, +Qualification Status,Qualifikationsstatus, +Qualified By,Qualifiziert von, +Qualified on,Qualifiziert am, Quality,Qualität, Quality Action,Qualitätsmaßnahme, Quality Goal.,Qualitätsziel., @@ -3227,6 +3234,7 @@ Warehouse Type,Lagertyp, 'Date' is required,'Datum' ist erforderlich, Budgets,Budgets, Bundle Qty,Bundle Menge, +Company Details,Unternehmensdetails, Company GSTIN,Unternehmen GSTIN, Company field is required,Firmenfeld ist erforderlich, Creating Dimensions...,Dimensionen erstellen ..., @@ -3562,6 +3570,7 @@ Performance,Performance, Period based On,Zeitraum basierend auf, Perpetual inventory required for the company {0} to view this report.,"Permanente Bestandsaufnahme erforderlich, damit das Unternehmen {0} diesen Bericht anzeigen kann.", Phone,Telefon, +Phone Ext.,Telefon Ext., Pick List,Auswahlliste, Plaid authentication error,Plaid-Authentifizierungsfehler, Plaid public token error,Plaid public token error, @@ -4970,7 +4979,7 @@ Number of Depreciations Booked,Anzahl der gebuchten Abschreibungen, Finance Books,Finanzbücher, Straight Line,Gerade Linie, Double Declining Balance,Doppelte degressive, -Manual,Handbuch, +Manual,Manuell, Value After Depreciation,Wert nach Abschreibung, Total Number of Depreciations,Gesamtzahl der Abschreibungen, Frequency of Depreciation (Months),Die Häufigkeit der Abschreibungen (Monate), @@ -5030,6 +5039,7 @@ Maintenance Team Name,Name des Wartungsteams, Maintenance Team Members,Mitglieder des Wartungsteams, Purpose,Zweck, Stock Manager,Lagerleiter, +Stock Movement,Lagerbewegung, Asset Movement Item,Vermögensbewegungsgegenstand, Source Location,Quellspeicherort, From Employee,Von Mitarbeiter, @@ -5126,6 +5136,7 @@ Default Bank Account,Standardbankkonto, Is Transporter,Ist Transporter, Represents Company,Repräsentiert das Unternehmen, Supplier Type,Lieferantentyp, +Allow Purchase,Einkauf zulassen, Allow Purchase Invoice Creation Without Purchase Order,Erstellen von Eingangsrechnung ohne Bestellung zulassen, Allow Purchase Invoice Creation Without Purchase Receipt,Erstellen von Eingangsrechnung ohne Eingangsbeleg zulassen, Warn RFQs,Warnung Ausschreibungen, @@ -5899,6 +5910,7 @@ Occupational Hazards and Environmental Factors,Berufsrisiken und Umweltfaktoren, Other Risk Factors,Andere Risikofaktoren, Patient Details,Patientendetails, Additional information regarding the patient,Zusätzliche Informationen zum Patienten, +Additional Info,Zusätzliche Informationen, HLC-APP-.YYYY.-,HLC-APP-.YYYY.-, Patient Age,Patient Alter, Get Prescribed Clinical Procedures,Holen Sie sich vorgeschriebene klinische Verfahren, @@ -6059,6 +6071,7 @@ Date Of Retirement,Zeitpunkt der Pensionierung, Department and Grade,Abteilung und Klasse, Reports to,Vorgesetzter, Attendance and Leave Details,Anwesenheits- und Urlaubsdetails, +Attendance & Leaves,Anwesenheit & Urlaub, Attendance Device ID (Biometric/RF tag ID),Anwesenheitsgeräte-ID (biometrische / RF-Tag-ID), Applicable Holiday List,Geltende Feiertagsliste, Default Shift,Standardverschiebung, @@ -6519,7 +6532,7 @@ Default Costing Rate,Standardkosten, Default Billing Rate,Standard-Rechnungspreis, Dependent Task,Abhängiger Vorgang, Project Type,Projekttyp, -% Complete Method,% abgeschlossene Methode, +% Complete Method,Fertigstellung bemessen nach, Task Completion,Aufgabenerledigung, Task Progress,Vorgangsentwicklung, % Completed,% abgeschlossen, @@ -6675,6 +6688,7 @@ Restaurant Reservation,Restaurant Reservierung, Waitlisted,Auf der Warteliste, No Show,Nicht angetreten, No of People,Anzahl von Personen, +No of Employees,Anzahl der Mitarbeiter, Reservation Time,Reservierungszeit, Reservation End Time,Reservierungsendzeit, No of Seats,Anzahl der Sitze, @@ -6686,6 +6700,7 @@ Default Company Bank Account,Standard-Bankkonto des Unternehmens, From Lead,Aus Lead, Account Manager,Kundenberater, Accounts Manager,Buchhalter, +Allow Sales,Verkauf zulassen, Allow Sales Invoice Creation Without Sales Order,Ermöglichen Sie die Erstellung von Ausgangsrechnungen ohne Auftrag, Allow Sales Invoice Creation Without Delivery Note,Ermöglichen Sie die Erstellung von Ausgangsrechnungen ohne Lieferschein, Default Price List,Standardpreisliste, @@ -6745,7 +6760,8 @@ Not Delivered,Nicht geliefert, Fully Delivered,Komplett geliefert, Partly Delivered,Teilweise geliefert, Not Applicable,Nicht andwendbar, -% Delivered,% geliefert, +% Delivered,% Geliefert, +% Picked,% Kommissioniert, % of materials delivered against this Sales Order,% der für diesen Auftrag gelieferten Materialien, % of materials billed against this Sales Order,% der Materialien welche zu diesem Auftrag gebucht wurden, Not Billed,Nicht abgerechnet, @@ -6879,6 +6895,7 @@ New Income,Neuer Verdienst, New Expenses,Neue Ausgaben, Annual Income,Jährliches Einkommen, Annual Expenses,Jährliche Kosten, +Annual Revenue,Jährlicher Umsatz, Bank Balance,Kontostand, Bank Credit Balance,Bankguthaben, Receivables,Forderungen, @@ -7352,6 +7369,7 @@ Actual Qty After Transaction,Tatsächliche Anzahl nach Transaktionen, Stock Value Difference,Lagerwert-Differenz, Stock Queue (FIFO),Lagerverfahren (FIFO), Is Cancelled,Ist storniert, +Is Cash or Non Trade Discount,Ist Bar- oder Nicht-Handelsrabatt, Stock Reconciliation,Bestandsabgleich, This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.,"Dieses Werkzeug hilft Ihnen dabei, die Menge und die Bewertung von Bestand im System zu aktualisieren oder zu ändern. Es wird in der Regel verwendet, um die Systemwerte und den aktuellen Bestand Ihrer Lager zu synchronisieren.", Reconciliation JSON,Abgleich JSON (JavaScript Object Notation), @@ -7453,6 +7471,7 @@ Warranty / AMC Status,Status der Garantie / des jährlichen Wartungsvertrags, Resolved By,Entschieden von, Service Address,Serviceadresse, If different than customer address,Falls abweichend von Kundenadresse, +"If yes, then this warehouse will be used to store rejected materials","Falls aktiviert, wird dieses Lager verwendet, um abgelehnte Ware zu lagern", Raised By,Gemeldet durch, From Company,Von Unternehmen, Rename Tool,Werkzeug zum Umbenennen, @@ -8628,6 +8647,7 @@ Print Receipt,Druckeingang, Edit Receipt,Beleg bearbeiten, Focus on search input,Konzentrieren Sie sich auf die Sucheingabe, Focus on Item Group filter,Fokus auf Artikelgruppenfilter, +Footer will display correctly only in PDF,Die Fußzeile wird nur im PDF korrekt angezeigt, Checkout Order / Submit Order / New Order,Kaufabwicklung / Bestellung abschicken / Neue Bestellung, Add Order Discount,Bestellrabatt hinzufügen, Item Code: {0} is not available under warehouse {1}.,Artikelcode: {0} ist unter Lager {1} nicht verfügbar., From 43ad2fed631a689cce9b88369f959f6e7049e8d7 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 25 Jun 2024 13:05:30 +0530 Subject: [PATCH 18/42] fix: custom delimiters (cherry picked from commit 13fb560401a89e64b0ff6400b0477314499bdfa8) # Conflicts: # erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json --- .../bank_statement_import.json | 20 +++++++++++++++++++ .../bank_statement_import.py | 2 ++ 2 files changed, 22 insertions(+) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json index eede3bdc6d1..fac19fd2d28 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json @@ -16,6 +16,8 @@ "html_5", "import_file", "download_template", + "custom_delimiters", + "delimiter_options", "status", "template_options", "import_warnings_section", @@ -198,11 +200,29 @@ "fieldtype": "Code", "label": "Statement Import Log", "options": "JSON" + }, + { + "default": "0", + "fieldname": "custom_delimiters", + "fieldtype": "Check", + "label": "Custom delimiters" + }, + { + "default": ",;\\t|", + "depends_on": "custom_delimiters", + "description": "If your CSV uses a different delimiter, add that character here, ensuring no spaces or additional characters are included.", + "fieldname": "delimiter_options", + "fieldtype": "Data", + "label": "Delimiter options" } ], "hide_toolbar": 1, "links": [], +<<<<<<< HEAD "modified": "2022-09-07 11:11:40.293317", +======= + "modified": "2024-06-25 15:34:35.214946", +>>>>>>> 13fb560401 (fix: custom delimiters) "modified_by": "Administrator", "module": "Accounts", "name": "Bank Statement Import", diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index 0d19e212cba..9b0989f5b23 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -31,6 +31,8 @@ class BankStatementImport(DataImport): bank: DF.Link | None bank_account: DF.Link company: DF.Link + custom_delimiters: DF.Check + delimiter_options: DF.Data | None google_sheets_url: DF.Data | None import_file: DF.Attach | None import_type: DF.Literal["", "Insert New Records", "Update Existing Records"] From 2f0b97d91b6027126978642090f0e30686181ee6 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 25 Jun 2024 14:27:04 +0530 Subject: [PATCH 19/42] fix: remove deprecated field "statement_import_log" (cherry picked from commit b20240942831d1118d00ca8762ebd4e0ff18f684) # Conflicts: # erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json --- .../bank_statement_import/bank_statement_import.json | 11 ++++------- .../bank_statement_import/bank_statement_import.py | 1 - 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json index fac19fd2d28..4d9721f771d 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json @@ -26,7 +26,6 @@ "section_import_preview", "import_preview", "import_log_section", - "statement_import_log", "show_failed_logs", "import_log_preview", "reference_doctype", @@ -195,12 +194,6 @@ "fieldname": "column_break_4", "fieldtype": "Column Break" }, - { - "fieldname": "statement_import_log", - "fieldtype": "Code", - "label": "Statement Import Log", - "options": "JSON" - }, { "default": "0", "fieldname": "custom_delimiters", @@ -218,11 +211,15 @@ ], "hide_toolbar": 1, "links": [], +<<<<<<< HEAD <<<<<<< HEAD "modified": "2022-09-07 11:11:40.293317", ======= "modified": "2024-06-25 15:34:35.214946", >>>>>>> 13fb560401 (fix: custom delimiters) +======= + "modified": "2024-06-25 16:33:50.675848", +>>>>>>> b202409428 (fix: remove deprecated field "statement_import_log") "modified_by": "Administrator", "module": "Accounts", "name": "Bank Statement Import", diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index 9b0989f5b23..0311f8b1bcb 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -39,7 +39,6 @@ class BankStatementImport(DataImport): mute_emails: DF.Check reference_doctype: DF.Link show_failed_logs: DF.Check - statement_import_log: DF.Code | None status: DF.Literal["Pending", "Success", "Partial Success", "Error"] submit_after_import: DF.Check template_options: DF.Code | None From 71311ffd624560994f7f558c3af73a66d4db38c6 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 25 Jun 2024 14:27:28 +0530 Subject: [PATCH 20/42] fix: import status (cherry picked from commit 07f68884e0c09fdcf769dc7f1d0cc1a03921ef01) --- .../bank_statement_import.js | 102 ++++++++++-------- .../bank_statement_import.py | 28 +++++ 2 files changed, 85 insertions(+), 45 deletions(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index 3dfbf701060..29cf5189d82 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -120,52 +120,64 @@ frappe.ui.form.on("Bank Statement Import", { }, show_import_status(frm) { - let import_log = JSON.parse(frm.doc.statement_import_log || "[]"); - let successful_records = import_log.filter((log) => log.success); - let failed_records = import_log.filter((log) => !log.success); - if (successful_records.length === 0) return; + frappe.call({ + method: "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.get_import_status", + args: { + docname: frm.doc.name, + }, + callback: function (r) { + let successful_records = cint(r.message.success); + let failed_records = cint(r.message.failed); + let total_records = cint(r.message.total_records); - let message; - if (failed_records.length === 0) { - let message_args = [successful_records.length]; - if (frm.doc.import_type === "Insert New Records") { - message = - successful_records.length > 1 - ? __("Successfully imported {0} records.", message_args) - : __("Successfully imported {0} record.", message_args); - } else { - message = - successful_records.length > 1 - ? __("Successfully updated {0} records.", message_args) - : __("Successfully updated {0} record.", message_args); - } - } else { - let message_args = [successful_records.length, import_log.length]; - if (frm.doc.import_type === "Insert New Records") { - message = - successful_records.length > 1 - ? __( - "Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.", - message_args - ) - : __( - "Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.", - message_args - ); - } else { - message = - successful_records.length > 1 - ? __( - "Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.", - message_args - ) - : __( - "Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.", - message_args - ); - } - } - frm.dashboard.set_headline(message); + if (!total_records) { + return; + } + + let message; + if (failed_records === 0) { + let message_args = [successful_records]; + if (frm.doc.import_type === "Insert New Records") { + message = + successful_records > 1 + ? __("Successfully imported {0} records.", message_args) + : __("Successfully imported {0} record.", message_args); + } else { + message = + successful_records > 1 + ? __("Successfully updated {0} records.", message_args) + : __("Successfully updated {0} record.", message_args); + } + } else { + let message_args = [successful_records, total_records]; + if (frm.doc.import_type === "Insert New Records") { + message = + successful_records > 1 + ? __( + "Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.", + message_args + ) + : __( + "Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.", + message_args + ); + } else { + message = + successful_records > 1 + ? __( + "Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.", + message_args + ) + : __( + "Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.", + message_args + ); + } + } + + frm.dashboard.set_headline(message); + }, + }); }, show_report_error_button(frm) { diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index 0311f8b1bcb..06f742afc19 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -242,6 +242,34 @@ def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None): return True +@frappe.whitelist() +def get_import_status(docname): + import_status = {} + + data_import = frappe.get_doc("Bank Statement Import", docname) + import_status["status"] = data_import.status + + logs = frappe.get_all( + "Data Import Log", + fields=["count(*) as count", "success"], + filters={"data_import": docname}, + group_by="success", + ) + + total_payload_count = 0 + + for log in logs: + total_payload_count += log.get("count", 0) + if log.get("success"): + import_status["success"] = log.get("count") + else: + import_status["failed"] = log.get("count") + + import_status["total_records"] = total_payload_count + + return import_status + + @frappe.whitelist() def upload_bank_statement(**args): args = frappe._dict(args) From 62aac8bb85b3fc28d5db9074ccfc2a03a3d76fb9 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 25 Jun 2024 14:47:23 +0530 Subject: [PATCH 21/42] fix: import log preview (cherry picked from commit 1ad264de48216889d1c1a5a83d0cd773070049d5) --- .../bank_statement_import.js | 142 +++++++++++------- .../bank_statement_import.py | 13 ++ 2 files changed, 99 insertions(+), 56 deletions(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index 29cf5189d82..d27a3fe1d33 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -299,7 +299,7 @@ frappe.ui.form.on("Bank Statement Import", { // method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template', show_import_preview(frm, preview_data) { - let import_log = JSON.parse(frm.doc.statement_import_log || "[]"); + let import_log = preview_data.import_log; if (frm.import_preview && frm.import_preview.doctype === frm.doc.reference_doctype) { frm.import_preview.preview_data = preview_data; @@ -413,49 +413,50 @@ frappe.ui.form.on("Bank Statement Import", { frm.trigger("show_import_log"); }, - show_import_log(frm) { - let import_log = JSON.parse(frm.doc.statement_import_log || "[]"); - let logs = import_log; - frm.toggle_display("import_log", false); - frm.toggle_display("import_log_section", logs.length > 0); + render_import_log(frm) { + frappe.call({ + method: "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.get_import_logs", + args: { + docname: frm.doc.name, + }, + callback: function (r) { + let logs = r.message; - if (logs.length === 0) { - frm.get_field("import_log_preview").$wrapper.empty(); - return; - } + if (logs.length === 0) return; - let rows = logs - .map((log) => { - let html = ""; - if (log.success) { - if (frm.doc.import_type === "Insert New Records") { - html = __("Successfully imported {0}", [ - `${frappe.utils.get_form_link( - frm.doc.reference_doctype, - log.docname, - true - )}`, - ]); - } else { - html = __("Successfully updated {0}", [ - `${frappe.utils.get_form_link( - frm.doc.reference_doctype, - log.docname, - true - )}`, - ]); - } - } else { - let messages = log.messages - .map(JSON.parse) - .map((m) => { - let title = m.title ? `${m.title}` : ""; - let message = m.message ? `
${m.message}
` : ""; - return title + message; - }) - .join(""); - let id = frappe.dom.get_unique_id(); - html = `${messages} + frm.toggle_display("import_log_section", true); + + let rows = logs + .map((log) => { + let html = ""; + if (log.success) { + if (frm.doc.import_type === "Insert New Records") { + html = __("Successfully imported {0}", [ + `${frappe.utils.get_form_link( + frm.doc.reference_doctype, + log.docname, + true + )}`, + ]); + } else { + html = __("Successfully updated {0}", [ + `${frappe.utils.get_form_link( + frm.doc.reference_doctype, + log.docname, + true + )}`, + ]); + } + } else { + let messages = JSON.parse(log.messages || "[]") + .map((m) => { + let title = m.title ? `${m.title}` : ""; + let message = m.message ? `
${m.message}
` : ""; + return title + message; + }) + .join(""); + let id = frappe.dom.get_unique_id(); + html = `${messages} @@ -464,16 +465,16 @@ frappe.ui.form.on("Bank Statement Import", {
${log.exception}
`; - } - let indicator_color = log.success ? "green" : "red"; - let title = log.success ? __("Success") : __("Failure"); + } + let indicator_color = log.success ? "green" : "red"; + let title = log.success ? __("Success") : __("Failure"); - if (frm.doc.show_failed_logs && log.success) { - return ""; - } + if (frm.doc.show_failed_logs && log.success) { + return ""; + } - return ` - ${log.row_indexes.join(", ")} + return ` + ${JSON.parse(log.row_indexes).join(", ")}
${title}
@@ -481,16 +482,16 @@ frappe.ui.form.on("Bank Statement Import", { ${html} `; - }) - .join(""); + }) + .join(""); - if (!rows && frm.doc.show_failed_logs) { - rows = ` + if (!rows && frm.doc.show_failed_logs) { + rows = ` ${__("No failed logs")} `; - } + } - frm.get_field("import_log_preview").$wrapper.html(` + frm.get_field("import_log_preview").$wrapper.html(` @@ -500,5 +501,34 @@ frappe.ui.form.on("Bank Statement Import", { ${rows}
${__("Row Number")}
`); + }, + }); + }, + + show_import_log(frm) { + frm.toggle_display("import_log_section", false); + + if (frm.is_new() || frm.import_in_progress) { + return; + } + + frappe.call({ + method: "frappe.client.get_count", + args: { + doctype: "Data Import Log", + filters: { + data_import: frm.doc.name, + }, + }, + callback: function (r) { + let count = r.message; + if (count < 5000) { + frm.trigger("render_import_log"); + } else { + frm.toggle_display("import_log_section", false); + frm.add_custom_button(__("Export Import Log"), () => frm.trigger("export_import_log")); + } + }, + }); }, }); diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index 06f742afc19..03979c42592 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -270,6 +270,19 @@ def get_import_status(docname): return import_status +@frappe.whitelist() +def get_import_logs(docname: str): + frappe.has_permission("Bank Statement Import") + + return frappe.get_all( + "Data Import Log", + fields=["success", "docname", "messages", "exception", "row_indexes"], + filters={"data_import": docname}, + limit_page_length=5000, + order_by="log_index", + ) + + @frappe.whitelist() def upload_bank_statement(**args): args = frappe._dict(args) From 4eb251b59ac5e26b06b0640a77bd6fe52f3e491b Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 25 Jun 2024 14:55:51 +0530 Subject: [PATCH 22/42] fix: download_import_log if rows are greater than 5000 (cherry picked from commit 2112d8f772e1536f15073af123211b7b78d02460) --- .../bank_statement_import/bank_statement_import.js | 9 +++++++++ .../bank_statement_import/bank_statement_import.py | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index d27a3fe1d33..d64d71e7312 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -338,6 +338,15 @@ frappe.ui.form.on("Bank Statement Import", { ); }, + export_import_log(frm) { + open_url_post( + "/api/method/erpnext.accounts.doctype.bank_statement_import.bank_statement_import.download_import_log", + { + data_import_name: frm.doc.name, + } + ); + }, + show_import_warnings(frm, preview_data) { let columns = preview_data.columns; let warnings = JSON.parse(frm.doc.template_warnings || "[]"); diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index 03979c42592..fd18819187e 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -121,6 +121,11 @@ def download_errored_template(data_import_name): data_import.export_errored_rows() +@frappe.whitelist() +def download_import_log(data_import_name): + return frappe.get_doc("Bank Statement Import", data_import_name).download_import_log() + + def parse_data_from_template(raw_data): data = [] From a04938d5ae9f9312757568ee4e01e18c9f4df901 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 25 Jun 2024 15:04:21 +0530 Subject: [PATCH 23/42] fix: field position (cherry picked from commit 98ad01c7360803435ee0f6f809b4681e2fe01d48) # Conflicts: # erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json --- .../bank_statement_import/bank_statement_import.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json index 4d9721f771d..a2eec24023b 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json @@ -11,13 +11,13 @@ "bank_account", "bank", "column_break_4", + "custom_delimiters", + "delimiter_options", "google_sheets_url", "refresh_google_sheet", "html_5", "import_file", "download_template", - "custom_delimiters", - "delimiter_options", "status", "template_options", "import_warnings_section", @@ -212,6 +212,7 @@ "hide_toolbar": 1, "links": [], <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "modified": "2022-09-07 11:11:40.293317", ======= @@ -220,6 +221,9 @@ ======= "modified": "2024-06-25 16:33:50.675848", >>>>>>> b202409428 (fix: remove deprecated field "statement_import_log") +======= + "modified": "2024-06-25 17:32:07.658250", +>>>>>>> 98ad01c736 (fix: field position) "modified_by": "Administrator", "module": "Accounts", "name": "Bank Statement Import", From 57896a8f9936f4716d9282df04e890ab5201e44e Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 25 Jun 2024 17:48:10 +0530 Subject: [PATCH 24/42] fix: changes as per review (cherry picked from commit ba08b2e8e808c154b155995bd93498004c2ff926) --- .../doctype/bank_statement_import/bank_statement_import.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index d64d71e7312..7361cb2fc1c 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -120,6 +120,8 @@ frappe.ui.form.on("Bank Statement Import", { }, show_import_status(frm) { + if (frm.doc.status == "Pending") return; + frappe.call({ method: "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.get_import_status", args: { From f281e064f2004745f730cf919f719e0489186be9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 9 Jul 2024 10:39:02 +0530 Subject: [PATCH 25/42] chore: resolve conflict --- .../bank_statement_import/bank_statement_import.json | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json index a2eec24023b..3210457c645 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json @@ -211,19 +211,7 @@ ], "hide_toolbar": 1, "links": [], -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - "modified": "2022-09-07 11:11:40.293317", -======= - "modified": "2024-06-25 15:34:35.214946", ->>>>>>> 13fb560401 (fix: custom delimiters) -======= - "modified": "2024-06-25 16:33:50.675848", ->>>>>>> b202409428 (fix: remove deprecated field "statement_import_log") -======= "modified": "2024-06-25 17:32:07.658250", ->>>>>>> 98ad01c736 (fix: field position) "modified_by": "Administrator", "module": "Accounts", "name": "Bank Statement Import", From 7916d6436f2152e90ec5d2f8de3416937c6de800 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:13:19 +0530 Subject: [PATCH 26/42] fix: auto serial and batch bundle not creating for Asset Capitalization (backport #42231) (#42242) fix: auto serial and batch bundle not creating for Asset Capitalization (#42231) (cherry picked from commit 5f53ad311743cee4539411aba73444ad6f292606) Co-authored-by: rohitwaghchaure --- erpnext/controllers/stock_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 212c9adf4af..1620fa3bfff 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -218,7 +218,7 @@ class StockController(AccountsController): "do_not_submit": True if not via_landed_cost_voucher else False, } - if row.get("qty") or row.get("consumed_qty"): + if row.get("qty") or row.get("consumed_qty") or row.get("stock_qty"): self.update_bundle_details(bundle_details, table_name, row) self.create_serial_batch_bundle(bundle_details, row) From 68a39dfa331d7f2922f7961a243d303015e90008 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:19:10 +0530 Subject: [PATCH 27/42] fix: BOM Creator Recursion Error on duplicate save (backport #41622) (#42179) fix: BOM Creator Recursion Error on duplicate save (#41622) (cherry picked from commit 9cfe09bdf60a30366e0608fe0f66fdfdad214129) Co-authored-by: Richard Case <110036763+casesolved-co-uk@users.noreply.github.com> --- .../manufacturing/doctype/bom_creator/bom_creator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index e236e7a6345..71b23ba3202 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -156,12 +156,12 @@ class BOMCreator(Document): amount = self.get_raw_material_cost() self.raw_material_cost = amount - def get_raw_material_cost(self, fg_reference_id=None, amount=0): - if not fg_reference_id: - fg_reference_id = self.name + def get_raw_material_cost(self, fg_item=None, amount=0): + if not fg_item: + fg_item = self.item_code for row in self.items: - if row.fg_reference_id != fg_reference_id: + if row.fg_item != fg_item: continue if not row.is_expandable: @@ -183,7 +183,7 @@ class BOMCreator(Document): else: row.amount = 0.0 - row.amount = self.get_raw_material_cost(row.name, row.amount) + row.amount = self.get_raw_material_cost(row.item_code, row.amount) row.rate = flt(row.amount) / (flt(row.qty) * flt(row.conversion_factor)) amount += flt(row.amount) From 954d9ab154ef824f3cdf847fbc7e8c02c55c6fb0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:52:06 +0530 Subject: [PATCH 28/42] fix(Stock Entry Type): reduce perms for Stock User (backport #42225) (#42245) * fix(Stock Entry Type): reduce perms for Stock User (#42225) (cherry picked from commit 4936514c8d91e539ef0c24ce26929c4e281578d5) # Conflicts: # erpnext/stock/doctype/stock_entry_type/stock_entry_type.json * chore: fix conflicts --------- Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Co-authored-by: rohitwaghchaure --- .../doctype/stock_entry_type/stock_entry_type.json | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json index eee38be0278..db8e1fef69b 100644 --- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json +++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json @@ -29,7 +29,7 @@ } ], "links": [], - "modified": "2021-05-21 11:27:01.144110", + "modified": "2024-07-08 08:41:19.385020", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Type", @@ -72,20 +72,12 @@ "write": 1 }, { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, "read": 1, - "report": 1, - "role": "Stock User", - "share": 1, - "write": 1 + "role": "Stock User" } ], "quick_entry": 1, "sort_field": "modified", "sort_order": "ASC", "track_changes": 1 -} \ No newline at end of file +} From b78a97df85c66ba0d3a8f4598d239be7df39c41d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:52:17 +0530 Subject: [PATCH 29/42] fix(Putaway Rule): reduce perms for Stock User (backport #42227) (#42244) * fix(Putaway Rule): reduce perms for Stock User (#42227) (cherry picked from commit 96b275c61e683f8b84d7f2e640f3c1ca2178847d) # Conflicts: # erpnext/stock/doctype/putaway_rule/putaway_rule.json * chore: fix conflicts --------- Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Co-authored-by: rohitwaghchaure --- .../stock/doctype/putaway_rule/putaway_rule.json | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.json b/erpnext/stock/doctype/putaway_rule/putaway_rule.json index a003f4986f9..f251fea16ee 100644 --- a/erpnext/stock/doctype/putaway_rule/putaway_rule.json +++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.json @@ -111,10 +111,11 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-11-25 20:39:19.973437", + "modified": "2024-07-08 09:19:26.711470", "modified_by": "Administrator", "module": "Stock", "name": "Putaway Rule", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -130,16 +131,10 @@ "write": 1 }, { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, "print": 1, "read": 1, "report": 1, - "role": "Stock User", - "share": 1, - "write": 1 + "role": "Stock User" }, { "email": 1, @@ -157,4 +152,4 @@ "sort_order": "DESC", "title_field": "item_code", "track_changes": 1 -} \ No newline at end of file +} From 3cc59e4a7a379b0dd22b95882ca26a07acdbbee2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:52:30 +0530 Subject: [PATCH 30/42] fix(Inventory Dimension): reduce perms for Stock User (backport #42226) (#42243) * fix(Inventory Dimension): reduce perms for Stock User (#42226) (cherry picked from commit edc1f84250a6fe3e19ad692e2b551408960f514b) # Conflicts: # erpnext/stock/doctype/inventory_dimension/inventory_dimension.json * chore: fix conflicts --------- Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Co-authored-by: rohitwaghchaure --- .../inventory_dimension/inventory_dimension.json | 14 +++----------- .../inventory_dimension/inventory_dimension.py | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json index 0e4055251f0..9638c4e6289 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json @@ -189,7 +189,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-10-05 12:52:18.705431", + "modified": "2024-07-08 08:58:50.228211", "modified_by": "Administrator", "module": "Stock", "name": "Inventory Dimension", @@ -221,19 +221,11 @@ "write": 1 }, { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, "read": 1, - "report": 1, - "role": "Stock User", - "share": 1, - "write": 1 + "role": "Stock User" } ], "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index 103d8a1ae57..3bafa12983f 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -33,7 +33,7 @@ class InventoryDimension(Document): dimension_name: DF.Data disabled: DF.Check document_type: DF.Link | None - fetch_from_parent: DF.Literal + fetch_from_parent: DF.Literal[None] istable: DF.Check mandatory_depends_on: DF.SmallText | None reference_document: DF.Link From 4866958a96bf99f69e842b6986ee339b885873ec Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:17:20 +0530 Subject: [PATCH 31/42] fix: actual qty in sales order (backport #42248) (#42256) fix: actual qty in sales order (#42248) (cherry picked from commit 1186ee128c5cf25a72774a0ee4217e05291863bc) Co-authored-by: rohitwaghchaure --- erpnext/public/js/utils/sales_common.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index 0b482c3292f..91a85aa02a5 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -220,6 +220,28 @@ erpnext.sales_common = { if (doc.docstatus === 0 && doc.is_return && !doc.return_against) { frappe.model.set_value(cdt, cdn, "incoming_rate", 0.0); } + + this.set_actual_qty(doc, cdt, cdn); + } + + set_actual_qty(doc, cdt, cdn) { + let row = locals[cdt][cdn]; + let sales_doctypes = ["Sales Invoice", "Delivery Note", "Sales Order"]; + + if (row.item_code && row.warehouse && sales_doctypes.includes(doc.doctype)) { + frappe.call({ + method: "erpnext.stock.get_item_details.get_bin_details", + args: { + item_code: row.item_code, + warehouse: row.warehouse, + }, + callback(r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, "actual_qty", r.message.actual_qty); + } + }, + }); + } } toggle_editable_price_list_rate() { From e1b50efeea222fd78eb68332638bbd17544a260e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:17:35 +0530 Subject: [PATCH 32/42] fix: corrected mismatch in the Purchase Receipt Status #15620 (backport #42138) (#42252) * fix: corrected mismatch in the Purchase Receipt Status #15620 (#42138) (cherry picked from commit 623b4c21cd70f709940ee938ddac9c2a86c7c3f0) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py * chore: fix conflicts --------- Co-authored-by: Poorvi-R-Bhat Co-authored-by: rohitwaghchaure --- erpnext/controllers/status_updater.py | 5 ++++- .../purchase_receipt/purchase_receipt.json | 3 ++- .../purchase_receipt/test_purchase_receipt.py | 18 +++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 492000d71d4..e8aa6254d34 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -94,7 +94,10 @@ status_map = { ["To Bill", "eval:self.per_billed == 0 and self.docstatus == 1"], ["Partly Billed", "eval:self.per_billed > 0 and self.per_billed < 100 and self.docstatus == 1"], ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"], - ["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"], + [ + "Completed", + "eval:(self.per_billed == 100 and self.docstatus == 1) or (self.docstatus == 1 and self.grand_total == 0 and self.per_returned != 100 and self.is_return == 0)", + ], ["Cancelled", "eval:self.docstatus==2"], ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"], ], diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index ee8df084f08..61a180caba4 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -1111,6 +1111,7 @@ "depends_on": "eval:!doc.__islocal", "fieldname": "per_returned", "fieldtype": "Percent", + "in_list_view": 1, "label": "% Returned", "no_copy": 1, "print_hide": 1, @@ -1272,7 +1273,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2024-04-08 20:23:03.699201", + "modified": "2024-07-04 14:50:10.538472", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 459f7f3f841..55bb803e9c0 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -8,6 +8,7 @@ from pypika import functions as fn import erpnext from erpnext.accounts.doctype.account.test_account import get_inventory_account +from erpnext.buying.doctype.supplier.test_supplier import create_supplier from erpnext.controllers.buying_controller import QtyMismatchError from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.item.test_item import create_item, make_item @@ -3229,6 +3230,22 @@ class TestPurchaseReceipt(FrappeTestCase): doc.flags.ignore_validate = True doc.save() + def test_status_mapping(self): + item_code = "item_for_status" + create_item(item_code) + create_item("item_for_status") + warehouse = create_warehouse("Stores") + supplier = "Test Supplier" + create_supplier(supplier_name=supplier) + pr = make_purchase_receipt( + item_code=item_code, + warehouse=warehouse, + qty=1, + rate=0, + ) + self.assertEqual(pr.grand_total, 0.0) + self.assertEqual(pr.status, "Completed") + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier @@ -3471,7 +3488,6 @@ def make_purchase_receipt(**args): pr.insert() if not args.do_not_submit: pr.submit() - pr.load_from_db() return pr From 43c7513cfe20713e23627a77e0b6b23471073c0a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:17:57 +0530 Subject: [PATCH 33/42] fix: Billed Qty and Qty to Bill Calculation in Purchase Order Analysis (backport #42100) (#42249) fix: Billed Qty and Qty to Bill Calculation in Purchase Order Analysis (#42100) (cherry picked from commit 0c76a8cc9ea2e9de44e4747b91fced5b579a30b7) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> --- .../report/purchase_order_analysis/purchase_order_analysis.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index b23c3f50b9a..cf5e0ca3f8f 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -39,6 +39,7 @@ def validate_filters(filters): def get_data(filters): po = frappe.qb.DocType("Purchase Order") po_item = frappe.qb.DocType("Purchase Order Item") + pi = frappe.qb.DocType("Purchase Invoice") pi_item = frappe.qb.DocType("Purchase Invoice Item") query = ( @@ -46,6 +47,8 @@ def get_data(filters): .from_(po_item) .left_join(pi_item) .on(pi_item.po_detail == po_item.name) + .left_join(pi) + .on(pi.name == pi_item.parent) .select( po.transaction_date.as_("date"), po_item.schedule_date.as_("required_date"), @@ -69,6 +72,7 @@ def get_data(filters): po_item.name, ) .where((po_item.parent == po.name) & (po.status.notin(("Stopped", "Closed"))) & (po.docstatus == 1)) + .where(pi.docstatus == 1) .groupby(po_item.name) .orderby(po.transaction_date) ) From 19ed6d10815144c74e5221a3584c099022da5f11 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 8 Jul 2024 20:48:53 +0530 Subject: [PATCH 34/42] fix: added filter to show only submitted assets (cherry picked from commit cd3a90049576daccb6527a98c610b6d43091d522) --- erpnext/assets/doctype/asset_repair/asset_repair.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index 489fbaca6b2..9284c86bd92 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -20,6 +20,15 @@ frappe.ui.form.on("Asset Repair", { }; }; + frm.set_query("asset", function () { + return { + filters: { + company: frm.doc.company, + docstatus: 1, + }, + }; + }); + frm.set_query("warehouse", "stock_items", function () { return { filters: { From b5556156c18f218338deafb49e8581be73b6361e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:53:38 +0530 Subject: [PATCH 35/42] fix(Holiday List): sort holidays on save to avoid disorienting the user (backport #42236) (#42250) fix(Holiday List): sort holidays on save to avoid disorienting the user (#42236) fix: sort holidays on save to avoid disorienting the user (cherry picked from commit ad137250fc8c9421fd085afd83ed439dcbbd0568) Co-authored-by: Rucha Mahabal --- erpnext/setup/doctype/holiday_list/holiday_list.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/setup/doctype/holiday_list/holiday_list.py b/erpnext/setup/doctype/holiday_list/holiday_list.py index 6932f7be825..0216f75a628 100644 --- a/erpnext/setup/doctype/holiday_list/holiday_list.py +++ b/erpnext/setup/doctype/holiday_list/holiday_list.py @@ -43,6 +43,7 @@ class HolidayList(Document): self.validate_days() self.total_holidays = len(self.holidays) self.validate_duplicate_date() + self.sort_holidays() @frappe.whitelist() def get_weekly_off_dates(self): @@ -57,8 +58,6 @@ class HolidayList(Document): self.append("holidays", {"description": _(self.weekly_off), "holiday_date": d, "weekly_off": 1}) - self.sort_holidays() - @frappe.whitelist() def get_supported_countries(self): from holidays.utils import list_supported_countries @@ -100,8 +99,6 @@ class HolidayList(Document): "holidays", {"description": holiday_name, "holiday_date": holiday_date, "weekly_off": 0} ) - self.sort_holidays() - def sort_holidays(self): self.holidays.sort(key=lambda x: getdate(x.holiday_date)) for i in range(len(self.holidays)): From 3fb5c7a3a629e60925709c136e735df73042e52d Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 2 Jul 2024 19:07:31 +0530 Subject: [PATCH 36/42] fix: updated logic for calculating tax_withholding_net_total in payment entry (cherry picked from commit c8a34cde7f70cc22fde9743314439f0091077e6b) --- .../doctype/payment_entry/payment_entry.py | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index fb3500e2c58..1103cd09795 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -82,7 +82,6 @@ class PaymentEntry(AccountsController): self.set_exchange_rate() self.validate_mandatory() self.validate_reference_documents() - self.set_tax_withholding() self.set_amounts() self.validate_amounts() self.apply_taxes() @@ -96,6 +95,7 @@ class PaymentEntry(AccountsController): self.validate_allocated_amount() self.validate_paid_invoices() self.ensure_supplier_is_not_blocked() + self.set_tax_withholding() self.set_status() self.set_total_in_words() @@ -756,9 +756,7 @@ class PaymentEntry(AccountsController): if not self.apply_tax_withholding_amount: return - order_amount = self.get_order_net_total() - - net_total = flt(order_amount) + flt(self.unallocated_amount) + net_total = self.calculate_tax_withholding_net_total() # Adding args as purchase invoice to get TDS amount args = frappe._dict( @@ -802,7 +800,26 @@ class PaymentEntry(AccountsController): for d in to_remove: self.remove(d) - def get_order_net_total(self): + def calculate_tax_withholding_net_total(self): + net_total = 0 + order_details = self.get_order_wise_tax_withholding_net_total() + + for d in self.references: + tax_withholding_net_total = order_details.get(d.reference_name) + if not tax_withholding_net_total: + continue + + net_taxable_outstanding = max( + 0, d.outstanding_amount - (d.total_amount - tax_withholding_net_total) + ) + + net_total += min(net_taxable_outstanding, d.allocated_amount) + + net_total += self.unallocated_amount + + return net_total + + def get_order_wise_tax_withholding_net_total(self): if self.party_type == "Supplier": doctype = "Purchase Order" else: @@ -810,12 +827,15 @@ class PaymentEntry(AccountsController): docnames = [d.reference_name for d in self.references if d.reference_doctype == doctype] - tax_withholding_net_total = frappe.db.get_value( - doctype, {"name": ["in", docnames]}, ["sum(base_tax_withholding_net_total)"] + return frappe._dict( + frappe.db.get_all( + doctype, + filters={"name": ["in", docnames]}, + fields=["name", "base_tax_withholding_net_total"], + as_list=True, + ) ) - return tax_withholding_net_total - def apply_taxes(self): self.initialize_taxes() self.determine_exclusive_rate() From 8264e3bc77dacc2ac62f46fac1bec83cf0ec93cb Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 10 Jul 2024 11:56:38 +0530 Subject: [PATCH 37/42] fix(tds): use doctype reference when mapping keys across multiple doctypes (#42258) * fix(tds): use doctype reference when mapping keys across multiple doctypes * fix: changes as per review --------- Co-authored-by: ljain112 (cherry picked from commit 5c0d52f783acd22fa6ffd0330239a739c902d5dc) # Conflicts: # erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py --- .../tax_withholding_details.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index 3a3cedc0bae..b0e47bfa025 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -12,7 +12,7 @@ def execute(filters=None): else: party_naming_by = frappe.db.get_single_value("Buying Settings", "supp_master_name") - filters.update({"naming_series": party_naming_by}) + filters["naming_series"] = party_naming_by validate_filters(filters) ( @@ -63,21 +63,28 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_ tax_withholding_category = tds_accounts.get(entry.account) # or else the consolidated value from the voucher document if not tax_withholding_category: - tax_withholding_category = tax_category_map.get(name) + tax_withholding_category = tax_category_map.get((voucher_type, name)) # or else from the party default if not tax_withholding_category: tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category") rate = tax_rate_map.get(tax_withholding_category) +<<<<<<< HEAD if net_total_map.get(name): if voucher_type == "Journal Entry": +======= + if net_total_map.get((voucher_type, name)): + if voucher_type == "Journal Entry" and tax_amount and rate: +>>>>>>> 5c0d52f783 (fix(tds): use doctype reference when mapping keys across multiple doctypes (#42258)) # back calcalute total amount from rate and tax_amount if rate: total_amount = grand_total = base_total = tax_amount / (rate / 100) elif voucher_type == "Purchase Invoice": - total_amount, grand_total, base_total, bill_no, bill_date = net_total_map.get(name) + total_amount, grand_total, base_total, bill_no, bill_date = net_total_map.get( + (voucher_type, name) + ) else: - total_amount, grand_total, base_total = net_total_map.get(name) + total_amount, grand_total, base_total = net_total_map.get((voucher_type, name)) else: total_amount += entry.credit @@ -97,7 +104,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_ } if filters.naming_series == "Naming Series": - row.update({"party_name": party_map.get(party, {}).get(party_name)}) + row["party_name"] = party_map.get(party, {}).get(party_name) row.update( { @@ -279,7 +286,6 @@ def get_tds_docs(filters): journal_entries = [] tax_category_map = frappe._dict() net_total_map = frappe._dict() - frappe._dict() journal_entry_party_map = frappe._dict() bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name") @@ -412,7 +418,7 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None): ) for entry in entries: - tax_category_map.update({entry.name: entry.tax_withholding_category}) + tax_category_map[(doctype, entry.name)] = entry.tax_withholding_category if doctype == "Purchase Invoice": value = [ entry.base_tax_withholding_net_total, @@ -427,7 +433,8 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None): value = [entry.paid_amount, entry.paid_amount_after_tax, entry.base_paid_amount] else: value = [entry.total_amount] * 3 - net_total_map.update({entry.name: value}) + + net_total_map[(doctype, entry.name)] = value def get_tax_rate_map(filters): From 23053f45deafbc0663faca36f355206a2078d564 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 10 Jul 2024 12:26:35 +0530 Subject: [PATCH 38/42] chore: resolve conflicts --- .../tax_withholding_details/tax_withholding_details.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index b0e47bfa025..276e95bd5d1 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -69,13 +69,8 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_ tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category") rate = tax_rate_map.get(tax_withholding_category) -<<<<<<< HEAD - if net_total_map.get(name): - if voucher_type == "Journal Entry": -======= if net_total_map.get((voucher_type, name)): - if voucher_type == "Journal Entry" and tax_amount and rate: ->>>>>>> 5c0d52f783 (fix(tds): use doctype reference when mapping keys across multiple doctypes (#42258)) + if voucher_type == "Journal Entry": # back calcalute total amount from rate and tax_amount if rate: total_amount = grand_total = base_total = tax_amount / (rate / 100) From ab987e9a86ba143a6920609fb3818dd162a05bba Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 1 Jul 2024 16:07:38 +0530 Subject: [PATCH 39/42] fix: removed max discount validation for sales return (cherry picked from commit db807d433be8646de8507a91850f24befa2e3c86) --- erpnext/controllers/selling_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index e9896fa0fd6..71e40d509b2 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -28,7 +28,7 @@ class SellingController(StockController): def validate(self): super().validate() self.validate_items() - if not self.get("is_debit_note"): + if not (self.get("is_debit_note") or self.get("is_return")): self.validate_max_discount() self.validate_selling_price() self.set_qty_as_per_stock_uom() From 90f5c78607656ed8b3a644f30de4efce2c127110 Mon Sep 17 00:00:00 2001 From: DaizyModi Date: Sat, 6 Jul 2024 18:50:20 +0530 Subject: [PATCH 40/42] fix: Project Status should be Open again if `percent_complete` is not 100 (cherry picked from commit c52fdffdafbbf2debd1bc03953797ccdd29d25c4) --- erpnext/projects/doctype/project/project.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 06eced2036f..b09735e5644 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -262,8 +262,7 @@ class Project(Document): if self.status == "Cancelled": return - if self.percent_complete == 100: - self.status = "Completed" + self.status = "Completed" if self.percent_complete == 100 else "Open" def update_costing(self): from frappe.query_builder.functions import Max, Min, Sum From 427439c3f19bfd1401628cf39a30ddd291dbfec7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 9 Jul 2024 13:18:09 +0530 Subject: [PATCH 41/42] fix: tax on stock_rbnb on repost of Purchase Receipt (cherry picked from commit 8633080dffcbb8e12c5c93946fe9e36837827f75) --- .../purchase_receipt/purchase_receipt.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index a8cb7fd83b4..3c97d13b904 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -785,7 +785,6 @@ class PurchaseReceipt(BuyingController): def make_tax_gl_entries(self, gl_entries, via_landed_cost_voucher=False): negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")]) - is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) # Cost center-wise amount breakup for other charges included for valuation valuation_tax = {} for tax in self.get("taxes"): @@ -810,26 +809,10 @@ class PurchaseReceipt(BuyingController): against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = negative_expense_to_be_booked - stock_rbnb = ( - self.get("asset_received_but_not_billed") - if is_asset_pr - else self.get_company_default("stock_received_but_not_billed") - ) i = 1 for tax in self.get("taxes"): if valuation_tax.get(tax.name): - if via_landed_cost_voucher or self.is_landed_cost_booked_for_any_item(): - account = tax.account_head - else: - negative_expense_booked_in_pi = frappe.db.sql( - """select name from `tabPurchase Invoice Item` pi - where docstatus = 1 and purchase_receipt=%s - and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' - and voucher_no=pi.parent and account=%s)""", - (self.name, tax.account_head), - ) - account = stock_rbnb if negative_expense_booked_in_pi else tax.account_head - + account = tax.account_head if i == len(valuation_tax): applicable_amount = amount_including_divisional_loss else: From 7b4fd896582d674d98e25f4004040e2fd79603ba Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 9 Jul 2024 13:30:47 +0530 Subject: [PATCH 42/42] test: tax account heads on PR report without LCV (cherry picked from commit 9562628ed67c958cf2a9d41257b89dfd5c5e5fd7) --- .../purchase_receipt/test_purchase_receipt.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 55bb803e9c0..869d3ed3cce 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3171,6 +3171,110 @@ class TestPurchaseReceipt(FrappeTestCase): lcv.save().submit() return lcv + def test_tax_account_heads_on_item_repost_without_lcv(self): + """ + PO -> PR -> PI + Backdated `Repost Item valuation` should not merge tax account heads into stock_rbnb if Purchase Receipt was created first + This scenario is without LCV + """ + from erpnext.accounts.doctype.account.test_account import create_account + from erpnext.buying.doctype.purchase_order.test_purchase_order import ( + create_purchase_order, + make_pr_against_po, + ) + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice + + stock_rbnb = "Stock Received But Not Billed - _TC" + stock_in_hand = "Stock In Hand - _TC" + test_cc = "_Test Cost Center - _TC" + test_company = "_Test Company" + creditors = "Creditors - _TC" + + company_doc = frappe.get_doc("Company", test_company) + company_doc.enable_perpetual_inventory = True + company_doc.stock_received_but_not_billed = stock_rbnb + company_doc.default_inventory_account = stock_in_hand + company_doc.save() + + packaging_charges_account = create_account( + account_name="Packaging Charges", + parent_account="Indirect Expenses - _TC", + company=test_company, + account_type="Tax", + ) + + po = create_purchase_order(qty=10, rate=100, do_not_save=1) + po.taxes = [] + po.append( + "taxes", + { + "category": "Valuation and Total", + "account_head": packaging_charges_account, + "cost_center": test_cc, + "description": "Test", + "add_deduct_tax": "Add", + "charge_type": "Actual", + "tax_amount": 250, + }, + ) + po.save().submit() + + pr = make_pr_against_po(po.name, received_qty=10) + pr_gl_entries = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) + expected_pr_gles = [ + {"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, + {"account": stock_in_hand, "debit": 1250.0, "credit": 0.0, "cost_center": test_cc}, + {"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc}, + ] + self.assertEqual(expected_pr_gles, pr_gl_entries) + + # Make PI against Purchase Receipt + pi = make_purchase_invoice(pr.name).save().submit() + pi_gl_entries = get_gl_entries(pi.doctype, pi.name, skip_cancelled=True) + expected_pi_gles = [ + {"account": stock_rbnb, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc}, + {"account": packaging_charges_account, "debit": 250.0, "credit": 0.0, "cost_center": test_cc}, + {"account": creditors, "debit": 0.0, "credit": 1250.0, "cost_center": None}, + ] + self.assertEqual(expected_pi_gles, pi_gl_entries) + + # Trigger Repost Item Valudation on a older date + repost_doc = frappe.get_doc( + { + "doctype": "Repost Item Valuation", + "based_on": "Item and Warehouse", + "item_code": pr.items[0].item_code, + "warehouse": pr.items[0].warehouse, + "posting_date": add_days(pr.posting_date, -1), + "posting_time": "00:00:00", + "company": pr.company, + "allow_negative_stock": 1, + "via_landed_cost_voucher": 0, + "allow_zero_rate": 0, + } + ) + repost_doc.save().submit() + + pr_gles_after_repost = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) + expected_pr_gles_after_repost = [ + {"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, + {"account": stock_in_hand, "debit": 1250.0, "credit": 0.0, "cost_center": test_cc}, + {"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc}, + ] + self.assertEqual(len(pr_gles_after_repost), len(expected_pr_gles_after_repost)) + self.assertEqual(expected_pr_gles_after_repost, pr_gles_after_repost) + + # teardown + pi.reload() + pi.cancel() + pr.reload() + pr.cancel() + + company_doc.enable_perpetual_inventory = False + company_doc.stock_received_but_not_billed = None + company_doc.default_inventory_account = None + company_doc.save() + def test_do_not_use_batchwise_valuation_rate(self): from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note