From 6d51d14dfd3924e369073fc18e643fcdaaa26e6f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 28 Aug 2024 12:21:22 +0530 Subject: [PATCH 01/25] fix: typeerror on Payment Entry (cherry picked from commit e9cf8937cd4a6c00666bf5f016b0647121d9838c) --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 57107c7a4533..5c00aff587d5 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -305,7 +305,7 @@ frappe.ui.form.on("Payment Entry", { set_dynamic_labels: function (frm) { var company_currency = frm.doc.company - ? frappe.get_doc(":Company", frm.doc.company).default_currency + ? frappe.get_doc(":Company", frm.doc.company)?.default_currency : ""; frm.set_currency_labels( From a833010d2bb09c7c5798d7aefad9c5dc61394641 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Thu, 29 Aug 2024 01:51:45 +0530 Subject: [PATCH 02/25] fix: don't allow capitalizing only service item for new composite asset (cherry picked from commit f1d21382589830392154823db0e29b99c758dd89) --- .../asset_capitalization/asset_capitalization.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 4c22f7ddd7a9..47c80d060e29 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -317,7 +317,16 @@ def validate_source_mandatory(self): if not self.target_is_fixed_asset and not self.get("asset_items"): frappe.throw(_("Consumed Asset Items is mandatory for Decapitalization")) - if not (self.get("stock_items") or self.get("asset_items") or self.get("service_items")): + if self.capitalization_method == "Create a new composite asset" and not ( + self.get("stock_items") or self.get("asset_items") + ): + frappe.throw( + _( + "Consumed Stock Items or Consumed Asset Items are mandatory for creating new composite asset" + ) + ) + + elif not (self.get("stock_items") or self.get("asset_items") or self.get("service_items")): frappe.throw( _( "Consumed Stock Items, Consumed Asset Items or Consumed Service Items is mandatory for Capitalization" From db746a4def25f0869708e480db8f1ecef95aba55 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 29 Aug 2024 11:46:29 +0530 Subject: [PATCH 03/25] refactor: better UX on Bank Clearance tool (cherry picked from commit 6a06e26d04b6fa50eab4068c9e26349560eb0fcb) --- .../doctype/bank_clearance/bank_clearance.js | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js index 2993825482c7..7ece7c9c2d6d 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js @@ -38,6 +38,11 @@ frappe.ui.form.on("Bank Clearance", { frm.add_custom_button(__("Get Payment Entries"), () => frm.trigger("get_payment_entries")); frm.change_custom_button_type(__("Get Payment Entries"), null, "primary"); + if (frm.doc.payment_entries.length) { + frm.add_custom_button(__("Update Clearance Date"), () => frm.trigger("update_clearance_date")); + frm.change_custom_button_type(__("Get Payment Entries"), null, "default"); + frm.change_custom_button_type(__("Update Clearance Date"), null, "primary"); + } }, update_clearance_date: function (frm) { @@ -45,13 +50,7 @@ frappe.ui.form.on("Bank Clearance", { method: "update_clearance_date", doc: frm.doc, callback: function (r, rt) { - frm.refresh_field("payment_entries"); - frm.refresh_fields(); - - if (!frm.doc.payment_entries.length) { - frm.change_custom_button_type(__("Get Payment Entries"), null, "primary"); - frm.change_custom_button_type(__("Update Clearance Date"), null, "default"); - } + frm.refresh(); }, }); }, @@ -60,17 +59,8 @@ frappe.ui.form.on("Bank Clearance", { return frappe.call({ method: "get_payment_entries", doc: frm.doc, - callback: function (r, rt) { - frm.refresh_field("payment_entries"); - - if (frm.doc.payment_entries.length) { - frm.add_custom_button(__("Update Clearance Date"), () => - frm.trigger("update_clearance_date") - ); - - frm.change_custom_button_type(__("Get Payment Entries"), null, "default"); - frm.change_custom_button_type(__("Update Clearance Date"), null, "primary"); - } + callback: function () { + frm.refresh(); }, }); }, From 42e7725442c0c6d4ace34c84a9ca4ec8ae6d6e8f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:41:08 +0530 Subject: [PATCH 04/25] fix: add the company in payment request bcz delete company transactions (backport #42664) (#42982) * fix: add the company in payment request bcz delete company transactions (cherry picked from commit 12834ccf9aebb7c53e3eeafb4a5747b09da5eb77) # Conflicts: # erpnext/accounts/doctype/payment_request/payment_request.json * fix: link company when make payment request (cherry picked from commit e3008843d15ef0301c3171250bfcd81db738e5ae) * fix: add the company in payment request bcz delete company transactions --conflicts --------- Co-authored-by: Nihantra Patel Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> --- .../doctype/payment_request/payment_request.json | 10 +++++++++- .../doctype/payment_request/payment_request.py | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index 0537ee6d3a38..d0651f74bdf3 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -9,6 +9,7 @@ "transaction_date", "column_break_2", "naming_series", + "company", "mode_of_payment", "party_details", "party_type", @@ -390,13 +391,20 @@ "options": "Payment Request", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "read_only": 1 } ], "in_create": 1, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-06-20 13:54:55.245774", + "modified": "2024-08-07 16:39:54.288002", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Request", diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index da63ccb6ad7a..f368c2a96130 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -84,6 +84,7 @@ class PaymentRequest(Document): subscription_plans: DF.Table[SubscriptionPlanDetail] swift_number: DF.ReadOnly | None transaction_date: DF.Date | None + company: DF.Link | None # end: auto-generated types def validate(self): @@ -491,6 +492,7 @@ def make_payment_request(**args): "message": gateway_account.get("message") or get_dummy_message(ref_doc), "reference_doctype": args.dt, "reference_name": args.dn, + "company": ref_doc.get("company"), "party_type": args.get("party_type") or "Customer", "party": args.get("party") or ref_doc.get("customer"), "bank_account": bank_account, From 53e1b57354d3743b29bab2c758d6b26574e3cdc9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 29 Aug 2024 15:54:55 +0530 Subject: [PATCH 05/25] refactor: link utility report with bank reconciliation statement (cherry picked from commit 00eac65712bc8dfdaf53589c8a4f5abc3d28a9c3) --- .../bank_reconciliation_statement.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js index efcfa7a5ee59..9335a8cd65a8 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js @@ -46,4 +46,20 @@ frappe.query_reports["Bank Reconciliation Statement"] = { fieldtype: "Check", }, ], + formatter: function (value, row, column, data, default_formatter, filter) { + if (column.fieldname == "payment_entry" && value == "Cheques and Deposits incorrectly cleared") { + column.link_onclick = + "frappe.query_reports['Bank Reconciliation Statement'].open_utility_report()"; + } + return default_formatter(value, row, column, data); + }, + open_utility_report: function () { + frappe.route_options = { + company: frappe.query_report.get_filter_value("company"), + account: frappe.query_report.get_filter_value("account"), + report_date: frappe.query_report.get_filter_value("report_date"), + }; + frappe.open_in_new_tab = true; + frappe.set_route("query-report", "Cheques and Deposits Incorrectly cleared"); + }, }; From f3c60ea0a781d0aea185013bda4baa4ec3064525 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:50:02 +0530 Subject: [PATCH 06/25] fix(capitalization): debit cwip account instead of fixed asset account (#42857) * fix(capitalization): debit cwip account instead of fixed asset account * fix: post entries for capitalized asset through background jobs * chore: run pre-commit * fix: correct GL entries posting for composite assets * fix(minor): resolve failing check * chore: update gl entry check logic * chore: handle none values (cherry picked from commit 5d99f17583bc3aeb82d7b44a97466a4b9f765d96) --- erpnext/assets/doctype/asset/asset.py | 41 ++++++++++++++++++- .../asset_capitalization.py | 19 +++++++-- .../test_asset_capitalization.py | 9 ++-- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index db37e54ada8f..8ede2670d386 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -691,12 +691,17 @@ def get_cwip_account(self, cwip_enabled=False): return cwip_account def make_gl_entries(self): + if self.check_asset_capitalization_gl_entries(): + return + gl_entries = [] purchase_document = self.get_purchase_document() fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account() - if purchase_document and self.purchase_amount and getdate(self.available_for_use_date) <= getdate(): + if (self.is_composite_asset or (purchase_document and self.purchase_amount)) and getdate( + self.available_for_use_date + ) <= getdate(): gl_entries.append( self.get_gl_dict( { @@ -733,6 +738,24 @@ def make_gl_entries(self): make_gl_entries(gl_entries) self.db_set("booked_fixed_asset", 1) + def check_asset_capitalization_gl_entries(self): + if self.is_composite_asset: + result = frappe.db.get_value( + "Asset Capitalization", + {"target_asset": self.name, "docstatus": 1}, + ["name", "target_fixed_asset_account"], + ) + + if result: + asset_capitalization, target_fixed_asset_account = result + # Check GL entries for the retrieved Asset Capitalization and target fixed asset account + return has_gl_entries( + "Asset Capitalization", asset_capitalization, target_fixed_asset_account + ) + # return if there are no submitted capitalization for given asset + return True + return False + @frappe.whitelist() def get_depreciation_rate(self, args, on_validate=False): if isinstance(args, str): @@ -779,6 +802,22 @@ def get_depreciation_rate(self, args, on_validate=False): return flt((100 * (1 - depreciation_rate)), float_precision) +def has_gl_entries(doctype, docname, target_account): + gl_entry = frappe.qb.DocType("GL Entry") + gl_entries = ( + frappe.qb.from_(gl_entry) + .select(gl_entry.account) + .where( + (gl_entry.voucher_type == doctype) + & (gl_entry.voucher_no == docname) + & (gl_entry.debit != 0) + & (gl_entry.account == target_account) + ) + .run(as_dict=True) + ) + return len(gl_entries) > 0 + + def update_maintenance_status(): assets = frappe.get_all( "Asset", filters={"docstatus": 1, "maintenance_required": 1, "disposal_date": ("is", "not set")} diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 47c80d060e29..b38bd952bca7 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -469,13 +469,24 @@ def get_gl_entries(self, warehouse_account=None, default_expense_account=None, d self.get_gl_entries_for_consumed_asset_items(gl_entries, target_account, target_against, precision) self.get_gl_entries_for_consumed_service_items(gl_entries, target_account, target_against, precision) - self.get_gl_entries_for_target_item(gl_entries, target_against, precision) + self.get_gl_entries_for_target_item(gl_entries, target_account, target_against, precision) return gl_entries def get_target_account(self): if self.target_is_fixed_asset: - return self.target_fixed_asset_account + from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled + + asset_category = frappe.get_cached_value("Asset", self.target_asset, "asset_category") + if is_cwip_accounting_enabled(asset_category): + target_account = get_asset_category_account( + "capital_work_in_progress_account", + asset_category=asset_category, + company=self.company, + ) + return target_account if target_account else self.target_fixed_asset_account + else: + return self.target_fixed_asset_account else: return self.warehouse_account[self.target_warehouse]["account"] @@ -563,13 +574,13 @@ def get_gl_entries_for_consumed_service_items( ) ) - def get_gl_entries_for_target_item(self, gl_entries, target_against, precision): + def get_gl_entries_for_target_item(self, gl_entries, target_account, target_against, precision): if self.target_is_fixed_asset: # Capitalization gl_entries.append( self.get_gl_dict( { - "account": self.target_fixed_asset_account, + "account": target_account, "against": ", ".join(target_against), "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "debit": flt(self.total_value, precision), diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 5508bdcbef2a..37b925528871 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -187,9 +187,10 @@ def test_capitalization_with_periodical_inventory(self): # Test General Ledger Entries default_expense_account = frappe.db.get_value("Company", company, "default_expense_account") expected_gle = { - "_Test Fixed Asset - _TC": 3000, - "Expenses Included In Asset Valuation - _TC": -1000, - default_expense_account: -2000, + "_Test Fixed Asset - _TC": -100000.0, + default_expense_account: -2000.0, + "CWIP Account - _TC": 103000.0, + "Expenses Included In Asset Valuation - _TC": -1000.0, } actual_gle = get_actual_gle_dict(asset_capitalization.name) @@ -424,7 +425,7 @@ def test_capitalize_only_service_item(self): self.assertEqual(target_asset.purchase_amount, total_amount) expected_gle = { - "_Test Fixed Asset - _TC": 1000.0, + "CWIP Account - _TC": 1000.0, "Expenses Included In Asset Valuation - _TC": -1000.0, } From a35ce12d6000152feef4e87a15046d63a7b85d7e Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 30 Aug 2024 16:51:26 +0530 Subject: [PATCH 07/25] fix: added app permission check for apps page (cherry picked from commit e8f8fb8a8f68f365a57f870697a5c81933d11c2c) --- erpnext/__init__.py | 11 +++++++++++ erpnext/hooks.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 4a9236066cce..40fde01861b3 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,6 +2,7 @@ import inspect import frappe +from frappe.utils.user import is_website_user __version__ = "15.28.2" @@ -149,3 +150,13 @@ def caller(*args, **kwargs): return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs) return caller + + +def check_app_permission(): + if frappe.session.user == "Administrator": + return True + + if is_website_user(): + return False + + return True \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 448d0d2a57bb..038f58bfcb91 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -16,7 +16,7 @@ "logo": "/assets/erpnext/images/erpnext-logo-blue.png", "title": "ERPNext", "route": "/app/home", - # "has_permission": "erpnext.api.permission.has_app_permission" + "has_permission": "erpnext.check_app_permission" } ] From 30f034555b7b677244dc96b1a6e95d6004dbeba5 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 30 Aug 2024 16:57:17 +0530 Subject: [PATCH 08/25] chore: linter fix (cherry picked from commit 1d9ed27a89fcbe77c86b8fc18940e26fc3b255ac) --- erpnext/__init__.py | 2 +- erpnext/hooks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 40fde01861b3..70fd15175cbe 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -159,4 +159,4 @@ def check_app_permission(): if is_website_user(): return False - return True \ No newline at end of file + return True diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 038f58bfcb91..1377288a06e4 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -16,7 +16,7 @@ "logo": "/assets/erpnext/images/erpnext-logo-blue.png", "title": "ERPNext", "route": "/app/home", - "has_permission": "erpnext.check_app_permission" + "has_permission": "erpnext.check_app_permission", } ] From 6c8e0fd1fbefca5e69be899ce317ecb10a6c10e7 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Mon, 2 Sep 2024 10:40:31 +0530 Subject: [PATCH 09/25] fix: update develop_version in hooks (cherry picked from commit 01b345e046501e273b8405a04b33759e4a3dddbb) --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 1377288a06e4..bd367d6d95e7 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -20,7 +20,7 @@ } ] -develop_version = "14.x.x-develop" +develop_version = "15.x.x-develop" app_include_js = "erpnext.bundle.js" app_include_css = "erpnext.bundle.css" From fee2255661e6a930a627acca9faa271f7b30c112 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:29:30 +0530 Subject: [PATCH 10/25] fix: validate component quantity according to BOM (backport #43011) (#43014) * fix: validate component quantity according to BOM (#43011) (cherry picked from commit f3b91d4d62f6f5748d5eb3e0aa7739899057a3db) # Conflicts: # erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- .../manufacturing_settings.json | 59 +++++++++++++++---- .../manufacturing_settings.py | 9 ++- .../doctype/work_order/test_work_order.py | 53 +++++++++++++++++ .../js/utils/serial_no_batch_selector.js | 29 +++++++++ .../stock/doctype/stock_entry/stock_entry.py | 29 +++++++++ 5 files changed, 165 insertions(+), 14 deletions(-) diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index 63e3fa3e9ff2..26cbc03eeb2b 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -5,18 +5,17 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ + "bom_and_work_order_tab", "raw_materials_consumption_section", "material_consumption", "get_rm_cost_from_consumption_entry", "column_break_3", "backflush_raw_materials_based_on", - "capacity_planning", - "disable_capacity_planning", - "allow_overtime", - "allow_production_on_holidays", - "column_break_5", - "capacity_planning_for_days", - "mins_between_operations", + "validate_components_quantities_per_bom", + "bom_section", + "update_bom_costs_automatically", + "column_break_lhyt", + "manufacture_sub_assembly_in_operation", "section_break_6", "default_wip_warehouse", "default_fg_warehouse", @@ -30,8 +29,14 @@ "add_corrective_operation_cost_in_finished_good_valuation", "column_break_24", "job_card_excess_transfer", + "capacity_planning", + "disable_capacity_planning", + "allow_overtime", + "allow_production_on_holidays", + "column_break_5", + "capacity_planning_for_days", + "mins_between_operations", "other_settings_section", - "update_bom_costs_automatically", "set_op_cost_and_scrape_from_sub_assemblies", "column_break_23", "make_serial_no_batch_from_work_order" @@ -149,7 +154,7 @@ { "fieldname": "raw_materials_consumption_section", "fieldtype": "Section Break", - "label": "Raw Materials Consumption" + "label": "Raw Materials Consumption " }, { "fieldname": "column_break_16", @@ -183,8 +188,8 @@ }, { "fieldname": "job_card_section", - "fieldtype": "Section Break", - "label": "Job Card" + "fieldtype": "Tab Break", + "label": "Job Card and Capacity Planning" }, { "fieldname": "column_break_24", @@ -210,13 +215,41 @@ "fieldname": "get_rm_cost_from_consumption_entry", "fieldtype": "Check", "label": "Get Raw Materials Cost from Consumption Entry" + }, + { + "fieldname": "bom_and_work_order_tab", + "fieldtype": "Tab Break", + "label": "BOM and Production" + }, + { + "fieldname": "bom_section", + "fieldtype": "Section Break", + "label": "BOM" + }, + { + "fieldname": "column_break_lhyt", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "If enabled then system will manufacture Sub-assembly against the Job Card (operation).", + "fieldname": "manufacture_sub_assembly_in_operation", + "fieldtype": "Check", + "label": "Manufacture Sub-assembly in Operation" + }, + { + "default": "0", + "depends_on": "eval:doc.backflush_raw_materials_based_on == \"BOM\"", + "fieldname": "validate_components_quantities_per_bom", + "fieldtype": "Check", + "label": "Validate Components Quantities Per BOM" } ], "icon": "icon-wrench", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-02-08 19:00:37.561244", + "modified": "2024-09-02 12:12:03.132567", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing Settings", @@ -234,4 +267,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py index 84dbce2b83f0..cb2916c8ac59 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py @@ -29,15 +29,22 @@ class ManufacturingSettings(Document): get_rm_cost_from_consumption_entry: DF.Check job_card_excess_transfer: DF.Check make_serial_no_batch_from_work_order: DF.Check + manufacture_sub_assembly_in_operation: DF.Check material_consumption: DF.Check mins_between_operations: DF.Int overproduction_percentage_for_sales_order: DF.Percent overproduction_percentage_for_work_order: DF.Percent set_op_cost_and_scrape_from_sub_assemblies: DF.Check update_bom_costs_automatically: DF.Check + validate_components_quantities_per_bom: DF.Check # end: auto-generated types - pass + def before_save(self): + self.reset_values() + + def reset_values(self): + if self.backflush_raw_materials_based_on != "BOM" and self.validate_components_quantities_per_bom: + self.validate_components_quantities_per_bom = 0 def get_mins_between_operations(): diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 627e5262dc9d..ebc400979b64 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2102,6 +2102,59 @@ def test_disassemby_order(self): stock_entry.submit() + def test_components_qty_for_bom_based_manufacture_entry(self): + frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM") + frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 1) + + fg_item = "Test FG Item For Component Validation" + source_warehouse = "Stores - _TC" + raw_materials = ["Test Component Validation RM Item 1", "Test Component Validation RM Item 2"] + + make_item(fg_item, {"is_stock_item": 1}) + for item in raw_materials: + make_item(item, {"is_stock_item": 1}) + test_stock_entry.make_stock_entry( + item_code=item, + target=source_warehouse, + qty=10, + basic_rate=100, + ) + + make_bom(item=fg_item, source_warehouse=source_warehouse, raw_materials=raw_materials) + + wo = make_wo_order_test_record( + item=fg_item, + qty=10, + source_warehouse=source_warehouse, + ) + + transfer_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10)) + transfer_entry.save() + for row in transfer_entry.items: + row.qty = 5 + + self.assertRaises(frappe.ValidationError, transfer_entry.save) + + transfer_entry.reload() + for row in transfer_entry.items: + self.assertEqual(row.qty, 10) + + transfer_entry.submit() + + manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10)) + manufacture_entry.save() + for row in manufacture_entry.items: + if not row.s_warehouse: + continue + + row.qty = 5 + + self.assertRaises(frappe.ValidationError, manufacture_entry.save) + manufacture_entry.reload() + manufacture_entry.submit() + + frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 0) + def make_operation(**kwargs): kwargs = frappe._dict(kwargs) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 78efb46f4c34..2ddfc24adb1c 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -368,8 +368,28 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { ]; } + get_batch_qty(batch_no, callback) { + let warehouse = this.item.s_warehouse || this.item.t_warehouse || this.item.warehouse; + frappe.call({ + method: "erpnext.stock.doctype.batch.batch.get_batch_qty", + args: { + batch_no: batch_no, + warehouse: warehouse, + item_code: this.item.item_code, + posting_date: this.frm.doc.posting_date, + posting_time: this.frm.doc.posting_time, + }, + callback: (r) => { + if (r.message) { + callback(flt(r.message)); + } + }, + }); + } + get_dialog_table_fields() { let fields = []; + let me = this; if (this.item.has_serial_no) { fields.push({ @@ -395,6 +415,15 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { fieldname: "batch_no", label: __("Batch No"), in_list_view: 1, + change() { + let doc = this.doc; + if (!doc.qty && me.item.type_of_transaction === "Outward") { + me.get_batch_qty(doc.batch_no, (qty) => { + doc.qty = qty; + this.grid.set_value("qty", qty, doc); + }); + } + }, get_query: () => { let is_inward = false; if ( diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 0ef54c785557..4ee4c6b2930a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -232,6 +232,7 @@ def validate(self): self.validate_serialized_batch() self.calculate_rate_and_amount() self.validate_putaway_capacity() + self.validate_component_quantities() if not self.get("purpose") == "Manufacture": # ignore scrap item wh difference and empty source/target wh @@ -747,6 +748,34 @@ def set_actual_qty(self): title=_("Insufficient Stock"), ) + def validate_component_quantities(self): + if self.purpose not in ["Manufacture", "Material Transfer for Manufacture"]: + return + + if not frappe.db.get_single_value("Manufacturing Settings", "validate_components_quantities_per_bom"): + return + + if not self.fg_completed_qty: + return + + raw_materials = self.get_bom_raw_materials(self.fg_completed_qty) + + precision = frappe.get_precision("Stock Entry Detail", "qty") + for row in self.items: + if not row.s_warehouse: + continue + + if details := raw_materials.get(row.item_code): + if flt(details.get("qty"), precision) != flt(row.qty, precision): + frappe.throw( + _("For the item {0}, the quantity should be {1} according to the BOM {2}.").format( + frappe.bold(row.item_code), + flt(details.get("qty"), precision), + get_link_to_form("BOM", self.bom_no), + ), + title=_("Incorrect Component Quantity"), + ) + @frappe.whitelist() def get_stock_and_rate(self): """ From 88e5ed79988596adad658d9571b9b77137468a59 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:50:28 +0530 Subject: [PATCH 11/25] chore: test case failing issue (cherry picked from commit 0bdffdfa9835780149a09d06e498386cc6f6a7ff) --- .../test_asset_capitalization.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 37b925528871..86293fac765d 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -31,6 +31,12 @@ def setUp(self): def test_capitalization_with_perpetual_inventory(self): company = "_Test Company with perpetual inventory" set_depreciation_settings_in_company(company=company) + name = frappe.db.get_value( + "Asset Category Account", + filters={"parent": "Computers", "company_name": company}, + fieldname=["name"], + ) + frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "") # Variables consumed_asset_value = 100000 @@ -215,6 +221,12 @@ def test_capitalization_with_periodical_inventory(self): def test_capitalization_with_wip_composite_asset(self): company = "_Test Company with perpetual inventory" set_depreciation_settings_in_company(company=company) + name = frappe.db.get_value( + "Asset Category Account", + filters={"parent": "Computers", "company_name": company}, + fieldname=["name"], + ) + frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "") stock_rate = 1000 stock_qty = 2 From f0e3fb466a7deeb438b0ff7d9e932dea2298951d Mon Sep 17 00:00:00 2001 From: vishnu Date: Wed, 28 Aug 2024 15:10:08 +0000 Subject: [PATCH 12/25] fix: retain date filter when redirecting in Profit and Loss report (cherry picked from commit bb29fc4c3d9e55a5dff14529b37d1bc78b0fb3cc) --- erpnext/public/js/financial_statements.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index bd8b70af02e3..31abef62700b 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -114,16 +114,17 @@ erpnext.financial_statements = { onload: function (report) { // dropdown for links to other financial statements erpnext.financial_statements.filters = get_filters(); + var filters = report.get_values(); - let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today()); - - frappe.model.with_doc("Fiscal Year", fiscal_year, function (r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - frappe.query_report.set_filter_value({ - period_start_date: fy.year_start_date, - period_end_date: fy.year_end_date, + if (!filters.period_start_date || !filters.period_end_date) { + frappe.model.with_doc("Fiscal Year", fiscal_year, function (r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + period_start_date: fy.year_start_date, + period_end_date: fy.year_end_date, + }); }); - }); + } if (report.page) { const views_menu = report.page.add_custom_button_group(__("Financial Statements")); From 4d7c0c004a2677be6fad1154ff1ff80eadfe7482 Mon Sep 17 00:00:00 2001 From: vishnu Date: Wed, 28 Aug 2024 15:12:22 +0000 Subject: [PATCH 13/25] fix: indentation (cherry picked from commit 598e9c1390d756ed0f1f0afa80fa13efe94af820) --- erpnext/public/js/financial_statements.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 31abef62700b..bd80c2548897 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -114,6 +114,8 @@ erpnext.financial_statements = { onload: function (report) { // dropdown for links to other financial statements erpnext.financial_statements.filters = get_filters(); + + let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today()); var filters = report.get_values(); if (!filters.period_start_date || !filters.period_end_date) { From b99cdb5be7be2d31acadc252404b792ba1d31561 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:54:42 +0530 Subject: [PATCH 14/25] fix: company accounts setup_queries (cherry picked from commit 80ace72541df3eb7b4e4daa6377f945308078751) --- erpnext/setup/doctype/company/company.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 939eaf257122..f14057a272e8 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -20,7 +20,6 @@ frappe.ui.form.on("Company", { }, setup: function (frm) { frm.__rename_queue = "long"; - erpnext.company.setup_queries(frm); frm.set_query("parent_company", function () { return { @@ -81,6 +80,8 @@ frappe.ui.form.on("Company", { }, refresh: function (frm) { + erpnext.company.setup_queries(frm); + frm.toggle_display("address_html", !frm.is_new()); if (!frm.is_new()) { From 7d6984c87331d27705f9a92be13d84ed46afa946 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 3 Sep 2024 15:54:39 +0530 Subject: [PATCH 15/25] fix: typerror on default_currency (cherry picked from commit 4a7cc4da8743599755f13588cfa25f9d58ad4998) --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 5c00aff587d5..b50dcd217e7c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -658,7 +658,7 @@ frappe.ui.form.on("Payment Entry", { frm.set_value("source_exchange_rate", 1); } else if (frm.doc.paid_from) { if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) { - let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; + let company_currency = frappe.get_doc(":Company", frm.doc.company)?.default_currency; frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", args: { From daa75eea00d46c565380c4303eba1e939fe3579a Mon Sep 17 00:00:00 2001 From: Lakshit Jain <108322669+ljain112@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:56:04 +0530 Subject: [PATCH 16/25] fix: adjust price insertion logic for internal suppliers/customers (#42988) * fix: adjust price insertion logic for internal suppliers/customers * refactor: correct indentation, specify conditions within function * fix: typo --------- Co-authored-by: Smit Vora (cherry picked from commit 38f925b376d4dc95c99c19b11a27ade2db6dc81f) --- erpnext/stock/get_item_details.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 6485c5f60228..5cfa306066d8 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -807,14 +807,10 @@ def get_price_list_rate(args, item_doc, out=None): if price_list_rate is None or frappe.db.get_single_value( "Stock Settings", "update_existing_price_list_rate" ): - if args.get("is_internal_supplier") or args.get("is_internal_customer"): - return out + insert_item_price(args) - if args.price_list and args.rate: - insert_item_price(args) - - if not price_list_rate: - return out + if price_list_rate is None: + return out out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) / flt(args.conversion_rate) @@ -835,6 +831,14 @@ def get_price_list_rate(args, item_doc, out=None): def insert_item_price(args): """Insert Item Price if Price List and Price List Rate are specified and currency is the same""" + if ( + not args.price_list + or not args.rate + or args.get("is_internal_supplier") + or args.get("is_internal_customer") + ): + return + if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency and cint( frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing") ): From 1121c6663f325502e867eea76a144dfe55c522b3 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 26 Aug 2024 23:33:10 +0530 Subject: [PATCH 17/25] fix: link Purchase Invoice and Receipt Items to Asset --- .../purchase_invoice/purchase_invoice.py | 6 +- erpnext/assets/doctype/asset/asset.js | 5 ++ erpnext/assets/doctype/asset/asset.json | 24 +++++-- erpnext/assets/doctype/asset/asset.py | 2 + erpnext/controllers/buying_controller.py | 2 + erpnext/patches.txt | 1 + .../v15_0/link_purchase_item_to_asset_doc.py | 68 +++++++++++++++++++ .../purchase_receipt/purchase_receipt.py | 6 +- 8 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 78fc02999efc..de99c35e27e9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1262,7 +1262,11 @@ def make_provisional_gl_entry(self, gl_entries, item): def update_gross_purchase_amount_for_linked_assets(self, item): assets = frappe.db.get_all( "Asset", - filters={"purchase_invoice": self.name, "item_code": item.item_code}, + filters={ + "purchase_invoice": self.name, + "item_code": item.item_code, + "purchase_invoice_item": ("in", [item.name, ""]), + }, fields=["name", "asset_quantity"], ) for asset in assets: diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 145e2ba3b3ea..2477e443c4ae 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -670,6 +670,11 @@ frappe.ui.form.on("Asset", { if (item.asset_location) { frm.set_value("location", item.asset_location); } + if (doctype === "Purchase Receipt") { + frm.set_value("purchase_receipt_item", item.name); + } else if (doctype === "Purchase Invoice") { + frm.set_value("purchase_invoice_item", item.name); + } }); }, diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index e408cc24d1eb..55932e9b7875 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -33,14 +33,16 @@ "dimension_col_break", "purchase_details_section", "purchase_receipt", + "purchase_receipt_item", "purchase_invoice", + "purchase_invoice_item", + "purchase_date", "available_for_use_date", - "total_asset_cost", - "additional_asset_cost", "column_break_23", "gross_purchase_amount", "asset_quantity", - "purchase_date", + "additional_asset_cost", + "total_asset_cost", "section_break_23", "calculate_depreciation", "column_break_33", @@ -536,6 +538,20 @@ "fieldname": "opening_number_of_booked_depreciations", "fieldtype": "Int", "label": "Opening Number of Booked Depreciations" + }, + { + "fieldname": "purchase_receipt_item", + "fieldtype": "Link", + "hidden": 1, + "label": "Purchase Receipt Item", + "options": "Purchase Receipt Item" + }, + { + "fieldname": "purchase_invoice_item", + "fieldtype": "Link", + "hidden": 1, + "label": "Purchase Invoice Item", + "options": "Purchase Invoice Item" } ], "idx": 72, @@ -579,7 +595,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2024-08-01 16:39:09.340973", + "modified": "2024-08-26 23:28:29.095139", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 8ede2670d386..f6846ea3cee7 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -94,7 +94,9 @@ class Asset(AccountsController): purchase_amount: DF.Currency purchase_date: DF.Date | None purchase_invoice: DF.Link | None + purchase_invoice_item: DF.Link | None purchase_receipt: DF.Link | None + purchase_receipt_item: DF.Link | None split_from: DF.Link | None status: DF.Literal[ "Draft", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index f797374d64e9..31b6f391ba40 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -822,6 +822,8 @@ def make_asset(self, row, is_grouped_asset=False): "asset_quantity": asset_quantity, "purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None, "purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None, + "purchase_receipt_item": row.name if self.doctype == "Purchase Receipt" else None, + "purchase_invoice_item": row.name if self.doctype == "Purchase Invoice" else None, } ) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index feaac9480991..2a97061a6a28 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -374,3 +374,4 @@ erpnext.patches.v15_0.do_not_use_batchwise_valuation erpnext.patches.v15_0.drop_index_posting_datetime_from_sle erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1 erpnext.patches.v15_0.set_standard_stock_entry_type +erpnext.patches.v15_0.link_purchase_item_to_asset_doc diff --git a/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py b/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py new file mode 100644 index 000000000000..e4311a8879dc --- /dev/null +++ b/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py @@ -0,0 +1,68 @@ +import frappe + + +def execute(): + if frappe.db.has_column("Asset", "purchase_invoice_item") and frappe.db.has_column( + "Asset", "purchase_receipt_item" + ): + # Get all assets with their related Purchase Invoice and Purchase Receipt + assets = frappe.get_all( + "Asset", + filters={"docstatus": 0}, + fields=[ + "name", + "item_code", + "purchase_invoice", + "purchase_receipt", + "gross_purchase_amount", + "asset_quantity", + "purchase_invoice_item", + "purchase_receipt_item", + ], + ) + + for asset in assets: + # Get Purchase Invoice Items + if asset.purchase_invoice and not asset.purchase_invoice_item: + purchase_invoice_item = get_linked_item( + "Purchase Invoice Item", + asset.purchase_invoice, + asset.item_code, + asset.gross_purchase_amount, + asset.asset_quantity, + ) + frappe.db.set_value("Asset", asset.name, "purchase_invoice_item", purchase_invoice_item) + + # Get Purchase Receipt Items + if asset.purchase_receipt and not asset.purchase_receipt_item: + purchase_receipt_item = get_linked_item( + "Purchase Receipt Item", + asset.purchase_receipt, + asset.item_code, + asset.gross_purchase_amount, + asset.asset_quantity, + ) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_item", purchase_receipt_item) + + +def get_linked_item(doctype, parent, item_code, amount, quantity): + items = frappe.get_all( + doctype, + filters={ + "parenttype": doctype.replace(" Item", ""), + "parent": parent, + "item_code": item_code, + }, + fields=["name", "amount", "qty", "landed_cost_voucher_amount"], + ) + if len(items) == 1: + # If only one item exists, return it directly + return items[0].name + + for item in items: + landed_cost = item.get("landed_cost_voucher_amount", 0) + if item.amount + landed_cost == amount and item.qty == quantity: + return item.name + + # If no exact match, return None + return None diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index afcb00141a15..42b70a08222d 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -837,7 +837,11 @@ def make_tax_gl_entries(self, gl_entries, via_landed_cost_voucher=False): def update_assets(self, item, valuation_rate): assets = frappe.db.get_all( "Asset", - filters={"purchase_receipt": self.name, "item_code": item.item_code}, + filters={ + "purchase_receipt": self.name, + "item_code": item.item_code, + "purchase_receipt_item": ("in", [item.name, ""]), + }, fields=["name", "asset_quantity"], ) From 3bb186736d8adbf83cdb1d75bb4f5f9db8af8532 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:10:12 +0530 Subject: [PATCH 18/25] fix: improve asset item matching logic --- .../patches/v15_0/link_purchase_item_to_asset_doc.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py b/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py index e4311a8879dc..e5386c1de0ac 100644 --- a/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py +++ b/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py @@ -53,7 +53,7 @@ def get_linked_item(doctype, parent, item_code, amount, quantity): "parent": parent, "item_code": item_code, }, - fields=["name", "amount", "qty", "landed_cost_voucher_amount"], + fields=["name", "rate", "amount", "qty", "landed_cost_voucher_amount"], ) if len(items) == 1: # If only one item exists, return it directly @@ -61,8 +61,13 @@ def get_linked_item(doctype, parent, item_code, amount, quantity): for item in items: landed_cost = item.get("landed_cost_voucher_amount", 0) - if item.amount + landed_cost == amount and item.qty == quantity: - return item.name + # Check if the asset is grouped + if quantity > 1: + if item.amount + landed_cost == amount and item.qty == quantity: + return item.name + else: + if item.rate + landed_cost == amount: + return item.name # If no exact match, return None return None From 957eabf53edc962393fa6c03acfebd1bcd587ff7 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Fri, 30 Aug 2024 00:30:47 +0530 Subject: [PATCH 19/25] chore: resolved linter warnings with #nosemgrep --- erpnext/stock/stock_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 2aacd86e3d24..07d401de8c16 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1532,7 +1532,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc operator = "<=" voucher_condition = f"and creation < '{creation}'" - sle = frappe.db.sql( + sle = frappe.db.sql( # nosemgrep f""" select *, posting_datetime as "timestamp" from `tabStock Ledger Entry` @@ -1629,6 +1629,7 @@ def get_stock_ledger_entries( if extra_cond: conditions += f"{extra_cond}" + # nosemgrep return frappe.db.sql( """ select *, posting_datetime as "timestamp" From 193d7981eabeea0b57c83255e0a76fa01fc20b43 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Fri, 30 Aug 2024 00:40:01 +0530 Subject: [PATCH 20/25] chore: linters/semgrep check --- erpnext/stock/stock_ledger.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 07d401de8c16..8f9f39c7a7b2 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1745,7 +1745,7 @@ def get_valuation_rate( return batch_obj.get_incoming_rate() # Get valuation rate from last sle for the same item and warehouse - if last_valuation_rate := frappe.db.sql( + if last_valuation_rate := frappe.db.sql( # nosemgrep """select valuation_rate from `tabStock Ledger Entry` force index (item_warehouse) where @@ -1825,7 +1825,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): detail = next_stock_reco_detail[0] datetime_limit_condition = get_datetime_limit_condition(detail) - frappe.db.sql( + frappe.db.sql( # nosemgrep f""" update `tabStock Ledger Entry` set qty_after_transaction = qty_after_transaction + {qty_shift} @@ -1992,7 +1992,7 @@ def is_negative_with_precision(neg_sle, is_batch=False): def get_future_sle_with_negative_qty(args): - return frappe.db.sql( + return frappe.db.sql( # nosemgrep """ select qty_after_transaction, posting_date, posting_time, @@ -2014,7 +2014,7 @@ def get_future_sle_with_negative_qty(args): def get_future_sle_with_negative_batch_qty(args): - return frappe.db.sql( + return frappe.db.sql( # nosemgrep """ with batch_ledger as ( select From e185a06a1519214269fa3941e8d38c2609001321 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:48:41 +0530 Subject: [PATCH 21/25] refactor: rename to in SLE query functions --- erpnext/stock/stock_ledger.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 8f9f39c7a7b2..556aac09abd1 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1991,7 +1991,7 @@ def is_negative_with_precision(neg_sle, is_batch=False): return qty_deficit < 0 and abs(qty_deficit) > 0.0001 -def get_future_sle_with_negative_qty(args): +def get_future_sle_with_negative_qty(sle_args): return frappe.db.sql( # nosemgrep """ select @@ -2008,12 +2008,12 @@ def get_future_sle_with_negative_qty(args): order by posting_date asc, posting_time asc limit 1 """, - args, + sle_args, as_dict=1, ) -def get_future_sle_with_negative_batch_qty(args): +def get_future_sle_with_negative_batch_qty(sle_args): return frappe.db.sql( # nosemgrep """ with batch_ledger as ( @@ -2034,7 +2034,7 @@ def get_future_sle_with_negative_batch_qty(args): and posting_datetime >= %(posting_datetime)s limit 1 """, - args, + sle_args, as_dict=1, ) From f5a4ec129be3233bd98832900531414d31dd86b1 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 3 Sep 2024 03:35:19 +0530 Subject: [PATCH 22/25] chore: patch correction --- erpnext/patches.txt | 1 + erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2a97061a6a28..637ce3dbed17 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -374,4 +374,5 @@ erpnext.patches.v15_0.do_not_use_batchwise_valuation erpnext.patches.v15_0.drop_index_posting_datetime_from_sle erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1 erpnext.patches.v15_0.set_standard_stock_entry_type +erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v15_0.link_purchase_item_to_asset_doc diff --git a/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py b/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py index e5386c1de0ac..662858e52a45 100644 --- a/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py +++ b/erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py @@ -65,9 +65,10 @@ def get_linked_item(doctype, parent, item_code, amount, quantity): if quantity > 1: if item.amount + landed_cost == amount and item.qty == quantity: return item.name + elif item.qty == quantity: + return item.name else: - if item.rate + landed_cost == amount: + if item.rate + (landed_cost / item.qty) == amount: return item.name - # If no exact match, return None - return None + return items[0].name if items else None From 5d5ec2ab7c2ecee1e616ca3c54e66b9f48243806 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Wed, 4 Sep 2024 03:08:30 +0530 Subject: [PATCH 23/25] chore: resolve test failing --- erpnext/patches.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 637ce3dbed17..2a97061a6a28 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -374,5 +374,4 @@ erpnext.patches.v15_0.do_not_use_batchwise_valuation erpnext.patches.v15_0.drop_index_posting_datetime_from_sle erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1 erpnext.patches.v15_0.set_standard_stock_entry_type -erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v15_0.link_purchase_item_to_asset_doc From 56dad7d365a7debf98b8739352002adb3294bc79 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:00:33 +0530 Subject: [PATCH 24/25] fix: disabled batches showing in the list (backport #43024) (#43069) fix: disabled batches showing in the list (#43024) (cherry picked from commit c13a147df1057db8a5fac86b1a81dfd970f570bc) Co-authored-by: rohitwaghchaure --- erpnext/controllers/queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index a07a00dd03ed..919bbb477f46 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -366,7 +366,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): def get_empty_batches(filters, start, page_len, filtered_batches=None, txt=None): - query_filter = {"item": filters.get("item_code")} + query_filter = {"item": filters.get("item_code"), "disabled": 0} if txt: query_filter["name"] = ("like", f"%{txt}%") From d2b200266461b9eaafab2f8e349189f7e017022d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:00:47 +0530 Subject: [PATCH 25/25] fix: auto reorder material request mail issue (backport #43066) (#43068) fix: auto reorder material request mail issue (#43066) fix: auto reorder matreial request mail issue (cherry picked from commit a8055a6da975a4a59ce17052d31d7672f3b46ba7) Co-authored-by: rohitwaghchaure --- erpnext/stock/reorder_item.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index 7bc8b71917c0..ed87906731e4 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -354,9 +354,14 @@ def get_email_list(company): def get_comapny_wise_users(company): + companies = [company] + + if parent_company := frappe.db.get_value("Company", company, "parent_company"): + companies.append(parent_company) + users = frappe.get_all( "User Permission", - filters={"allow": "Company", "for_value": company, "apply_to_all_doctypes": 1}, + filters={"allow": "Company", "for_value": ("in", companies), "apply_to_all_doctypes": 1}, fields=["user"], )