From 6157fed71c0a7b2cbe90cc81cb22f034b7d0a543 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 19 Feb 2025 15:54:31 +0530 Subject: [PATCH 01/97] fix: source warehouse not fetched in bom creator --- .../doctype/bom_creator/bom_creator.py | 5 +++-- .../bom_creator_item/bom_creator_item.json | 9 ++++++++- .../bom_creator_item/bom_creator_item.py | 1 + .../bom_configurator/bom_configurator.bundle.js | 17 +++++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index 45ce95b6d58..c4fb6345c93 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -28,6 +28,8 @@ BOM_ITEM_FIELDS = [ "stock_uom", "conversion_factor", "do_not_explode", + "source_warehouse", + "allow_alternative_item", ] @@ -291,7 +293,6 @@ class BOMCreator(Document): "item": row.item_code, "bom_type": "Production", "quantity": row.qty, - "allow_alternative_item": 1, "bom_creator": self.name, "bom_creator_item": bom_creator_item, } @@ -315,7 +316,6 @@ class BOMCreator(Document): item_args.update( { "bom_no": bom_no, - "allow_alternative_item": 1, "allow_scrap_items": 1, "include_item_in_manufacturing": 1, } @@ -428,6 +428,7 @@ def add_sub_assembly(**kwargs): "do_not_explode": 1, "is_expandable": 1, "stock_uom": item_info.stock_uom, + "allow_alternative_item": kwargs.allow_alternative_item, }, ) diff --git a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json index 1726f898751..a6e67b956cf 100644 --- a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json +++ b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json @@ -15,6 +15,7 @@ "is_expandable", "sourced_by_supplier", "bom_created", + "allow_alternative_item", "description_section", "description", "quantity_and_rate_section", @@ -225,12 +226,18 @@ "label": "BOM Created", "no_copy": 1, "print_hide": 1 + }, + { + "default": "1", + "fieldname": "allow_alternative_item", + "fieldtype": "Check", + "label": "Allow Alternative Item" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-06-03 18:45:24.339532", + "modified": "2025-02-19 13:25:15.732496", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Creator Item", diff --git a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.py b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.py index e172f36224d..fdd3f77ae26 100644 --- a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.py +++ b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.py @@ -14,6 +14,7 @@ class BOMCreatorItem(Document): if TYPE_CHECKING: from frappe.types import DF + allow_alternative_item: DF.Check amount: DF.Currency base_amount: DF.Currency base_rate: DF.Currency diff --git a/erpnext/public/js/bom_configurator/bom_configurator.bundle.js b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js index b1019f67ca9..6d24751792c 100644 --- a/erpnext/public/js/bom_configurator/bom_configurator.bundle.js +++ b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js @@ -210,6 +210,13 @@ class BOMConfigurator { [ { label: __("Item"), fieldname: "item_code", fieldtype: "Link", options: "Item", reqd: 1 }, { label: __("Qty"), fieldname: "qty", default: 1.0, fieldtype: "Float", reqd: 1 }, + { + label: __("Allow Alternative Item"), + fieldname: "allow_alternative_item", + default: 1.0, + fieldtype: "Check", + reqd: 1, + }, ], (data) => { if (!node.data.parent_id) { @@ -224,6 +231,7 @@ class BOMConfigurator { item_code: data.item_code, fg_reference_id: node.data.name || this.frm.doc.name, qty: data.qty, + allow_alternative_item: data.allow_alternative_item, }, callback: (r) => { view.events.load_tree(r, node); @@ -258,6 +266,7 @@ class BOMConfigurator { fg_item: node.data.value, fg_reference_id: node.data.name || this.frm.doc.name, bom_item: bom_item, + allow_alternative_item: bom_item.allow_alternative_item, }, callback: (r) => { view.events.load_tree(r, node); @@ -278,6 +287,14 @@ class BOMConfigurator { reqd: 1, read_only: read_only, }, + { + label: __("Allow Alternative Item"), + fieldname: "allow_alternative_item", + default: 1.0, + fieldtype: "Check", + reqd: 1, + read_only: read_only, + }, { fieldtype: "Column Break" }, { label: __("Qty"), From 171df3aba512d3e1d9cdb26c456a9e12469066d0 Mon Sep 17 00:00:00 2001 From: Ravibharathi <131471282+ravibharathi656@users.noreply.github.com> Date: Fri, 21 Feb 2025 19:45:21 +0530 Subject: [PATCH 02/97] feat: add total weight in shipment (#46049) Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> (cherry picked from commit 1ec182430dc61cd45ab3076fb5733a3a0c98d568) # Conflicts: # erpnext/stock/doctype/shipment/shipment.json --- erpnext/stock/doctype/shipment/shipment.json | 11 +++++++++++ erpnext/stock/doctype/shipment/shipment.py | 8 ++++++++ erpnext/stock/doctype/shipment/test_shipment.py | 11 +++++++++++ 3 files changed, 30 insertions(+) diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json index 53b549deec1..aaa2515656b 100644 --- a/erpnext/stock/doctype/shipment/shipment.json +++ b/erpnext/stock/doctype/shipment/shipment.json @@ -34,6 +34,7 @@ "shipment_parcel", "parcel_template", "add_template", + "total_weight", "column_break_28", "shipment_delivery_note", "shipment_details_section", @@ -429,11 +430,21 @@ "label": "Pickup Contact Person", "mandatory_depends_on": "eval:doc.pickup_from_type === 'Company'", "options": "User" + }, + { + "fieldname": "total_weight", + "fieldtype": "Float", + "label": "Total Weight (kg)", + "read_only": 1 } ], "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2022-11-17 17:23:27.025802", +======= + "modified": "2025-02-20 16:55:20.076418", +>>>>>>> 1ec182430d (feat: add total weight in shipment (#46049)) "modified_by": "Administrator", "module": "Stock", "name": "Shipment", diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py index 346e70e6060..cf9d165fdd3 100644 --- a/erpnext/stock/doctype/shipment/shipment.py +++ b/erpnext/stock/doctype/shipment/shipment.py @@ -65,6 +65,7 @@ class Shipment(Document): shipment_parcel: DF.Table[ShipmentParcel] shipment_type: DF.Literal["Goods", "Documents"] status: DF.Literal["Draft", "Submitted", "Booked", "Cancelled", "Completed"] + total_weight: DF.Float tracking_status: DF.Literal["", "In Progress", "Delivered", "Returned", "Lost"] tracking_status_info: DF.Data | None tracking_url: DF.SmallText | None @@ -75,6 +76,7 @@ class Shipment(Document): self.validate_weight() self.validate_pickup_time() self.set_value_of_goods() + self.set_total_weight() if self.docstatus == 0: self.status = "Draft" @@ -93,6 +95,12 @@ class Shipment(Document): if flt(parcel.weight) <= 0: frappe.throw(_("Parcel weight cannot be 0")) + def set_total_weight(self): + self.total_weight = self.get_total_weight() + + def get_total_weight(self): + return sum(flt(parcel.weight) * parcel.count for parcel in self.shipment_parcel if parcel.count > 0) + def validate_pickup_time(self): if self.pickup_from and self.pickup_to and get_time(self.pickup_to) < get_time(self.pickup_from): frappe.throw(_("Pickup To time should be greater than Pickup From time")) diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index 4d4eadc339b..1c91a054ebc 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -20,6 +20,17 @@ class TestShipment(FrappeTestCase): self.assertEqual(len(second_shipment.shipment_delivery_note), 1) self.assertEqual(second_shipment.shipment_delivery_note[0].delivery_note, delivery_note.name) + def test_get_total_weight(self): + shipment = frappe.new_doc("Shipment") + shipment.extend( + "shipment_parcel", + [ + {"length": 5, "width": 5, "height": 5, "weight": 5, "count": 5}, + {"length": 5, "width": 5, "height": 5, "weight": 10, "count": 1}, + ], + ) + self.assertEqual(shipment.get_total_weight(), 35) + def create_test_delivery_note(): company = get_shipment_company() From f609012f02c28317dec6a03f986ee25689879312 Mon Sep 17 00:00:00 2001 From: 0xD0M1M0 <76812428+0xD0M1M0@users.noreply.github.com> Date: Fri, 21 Feb 2025 22:21:09 +0100 Subject: [PATCH 03/97] fix: discount accounting for v15 --- .../doctype/payment_entry/payment_entry.py | 6 ++- .../payment_entry/test_payment_entry.py | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 967b12599bf..d1533fe0131 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -3337,13 +3337,14 @@ def add_income_discount_loss(pe, doc, total_discount_percent) -> float: """Add loss on income discount in base currency.""" precision = doc.precision("total") base_loss_on_income = doc.get("base_total") * (total_discount_percent / 100) + positive_negative = -1 if pe.payment_type == "Pay" else 1 pe.append( "deductions", { "account": frappe.get_cached_value("Company", pe.company, "default_discount_account"), "cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"), - "amount": flt(base_loss_on_income, precision), + "amount": flt(base_loss_on_income, precision) * positive_negative, }, ) @@ -3355,6 +3356,7 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float: tax_discount_loss = {} base_total_tax_loss = 0 precision = doc.precision("tax_amount_after_discount_amount", "taxes") + positive_negative = -1 if pe.payment_type == "Pay" else 1 # The same account head could be used more than once for tax in doc.get("taxes", []): @@ -3377,7 +3379,7 @@ def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float: "account": account, "cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"), - "amount": flt(loss, precision), + "amount": flt(loss, precision) * positive_negative, }, ) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 5883d4e2f1f..e43ba85373c 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -282,6 +282,48 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(si.payment_schedule[0].paid_amount, 200.0) self.assertEqual(si.payment_schedule[1].paid_amount, 36.0) + def test_payment_entry_against_payment_terms_with_discount_on_pi(self): + pi = make_purchase_invoice(do_not_save=1) + create_payment_terms_template_with_discount() + pi.payment_terms_template = "Test Discount Template" + + frappe.db.set_value("Company", pi.company, "default_discount_account", "Write Off - _TC") + + pi.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 18, + }, + ) + pi.save() + pi.submit() + + frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1) + pe_with_tax_loss = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Cash - _TC") + + self.assertEqual(pe_with_tax_loss.references[0].payment_term, "30 Credit Days with 10% Discount") + self.assertEqual(pe_with_tax_loss.payment_type, "Pay") + self.assertEqual(pe_with_tax_loss.references[0].allocated_amount, 295.0) + self.assertEqual(pe_with_tax_loss.paid_amount, 265.5) + self.assertEqual(pe_with_tax_loss.difference_amount, 0) + self.assertEqual(pe_with_tax_loss.deductions[0].amount, -25.0) # Loss on Income + self.assertEqual(pe_with_tax_loss.deductions[1].amount, -4.5) # Loss on Tax + self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC") + + frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Cash - _TC") + + self.assertEqual(pe.references[0].payment_term, "30 Credit Days with 10% Discount") + self.assertEqual(pe.payment_type, "Pay") + self.assertEqual(pe.references[0].allocated_amount, 295.0) + self.assertEqual(pe.paid_amount, 265.5) + self.assertEqual(pe.deductions[0].amount, -29.5) + self.assertEqual(pe.difference_amount, 0) + def test_payment_entry_against_payment_terms_with_discount(self): si = create_sales_invoice(do_not_save=1, qty=1, rate=200) create_payment_terms_template_with_discount() From 20c44878533e830a706a4d5f60a507a62d01038f Mon Sep 17 00:00:00 2001 From: Ninad1306 Date: Mon, 24 Feb 2025 12:42:05 +0530 Subject: [PATCH 04/97] fix(report): allow `Closed` purchase orders to be visible (cherry picked from commit 3b2879d3a1e81fd71cc34fa68543411b67ade36a) # Conflicts: # erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js --- .../purchase_order_analysis.js | 13 ++++++++++++- .../purchase_order_analysis.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js index 58657da9168..3fe5bbdcb81 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js @@ -52,9 +52,20 @@ frappe.query_reports["Purchase Order Analysis"] = { label: __("Status"), fieldtype: "MultiSelectList", width: "80", - options: ["To Pay", "To Bill", "To Receive", "To Receive and Bill", "Completed"], + options: ["To Pay", "To Bill", "To Receive", "To Receive and Bill", "Completed", "Closed"], get_data: function (txt) { +<<<<<<< HEAD let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"]; +======= + let status = [ + "To Pay", + "To Bill", + "To Receive", + "To Receive and Bill", + "Completed", + "Closed", + ]; +>>>>>>> 3b2879d3a1 (fix(report): allow `Closed` purchase orders to be visible) let options = []; for (let option of status) { options.push({ diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index f583ce3e6c8..3efb9b021a9 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -70,7 +70,7 @@ def get_data(filters): po.company, po_item.name, ) - .where((po_item.parent == po.name) & (po.status.notin(("Stopped", "Closed"))) & (po.docstatus == 1)) + .where((po_item.parent == po.name) & (po.status.notin(("Stopped", "On Hold"))) & (po.docstatus == 1)) .groupby(po_item.name) .orderby(po.transaction_date) ) From 8799af974751998909fd0b73d611c39a87228a42 Mon Sep 17 00:00:00 2001 From: Ninad1306 Date: Tue, 25 Feb 2025 15:59:46 +0530 Subject: [PATCH 05/97] fix(report): allow `Closed` sales orders to be visible (cherry picked from commit 2394e76e7d6c33bc23d0c9bbb4acf27a47f02a69) # Conflicts: # erpnext/selling/report/sales_order_analysis/sales_order_analysis.js --- .../sales_order_analysis/sales_order_analysis.js | 13 ++++++++++++- .../sales_order_analysis/sales_order_analysis.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js index 5866fcbc845..3343c416749 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js @@ -53,10 +53,21 @@ frappe.query_reports["Sales Order Analysis"] = { fieldname: "status", label: __("Status"), fieldtype: "MultiSelectList", - options: ["To Pay", "To Bill", "To Deliver", "To Deliver and Bill", "Completed"], + options: ["To Pay", "To Bill", "To Deliver", "To Deliver and Bill", "Completed", "Closed"], width: "80", get_data: function (txt) { +<<<<<<< HEAD let status = ["To Bill", "To Deliver", "To Deliver and Bill", "Completed"]; +======= + let status = [ + "To Pay", + "To Bill", + "To Deliver", + "To Deliver and Bill", + "Completed", + "Closed", + ]; +>>>>>>> 2394e76e7d (fix(report): allow `Closed` sales orders to be visible) let options = []; for (let option of status) { options.push({ diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index 8fcf29bd7a6..90c33c323ce 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -86,7 +86,7 @@ def get_data(conditions, filters): ON sii.so_detail = soi.name and sii.docstatus = 1 WHERE soi.parent = so.name - and so.status not in ('Stopped', 'Closed', 'On Hold') + and so.status not in ('Stopped', 'On Hold') and so.docstatus = 1 {conditions} GROUP BY soi.name From 2221bf1cba47488ce897124ea92cf31bfaf4f84f Mon Sep 17 00:00:00 2001 From: Ninad1306 Date: Tue, 25 Feb 2025 17:13:09 +0530 Subject: [PATCH 06/97] fix(report): filter sales / purchase orders based on date filters (cherry picked from commit 936d7d434234c9e0447eb08a504f3a083db9ab42) --- .../purchase_order_analysis.js | 22 ++++++++++++++----- .../purchase_order_analysis.py | 8 ++++--- .../sales_order_analysis.js | 21 +++++++++++++----- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js index 3fe5bbdcb81..8d254722a4f 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js @@ -19,6 +19,10 @@ frappe.query_reports["Purchase Order Analysis"] = { width: "80", reqd: 1, default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + on_change: (report) => { + report.set_filter_value("name", []); + report.refresh(); + }, }, { fieldname: "to_date", @@ -27,6 +31,10 @@ frappe.query_reports["Purchase Order Analysis"] = { width: "80", reqd: 1, default: frappe.datetime.get_today(), + on_change: (report) => { + report.set_filter_value("name", []); + report.refresh(); + }, }, { fieldname: "project", @@ -38,13 +46,17 @@ frappe.query_reports["Purchase Order Analysis"] = { { fieldname: "name", label: __("Purchase Order"), - fieldtype: "Link", + fieldtype: "MultiSelectList", width: "80", options: "Purchase Order", - get_query: () => { - return { - filters: { docstatus: 1 }, - }; + get_data: function (txt) { + let filters = { docstatus: 1 }; + + const from_date = frappe.query_report.get_filter_value("from_date"); + const to_date = frappe.query_report.get_filter_value("to_date"); + if (from_date && to_date) filters["transaction_date"] = ["between", [from_date, to_date]]; + + return frappe.db.get_link_options("Purchase Order", txt, filters); }, }, { diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index 3efb9b021a9..b6bf1d9f8da 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -75,9 +75,11 @@ def get_data(filters): .orderby(po.transaction_date) ) - for field in ("company", "name"): - if filters.get(field): - query = query.where(po[field] == filters.get(field)) + if filters.get("company"): + query = query.where(po.company == filters.get("company")) + + if filters.get("name"): + query = query.where(po.name.isin(filters.get("name"))) if filters.get("from_date") and filters.get("to_date"): query = query.where(po.transaction_date.between(filters.get("from_date"), filters.get("to_date"))) diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js index 3343c416749..e470672b3a5 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js @@ -19,6 +19,10 @@ frappe.query_reports["Sales Order Analysis"] = { width: "80", reqd: 1, default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + on_change: (report) => { + report.set_filter_value("sales_order", []); + report.refresh(); + }, }, { fieldname: "to_date", @@ -27,6 +31,10 @@ frappe.query_reports["Sales Order Analysis"] = { width: "80", reqd: 1, default: frappe.datetime.get_today(), + on_change: (report) => { + report.set_filter_value("sales_order", []); + report.refresh(); + }, }, { fieldname: "sales_order", @@ -35,12 +43,13 @@ frappe.query_reports["Sales Order Analysis"] = { width: "80", options: "Sales Order", get_data: function (txt) { - return frappe.db.get_link_options("Sales Order", txt); - }, - get_query: () => { - return { - filters: { docstatus: 1 }, - }; + let filters = { docstatus: 1 }; + + const from_date = frappe.query_report.get_filter_value("from_date"); + const to_date = frappe.query_report.get_filter_value("to_date"); + if (from_date && to_date) filters["transaction_date"] = ["between", [from_date, to_date]]; + + return frappe.db.get_link_options("Sales Order", txt, filters); }, }, { From 3fb9033fb70839bcd33e9e198ac3123501be277e Mon Sep 17 00:00:00 2001 From: vishakhdesai Date: Thu, 27 Feb 2025 18:18:49 +0530 Subject: [PATCH 07/97] fix: payment entry exchange gain loss issue (cherry picked from commit 2dbef23244ed40871561489dccbe78e5d795affe) --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 55fab670fd8..2e13c932de4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -258,6 +258,10 @@ frappe.ui.form.on("Payment Entry", { frappe.flags.allocate_payment_amount = true; }, + validate: async function (frm) { + await frm.events.set_exchange_gain_loss_deduction(frm); + }, + validate_company: (frm) => { if (!frm.doc.company) { frappe.throw({ message: __("Please select a Company first."), title: __("Mandatory") }); @@ -1893,8 +1897,6 @@ function prompt_for_missing_account(frm, account) { (values) => resolve(values?.[account]), __("Please Specify Account") ); - - dialog.on_hide = () => resolve(""); }); } From a94292a69fb9672d2ec68762fadcc3aff99bf613 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 28 Feb 2025 09:48:11 +0530 Subject: [PATCH 08/97] fix: removed mandatory property for the cost center field (cherry picked from commit 079cf772aaf519eb6b16abef16a6d0f85187988b) --- erpnext/selling/doctype/sales_order_item/sales_order_item.json | 3 +-- erpnext/selling/doctype/sales_order_item/sales_order_item.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 500a7657176..ea1a646ba2d 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -946,7 +946,6 @@ "options": "Cost Center", "print_hide": 1, "print_width": "120px", - "reqd": 1, "width": "120px" }, { @@ -965,7 +964,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2025-02-06 13:29:24.619850", + "modified": "2025-02-28 09:45:43.934947", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.py b/erpnext/selling/doctype/sales_order_item/sales_order_item.py index 7f2a37b1616..2fa06ac7299 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.py +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.py @@ -32,7 +32,7 @@ class SalesOrderItem(Document): brand: DF.Link | None company_total_stock: DF.Float conversion_factor: DF.Float - cost_center: DF.Link + cost_center: DF.Link | None customer_item_code: DF.Data | None delivered_by_supplier: DF.Check delivered_qty: DF.Float From 0f263bcff286017065b776bdafb27325a0719596 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 28 Feb 2025 15:43:59 +0530 Subject: [PATCH 09/97] fix: pos item selection using serial no (backport #46200) (#46203) fix: pos item selection using serial no (#46200) (cherry picked from commit 8fb09decd269ddb01ed45dd68eb557b14e2f7acf) Co-authored-by: Diptanil Saha --- erpnext/selling/page/point_of_sale/pos_controller.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index bfde624f7b3..e7208c41dde 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -605,6 +605,14 @@ erpnext.PointOfSale.Controller = class { if (this.is_current_item_being_edited(item_row) || from_selector) { await frappe.model.set_value(item_row.doctype, item_row.name, field, value); + if (item.serial_no && from_selector) { + await frappe.model.set_value( + item_row.doctype, + item_row.name, + "serial_no", + item_row.serial_no + `\n${item.serial_no}` + ); + } this.update_cart_html(item_row); } } else { From de0dfbca9afaec9152462cd1f06d63d449f8fbae Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 28 Feb 2025 23:24:49 +0530 Subject: [PATCH 10/97] fix: pos item detail serial no field (backport #46211) (#46212) fix: pos item detail serial no field (#46211) (cherry picked from commit d2fad44e894b4bbbd1033d105e7e8d0c3b94c09c) Co-authored-by: Diptanil Saha --- .../selling/page/point_of_sale/pos_item_details.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 333b50810c9..e0abdd4f4c3 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -187,6 +187,7 @@ erpnext.PointOfSale.ItemDetails = class { this[`${fieldname}_control`].set_value(item[fieldname]); }); + this.resize_serial_control(item); this.make_auto_serial_selection_btn(item); this.bind_custom_control_change_event(); @@ -203,11 +204,17 @@ erpnext.PointOfSale.ItemDetails = class { "actual_qty", "price_list_rate", ]; - if (item.has_serial_no) fields.push("serial_no"); - if (item.has_batch_no) fields.push("batch_no"); + if (item.has_serial_no || item.serial_no) fields.push("serial_no"); + if (item.has_batch_no || item.batch_no) fields.push("batch_no"); return fields; } + resize_serial_control(item) { + if (item.has_serial_no || item.serial_no) { + this.$form_container.find(".serial_no-control").find("textarea").css("height", "6rem"); + } + } + make_auto_serial_selection_btn(item) { if (item.has_serial_no || item.has_batch_no) { if (item.has_serial_no && item.has_batch_no) { @@ -225,7 +232,6 @@ erpnext.PointOfSale.ItemDetails = class { `
${label}
` ); } - this.$form_container.find(".serial_no-control").find("textarea").css("height", "6rem"); } } From 87703c6511130a9d7885815b3ff3797b2cbb8e9a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 28 Feb 2025 15:30:42 +0530 Subject: [PATCH 11/97] fix: rearrange stock settings fields (cherry picked from commit 93f461c6f34135a63de10f1068ca216898214645) # Conflicts: # erpnext/stock/doctype/stock_settings/stock_settings.json --- .../stock_settings/stock_settings.json | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 22a24d1bfa1..282539c9d90 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -46,18 +46,20 @@ "auto_reserve_serial_and_batch", "serial_and_batch_item_settings_tab", "section_break_7", + "allow_existing_serial_no", "do_not_use_batchwise_valuation", "auto_create_serial_and_batch_bundle_for_outward", "pick_serial_and_batch_based_on", - "naming_series_prefix", "column_break_mhzc", "disable_serial_no_and_batch_selector", - "use_naming_series", "use_serial_batch_fields", "do_not_update_serial_batch_on_creation_of_auto_bundle", - "allow_existing_serial_no", "serial_and_batch_bundle_section", "set_serial_and_batch_bundle_naming_based_on_naming_series", + "section_break_gnhq", + "use_naming_series", + "column_break_wslv", + "naming_series_prefix", "stock_planning_tab", "auto_material_request", "auto_indent", @@ -480,6 +482,14 @@ "fieldname": "set_serial_and_batch_bundle_naming_based_on_naming_series", "fieldtype": "Check", "label": "Set Serial and Batch Bundle Naming Based on Naming Series" + }, + { + "fieldname": "section_break_gnhq", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_wslv", + "fieldtype": "Column Break" } ], "icon": "icon-cog", @@ -487,7 +497,11 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2025-02-17 14:36:36.177743", +======= + "modified": "2025-02-28 15:08:35.938840", +>>>>>>> 93f461c6f3 (fix: rearrange stock settings fields) "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From 93fed0ce86b91c97e25dfd0685b657b47029290b Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 1 Mar 2025 12:53:44 +0530 Subject: [PATCH 12/97] chore: fix conflicts --- erpnext/stock/doctype/stock_settings/stock_settings.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 282539c9d90..1987bc8642d 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -497,11 +497,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], -<<<<<<< HEAD - "modified": "2025-02-17 14:36:36.177743", -======= - "modified": "2025-02-28 15:08:35.938840", ->>>>>>> 93f461c6f3 (fix: rearrange stock settings fields) + "modified": "2025-02-28 16:08:35.938840", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From 331798babc6f7ad40d0e5d9b73c71b1eef66409d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 1 Mar 2025 21:34:06 +0530 Subject: [PATCH 13/97] fix: stock qty not recalculate on changing of the qty (cherry picked from commit 464e3339fed51b80addbff455022fb9d6c8147cd) --- erpnext/public/js/controllers/transaction.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 81a8a617e64..69ecac19852 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1331,6 +1331,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe () => this.calculate_stock_uom_rate(doc, cdt, cdn), () => this.apply_pricing_rule(item, true) ]); + } else { + this.conversion_factor(doc, cdt, cdn, true) } } From 84ca0ada1b2b11ef2ed2a1ecec937a4101fe55c4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 01:59:13 +0530 Subject: [PATCH 14/97] refactor: using function to unset grand total to default mode of payment in pos (backport #46228) (#46229) refactor: using function to unset grand total to default mode of payment in pos (#46228) (cherry picked from commit 62c3915ecb309569a09344282398ea3f81ede7a1) Co-authored-by: Diptanil Saha --- .../selling/page/point_of_sale/pos_payment.js | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index fc8b75031c8..b47c25e20bb 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -340,19 +340,11 @@ erpnext.PointOfSale.Payment = class { // pass } - async render_payment_section() { + render_payment_section() { this.render_payment_mode_dom(); this.make_invoice_fields_control(); this.update_totals_section(); - let r = await frappe.db.get_value( - "POS Profile", - this.frm.doc.pos_profile, - "disable_grand_total_to_default_mop" - ); - - if (!r.message.disable_grand_total_to_default_mop) { - this.focus_on_default_mop(); - } + this.unset_grand_total_to_default_mop(); } after_render() { @@ -637,6 +629,19 @@ erpnext.PointOfSale.Payment = class { .toLowerCase(); } + async unset_grand_total_to_default_mop() { + const doc = this.events.get_frm().doc; + let r = await frappe.db.get_value( + "POS Profile", + doc.pos_profile, + "disable_grand_total_to_default_mop" + ); + + if (!r.message.disable_grand_total_to_default_mop) { + this.focus_on_default_mop(); + } + } + validate_reqd_invoice_fields() { const doc = this.events.get_frm().doc; let validation_flag = true; From 9e649d852237a54bae334f7c6f413cbdd38f54c5 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sun, 2 Mar 2025 11:29:39 +0530 Subject: [PATCH 15/97] fix: dont update rate of free item when batch is updated (cherry picked from commit a3596f717b6a8145535d4a133d518f565e4ec0c5) # Conflicts: # erpnext/stock/get_item_details.py --- erpnext/stock/get_item_details.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 5c5fe5db276..58f83d7a593 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -221,7 +221,7 @@ def update_stock(ctx, out, doc=None): else: qty -= batch_qty - out.update({"batch_no": batch_no, "actual_batch_qty": qty}) + out.update({"batch_no": batch_no, "actual_batch_qty": batch_qty}) if rate: out.update({"rate": rate, "price_list_rate": rate}) @@ -1051,8 +1051,13 @@ def get_batch_based_item_price(params, item_code) -> float: if not item_price: item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True) +<<<<<<< HEAD if item_price and item_price[0][2] == params.get("uom"): return item_price[0][1] +======= + if item_price and item_price[0].uom == pctx.uom and not pctx.get("items")[0].get("is_free_item"): + return item_price[0].price_list_rate +>>>>>>> a3596f717b (fix: dont update rate of free item when batch is updated) return 0.0 From 61d5680c8daf0b683be3460487711f98ac2d2232 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sun, 2 Mar 2025 12:07:31 +0530 Subject: [PATCH 16/97] fix: error (cherry picked from commit 7c9c0c7776b709005e4abe0edaa2591e3fa1e7cc) # Conflicts: # erpnext/stock/get_item_details.py --- erpnext/stock/get_item_details.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 58f83d7a593..dee4c362363 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1051,11 +1051,15 @@ def get_batch_based_item_price(params, item_code) -> float: if not item_price: item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True) +<<<<<<< HEAD <<<<<<< HEAD if item_price and item_price[0][2] == params.get("uom"): return item_price[0][1] ======= if item_price and item_price[0].uom == pctx.uom and not pctx.get("items")[0].get("is_free_item"): +======= + if item_price and item_price[0].uom == pctx.uom and not pctx.get("items", [{}])[0].get("is_free_item", 0): +>>>>>>> 7c9c0c7776 (fix: error) return item_price[0].price_list_rate >>>>>>> a3596f717b (fix: dont update rate of free item when batch is updated) From faee8d6c5e54184cbc223f3a2dd1f21e5b41b63e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 11:16:37 +0530 Subject: [PATCH 17/97] fix: don't allow renaming account while system is actively in use (backport #46176) (#46210) fix: don't allow renaming account while system is actively in use (#46176) (cherry picked from commit 999f1cf96db3752ca816093a1f3b75236688ab71) Co-authored-by: Ankush Menat --- erpnext/accounts/doctype/account/account.py | 28 ++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index b510651e68f..4098084a802 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -4,7 +4,7 @@ import frappe from frappe import _, throw -from frappe.utils import cint, cstr +from frappe.utils import add_to_date, cint, cstr, pretty_date from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of import erpnext @@ -481,6 +481,7 @@ def get_account_autoname(account_number, account_name, company): @frappe.whitelist() def update_account_number(name, account_name, account_number=None, from_descendant=False): + _ensure_idle_system() account = frappe.get_cached_doc("Account", name) if not account: return @@ -542,6 +543,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda @frappe.whitelist() def merge_account(old, new): + _ensure_idle_system() # Validate properties before merging new_account = frappe.get_cached_doc("Account", new) old_account = frappe.get_cached_doc("Account", old) @@ -595,3 +597,27 @@ def sync_update_account_number_in_child( for d in frappe.db.get_values("Account", filters=filters, fieldname=["company", "name"], as_dict=True): update_account_number(d["name"], account_name, account_number, from_descendant=True) + + +def _ensure_idle_system(): + # Don't allow renaming if accounting entries are actively being updated, there are two main reasons: + # 1. Correctness: It's next to impossible to ensure that renamed account is not being used *right now*. + # 2. Performance: Renaming requires locking out many tables entirely and severely degrades performance. + + if frappe.flags.in_test: + return + + try: + # We also lock inserts to GL entry table with for_update here. + last_gl_update = frappe.db.get_value("GL Entry", {}, "modified", for_update=True, wait=False) + except frappe.QueryTimeoutError: + # wait=False fails immediately if there's an active transaction. + last_gl_update = add_to_date(None, seconds=-1) + + if last_gl_update > add_to_date(None, minutes=-5): + frappe.throw( + _( + "Last GL Entry update was done {}. This operation is not allowed while system is actively being used. Please wait for 5 minutes before retrying." + ).format(pretty_date(last_gl_update)), + title=_("System In Use"), + ) From 899e468f6a70000a10f104a9db5fc53e36ee160e Mon Sep 17 00:00:00 2001 From: venkat102 Date: Fri, 28 Feb 2025 15:14:25 +0530 Subject: [PATCH 18/97] fix(asset depreciation schedules): enable auto commit (cherry picked from commit a4b24f7451c7d7c156d97dda62824c4ba09045a4) --- .../v15_0/create_asset_depreciation_schedules_from_assets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index ff77fbb91ec..d4350d8f9a1 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -82,6 +82,9 @@ def get_asset_depreciation_schedules_map(): .orderby(ds.idx) ).run(as_dict=True) + if len(records) > 20000: + frappe.db.auto_commit_on_many_writes = True + asset_depreciation_schedules_map = frappe._dict() for d in records: asset_depreciation_schedules_map.setdefault((d.asset_name, cstr(d.finance_book)), []).append(d) From 7f4d553201c496cc75bcf370aa84f3fff143a775 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 3 Mar 2025 12:28:06 +0530 Subject: [PATCH 19/97] chore: resolve conflicts --- erpnext/stock/get_item_details.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index dee4c362363..68d4cc85c42 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1051,17 +1051,8 @@ def get_batch_based_item_price(params, item_code) -> float: if not item_price: item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True) -<<<<<<< HEAD -<<<<<<< HEAD - if item_price and item_price[0][2] == params.get("uom"): - return item_price[0][1] -======= - if item_price and item_price[0].uom == pctx.uom and not pctx.get("items")[0].get("is_free_item"): -======= if item_price and item_price[0].uom == pctx.uom and not pctx.get("items", [{}])[0].get("is_free_item", 0): ->>>>>>> 7c9c0c7776 (fix: error) return item_price[0].price_list_rate ->>>>>>> a3596f717b (fix: dont update rate of free item when batch is updated) return 0.0 From c247cf888b03cfcf172d90110611a89e23c16338 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 3 Mar 2025 13:38:38 +0530 Subject: [PATCH 20/97] fix: incorrectly billed amount in the purchase receipt (cherry picked from commit a5271fdb2e5c826ec24162ddd101ae95ed9fdcd7) --- .../purchase_receipt/purchase_receipt.py | 6 +++- .../purchase_receipt/test_purchase_receipt.py | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 0328c447ec2..7aa23c8153b 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1048,15 +1048,19 @@ def get_billed_amount_against_po(po_items): if not po_items: return {} + purchase_invoice = frappe.qb.DocType("Purchase Invoice") purchase_invoice_item = frappe.qb.DocType("Purchase Invoice Item") query = ( frappe.qb.from_(purchase_invoice_item) + .inner_join(purchase_invoice) + .on(purchase_invoice_item.parent == purchase_invoice.name) .select(fn.Sum(purchase_invoice_item.amount).as_("billed_amt"), purchase_invoice_item.po_detail) .where( (purchase_invoice_item.po_detail.isin(po_items)) - & (purchase_invoice_item.docstatus == 1) + & (purchase_invoice.docstatus == 1) & (purchase_invoice_item.pr_detail.isnull()) + & (purchase_invoice.update_stock == 0) ) .groupby(purchase_invoice_item.po_detail) ).run(as_dict=1) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index b097c0e6441..9cf9f4d4958 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -4047,6 +4047,36 @@ class TestPurchaseReceipt(FrappeTestCase): batch_return.save() batch_return.submit() + def test_pr_status_based_on_invoices_with_update_stock(self): + from erpnext.buying.doctype.purchase_order.purchase_order import ( + make_purchase_invoice as _make_purchase_invoice, + ) + from erpnext.buying.doctype.purchase_order.purchase_order import ( + make_purchase_receipt as _make_purchase_receipt, + ) + from erpnext.buying.doctype.purchase_order.test_purchase_order import ( + create_pr_against_po, + create_purchase_order, + ) + + item_code = "Test Item for PR Status Based on Invoices" + create_item(item_code) + + po = create_purchase_order(item_code=item_code, qty=10) + pi = _make_purchase_invoice(po.name) + pi.update_stock = 1 + pi.items[0].qty = 5 + pi.submit() + + po.reload() + self.assertEqual(po.per_billed, 50) + + pr = _make_purchase_receipt(po.name) + self.assertEqual(pr.items[0].qty, 5) + pr.submit() + pr.reload() + self.assertEqual(pr.status, "To Bill") + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From e94f0b1ccaa6b2cf96c67cd4013c76343b668ff3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 2 Mar 2025 12:36:17 +0530 Subject: [PATCH 21/97] fix: incorrect batch picked (cherry picked from commit d2564cad684d998dac15995017d79566525dee3c) --- erpnext/public/js/utils/serial_no_batch_selector.js | 2 ++ erpnext/stock/serial_batch_bundle.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index de1faf36ef5..e02d7a3d785 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -540,6 +540,8 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { has_batch_no: this.item.has_batch_no, qty: qty, based_on: based_on, + posting_date: this.frm.doc.posting_date, + posting_time: this.frm.doc.posting_time, }, callback: (r) => { if (r.message) { diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 993d918f8bc..c88df01665f 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -1006,6 +1006,10 @@ class SerialBatchCreation: elif self.has_serial_no and not self.get("serial_nos"): self.serial_nos = get_serial_nos_for_outward(kwargs) elif not self.has_serial_no and self.has_batch_no and not self.get("batches"): + if self.get("posting_date"): + kwargs["posting_date"] = self.get("posting_date") + kwargs["posting_time"] = self.get("posting_time") + self.batches = get_available_batches(kwargs) def set_auto_serial_batch_entries_for_inward(self): From 81c7b8c273d1ef7ceab82e00030c7d7a11530415 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 3 Mar 2025 15:05:44 +0530 Subject: [PATCH 22/97] chore: resolve conflicts --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 68d4cc85c42..cd5f7107561 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1051,7 +1051,7 @@ def get_batch_based_item_price(params, item_code) -> float: if not item_price: item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True) - if item_price and item_price[0].uom == pctx.uom and not pctx.get("items", [{}])[0].get("is_free_item", 0): + if item_price and item_price[0].uom == params.uom and not params.get("items", [{}])[0].get("is_free_item", 0): return item_price[0].price_list_rate return 0.0 From cc535b76365325d189d7faee0556a23679bb6bb1 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 3 Mar 2025 15:27:35 +0530 Subject: [PATCH 23/97] fix: syntax error --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index cd5f7107561..f220024e415 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1051,7 +1051,7 @@ def get_batch_based_item_price(params, item_code) -> float: if not item_price: item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True) - if item_price and item_price[0].uom == params.uom and not params.get("items", [{}])[0].get("is_free_item", 0): + if item_price and item_price[0].uom == params.get("uom") and not params.get("items", [{}])[0].get("is_free_item", 0): return item_price[0].price_list_rate return 0.0 From bd48d391e4a9c6e78e0e9896da9ebf8bce64efc5 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 3 Mar 2025 16:40:19 +0530 Subject: [PATCH 24/97] fix: syntax error --- erpnext/stock/get_item_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index f220024e415..23e6e003bb0 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1051,8 +1051,8 @@ def get_batch_based_item_price(params, item_code) -> float: if not item_price: item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True) - if item_price and item_price[0].uom == params.get("uom") and not params.get("items", [{}])[0].get("is_free_item", 0): - return item_price[0].price_list_rate + if item_price and item_price[0][2] == params.get("uom") and not params.get("items", [{}])[0].get("is_free_item", 0): + return item_price[0][1] return 0.0 From 90dea426d8adc86a56a37666e1f3f7b274b0df2f Mon Sep 17 00:00:00 2001 From: Ninad1306 Date: Tue, 18 Feb 2025 12:52:33 +0530 Subject: [PATCH 25/97] fix: set taxes before calculating taxes and totals (cherry picked from commit 0fd0695bbbaec834bd120b4fdc93bc7142f60532) --- erpnext/controllers/accounts_controller.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index bb0a4070981..884ce4de71b 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -194,6 +194,14 @@ class AccountsController(TransactionBase): self.set_incoming_rate() self.init_internal_values() + # Need to set taxes based on taxes_and_charges template + # before calculating taxes and totals + if self.meta.get_field("taxes_and_charges"): + self.validate_enabled_taxes_and_charges() + self.validate_tax_account_company() + + self.set_taxes_and_charges() + if self.meta.get_field("currency"): self.calculate_taxes_and_totals() @@ -204,10 +212,6 @@ class AccountsController(TransactionBase): self.validate_all_documents_schedule() - if self.meta.get_field("taxes_and_charges"): - self.validate_enabled_taxes_and_charges() - self.validate_tax_account_company() - self.validate_party() self.validate_currency() self.validate_party_account_currency() @@ -252,8 +256,6 @@ class AccountsController(TransactionBase): self.validate_deferred_income_expense_account() self.set_inter_company_account() - self.set_taxes_and_charges() - if self.doctype == "Purchase Invoice": self.calculate_paid_amount() # apply tax withholding only if checked and applicable From 41e9a10ab413fd4d2c279aedb0fe08ff05426f2b Mon Sep 17 00:00:00 2001 From: Ninad1306 Date: Tue, 18 Feb 2025 14:03:35 +0530 Subject: [PATCH 26/97] test: validate fetching of taxes based on taxes and charges template (cherry picked from commit 196ef7ac4e496785ac7fb5dbff2e13c9317071bc) --- .../tests/test_accounts_controller.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 2c46f04af71..ccabb361b32 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -931,6 +931,35 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) + @IntegrationTestCase.change_settings("Accounts Settings", {"add_taxes_from_item_tax_template": 1}) + def test_18_fetch_taxes_based_on_taxes_and_charges_template(self): + # Create a Sales Taxes and Charges Template + if not frappe.db.exists("Sales Taxes and Charges Template", "_Test Tax - _TC"): + doc = frappe.new_doc("Sales Taxes and Charges Template") + doc.company = self.company + doc.title = "_Test Tax" + doc.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "Sales Expenses - _TC", + "description": "Test taxes", + "rate": 9, + }, + ) + doc.insert() + + # Create a Sales Invoice + sinv = frappe.new_doc("Sales Invoice") + sinv.customer = self.customer + sinv.company = self.company + sinv.currency = "INR" + sinv.taxes_and_charges = "_Test Tax - _TC" + sinv.append("items", {"item_code": "_Test Item", "qty": 1, "rate": 50}) + sinv.insert() + + self.assertEqual(sinv.total_taxes_and_charges, 4.5) + def test_20_journal_against_sales_invoice(self): # Invoice in Foreign Currency si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1) From 5e083861a419b6ead0071df9004f5c3d1c6f4977 Mon Sep 17 00:00:00 2001 From: Ninad1306 Date: Fri, 28 Feb 2025 11:08:30 +0530 Subject: [PATCH 27/97] fix: exclude cancelled gl entries (cherry picked from commit 3251a331dd76767e51f49dfb3cbbad6e932cb7cc) --- .../report/budget_variance_report/budget_variance_report.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index e540aa9993c..db42d23a839 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -263,6 +263,7 @@ def get_actual_details(name, filters): and ba.account=gl.account and b.{budget_against} = gl.{budget_against} and gl.fiscal_year between %s and %s + and gl.is_cancelled = 0 and b.{budget_against} = %s and exists( select From 35df539da3d059c82f798541a80ab2e5ccc555c7 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 3 Mar 2025 17:02:15 +0530 Subject: [PATCH 28/97] chore: fix linters --- erpnext/controllers/tests/test_accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index ccabb361b32..f959cbd0488 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -931,7 +931,7 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) - @IntegrationTestCase.change_settings("Accounts Settings", {"add_taxes_from_item_tax_template": 1}) + @change_settings("Accounts Settings", {"add_taxes_from_item_tax_template": 1}) def test_18_fetch_taxes_based_on_taxes_and_charges_template(self): # Create a Sales Taxes and Charges Template if not frappe.db.exists("Sales Taxes and Charges Template", "_Test Tax - _TC"): From 8ed512f6c6e7cced1e48fb93ed71e9a08a46a8aa Mon Sep 17 00:00:00 2001 From: Nirmalrajaa K Date: Fri, 21 Feb 2025 14:57:16 +0530 Subject: [PATCH 29/97] fix: Batch Price gets updated only if it is a billed item (cherry picked from commit 9597b1a69e965638d68c7018fc564ba71e9d4246) # Conflicts: # erpnext/stock/get_item_details.py --- erpnext/stock/get_item_details.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 5c5fe5db276..1cc4cb750db 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1051,8 +1051,14 @@ def get_batch_based_item_price(params, item_code) -> float: if not item_price: item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True) +<<<<<<< HEAD if item_price and item_price[0][2] == params.get("uom"): return item_price[0][1] +======= + + if item_price and item_price[0].uom == pctx.uom and params.get("is_free_item") == 0: + return item_price[0].price_list_rate +>>>>>>> 9597b1a69e (fix: Batch Price gets updated only if it is a billed item) return 0.0 From dbd47dff985019d18a5b68de44149e81dd1663cf Mon Sep 17 00:00:00 2001 From: Nirmalrajaa K Date: Sat, 22 Feb 2025 22:04:00 +0530 Subject: [PATCH 30/97] fix: Batch Price gets updated only if it is a billed item (cherry picked from commit 1a56b83054003cde73aa2aa7c16f80e82be58a20) # Conflicts: # erpnext/stock/get_item_details.py --- erpnext/stock/get_item_details.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1cc4cb750db..71a58e7f836 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1051,12 +1051,16 @@ def get_batch_based_item_price(params, item_code) -> float: if not item_price: item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True) +<<<<<<< HEAD <<<<<<< HEAD if item_price and item_price[0][2] == params.get("uom"): return item_price[0][1] ======= +======= + is_free_item = pctx.get('items', [{}])[0].get('is_free_item') +>>>>>>> 1a56b83054 (fix: Batch Price gets updated only if it is a billed item) - if item_price and item_price[0].uom == pctx.uom and params.get("is_free_item") == 0: + if item_price and item_price[0].uom == pctx.uom and not is_free_item: return item_price[0].price_list_rate >>>>>>> 9597b1a69e (fix: Batch Price gets updated only if it is a billed item) From 6762dc3392db930d920f5042eadb95ddf4d83998 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 3 Mar 2025 14:01:20 +0530 Subject: [PATCH 31/97] chore: linter fix (cherry picked from commit 0a2193e4589a9e748041001a8eb592be2bf091ce) # Conflicts: # erpnext/stock/get_item_details.py --- erpnext/stock/get_item_details.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 71a58e7f836..66c5ae28478 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1051,6 +1051,7 @@ def get_batch_based_item_price(params, item_code) -> float: if not item_price: item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True) +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD if item_price and item_price[0][2] == params.get("uom"): @@ -1059,6 +1060,9 @@ def get_batch_based_item_price(params, item_code) -> float: ======= is_free_item = pctx.get('items', [{}])[0].get('is_free_item') >>>>>>> 1a56b83054 (fix: Batch Price gets updated only if it is a billed item) +======= + is_free_item = pctx.get("items", [{}])[0].get("is_free_item") +>>>>>>> 0a2193e458 (chore: linter fix) if item_price and item_price[0].uom == pctx.uom and not is_free_item: return item_price[0].price_list_rate From f3cafef6a744e728b74299ac55c757fde4d94ba9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 21:37:02 +0530 Subject: [PATCH 32/97] fix(patch): Ensure SLE indexes (backport #46131) (#46135) * fix(patch): Ensure SLE indexes (#46131) Because of the way this change was pushed in parts, some sites don't see this as "update" and don't have the new indexes. (cherry picked from commit f62aa8fc57dcf4f2d4282811db1c85ff4259a2a5) # Conflicts: # erpnext/patches.txt * fix: resolved conflict --------- Co-authored-by: Ankush Menat Co-authored-by: Nabin Hait --- erpnext/patches.txt | 1 + .../stock/doctype/stock_ledger_entry/patches/__init__.py | 0 .../stock_ledger_entry/patches/ensure_sle_indexes.py | 9 +++++++++ 3 files changed, 10 insertions(+) create mode 100644 erpnext/stock/doctype/stock_ledger_entry/patches/__init__.py create mode 100644 erpnext/stock/doctype/stock_ledger_entry/patches/ensure_sle_indexes.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index cb10fcce1c4..df1b885ad18 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -394,3 +394,4 @@ execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_post erpnext.patches.v14_0.disable_add_row_in_gross_profit erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime +erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes diff --git a/erpnext/stock/doctype/stock_ledger_entry/patches/__init__.py b/erpnext/stock/doctype/stock_ledger_entry/patches/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/doctype/stock_ledger_entry/patches/ensure_sle_indexes.py b/erpnext/stock/doctype/stock_ledger_entry/patches/ensure_sle_indexes.py new file mode 100644 index 00000000000..7f29b27af3f --- /dev/null +++ b/erpnext/stock/doctype/stock_ledger_entry/patches/ensure_sle_indexes.py @@ -0,0 +1,9 @@ +from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import ( + on_doctype_update as create_sle_indexes, +) + + +def execute(): + """Ensure SLE Indexes""" + + create_sle_indexes() From ef195513d0056f9b8069532e310e4902f469c564 Mon Sep 17 00:00:00 2001 From: Sanket Shah <113279972+Sanket322@users.noreply.github.com> Date: Mon, 3 Mar 2025 21:38:11 +0530 Subject: [PATCH 33/97] fix: Convert tuple of tuples to list of dicts for dot notation access (#46062) fix: use as_dict to convert tuples into list of dict Co-authored-by: Sanket322 (cherry picked from commit e4b0ab6656e9caf5d22fdc8ee8ac400cc99c1c9d) --- .../doctype/tax_withholding_category/tax_withholding_category.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 06549973242..a355e5ddf44 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -436,6 +436,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): tax_details.get("tax_withholding_category"), company, ), + as_dict=1, ) for d in journal_entries_details: From 0a65217423035b0fe6e8a2934f1bd2aed85e2ac9 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 27 Feb 2025 13:17:57 +0530 Subject: [PATCH 34/97] fix: if invoice is return then add amount in proper column (cherry picked from commit ccb4bdbe4cd7e11118621968f4097aa20fc0dd1d) --- .../accounts_receivable/accounts_receivable.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 1ddf9bce06f..c7a0da5afe9 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -267,6 +267,18 @@ class ReceivablePayableReport: row.invoiced_in_account_currency += amount_in_account_currency else: if self.is_invoice(ple): + # when invoice has is_return marked + if self.invoice_details.get(row.voucher_no, {}).get("is_return"): + # for Credit Note + if row.voucher_type == "Sales Invoice": + row.credit_note -= amount + row.credit_note_in_account_currency -= amount_in_account_currency + # for Debit Note + else: + row.invoiced -= amount + row.invoiced_in_account_currency -= amount_in_account_currency + return + if row.voucher_no == ple.voucher_no == ple.against_voucher_no: row.paid -= amount row.paid_in_account_currency -= amount_in_account_currency @@ -421,7 +433,7 @@ class ReceivablePayableReport: # nosemgrep si_list = frappe.db.sql( """ - select name, due_date, po_no + select name, due_date, po_no, is_return from `tabSales Invoice` where posting_date <= %s and company = %s @@ -453,7 +465,7 @@ class ReceivablePayableReport: # nosemgrep for pi in frappe.db.sql( """ - select name, due_date, bill_no, bill_date + select name, due_date, bill_no, bill_date, is_return from `tabPurchase Invoice` where posting_date <= %s From 9f4311e7fbaa4ad607747f61a3a4b95ab61b75a2 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 27 Feb 2025 16:52:13 +0530 Subject: [PATCH 35/97] fix: fixing test case (cherry picked from commit 9b2b477ae05f1d0d6ccb5ce990061956e90acaef) --- .../accounts_receivable/test_accounts_receivable.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 39ca78153c3..f3513286c9e 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -204,7 +204,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): expected_data_after_credit_note = [ [100.0, 100.0, 40.0, 0.0, 60.0, si.name], - [0, 0, 100.0, 0.0, -100.0, cr_note.name], + [0, 0, 0, 100.0, -100.0, cr_note.name], ] self.assertEqual(len(report[1]), 2) si_row = next( @@ -478,13 +478,19 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): report = execute(filters)[1] self.assertEqual(len(report), 2) - expected_data = {sr.name: [10.0, -10.0, 0.0, -10], si.name: [100.0, 100.0, 10.0, 90.0]} + expected_data = {sr.name: [0.0, 10.0, -10.0, 0.0, -10], si.name: [100.0, 0.0, 100.0, 10.0, 90.0]} rows = report[:2] for row in rows: self.assertEqual( expected_data[row.voucher_no], - [row.invoiced or row.paid, row.outstanding, row.remaining_balance, row.future_amount], + [ + row.invoiced or row.paid, + row.credit_note, + row.outstanding, + row.remaining_balance, + row.future_amount, + ], ) pe.cancel() From a8b31df65ddeae3e0fcee7895dbafc9691034f10 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 27 Feb 2025 17:14:29 +0530 Subject: [PATCH 36/97] fix: test case for debit note (cherry picked from commit 6719bbeb10156f1c0d7b148e178cafaa3014e248) --- .../accounts_payable/test_accounts_payable.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index 8971dc3d37b..69f332d9800 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -38,6 +38,23 @@ class TestAccountsPayable(AccountsTestMixin, FrappeTestCase): self.assertEqual(data[1][0].get("outstanding"), 300) self.assertEqual(data[1][0].get("currency"), "USD") + def test_account_payable_for_debit_note(self): + pi = self.create_purchase_invoice(do_not_submit=True) + pi.is_return = 1 + pi.items[0].qty = -1 + pi = pi.save().submit() + + filters = { + "company": self.company, + "party_type": "Supplier", + "party": [self.supplier], + "report_date": today(), + "range": "30, 60, 90, 120", + } + + data = execute(filters) + self.assertEqual(data[1][0].get("invoiced"), 300) + def create_purchase_invoice(self, do_not_submit=False): frappe.set_user("Administrator") pi = make_purchase_invoice( From 854632dd510c5371307d543d1cf04ce3c7e5cfae Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 3 Mar 2025 20:28:24 +0530 Subject: [PATCH 37/97] chore: resolve conflict --- erpnext/stock/get_item_details.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 66c5ae28478..c592d31f4dd 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1051,22 +1051,10 @@ def get_batch_based_item_price(params, item_code) -> float: if not item_price: item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - if item_price and item_price[0][2] == params.get("uom"): - return item_price[0][1] -======= -======= - is_free_item = pctx.get('items', [{}])[0].get('is_free_item') ->>>>>>> 1a56b83054 (fix: Batch Price gets updated only if it is a billed item) -======= - is_free_item = pctx.get("items", [{}])[0].get("is_free_item") ->>>>>>> 0a2193e458 (chore: linter fix) + is_free_item = params.get("items", [{}])[0].get("is_free_item") - if item_price and item_price[0].uom == pctx.uom and not is_free_item: - return item_price[0].price_list_rate ->>>>>>> 9597b1a69e (fix: Batch Price gets updated only if it is a billed item) + if item_price and item_price[0][2] == params.get("uom") and not is_free_item: + return item_price[0][1] return 0.0 From 3a03865a8f057283a0ce757acf920ca6d1750e46 Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Wed, 5 Feb 2025 18:40:21 +0530 Subject: [PATCH 38/97] fix: change voucher_type and voucher_no field type to data (cherry picked from commit f8ab02192037171e4f8de7d79edfd9978238ac09) # Conflicts: # erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json --- .../tax_withheld_vouchers.json | 14 ++++++++------ .../tax_withheld_vouchers/tax_withheld_vouchers.py | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json index 46b430c6594..4b1f586b60c 100644 --- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json @@ -13,17 +13,15 @@ "fields": [ { "fieldname": "voucher_type", - "fieldtype": "Link", + "fieldtype": "Data", "in_list_view": 1, - "label": "Voucher Type", - "options": "DocType" + "label": "Voucher Type" }, { "fieldname": "voucher_name", - "fieldtype": "Dynamic Link", + "fieldtype": "Data", "in_list_view": 1, - "label": "Voucher Name", - "options": "voucher_type" + "label": "Voucher Name" }, { "fieldname": "taxable_amount", @@ -36,7 +34,11 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-01-13 13:40:41.479208", +======= + "modified": "2025-02-05 16:39:14.863698", +>>>>>>> f8ab021920 (fix: change voucher_type and voucher_no field type to data) "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withheld Vouchers", diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py index bc2003e2bea..dbb69a2e769 100644 --- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py @@ -18,8 +18,8 @@ class TaxWithheldVouchers(Document): parentfield: DF.Data parenttype: DF.Data taxable_amount: DF.Currency - voucher_name: DF.DynamicLink | None - voucher_type: DF.Link | None + voucher_name: DF.Data | None + voucher_type: DF.Data | None # end: auto-generated types pass From 489efda98512bd347f79cc36abb8dd470f1f063a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 4 Mar 2025 15:47:03 +0530 Subject: [PATCH 39/97] chore: resolve conflict --- .../doctype/tax_withheld_vouchers/tax_withheld_vouchers.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json index 4b1f586b60c..51dc3674594 100644 --- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json @@ -34,11 +34,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-01-13 13:40:41.479208", -======= "modified": "2025-02-05 16:39:14.863698", ->>>>>>> f8ab021920 (fix: change voucher_type and voucher_no field type to data) "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withheld Vouchers", From 83dcbec86af4fe281ac3abde7fec90efe0269679 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 4 Mar 2025 16:43:55 +0530 Subject: [PATCH 40/97] chore: resolve conflicts --- .../report/purchase_order_analysis/purchase_order_analysis.js | 4 ---- .../report/sales_order_analysis/sales_order_analysis.js | 4 ---- 2 files changed, 8 deletions(-) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js index 8d254722a4f..99b4c26ac8e 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js @@ -66,9 +66,6 @@ frappe.query_reports["Purchase Order Analysis"] = { width: "80", options: ["To Pay", "To Bill", "To Receive", "To Receive and Bill", "Completed", "Closed"], get_data: function (txt) { -<<<<<<< HEAD - let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"]; -======= let status = [ "To Pay", "To Bill", @@ -77,7 +74,6 @@ frappe.query_reports["Purchase Order Analysis"] = { "Completed", "Closed", ]; ->>>>>>> 3b2879d3a1 (fix(report): allow `Closed` purchase orders to be visible) let options = []; for (let option of status) { options.push({ diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js index e470672b3a5..b7f7a34c1b8 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js @@ -65,9 +65,6 @@ frappe.query_reports["Sales Order Analysis"] = { options: ["To Pay", "To Bill", "To Deliver", "To Deliver and Bill", "Completed", "Closed"], width: "80", get_data: function (txt) { -<<<<<<< HEAD - let status = ["To Bill", "To Deliver", "To Deliver and Bill", "Completed"]; -======= let status = [ "To Pay", "To Bill", @@ -76,7 +73,6 @@ frappe.query_reports["Sales Order Analysis"] = { "Completed", "Closed", ]; ->>>>>>> 2394e76e7d (fix(report): allow `Closed` sales orders to be visible) let options = []; for (let option of status) { options.push({ From 78a329e5738c92e34e6cac0350263aa40b93c940 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 13 Feb 2025 14:56:11 +0530 Subject: [PATCH 41/97] fix: auto allocation for negative amount outstanding for Customers in Payment Entry (cherry picked from commit 6275b44a0bdc016d6ed492475cf474c0e94e7a89) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index d1533fe0131..45462398e1c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1843,7 +1843,7 @@ class PaymentEntry(AccountsController): allocated_positive_outstanding = paid_amount + allocated_negative_outstanding - elif self.party_type in ("Supplier", "Employee"): + elif self.party_type in ("Supplier", "Customer"): if paid_amount > total_negative_outstanding: if total_negative_outstanding == 0: frappe.msgprint( From eee500f20e8c9df46e72cf7151d777016dbdc089 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 4 Mar 2025 12:49:48 +0530 Subject: [PATCH 42/97] fix: do not include opening invoices in billed items to be received report (cherry picked from commit c1ddf444c65b50ec59f95e6a88b7e263d303aadc) --- .../billed_items_to_be_received/billed_items_to_be_received.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py index f6efc8a685c..dc6192e7544 100644 --- a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py @@ -27,6 +27,7 @@ def get_report_filters(report_filters): ["Purchase Invoice", "docstatus", "=", 1], ["Purchase Invoice", "per_received", "<", 100], ["Purchase Invoice", "update_stock", "=", 0], + ["Purchase Invoice", "is_opening", "!=", "Yes"], ] if report_filters.get("purchase_invoice"): From 1630979f0556de8fba6941de64f727e0b90ecbfc Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 20 Feb 2025 11:33:53 +0530 Subject: [PATCH 43/97] refactor: add new line ragardless of postal code (cherry picked from commit 746adfd057cb77cbc601da96dcd069b3bc50027a) --- erpnext/regional/address_template/templates/united_states.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/address_template/templates/united_states.html b/erpnext/regional/address_template/templates/united_states.html index 77fce46b9d7..f00f99c1299 100644 --- a/erpnext/regional/address_template/templates/united_states.html +++ b/erpnext/regional/address_template/templates/united_states.html @@ -1,4 +1,4 @@ {{ address_line1 }}
{% if address_line2 %}{{ address_line2 }}
{% endif -%} -{{ city }}, {% if state %}{{ state }}{% endif -%}{% if pincode %} {{ pincode }}
{% endif -%} +{{ city }}, {% if state %}{{ state }}{% endif -%}{% if pincode %} {{ pincode }}{% endif -%}
{% if country != "United States" %}{{ country }}{% endif -%} From ddcf79da1da6dfe5b9cd7f367bf86c7c41992b21 Mon Sep 17 00:00:00 2001 From: mhh008 Date: Tue, 4 Mar 2025 13:56:19 +0100 Subject: [PATCH 44/97] fix: translation DE --- erpnext/translations/de.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 9ef1d4bc63a..aa8d7322958 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -5098,7 +5098,7 @@ Percentage you are allowed to transfer more against the quantity ordered. For ex PUR-ORD-.YYYY.-,PUR-ORD-.YYYY.-, Get Items from Open Material Requests,Hole Artikel von offenen Material Anfragen, Fetch items based on Default Supplier.,Abrufen von Elementen basierend auf dem Standardlieferanten., -Required By,Benötigt von, +Required By,Benötigt bis, Order Confirmation No,Auftragsbestätigung Nr, Order Confirmation Date,Auftragsbestätigungsdatum, Customer Mobile No,Mobilnummer des Kunden, From 8bd71954f3d67efbc88a3994f8870e63ef662af5 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 28 Feb 2025 19:28:20 +0530 Subject: [PATCH 45/97] fix: stock reservation issue while making Purchase Invoice (cherry picked from commit 64985bffe049ecdb58d41760f89a142c7c41ed07) --- erpnext/stock/stock_ledger.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ca08a5ef121..aaeb90b7d30 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -561,12 +561,28 @@ class update_entries_after: self.new_items_found = False self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict()) self.affected_transactions: set[tuple[str, str]] = set() - self.reserved_stock = flt(self.args.reserved_stock) + self.reserved_stock = self.get_reserved_stock() self.data = frappe._dict() self.initialize_previous_data(self.args) self.build() + def get_reserved_stock(self): + sre = frappe.qb.DocType("Stock Reservation Entry") + posting_datetime = get_combine_datetime(self.args.posting_date, self.args.posting_time) + query = ( + frappe.qb.from_(sre) + .select(Sum(sre.reserved_qty) - Sum(sre.delivered_qty)) + .where( + (sre.item_code == self.item_code) + & (sre.warehouse == self.args.warehouse) + & (sre.docstatus == 1) + & (sre.creation <= posting_datetime) + ) + ).run() + + return flt(query[0][0]) if query else 0.0 + def set_precision(self): self.flt_precision = cint(frappe.db.get_default("float_precision")) or 2 self.currency_precision = get_field_precision( From 75bc68b8631a2c38fe82989ce65c203742334a14 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 4 Mar 2025 20:01:50 +0530 Subject: [PATCH 46/97] fix: rate changing on the deliver note (cherry picked from commit 6f40849d55d75979c9b116caba6cf4656a94ddec) --- erpnext/controllers/accounts_controller.py | 10 +++-- .../doctype/sales_order/test_sales_order.py | 39 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 884ce4de71b..6ed55a0b55e 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -823,11 +823,15 @@ class AccountsController(TransactionBase): and item.get("use_serial_batch_fields") ) ): - if fieldname == "batch_no" and not item.batch_no and not item.is_free_item: - item.set("rate", ret.get("rate")) - item.set("price_list_rate", ret.get("price_list_rate")) item.set(fieldname, value) + if fieldname == "batch_no" and item.batch_no and not item.is_free_item: + if ret.get("rate"): + item.set("rate", ret.get("rate")) + + if not item.get("price_list_rate") and ret.get("price_list_rate"): + item.set("price_list_rate", ret.get("price_list_rate")) + elif fieldname in ["cost_center", "conversion_factor"] and not item.get( fieldname ): diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 47d42b0a9d5..003ffd5ac82 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2097,6 +2097,45 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 0) frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0) + def test_delivery_note_rate_on_change_of_warehouse(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + item = make_item( + "_Test Batch Item for Delivery Note Rate", + { + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BH-SDDTBIFRM-.#####", + }, + ) + + frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1) + so = make_sales_order( + item_code=item.name, rate=27648.00, price_list_rate=27648.00, qty=1, do_not_submit=True + ) + + so.items[0].rate = 90 + so.save() + self.assertTrue(so.items[0].discount_amount == 27558.0) + so.submit() + + warehouse = create_warehouse("NW Warehouse FOR Rate", company=so.company) + + make_stock_entry( + item_code=item.name, + qty=2, + target=warehouse, + basic_rate=100, + company=so.company, + use_serial_batch_fields=1, + ) + + dn = make_delivery_note(so.name) + dn.items[0].warehouse = warehouse + dn.save() + + self.assertEqual(dn.items[0].rate, 90) + def test_credit_limit_on_so_reopning(self): # set credit limit company = "_Test Company" From 41ab7f3f7c0a968171e2866cdd856584f67513a9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 11:28:22 +0530 Subject: [PATCH 47/97] perf: don't track seen for POS Invoice (backport #46187) (#46189) * perf: don't track seen for POS Invoice (#46187) This is a moving doctype. Do people even browse the list view? It doesn't make much sense, either. POS INvoices are rarely "reviewed" by multiple users. (cherry picked from commit ded0aab680932730273c6544256632b72ebed5e5) # Conflicts: # erpnext/accounts/doctype/pos_invoice/pos_invoice.json * chore: conflicts --------- Co-authored-by: Ankush Menat --- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index 42861140494..c15309df294 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -1623,6 +1623,5 @@ "states": [], "timeline_field": "customer", "title_field": "title", - "track_changes": 1, - "track_seen": 1 -} \ No newline at end of file + "track_changes": 1 +} From 1b00de18151b12f0b96df53ecc6493f192b600a8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 11:35:52 +0530 Subject: [PATCH 48/97] chore: erpnext.com -> frappe.io/erpnext (backport #46288) (#46290) * chore: erpnext.com -> frappe.io/erpnext (#46288) (cherry picked from commit 41fe30ea6e339e98633a73444f443c4b463bbe57) # Conflicts: # README.md * Update README.md --------- Co-authored-by: Ankush Menat --- erpnext/hooks.py | 4 ++-- erpnext/setup/install.py | 2 +- erpnext/templates/includes/footer/footer_powered.html | 2 +- package.json | 2 +- pyproject.toml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 2b23cca886e..21f301f2450 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -4,7 +4,7 @@ app_publisher = "Frappe Technologies Pvt. Ltd." app_description = """ERP made simple""" app_icon = "fa fa-th" app_color = "#e74c3c" -app_email = "info@erpnext.com" +app_email = "hello@frappe.io" app_license = "GNU General Public License (v3)" source_link = "https://github.com/frappe/erpnext" app_logo_url = "/assets/erpnext/images/erpnext-logo.svg" @@ -484,7 +484,7 @@ email_brand_image = "assets/erpnext/images/erpnext-logo.jpg" default_mail_footer = """ Sent via - + ERPNext diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 97ec418d955..23ffd49de5d 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -16,7 +16,7 @@ from erpnext.setup.doctype.incoterm.incoterm import create_incoterms from .default_success_action import get_default_success_action default_mail_footer = """
Sent via - ERPNext
""" + ERPNext""" def after_install(): diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html index 8310063e575..fb73931d18e 100644 --- a/erpnext/templates/includes/footer/footer_powered.html +++ b/erpnext/templates/includes/footer/footer_powered.html @@ -1 +1 @@ -{{ _("Powered by {0}").format('ERPNext') }} +{{ _("Powered by {0}").format('ERPNext') }} diff --git a/package.json b/package.json index 4e686f7ca74..509fe275e05 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "git", "url": "git+https://github.com/frappe/erpnext.git" }, - "homepage": "https://erpnext.com", + "homepage": "https://frappe.io/erpnext", "author": "Frappe Technologies Pvt. Ltd.", "license": "GPL-3.0", "bugs": { diff --git a/pyproject.toml b/pyproject.toml index d891b186d89..e122b2d176c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,6 +70,6 @@ docstring-code-format = true [project.urls] -Homepage = "https://erpnext.com/" +Homepage = "https://frappe.io/erpnext" Repository = "https://github.com/frappe/erpnext.git" "Bug Reports" = "https://github.com/frappe/erpnext/issues" From 7759775ee6696038c241466e120edefd193253a7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 12:04:17 +0530 Subject: [PATCH 49/97] fix: Add permission check in POS's `Toggle Recent Orders` (backport #46010) (#46274) fix: use get_list to check permissions (cherry picked from commit a08bc6b913b932231abfd72ebf3acf82cf32288c) Co-authored-by: Sanket322 --- erpnext/selling/page/point_of_sale/point_of_sale.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index b86a87983d5..7f758f4c8db 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -320,13 +320,13 @@ def get_past_order_list(search_term, status, limit=20): invoice_list = [] if search_term and status: - invoices_by_customer = frappe.db.get_all( + invoices_by_customer = frappe.db.get_list( "POS Invoice", filters={"customer": ["like", f"%{search_term}%"], "status": status}, fields=fields, page_length=limit, ) - invoices_by_name = frappe.db.get_all( + invoices_by_name = frappe.db.get_list( "POS Invoice", filters={"name": ["like", f"%{search_term}%"], "status": status}, fields=fields, @@ -335,7 +335,7 @@ def get_past_order_list(search_term, status, limit=20): invoice_list = invoices_by_customer + invoices_by_name elif status: - invoice_list = frappe.db.get_all( + invoice_list = frappe.db.get_list( "POS Invoice", filters={"status": status}, fields=fields, page_length=limit ) From d0b8e0da8de036445e07301f6dc06ae8089f350c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 20 Feb 2025 15:03:19 +0530 Subject: [PATCH 50/97] perf: replace if function in query (cherry picked from commit 5e66231ca4e8dc93659f229843fd632181408ca9) --- erpnext/projects/doctype/project/project.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 4ed8ffc0077..be3190ec00d 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -325,10 +325,10 @@ class Project(Document): # nosemgrep total_billed_amount = frappe.db.sql( """select sum(base_net_amount) - from `tabSales Invoice Item` si_item, `tabSales Invoice` si - where si_item.parent = si.name - and if(si_item.project, si_item.project, si.project) = %s - and si.docstatus=1""", + from `tabSales Invoice Item` si_item + join `tabSales Invoice` si on si_item.parent = si.name + where (si_item.project = %s or (si_item.project is null and si.project = %s)) + and si.docstatus = 1""", self.name, ) From 1790bcc6d1901e8f52b3efbcd80801efb876019c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 20 Feb 2025 15:28:13 +0530 Subject: [PATCH 51/97] fix: syntax error (cherry picked from commit 2f1e253e19109cd12d25a3e7d643344663bcf340) --- erpnext/projects/doctype/project/project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index be3190ec00d..8c762be5f6c 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -327,9 +327,9 @@ class Project(Document): """select sum(base_net_amount) from `tabSales Invoice Item` si_item join `tabSales Invoice` si on si_item.parent = si.name - where (si_item.project = %s or (si_item.project is null and si.project = %s)) + where (si_item.project = %(name)s or (si_item.project is null and si.project = %(name)s)) and si.docstatus = 1""", - self.name, + {"name": self.name}, ) self.total_billed_amount = total_billed_amount and total_billed_amount[0][0] or 0 From 7437cea458cde7e52ed8b10d34abe38e3e966564 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 21 Feb 2025 11:02:38 +0530 Subject: [PATCH 52/97] fix: revamp logic (split parent and child) (cherry picked from commit f7594e2ff99a1fbc4b320b7aa4372ea7389068cc) --- erpnext/projects/doctype/project/project.py | 28 +++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 8c762be5f6c..f7653d92d63 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -322,17 +322,31 @@ class Project(Document): self.total_sales_amount = total_sales_amount and total_sales_amount[0][0] or 0 def update_billed_amount(self): - # nosemgrep + self.total_billed_amount = self.get_billed_amount_from_parent() + self.get_billed_amount_from_child() + + def get_billed_amount_from_parent(self): total_billed_amount = frappe.db.sql( """select sum(base_net_amount) - from `tabSales Invoice Item` si_item - join `tabSales Invoice` si on si_item.parent = si.name - where (si_item.project = %(name)s or (si_item.project is null and si.project = %(name)s)) - and si.docstatus = 1""", - {"name": self.name}, + from `tabSales Invoice` si join `tabSales Invoice Item` si_item on si_item.parent = si.name + where si_item.project is null + and si.project is not null + and si.project = %s + and si.docstatus = 1""", + self.name, ) - self.total_billed_amount = total_billed_amount and total_billed_amount[0][0] or 0 + return total_billed_amount and total_billed_amount[0][0] or 0 + + def get_billed_amount_from_child(self): + total_billed_amount = frappe.db.sql( + """select sum(base_net_amount) + from `tabSales Invoice Item` + where project = %s + and docstatus = 1""", + self.name, + ) + + return total_billed_amount and total_billed_amount[0][0] or 0 def after_rename(self, old_name, new_name, merge=False): if old_name == self.copied_from: From d8a1d0e9080dcfd96453a06a113fe8af4319a713 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sun, 23 Feb 2025 22:26:01 +0530 Subject: [PATCH 53/97] test: added test (cherry picked from commit 6073f5a6f95f5f0fdeae21e0bdd869eddf34cb85) --- .../sales_invoice/test_sales_invoice.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 1c33246ee68..99867e94fcb 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -4246,6 +4246,31 @@ class TestSalesInvoice(FrappeTestCase): doc = frappe.get_doc("Project", project.name) self.assertEqual(doc.total_billed_amount, si.grand_total) + def test_total_billed_amount_with_different_projects(self): + # This test case is for checking the scenario where project is set at document level and for **some** child items only, not all + from copy import copy + + si = create_sales_invoice(do_not_submit=True) + + project = frappe.new_doc("Project") + project.company = "_Test Company" + project.project_name = "Test Total Billed Amount" + project.save() + + si.project = project.name + si.items.append(copy(si.items[0])) + si.items.append(copy(si.items[0])) + si.items[0].project = project.name + si.items[1].project = project.name + # Not setting project on last item + si.items[1].insert() + si.items[2].insert() + si.submit() + + project.reload() + self.assertIsNone(si.items[2].project) + self.assertEqual(project.total_billed_amount, 300) + def test_pos_returns_with_party_account_currency(self): from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return From 88234bbf9a6ca7c42ff093469a323ac751112185 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 12:19:31 +0530 Subject: [PATCH 54/97] fix: Include additional account types for Expense Account in LCV (backport #46206) (#46296) fix: Include additional account types for Expense Account in LCV (#46206) fix: additional account types in filters for the Expense account selection (cherry picked from commit 59e99f167d4abbd1959092a7d606d782940d7827) Co-authored-by: Priyansh Shah <108476017+priyanshshah2442@users.noreply.github.com> --- erpnext/public/js/utils/landed_taxes_and_charges_common.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/public/js/utils/landed_taxes_and_charges_common.js b/erpnext/public/js/utils/landed_taxes_and_charges_common.js index 2cb30160453..7d801ca91e6 100644 --- a/erpnext/public/js/utils/landed_taxes_and_charges_common.js +++ b/erpnext/public/js/utils/landed_taxes_and_charges_common.js @@ -14,6 +14,10 @@ erpnext.landed_cost_taxes_and_charges = { "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation", + "Expense Account", + "Direct Expense", + "Indirect Expense", + "Stock Received But Not Billed", ], ], company: frm.doc.company, From d10add4b1ecddff754e822d3558b9917117baf17 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 4 Mar 2025 11:48:28 +0530 Subject: [PATCH 55/97] fix: delivery note from sales order uom conversion mistake (cherry picked from commit 49a43d355d346a492d88bb18b3a76e8d5a255abd) # Conflicts: # erpnext/selling/doctype/sales_order/sales_order.py --- erpnext/selling/doctype/sales_order/sales_order.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index effc3f3894d..4e19526c06a 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1044,7 +1044,12 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): ignore_permissions=True, ) +<<<<<<< HEAD dn_item.qty = flt(sre.reserved_qty) * flt(dn_item.get("conversion_factor", 1)) +======= + dn_item.qty = flt(sre.reserved_qty) / flt(dn_item.get("conversion_factor", 1)) + dn_item.warehouse = sre.warehouse +>>>>>>> 49a43d355d (fix: delivery note from sales order uom conversion mistake) if sre.reservation_based_on == "Serial and Batch" and (sre.has_serial_no or sre.has_batch_no): dn_item.serial_and_batch_bundle = get_ssb_bundle_for_voucher(sre) From d02d0059139f088b343962ed20932e86216ceb6d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 12:40:13 +0530 Subject: [PATCH 56/97] fix: use valuation method from settings in stock ageing report (backport #46068) (#46297) fix: use valuation method from settings in stock ageing report (cherry picked from commit da09c278c8869fa671bdcd1c4844d169b0bb085a) Co-authored-by: Mihir Kandoi --- .../stock/report/stock_ageing/stock_ageing.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index cc597e14196..0edce832aec 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -51,6 +51,10 @@ def format_report_data(filters: Filters, item_details: dict, to_date: str) -> li latest_age = date_diff(to_date, fifo_queue[-1][1]) range_values = get_range_age(filters, fifo_queue, to_date, item_dict) + check_and_replace_valuations_if_moving_average( + range_values, details.valuation_method, details.valuation_rate + ) + row = [details.name, details.item_name, details.description, details.item_group, details.brand] if filters.get("show_warehouse_wise_stock"): @@ -72,6 +76,15 @@ def format_report_data(filters: Filters, item_details: dict, to_date: str) -> li return data +def check_and_replace_valuations_if_moving_average(range_values, item_valuation_method, valuation_rate): + if item_valuation_method == "Moving Average" or ( + not item_valuation_method + and frappe.db.get_single_value("Stock Settings", "valuation_method") == "Moving Average" + ): + for i in range(0, len(range_values), 2): + range_values[i + 1] = range_values[i] * valuation_rate + + def get_average_age(fifo_queue: list, to_date: str) -> float: batch_age = age_qty = total_qty = 0.0 for batch in fifo_queue: @@ -267,7 +280,7 @@ class FIFOSlots: self.__update_balances(d, key) - # Note that stock_ledger_entries is an iterator, you can not reuse it like a list + # Note that stock_ledger_entries is an iterator, you can not reuse it like a list del stock_ledger_entries if not self.filters.get("show_warehouse_wise_stock"): @@ -396,6 +409,7 @@ class FIFOSlots: self.item_details[key]["total_qty"] += row.actual_qty self.item_details[key]["has_serial_no"] = row.has_serial_no + self.item_details[key]["details"].valuation_rate = row.valuation_rate def __aggregate_details_by_item(self, wh_wise_data: dict) -> dict: "Aggregate Item-Wh wise data into single Item entry." @@ -437,8 +451,10 @@ class FIFOSlots: item.description, item.stock_uom, item.has_serial_no, + item.valuation_method, sle.actual_qty, sle.stock_value_difference, + sle.valuation_rate, sle.posting_date, sle.voucher_type, sle.voucher_no, @@ -506,7 +522,14 @@ class FIFOSlots: item_table = frappe.qb.DocType("Item") item = frappe.qb.from_("Item").select( - "name", "item_name", "description", "stock_uom", "brand", "item_group", "has_serial_no" + "name", + "item_name", + "description", + "stock_uom", + "brand", + "item_group", + "has_serial_no", + "valuation_method", ) if self.filters.get("item_code"): From c5717b983da7e0a8e117f2f4e1dd89133ce58f98 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 18 Feb 2025 18:19:49 +0530 Subject: [PATCH 57/97] fix: rename some sla fields (cherry picked from commit baa0dd1235e94e94cb5fca56212f2cb110e690c0) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 5 +++ .../patches/__init__.py | 0 .../patches/rename_sla_fields.py | 9 +++++ .../service_level_agreement.py | 36 ++++++++++--------- 4 files changed, 33 insertions(+), 17 deletions(-) create mode 100644 erpnext/support/doctype/service_level_agreement/patches/__init__.py create mode 100644 erpnext/support/doctype/service_level_agreement/patches/rename_sla_fields.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index df1b885ad18..936bd74874f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -392,6 +392,11 @@ erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect erpnext.patches.v15_0.sync_auto_reconcile_config execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") erpnext.patches.v14_0.disable_add_row_in_gross_profit +<<<<<<< HEAD erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes +======= +erpnext.patches.v14_0.update_posting_datetime +erpnext.support.doctype.service_level_agreement.patches.rename_sla_fields +>>>>>>> baa0dd1235 (fix: rename some sla fields) diff --git a/erpnext/support/doctype/service_level_agreement/patches/__init__.py b/erpnext/support/doctype/service_level_agreement/patches/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/support/doctype/service_level_agreement/patches/rename_sla_fields.py b/erpnext/support/doctype/service_level_agreement/patches/rename_sla_fields.py new file mode 100644 index 00000000000..8f3146da345 --- /dev/null +++ b/erpnext/support/doctype/service_level_agreement/patches/rename_sla_fields.py @@ -0,0 +1,9 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import rename_fieldname + + +def execute(): + doctypes = frappe.get_all("Service Level Agreement", pluck="document_type") + for doctype in doctypes: + rename_fieldname(doctype + "-resolution_by", "sla_resolution_by") + rename_fieldname(doctype + "-resolution_date", "sla_resolution_date") diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index 011a5bc371f..3433a842ea8 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -514,7 +514,7 @@ def apply(doc, method=None): def remove_sla_if_applied(doc): doc.service_level_agreement = None doc.response_by = None - doc.resolution_by = None + doc.sla_resolution_by = None def process_sla(doc, sla): @@ -557,7 +557,7 @@ def handle_status_change(doc, apply_sla_for_resolution): # In case issue was closed and after few days it has been opened # The hold time should be calculated from resolution_date - on_hold_since = doc.resolution_date or doc.on_hold_since + on_hold_since = doc.sla_resolution_date or doc.on_hold_since if on_hold_since: current_hold_hours = time_diff_in_seconds(now_time, on_hold_since) doc.total_hold_time = (doc.total_hold_time or 0) + current_hold_hours @@ -582,7 +582,7 @@ def handle_status_change(doc, apply_sla_for_resolution): # Open to Closed if is_open_status(prev_status) and is_fulfilled_status(doc.status): # Issue is closed -> Set resolution_date - doc.resolution_date = now_time + doc.sla_resolution_date = now_time set_resolution_time(doc) # Closed to Open @@ -606,7 +606,7 @@ def handle_status_change(doc, apply_sla_for_resolution): calculate_hold_hours() # Issue is closed -> Set resolution_date if apply_sla_for_resolution: - doc.resolution_date = now_time + doc.sla_resolution_date = now_time set_resolution_time(doc) @@ -713,7 +713,7 @@ def get_support_days(service_level): def set_resolution_time(doc): start_date_time = get_datetime(doc.get("service_level_agreement_creation") or doc.creation) if doc.meta.has_field("resolution_time"): - doc.resolution_time = time_diff_in_seconds(doc.resolution_date, start_date_time) + doc.resolution_time = time_diff_in_seconds(doc.sla_resolution_date, start_date_time) # total time taken by a user to close the issue apart from wait_time if not doc.meta.has_field("user_resolution_time"): @@ -737,7 +737,7 @@ def set_resolution_time(doc): pending_time.append(wait_time) total_pending_time = sum(pending_time) - resolution_time_in_secs = time_diff_in_seconds(doc.resolution_date, start_date_time) + resolution_time_in_secs = time_diff_in_seconds(doc.sla_resolution_date, start_date_time) doc.user_resolution_time = resolution_time_in_secs - total_pending_time @@ -793,8 +793,8 @@ def reset_service_level_agreement(doctype: str, docname: str, reason, user): def reset_resolution_metrics(doc): - if doc.meta.has_field("resolution_date"): - doc.resolution_date = None + if doc.meta.has_field("sla_resolution_date"): + doc.sla_resolution_date = None if doc.meta.has_field("resolution_time"): doc.resolution_time = None @@ -861,8 +861,8 @@ def on_communication_update(doc, status): def reset_expected_response_and_resolution(doc): if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"): doc.response_by = None - if doc.meta.has_field("resolution_by") and not doc.get("resolution_date"): - doc.resolution_by = None + if doc.meta.has_field("sla_resolution_by") and not doc.get("sla_resolution_date"): + doc.sla_resolution_by = None def set_response_by(doc, start_date_time, priority): @@ -879,12 +879,14 @@ def set_response_by(doc, start_date_time, priority): def set_resolution_by(doc, start_date_time, priority): - if doc.meta.has_field("resolution_by"): - doc.resolution_by = get_expected_time_for( + if doc.meta.has_field("sla_resolution_by"): + doc.sla_resolution_by = get_expected_time_for( parameter="resolution", service_level=priority, start_date_time=start_date_time ) if doc.meta.has_field("total_hold_time") and doc.get("total_hold_time"): - doc.resolution_by = add_to_date(doc.resolution_by, seconds=round(doc.get("total_hold_time"))) + doc.sla_resolution_by = add_to_date( + doc.sla_resolution_by, seconds=round(doc.get("total_hold_time")) + ) def record_assigned_users_on_failure(doc): @@ -943,7 +945,7 @@ def get_service_level_agreement_fields(): "read_only": 1, }, { - "fieldname": "resolution_by", + "fieldname": "sla_resolution_by", "fieldtype": "Datetime", "label": "Resolution By", "read_only": 1, @@ -957,7 +959,7 @@ def get_service_level_agreement_fields(): }, { "depends_on": "eval:!doc.__islocal", - "fieldname": "resolution_date", + "fieldname": "sla_resolution_date", "fieldtype": "Datetime", "label": "Resolution Date", "no_copy": 1, @@ -977,9 +979,9 @@ def update_agreement_status(doc, apply_sla_for_resolution): if apply_sla_for_resolution: if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"): doc.agreement_status = "First Response Due" - elif doc.meta.has_field("resolution_date") and not doc.get("resolution_date"): + elif doc.meta.has_field("sla_resolution_date") and not doc.get("sla_resolution_date"): doc.agreement_status = "Resolution Due" - elif get_datetime(doc.get("resolution_date")) <= get_datetime(doc.get("resolution_by")): + elif get_datetime(doc.get("sla_resolution_date")) <= get_datetime(doc.get("sla_resolution_by")): doc.agreement_status = "Fulfilled" else: doc.agreement_status = "Failed" From d41303961c6ec673966bf4884d20c1a3fd3fc756 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 18 Feb 2025 20:40:50 +0530 Subject: [PATCH 58/97] fix: tests (cherry picked from commit 1b831e9abd0a98f0bf15eb0ff6da92f694d21026) --- .../service_level_agreement/test_service_level_agreement.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py index 47ea251b0fe..cabd38f6427 100644 --- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py @@ -227,7 +227,7 @@ class TestServiceLevelAgreement(unittest.TestCase): self.assertEqual(lead.service_level_agreement, lead_sla.name) self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0)) - self.assertEqual(lead.resolution_by, datetime.datetime(2019, 3, 4, 18, 0)) + self.assertEqual(lead.sla_resolution_by, datetime.datetime(2019, 3, 4, 18, 0)) frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 0) lead.reload() @@ -268,7 +268,7 @@ class TestServiceLevelAgreement(unittest.TestCase): lead.reload() self.assertEqual(flt(lead.total_hold_time, 2), 3000) - self.assertEqual(lead.resolution_by, datetime.datetime(2020, 3, 4, 16, 50)) + self.assertEqual(lead.sla_resolution_by, datetime.datetime(2020, 3, 4, 16, 50)) def test_failed_sla_for_response_only(self): doctype = "Lead" From 46b0734d6f2c0b6eaab754f6755d5510f6fa7b6b Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 18 Feb 2025 21:24:53 +0530 Subject: [PATCH 59/97] fix: tests (cherry picked from commit 019303dd12fb6ff9ec02bb6ad17b35e4e6e76191) # Conflicts: # erpnext/support/doctype/issue/issue.json --- erpnext/support/doctype/issue/issue.json | 42 ++++++++++--------- erpnext/support/doctype/issue/issue.py | 4 +- erpnext/support/doctype/issue/test_issue.py | 38 ++++++++--------- .../patches/rename_sla_fields.py | 4 ++ 4 files changed, 48 insertions(+), 40 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index 3ff7d02f1ae..c048eb1d5cd 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -27,7 +27,7 @@ "reset_service_level_agreement", "cb", "agreement_status", - "resolution_by", + "sla_resolution_by", "service_level_agreement_creation", "on_hold_since", "total_hold_time", @@ -41,7 +41,7 @@ "column_break1", "opening_date", "opening_time", - "resolution_date", + "sla_resolution_date", "resolution_time", "user_resolution_time", "additional_info", @@ -176,13 +176,6 @@ "options": "fa fa-pushpin", "read_only": 1 }, - { - "depends_on": "eval: doc.status != 'Replied' && doc.service_level_agreement;", - "fieldname": "resolution_by", - "fieldtype": "Datetime", - "label": "Resolution By", - "read_only": 1 - }, { "collapsible": 1, "fieldname": "response", @@ -287,16 +280,6 @@ "oldfieldtype": "Time", "read_only": 1 }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "resolution_date", - "fieldtype": "Datetime", - "label": "Resolution Date", - "no_copy": 1, - "oldfieldname": "resolution_date", - "oldfieldtype": "Date", - "read_only": 1 - }, { "fieldname": "content_type", "fieldtype": "Data", @@ -386,12 +369,33 @@ "fieldtype": "Duration", "label": "First Response Time", "read_only": 1 + }, + { + "depends_on": "eval: doc.status != 'Replied' && doc.service_level_agreement;", + "fieldname": "sla_resolution_by", + "fieldtype": "Datetime", + "label": "Resolution By", + "read_only": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "sla_resolution_date", + "fieldtype": "Datetime", + "label": "Resolution Date", + "no_copy": 1, + "oldfieldname": "resolution_date", + "oldfieldtype": "Date", + "read_only": 1 } ], "icon": "fa fa-ticket", "idx": 7, "links": [], +<<<<<<< HEAD "modified": "2021-11-24 13:13:10.276630", +======= + "modified": "2025-02-18 21:18:52.797745", +>>>>>>> 019303dd12 (fix: tests) "modified_by": "Administrator", "module": "Support", "name": "Issue", diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index aeaf28c169c..22630d80f38 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -48,13 +48,13 @@ class Issue(Document): priority: DF.Link | None project: DF.Link | None raised_by: DF.Data | None - resolution_by: DF.Datetime | None - resolution_date: DF.Datetime | None resolution_details: DF.TextEditor | None resolution_time: DF.Duration | None response_by: DF.Datetime | None service_level_agreement: DF.Link | None service_level_agreement_creation: DF.Datetime | None + sla_resolution_by: DF.Datetime | None + sla_resolution_date: DF.Datetime | None status: DF.Literal["Open", "Replied", "On Hold", "Resolved", "Closed"] subject: DF.Data total_hold_time: DF.Duration | None diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py index acad115f626..c334a2251b3 100644 --- a/erpnext/support/doctype/issue/test_issue.py +++ b/erpnext/support/doctype/issue/test_issue.py @@ -33,48 +33,48 @@ class TestIssue(TestSetUp): issue = make_issue(creation, "_Test Customer", 1) self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 15:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 15:00")) # make issue with customer_group specific SLA create_customer("__Test Customer", "_Test SLA Customer Group", "__Test SLA Territory") issue = make_issue(creation, "__Test Customer", 2) self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 15:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 15:00")) # make issue with territory specific SLA create_customer("___Test Customer", "__Test SLA Customer Group", "_Test SLA Territory") issue = make_issue(creation, "___Test Customer", 3) self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 15:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 15:00")) # make issue with default SLA issue = make_issue(creation=creation, index=4) self.assertEqual(issue.response_by, get_datetime("2019-03-04 16:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 18:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 18:00")) # make issue with default SLA before working hours creation = get_datetime("2019-03-04 7:00") issue = make_issue(creation=creation, index=5) self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 16:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 16:00")) # make issue with default SLA after working hours creation = get_datetime("2019-03-04 20:00") issue = make_issue(creation, index=6) self.assertEqual(issue.response_by, get_datetime("2019-03-06 14:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-06 16:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-06 16:00")) # make issue with default SLA next day creation = get_datetime("2019-03-04 14:00") issue = make_issue(creation=creation, index=7) self.assertEqual(issue.response_by, get_datetime("2019-03-04 18:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-06 12:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-06 12:00")) frappe.flags.current_time = get_datetime("2019-03-04 15:00") issue.reload() @@ -98,7 +98,7 @@ class TestIssue(TestSetUp): issue.save() self.assertEqual(issue.on_hold_since, frappe.flags.current_time) - self.assertFalse(issue.resolution_by) + self.assertFalse(issue.sla_resolution_by) creation = get_datetime("2020-03-04 5:00") frappe.flags.current_time = get_datetime("2020-03-04 5:00") @@ -106,7 +106,7 @@ class TestIssue(TestSetUp): issue.reload() self.assertEqual(flt(issue.total_hold_time, 2), 2700) - self.assertEqual(issue.resolution_by, get_datetime("2020-03-04 16:45")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2020-03-04 16:45")) creation = get_datetime("2020-03-04 5:05") create_communication(issue.name, "test@admin.com", "Sent", creation) @@ -140,8 +140,8 @@ class TestIssue(TestSetUp): issue.status = "Closed" issue.save() - self.assertEqual(issue.resolution_by, get_datetime("2021-11-22 06:00:00")) - self.assertEqual(issue.resolution_date, get_datetime("2021-11-22 01:00:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-22 06:00:00")) + self.assertEqual(issue.sla_resolution_date, get_datetime("2021-11-22 01:00:00")) self.assertEqual(issue.agreement_status, "Fulfilled") def test_issue_open_after_closed(self): @@ -153,7 +153,7 @@ class TestIssue(TestSetUp): create_communication(issue.name, "test@example.com", "Received", frappe.flags.current_time) self.assertEqual(issue.agreement_status, "First Response Due") self.assertEqual(issue.response_by, get_datetime("2021-11-01 17:00")) - self.assertEqual(issue.resolution_by, get_datetime("2021-11-01 19:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-01 19:00")) # Replied on → 2 pm frappe.flags.current_time = get_datetime("2021-11-01 14:00") @@ -173,7 +173,7 @@ class TestIssue(TestSetUp): # Hold Time + 1 Hrs self.assertEqual(issue.total_hold_time, 3600) # Resolution By should increase by one hrs - self.assertEqual(issue.resolution_by, get_datetime("2021-11-01 20:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-01 20:00")) # Replied on → 4 pm, Open → 1 hr, Resolution Due → 8 pm frappe.flags.current_time = get_datetime("2021-11-01 16:00") @@ -190,9 +190,9 @@ class TestIssue(TestSetUp): # Hold Time + 6 Hrs self.assertEqual(issue.total_hold_time, 3600 + 21600) # Resolution By should increase by 6 hrs - self.assertEqual(issue.resolution_by, get_datetime("2021-11-02 02:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-02 02:00")) self.assertEqual(issue.agreement_status, "Fulfilled") - self.assertEqual(issue.resolution_date, frappe.flags.current_time) + self.assertEqual(issue.sla_resolution_date, frappe.flags.current_time) # Customer Open → 3 am i.e after resolution by is crossed frappe.flags.current_time = get_datetime("2021-11-02 03:00") @@ -201,17 +201,17 @@ class TestIssue(TestSetUp): # Since issue was Resolved, Resolution By should be increased by 5 hrs (3am - 10pm) self.assertEqual(issue.total_hold_time, 3600 + 21600 + 18000) # Resolution By should increase by 5 hrs - self.assertEqual(issue.resolution_by, get_datetime("2021-11-02 07:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-02 07:00")) self.assertEqual(issue.agreement_status, "Resolution Due") - self.assertFalse(issue.resolution_date) + self.assertFalse(issue.sla_resolution_date) # We Closed → 4 am, SLA should be Fulfilled frappe.flags.current_time = get_datetime("2021-11-02 04:00") issue.status = "Closed" issue.save() - self.assertEqual(issue.resolution_by, get_datetime("2021-11-02 07:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-02 07:00")) self.assertEqual(issue.agreement_status, "Fulfilled") - self.assertEqual(issue.resolution_date, frappe.flags.current_time) + self.assertEqual(issue.sla_resolution_date, frappe.flags.current_time) def test_recording_of_assignment_on_first_reponse_failure(self): from frappe.desk.form.assign_to import add as add_assignment diff --git a/erpnext/support/doctype/service_level_agreement/patches/rename_sla_fields.py b/erpnext/support/doctype/service_level_agreement/patches/rename_sla_fields.py index 8f3146da345..5e3e92d85c2 100644 --- a/erpnext/support/doctype/service_level_agreement/patches/rename_sla_fields.py +++ b/erpnext/support/doctype/service_level_agreement/patches/rename_sla_fields.py @@ -1,5 +1,6 @@ import frappe from frappe.custom.doctype.custom_field.custom_field import rename_fieldname +from frappe.model.utils.rename_field import rename_field def execute(): @@ -7,3 +8,6 @@ def execute(): for doctype in doctypes: rename_fieldname(doctype + "-resolution_by", "sla_resolution_by") rename_fieldname(doctype + "-resolution_date", "sla_resolution_date") + + rename_field("Issue", "resolution_by", "sla_resolution_by") + rename_field("Issue", "resolution_date", "sla_resolution_date") From af49f5a8affe9cdfa5bacecf37be200d2c19cade Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 19 Feb 2025 13:10:13 +0530 Subject: [PATCH 60/97] fix: patch path (cherry picked from commit dcec446e558ae83e93c8d036cb23f4460ec0891a) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 4 ++++ .../patches => patches/v15_0}/rename_sla_fields.py | 0 .../doctype/service_level_agreement/patches/__init__.py | 0 3 files changed, 4 insertions(+) rename erpnext/{support/doctype/service_level_agreement/patches => patches/v15_0}/rename_sla_fields.py (100%) delete mode 100644 erpnext/support/doctype/service_level_agreement/patches/__init__.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 936bd74874f..2f7fbac904d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -395,8 +395,12 @@ erpnext.patches.v14_0.disable_add_row_in_gross_profit <<<<<<< HEAD erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime +<<<<<<< HEAD erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes ======= erpnext.patches.v14_0.update_posting_datetime erpnext.support.doctype.service_level_agreement.patches.rename_sla_fields >>>>>>> baa0dd1235 (fix: rename some sla fields) +======= +erpnext.patches.v15_0.rename_sla_fields +>>>>>>> dcec446e55 (fix: patch path) diff --git a/erpnext/support/doctype/service_level_agreement/patches/rename_sla_fields.py b/erpnext/patches/v15_0/rename_sla_fields.py similarity index 100% rename from erpnext/support/doctype/service_level_agreement/patches/rename_sla_fields.py rename to erpnext/patches/v15_0/rename_sla_fields.py diff --git a/erpnext/support/doctype/service_level_agreement/patches/__init__.py b/erpnext/support/doctype/service_level_agreement/patches/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 From 6b56724436c8200bfa0a4cf6d30c8858b2af2ce6 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Mar 2025 12:46:52 +0530 Subject: [PATCH 61/97] chore: resolve conflicts --- erpnext/selling/doctype/sales_order/sales_order.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 4e19526c06a..74e2328be24 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1044,12 +1044,7 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): ignore_permissions=True, ) -<<<<<<< HEAD - dn_item.qty = flt(sre.reserved_qty) * flt(dn_item.get("conversion_factor", 1)) -======= dn_item.qty = flt(sre.reserved_qty) / flt(dn_item.get("conversion_factor", 1)) - dn_item.warehouse = sre.warehouse ->>>>>>> 49a43d355d (fix: delivery note from sales order uom conversion mistake) if sre.reservation_based_on == "Serial and Batch" and (sre.has_serial_no or sre.has_batch_no): dn_item.serial_and_batch_bundle = get_ssb_bundle_for_voucher(sre) From f29c43811c6dc3c61698d78a9a3cb1476b98cf0a Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Mar 2025 12:55:10 +0530 Subject: [PATCH 62/97] chore: resolve conflicts --- erpnext/patches.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2f7fbac904d..6a41b007308 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -392,15 +392,7 @@ erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect erpnext.patches.v15_0.sync_auto_reconcile_config execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") erpnext.patches.v14_0.disable_add_row_in_gross_profit -<<<<<<< HEAD erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime -<<<<<<< HEAD erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes -======= -erpnext.patches.v14_0.update_posting_datetime -erpnext.support.doctype.service_level_agreement.patches.rename_sla_fields ->>>>>>> baa0dd1235 (fix: rename some sla fields) -======= erpnext.patches.v15_0.rename_sla_fields ->>>>>>> dcec446e55 (fix: patch path) From 0b50f1a9c33361bd13603fe1472acfbb608541d7 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Mar 2025 12:57:53 +0530 Subject: [PATCH 63/97] chore: fix pre-commit/linter error --- erpnext/stock/get_item_details.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 23e6e003bb0..ff87d159e36 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1051,7 +1051,11 @@ def get_batch_based_item_price(params, item_code) -> float: if not item_price: item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True) - if item_price and item_price[0][2] == params.get("uom") and not params.get("items", [{}])[0].get("is_free_item", 0): + if ( + item_price + and item_price[0][2] == params.get("uom") + and not params.get("items", [{}])[0].get("is_free_item", 0) + ): return item_price[0][1] return 0.0 From 446a8fe096cab70370a88799405217d8bd06187a Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Mar 2025 13:08:47 +0530 Subject: [PATCH 64/97] chore: resolve conflicts --- erpnext/support/doctype/issue/issue.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index c048eb1d5cd..622e19e9e3a 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -391,11 +391,7 @@ "icon": "fa fa-ticket", "idx": 7, "links": [], -<<<<<<< HEAD - "modified": "2021-11-24 13:13:10.276630", -======= "modified": "2025-02-18 21:18:52.797745", ->>>>>>> 019303dd12 (fix: tests) "modified_by": "Administrator", "module": "Support", "name": "Issue", @@ -424,4 +420,4 @@ "title_field": "subject", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} From 5ae9faab91116fabe663a891d4b343ceffbc65cc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 13:11:34 +0530 Subject: [PATCH 65/97] fix: only include submitted docs for internal received quantity validation (backport #46262) (#46304) fix: only include submitted docs for internal received quantity validation (#46262) (cherry picked from commit 88fcdbb81e5aa95fdf29b17d5cdc6f4cc58ccabd) Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com> --- erpnext/controllers/stock_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index a8f9976c83b..e892c5d27e2 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1234,7 +1234,7 @@ class StockController(AccountsController): child_tab.item_code, child_tab.qty, ) - .where(parent_tab.docstatus < 2) + .where(parent_tab.docstatus == 1) ) if self.doctype == "Purchase Invoice": From 087dde5873fe64979307856be0a68e38a5f2b445 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 13:11:45 +0530 Subject: [PATCH 66/97] fix: depreciation and balances report correction (backport #46259) (#46305) fix: depreciation and balances report correction (#46259) (cherry picked from commit 4a542b22a4201f047a9bde553680406bd22960d4) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- .../asset_depreciations_and_balances.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 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 bec5d128f0a..5229839bec6 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 @@ -50,6 +50,7 @@ def get_group_by_asset_category_data(filters): flt(row.accumulated_depreciation_as_on_from_date) + flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated_during_the_period) + - flt(row.depreciation_eliminated_via_reversal) ) row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt( @@ -247,6 +248,7 @@ def get_group_by_asset_data(filters): flt(row.accumulated_depreciation_as_on_from_date) + flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated_during_the_period) + - flt(row.depreciation_eliminated_via_reversal) ) row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt( @@ -276,6 +278,7 @@ def get_assets_for_grouped_by_category(filters): f""" SELECT results.asset_category, sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date, + sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal, sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period from (SELECT a.asset_category, @@ -284,6 +287,11 @@ def get_assets_for_grouped_by_category(filters): else 0 end), 0) as accumulated_depreciation_as_on_from_date, + ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then + gle.credit + else + 0 + end), 0) as depreciation_eliminated_via_reversal, ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then gle.debit @@ -307,7 +315,6 @@ def get_assets_for_grouped_by_category(filters): a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s - and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) {condition} {finance_book_filter} @@ -319,6 +326,7 @@ def get_assets_for_grouped_by_category(filters): else a.opening_accumulated_depreciation end), 0) as accumulated_depreciation_as_on_from_date, + 0 as depreciation_eliminated_via_reversal, ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then a.opening_accumulated_depreciation else @@ -354,6 +362,7 @@ def get_assets_for_grouped_by_asset(filters): f""" SELECT results.name as asset, sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date, + sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal, sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period from (SELECT a.name as name, @@ -362,6 +371,11 @@ def get_assets_for_grouped_by_asset(filters): else 0 end), 0) as accumulated_depreciation_as_on_from_date, + ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then + gle.credit + else + 0 + end), 0) as depreciation_eliminated_via_reversal, ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then gle.debit @@ -385,7 +399,6 @@ def get_assets_for_grouped_by_asset(filters): a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s - and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) {finance_book_filter} {condition} @@ -397,6 +410,7 @@ def get_assets_for_grouped_by_asset(filters): else a.opening_accumulated_depreciation end), 0) as accumulated_depreciation_as_on_from_date, + 0 as depreciation_as_on_from_date_credit, ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then a.opening_accumulated_depreciation else @@ -503,6 +517,12 @@ def get_columns(filters): "fieldtype": "Currency", "width": 270, }, + { + "label": _("Depreciation eliminated via reversal"), + "fieldname": "depreciation_eliminated_via_reversal", + "fieldtype": "Currency", + "width": 270, + }, { "label": _("Net Asset value as on") + " " + formatdate(filters.day_before_from_date), "fieldname": "net_asset_value_as_on_from_date", From 363129bcd4d674df9762933ec2642330bc862e50 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 13:11:53 +0530 Subject: [PATCH 67/97] fix(workspace): enable is_query_report on purchase reports (backport #46249) (#46306) fix(workspace): enable is_query_report on purchase reports (#46249) * fix(workspace): enable is_query_report on purchase reports * fix: resolved conflict --------- Co-authored-by: venkat102 (cherry picked from commit 5513e24b001cce2e4011e711cf2474150b77e5ef) Co-authored-by: Nabin Hait --- .../accounts/workspace/payables/payables.json | 14 +++++------ erpnext/patches.txt | 1 + erpnext/patches/v15_0/update_query_report.py | 25 +++++++++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 erpnext/patches/v15_0/update_query_report.py diff --git a/erpnext/accounts/workspace/payables/payables.json b/erpnext/accounts/workspace/payables/payables.json index f8c85648756..96c626c7291 100644 --- a/erpnext/accounts/workspace/payables/payables.json +++ b/erpnext/accounts/workspace/payables/payables.json @@ -93,7 +93,7 @@ }, { "hidden": 0, - "is_query_report": 0, + "is_query_report": 1, "label": "Accounts Payable", "link_count": 0, "link_to": "Accounts Payable", @@ -103,7 +103,7 @@ }, { "hidden": 0, - "is_query_report": 0, + "is_query_report": 1, "label": "Accounts Payable Summary", "link_count": 0, "link_to": "Accounts Payable Summary", @@ -113,7 +113,7 @@ }, { "hidden": 0, - "is_query_report": 0, + "is_query_report": 1, "label": "Purchase Register", "link_count": 0, "link_to": "Purchase Register", @@ -123,7 +123,7 @@ }, { "hidden": 0, - "is_query_report": 0, + "is_query_report": 1, "label": "Item-wise Purchase Register", "link_count": 0, "link_to": "Item-wise Purchase Register", @@ -133,7 +133,7 @@ }, { "hidden": 0, - "is_query_report": 0, + "is_query_report": 1, "label": "Purchase Order Analysis", "link_count": 0, "link_to": "Purchase Order Analysis", @@ -143,7 +143,7 @@ }, { "hidden": 0, - "is_query_report": 0, + "is_query_report": 1, "label": "Received Items To Be Billed", "link_count": 0, "link_to": "Received Items To Be Billed", @@ -153,7 +153,7 @@ }, { "hidden": 0, - "is_query_report": 0, + "is_query_report": 1, "label": "Supplier Ledger Summary", "link_count": 0, "link_to": "Supplier Ledger Summary", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index df1b885ad18..f292abfd50a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -395,3 +395,4 @@ erpnext.patches.v14_0.disable_add_row_in_gross_profit erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes +erpnext.patches.v15_0.update_query_report diff --git a/erpnext/patches/v15_0/update_query_report.py b/erpnext/patches/v15_0/update_query_report.py new file mode 100644 index 00000000000..0efdf8af2c3 --- /dev/null +++ b/erpnext/patches/v15_0/update_query_report.py @@ -0,0 +1,25 @@ +import frappe + + +def execute(): + reports = [ + "Accounts Payable", + "Accounts Payable Summary", + "Purchase Register", + "Item-wise Purchase Register", + "Purchase Order Analysis", + "Received Items To Be Billed", + "Supplier Ledger Summary", + ] + frappe.db.set_value( + "Workspace Link", + { + "parent": "Payables", + "link_type": "Report", + "type": "Link", + "link_to": ["in", reports], + "is_query_report": 0, + }, + "is_query_report", + 1, + ) From 400f4f32adf4e789d692750ff4213a7321c41f39 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 13:12:09 +0530 Subject: [PATCH 68/97] feat(received items to be billed): add company and date filters (backport #46271) (#46302) feat(received items to be billed): add company and date filters (#46271) * feat(received items to be billed): add company and date filters * feat(delivered to be billed): add company and date filters * feat: add company and date conditions * chore: remove debugger (cherry picked from commit 6117706ab5ad68becd81e967561ddfe0b4f67c73) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> --- .../delivered_items_to_be_billed.js | 24 ++++- .../delivered_items_to_be_billed.py | 13 +-- erpnext/accounts/report/non_billed_report.py | 89 +++++++++++-------- .../received_items_to_be_billed.js | 24 ++++- .../received_items_to_be_billed.py | 13 +-- 5 files changed, 104 insertions(+), 59 deletions(-) diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.js b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.js index f6051d7e04f..1be58ad9d55 100644 --- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.js +++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.js @@ -2,5 +2,27 @@ // For license information, please see license.txt frappe.query_reports["Delivered Items To Be Billed"] = { - filters: [], + filters: [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + reqd: 1, + default: frappe.defaults.get_default("Company"), + }, + { + label: __("As on Date"), + fieldname: "posting_date", + fieldtype: "Date", + reqd: 1, + default: frappe.datetime.get_today(), + }, + { + label: __("Delivery Note"), + fieldname: "delivery_note", + fieldtype: "Link", + options: "Delivery Note", + }, + ], }; diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py index 59914dc29ac..d2e5ff28247 100644 --- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py +++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py @@ -3,6 +3,7 @@ from frappe import _ +from pypika import Order from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_data @@ -10,7 +11,7 @@ from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_d def execute(filters=None): columns = get_column() args = get_args() - data = get_ordered_to_be_billed_data(args) + data = get_ordered_to_be_billed_data(args, filters) return columns, data @@ -76,13 +77,6 @@ def get_column(): "options": "Project", "width": 120, }, - { - "label": _("Company"), - "fieldname": "company", - "fieldtype": "Link", - "options": "Company", - "width": 120, - }, ] @@ -92,5 +86,6 @@ def get_args(): "party": "customer", "date": "posting_date", "order": "name", - "order_by": "desc", + "order_by": Order.desc, + "reference_field": "delivery_note", } diff --git a/erpnext/accounts/report/non_billed_report.py b/erpnext/accounts/report/non_billed_report.py index 39c5311cd99..c0ca604cc6d 100644 --- a/erpnext/accounts/report/non_billed_report.py +++ b/erpnext/accounts/report/non_billed_report.py @@ -4,11 +4,12 @@ import frappe from frappe.model.meta import get_field_precision +from frappe.query_builder.functions import IfNull, Round from erpnext import get_default_currency -def get_ordered_to_be_billed_data(args): +def get_ordered_to_be_billed_data(args, filters=None): doctype, party = args.get("doctype"), args.get("party") child_tab = doctype + " Item" precision = ( @@ -18,47 +19,57 @@ def get_ordered_to_be_billed_data(args): or 2 ) - project_field = get_project_field(doctype, party) + doctype = frappe.qb.DocType(doctype) + child_doctype = frappe.qb.DocType(child_tab) - return frappe.db.sql( - """ - Select - `{parent_tab}`.name, `{parent_tab}`.{date_field}, - `{parent_tab}`.{party}, `{parent_tab}`.{party}_name, - `{child_tab}`.item_code, - `{child_tab}`.base_amount, - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)), - (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)), - (`{child_tab}`.base_amount - - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)) - - (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))), - `{child_tab}`.item_name, `{child_tab}`.description, - {project_field}, `{parent_tab}`.company - from - `{parent_tab}`, `{child_tab}` - where - `{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1 - and `{parent_tab}`.status not in ('Closed', 'Completed') - and `{child_tab}`.amount > 0 - and (`{child_tab}`.base_amount - - round(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) - - (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0 - order by - `{parent_tab}`.{order} {order_by} - """.format( - parent_tab="tab" + doctype, - child_tab="tab" + child_tab, - precision=precision, - party=party, - date_field=args.get("date"), - project_field=project_field, - order=args.get("order"), - order_by=args.get("order_by"), + docname = filters.get(args.get("reference_field"), None) + project_field = get_project_field(doctype, child_doctype, party) + + query = ( + frappe.qb.from_(doctype) + .inner_join(child_doctype) + .on(doctype.name == child_doctype.parent) + .select( + doctype.name, + doctype[args.get("date")].as_("date"), + doctype[party], + doctype[party + "_name"], + child_doctype.item_code, + child_doctype.base_amount.as_("amount"), + (child_doctype.billed_amt * IfNull(doctype.conversion_rate, 1)).as_("billed_amount"), + (child_doctype.base_rate * IfNull(child_doctype.returned_qty, 0)).as_("returned_amount"), + ( + child_doctype.base_amount + - (child_doctype.billed_amt * IfNull(doctype.conversion_rate, 1)) + - (child_doctype.base_rate * IfNull(child_doctype.returned_qty, 0)) + ).as_("pending_amount"), + child_doctype.item_name, + child_doctype.description, + project_field, ) + .where( + (doctype.docstatus == 1) + & (doctype.status.notin(["Closed", "Completed"])) + & (doctype.company == filters.get("company")) + & (doctype.posting_date <= filters.get("posting_date")) + & (child_doctype.amount > 0) + & ( + child_doctype.base_amount + - Round(child_doctype.billed_amt * IfNull(doctype.conversion_rate, 1), precision) + - (child_doctype.base_rate * IfNull(child_doctype.returned_qty, 0)) + ) + > 0 + ) + .orderby(doctype[args.get("order")], order=args.get("order_by")) ) + if docname: + query = query.where(doctype.name == docname) -def get_project_field(doctype, party): + return query.run(as_dict=True) + + +def get_project_field(doctype, child_doctype, party): if party == "supplier": - doctype = doctype + " Item" - return "`tab%s`.project" % (doctype) + return child_doctype.project + return doctype.project diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.js b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.js index ad97f270dd3..2577a82ef65 100644 --- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.js +++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.js @@ -2,5 +2,27 @@ // For license information, please see license.txt frappe.query_reports["Received Items To Be Billed"] = { - filters: [], + filters: [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + reqd: 1, + default: frappe.defaults.get_default("Company"), + }, + { + label: __("As on Date"), + fieldname: "posting_date", + fieldtype: "Date", + reqd: 1, + default: frappe.datetime.get_today(), + }, + { + label: __("Purchase Receipt"), + fieldname: "purchase_receipt", + fieldtype: "Link", + options: "Purchase Receipt", + }, + ], }; diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py index 1dcacb97420..87b7b109b99 100644 --- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py +++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py @@ -3,6 +3,7 @@ from frappe import _ +from pypika import Order from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_data @@ -10,7 +11,7 @@ from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_d def execute(filters=None): columns = get_column() args = get_args() - data = get_ordered_to_be_billed_data(args) + data = get_ordered_to_be_billed_data(args, filters) return columns, data @@ -76,13 +77,6 @@ def get_column(): "options": "Project", "width": 120, }, - { - "label": _("Company"), - "fieldname": "company", - "fieldtype": "Link", - "options": "Company", - "width": 120, - }, ] @@ -92,5 +86,6 @@ def get_args(): "party": "supplier", "date": "posting_date", "order": "name", - "order_by": "desc", + "order_by": Order.desc, + "reference_field": "purchase_receipt", } From 4f80ddd8348d5004744ba5d39ab1bb73bff8c923 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 13:12:20 +0530 Subject: [PATCH 69/97] fix: Add company filter at get_invoice method (backport #46238) (#46299) fix: Add company filter at get_invoice method (#46238) (cherry picked from commit a8d1cbc1c34b644ce595e4fb88af961269dcdc3d) Co-authored-by: Kunhi --- .../deferred_revenue_and_expense/deferred_revenue_and_expense.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index 377777ab2a3..1c85061a551 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -307,6 +307,7 @@ class Deferred_Revenue_and_Expense_Report: .where( (inv.docstatus == 1) & (deferred_flag_field == 1) + & (inv.company == self.filters.company) & ( ( (self.period_list[0].from_date >= inv_item.service_start_date) From 36fa6bf15cf90e3ead977769b3292eba2bb680cd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 5 Mar 2025 13:55:50 +0530 Subject: [PATCH 70/97] chore: resolve conflict --- erpnext/stock/doctype/shipment/shipment.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json index aaa2515656b..8b6e70ac65b 100644 --- a/erpnext/stock/doctype/shipment/shipment.json +++ b/erpnext/stock/doctype/shipment/shipment.json @@ -440,11 +440,7 @@ ], "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2022-11-17 17:23:27.025802", -======= "modified": "2025-02-20 16:55:20.076418", ->>>>>>> 1ec182430d (feat: add total weight in shipment (#46049)) "modified_by": "Administrator", "module": "Stock", "name": "Shipment", From 1c6e4649bd424cba951c0e18aa62cdefacbd5089 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:06:55 +0530 Subject: [PATCH 71/97] feat(Sales Invoice): add items row via "Fetch Timesheet" (backport #46071) (#46311) feat(Sales Invoice): add items row via "Fetch Timesheet" (#46071) (cherry picked from commit 94547188bfd8685d96649b61aff555950ed1e4f6) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .../doctype/sales_invoice/sales_invoice.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index ed7eb5685c1..792e1ddbdad 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -922,9 +922,25 @@ frappe.ui.form.on("Sales Invoice", { } const timesheets = await frm.events.get_timesheet_data(frm, kwargs); + + if (kwargs.item_code) { + frm.events.add_timesheet_item(frm, kwargs.item_code, timesheets); + } + return frm.events.set_timesheet_data(frm, timesheets); }, + add_timesheet_item: function (frm, item_code, timesheets) { + const row = frm.add_child("items"); + frappe.model.set_value(row.doctype, row.name, "item_code", item_code); + frappe.model.set_value( + row.doctype, + row.name, + "qty", + timesheets.reduce((a, b) => a + (b["billing_hours"] || 0.0), 0.0) + ); + }, + async get_timesheet_data(frm, kwargs) { return frappe .call({ @@ -1022,6 +1038,22 @@ frappe.ui.form.on("Sales Invoice", { fieldtype: "Date", reqd: 1, }, + { + label: __("Item Code"), + fieldname: "item_code", + fieldtype: "Link", + options: "Item", + get_query: () => { + return { + query: "erpnext.controllers.queries.item_query", + filters: { + is_sales_item: 1, + customer: frm.doc.customer, + has_variants: 0, + }, + }; + }, + }, { fieldtype: "Column Break", fieldname: "col_break_1", @@ -1046,6 +1078,7 @@ frappe.ui.form.on("Sales Invoice", { from_time: data.from_time, to_time: data.to_time, project: data.project, + item_code: data.item_code, }); d.hide(); }, From 1ff085876e62c642694c090cbee7a65160557776 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:07:03 +0530 Subject: [PATCH 72/97] fix: Wrong Overdue Status in Sales Invoices (Floating-point arithmetic) (backport #46146) (#46310) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: Wrong Overdue Status in Sales Invoices (Floating-point arithmetic) (#46146) * fix: Wrong Overdue Status in Sales Invoices (Floating-point arithmetic) * style: after run pre-commit (cherry picked from commit 89bcdd6fa5ccd9b7072a7fcc1e36f033e2db8c0e) Co-authored-by: Diógenes Souza <103958767+devdiogenes@users.noreply.github.com> --- .../accounts/doctype/sales_invoice/sales_invoice.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index c860fe41a66..9fcb1ae526c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1937,13 +1937,16 @@ def is_overdue(doc, total): "base_payment_amount" if doc.party_account_currency != doc.currency else "payment_amount" ) - payable_amount = sum( - payment.get(payment_amount_field) - for payment in doc.payment_schedule - if getdate(payment.due_date) < today + payable_amount = flt( + sum( + payment.get(payment_amount_field) + for payment in doc.payment_schedule + if getdate(payment.due_date) < today + ), + doc.precision("outstanding_amount"), ) - return (total - outstanding_amount) < payable_amount + return flt(total - outstanding_amount, doc.precision("outstanding_amount")) < payable_amount def get_discounting_status(sales_invoice): From ccc0358db6ccaa164a20e5eaa19cf95a6fe526d1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:07:12 +0530 Subject: [PATCH 73/97] fix: use value from currency exchange when exchange api is disabled (backport #46137) (#46309) fix: use value from currency exchange when exchange api is disabled (#46137) (cherry picked from commit 2d26bff870254befa94fbb577a00b7e862030262) Co-authored-by: Venkatesh <47534423+venkat102@users.noreply.github.com> --- erpnext/setup/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index 638c8af6263..e41f27b4d8c 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -68,9 +68,6 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No if not transaction_date: transaction_date = nowdate() - if rate := get_pegged_rate(from_currency, to_currency, transaction_date): - return rate - currency_settings = frappe.get_doc("Accounts Settings").as_dict() allow_stale_rates = currency_settings.get("allow_stale") @@ -100,6 +97,9 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No if frappe.get_cached_value("Currency Exchange Settings", "Currency Exchange Settings", "disabled"): return 0.00 + if rate := get_pegged_rate(from_currency, to_currency, transaction_date): + return rate + try: cache = frappe.cache() key = f"currency_exchange_rate_{transaction_date}:{from_currency}:{to_currency}" From d5f07f06c7df951480593c54d36b4c2f3f3a0798 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:08:10 +0530 Subject: [PATCH 74/97] refactor: rename subcontracting fields (backport #46226) (#46301) * refactor: rename subcontracting fields (cherry picked from commit b4f65154f595fa7d4aa69fe2631602f7e73205c7) # Conflicts: # erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json * chore: resolve conflicts --------- Co-authored-by: Mihir Kandoi --- .../doctype/purchase_order/purchase_order.js | 2 +- .../doctype/purchase_order/purchase_order.py | 4 +-- .../purchase_order/test_purchase_order.py | 10 +++---- .../purchase_order_item.json | 8 +++--- .../purchase_order_item.py | 2 +- .../controllers/subcontracting_controller.py | 18 ++++++++----- erpnext/manufacturing/doctype/bom/bom.py | 10 +++---- erpnext/patches.txt | 1 + .../v15_0/rename_subcontracting_fields.py | 7 +++++ .../subcontracting_order.js | 4 +-- .../subcontracting_order.py | 18 ++++++++----- .../subcontracting_order_item.json | 26 ++++++++++++++++--- .../subcontracting_order_item.py | 2 +- 13 files changed, 74 insertions(+), 38 deletions(-) create mode 100644 erpnext/patches/v15_0/rename_subcontracting_fields.py diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 3a15c7de4ba..0b7c9de467a 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -404,7 +404,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends ( ); } } else { - if (!doc.items.every((item) => item.qty == item.sco_qty)) { + if (!doc.items.every((item) => item.qty == item.subcontracted_quantity)) { this.frm.add_custom_button( __("Subcontracting Order"), () => { diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 26c0101b49b..ee8ba35222f 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -898,7 +898,7 @@ def is_po_fully_subcontracted(po_name): query = ( frappe.qb.from_(table) .select(table.name) - .where((table.parent == po_name) & (table.qty != table.sco_qty)) + .where((table.parent == po_name) & (table.qty != table.subcontracted_quantity)) ) return not query.run(as_dict=True) @@ -945,7 +945,7 @@ def get_mapped_subcontracting_order(source_name, target_doc=None): "material_request_item": "material_request_item", }, "field_no_map": ["qty", "fg_item_qty", "amount"], - "condition": lambda item: item.qty != item.sco_qty, + "condition": lambda item: item.qty != item.subcontracted_quantity, }, }, target_doc, diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 5d8ce73e8ab..99ad609f8df 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -1076,9 +1076,9 @@ class TestPurchaseOrder(FrappeTestCase): # Test - 2: Subcontracted Quantity for the PO Items of each line item should be updated accordingly po.reload() - self.assertEqual(po.items[0].sco_qty, 5) - self.assertEqual(po.items[1].sco_qty, 0) - self.assertEqual(po.items[2].sco_qty, 12.5) + self.assertEqual(po.items[0].subcontracted_quantity, 5) + self.assertEqual(po.items[1].subcontracted_quantity, 0) + self.assertEqual(po.items[2].subcontracted_quantity, 12.5) # Test - 3: Amount for both FG Item and its Service Item should be updated correctly based on change in Quantity self.assertEqual(sco.items[0].amount, 2000) @@ -1114,10 +1114,10 @@ class TestPurchaseOrder(FrappeTestCase): # Test - 8: Subcontracted Quantity for each PO Item should be subtracted if SCO gets cancelled po.reload() - self.assertEqual(po.items[2].sco_qty, 25) + self.assertEqual(po.items[2].subcontracted_quantity, 25) sco.cancel() po.reload() - self.assertEqual(po.items[2].sco_qty, 12.5) + self.assertEqual(po.items[2].subcontracted_quantity, 12.5) sco = make_subcontracting_order(po.name) sco.save() diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 1724e3cc99c..894c705dc96 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -26,7 +26,7 @@ "quantity_and_rate", "qty", "stock_uom", - "sco_qty", + "subcontracted_quantity", "col_break2", "uom", "conversion_factor", @@ -913,7 +913,7 @@ }, { "allow_on_submit": 1, - "fieldname": "sco_qty", + "fieldname": "subcontracted_quantity", "fieldtype": "Float", "label": "Subcontracted Quantity", "no_copy": 1, @@ -921,11 +921,12 @@ "read_only": 1 } ], + "grid_page_length": 50, "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-02-18 12:35:04.432636", + "modified": "2025-03-02 16:58:26.059601", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", @@ -933,6 +934,7 @@ "owner": "Administrator", "permissions": [], "quick_entry": 1, + "row_format": "Dynamic", "search_fields": "item_name", "sort_field": "modified", "sort_order": "DESC", diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py index b80abda56c3..5e4ce19d340 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py @@ -80,10 +80,10 @@ class PurchaseOrderItem(Document): sales_order_item: DF.Data | None sales_order_packed_item: DF.Data | None schedule_date: DF.Date - sco_qty: DF.Float stock_qty: DF.Float stock_uom: DF.Link stock_uom_rate: DF.Currency + subcontracted_quantity: DF.Float supplier_part_no: DF.Data | None supplier_quotation: DF.Link | None supplier_quotation_item: DF.Link | None diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index b6a7f715d22..a672360046c 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -104,18 +104,18 @@ class SubcontractingController(StockController): ) if ( - self.doctype == "Subcontracting Order" and not item.sc_conversion_factor + self.doctype == "Subcontracting Order" and not item.subcontracting_conversion_factor ): # this condition will only be true if user has recently updated from develop branch service_item_qty = frappe.get_value( "Subcontracting Order Service Item", filters={"purchase_order_item": item.purchase_order_item, "parent": self.name}, fieldname=["qty"], ) - item.sc_conversion_factor = service_item_qty / item.qty + item.subcontracting_conversion_factor = service_item_qty / item.qty if self.doctype not in "Subcontracting Receipt" and item.qty > flt( - get_pending_sco_qty(self.purchase_order).get(item.purchase_order_item) - / item.sc_conversion_factor, + get_pending_subcontracted_quantity(self.purchase_order).get(item.purchase_order_item) + / item.subcontracting_conversion_factor, frappe.get_precision("Purchase Order Item", "qty"), ): frappe.throw( @@ -1132,10 +1132,14 @@ def get_item_details(items): return item_details -def get_pending_sco_qty(po_name): +def get_pending_subcontracted_quantity(po_name): table = frappe.qb.DocType("Purchase Order Item") - query = frappe.qb.from_(table).select(table.name, table.qty, table.sco_qty).where(table.parent == po_name) - return {item.name: item.qty - item.sco_qty for item in query.run(as_dict=True)} + query = ( + frappe.qb.from_(table) + .select(table.name, table.qty, table.subcontracted_quantity) + .where(table.parent == po_name) + ) + return {item.name: item.qty - item.subcontracted_quantity for item in query.run(as_dict=True)} @frappe.whitelist() diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 5d13471f541..054b482b260 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1371,7 +1371,7 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None): }, ) - def get_max_op_qty(): + def get_max_operation_quantity(): from frappe.query_builder.functions import Sum table = frappe.qb.DocType("Job Card") @@ -1387,7 +1387,7 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None): ) return min([d.qty for d in query.run(as_dict=True)], default=0) - def get_utilised_cc(): + def get_utilised_corrective_cost(): from frappe.query_builder.functions import Sum table = frappe.qb.DocType("Stock Entry") @@ -1417,15 +1417,15 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None): ) ) ): - max_qty = get_max_op_qty() - work_order.produced_qty - remaining_cc = work_order.corrective_operation_cost - get_utilised_cc() + max_qty = get_max_operation_quantity() - work_order.produced_qty + remaining_corrective_cost = work_order.corrective_operation_cost - get_utilised_corrective_cost() stock_entry.append( "additional_costs", { "expense_account": expense_account, "description": "Corrective Operation Cost", "has_corrective_cost": 1, - "amount": remaining_cc / max_qty * flt(stock_entry.fg_completed_qty), + "amount": remaining_corrective_cost / max_qty * flt(stock_entry.fg_completed_qty), }, ) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f292abfd50a..43d471f105e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -261,6 +261,7 @@ erpnext.patches.v14_0.show_loan_management_deprecation_warning erpnext.patches.v14_0.clear_reconciliation_values_from_singles execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True) erpnext.patches.v14_0.update_proprietorship_to_individual +erpnext.patches.v15_0.rename_subcontracting_fields [post_model_sync] erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets diff --git a/erpnext/patches/v15_0/rename_subcontracting_fields.py b/erpnext/patches/v15_0/rename_subcontracting_fields.py new file mode 100644 index 00000000000..d18d6149cac --- /dev/null +++ b/erpnext/patches/v15_0/rename_subcontracting_fields.py @@ -0,0 +1,7 @@ +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + rename_field("Purchase Order Item", "sco_qty", "subcontracted_quantity") + rename_field("Subcontracting Order Item", "sc_conversion_factor", "subcontracting_conversion_factor") diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index e9513a47597..bf0c8dc53f8 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -16,14 +16,14 @@ frappe.ui.form.on("Subcontracting Order Item", { service_item.doctype, service_item.name, "qty", - row.qty * row.sc_conversion_factor + row.qty * row.subcontracting_conversion_factor ); frappe.model.set_value(service_item.doctype, service_item.name, "fg_item_qty", row.qty); frappe.model.set_value( service_item.doctype, service_item.name, "amount", - row.qty * row.sc_conversion_factor * service_item.rate + row.qty * row.subcontracting_conversion_factor * service_item.rate ); }, before_items_remove(frm, cdt, cdn) { diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index f6d3fa04148..d171883c408 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -119,12 +119,12 @@ class SubcontractingOrder(SubcontractingController): def on_submit(self): self.update_prevdoc_status() self.update_status() - self.update_sco_qty_in_po() + self.update_subcontracted_quantity_in_po() def on_cancel(self): self.update_prevdoc_status() self.update_status() - self.update_sco_qty_in_po(cancel=True) + self.update_subcontracted_quantity_in_po(cancel=True) def validate_purchase_order_for_subcontracting(self): if self.purchase_order: @@ -162,7 +162,7 @@ class SubcontractingOrder(SubcontractingController): item = next( item for item in self.items if item.purchase_order_item == service_item.purchase_order_item ) - service_item.qty = item.qty * item.sc_conversion_factor + service_item.qty = item.qty * item.subcontracting_conversion_factor service_item.fg_item_qty = item.qty service_item.amount = service_item.qty * service_item.rate @@ -250,7 +250,7 @@ class SubcontractingOrder(SubcontractingController): item = frappe.get_doc("Item", si.fg_item) po_item = frappe.get_doc("Purchase Order Item", si.purchase_order_item) - available_qty = po_item.qty - po_item.sco_qty + available_qty = po_item.qty - po_item.subcontracted_quantity if available_qty == 0: continue @@ -276,7 +276,7 @@ class SubcontractingOrder(SubcontractingController): "schedule_date": self.schedule_date, "description": item.description, "qty": si.fg_item_qty, - "sc_conversion_factor": conversion_factor, + "subcontracting_conversion_factor": conversion_factor, "stock_uom": item.stock_uom, "bom": bom, "purchase_order_item": si.purchase_order_item, @@ -330,10 +330,14 @@ class SubcontractingOrder(SubcontractingController): self.update_ordered_qty_for_subcontracting() self.update_reserved_qty_for_subcontracting() - def update_sco_qty_in_po(self, cancel=False): + def update_subcontracted_quantity_in_po(self, cancel=False): for service_item in self.service_items: doc = frappe.get_doc("Purchase Order Item", service_item.purchase_order_item) - doc.sco_qty = (doc.sco_qty + service_item.qty) if not cancel else (doc.sco_qty - service_item.qty) + doc.subcontracted_quantity = ( + (doc.subcontracted_quantity + service_item.qty) + if not cancel + else (doc.subcontracted_quantity - service_item.qty) + ) doc.save() diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json index 31616944fda..e85929dd2bb 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json @@ -52,7 +52,7 @@ "section_break_34", "purchase_order_item", "page_break", - "sc_conversion_factor" + "subcontracting_conversion_factor" ], "fields": [ { @@ -384,18 +384,35 @@ "search_index": 1 }, { - "fieldname": "sc_conversion_factor", + "fieldname": "references_section", + "fieldtype": "Section Break", + "label": "References" + }, + { + "fieldname": "job_card", + "fieldtype": "Link", + "label": "Job Card", + "options": "Job Card", + "read_only": 1 + }, + { + "fieldname": "column_break_nfod", + "fieldtype": "Column Break" + }, + { + "fieldname": "subcontracting_conversion_factor", "fieldtype": "Float", "hidden": 1, - "label": "SC Conversion Factor", + "label": "Subcontracting Conversion Factor", "read_only": 1 } ], + "grid_page_length": 50, "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-12-13 13:35:28.935898", + "modified": "2025-03-02 17:05:28.386492", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Order Item", @@ -403,6 +420,7 @@ "owner": "Administrator", "permissions": [], "quick_entry": 1, + "row_format": "Dynamic", "search_fields": "item_name", "sort_field": "modified", "sort_order": "DESC", diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py index d8f2e5664e7..db49fccce3c 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py +++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py @@ -42,10 +42,10 @@ class SubcontractingOrderItem(Document): received_qty: DF.Float returned_qty: DF.Float rm_cost_per_qty: DF.Currency - sc_conversion_factor: DF.Float schedule_date: DF.Date | None service_cost_per_qty: DF.Currency stock_uom: DF.Link + subcontracting_conversion_factor: DF.Float warehouse: DF.Link # end: auto-generated types From fdaf5fafda7d1d42fe04b5b85c8e7ce16f78a14d Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 17 Feb 2025 14:36:15 +0530 Subject: [PATCH 75/97] fix: set landed cost based on purchase invoice rate (cherry picked from commit 17d415b1054bbfc483a1733c930b820c493b43ff) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py # erpnext/patches.txt # erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json --- .../purchase_invoice/test_purchase_invoice.py | 72 +++++++++++++++++++ erpnext/controllers/buying_controller.py | 2 +- erpnext/patches.txt | 8 +++ .../purchase_receipt/purchase_receipt.py | 49 ++++++++++--- .../purchase_receipt_item/patches/__init__.py | 0 .../recalculate_amount_difference_field.py | 37 ++++++++++ ...om_rate_difference_to_amount_difference.py | 17 +++++ .../purchase_receipt_item.json | 29 +++++--- .../purchase_receipt_item.py | 2 +- 9 files changed, 195 insertions(+), 21 deletions(-) create mode 100644 erpnext/stock/doctype/purchase_receipt_item/patches/__init__.py create mode 100644 erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py create mode 100644 erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index bc28edbf396..be6206965e0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2464,6 +2464,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) +<<<<<<< HEAD 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) @@ -2481,6 +2482,77 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): pi1.cancel() item.reload() self.assertEqual(item.last_purchase_rate, 0) +======= + def test_adjust_incoming_rate_from_pi_with_multi_currency_and_partial_billing(self): + frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0) + + frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1) + + pr = make_purchase_receipt( + qty=10, rate=10, currency="USD", do_not_save=1, supplier="_Test Supplier USD" + ) + pr.conversion_rate = 5300 + pr.save() + pr.submit() + + incoming_rate = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "incoming_rate", + ) + self.assertEqual(incoming_rate, 53000) # Asserting to confirm if the default calculation is correct + + pi = create_purchase_invoice_from_receipt(pr.name) + for row in pi.items: + row.qty = 1 + + pi.save() + pi.submit() + + incoming_rate = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "incoming_rate", + ) + # Test 1 : Incoming rate should not change as only the qty has changed and not the rate (this was not the case before) + self.assertEqual(incoming_rate, 53000) + + pi = create_purchase_invoice_from_receipt(pr.name) + for row in pi.items: + row.qty = 1 + row.rate = 9 + + pi.save() + pi.submit() + + incoming_rate = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "incoming_rate", + ) + # Test 2 : Rate in new PI is lower than PR, so incoming rate should also be lower + self.assertEqual(incoming_rate, 50350) + + pi = create_purchase_invoice_from_receipt(pr.name) + for row in pi.items: + row.qty = 1 + row.rate = 12 + + pi.save() + pi.submit() + + incoming_rate = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + "incoming_rate", + ) + # Test 3 : Rate in new PI is higher than PR, so incoming rate should also be higher + self.assertEqual(incoming_rate, 54766.667) + + frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0) + + frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) +>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate) def test_opening_invoice_rounding_adjustment_validation(self): pi = make_purchase_invoice(do_not_save=1) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 9643110b76a..c2a36ac36d0 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -333,7 +333,7 @@ class BuyingController(SubcontractingController): net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount) - + flt(item.get("rate_difference_with_purchase_invoice")) + + flt(item.get("amount_difference_with_purchase_invoice")) ) / qty_in_stock_uom else: item.valuation_rate = 0.0 diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a20b2a67bff..34794556022 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -261,7 +261,11 @@ erpnext.patches.v14_0.show_loan_management_deprecation_warning erpnext.patches.v14_0.clear_reconciliation_values_from_singles execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True) erpnext.patches.v14_0.update_proprietorship_to_individual +<<<<<<< HEAD erpnext.patches.v15_0.rename_subcontracting_fields +======= +erpnext.stock.doctype.purchase_receipt_item.patches.rename_field_from_rate_difference_to_amount_difference +>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate) [post_model_sync] erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets @@ -393,8 +397,12 @@ erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect erpnext.patches.v15_0.sync_auto_reconcile_config execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") erpnext.patches.v14_0.disable_add_row_in_gross_profit +<<<<<<< HEAD erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes erpnext.patches.v15_0.rename_sla_fields erpnext.patches.v15_0.update_query_report +======= +erpnext.stock.doctype.purchase_receipt_item.patches.recalculate_amount_difference_field +>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 7aa23c8153b..7f2c04316e9 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -424,6 +424,14 @@ class PurchaseReceipt(BuyingController): self.delete_auto_created_batches() self.set_consumed_qty_in_subcontract_order() + def before_cancel(self): + super().before_cancel() + self.remove_amount_difference_with_purchase_invoice() + + def remove_amount_difference_with_purchase_invoice(self): + for item in self.items: + item.amount_difference_with_purchase_invoice = 0 + def get_gl_entries(self, warehouse_account=None, via_landed_cost_voucher=False): from erpnext.accounts.general_ledger import process_gl_map @@ -571,15 +579,15 @@ class PurchaseReceipt(BuyingController): item=item, ) - def make_rate_difference_entry(item): - if item.rate_difference_with_purchase_invoice and stock_asset_rbnb: + def make_amount_difference_entry(item): + if item.amount_difference_with_purchase_invoice and stock_asset_rbnb: account_currency = get_account_currency(stock_asset_rbnb) self.add_gl_entry( gl_entries=gl_entries, account=stock_asset_rbnb, cost_center=item.cost_center, debit=0.0, - credit=flt(item.rate_difference_with_purchase_invoice), + credit=flt(item.amount_difference_with_purchase_invoice), remarks=_("Adjustment based on Purchase Invoice rate"), against_account=stock_asset_account_name, account_currency=account_currency, @@ -612,7 +620,7 @@ class PurchaseReceipt(BuyingController): + flt(item.landed_cost_voucher_amount) + flt(item.rm_supp_cost) + flt(item.item_tax_amount) - + flt(item.rate_difference_with_purchase_invoice) + + flt(item.amount_difference_with_purchase_invoice) ) divisional_loss = flt( @@ -712,7 +720,7 @@ class PurchaseReceipt(BuyingController): make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name) outgoing_amount = make_stock_received_but_not_billed_entry(d) make_landed_cost_gl_entries(d) - make_rate_difference_entry(d) + make_amount_difference_entry(d) make_sub_contracting_gl_entries(d) make_divisional_loss_gl_entry(d, outgoing_amount) elif (d.warehouse and d.warehouse not in warehouse_with_no_account) or ( @@ -1094,11 +1102,19 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate if adjust_incoming_rate: adjusted_amt = 0.0 - if item.billed_amt is not None and item.amount is not None: - adjusted_amt = flt(item.billed_amt) - flt(item.amount) + item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc) - adjusted_amt = adjusted_amt * flt(pr_doc.conversion_rate) - item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False) + if ( + item.billed_amt is not None + and item.amount is not None + and item_wise_billed_qty.get(item.name) + ): + adjusted_amt = ( + flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate) + ) * item.qty + + adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount")) + item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False) percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6) pr_doc.db_set("per_billed", percent_billed) @@ -1111,6 +1127,21 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate adjust_incoming_rate_for_pr(pr_doc) +def get_billed_qty_against_purchase_receipt(pr_doc): + pr_names = [d.name for d in pr_doc.items] + table = frappe.qb.DocType("Purchase Invoice Item") + query = ( + frappe.qb.from_(table) + .select(table.pr_detail, fn.Sum(table.qty).as_("qty")) + .where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1)) + ) + invoice_data = query.run(as_list=1) + + if not invoice_data: + return frappe._dict() + return frappe._dict(invoice_data) + + def adjust_incoming_rate_for_pr(doc): doc.update_valuation_rate(reset_outgoing_rate=False) diff --git a/erpnext/stock/doctype/purchase_receipt_item/patches/__init__.py b/erpnext/stock/doctype/purchase_receipt_item/patches/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py b/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py new file mode 100644 index 00000000000..e2d8bedaa66 --- /dev/null +++ b/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py @@ -0,0 +1,37 @@ +import frappe +from frappe.utils import flt + +from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + adjust_incoming_rate_for_pr, + get_billed_qty_against_purchase_receipt, +) + + +def execute(): + table = frappe.qb.DocType("Purchase Receipt Item") + query = ( + frappe.qb.from_(table) + .select(table.parent) + .distinct() + .where((table.amount_difference_with_purchase_invoice > 0) & (table.docstatus == 1)) + ) + pr_names = [item.parent for item in query.run(as_dict=True)] + + for pr_name in pr_names: + pr_doc = frappe.get_doc("Purchase Receipt", pr_name) + for item in pr_doc.items: + adjusted_amt = 0.0 + item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc) + + if ( + item.billed_amt is not None + and item.amount is not None + and item_wise_billed_qty.get(item.name) + ): + adjusted_amt = ( + flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate) + ) * item.qty + + adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount")) + item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False) + adjust_incoming_rate_for_pr(pr_doc) diff --git a/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py b/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py new file mode 100644 index 00000000000..44c8c49cba8 --- /dev/null +++ b/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py @@ -0,0 +1,17 @@ +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + frappe.db.set_value( + "DocField", + {"parent": "Purchase Receipt Item", "fieldname": "rate_difference_with_purchase_invoice"}, + "label", + "Amount Difference with Purchase Invoice", + ) + rename_field( + "Purchase Receipt Item", + "rate_difference_with_purchase_invoice", + "amount_difference_with_purchase_invoice", + ) + frappe.clear_cache(doctype="Purchase Receipt Item") diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 610bceddf0f..2f6598c3fb2 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -71,7 +71,7 @@ "item_tax_amount", "rm_supp_cost", "landed_cost_voucher_amount", - "rate_difference_with_purchase_invoice", + "amount_difference_with_purchase_invoice", "billed_amt", "warehouse_and_reference", "warehouse", @@ -998,14 +998,6 @@ "label": "Has Item Scanned", "read_only": 1 }, - { - "fieldname": "rate_difference_with_purchase_invoice", - "fieldtype": "Currency", - "label": "Rate Difference with Purchase Invoice", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, { "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1", "fieldname": "serial_and_batch_bundle", @@ -1135,12 +1127,29 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1 +<<<<<<< HEAD +======= + }, + { + "fieldname": "distributed_discount_amount", + "fieldtype": "Currency", + "label": "Distributed Discount Amount", + "options": "currency" + }, + { + "fieldname": "amount_difference_with_purchase_invoice", + "fieldtype": "Currency", + "label": "Amount Difference with Purchase Invoice", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 +>>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate) } ], "idx": 1, "istable": 1, "links": [], - "modified": "2024-07-19 12:14:21.521466", + "modified": "2025-02-17 13:15:36.692202", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py index 2154007771d..0db866f52c1 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py @@ -16,6 +16,7 @@ class PurchaseReceiptItem(Document): allow_zero_valuation_rate: DF.Check amount: DF.Currency + amount_difference_with_purchase_invoice: DF.Currency apply_tds: DF.Check asset_category: DF.Link | None asset_location: DF.Link | None @@ -76,7 +77,6 @@ class PurchaseReceiptItem(Document): qty: DF.Float quality_inspection: DF.Link | None rate: DF.Currency - rate_difference_with_purchase_invoice: DF.Currency rate_with_margin: DF.Currency received_qty: DF.Float received_stock_qty: DF.Float From ee41e55343166f88cd105fd608a3792d0fb84bb0 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 17 Feb 2025 18:05:58 +0530 Subject: [PATCH 76/97] perf: patch (cherry picked from commit a41024813b22fea5d737be8fab50224c2b909cbc) --- .../recalculate_amount_difference_field.py | 81 +++++++++++++------ 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py b/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py index e2d8bedaa66..9def5aa8d3b 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py +++ b/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py @@ -1,37 +1,70 @@ import frappe +from frappe.query_builder.functions import Sum from frappe.utils import flt -from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( - adjust_incoming_rate_for_pr, - get_billed_qty_against_purchase_receipt, -) +from erpnext.accounts.utils import get_fiscal_year +from erpnext.stock.doctype.purchase_receipt.purchase_receipt import adjust_incoming_rate_for_pr def execute(): + fiscal_year_dates = get_fiscal_year(frappe.utils.datetime.date.today()) table = frappe.qb.DocType("Purchase Receipt Item") + parent = frappe.qb.DocType("Purchase Receipt") query = ( frappe.qb.from_(table) - .select(table.parent) - .distinct() - .where((table.amount_difference_with_purchase_invoice > 0) & (table.docstatus == 1)) + .join(parent) + .on(table.parent == parent.name) + .select( + table.parent, + table.name, + table.amount, + table.billed_amt, + table.amount_difference_with_purchase_invoice, + table.rate, + table.qty, + parent.conversion_rate, + ) + .where( + (table.amount_difference_with_purchase_invoice != 0) + & (table.docstatus == 1) + & (parent.posting_date.between(fiscal_year_dates[1], fiscal_year_dates[2])) + ) ) - pr_names = [item.parent for item in query.run(as_dict=True)] + result = query.run(as_dict=True) - for pr_name in pr_names: - pr_doc = frappe.get_doc("Purchase Receipt", pr_name) - for item in pr_doc.items: - adjusted_amt = 0.0 - item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc) + item_wise_billed_qty = get_billed_qty_against_purchase_receipt([item.name for item in result]) - if ( - item.billed_amt is not None - and item.amount is not None - and item_wise_billed_qty.get(item.name) - ): - adjusted_amt = ( - flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate) - ) * item.qty + for item in result: + adjusted_amt = 0.0 - adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount")) - item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False) - adjust_incoming_rate_for_pr(pr_doc) + if item.billed_amt is not None and item.amount is not None and item_wise_billed_qty.get(item.name): + adjusted_amt = ( + flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate) + ) * item.qty + adjusted_amt = flt( + adjusted_amt * flt(item.conversion_rate), frappe.get_precision("Purchase Receipt Item", "amount") + ) + + if adjusted_amt != item.amount_difference_with_purchase_invoice: + frappe.db.set_value( + "Purchase Receipt Item", + item.name, + "amount_difference_with_purchase_invoice", + adjusted_amt, + update_modified=False, + ) + adjust_incoming_rate_for_pr(frappe.get_doc("Purchase Receipt", item.parent)) + + +def get_billed_qty_against_purchase_receipt(pr_names): + table = frappe.qb.DocType("Purchase Invoice Item") + query = ( + frappe.qb.from_(table) + .select(table.pr_detail, Sum(table.qty).as_("qty")) + .where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1)) + ) + invoice_data = query.run(as_list=1) + + if not invoice_data: + return frappe._dict() + return frappe._dict(invoice_data) From 7b13d8cd98763a2936cdf1e56ba798f36f85c1e5 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 17 Feb 2025 18:44:33 +0530 Subject: [PATCH 77/97] fix: fiscal year error (cherry picked from commit 7cf8e498c4ea5b7d2927af205ca815b8c4451cae) --- .../patches/recalculate_amount_difference_field.py | 9 +++------ ...me_field_from_rate_difference_to_amount_difference.py | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py b/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py index 9def5aa8d3b..0222388d270 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py +++ b/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py @@ -7,7 +7,6 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import adjust_incom def execute(): - fiscal_year_dates = get_fiscal_year(frappe.utils.datetime.date.today()) table = frappe.qb.DocType("Purchase Receipt Item") parent = frappe.qb.DocType("Purchase Receipt") query = ( @@ -24,12 +23,10 @@ def execute(): table.qty, parent.conversion_rate, ) - .where( - (table.amount_difference_with_purchase_invoice != 0) - & (table.docstatus == 1) - & (parent.posting_date.between(fiscal_year_dates[1], fiscal_year_dates[2])) - ) + .where((table.amount_difference_with_purchase_invoice != 0) & (table.docstatus == 1)) ) + if fiscal_year_dates := get_fiscal_year(frappe.utils.datetime.date.today(), raise_on_missing=False): + query.where(parent.posting_date.between(fiscal_year_dates[1], fiscal_year_dates[2])) result = query.run(as_dict=True) item_wise_billed_qty = get_billed_qty_against_purchase_receipt([item.name for item in result]) diff --git a/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py b/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py index 44c8c49cba8..ce802b029da 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py +++ b/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py @@ -13,5 +13,6 @@ def execute(): "Purchase Receipt Item", "rate_difference_with_purchase_invoice", "amount_difference_with_purchase_invoice", + validate=False, ) frappe.clear_cache(doctype="Purchase Receipt Item") From a09c57f0d1429d737a2819cd5b6ea87b44843fbb Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 17 Feb 2025 20:20:17 +0530 Subject: [PATCH 78/97] fix: revert last commit (cherry picked from commit 154e9813c4f27c2a7d51b133fd89cdfbaa1f1477) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 8 ++++ .../recalculate_amount_difference_field.py | 45 ++++++++++--------- ...om_rate_difference_to_amount_difference.py | 1 - 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 34794556022..604f0ff6141 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -262,10 +262,13 @@ erpnext.patches.v14_0.clear_reconciliation_values_from_singles execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True) erpnext.patches.v14_0.update_proprietorship_to_individual <<<<<<< HEAD +<<<<<<< HEAD erpnext.patches.v15_0.rename_subcontracting_fields ======= erpnext.stock.doctype.purchase_receipt_item.patches.rename_field_from_rate_difference_to_amount_difference >>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate) +======= +>>>>>>> 154e9813c4 (fix: revert last commit) [post_model_sync] erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets @@ -398,6 +401,7 @@ erpnext.patches.v15_0.sync_auto_reconcile_config execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") erpnext.patches.v14_0.disable_add_row_in_gross_profit <<<<<<< HEAD +<<<<<<< HEAD erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes @@ -406,3 +410,7 @@ erpnext.patches.v15_0.update_query_report ======= erpnext.stock.doctype.purchase_receipt_item.patches.recalculate_amount_difference_field >>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate) +======= +erpnext.stock.doctype.purchase_receipt_item.patches.rename_field_from_rate_difference_to_amount_difference +erpnext.stock.doctype.purchase_receipt_item.patches.recalculate_amount_difference_field +>>>>>>> 154e9813c4 (fix: revert last commit) diff --git a/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py b/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py index 0222388d270..fc904baa927 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py +++ b/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py @@ -27,30 +27,35 @@ def execute(): ) if fiscal_year_dates := get_fiscal_year(frappe.utils.datetime.date.today(), raise_on_missing=False): query.where(parent.posting_date.between(fiscal_year_dates[1], fiscal_year_dates[2])) - result = query.run(as_dict=True) - item_wise_billed_qty = get_billed_qty_against_purchase_receipt([item.name for item in result]) + if result := query.run(as_dict=True): + item_wise_billed_qty = get_billed_qty_against_purchase_receipt([item.name for item in result]) - for item in result: - adjusted_amt = 0.0 + for item in result: + adjusted_amt = 0.0 - if item.billed_amt is not None and item.amount is not None and item_wise_billed_qty.get(item.name): - adjusted_amt = ( - flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate) - ) * item.qty - adjusted_amt = flt( - adjusted_amt * flt(item.conversion_rate), frappe.get_precision("Purchase Receipt Item", "amount") - ) - - if adjusted_amt != item.amount_difference_with_purchase_invoice: - frappe.db.set_value( - "Purchase Receipt Item", - item.name, - "amount_difference_with_purchase_invoice", - adjusted_amt, - update_modified=False, + if ( + item.billed_amt is not None + and item.amount is not None + and item_wise_billed_qty.get(item.name) + ): + adjusted_amt = ( + flt(item.billed_amt / item_wise_billed_qty.get(item.name)) - flt(item.rate) + ) * item.qty + adjusted_amt = flt( + adjusted_amt * flt(item.conversion_rate), + frappe.get_precision("Purchase Receipt Item", "amount"), ) - adjust_incoming_rate_for_pr(frappe.get_doc("Purchase Receipt", item.parent)) + + if adjusted_amt != item.amount_difference_with_purchase_invoice: + frappe.db.set_value( + "Purchase Receipt Item", + item.name, + "amount_difference_with_purchase_invoice", + adjusted_amt, + update_modified=False, + ) + adjust_incoming_rate_for_pr(frappe.get_doc("Purchase Receipt", item.parent)) def get_billed_qty_against_purchase_receipt(pr_names): diff --git a/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py b/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py index ce802b029da..44c8c49cba8 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py +++ b/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py @@ -13,6 +13,5 @@ def execute(): "Purchase Receipt Item", "rate_difference_with_purchase_invoice", "amount_difference_with_purchase_invoice", - validate=False, ) frappe.clear_cache(doctype="Purchase Receipt Item") From 8f2fdcae88252cddaaeeb89f881671c2101ad886 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 19 Feb 2025 13:03:45 +0530 Subject: [PATCH 79/97] fix: patch (cherry picked from commit 1230127d24cc1ae97129ad8e592ca3cdf8f59c32) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 5 +++++ .../v15_0}/recalculate_amount_difference_field.py | 9 +++++++-- ...me_field_from_rate_difference_to_amount_difference.py | 0 .../doctype/purchase_receipt_item/patches/__init__.py | 0 4 files changed, 12 insertions(+), 2 deletions(-) rename erpnext/{stock/doctype/purchase_receipt_item/patches => patches/v15_0}/recalculate_amount_difference_field.py (88%) rename erpnext/{stock/doctype/purchase_receipt_item/patches => patches/v15_0}/rename_field_from_rate_difference_to_amount_difference.py (100%) delete mode 100644 erpnext/stock/doctype/purchase_receipt_item/patches/__init__.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 604f0ff6141..e0a1fcf3cbf 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -402,6 +402,7 @@ execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_post erpnext.patches.v14_0.disable_add_row_in_gross_profit <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes @@ -414,3 +415,7 @@ erpnext.stock.doctype.purchase_receipt_item.patches.recalculate_amount_differenc erpnext.stock.doctype.purchase_receipt_item.patches.rename_field_from_rate_difference_to_amount_difference erpnext.stock.doctype.purchase_receipt_item.patches.recalculate_amount_difference_field >>>>>>> 154e9813c4 (fix: revert last commit) +======= +erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference +erpnext.patches.v15_0.recalculate_amount_difference_field +>>>>>>> 1230127d24 (fix: patch) diff --git a/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py b/erpnext/patches/v15_0/recalculate_amount_difference_field.py similarity index 88% rename from erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py rename to erpnext/patches/v15_0/recalculate_amount_difference_field.py index fc904baa927..5ece5f08ddb 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/patches/recalculate_amount_difference_field.py +++ b/erpnext/patches/v15_0/recalculate_amount_difference_field.py @@ -31,6 +31,8 @@ def execute(): if result := query.run(as_dict=True): item_wise_billed_qty = get_billed_qty_against_purchase_receipt([item.name for item in result]) + purchase_receipts = set() + precision = frappe.get_precision("Purchase Receipt Item", "amount") for item in result: adjusted_amt = 0.0 @@ -44,7 +46,7 @@ def execute(): ) * item.qty adjusted_amt = flt( adjusted_amt * flt(item.conversion_rate), - frappe.get_precision("Purchase Receipt Item", "amount"), + precision, ) if adjusted_amt != item.amount_difference_with_purchase_invoice: @@ -55,7 +57,10 @@ def execute(): adjusted_amt, update_modified=False, ) - adjust_incoming_rate_for_pr(frappe.get_doc("Purchase Receipt", item.parent)) + purchase_receipts.add(item.parent) + + for pr in purchase_receipts: + adjust_incoming_rate_for_pr(frappe.get_doc("Purchase Receipt", pr)) def get_billed_qty_against_purchase_receipt(pr_names): diff --git a/erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py b/erpnext/patches/v15_0/rename_field_from_rate_difference_to_amount_difference.py similarity index 100% rename from erpnext/stock/doctype/purchase_receipt_item/patches/rename_field_from_rate_difference_to_amount_difference.py rename to erpnext/patches/v15_0/rename_field_from_rate_difference_to_amount_difference.py diff --git a/erpnext/stock/doctype/purchase_receipt_item/patches/__init__.py b/erpnext/stock/doctype/purchase_receipt_item/patches/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 From 5a3073c4c145a0ae3862c824be71e6d5f6e5ef1c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:48:01 +0530 Subject: [PATCH 80/97] fix: incorrect batch picked in the pick list (backport #45761) (#46315) fix: incorrect batch picked in the pick list (cherry picked from commit e1b7688a17fd5b07e89fc6f044ca32a297b378e9) Co-authored-by: Rohit Waghchaure --- erpnext/stock/doctype/pick_list/pick_list.js | 7 +++ erpnext/stock/doctype/pick_list/pick_list.py | 66 +++++++++++++++++++- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index 518782d759b..6a6bb226a9e 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -2,6 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on("Pick List", { + after_save(frm) { + setTimeout(() => { + // Added to fix the issue of locations table not getting updated after save + frm.reload_doc(); + }, 500); + }, + setup: (frm) => { frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"]; diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 4550d71677a..998f15945d3 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -73,6 +73,7 @@ class PickList(Document): self.set_onload("has_reserved_stock", True) def validate(self): + self.validate_expired_batches() self.validate_for_qty() self.validate_stock_qty() self.check_serial_no_status() @@ -205,6 +206,33 @@ class PickList(Document): self.update_reference_qty() self.update_sales_order_picking_status() + def validate_expired_batches(self): + batches = [] + for row in self.get("locations"): + if row.get("batch_no") and row.get("picked_qty"): + batches.append(row.batch_no) + + if batches: + batch = frappe.qb.DocType("Batch") + query = ( + frappe.qb.from_(batch) + .select(batch.name) + .where( + (batch.name.isin(batches)) + & (batch.expiry_date <= frappe.utils.nowdate()) + & (batch.expiry_date.isnotnull()) + ) + ) + + expired_batches = query.run(as_dict=True) + if expired_batches: + msg = "
    " + "".join(f"
  • {batch.name}
  • " for batch in expired_batches) + "
" + + frappe.throw( + _("The following batches are expired, please restock them:
{0}").format(msg), + title=_("Expired Batches"), + ) + def make_bundle_using_old_serial_batch_fields(self): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -447,6 +475,7 @@ class PickList(Document): self.remove(row) updated_locations = frappe._dict() + len_idx = len(self.get("locations")) or 0 for item_doc in items: item_code = item_doc.item_code @@ -489,6 +518,8 @@ class PickList(Document): if location.picked_qty > location.stock_qty: location.picked_qty = location.stock_qty + len_idx += 1 + location.idx = len_idx self.append("locations", location) # If table is empty on update after submit, set stock_qty, picked_qty to 0 so that indicator is red @@ -497,7 +528,11 @@ class PickList(Document): for location in locations_replica: location.stock_qty = 0 location.picked_qty = 0 + + len_idx += 1 + location.idx = len_idx self.append("locations", location) + frappe.msgprint( _( "Please Restock Items and Update the Pick List to continue. To discontinue, cancel the Pick List." @@ -638,8 +673,31 @@ class PickList(Document): if serial_no: picked_items[item_data.item_code][key]["serial_no"].extend(serial_no) + self.update_picked_item_from_current_pick_list(picked_items) return picked_items + def update_picked_item_from_current_pick_list(self, picked_items): + for row in self.locations: + if flt(row.picked_qty) > 0: + key = (row.warehouse, row.batch_no) if row.batch_no else row.warehouse + serial_no = [x for x in row.serial_no.split("\n") if x] if row.serial_no else None + if row.item_code not in picked_items: + picked_items[row.item_code] = {} + + if key not in picked_items[row.item_code]: + picked_items[row.item_code][key] = frappe._dict( + { + "picked_qty": 0, + "serial_no": [], + "batch_no": row.batch_no or "", + "warehouse": row.warehouse, + } + ) + + picked_items[row.item_code][key]["picked_qty"] += flt(row.stock_qty) or flt(row.picked_qty) + if serial_no: + picked_items[row.item_code][key]["serial_no"].extend(serial_no) + def _get_pick_list_items(self, items): pi = frappe.qb.DocType("Pick List") pi_item = frappe.qb.DocType("Pick List Item") @@ -653,9 +711,11 @@ class PickList(Document): pi_item.batch_no, pi_item.serial_and_batch_bundle, pi_item.serial_no, - (Case().when(pi_item.picked_qty > 0, pi_item.picked_qty).else_(pi_item.stock_qty)).as_( - "picked_qty" - ), + ( + Case() + .when((pi_item.picked_qty > 0) & (pi_item.docstatus == 1), pi_item.picked_qty) + .else_(pi_item.stock_qty) + ).as_("picked_qty"), ) .where( (pi_item.item_code.isin([x.item_code for x in items])) From 941d67a0b63b8010473f735a65cf73d36ae57846 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Mar 2025 14:48:41 +0530 Subject: [PATCH 81/97] chore: resolve conflicts --- .../purchase_invoice/test_purchase_invoice.py | 4 +--- erpnext/patches.txt | 12 ------------ .../purchase_receipt_item/purchase_receipt_item.json | 9 --------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index be6206965e0..e2386122522 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2464,7 +2464,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) -<<<<<<< HEAD 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) @@ -2482,7 +2481,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): pi1.cancel() item.reload() self.assertEqual(item.last_purchase_rate, 0) -======= + def test_adjust_incoming_rate_from_pi_with_multi_currency_and_partial_billing(self): frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0) @@ -2552,7 +2551,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0) frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) ->>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate) def test_opening_invoice_rounding_adjustment_validation(self): pi = make_purchase_invoice(do_not_save=1) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e0a1fcf3cbf..18feaa19ee5 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -400,22 +400,10 @@ erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect erpnext.patches.v15_0.sync_auto_reconcile_config execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") erpnext.patches.v14_0.disable_add_row_in_gross_profit -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes erpnext.patches.v15_0.rename_sla_fields erpnext.patches.v15_0.update_query_report -======= -erpnext.stock.doctype.purchase_receipt_item.patches.recalculate_amount_difference_field ->>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate) -======= -erpnext.stock.doctype.purchase_receipt_item.patches.rename_field_from_rate_difference_to_amount_difference -erpnext.stock.doctype.purchase_receipt_item.patches.recalculate_amount_difference_field ->>>>>>> 154e9813c4 (fix: revert last commit) -======= erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference erpnext.patches.v15_0.recalculate_amount_difference_field ->>>>>>> 1230127d24 (fix: patch) diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 2f6598c3fb2..12d3f99e37b 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -1127,14 +1127,6 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1 -<<<<<<< HEAD -======= - }, - { - "fieldname": "distributed_discount_amount", - "fieldtype": "Currency", - "label": "Distributed Discount Amount", - "options": "currency" }, { "fieldname": "amount_difference_with_purchase_invoice", @@ -1143,7 +1135,6 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 ->>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate) } ], "idx": 1, From fe8c9a360556c86b505ef18d32facaff6af637af Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Mar 2025 14:54:40 +0530 Subject: [PATCH 82/97] chore: resolve conflicts --- erpnext/patches.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 18feaa19ee5..8509989a2ca 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -261,14 +261,7 @@ erpnext.patches.v14_0.show_loan_management_deprecation_warning erpnext.patches.v14_0.clear_reconciliation_values_from_singles execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True) erpnext.patches.v14_0.update_proprietorship_to_individual -<<<<<<< HEAD -<<<<<<< HEAD erpnext.patches.v15_0.rename_subcontracting_fields -======= -erpnext.stock.doctype.purchase_receipt_item.patches.rename_field_from_rate_difference_to_amount_difference ->>>>>>> 17d415b105 (fix: set landed cost based on purchase invoice rate) -======= ->>>>>>> 154e9813c4 (fix: revert last commit) [post_model_sync] erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets From 836fd8fbc4a873e4dde27d130b0006ad3eb354d5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:07:36 +0530 Subject: [PATCH 83/97] fix: consider journal entry and return invoice in paid_amount calculation (backport #46129) (#46319) fix: consider journal entry and return invoice in paid_amount calculation (#46129) * fix: consider journal entry and return invoice in paid_amount calculation * test: add new unit test to consider journal entry and return invoice in paid_amount calculation (cherry picked from commit 425fb12e9162f06b7868738d94e6df68c27d99a0) Co-authored-by: Sugesh G <73237300+Sugesh393@users.noreply.github.com> --- .../payment_request/payment_request.py | 40 ++++++++++++------- .../payment_request/test_payment_request.py | 28 +++++++++++++ 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 84e44621784..4f6205a2445 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -768,29 +768,39 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None = def get_existing_paid_amount(doctype, name): - PL = frappe.qb.DocType("Payment Ledger Entry") + PLE = frappe.qb.DocType("Payment Ledger Entry") PER = frappe.qb.DocType("Payment Entry Reference") query = ( - frappe.qb.from_(PL) + frappe.qb.from_(PLE) .left_join(PER) .on( - (PL.against_voucher_type == PER.reference_doctype) - & (PL.against_voucher_no == PER.reference_name) - & (PL.voucher_type == PER.parenttype) - & (PL.voucher_no == PER.parent) + (PLE.against_voucher_type == PER.reference_doctype) + & (PLE.against_voucher_no == PER.reference_name) + & (PLE.voucher_type == PER.parenttype) + & (PLE.voucher_no == PER.parent) + ) + .select( + Abs(Sum(PLE.amount)).as_("total_amount"), + Abs(Sum(frappe.qb.terms.Case().when(PER.payment_request.isnotnull(), PLE.amount).else_(0))).as_( + "request_paid_amount" + ), + ) + .where( + (PLE.voucher_type.isin([doctype, "Journal Entry", "Payment Entry"])) + & (PLE.against_voucher_type == doctype) + & (PLE.against_voucher_no == name) + & (PLE.delinked == 0) + & (PLE.docstatus == 1) + & (PLE.amount < 0) ) - .select(Abs(Sum(PL.amount)).as_("total_paid_amount")) - .where(PL.against_voucher_type.eq(doctype)) - .where(PL.against_voucher_no.eq(name)) - .where(PL.amount < 0) - .where(PL.delinked == 0) - .where(PER.docstatus == 1) - .where(PER.payment_request.isnull()) ) - response = query.run() - return response[0][0] if response[0] else 0 + result = query.run() + ledger_amount = flt(result[0][0]) if result else 0 + request_paid_amount = flt(result[0][1]) if result else 0 + + return ledger_amount - request_paid_amount def get_gateway_details(args): # nosemgrep diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index 7ce6cc0b7b7..02ecb85ac4d 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -581,6 +581,34 @@ class TestPaymentRequest(FrappeTestCase): pi.load_from_db() self.assertEqual(pr_2.grand_total, pi.outstanding_amount) + def test_consider_journal_entry_and_return_invoice(self): + from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry + + si = create_sales_invoice(currency="INR", qty=5, rate=500) + + je = make_journal_entry("_Test Cash - _TC", "Debtors - _TC", 500, save=False) + je.accounts[1].party_type = "Customer" + je.accounts[1].party = si.customer + je.accounts[1].reference_type = "Sales Invoice" + je.accounts[1].reference_name = si.name + je.accounts[1].credit_in_account_currency = 500 + je.submit() + + pe = get_payment_entry("Sales Invoice", si.name) + pe.paid_amount = 500 + pe.references[0].allocated_amount = 500 + pe.save() + pe.submit() + + cr_note = create_sales_invoice(qty=-1, rate=500, is_return=1, return_against=si.name, do_not_save=1) + cr_note.update_outstanding_for_self = 0 + cr_note.save() + cr_note.submit() + + si.load_from_db() + pr = make_payment_request(dt="Sales Invoice", dn=si.name, mute_email=1) + self.assertEqual(pr.grand_total, si.outstanding_amount) + def test_partial_paid_invoice_with_submitted_payment_entry(self): pi = make_purchase_invoice(currency="INR", qty=1, rate=5000) From e3ce17bd6e8d07892ae92539c52f3512c387b921 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:19:41 +0530 Subject: [PATCH 84/97] fix:[regional] Italian einvoice xml generated with wrong prices (#40254) (backport #45840) (#46318) Merge pull request #45840 from gms-electronics/40254-italian-einvoice fix:[regional] Italian einvoice xml generated with wrong prices (#40254) (cherry picked from commit c5f90c823d5e1d7a5d04a0869b8a1635c3fa2c8e) Co-authored-by: Fab --- erpnext/regional/italy/e-invoice.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/italy/e-invoice.xml b/erpnext/regional/italy/e-invoice.xml index 69b8e3e488d..7c436a2b449 100644 --- a/erpnext/regional/italy/e-invoice.xml +++ b/erpnext/regional/italy/e-invoice.xml @@ -188,9 +188,9 @@ {{ html2text(item.description or '') or item.item_name }} {{ format_float(item.qty) }} {{ item.stock_uom }} - {{ format_float(item.price_list_rate or item.rate, item_meta.get_field("rate").precision) }} + {{ format_float(item.net_rate or item.price_list_rate or item.rate, item_meta.get_field("rate").precision) }} {{ render_discount_or_margin(item) }} - {{ format_float(item.amount, item_meta.get_field("amount").precision) }} + {{ format_float(item.net_amount, item_meta.get_field("amount").precision) }} {{ format_float(item.tax_rate, item_meta.get_field("tax_rate").precision) }} {%- if item.tax_exemption_reason %} {{ item.tax_exemption_reason.split("-")[0] }} From 9a433a675022b48d8edbac5aa6fdd57c6ea01c02 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:22:46 +0530 Subject: [PATCH 85/97] fix: adding cost center on pos invoice items while applying product discount (backport #46082) (#46322) fix: adding cost center on pos invoice items while applying product discount (#46082) (cherry picked from commit 926e4ecc4fbcdb9e5f33d44e76d89c39819e66f1) Co-authored-by: Diptanil Saha --- erpnext/public/js/controllers/transaction.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 69ecac19852..a98cb450eb3 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1857,7 +1857,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe const exist_items = items.map(row => { return {item_code: row.item_code, pricing_rules: row.pricing_rules};}); - args.free_item_data.forEach(pr_row => { + args.free_item_data.forEach(async pr_row => { let row_to_modify = {}; // If there are no free items, or if the current free item doesn't exist in the table, add it @@ -1875,6 +1875,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe for (let key in pr_row) { row_to_modify[key] = pr_row[key]; } + + if (this.frm.doc.hasOwnProperty("is_pos") && this.frm.doc.is_pos) { + let r = await frappe.db.get_value("POS Profile", this.frm.doc.pos_profile, "cost_center"); + if (r.message.cost_center) { + row_to_modify["cost_center"] = r.message.cost_center; + } + } + this.frm.script_manager.copy_from_first_row("items", row_to_modify, ["expense_account", "income_account"]); }); From 506dd3c6b94c25b1db4f35bf62fabf9d44f5ac00 Mon Sep 17 00:00:00 2001 From: Ben Kebdani Date: Wed, 5 Mar 2025 13:04:55 +0300 Subject: [PATCH 86/97] fix: Close and Reopen buttons dissapear after saving changes (#46048) * fix: Close and Reopen buttons dissapear after saving changes * style: linter issue --------- Co-authored-by: Nabin Hait --- erpnext/support/doctype/issue/issue.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index 03d209e99e3..eb53469acd5 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -15,7 +15,9 @@ frappe.ui.form.on("Issue", { } } ); + }, + refresh: function (frm) { // buttons if (frm.doc.status !== "Closed") { frm.add_custom_button(__("Close"), function () { From 6aa8803068752b1e1906b0ce664bcacd0b9cb0bb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 16:18:42 +0530 Subject: [PATCH 87/97] fix: consolidate gl entries by project in General Ledger Report (backport #46314) (#46321) fix: consolidate gl entries by project in General Ledger Report (#46314) (cherry picked from commit 1f685efcaf01d11583b7c0f23a4e2f35a32dce92) Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com> --- erpnext/accounts/report/general_ledger/general_ledger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index ed16892a36e..a62ba2e3732 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -534,6 +534,7 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, tot for dim in accounting_dimensions: keylist.append(gle.get(dim)) keylist.append(gle.get("cost_center")) + keylist.append(gle.get("project")) key = tuple(keylist) if key not in consolidated_gle: @@ -679,10 +680,11 @@ def get_columns(filters): {"label": _("Against Account"), "fieldname": "against", "width": 120}, {"label": _("Party Type"), "fieldname": "party_type", "width": 100}, {"label": _("Party"), "fieldname": "party", "width": 100}, - {"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100}, ] if filters.get("include_dimensions"): + columns.append({"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100}) + for dim in get_accounting_dimensions(as_list=False): columns.append( {"label": _(dim.label), "options": dim.label, "fieldname": dim.fieldname, "width": 100} From aaf35c5df930da736cce59861034e9ba1e9c2fb2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 16:18:50 +0530 Subject: [PATCH 88/97] fix: Naming of Purchase Amount (backport #46051) (#46324) fix: Naming of Purchase Amount (#46051) * fix: Naming of Purchase Amount * fix: linters (cherry picked from commit 104f60cc57730857e0a7f296df24b3268b590522) Co-authored-by: 0xD0M1M0 <76812428+0xD0M1M0@users.noreply.github.com> --- erpnext/assets/doctype/asset/asset.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 06b851f2cca..aa5fd217665 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -225,7 +225,7 @@ { "fieldname": "gross_purchase_amount", "fieldtype": "Currency", - "label": "Gross Purchase Amount", + "label": "Net Purchase Amount", "mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)", "options": "Company:company:default_currency" }, @@ -592,7 +592,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2025-02-11 16:01:56.140904", + "modified": "2025-02-20 14:09:05.421913", "modified_by": "Administrator", "module": "Assets", "name": "Asset", From 5668795884919f6fee4703ce9da2d6386148f3fd Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Mar 2025 13:48:34 +0530 Subject: [PATCH 89/97] fix: production analytics report (cherry picked from commit 772e9ecfaab0b71a51b3769e2f621ffae11e7095) --- .../production_analytics.py | 57 +++++++------------ 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py index e511612d3a3..0a69cd5b1c0 100644 --- a/erpnext/manufacturing/report/production_analytics/production_analytics.py +++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py @@ -4,7 +4,7 @@ import frappe from frappe import _, scrub -from frappe.utils import getdate +from frappe.utils import getdate, today from erpnext.stock.report.stock_analytics.stock_analytics import get_period, get_period_date_ranges @@ -30,7 +30,6 @@ def get_columns(filters): def get_periodic_data(filters, entry): periodic_data = { - "All Work Orders": {}, "Not Started": {}, "Overdue": {}, "Pending": {}, @@ -42,33 +41,26 @@ def get_periodic_data(filters, entry): for from_date, end_date in ranges: period = get_period(end_date, filters) for d in entry: - if getdate(d.creation) <= getdate(from_date) or getdate(d.creation) <= getdate(end_date): - periodic_data = update_periodic_data(periodic_data, "All Work Orders", period) - if d.status == "Completed": - if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate( - from_date - ): - periodic_data = update_periodic_data(periodic_data, "Completed", period) - elif getdate(d.actual_start_date) < getdate(from_date): - periodic_data = update_periodic_data(periodic_data, "Pending", period) - elif getdate(d.planned_start_date) < getdate(from_date): - periodic_data = update_periodic_data(periodic_data, "Overdue", period) - else: - periodic_data = update_periodic_data(periodic_data, "Not Started", period) + if getdate(from_date) <= getdate(d.creation) <= getdate(end_date) and d.status not in [ + "Draft", + "Submitted", + "Completed", + "Stopped", + "Closed", + "Cancelled", + ]: + if d.status == "Not Started": + periodic_data = update_periodic_data(periodic_data, "Not Started", period) + elif today() > getdate(d.planned_end_date): + periodic_data = update_periodic_data(periodic_data, "Overdue", period) + elif today() < getdate(d.planned_end_date): + periodic_data = update_periodic_data(periodic_data, "Pending", period) - elif d.status == "In Process": - if getdate(d.actual_start_date) < getdate(from_date): - periodic_data = update_periodic_data(periodic_data, "Pending", period) - elif getdate(d.planned_start_date) < getdate(from_date): - periodic_data = update_periodic_data(periodic_data, "Overdue", period) - else: - periodic_data = update_periodic_data(periodic_data, "Not Started", period) - - elif d.status == "Not Started": - if getdate(d.planned_start_date) < getdate(from_date): - periodic_data = update_periodic_data(periodic_data, "Overdue", period) - else: - periodic_data = update_periodic_data(periodic_data, "Not Started", period) + if ( + getdate(from_date) <= getdate(d.actual_end_date) <= getdate(end_date) + and d.status == "Completed" + ): + periodic_data = update_periodic_data(periodic_data, "Completed", period) return periodic_data @@ -88,10 +80,7 @@ def get_data(filters, columns): "Work Order", fields=[ "creation", - "modified", - "actual_start_date", "actual_end_date", - "planned_start_date", "planned_end_date", "status", ], @@ -100,7 +89,7 @@ def get_data(filters, columns): periodic_data = get_periodic_data(filters, entry) - labels = ["All Work Orders", "Not Started", "Overdue", "Pending", "Completed"] + labels = ["Not Started", "Overdue", "Pending", "Completed"] chart_data = get_chart_data(periodic_data, columns) ranges = get_period_date_ranges(filters) @@ -121,17 +110,15 @@ def get_data(filters, columns): def get_chart_data(periodic_data, columns): labels = [d.get("label") for d in columns[1:]] - all_data, not_start, overdue, pending, completed = [], [], [], [], [] + not_start, overdue, pending, completed = [], [], [], [] datasets = [] for d in labels: - all_data.append(periodic_data.get("All Work Orders").get(d)) not_start.append(periodic_data.get("Not Started").get(d)) overdue.append(periodic_data.get("Overdue").get(d)) pending.append(periodic_data.get("Pending").get(d)) completed.append(periodic_data.get("Completed").get(d)) - datasets.append({"name": _("All Work Orders"), "values": all_data}) datasets.append({"name": _("Not Started"), "values": not_start}) datasets.append({"name": _("Overdue"), "values": overdue}) datasets.append({"name": _("Pending"), "values": pending}) From 6f760d197d9620aac598ec8b2cbe509723f71d04 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Mar 2025 13:56:10 +0530 Subject: [PATCH 90/97] fix: use else instead of unnecessary elif (cherry picked from commit 961258a4ce5116b0b93ba6a538aa750012560267) --- .../report/production_analytics/production_analytics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py index 0a69cd5b1c0..4b09de87ba6 100644 --- a/erpnext/manufacturing/report/production_analytics/production_analytics.py +++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py @@ -53,7 +53,7 @@ def get_periodic_data(filters, entry): periodic_data = update_periodic_data(periodic_data, "Not Started", period) elif today() > getdate(d.planned_end_date): periodic_data = update_periodic_data(periodic_data, "Overdue", period) - elif today() < getdate(d.planned_end_date): + else: periodic_data = update_periodic_data(periodic_data, "Pending", period) if ( From 7749814571d8f373ada0c99111d483a47718bd51 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Mar 2025 15:14:54 +0530 Subject: [PATCH 91/97] feat: add new Closed and Stopped rows (cherry picked from commit 6cc3d678352fad2c7cdc349cf90562b50091b611) --- .../production_analytics.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py index 4b09de87ba6..1c4082233e1 100644 --- a/erpnext/manufacturing/report/production_analytics/production_analytics.py +++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py @@ -34,6 +34,8 @@ def get_periodic_data(filters, entry): "Overdue": {}, "Pending": {}, "Completed": {}, + "Closed": {}, + "Stopped": {}, } ranges = get_period_date_ranges(filters) @@ -45,15 +47,13 @@ def get_periodic_data(filters, entry): "Draft", "Submitted", "Completed", - "Stopped", - "Closed", "Cancelled", ]: - if d.status == "Not Started": - periodic_data = update_periodic_data(periodic_data, "Not Started", period) + if d.status in ["Not Started", "Closed", "Stopped"]: + periodic_data = update_periodic_data(periodic_data, d.status, period) elif today() > getdate(d.planned_end_date): periodic_data = update_periodic_data(periodic_data, "Overdue", period) - else: + elif today() < getdate(d.planned_end_date): periodic_data = update_periodic_data(periodic_data, "Pending", period) if ( @@ -89,7 +89,7 @@ def get_data(filters, columns): periodic_data = get_periodic_data(filters, entry) - labels = ["Not Started", "Overdue", "Pending", "Completed"] + labels = ["Not Started", "Overdue", "Pending", "Completed", "Closed", "Stopped"] chart_data = get_chart_data(periodic_data, columns) ranges = get_period_date_ranges(filters) @@ -110,7 +110,7 @@ def get_data(filters, columns): def get_chart_data(periodic_data, columns): labels = [d.get("label") for d in columns[1:]] - not_start, overdue, pending, completed = [], [], [], [] + not_start, overdue, pending, completed, closed, stopped = [], [], [], [], [], [] datasets = [] for d in labels: @@ -118,11 +118,15 @@ def get_chart_data(periodic_data, columns): overdue.append(periodic_data.get("Overdue").get(d)) pending.append(periodic_data.get("Pending").get(d)) completed.append(periodic_data.get("Completed").get(d)) + closed.append(periodic_data.get("Closed").get(d)) + stopped.append(periodic_data.get("Stopped").get(d)) datasets.append({"name": _("Not Started"), "values": not_start}) datasets.append({"name": _("Overdue"), "values": overdue}) datasets.append({"name": _("Pending"), "values": pending}) datasets.append({"name": _("Completed"), "values": completed}) + datasets.append({"name": _("Closed"), "values": closed}) + datasets.append({"name": _("Stopped"), "values": stopped}) chart = {"data": {"labels": labels, "datasets": datasets}} chart["type"] = "line" From 5e06e4accefcff184e3e5b2477cc04c1b60eace2 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Mar 2025 16:32:26 +0530 Subject: [PATCH 92/97] fix: patch --- .../patches/v15_0/recalculate_amount_difference_field.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v15_0/recalculate_amount_difference_field.py b/erpnext/patches/v15_0/recalculate_amount_difference_field.py index 5ece5f08ddb..fa45211be94 100644 --- a/erpnext/patches/v15_0/recalculate_amount_difference_field.py +++ b/erpnext/patches/v15_0/recalculate_amount_difference_field.py @@ -25,8 +25,11 @@ def execute(): ) .where((table.amount_difference_with_purchase_invoice != 0) & (table.docstatus == 1)) ) - if fiscal_year_dates := get_fiscal_year(frappe.utils.datetime.date.today(), raise_on_missing=False): - query.where(parent.posting_date.between(fiscal_year_dates[1], fiscal_year_dates[2])) + try: + if fiscal_year_dates := get_fiscal_year(frappe.utils.datetime.date.today()): + query.where(parent.posting_date.between(fiscal_year_dates[1], fiscal_year_dates[2])) + except Exception: + return if result := query.run(as_dict=True): item_wise_billed_qty = get_billed_qty_against_purchase_receipt([item.name for item in result]) From 6df9cf327db7a66ceaceeff87f1edd82232f9c4d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 5 Mar 2025 16:19:47 +0530 Subject: [PATCH 93/97] fix: Accounting Period validation throwing for different companies (cherry picked from commit b1508efca24bbcbfd1c804a77fa611295679d800) --- erpnext/accounts/utils.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index db7a3a2a70f..2758ff0e26f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1417,7 +1417,7 @@ def repost_gle_for_stock_vouchers( if not warehouse_account: warehouse_account = get_warehouse_account_map(company) - stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers) + stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers, company=company) if repost_doc and repost_doc.gl_reposting_index: # Restore progress stock_vouchers = stock_vouchers[cint(repost_doc.gl_reposting_index) :] @@ -1470,7 +1470,9 @@ def _delete_accounting_ledger_entries(voucher_type, voucher_no): _delete_pl_entries(voucher_type, voucher_no) -def sort_stock_vouchers_by_posting_date(stock_vouchers: list[tuple[str, str]]) -> list[tuple[str, str]]: +def sort_stock_vouchers_by_posting_date( + stock_vouchers: list[tuple[str, str]], company=None +) -> list[tuple[str, str]]: sle = frappe.qb.DocType("Stock Ledger Entry") voucher_nos = [v[1] for v in stock_vouchers] @@ -1481,7 +1483,12 @@ def sort_stock_vouchers_by_posting_date(stock_vouchers: list[tuple[str, str]]) - .groupby(sle.voucher_type, sle.voucher_no) .orderby(sle.posting_datetime) .orderby(sle.creation) - ).run(as_dict=True) + ) + + if company: + sles = sles.where(sle.company == company) + + sles = sles.run(as_dict=True) sorted_vouchers = [(sle.voucher_type, sle.voucher_no) for sle in sles] unknown_vouchers = set(stock_vouchers) - set(sorted_vouchers) From 1e85f69072568fc92ed35a2a6b54154b5ab1ad56 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 18:00:31 +0530 Subject: [PATCH 94/97] fix: replacing serial and batch bundle on pos with auto fetch serial nos (backport #46236) (#46337) fix: replacing serial and batch bundle on pos with auto fetch serial nos (#46236) * fix: replacing serial and batch bundle on pos with auto fetch serial nos * fix: reserved serial no added a check to look for serial no in reserved serial nos list before removing it as there might be a situation where an item is returned which was already consolidated. (cherry picked from commit 35512d40bb4b3ae2f178b409c4c7f6edfd88dc60) Co-authored-by: Diptanil Saha --- .../page/point_of_sale/pos_item_details.js | 67 ++++++++++++------- erpnext/stock/doctype/serial_no/serial_no.py | 19 +++--- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index e0abdd4f4c3..a0476ee6bda 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -216,22 +216,16 @@ erpnext.PointOfSale.ItemDetails = class { } make_auto_serial_selection_btn(item) { - if (item.has_serial_no || item.has_batch_no) { - if (item.has_serial_no && item.has_batch_no) { - this.$form_container.append( - `
${__( - "Select Serial No / Batch No" - )}
` - ); - } else { - const classname = item.has_serial_no ? ".serial_no-control" : ".batch_no-control"; - const label = item.has_serial_no ? __("Select Serial No") : __("Select Batch No"); - this.$form_container - .find(classname) - .append( - `
${label}
` - ); + const doc = this.events.get_frm().doc; + if (!doc.is_return && (item.has_serial_no || item.serial_no)) { + if (!item.has_batch_no) { + this.$form_container.append(`
`); } + const label = __("Auto Fetch Serial Numbers"); + this.$form_container.append( + `
${label}
` + ); + this.$form_container.find(".serial_no-control").find("textarea").css("height", "6rem"); } } @@ -416,18 +410,41 @@ erpnext.PointOfSale.ItemDetails = class { bind_auto_serial_fetch_event() { this.$form_container.on("click", ".auto-fetch-btn", () => { - let frm = this.events.get_frm(); - let item_row = this.item_row; - item_row.type_of_transaction = "Outward"; + this.batch_no_control && this.batch_no_control.set_value(""); + let qty = this.qty_control.get_value(); + let conversion_factor = this.conversion_factor_control.get_value(); + let expiry_date = this.item_row.has_batch_no ? this.events.get_frm().doc.posting_date : ""; - new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { - if (r) { - frappe.model.set_value(item_row.doctype, item_row.name, { - serial_and_batch_bundle: r.name, - qty: Math.abs(r.total_qty), - use_serial_batch_fields: 0, - }); + let numbers = frappe.call({ + method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", + args: { + qty: qty * conversion_factor, + item_code: this.current_item.item_code, + warehouse: this.warehouse_control.get_value() || "", + batch_nos: this.current_item.batch_no || "", + posting_date: expiry_date, + for_doctype: "POS Invoice", + }, + }); + + numbers.then((data) => { + let auto_fetched_serial_numbers = data.message; + let records_length = auto_fetched_serial_numbers.length; + if (!records_length) { + const warehouse = this.warehouse_control.get_value().bold(); + const item_code = this.current_item.item_code.bold(); + frappe.msgprint( + __( + "Serial numbers unavailable for Item {0} under warehouse {1}. Please try changing warehouse.", + [item_code, warehouse] + ) + ); + } else if (records_length < qty) { + frappe.msgprint(__("Fetched only {0} available serial numbers.", [records_length])); + this.qty_control.set_value(records_length); } + numbers = auto_fetched_serial_numbers.join(`\n`); + this.serial_no_control.set_value(numbers); }); }); } diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 274ce0f0d9d..1560db6a114 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -230,15 +230,17 @@ def get_pos_reserved_serial_nos(filters): pos_transacted_sr_nos = query.run(as_dict=True) - reserved_sr_nos = set() - returned_sr_nos = set() + reserved_sr_nos = list() + returned_sr_nos = list() for d in pos_transacted_sr_nos: if d.is_return == 0: - [reserved_sr_nos.add(x) for x in get_serial_nos(d.serial_no)] + [reserved_sr_nos.append(x) for x in get_serial_nos(d.serial_no)] elif d.is_return == 1: - [returned_sr_nos.add(x) for x in get_serial_nos(d.serial_no)] + [returned_sr_nos.append(x) for x in get_serial_nos(d.serial_no)] - reserved_sr_nos = list(reserved_sr_nos - returned_sr_nos) + for x in returned_sr_nos: + if x in reserved_sr_nos: + reserved_sr_nos.remove(x) return reserved_sr_nos @@ -254,12 +256,7 @@ def fetch_serial_numbers(filters, qty, do_not_include=None): query = ( frappe.qb.from_(serial_no) .select(serial_no.name) - .where( - (serial_no.item_code == filters["item_code"]) - & (serial_no.warehouse == filters["warehouse"]) - & (Coalesce(serial_no.sales_invoice, "") == "") - & (Coalesce(serial_no.delivery_document_no, "") == "") - ) + .where((serial_no.item_code == filters["item_code"]) & (serial_no.warehouse == filters["warehouse"])) .orderby(serial_no.creation) .limit(qty or 1) ) From 6c1ceff8eeb1df3224f83210eb889b5066ed57e9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 18:07:14 +0530 Subject: [PATCH 95/97] fix: exclude already consumed purchase receipt items from asset capitalization (backport #46329) (#46336) * fix: exclude already consumed purchase receipt items from asset capitalization (#46329) * feat: link purchase receipt row item to capitalization * fix: avoid fetching already consumed stock and asset items during capitalization * fix(patch): added patch to link purchase receipt item to stock item child table * fix: added nosemgrep * refactor: rename to (cherry picked from commit f50d479bfd77bc27c3c309894474ff0d84160e8e) # Conflicts: # erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json # erpnext/patches.txt * fix: resolved conflicts * fix: resolved conflicts --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- .../asset_capitalization.js | 11 +++- .../asset_capitalization.py | 66 +++++++++++++++---- .../asset_capitalization_stock_item.json | 19 ++++-- .../asset_capitalization_stock_item.py | 1 + erpnext/patches.txt | 1 + ...t_row_item_to_capitalization_stock_item.py | 21 ++++++ 6 files changed, 100 insertions(+), 19 deletions(-) create mode 100644 erpnext/patches/v15_0/set_purchase_receipt_row_item_to_capitalization_stock_item.py diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index 28a8b81f3ad..848a31c4dfa 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -143,14 +143,19 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s } } - set_consumed_stock_items_tagged_to_wip_composite_asset(asset) { + set_consumed_stock_items_tagged_to_wip_composite_asset(target_asset) { var me = this; - if (asset) { + if (target_asset) { return me.frm.call({ method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_items_tagged_to_wip_composite_asset", args: { - asset: asset, + params: { + target_asset: target_asset, + finance_book: me.frm.doc.finance_book, + posting_date: me.frm.doc.posting_date, + posting_time: me.frm.doc.posting_time, + }, }, callback: function (r) { if (!r.exc && r.message) { diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index bed6cda43ed..f4addb66eb9 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -856,7 +856,10 @@ def get_service_item_details(args): @frappe.whitelist() -def get_items_tagged_to_wip_composite_asset(asset): +def get_items_tagged_to_wip_composite_asset(params): + if isinstance(params, str): + params = json.loads(params) + fields = [ "item_code", "item_name", @@ -871,25 +874,66 @@ def get_items_tagged_to_wip_composite_asset(asset): "amount", "is_fixed_asset", "parent", + "name", ] pr_items = frappe.get_all( - "Purchase Receipt Item", filters={"wip_composite_asset": asset, "docstatus": 1}, fields=fields + "Purchase Receipt Item", + filters={"wip_composite_asset": params.get("target_asset"), "docstatus": 1}, + fields=fields, ) stock_items = [] asset_items = [] + for d in pr_items: if not d.is_fixed_asset: - stock_items.append(frappe._dict(d)) + stock_item = process_stock_item(d) + if stock_item: + stock_items.append(stock_item) else: - asset_details = frappe.db.get_value( - "Asset", - {"item_code": d.item_code, "purchase_receipt": d.parent}, - ["name as asset", "asset_name"], - as_dict=1, - ) - d.update(asset_details) - asset_items.append(frappe._dict(d)) + asset_item = process_fixed_asset(d) + if asset_item: + asset_items.append(asset_item) return stock_items, asset_items + + +def process_stock_item(d): + stock_capitalized = frappe.db.exists( + "Asset Capitalization Stock Item", + { + "purchase_receipt_item": d.name, + "parentfield": "stock_items", + "parenttype": "Asset Capitalization", + "docstatus": 1, + }, + ) + + if stock_capitalized: + return None + + stock_item_data = frappe._dict(d) + stock_item_data.purchase_receipt_item = d.name + return stock_item_data + + +def process_fixed_asset(d): + asset_details = frappe.db.get_value( + "Asset", + { + "item_code": d.item_code, + "purchase_receipt": d.parent, + "status": ("not in", ["Draft", "Scrapped", "Sold", "Capitalized"]), + }, + ["name as asset", "asset_name", "company"], + as_dict=1, + ) + + if asset_details: + asset_details.update(d) + asset_details.update(get_consumed_asset_details(asset_details)) + d.update(asset_details) + + return frappe._dict(d) + return None diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json index da05e930eab..c96681411f7 100644 --- a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json +++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json @@ -10,12 +10,13 @@ "column_break_3", "warehouse", "section_break_6", + "purchase_receipt_item", "stock_qty", - "stock_uom", "actual_qty", "column_break_9", "valuation_rate", "amount", + "stock_uom", "batch_and_serial_no_section", "serial_and_batch_bundle", "use_serial_batch_fields", @@ -53,14 +54,14 @@ { "fieldname": "section_break_6", "fieldtype": "Section Break", - "label": "Qty and Rate" + "label": "Purchase Details" }, { "columns": 1, "fieldname": "stock_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Qty", + "label": "Quantity", "non_negative": 1 }, { @@ -172,18 +173,26 @@ { "fieldname": "column_break_mbuv", "fieldtype": "Column Break" + }, + { + "fieldname": "purchase_receipt_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Purchase Receipt Item" } ], + "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-06-26 17:06:22.564438", + "modified": "2025-03-05 12:46:01.074742", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization Stock Item", "owner": "Administrator", "permissions": [], - "sort_field": "modified", + "row_format": "Dynamic", + "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py index 0f06cc7442e..c56f87b67fa 100644 --- a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py +++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py @@ -23,6 +23,7 @@ class AssetCapitalizationStockItem(Document): parent: DF.Data parentfield: DF.Data parenttype: DF.Data + purchase_receipt_item: DF.Data | None serial_and_batch_bundle: DF.Link | None serial_no: DF.Text | None stock_qty: DF.Float diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8509989a2ca..505eb81e4e9 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -400,3 +400,4 @@ erpnext.patches.v15_0.rename_sla_fields erpnext.patches.v15_0.update_query_report erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference erpnext.patches.v15_0.recalculate_amount_difference_field +erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item diff --git a/erpnext/patches/v15_0/set_purchase_receipt_row_item_to_capitalization_stock_item.py b/erpnext/patches/v15_0/set_purchase_receipt_row_item_to_capitalization_stock_item.py new file mode 100644 index 00000000000..f1d17d1da4d --- /dev/null +++ b/erpnext/patches/v15_0/set_purchase_receipt_row_item_to_capitalization_stock_item.py @@ -0,0 +1,21 @@ +import frappe + + +def execute(): + # nosemgrep + frappe.db.sql( + """ + UPDATE `tabAsset Capitalization Stock Item` ACSI + JOIN `tabAsset Capitalization` AC + ON ACSI.parent = AC.name + JOIN `tabPurchase Receipt Item` PRI + ON + PRI.item_code = ACSI.item_code + AND PRI.wip_composite_asset = AC.target_asset + SET + ACSI.purchase_receipt_item = PRI.name + WHERE + ACSI.purchase_receipt_item IS NULL + AND AC.docstatus = 1 + """ + ) From 1a382ebe86633cfbc5e77ae0689b0e73392aa4aa Mon Sep 17 00:00:00 2001 From: Ejaaz Khan <67804911+iamejaaz@users.noreply.github.com> Date: Wed, 5 Mar 2025 18:06:59 +0530 Subject: [PATCH 96/97] feat: create sales invoice print format (#45403) * feat: create sales invoice print format * fix: linter issue * style: remove border from table * refactor: change label to uppercase and show taxes * refactor: format date and add translation on label * refactor: remove default header and format labels * refactor: change label style and small fix * chore: Qty in title case --------- Co-authored-by: Nabin Hait (cherry picked from commit 38aa7cab8ae2c94cf95ff5ae2275acd895d9536c) --- .../doctype/sales_invoice/sales_invoice.json | 5 +- .../sales_invoice_print/__init__.py | 0 .../sales_invoice_print.html | 161 ++++++++++++++++++ .../sales_invoice_print.json | 32 ++++ 4 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 erpnext/accounts/print_format/sales_invoice_print/__init__.py create mode 100644 erpnext/accounts/print_format/sales_invoice_print/sales_invoice_print.html create mode 100644 erpnext/accounts/print_format/sales_invoice_print/sales_invoice_print.json diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 4183cc21ab8..e2c5fd965e8 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -3,6 +3,7 @@ "allow_import": 1, "autoname": "naming_series:", "creation": "2022-01-25 10:29:57.771398", + "default_print_format": "Sales Invoice Print", "doctype": "DocType", "engine": "InnoDB", "field_order": [ @@ -2177,6 +2178,7 @@ "print_hide": 1 } ], + "grid_page_length": 50, "icon": "fa fa-file-text", "idx": 181, "is_submittable": 1, @@ -2187,7 +2189,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2025-02-06 15:59:54.636202", + "modified": "2025-03-05 17:06:59.720616", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", @@ -2233,6 +2235,7 @@ } ], "quick_entry": 1, + "row_format": "Dynamic", "search_fields": "posting_date, due_date, customer, base_grand_total, outstanding_amount", "show_name_in_global_search": 1, "sort_field": "modified", diff --git a/erpnext/accounts/print_format/sales_invoice_print/__init__.py b/erpnext/accounts/print_format/sales_invoice_print/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/print_format/sales_invoice_print/sales_invoice_print.html b/erpnext/accounts/print_format/sales_invoice_print/sales_invoice_print.html new file mode 100644 index 00000000000..958c5c10fc1 --- /dev/null +++ b/erpnext/accounts/print_format/sales_invoice_print/sales_invoice_print.html @@ -0,0 +1,161 @@ +{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None, print_heading_template=None) -%} + {% if letter_head and not no_letterhead %} +
{{ letter_head }}
+ {% endif %} + {% if print_heading_template %} + {{ frappe.render_template(print_heading_template, {"doc":doc}) }} + {% else %} + {% endif %} + {%- if doc.meta.is_submittable and doc.docstatus==2-%} +
+

{{ _("CANCELLED") }}

+
+ {%- endif -%} +{%- endmacro -%} +{% for page in layout %} +
+
+ {{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }} +
+ + + {% if print_settings.repeat_header_footer %} + + {% endif %} + +
+
+
{{ doc.customer }}
+
+ {{ doc.address_display }} +
+
+ {{ _("Conatct: ")+doc.contact_display if doc.contact_display else '' }} +
+
+ {{ _("Mobile: ")+doc.contact_mobile if doc.contact_mobile else '' }} +
+
+
+
+
+
+
{{ doc.name }}
+
+
+
+
{{ frappe.utils.format_date(doc.posting_date) }}
+
+
+
+
{{ frappe.utils.format_date(doc.due_date) }}
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + {% for item in doc.items %} + + + + + + + + {% endfor %} +
{{ _("Sr") }}{{ _("Details") }}{{ _("Qty") }}{{ _("Rate") }}{{ _("Amount") }}
{{ loop.index }} + {{ item.item_code }}: {{ item.item_name }} + {% if (item.description != item.item_name) %} +
{{ item.description }} + {% endif %} +
+ {{ item.get_formatted("qty", 0) }} + {{ item.get_formatted("uom", 0) }} + {{ item.get_formatted("net_rate", doc) }}{{ item.get_formatted("net_amount", doc) }}
+ +
+ +
+
+ + {{ doc.in_words }} +
+
+ + {{ doc.status }} +
+
+
+
+
{{ _("Sub Total") }}
+
{{ doc.get_formatted("net_total", doc) }}
+
+
+ {% for d in doc.taxes %} + {% if d.tax_amount %} +
+
{{ _(d.description) }}
+
{{ d.get_formatted("tax_amount") }}
+
+ {% endif %} + {% endfor %} +
+
+
{{ _("Total") }}
+
{{ doc.get_formatted("grand_total", doc) }}
+
+
+ +
+
+ + +
+
+
+
+
{{ doc.terms if doc.terms else '' }}
+
+
+
+
+{% endfor %} diff --git a/erpnext/accounts/print_format/sales_invoice_print/sales_invoice_print.json b/erpnext/accounts/print_format/sales_invoice_print/sales_invoice_print.json new file mode 100644 index 00000000000..d4acf5fb36e --- /dev/null +++ b/erpnext/accounts/print_format/sales_invoice_print/sales_invoice_print.json @@ -0,0 +1,32 @@ +{ + "absolute_value": 0, + "align_labels_right": 0, + "creation": "2025-01-22 16:23:51.012200", + "css": "", + "custom_format": 0, + "default_print_language": "en", + "disabled": 0, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "", + "font_size": 14, + "idx": 0, + "line_breaks": 0, + "margin_bottom": 0.0, + "margin_left": 0.0, + "margin_right": 0.0, + "margin_top": 0.0, + "modified": "2025-01-22 16:23:51.012200", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Invoice Print", + "owner": "Administrator", + "page_number": "Hide", + "print_format_builder": 0, + "print_format_builder_beta": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file From 29f3aac925ea455c599cfb5e488c3eedc87c446f Mon Sep 17 00:00:00 2001 From: Sugesh G <73237300+Sugesh393@users.noreply.github.com> Date: Wed, 5 Mar 2025 18:06:42 +0530 Subject: [PATCH 97/97] fix(pos): get parent item group without user permission (#46020) * fix(pos): get parent item group without user permission * feat: add item group filter based on user permission --------- Co-authored-by: venkat102 (cherry picked from commit 8caf7f275eebe650686e1ed012ed2da15650221c) --- .../doctype/pos_profile/pos_profile.py | 27 ++++++++++++++++++- .../page/point_of_sale/point_of_sale.py | 8 ++++++ .../page/point_of_sale/pos_item_selector.js | 9 +++++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index a8c4a4d8d72..2bcfb66ce73 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -4,6 +4,7 @@ import frappe from frappe import _, msgprint, scrub, unscrub +from frappe.core.doctype.user_permission.user_permission import get_permitted_documents from frappe.model.document import Document from frappe.utils import get_link_to_form, now @@ -204,17 +205,41 @@ class POSProfile(Document): def get_item_groups(pos_profile): item_groups = [] pos_profile = frappe.get_cached_doc("POS Profile", pos_profile) + permitted_item_groups = get_permitted_nodes("Item Group") if pos_profile.get("item_groups"): # Get items based on the item groups defined in the POS profile for data in pos_profile.get("item_groups"): item_groups.extend( - ["%s" % frappe.db.escape(d.name) for d in get_child_nodes("Item Group", data.item_group)] + [ + "%s" % frappe.db.escape(d.name) + for d in get_child_nodes("Item Group", data.item_group) + if not permitted_item_groups or d.name in permitted_item_groups + ] ) + if not item_groups and permitted_item_groups: + item_groups = ["%s" % frappe.db.escape(d) for d in permitted_item_groups] + return list(set(item_groups)) +def get_permitted_nodes(group_type): + nodes = [] + permitted_nodes = get_permitted_documents(group_type) + + if not permitted_nodes: + return nodes + + for node in permitted_nodes: + if frappe.db.get_value(group_type, node, "is_group"): + nodes.extend([d.name for d in get_child_nodes(group_type, node)]) + else: + nodes.append(node) + + return nodes + + def get_child_nodes(group_type, root): lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"]) return frappe.db.sql( diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 7f758f4c8db..9be5a656a8e 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -115,6 +115,14 @@ def filter_result_items(result, pos_profile): result["items"] = [item for item in result.get("items") if item.get("item_group") in pos_item_groups] +@frappe.whitelist() +def get_parent_item_group(): + # Using get_all to ignore user permission + item_group = frappe.get_all("Item Group", {"lft": 1, "is_group": 1}, pluck="name") + if item_group: + return item_group[0] + + @frappe.whitelist() def get_items(start, page_length, price_list, item_group, pos_profile, search_term=""): warehouse, hide_unavailable_items = frappe.db.get_value( diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 909d1bb9c2d..6ae0d675140 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -38,8 +38,13 @@ erpnext.PointOfSale.ItemSelector = class { async load_items_data() { if (!this.item_group) { - const res = await frappe.db.get_value("Item Group", { lft: 1, is_group: 1 }, "name"); - this.parent_item_group = res.message.name; + frappe.call({ + method: "erpnext.selling.page.point_of_sale.point_of_sale.get_parent_item_group", + async: false, + callback: (r) => { + if (r.message) this.parent_item_group = r.message; + }, + }); } if (!this.price_list) { const res = await frappe.db.get_value("POS Profile", this.pos_profile, "selling_price_list");