From 47f06dc180ee434ce21eea9587fa43bb20079215 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:09:17 +0530 Subject: [PATCH 01/25] fix: Cannot read properties of undefined (reading 'price_list_rate') (backport #43376) (#43377) fix: Cannot read properties of undefined (reading 'price_list_rate') (#43376) (cherry picked from commit a63dca098452e46239878eb90d18a8f4ffeb3d16) Co-authored-by: rohitwaghchaure --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 67c61118952..204c09299ad 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1102,7 +1102,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe apply_discount_on_item(doc, cdt, cdn, field) { var item = frappe.get_doc(cdt, cdn); - if(!item.price_list_rate) { + if(!item?.price_list_rate) { item[field] = 0.0; } else { this.price_list_rate(doc, cdt, cdn); From 74c880c232d6fa1cf4f0d53c5faad16f7e809c16 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 12:06:27 +0530 Subject: [PATCH 02/25] fix: serial and batch no selector (backport #43387) (#43390) fix: serial and batch no selector (#43387) (cherry picked from commit e4e96d2a442b771ff0336be0b52afe359831b919) Co-authored-by: rohitwaghchaure --- erpnext/public/js/controllers/buying.js | 18 +- .../js/utils/serial_no_batch_selector.js | 164 +++++++++++------- .../serial_and_batch_bundle.py | 23 ++- 3 files changed, 132 insertions(+), 73 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index b5a8b757706..1d0680de861 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -342,12 +342,15 @@ erpnext.buying = { add_serial_batch_bundle(doc, cdt, cdn) { let item = locals[cdt][cdn]; let me = this; + let fields = ["has_batch_no", "has_serial_no"]; - frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) + frappe.db.get_value("Item", item.item_code, fields) .then((r) => { if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { - item.has_serial_no = r.message.has_serial_no; - item.has_batch_no = r.message.has_batch_no; + fields.forEach((field) => { + item[field] = r.message[field]; + }); + item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward"; item.is_rejected = false; @@ -380,12 +383,15 @@ erpnext.buying = { add_serial_batch_for_rejected_qty(doc, cdt, cdn) { let item = locals[cdt][cdn]; let me = this; + let fields = ["has_batch_no", "has_serial_no"]; - frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) + frappe.db.get_value("Item", item.item_code, fields) .then((r) => { if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { - item.has_serial_no = r.message.has_serial_no; - item.has_batch_no = r.message.has_batch_no; + fields.forEach((field) => { + item[field] = r.message[field]; + }); + item.type_of_transaction = item.rejected_qty > 0 ? "Inward" : "Outward"; item.is_rejected = true; diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 2ddfc24adb1..46ff6366de8 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -16,7 +16,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { let label = this.item?.has_serial_no ? __("Serial Nos") : __("Batch Nos"); let primary_label = this.bundle ? __("Update") : __("Add"); - if (this.item?.has_serial_no && this.item?.batch_no) { + if (this.item?.has_serial_no && this.item?.has_batch_no) { label = __("Serial Nos / Batch Nos"); } @@ -24,6 +24,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { this.dialog = new frappe.ui.Dialog({ title: this.item?.title || primary_label, + size: "large", fields: this.get_dialog_fields(), primary_action_label: primary_label, primary_action: () => this.update_bundle_entries(), @@ -164,12 +165,14 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { fields.push({ fieldtype: "Section Break", + depends_on: "eval:doc.enter_manually !== 1 || doc.entries?.length > 0", }); fields.push({ fieldname: "entries", fieldtype: "Table", allow_bulk_edit: true, + depends_on: "eval:doc.enter_manually !== 1 || doc.entries?.length > 0", data: [], fields: this.get_dialog_table_fields(), }); @@ -178,6 +181,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } get_attach_field() { + let me = this; let label = this.item?.has_serial_no ? __("Serial Nos") : __("Batch Nos"); let primary_label = this.bundle ? __("Update") : __("Add"); @@ -185,66 +189,41 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { label = __("Serial Nos / Batch Nos"); } - let fields = [ - { - fieldtype: "Section Break", - label: __("{0} {1} via CSV File", [primary_label, label]), - }, - ]; - - if (this.item?.has_serial_no) { - fields = [ - ...fields, - { - fieldtype: "Check", - label: __("Import Using CSV file"), - fieldname: "import_using_csv_file", - default: 0, + let fields = []; + if (this.item.has_serial_no) { + fields.push({ + fieldtype: "Check", + label: __("Enter Manually"), + fieldname: "enter_manually", + default: 1, + depends_on: "eval:doc.import_using_csv_file !== 1", + change() { + if (me.dialog.get_value("enter_manually")) { + me.dialog.set_value("import_using_csv_file", 0); + } }, - { - fieldtype: "Section Break", - label: __("{0} {1} Manually", [primary_label, label]), - depends_on: "eval:doc.import_using_csv_file === 0", - }, - { - fieldtype: "Data", - label: __("Enter Serial No Range"), - fieldname: "serial_no_range", - depends_on: "eval:doc.import_using_csv_file === 0", - description: __('Enter "ABC-001::100" for serial nos "ABC-001" to "ABC-100".'), - onchange: () => { - this.set_serial_nos_from_range(); - }, - }, - { - fieldtype: "Small Text", - label: __("Enter Serial Nos"), - fieldname: "upload_serial_nos", - depends_on: "eval:doc.import_using_csv_file === 0", - description: __("Enter each serial no in a new line"), - }, - { - fieldtype: "Column Break", - depends_on: "eval:doc.import_using_csv_file === 0", - }, - { - fieldtype: "Button", - fieldname: "make_serial_nos", - label: __("Create Serial Nos"), - depends_on: "eval:doc.import_using_csv_file === 0", - click: () => { - this.create_serial_nos(); - }, - }, - { - fieldtype: "Section Break", - depends_on: "eval:doc.import_using_csv_file === 1", - }, - ]; + }); } fields = [ ...fields, + { + fieldtype: "Check", + label: __("Import Using CSV file"), + fieldname: "import_using_csv_file", + depends_on: "eval:doc.enter_manually !== 1", + default: !this.item.has_serial_no ? 1 : 0, + change() { + if (me.dialog.get_value("import_using_csv_file")) { + me.dialog.set_value("enter_manually", 0); + } + }, + }, + { + fieldtype: "Section Break", + depends_on: "eval:doc.import_using_csv_file === 1", + label: __("{0} {1} via CSV File", [primary_label, label]), + }, { fieldtype: "Button", fieldname: "download_csv", @@ -262,9 +241,51 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { }, ]; + if (this.item?.has_serial_no) { + fields = [ + ...fields, + { + fieldtype: "Section Break", + label: __("{0} {1} Manually", [primary_label, label]), + depends_on: "eval:doc.enter_manually === 1", + }, + { + fieldtype: "Data", + label: __("Serial No Range"), + fieldname: "serial_no_range", + depends_on: "eval:doc.enter_manually === 1 && !doc.serial_no_series", + description: __('"SN-01::10" for "SN-01" to "SN-10"'), + onchange: () => { + this.set_serial_nos_from_range(); + }, + }, + ]; + } + + if (this.item?.has_serial_no) { + fields = [ + ...fields, + { + fieldtype: "Column Break", + depends_on: "eval:doc.enter_manually === 1", + }, + { + fieldtype: "Small Text", + label: __("Enter Serial Nos"), + fieldname: "upload_serial_nos", + depends_on: "eval:doc.enter_manually === 1", + description: __("Enter each serial no in a new line"), + }, + ]; + } + return fields; } + set_serial_nos_from_series() {} + + set_batch_nos_from_series() {} + set_serial_nos_from_range() { const serial_no_range = this.dialog.get_value("serial_no_range"); @@ -511,6 +532,8 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { scan_barcode_data() { const { scan_serial_no, scan_batch_no } = this.dialog.get_values(); + this.dialog.set_value("enter_manually", 0); + if (scan_serial_no || scan_batch_no) { frappe.call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.is_serial_batch_no_exists", @@ -554,14 +577,13 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { serial_no: scan_serial_no, }, callback: (r) => { - if (r.message) { - this.dialog.fields_dict.entries.df.data.push({ - serial_no: scan_serial_no, - batch_no: r.message, - }); + this.dialog.fields_dict.entries.df.data.push({ + serial_no: scan_serial_no, + batch_no: r.message, + }); - this.dialog.fields_dict.scan_serial_no.set_value(""); - } + this.dialog.fields_dict.scan_serial_no.set_value(""); + this.dialog.fields_dict.entries.grid.refresh(); }, }); } @@ -590,6 +612,12 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { update_bundle_entries() { let entries = this.dialog.get_values().entries; let warehouse = this.dialog.get_value("warehouse"); + let upload_serial_nos = this.dialog.get_value("upload_serial_nos"); + + if (!entries?.length && upload_serial_nos) { + this.create_serial_nos(); + return; + } if ((entries && !entries.length) || !entries) { frappe.throw(__("Please add atleast one Serial No / Batch No")); @@ -610,9 +638,13 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { }, }) .then((r) => { - this.callback && this.callback(r.message); - this.frm.save(); - this.dialog.hide(); + frappe.run_serially([ + () => { + this.callback && this.callback(r.message); + }, + () => this.frm.save(), + () => this.dialog.hide(), + ]); }); } 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 956eb08d9ff..a1944732fb6 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 @@ -1329,6 +1329,15 @@ def create_serial_batch_no_ledgers( } ) + batch_no = None + + if ( + not entries[0].get("batch_no") + and entries[0].get("serial_no") + and frappe.get_cached_value("Item", child_row.item_code, "has_batch_no") + ): + batch_no = get_batch(child_row.item_code) + for row in entries: row = frappe._dict(row) doc.append( @@ -1336,7 +1345,7 @@ def create_serial_batch_no_ledgers( { "qty": (flt(row.qty) or 1.0) * (1 if type_of_transaction == "Inward" else -1), "warehouse": warehouse, - "batch_no": row.batch_no, + "batch_no": row.batch_no or batch_no, "serial_no": row.serial_no, }, ) @@ -1351,6 +1360,18 @@ def create_serial_batch_no_ledgers( return doc +def get_batch(item_code): + from erpnext.stock.doctype.batch.batch import make_batch + + return make_batch( + frappe._dict( + { + "item": item_code, + } + ) + ) + + def get_type_of_transaction(parent_doc, child_row): type_of_transaction = child_row.get("type_of_transaction") if parent_doc.get("doctype") == "Stock Entry": From 1d6f97ad945f8d0225533fb03e6866561316481b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 27 Sep 2024 23:29:39 +0530 Subject: [PATCH 03/25] fix: Ignore transaction deletion check on ledger entry insertion (cherry picked from commit 998f6a92a48b57dbde1d76e848378bd743d0fa03) --- .../transaction_deletion_record.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index c9c3c837ceb..ce3f918f7eb 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -485,8 +485,14 @@ def is_deletion_doc_running(company: str | None = None, err_msg: str | None = No def check_for_running_deletion_job(doc, method=None): # Check if DocType has 'company' field - df = qb.DocType("DocField") - if qb.from_(df).select(df.parent).where((df.fieldname == "company") & (df.parent == doc.doctype)).run(): - is_deletion_doc_running( - doc.company, _("Cannot make any transactions until the deletion job is completed") - ) + if doc.doctype not in ("GL Entry", "Payment Ledger Entry", "Stock Ledger Entry"): + df = qb.DocType("DocField") + if ( + qb.from_(df) + .select(df.parent) + .where((df.fieldname == "company") & (df.parent == doc.doctype)) + .run() + ): + is_deletion_doc_running( + doc.company, _("Cannot make any transactions until the deletion job is completed") + ) From 27cd51e267b3a11ab474ca085b5a0dc6cef87fad Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:49:01 +0530 Subject: [PATCH 04/25] feat: added 'cost of new capitalized asset' column (cherry picked from commit 1eb9cc33fc298c90ef5a04e5de2ee4dec67f941a) --- .../asset_depreciations_and_balances.py | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index 64e6a340464..cdd5baf3240 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -36,8 +36,9 @@ def get_group_by_asset_category_data(filters): + flt(row.cost_of_new_purchase) - flt(row.cost_of_sold_asset) - flt(row.cost_of_scrapped_asset) + - flt(row.cost_of_capitalized_asset) ) - # Update row with corresponding asset data + row.update( next( asset @@ -111,13 +112,24 @@ def get_asset_categories_for_grouped_by_category(filters): end else 0 - end), 0) as cost_of_scrapped_asset + end), 0) as cost_of_scrapped_asset, + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Capitalized" then + a.gross_purchase_amount + else + 0 + end + else + 0 + end), 0) as cost_of_capitalized_asset from `tabAsset` a where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} and not exists( select 1 from `tabAsset Capitalization Asset Item` acai join `tabAsset Capitalization` ac on acai.parent=ac.name where acai.asset = a.name - and ac.posting_date <= %(to_date)s + and ac.posting_date < %(from_date)s and ac.docstatus=1 ) group by a.asset_category @@ -179,13 +191,24 @@ def get_asset_details_for_grouped_by_category(filters): end else 0 - end), 0) as cost_of_scrapped_asset + end), 0) as cost_of_scrapped_asset, + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Capitalized" then + a.gross_purchase_amount + else + 0 + end + else + 0 + end), 0) as cost_of_capitalized_asset from `tabAsset` a where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} and not exists( select 1 from `tabAsset Capitalization Asset Item` acai join `tabAsset Capitalization` ac on acai.parent=ac.name where acai.asset = a.name - and ac.posting_date <= %(to_date)s + and ac.posting_date < %(from_date)s and ac.docstatus=1 ) group by a.name @@ -217,6 +240,7 @@ def get_group_by_asset_data(filters): + flt(row.cost_of_new_purchase) - flt(row.cost_of_sold_asset) - flt(row.cost_of_scrapped_asset) + - flt(row.cost_of_capitalized_asset) ) row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", ""))) @@ -445,6 +469,12 @@ def get_columns(filters): "fieldtype": "Currency", "width": 140, }, + { + "label": _("Cost of New Capitalized Asset"), + "fieldname": "cost_of_capitalized_asset", + "fieldtype": "Currency", + "width": 140, + }, { "label": _("Cost as on") + " " + formatdate(filters.to_date), "fieldname": "cost_as_on_to_date", From a1b6628c41ef965d855cd6890f0919871b4ca58d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 29 Sep 2024 09:46:14 +0530 Subject: [PATCH 05/25] fix: quality inspection creation (backport #43416) (#43417) fix: quality inspection creation (#43416) (cherry picked from commit a594c05296cae58ea546933f834dd3f5b651fd09) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/quality_inspection/quality_inspection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 5dca440adbe..43efcbc8ca6 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -245,6 +245,7 @@ class QualityInspection(Document): for i in range(1, 11): field = "reading_" + str(i) if reading.get(field) is None: + data[field] = 0.0 continue data[field] = parse_float(reading.get(field)) From d495d93840c809bfdf7a7109e6fe883eae0e5193 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 29 Sep 2024 11:09:32 +0530 Subject: [PATCH 06/25] fix: use serial and batch fields (backport #43421) (#43423) fix: use serial and batch fields (#43421) (cherry picked from commit ca16089d9ddb692953ae6e493fff1019e9ab70f2) Co-authored-by: rohitwaghchaure --- erpnext/public/js/controllers/transaction.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 204c09299ad..851bd64f3b4 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -573,6 +573,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe callback: function(r) { if(!r.exc) { frappe.run_serially([ + () => { + if (item.docstatus === 0 + && frappe.meta.has_field(item.doctype, "use_serial_batch_fields") + && !item.use_serial_batch_fields + && cint(frappe.user_defaults?.use_serial_batch_fields) === 1 + ) { + item["use_serial_batch_fields"] = 1; + } + }, () => { var d = locals[cdt][cdn]; me.add_taxes_from_item_tax_template(d.item_tax_rate); From 4b3f143f8366881d8cc427d56a14d6c38403d688 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 29 Sep 2024 21:18:25 +0530 Subject: [PATCH 07/25] fix: Data missing in table: None, MandatoryError (backport #43422) (#43429) fix: Data missing in table: None, MandatoryError (#43422) (cherry picked from commit 8e33e0e1d2df18d8401919e518cdf75e469d87a7) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/stock_entry/stock_entry.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 4ee4c6b2930..627ad78be58 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -3148,11 +3148,13 @@ def get_available_materials(work_order) -> dict: if row.serial_no: for serial_no in get_serial_nos(row.serial_no): - item_data.serial_nos.remove(serial_no) + if serial_no in item_data.serial_nos: + item_data.serial_nos.remove(serial_no) elif row.serial_nos: for serial_no in get_serial_nos(row.serial_nos): - item_data.serial_nos.remove(serial_no) + if serial_no in item_data.serial_nos: + item_data.serial_nos.remove(serial_no) return available_materials @@ -3270,6 +3272,9 @@ def create_serial_and_batch_bundle(parent_doc, row, child, type_of_transaction=N for batch_no, qty in row.batches_to_be_consume.items(): doc.append("entries", {"batch_no": batch_no, "warehouse": row.warehouse, "qty": qty * -1}) + if not doc.entries: + return None + return doc.insert(ignore_permissions=True).name From 86b10ce9bbfbf4279d52df9b17c660e6baeda934 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 29 Sep 2024 22:06:50 +0530 Subject: [PATCH 08/25] fix: tests for work order consumption (backport #41814) (#43430) fix: tests for work order consumption (#41814) * fix: tests for work order automatic SABB creation * fix: qty * chore: show created sabb * chore: fix syntax * fix: check SABB qty * fix: add batched consumable to manufacture * fix: missing fg qty field * fix: improve test debug * chore: linting * chore: removed extra hash icons --------- Co-authored-by: Rohit Waghchaure (cherry picked from commit ca3c6809098e4383db4f6ac2e3243c13da2970cf) Co-authored-by: Richard Case <110036763+casesolved-co-uk@users.noreply.github.com> --- .../doctype/work_order/test_work_order.py | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index ebc400979b6..6ee3f73eb08 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1531,6 +1531,197 @@ class TestWorkOrder(FrappeTestCase): self.assertFalse(serial_nos) + def test_backflushed_batch_raw_materials_based_on_transferred_autosabb(self): + frappe.db.set_single_value( + "Manufacturing Settings", + "backflush_raw_materials_based_on", + "Material Transferred for Manufacture", + ) + + batch_item = "Test Batch MCC Keyboard" + fg_item = "Test FG Item with Batch Raw Materials" + + ste_doc = test_stock_entry.make_stock_entry( + item_code=batch_item, target="Stores - _TC", qty=8, basic_rate=100, do_not_save=True + ) + + # Inward raw materials in Stores warehouse + ste_doc.submit() + ste_doc.reload() + + batch_no = get_batch_from_bundle(ste_doc.items[0].serial_and_batch_bundle) + + wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4) + # action taken upon Start button: + transferred_ste_doc = frappe.get_doc( + make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4) + ) + + transferred_ste_doc.submit() + transferred_ste_doc.reload() + + self.assertTrue(transferred_ste_doc.items[0].serial_and_batch_bundle) + self.assertEqual( + get_batch_from_bundle(transferred_ste_doc.items[0].serial_and_batch_bundle), batch_no + ) + self.assertEqual(transferred_ste_doc.items[0].qty, 4.0) + + # Make additional consumption and link to WO + test_stock_entry.make_stock_entry( + item_code="Test Batch Battery Consumable", + target="Stores - _TC", + qty=8, + basic_rate=2.33, + ) + consume_use_doc = test_stock_entry.make_stock_entry( + item_code="Test Batch Battery Consumable", # consumable not linked to BOM + source="Stores - _TC", + qty=4, + purpose="Material Consumption for Manufacture", + do_not_save=True, + ) + consume_use_doc.work_order = wo_doc.name + consume_use_doc.fg_completed_qty = 4 + consume_use_doc.submit() + consume_use_doc.reload() + + manufacture_ste_doc = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 4)) + mfr_items = [i.as_dict() for i in manufacture_ste_doc.items] + manufacture_ste_doc.submit() + manufacture_ste_doc.reload() + + self.assertTrue(len(mfr_items), 2) + self.assertTrue(manufacture_ste_doc.items[0].serial_and_batch_bundle) + self.assertEqual( + get_batch_from_bundle(manufacture_ste_doc.items[0].serial_and_batch_bundle), batch_no + ) + self.assertEqual(manufacture_ste_doc.items[0].qty, 4.0) + + def test_backflushed_serial_no_raw_materials_based_on_transferred_autosabb(self): + frappe.db.set_single_value( + "Manufacturing Settings", + "backflush_raw_materials_based_on", + "Material Transferred for Manufacture", + ) + + sn_item = "Test Serial No BTT Headphone" + fg_item = "Test FG Item with Serial No Raw Materials" + + ste_doc = test_stock_entry.make_stock_entry( + item_code=sn_item, target="Stores - _TC", qty=4, basic_rate=100, do_not_save=True + ) + + # Inward raw materials in Stores warehouse + ste_doc.submit() + ste_doc.reload() + + serial_nos_list = sorted(get_serial_nos_from_bundle(ste_doc.items[0].serial_and_batch_bundle)) + + wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4) + transferred_ste_doc = frappe.get_doc( + make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4) + ) + + transferred_ste_doc.submit() + transferred_ste_doc.reload() + + self.assertTrue(transferred_ste_doc.items[0].serial_and_batch_bundle) + self.assertEqual( + sorted(get_serial_nos_from_bundle(transferred_ste_doc.items[0].serial_and_batch_bundle)), + serial_nos_list, + ) + self.assertEqual(transferred_ste_doc.items[0].qty, 4.0) + + # Make additional consumption and link to WO + test_stock_entry.make_stock_entry( + item_code="Test Serial Battery Consumable", + target="Stores - _TC", + qty=8, + basic_rate=3.33, + ) + consume_use_doc = test_stock_entry.make_stock_entry( + item_code="Test Serial Battery Consumable", # consumable not linked to BOM + source="Stores - _TC", + qty=4, + purpose="Material Consumption for Manufacture", + do_not_save=True, + ) + consume_use_doc.work_order = wo_doc.name + consume_use_doc.fg_completed_qty = 4 + consume_use_doc.submit() + consume_use_doc.reload() + + manufacture_ste_doc = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 4)) + mfr_items = [i.as_dict() for i in manufacture_ste_doc.items] + manufacture_ste_doc.submit() + manufacture_ste_doc.reload() + + self.assertTrue(len(mfr_items), 2) + self.assertTrue(manufacture_ste_doc.items[0].serial_and_batch_bundle) + self.assertEqual( + sorted(get_serial_nos_from_bundle(manufacture_ste_doc.items[0].serial_and_batch_bundle)), + serial_nos_list, + ) + self.assertEqual(manufacture_ste_doc.items[0].qty, 4.0) + + def test_backflushed_serial_no_batch_raw_materials_based_on_transferred_autosabb(self): + frappe.db.set_single_value( + "Manufacturing Settings", + "backflush_raw_materials_based_on", + "Material Transferred for Manufacture", + ) + + sn_batch_item = "Test Batch Serial No WebCam" + fg_item = "Test FG Item with Serial & Batch No Raw Materials" + + ste_doc = test_stock_entry.make_stock_entry( + item_code=sn_batch_item, target="Stores - _TC", qty=4, basic_rate=100, do_not_save=True + ) + + ste_doc.submit() + ste_doc.reload() + + serial_nos_list = sorted(get_serial_nos_from_bundle(ste_doc.items[0].serial_and_batch_bundle)) + batch_no = get_batch_from_bundle(ste_doc.items[0].serial_and_batch_bundle) + + wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4) + transferred_ste_doc = frappe.get_doc( + make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4) + ) + + transferred_ste_doc.submit() + transferred_ste_doc.reload() + + self.assertTrue(transferred_ste_doc.items[0].serial_and_batch_bundle) + self.assertEqual( + sorted(get_serial_nos_from_bundle(transferred_ste_doc.items[0].serial_and_batch_bundle)), + serial_nos_list, + ) + self.assertEqual( + get_batch_from_bundle(transferred_ste_doc.items[0].serial_and_batch_bundle), batch_no + ) + self.assertEqual(transferred_ste_doc.items[0].qty, 4.0) + + manufacture_ste_doc = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 4)) + manufacture_ste_doc.submit() + manufacture_ste_doc.reload() + + self.assertTrue(manufacture_ste_doc.items[0].serial_and_batch_bundle) + self.assertEqual( + sorted(get_serial_nos_from_bundle(manufacture_ste_doc.items[0].serial_and_batch_bundle)), + serial_nos_list, + ) + self.assertEqual( + get_batch_from_bundle(manufacture_ste_doc.items[0].serial_and_batch_bundle), batch_no + ) + self.assertEqual(manufacture_ste_doc.items[0].qty, 4.0) + + bundle = manufacture_ste_doc.items[0].serial_and_batch_bundle + bundle_doc = frappe.get_doc("Serial and Batch Bundle", bundle) + qty = sum(e.qty for e in bundle_doc.entries) + self.assertEqual(qty, -4.0) + + ### def test_non_consumed_material_return_against_work_order(self): frappe.db.set_single_value( "Manufacturing Settings", @@ -2335,6 +2526,29 @@ def prepare_data_for_backflush_based_on_materials_transferred(): make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[batch_item_doc.name]) + # Make additional items not attached to a BOM + make_item( + "Test Batch Battery Consumable", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TBMK.#####", + "valuation_rate": 2.33, + "stock_uom": "Nos", + }, + ) + make_item( + "Test Serial Battery Consumable", + { + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "TSBH.#####", + "valuation_rate": 3.33, + "stock_uom": "Nos", + }, + ) + sn_item_doc = make_item( "Test Serial No BTT Headphone", { From 2984bad2c09061c949a55d8d62f0fa39bbbbdfd2 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 29 Sep 2024 09:18:52 +0530 Subject: [PATCH 09/25] fix: Stock Ledger Invariant Check report (cherry picked from commit d7daedc5b21a952c0bfa46079595f016179bcca0) --- .../stock_ledger_invariant_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py index fb392f7e36a..6fc109f1c6e 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py @@ -52,7 +52,7 @@ def add_invariant_check_fields(sles): balance_qty = 0.0 balance_stock_value = 0.0 for idx, sle in enumerate(sles): - queue = json.loads(sle.stock_queue) + queue = json.loads(sle.stock_queue) if sle.stock_queue else [] fifo_qty = 0.0 fifo_value = 0.0 From 50e47e796dc4a91fb4b3e653fb5cc679c58916cf Mon Sep 17 00:00:00 2001 From: Kitti U Date: Fri, 6 Sep 2024 12:53:47 +0700 Subject: [PATCH 10/25] feat: provide hook point for bulk transaction tasks (cherry picked from commit d4dd01d8d142f10068aa76a3fe2aedb84820a151) --- erpnext/utilities/bulk_transaction.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index 4319fa7d6ea..7ba687941c9 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -137,6 +137,11 @@ def task(doc_name, from_doctype, to_doctype): }, "Purchase Receipt": {"Purchase Invoice": purchase_receipt.make_purchase_invoice}, } + + hooks = frappe.get_hooks("bulk_transaction_task_mapper") + for hook in hooks: + mapper.update(frappe.get_attr(hook)()) + frappe.flags.bulk_transaction = True if to_doctype in ["Payment Entry"]: obj = mapper[from_doctype][to_doctype](from_doctype, doc_name) From 21a01575b66aa5f620974f171fbbdd7c1d335fe0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 30 Sep 2024 12:05:17 +0530 Subject: [PATCH 11/25] fix: 'NoneType' object has no attribute 'has_serial_no' (cherry picked from commit 28f9fd2507861ea72afd0853312ea04a7e42e3f8) --- erpnext/controllers/stock_controller.py | 12 ++++++++++++ .../stock_reconciliation/stock_reconciliation.py | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 1620fa3bfff..9fb0cc88cfb 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -64,6 +64,18 @@ class StockController(AccountsController): self.validate_internal_transfer() self.validate_putaway_capacity() + def validate_items_exist(self): + if not self.get("items"): + return + + items = [d.item_code for d in self.get("items")] + + exists_items = frappe.get_all("Item", filters={"name": ("in", items)}, pluck="name") + non_exists_items = set(items) - set(exists_items) + + if non_exists_items: + frappe.throw(_("Items {0} do not exist in the Item master.").format(", ".join(non_exists_items))) + def validate_duplicate_serial_and_batch_bundle(self, table_name): if not self.get(table_name): return diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 93e3e69729b..00f5bcf30f1 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -61,6 +61,7 @@ class StockReconciliation(StockController): self.head_row = ["Item Code", "Warehouse", "Quantity", "Valuation Rate"] def validate(self): + self.validate_items_exist() if not self.expense_account: self.expense_account = frappe.get_cached_value( "Company", self.company, "stock_adjustment_account" @@ -162,6 +163,9 @@ class StockReconciliation(StockController): def set_current_serial_and_batch_bundle(self, voucher_detail_no=None, save=False) -> None: """Set Serial and Batch Bundle for each item""" for item in self.items: + if not frappe.db.exists("Item", item.item_code): + frappe.throw(_("Item {0} does not exist").format(item.item_code)) + if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle: bundle = self.get_bundle_for_specific_serial_batch(item) item.current_serial_and_batch_bundle = bundle.name @@ -357,6 +361,9 @@ class StockReconciliation(StockController): def set_new_serial_and_batch_bundle(self): for item in self.items: + if not item.item_code: + continue + if item.use_serial_batch_fields: continue From c551c2714c97da39541d3d47f8a9d0f9ecaa20e3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 30 Sep 2024 13:38:54 +0530 Subject: [PATCH 12/25] fix: adjustmen entry for stock reco (cherry picked from commit 4e463b7d6deffc33bdd793322cfaf82c0e86fb63) --- .../stock/doctype/stock_reconciliation/stock_reconciliation.py | 2 +- erpnext/stock/stock_ledger.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 00f5bcf30f1..8ee3d9b3901 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -691,7 +691,7 @@ class StockReconciliation(StockController): from erpnext.stock.stock_ledger import get_stock_value_difference difference_amount = get_stock_value_difference( - row.item_code, row.warehouse, self.posting_date, self.posting_time + row.item_code, row.warehouse, self.posting_date, self.posting_time, self.name ) if not difference_amount: diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 49a3a3335d1..b167ccf6df8 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -865,7 +865,7 @@ class update_entries_after: sle.stock_value = self.wh_data.stock_value sle.stock_queue = json.dumps(self.wh_data.stock_queue) - if not sle.is_adjustment_entry or not self.args.get("sle_id"): + if not sle.is_adjustment_entry: sle.stock_value_difference = stock_value_difference sle.doctype = "Stock Ledger Entry" From 5fc59349421e739cea28aab29b437678808a4224 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:18:49 +0200 Subject: [PATCH 13/25] fix(Item): error message on tax rate (backport #42955) (#42956) Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> --- erpnext/stock/doctype/item/item.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 1ceb949d691..751a37e3164 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -345,7 +345,13 @@ class Item(Document): def validate_item_tax_net_rate_range(self): for tax in self.get("taxes"): if flt(tax.maximum_net_rate) < flt(tax.minimum_net_rate): - frappe.throw(_("Row #{0}: Maximum Net Rate cannot be greater than Minimum Net Rate")) + frappe.throw( + _("Taxes row #{0}: {1} cannot be smaller than {2}").format( + tax.idx, + bold(_(tax.meta.get_label("maximum_net_rate"))), + bold(_(tax.meta.get_label("minimum_net_rate"))), + ) + ) def update_template_tables(self): template = frappe.get_cached_doc("Item", self.variant_of) From 7bf6251c21a5249c1c3ade63b29b25d1cb597433 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:34:48 +0530 Subject: [PATCH 14/25] fix: negative stock error for batch (backport #43450) (#43454) fix: negative stock error for batch (#43450) (cherry picked from commit 912ba7789c9178c596cc38e253e316f3bd6cd9ce) Co-authored-by: rohitwaghchaure --- .../serial_and_batch_bundle.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 a1944732fb6..62f95f5b2e8 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 @@ -280,7 +280,7 @@ class SerialandBatchBundle(Document): ) def validate_negative_batch(self, batch_no, available_qty): - if available_qty < 0: + if available_qty < 0 and not self.is_stock_reco_for_valuation_adjustment(available_qty): msg = f"""Batch No {bold(batch_no)} of an Item {bold(self.item_code)} has negative stock of quantity {bold(available_qty)} in the @@ -288,6 +288,18 @@ class SerialandBatchBundle(Document): frappe.throw(_(msg), BatchNegativeStockError) + def is_stock_reco_for_valuation_adjustment(self, available_qty): + if ( + self.voucher_type == "Stock Reconciliation" + and self.type_of_transaction == "Outward" + and self.voucher_detail_no + and abs(frappe.db.get_value("Stock Reconciliation Item", self.voucher_detail_no, "qty")) + == abs(available_qty) + ): + return True + + return False + def get_sle_for_outward_transaction(self): sle = frappe._dict( { From 4fc6d3ef64df33303078b624cba6ca09cea03845 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Tue, 1 Oct 2024 10:50:43 +0530 Subject: [PATCH 15/25] fix: add company filter in Warehouse wise Item Balance Age and Value (cherry picked from commit 75950f86cff9e456693947b530ce257d354648bd) --- .../warehouse_wise_item_balance_age_and_value.js | 15 +++++++++++++++ .../warehouse_wise_item_balance_age_and_value.py | 2 -- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.js b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.js index 137c786e44b..ac1ecfff530 100644 --- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.js +++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.js @@ -3,6 +3,15 @@ frappe.query_reports["Warehouse wise Item Balance Age and Value"] = { filters: [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + width: "80", + options: "Company", + reqd: 1, + default: frappe.defaults.get_user_default("Company"), + }, { fieldname: "from_date", label: __("From Date"), @@ -39,6 +48,12 @@ frappe.query_reports["Warehouse wise Item Balance Age and Value"] = { fieldtype: "Link", width: "80", options: "Warehouse", + get_query: function () { + const company = frappe.query_report.get_filter_value("company"); + return { + filters: { company: company }, + }; + }, }, { fieldname: "filter_total_zero_qty", diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py index 0df8b6ddb48..68caff40356 100644 --- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py +++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py @@ -109,8 +109,6 @@ def validate_filters(filters): sle_count = flt(frappe.qb.from_("Stock Ledger Entry").select(Count("name")).run()[0][0]) if sle_count > 500000: frappe.throw(_("Please set filter based on Item or Warehouse")) - if not filters.get("company"): - filters["company"] = frappe.defaults.get_user_default("Company") def get_warehouse_list(filters): From ee2c8c869ad19a5d5e629b5598f508a9cc4927e9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:54:26 +0530 Subject: [PATCH 16/25] fix: last purchase rate for purchase invoice (backport #43448) (#43452) * fix: last purchase rate for purchase invoice (cherry picked from commit fb9d10663388431644ac1798ba1f9363d9db2775) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py * chore: fix conflicts --------- Co-authored-by: Rohit Waghchaure --- .../purchase_invoice/test_purchase_invoice.py | 18 +++++ erpnext/controllers/buying_controller.py | 8 ++- erpnext/public/js/controllers/transaction.js | 4 ++ erpnext/stock/doctype/item/item.py | 71 ++++++++++++------- 4 files changed, 71 insertions(+), 30 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 31143fb72b8..cf5bfedaebd 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2292,6 +2292,24 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) + def test_last_purchase_rate(self): + item = create_item("_Test Item For Last Purchase Rate from PI", is_stock_item=1) + pi1 = make_purchase_invoice(item_code=item.item_code, qty=10, rate=100) + item.reload() + self.assertEqual(item.last_purchase_rate, 100) + + pi2 = make_purchase_invoice(item_code=item.item_code, qty=10, rate=200) + item.reload() + self.assertEqual(item.last_purchase_rate, 200) + + pi2.cancel() + item.reload() + self.assertEqual(item.last_purchase_rate, 100) + + pi1.cancel() + item.reload() + self.assertEqual(item.last_purchase_rate, 0) + def set_advance_flag(company, flag, default_account): frappe.db.set_value( diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 31b6f391ba4..e9e7ef62670 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -702,9 +702,11 @@ class BuyingController(SubcontractingController): if self.get("is_return"): return - if self.doctype in ["Purchase Order", "Purchase Receipt"] and not frappe.db.get_single_value( - "Buying Settings", "disable_last_purchase_rate" - ): + if self.doctype in [ + "Purchase Order", + "Purchase Receipt", + "Purchase Invoice", + ] and not frappe.db.get_single_value("Buying Settings", "disable_last_purchase_rate"): update_last_purchase_rate(self, is_submit=0) if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 851bd64f3b4..92e9e559ada 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1277,6 +1277,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe .filter(Boolean).length > 0; } else if (this.frm.doc?.items) { let first_row = this.frm.doc.items[0]; + if (!first_row) { + return false + }; + let mapped_rows = mappped_fields.filter(d => first_row[d]) return mapped_rows?.length > 0; diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 751a37e3164..e40b3822af6 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -22,6 +22,7 @@ from frappe.utils import ( strip_html, ) from frappe.utils.html_utils import clean_html +from pypika import Order import erpnext from erpnext.controllers.item_variant import ( @@ -1139,34 +1140,10 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): """returns last purchase details in stock uom""" # get last purchase order item details - last_purchase_order = frappe.db.sql( - """\ - select po.name, po.transaction_date, po.conversion_rate, - po_item.conversion_factor, po_item.base_price_list_rate, - po_item.discount_percentage, po_item.base_rate, po_item.base_net_rate - from `tabPurchase Order` po, `tabPurchase Order Item` po_item - where po.docstatus = 1 and po_item.item_code = %s and po.name != %s and - po.name = po_item.parent - order by po.transaction_date desc, po.name desc - limit 1""", - (item_code, cstr(doc_name)), - as_dict=1, - ) + last_purchase_order = get_purchase_voucher_details("Purchase Order", item_code, doc_name) # get last purchase receipt item details - last_purchase_receipt = frappe.db.sql( - """\ - select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate, - pr_item.conversion_factor, pr_item.base_price_list_rate, pr_item.discount_percentage, - pr_item.base_rate, pr_item.base_net_rate - from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item - where pr.docstatus = 1 and pr_item.item_code = %s and pr.name != %s and - pr.name = pr_item.parent - order by pr.posting_date desc, pr.posting_time desc, pr.name desc - limit 1""", - (item_code, cstr(doc_name)), - as_dict=1, - ) + last_purchase_receipt = get_purchase_voucher_details("Purchase Receipt", item_code, doc_name) purchase_order_date = getdate( last_purchase_order and last_purchase_order[0].transaction_date or "1900-01-01" @@ -1187,7 +1164,13 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): purchase_date = purchase_receipt_date else: - return frappe._dict() + last_purchase_invoice = get_purchase_voucher_details("Purchase Invoice", item_code, doc_name) + + if last_purchase_invoice: + last_purchase = last_purchase_invoice[0] + purchase_date = getdate(last_purchase.posting_date) + else: + return frappe._dict() conversion_factor = flt(last_purchase.conversion_factor) out = frappe._dict( @@ -1213,6 +1196,40 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): return out +def get_purchase_voucher_details(doctype, item_code, document_name): + parent_doc = frappe.qb.DocType(doctype) + child_doc = frappe.qb.DocType(doctype + " Item") + + query = ( + frappe.qb.from_(parent_doc) + .inner_join(child_doc) + .on(parent_doc.name == child_doc.parent) + .select( + parent_doc.name, + parent_doc.conversion_rate, + child_doc.conversion_factor, + child_doc.base_price_list_rate, + child_doc.discount_percentage, + child_doc.base_rate, + child_doc.base_net_rate, + ) + .where(parent_doc.docstatus == 1) + .where(child_doc.item_code == item_code) + .where(parent_doc.name != document_name) + ) + + if doctype in ("Purchase Receipt", "Purchase Invoice"): + query = query.select(parent_doc.posting_date, parent_doc.posting_time) + query = query.orderby( + parent_doc.posting_date, parent_doc.posting_time, parent_doc.name, order=Order.desc + ) + else: + query = query.select(parent_doc.transaction_date) + query = query.orderby(parent_doc.transaction_date, parent_doc.name, order=Order.desc) + + return query.run(as_dict=1) + + def check_stock_uom_with_bin(item, stock_uom): if stock_uom == frappe.db.get_value("Item", item, "stock_uom"): return From 9c0a17e4d5dceff6e4ef0a90e1d36de762f8d3af Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:26:38 +0530 Subject: [PATCH 17/25] fix: removed validation for materials return (backport #43461) (#43463) fix: removed validation for materials return (#43461) (cherry picked from commit 1c7154c7ca3df8b53be00e60b977e48ec9dd2ae3) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 627ad78be58..ae6ca4e25d0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1594,10 +1594,6 @@ class StockEntry(StockController): if pro_doc.status == "Stopped": msg = f"Transaction not allowed against stopped Work Order {self.work_order}" - if self.is_return and pro_doc.status not in ["Completed", "Closed"]: - title = _("Stock Return") - msg = f"Work Order {self.work_order} must be completed or closed" - if msg: frappe.throw(_(msg), title=title) From 96c4d1af63e6f47a6fe4f27b085b7e09a889acbe Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:27:27 +0530 Subject: [PATCH 18/25] Serial no report (backport #43444) (#43464) Serial no report (#43444) * chore: remove the field that which is not exiting in serial no * chore: remove the field that which is not exiting in serial no * chore: remove the field that which is not exiting in serial no (cherry picked from commit 661efadf410daa15eb4cdea7652aa6018b30633a) Co-authored-by: Vishv-silveroak <108357657+Vishv-024@users.noreply.github.com> --- .../serial_no_service_contract_expiry.json | 43 ++++++++++--------- .../serial_no_status/serial_no_status.json | 43 ++++++++++--------- .../serial_no_warranty_expiry.json | 43 ++++++++++--------- 3 files changed, 69 insertions(+), 60 deletions(-) diff --git a/erpnext/stock/report/serial_no_service_contract_expiry/serial_no_service_contract_expiry.json b/erpnext/stock/report/serial_no_service_contract_expiry/serial_no_service_contract_expiry.json index 6d20719f8eb..75e2fac98fd 100644 --- a/erpnext/stock/report/serial_no_service_contract_expiry/serial_no_service_contract_expiry.json +++ b/erpnext/stock/report/serial_no_service_contract_expiry/serial_no_service_contract_expiry.json @@ -1,30 +1,33 @@ { - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2013-01-14 10:52:58", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 3, - "is_standard": "Yes", - "json": "{\"add_total_row\": 0, \"sort_by\": \"Serial No.modified\", \"sort_order\": \"desc\", \"sort_by_next\": null, \"filters\": [[\"Serial No\", \"delivery_document_type\", \"in\", [\"Delivery Note\", \"Sales Invoice\"]], [\"Serial No\", \"warehouse\", \"=\", \"\"]], \"sort_order_next\": \"desc\", \"columns\": [[\"name\", \"Serial No\"], [\"item_code\", \"Serial No\"], [\"amc_expiry_date\", \"Serial No\"], [\"maintenance_status\", \"Serial No\"], [\"delivery_document_no\", \"Serial No\"], [\"customer\", \"Serial No\"], [\"customer_name\", \"Serial No\"], [\"item_name\", \"Serial No\"], [\"description\", \"Serial No\"], [\"item_group\", \"Serial No\"], [\"brand\", \"Serial No\"]]}", - "modified": "2017-02-24 20:02:00.706889", - "modified_by": "Administrator", - "module": "Stock", - "name": "Serial No Service Contract Expiry", - "owner": "Administrator", - "ref_doctype": "Serial No", - "report_name": "Serial No Service Contract Expiry", - "report_type": "Report Builder", + "add_total_row": 0, + "columns": [], + "creation": "2013-01-14 10:52:58", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 3, + "is_standard": "Yes", + "json": "{\"add_total_row\": 0, \"sort_by\": \"Serial No.modified\", \"sort_order\": \"desc\", \"sort_by_next\": null, \"filters\": [[\"Serial No\", \"warehouse\", \"=\", \"\"]], \"sort_order_next\": \"desc\", \"columns\": [[\"name\", \"Serial No\"], [\"item_code\", \"Serial No\"], [\"amc_expiry_date\", \"Serial No\"], [\"maintenance_status\", \"Serial No\"],[\"item_name\", \"Serial No\"], [\"description\", \"Serial No\"], [\"item_group\", \"Serial No\"], [\"brand\", \"Serial No\"]]}", + "letterhead": null, + "modified": "2024-09-26 13:07:23.451182", + "modified_by": "Administrator", + "module": "Stock", + "name": "Serial No Service Contract Expiry", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Serial No", + "report_name": "Serial No Service Contract Expiry", + "report_type": "Report Builder", "roles": [ { "role": "Item Manager" - }, + }, { "role": "Stock Manager" - }, + }, { "role": "Stock User" } ] -} \ No newline at end of file +} diff --git a/erpnext/stock/report/serial_no_status/serial_no_status.json b/erpnext/stock/report/serial_no_status/serial_no_status.json index 8f03567c852..d74c2087f72 100644 --- a/erpnext/stock/report/serial_no_status/serial_no_status.json +++ b/erpnext/stock/report/serial_no_status/serial_no_status.json @@ -1,30 +1,33 @@ { - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2013-01-14 10:52:58", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 4, - "is_standard": "Yes", - "json": "{\"add_total_row\": 0, \"sort_by\": \"Serial No.name\", \"sort_order\": \"desc\", \"sort_by_next\": null, \"filters\": [], \"sort_order_next\": \"desc\", \"columns\": [[\"name\", \"Serial No\"], [\"item_code\", \"Serial No\"], [\"warehouse\", \"Serial No\"], [\"item_name\", \"Serial No\"], [\"description\", \"Serial No\"], [\"item_group\", \"Serial No\"], [\"brand\", \"Serial No\"], [\"purchase_document_type\", \"Serial No\"], [\"purchase_document_no\", \"Serial No\"], [\"purchase_date\", \"Serial No\"], [\"customer\", \"Serial No\"], [\"customer_name\", \"Serial No\"], [\"purchase_rate\", \"Serial No\"], [\"delivery_document_type\", \"Serial No\"], [\"delivery_document_no\", \"Serial No\"], [\"delivery_date\", \"Serial No\"], [\"supplier\", \"Serial No\"], [\"supplier_name\", \"Serial No\"]]}", - "modified": "2017-02-24 19:54:21.392265", - "modified_by": "Administrator", - "module": "Stock", - "name": "Serial No Status", - "owner": "Administrator", - "ref_doctype": "Serial No", - "report_name": "Serial No Status", - "report_type": "Report Builder", + "add_total_row": 0, + "columns": [], + "creation": "2013-01-14 10:52:58", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 4, + "is_standard": "Yes", + "json": "{\"add_total_row\": 0, \"sort_by\": \"Serial No.name\", \"sort_order\": \"desc\", \"sort_by_next\": null, \"filters\": [], \"sort_order_next\": \"desc\", \"columns\": [[\"name\", \"Serial No\"], [\"item_code\", \"Serial No\"], [\"warehouse\", \"Serial No\"], [\"item_name\", \"Serial No\"], [\"description\", \"Serial No\"], [\"item_group\", \"Serial No\"], [\"brand\", \"Serial No\"],[\"purchase_document_no\", \"Serial No\"]]}", + "letterhead": null, + "modified": "2024-09-26 13:10:52.693648", + "modified_by": "Administrator", + "module": "Stock", + "name": "Serial No Status", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Serial No", + "report_name": "Serial No Status", + "report_type": "Report Builder", "roles": [ { "role": "Item Manager" - }, + }, { "role": "Stock Manager" - }, + }, { "role": "Stock User" } ] -} \ No newline at end of file +} diff --git a/erpnext/stock/report/serial_no_warranty_expiry/serial_no_warranty_expiry.json b/erpnext/stock/report/serial_no_warranty_expiry/serial_no_warranty_expiry.json index ef345f45216..75e2fac98fd 100644 --- a/erpnext/stock/report/serial_no_warranty_expiry/serial_no_warranty_expiry.json +++ b/erpnext/stock/report/serial_no_warranty_expiry/serial_no_warranty_expiry.json @@ -1,30 +1,33 @@ { - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2013-01-14 10:52:58", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 3, - "is_standard": "Yes", - "json": "{\"add_total_row\": 0, \"sort_by\": \"Serial No.modified\", \"sort_order\": \"desc\", \"sort_by_next\": null, \"filters\": [[\"Serial No\", \"delivery_document_type\", \"in\", [\"Delivery Note\", \"Sales Invoice\"]], [\"Serial No\", \"warehouse\", \"=\", \"\"]], \"sort_order_next\": \"desc\", \"columns\": [[\"name\", \"Serial No\"], [\"item_code\", \"Serial No\"], [\"warranty_expiry_date\", \"Serial No\"], [\"warranty_period\", \"Serial No\"], [\"maintenance_status\", \"Serial No\"], [\"purchase_document_no\", \"Serial No\"], [\"purchase_date\", \"Serial No\"], [\"supplier\", \"Serial No\"], [\"supplier_name\", \"Serial No\"], [\"delivery_document_no\", \"Serial No\"], [\"delivery_date\", \"Serial No\"], [\"customer\", \"Serial No\"], [\"customer_name\", \"Serial No\"], [\"item_name\", \"Serial No\"], [\"description\", \"Serial No\"], [\"item_group\", \"Serial No\"], [\"brand\", \"Serial No\"]]}", - "modified": "2017-02-24 20:01:53.097456", - "modified_by": "Administrator", - "module": "Stock", - "name": "Serial No Warranty Expiry", - "owner": "Administrator", - "ref_doctype": "Serial No", - "report_name": "Serial No Warranty Expiry", - "report_type": "Report Builder", + "add_total_row": 0, + "columns": [], + "creation": "2013-01-14 10:52:58", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 3, + "is_standard": "Yes", + "json": "{\"add_total_row\": 0, \"sort_by\": \"Serial No.modified\", \"sort_order\": \"desc\", \"sort_by_next\": null, \"filters\": [[\"Serial No\", \"warehouse\", \"=\", \"\"]], \"sort_order_next\": \"desc\", \"columns\": [[\"name\", \"Serial No\"], [\"item_code\", \"Serial No\"], [\"amc_expiry_date\", \"Serial No\"], [\"maintenance_status\", \"Serial No\"],[\"item_name\", \"Serial No\"], [\"description\", \"Serial No\"], [\"item_group\", \"Serial No\"], [\"brand\", \"Serial No\"]]}", + "letterhead": null, + "modified": "2024-09-26 13:07:23.451182", + "modified_by": "Administrator", + "module": "Stock", + "name": "Serial No Service Contract Expiry", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Serial No", + "report_name": "Serial No Service Contract Expiry", + "report_type": "Report Builder", "roles": [ { "role": "Item Manager" - }, + }, { "role": "Stock Manager" - }, + }, { "role": "Stock User" } ] -} \ No newline at end of file +} From d96cee8779a8143b54231968888a1016dd00b42a Mon Sep 17 00:00:00 2001 From: Corentin Forler Date: Wed, 2 Oct 2024 10:06:44 +0200 Subject: [PATCH 19/25] fix: Fix API endpoint for Frankfurter (cherry picked from commit 33e72111c7d7f60dbe041282026f607230624f52) --- .../currency_exchange_settings/currency_exchange_settings.py | 2 +- erpnext/setup/install.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py index 8cbb99e9252..160e791978e 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py @@ -109,7 +109,7 @@ def get_api_endpoint(service_provider: str | None = None, use_http: bool = False if service_provider == "exchangerate.host": api = "api.exchangerate.host/convert" elif service_provider == "frankfurter.app": - api = "frankfurter.app/{transaction_date}" + api = "api.frankfurter.app/{transaction_date}" protocol = "https://" if use_http: diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index bba9e79a348..97ec418d955 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -98,7 +98,7 @@ def setup_currency_exchange(): ces.set("result_key", []) ces.set("req_params", []) - ces.api_endpoint = "https://frankfurter.app/{transaction_date}" + ces.api_endpoint = "https://api.frankfurter.app/{transaction_date}" ces.append("result_key", {"key": "rates"}) ces.append("result_key", {"key": "{to_currency}"}) ces.append("req_params", {"key": "base", "value": "{from_currency}"}) From 8e7d8936692c28ac9830a3d64697a15b9e4d3ea8 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 2 Oct 2024 14:28:54 +0530 Subject: [PATCH 20/25] test: update test for API change (cherry picked from commit c444de017aee1cfa6e4e214dbddbc2543dccd966) --- .../setup/doctype/currency_exchange/test_currency_exchange.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index 3249c93ef87..d28b1e65b7b 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -68,9 +68,9 @@ def patched_requests_get(*args, **kwargs): if kwargs["params"].get("date") and kwargs["params"].get("from") and kwargs["params"].get("to"): if test_exchange_values.get(kwargs["params"]["date"]): return PatchResponse({"result": test_exchange_values[kwargs["params"]["date"]]}, 200) - elif args[0].startswith("https://frankfurter.app") and kwargs.get("params"): + elif args[0].startswith("https://api.frankfurter.app") and kwargs.get("params"): if kwargs["params"].get("base") and kwargs["params"].get("symbols"): - date = args[0].replace("https://frankfurter.app/", "") + date = args[0].replace("https://api.frankfurter.app/", "") if test_exchange_values.get(date): return PatchResponse( {"rates": {kwargs["params"].get("symbols"): test_exchange_values.get(date)}}, 200 From 35a08f88301b8c80e752a5bd236de97a3546245e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:15:48 +0530 Subject: [PATCH 21/25] fix: patch to update Currency Exchange Settings for `frankfurter.app` (backport #43481) (#43483) Co-authored-by: Sagar Vora --- erpnext/patches.txt | 1 + ...date_currency_exchange_settings_for_frankfurter.py | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 erpnext/patches/v14_0/update_currency_exchange_settings_for_frankfurter.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 5b1455ef723..333fca1d1da 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -377,3 +377,4 @@ 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 +erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter diff --git a/erpnext/patches/v14_0/update_currency_exchange_settings_for_frankfurter.py b/erpnext/patches/v14_0/update_currency_exchange_settings_for_frankfurter.py new file mode 100644 index 00000000000..a67c5a26237 --- /dev/null +++ b/erpnext/patches/v14_0/update_currency_exchange_settings_for_frankfurter.py @@ -0,0 +1,11 @@ +import frappe + + +def execute(): + settings = frappe.get_doc("Currency Exchange Settings") + if settings.service_provider != "frankfurter.app": + return + + settings.set_parameters_and_result() + settings.flags.ignore_validate = True + settings.save() From 6516e68fa0c296507e12ff6944d797e09da1d0cc Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 2 Oct 2024 13:11:13 +0530 Subject: [PATCH 22/25] fix: set margin fields for purchase documents when updating items (cherry picked from commit 7be4d56be2714446525b93acce961f04ca1bd2d6) --- erpnext/controllers/accounts_controller.py | 23 +++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 44a9813c89e..77f54818040 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3309,7 +3309,6 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil items_added_or_removed = False # updated to true if any new item is added or removed any_conversion_factor_changed = False - sales_doctypes = ["Sales Order", "Sales Invoice", "Delivery Note", "Quotation"] parent = frappe.get_doc(parent_doctype, parent_doctype_name) check_doc_permissions(parent, "write") @@ -3425,25 +3424,21 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil # if rate is greater than price_list_rate, set margin # or set discount child_item.discount_percentage = 0 - - if parent_doctype in sales_doctypes: - child_item.margin_type = "Amount" - child_item.margin_rate_or_amount = flt( - child_item.rate - child_item.price_list_rate, - child_item.precision("margin_rate_or_amount"), - ) - child_item.rate_with_margin = child_item.rate + child_item.margin_type = "Amount" + child_item.margin_rate_or_amount = flt( + child_item.rate - child_item.price_list_rate, + child_item.precision("margin_rate_or_amount"), + ) + child_item.rate_with_margin = child_item.rate else: child_item.discount_percentage = flt( (1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0, child_item.precision("discount_percentage"), ) child_item.discount_amount = flt(child_item.price_list_rate) - flt(child_item.rate) - - if parent_doctype in sales_doctypes: - child_item.margin_type = "" - child_item.margin_rate_or_amount = 0 - child_item.rate_with_margin = 0 + child_item.margin_type = "" + child_item.margin_rate_or_amount = 0 + child_item.rate_with_margin = 0 child_item.flags.ignore_validate_update_after_submit = True if new_child_flag: From f2a72e5f82feec1683b858d72a2ca315892a97e2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 18:46:01 +0530 Subject: [PATCH 23/25] fix: Stock UOM not fetched when Stock Entry create from Item Dashboard (backport #43457) (#43465) fix: Stock UOM not fetched when Stock Entry create from Item Dashboard (#43457) (cherry picked from commit 895b072bad6caaca384609a842719349eaa371f4) Co-authored-by: rohitwaghchaure --- erpnext/stock/dashboard/item_dashboard.js | 9 ++++++--- erpnext/stock/dashboard/item_dashboard.py | 1 + erpnext/stock/dashboard/item_dashboard_list.html | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index 36a51056bb4..6fc9e6666a2 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -48,17 +48,18 @@ erpnext.stock.ItemDashboard = class ItemDashboard { let actual_qty = unescape(element.attr("data-actual_qty")); let disable_quick_entry = Number(unescape(element.attr("data-disable_quick_entry"))); let entry_type = action === "Move" ? "Material Transfer" : "Material Receipt"; + let stock_uom = unescape(element.attr("data-stock-uom")); if (disable_quick_entry) { open_stock_entry(item, warehouse, entry_type); } else { if (action === "Add") { let rate = unescape($(this).attr("data-rate")); - erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function () { + erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, stock_uom, function () { me.refresh(); }); } else { - erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function () { + erpnext.stock.move_item(item, warehouse, null, actual_qty, null, stock_uom, function () { me.refresh(); }); } @@ -207,7 +208,7 @@ erpnext.stock.ItemDashboard = class ItemDashboard { } }; -erpnext.stock.move_item = function (item, source, target, actual_qty, rate, callback) { +erpnext.stock.move_item = function (item, source, target, actual_qty, rate, stock_uom, callback) { var dialog = new frappe.ui.Dialog({ title: target ? __("Add Item") : __("Move Item"), fields: [ @@ -295,6 +296,8 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call let row = frappe.model.add_child(doc, "items"); row.item_code = dialog.get_value("item_code"); row.s_warehouse = dialog.get_value("source"); + row.stock_uom = stock_uom; + row.uom = stock_uom; row.t_warehouse = dialog.get_value("target"); row.qty = dialog.get_value("qty"); row.conversion_factor = 1; diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py index e6382688669..3d7c21639e0 100644 --- a/erpnext/stock/dashboard/item_dashboard.py +++ b/erpnext/stock/dashboard/item_dashboard.py @@ -71,6 +71,7 @@ def get_data( item.update( { "item_name": frappe.get_cached_value("Item", item.item_code, "item_name"), + "stock_uom": frappe.get_cached_value("Item", item.item_code, "stock_uom"), "disable_quick_entry": frappe.get_cached_value("Item", item.item_code, "has_batch_no") or frappe.get_cached_value("Item", item.item_code, "has_serial_no"), "projected_qty": flt(item.projected_qty, precision), diff --git a/erpnext/stock/dashboard/item_dashboard_list.html b/erpnext/stock/dashboard/item_dashboard_list.html index 3b2619133bf..ae90ff80686 100644 --- a/erpnext/stock/dashboard/item_dashboard_list.html +++ b/erpnext/stock/dashboard/item_dashboard_list.html @@ -49,12 +49,14 @@ data-disable_quick_entry="{{ d.disable_quick_entry }}" data-warehouse="{{ d.warehouse }}" data-actual_qty="{{ d.actual_qty }}" + data-stock-uom="{{ d.stock_uom }}" data-item="{{ escape(d.item_code) }}">{{ __("Move") }} {% endif %}