From 725f9ea0126be5faff11acd22bd3f2c8b365c2db Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Sat, 5 Jul 2025 12:48:50 +0530 Subject: [PATCH 01/28] fix: resolve bundle item into line item if againt default supplier checked (cherry picked from commit ec07549d5ec8168d8374fa66b092c507b7a609b5) --- .../doctype/sales_order/sales_order.py | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 2f2f745bedb..cb4378e2af9 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1352,10 +1352,17 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t target.stock_qty = flt(source.stock_qty) - flt(source.ordered_qty) target.project = source_parent.project + def update_item_for_packed_item(source, target, source_parent): + target.qty = flt(source.qty) - flt(source.ordered_qty) + suppliers = [item.get("supplier") for item in selected_items if item.get("supplier")] suppliers = list(dict.fromkeys(suppliers)) # remove duplicates while preserving order - items_to_map = [item.get("item_code") for item in selected_items if item.get("item_code")] + items_to_map = [ + item.get("item_code") + for item in selected_items + if item.get("item_code") and item.get("delivered_by_supplier") + ] items_to_map = list(set(items_to_map)) if not suppliers: @@ -1405,13 +1412,35 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t "condition": lambda doc: doc.ordered_qty < doc.stock_qty and doc.supplier == supplier and doc.item_code in items_to_map - and doc.delivered_by_supplier == 1, + and not is_product_bundle(doc.item_code), + }, + "Packed Item": { + "doctype": "Purchase Order Item", + "field_map": [ + ["name", "sales_order_packed_item"], + ["parent", "sales_order"], + ["uom", "uom"], + ["conversion_factor", "conversion_factor"], + ["parent_item", "product_bundle"], + ["rate", "rate"], + ], + "field_no_map": [ + "price_list_rate", + "item_tax_template", + "discount_percentage", + "discount_amount", + "supplier", + "pricing_rules", + ], + "postprocess": update_item_for_packed_item, + "condition": lambda doc: doc.parent_item in items_to_map, }, }, target_doc, set_missing_values, ) + set_delivery_date(doc.items, source_name) doc.insert() frappe.db.commit() purchase_orders.append(doc) @@ -1427,9 +1456,7 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): if isinstance(selected_items, str): selected_items = json.loads(selected_items) - items_to_map = [ - item.get("item_code") for item in selected_items if item.get("item_code") and item.get("item_code") - ] + items_to_map = [item.get("item_code") for item in selected_items if item.get("item_code")] items_to_map = list(set(items_to_map)) def is_drop_ship_order(target): From 6fddf4c5aafecd2f6155c61a6327b4a7387ebfe2 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Wed, 9 Jul 2025 19:26:41 +0530 Subject: [PATCH 02/28] fix: carry forward the delivered_by_supplier check to PO (cherry picked from commit f3460ec840a6ee7a7e0b383cd91dcce96d8063b2) # Conflicts: # erpnext/stock/doctype/packed_item/packed_item.json --- .../selling/doctype/sales_order/sales_order.py | 6 +----- .../stock/doctype/packed_item/packed_item.json | 17 +++++++++++++++++ .../stock/doctype/packed_item/packed_item.py | 2 ++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index cb4378e2af9..81b35c6f063 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1358,11 +1358,7 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t suppliers = [item.get("supplier") for item in selected_items if item.get("supplier")] suppliers = list(dict.fromkeys(suppliers)) # remove duplicates while preserving order - items_to_map = [ - item.get("item_code") - for item in selected_items - if item.get("item_code") and item.get("delivered_by_supplier") - ] + items_to_map = [item.get("item_code") for item in selected_items if item.get("item_code")] items_to_map = list(set(items_to_map)) if not suppliers: diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json index cb415e3813a..e0b5e2b4aaa 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.json +++ b/erpnext/stock/doctype/packed_item/packed_item.json @@ -23,6 +23,7 @@ "use_serial_batch_fields", "column_break_11", "serial_and_batch_bundle", + "delivered_by_supplier", "section_break_bgys", "serial_no", "column_break_qlha", @@ -290,19 +291,35 @@ { "fieldname": "column_break_qlha", "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "delivered_by_supplier", + "fieldtype": "Check", + "label": "Supplier delivers to Customer", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2025-02-18 13:07:02.789654", +======= + "modified": "2025-07-09 19:12:45.850219", +>>>>>>> f3460ec840 (fix: carry forward the delivered_by_supplier check to PO) "modified_by": "Administrator", "module": "Stock", "name": "Packed Item", "owner": "Administrator", "permissions": [], +<<<<<<< HEAD "sort_field": "modified", +======= + "row_format": "Dynamic", + "sort_field": "creation", +>>>>>>> f3460ec840 (fix: carry forward the delivered_by_supplier check to PO) "sort_order": "DESC", "states": [], "track_changes": 1 diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index ceb2fdb0087..5a4f3e7722d 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -27,6 +27,7 @@ class PackedItem(Document): actual_qty: DF.Float batch_no: DF.Link | None conversion_factor: DF.Float + delivered_by_supplier: DF.Check description: DF.TextEditor | None incoming_rate: DF.Currency item_code: DF.Link | None @@ -209,6 +210,7 @@ def update_packed_item_basic_data(main_item_row, pi_row, packing_item, item_data pi_row.uom = item_data.stock_uom pi_row.qty = flt(packing_item.qty) * flt(main_item_row.stock_qty) pi_row.conversion_factor = main_item_row.conversion_factor + pi_row.delivered_by_supplier = main_item_row.get("delivered_by_supplier") if not pi_row.description: pi_row.description = packing_item.get("description") From d04c256b73f46ccf2a489881e2aabe0acedf7a57 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Thu, 3 Jul 2025 15:37:44 +0530 Subject: [PATCH 03/28] fix: fetch sales invoice based on mode_of_payment in item-wise sales register (cherry picked from commit 39cd7a29df2552c7b3f36d5689a2e8da7167f172) --- .../item_wise_sales_register.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 73d8c3f7d14..a373b1f0a1e 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -343,7 +343,7 @@ def get_columns(additional_table_columns, filters): return columns -def apply_conditions(query, si, sii, filters, additional_conditions=None): +def apply_conditions(query, si, sii, sip, filters, additional_conditions=None): for opts in ("company", "customer"): if filters.get(opts): query = query.where(si[opts] == filters[opts]) @@ -355,10 +355,7 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): query = query.where(si.posting_date <= filters.get("to_date")) if filters.get("mode_of_payment"): - sales_invoice = frappe.db.get_all( - "Sales Invoice Payment", {"mode_of_payment": filters.get("mode_of_payment")}, pluck="parent" - ) - query = query.where(si.name.isin(sales_invoice)) + query = query.where(sip.mode_of_payment == filters.get("mode_of_payment")) if filters.get("warehouse"): if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"): @@ -416,6 +413,7 @@ def apply_order_by_conditions(query, si, ii, filters): def get_items(filters, additional_query_columns, additional_conditions=None): doctype = "Sales Invoice" si = frappe.qb.DocType(doctype) + sip = frappe.qb.DocType(f"{doctype} Payment") sii = frappe.qb.DocType(f"{doctype} Item") item = frappe.qb.DocType("Item") @@ -423,6 +421,8 @@ def get_items(filters, additional_query_columns, additional_conditions=None): frappe.qb.from_(si) .join(sii) .on(si.name == sii.parent) + .left_join(sip) + .on(sip.parent == si.name) .left_join(item) .on(sii.item_code == item.name) .select( @@ -462,6 +462,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None): si.update_stock, sii.uom, sii.qty, + sip.mode_of_payment, ) .where(si.docstatus == 1) .where(sii.parenttype == doctype) @@ -481,7 +482,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None): if filters.get("customer_group"): query = query.where(si.customer_group == filters["customer_group"]) - query = apply_conditions(query, si, sii, filters, additional_conditions) + query = apply_conditions(query, si, sii, sip, filters, additional_conditions) from frappe.desk.reportview import build_match_conditions From 467fe1d72f77c85e5ef65c7d81ba8a397ccfd4e9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 15 Jul 2025 17:27:00 +0530 Subject: [PATCH 04/28] fix: added serial no condition (cherry picked from commit bb7ddd11f104a27086e83915acac6ddd48acee9c) --- erpnext/controllers/sales_and_purchase_return.py | 9 +++++---- erpnext/controllers/selling_controller.py | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 011f21fe388..599221185d9 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -4,7 +4,7 @@ from collections import defaultdict import frappe -from frappe import _ +from frappe import _, bold from frappe.model.meta import get_field_precision from frappe.utils import cint, flt, format_datetime, get_datetime @@ -40,11 +40,12 @@ def validate_return_against(doc): frappe.throw( _("The {0} {1} does not match with the {0} {2} in the {3} {4}").format( doc.meta.get_label(party_type), - doc.get(party_type), - ref_doc.get(party_type), + bold(doc.get(party_type)), + bold(ref_doc.get(party_type)), ref_doc.doctype, ref_doc.name, - ) + ), + title=_("Party Mismatch"), ) if ( diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 5f7cfb165d4..d85c1b28f97 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -68,10 +68,13 @@ class SellingController(StockController): serial_nos = frappe.get_all( "Serial and Batch Entry", - filters={"parent": ("in", bundle_ids)}, + filters={"parent": ("in", bundle_ids), "serial_no": ("is", "set")}, pluck="serial_no", ) + if not serial_nos: + return + if serial_nos := frappe.get_all( "Serial No", filters={"name": ("in", serial_nos), "customer": ("is", "set")}, From 873e0a4219ad7c96e0394eb0305a3ba928f478f2 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 15 Jul 2025 17:17:20 +0530 Subject: [PATCH 05/28] chore: fix conflicts --- erpnext/stock/doctype/packed_item/packed_item.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json index e0b5e2b4aaa..31726dff277 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.json +++ b/erpnext/stock/doctype/packed_item/packed_item.json @@ -304,22 +304,13 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2025-02-18 13:07:02.789654", -======= "modified": "2025-07-09 19:12:45.850219", ->>>>>>> f3460ec840 (fix: carry forward the delivered_by_supplier check to PO) "modified_by": "Administrator", "module": "Stock", "name": "Packed Item", "owner": "Administrator", "permissions": [], -<<<<<<< HEAD "sort_field": "modified", -======= - "row_format": "Dynamic", - "sort_field": "creation", ->>>>>>> f3460ec840 (fix: carry forward the delivered_by_supplier check to PO) "sort_order": "DESC", "states": [], "track_changes": 1 From 6b96a26462059d0d803490fcb9f06f1b7e159e04 Mon Sep 17 00:00:00 2001 From: Diptanil Saha Date: Tue, 15 Jul 2025 18:51:51 +0530 Subject: [PATCH 06/28] Merge pull request #48614 from diptanilsaha/backport_48411 fix: employee search based on the fields mentioned in the employee doctype search fields (backport #48411) --- erpnext/controllers/queries.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 8390ecada10..b4610337ceb 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -42,14 +42,14 @@ def employee_query( ptype="select" if frappe.only_has_select_perm(doctype) else "read", ) + search_conditions = " or ".join([f"{field} like %(txt)s" for field in fields]) mcond = "" if ignore_permissions else get_match_cond(doctype) return frappe.db.sql( """select {fields} from `tabEmployee` where status in ('Active', 'Suspended') and docstatus < 2 - and ({key} like %(txt)s - or employee_name like %(txt)s) + and ({key} like %(txt)s or {search_conditions}) {fcond} {mcond} order by (case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end), @@ -62,6 +62,7 @@ def employee_query( "key": searchfield, "fcond": get_filters_cond(doctype, filters, conditions), "mcond": mcond, + "search_conditions": search_conditions, } ), {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len}, From a46cafe652cbb887c2ed4256a8fddb5f71af8378 Mon Sep 17 00:00:00 2001 From: Diptanil Saha Date: Wed, 16 Jul 2025 11:08:29 +0530 Subject: [PATCH 07/28] fix: pos customer selection on new order (#48623) --- erpnext/selling/page/point_of_sale/pos_controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 50bdc294987..6905292420b 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -502,6 +502,7 @@ erpnext.PointOfSale.Controller = class { () => frappe.dom.freeze(), () => this.make_new_invoice(), () => this.item_selector.toggle_component(true), + () => this.cart.enable_customer_selection(), () => frappe.dom.unfreeze(), ]); }, From 2b1fdba7fd4d06bb0e17784cdd763953956cc70d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:09:58 +0530 Subject: [PATCH 08/28] fix(transaction): recalculate tax and total when quantity changes (backport #48565) (#48625) fix(transaction): recalculate tax and total when quantity changes (cherry picked from commit ac7b6c6a3d3d92b06c1d3b6f1afb1ac26fbc96b6) Co-authored-by: Bhavan23 --- erpnext/public/js/controllers/transaction.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index c7260ccc722..03cb670c4df 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1407,6 +1407,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe ]); } else { this.conversion_factor(doc, cdt, cdn, true) + this.calculate_taxes_and_totals() } } From b9e6f524e571fe00cd72c2b804ca67c9534fc0eb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 16 Jul 2025 15:55:21 +0530 Subject: [PATCH 09/28] fix: performance issue while submitting the purchase invoice (cherry picked from commit 47979871def9642f7fa1de0b46e95ea406b49173) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 3050f3ac266..73f2764fd67 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1223,6 +1223,9 @@ class PurchaseInvoice(BuyingController): def get_provisional_accounts(self): self.provisional_accounts = frappe._dict() linked_purchase_receipts = set([d.purchase_receipt for d in self.items if d.purchase_receipt]) + if not linked_purchase_receipts: + return + pr_items = frappe.get_all( "Purchase Receipt Item", filters={"parent": ("in", linked_purchase_receipts)}, From 98bd880c73024a79b56c9956e56c278332ca6e24 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Tue, 15 Jul 2025 17:38:37 +0530 Subject: [PATCH 10/28] fix(period closing voucher): closing account head debit and debit in account currency should be equal (cherry picked from commit d6fd6132724b9317b64de9d6b96ce813ab6e40cb) --- .../period_closing_voucher.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 2d065632419..7f51f2add59 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -210,8 +210,10 @@ class PeriodClosingVoucher(AccountsController): return gl_entry def get_gle_for_closing_account(self, dimension_balance, dimensions): - balance_in_account_currency = flt(dimension_balance.balance_in_account_currency) balance_in_company_currency = flt(dimension_balance.balance_in_company_currency) + debit = balance_in_company_currency if balance_in_company_currency > 0 else 0 + credit = abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0 + gl_entry = frappe._dict( { "company": self.company, @@ -220,14 +222,10 @@ class PeriodClosingVoucher(AccountsController): "account_currency": frappe.db.get_value( "Account", self.closing_account_head, "account_currency" ), - "debit_in_account_currency": balance_in_account_currency - if balance_in_account_currency > 0 - else 0, - "debit": balance_in_company_currency if balance_in_company_currency > 0 else 0, - "credit_in_account_currency": abs(balance_in_account_currency) - if balance_in_account_currency < 0 - else 0, - "credit": abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0, + "debit_in_account_currency": debit, + "debit": debit, + "credit_in_account_currency": credit, + "credit": credit, "is_period_closing_voucher_entry": 1, "voucher_type": "Period Closing Voucher", "voucher_no": self.name, From 32915cf2b7141b1ce1653b15d63dc2795f0c4879 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 17 Jul 2025 11:20:05 +0530 Subject: [PATCH 11/28] fix: do not consider cancelled SLEs in report (cherry picked from commit 71578cb2ef8077c58d529da049d1d463f2148e2f) --- .../warehouse_wise_item_balance_age_and_value.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py index 68caff40356..0401ba0d954 100644 --- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py +++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py @@ -106,7 +106,10 @@ def get_columns(filters): def validate_filters(filters): if not (filters.get("item_code") or filters.get("warehouse")): - sle_count = flt(frappe.qb.from_("Stock Ledger Entry").select(Count("name")).run()[0][0]) + table = frappe.qb.DocType("Stock Ledger Entry") + sle_count = flt( + frappe.qb.from_(table).select(Count(table.name)).where(table.is_cancelled == 0).run()[0][0] + ) if sle_count > 500000: frappe.throw(_("Please set filter based on Item or Warehouse")) From 93c2a679309825ede65481a070a4e2b608adb9f7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 15 Jul 2025 23:06:54 +0530 Subject: [PATCH 12/28] fix: stand-alone credit note gl entries (cherry picked from commit f3d6a641560e5fac582fec07b513815ce0d01e34) --- .../purchase_invoice/purchase_invoice.py | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 73f2764fd67..9ec3555b49a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1310,8 +1310,37 @@ class PurchaseInvoice(BuyingController): net_amt_precision, ) - # Stock ledger value is not matching with the warehouse amount - if ( + if self.is_return and self.update_stock and (self.is_internal_supplier or not self.return_against): + net_rate = item.base_net_amount + if item.sales_incoming_rate: # for internal transfer + net_rate = item.qty * item.sales_incoming_rate + + stock_amount = net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount) + warehouse_debit_amount = flt( + voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision + ) + + if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision): + cost_of_goods_sold_account = self.get_company_default("default_expense_account") + stock_adjustment_amt = stock_amount - warehouse_debit_amount + + gl_entries.append( + self.get_gl_dict( + { + "account": cost_of_goods_sold_account, + "against": item.expense_account, + "debit": stock_adjustment_amt, + "debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate, + "remarks": self.get("remarks") or _("Stock Adjustment"), + "cost_center": item.cost_center, + "project": item.project or self.project, + }, + account_currency, + item=item, + ) + ) + + elif ( self.update_stock and voucher_wise_stock_value.get((item.name, item.warehouse)) and warehouse_debit_amount @@ -1339,33 +1368,6 @@ class PurchaseInvoice(BuyingController): warehouse_debit_amount = stock_amount - elif self.is_return and self.update_stock and (self.is_internal_supplier or not self.return_against): - net_rate = item.base_net_amount - if item.sales_incoming_rate: # for internal transfer - net_rate = item.qty * item.sales_incoming_rate - - stock_amount = net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount) - - if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision): - cost_of_goods_sold_account = self.get_company_default("default_expense_account") - stock_adjustment_amt = stock_amount - warehouse_debit_amount - - gl_entries.append( - self.get_gl_dict( - { - "account": cost_of_goods_sold_account, - "against": item.expense_account, - "debit": stock_adjustment_amt, - "debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate, - "remarks": self.get("remarks") or _("Stock Adjustment"), - "cost_center": item.cost_center, - "project": item.project or self.project, - }, - account_currency, - item=item, - ) - ) - return warehouse_debit_amount def make_tax_gl_entries(self, gl_entries): From 3e53660bba95c2ed812d6a77229488b1912431d5 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 17 Jul 2025 15:10:06 +0530 Subject: [PATCH 13/28] fix: precision issue for Sales Incoming Rate (cherry picked from commit 7b99275ceb4e6bccd5ef5b19dc4c4ff8c4a61ca2) --- erpnext/controllers/buying_controller.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 4e726333a7f..73d8a42c505 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -447,13 +447,12 @@ class BuyingController(SubcontractingController): raise_error_if_no_rate=False, ) - d.sales_incoming_rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate")) + d.sales_incoming_rate = flt(outgoing_rate * (d.conversion_factor or 1)) else: field = "incoming_rate" if self.get("is_internal_supplier") else "rate" d.sales_incoming_rate = flt( frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field) - * (d.conversion_factor or 1), - d.precision("rate"), + * (d.conversion_factor or 1) ) def validate_for_subcontracting(self): From 65efc7e9507ad6240ebf894eef563b5734321043 Mon Sep 17 00:00:00 2001 From: Diptanil Saha Date: Fri, 18 Jul 2025 11:09:32 +0530 Subject: [PATCH 14/28] fix: sales partner in pos invoice (#48670) * Merge pull request #48667 from diptanilsaha/fix_pos_sales_partner fix: sales partner on pos invoice * fix: remove incorrect report conditions and unset sales partner on consolidated sales invoice (#48669) * fix: undo query changes for sales partner related reports * fix: patch to remove sales partner from consolidated sales invoice --- .../doctype/pos_invoice/pos_invoice.json | 4 +++- .../pos_invoice_merge_log.py | 5 +++++ .../sales_partners_commission.json | 14 ++++++++------ erpnext/patches.txt | 1 + ...partner_from_consolidated_sales_invoice.py | 19 +++++++++++++++++++ .../sales_partner_commission_summary.js | 2 +- ...ner_target_variance_based_on_item_group.js | 2 +- .../sales_partner_transaction_summary.js | 2 +- 8 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 erpnext/patches/v15_0/remove_sales_partner_from_consolidated_sales_invoice.py diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index c15309df294..aaf5142362f 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -1443,6 +1443,8 @@ "width": "50%" }, { + "fetch_from": "sales_partner.commission_rate", + "fetch_if_empty": 1, "fieldname": "commission_rate", "fieldtype": "Float", "label": "Commission Rate (%)", @@ -1571,7 +1573,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2024-11-26 13:10:50.309570", + "modified": "2025-07-17 16:51:40.886083", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index fedc6a7772d..913ab7e6c47 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -337,6 +337,11 @@ class POSInvoiceMergeLog(Document): invoice.flags.ignore_pos_profile = True invoice.pos_profile = "" + # Unset Commission Section + invoice.set("sales_partner", None) + invoice.set("commission_rate", 0) + invoice.set("total_commission", 0) + return invoice def get_new_sales_invoice(self): diff --git a/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json b/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json index 9dd4e437f7f..9a1131e069b 100644 --- a/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json +++ b/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json @@ -1,21 +1,22 @@ { "add_total_row": 0, + "add_translate_data": 0, "columns": [], "creation": "2013-05-06 12:28:23", - "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", "filters": [], - "idx": 3, + "idx": 6, "is_standard": "Yes", - "modified": "2021-10-06 06:26:07.881340", + "letterhead": null, + "modified": "2025-07-17 23:16:19.892044", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Partners Commission", "owner": "Administrator", "prepared_report": 0, - "query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:220\",\n\tsum(base_net_total) as \"Invoiced Amount (Excl. Tax):Currency:220\",\n\tsum(amount_eligible_for_commission) as \"Amount Eligible for Commission:Currency:220\",\n\tsum(total_commission) as \"Total Commission:Currency:170\",\n\tsum(total_commission)*100/sum(amount_eligible_for_commission) as \"Average Commission Rate:Percent:220\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"", + "query": "SELECT\n sales_partner as \"Sales Partner:Link / Sales Partner:220\",\n sum(base_net_total) as \"Invoiced Amount (Excl. Tax):Currency:220\",\n sum(amount_eligible_for_commission) as \"Amount Eligible for Commission:Currency:220\",\n sum(total_commission) as \"Total Commission:Currency:170\",\n sum(total_commission)*100 / sum(amount_eligible_for_commission) as \"Average Commission Rate:Percent:220\"\nFROM\n (\n SELECT\n sales_partner,\n base_net_total,\n total_commission,\n amount_eligible_for_commission\n FROM\n `tabSales Invoice` \n WHERE\n docstatus = 1\n AND IFNULL(base_net_total, 0) > 0\n AND IFNULL(total_commission, 0) > 0\n\n UNION ALL\n\n SELECT\n sales_partner,\n base_net_total,\n total_commission,\n amount_eligible_for_commission\n FROM\n `tabPOS Invoice`\n WHERE\n docstatus = 1\n AND IFNULL(base_net_total, 0) > 0\n AND IFNULL(total_commission, 0) > 0\n ) AS sub\nGROUP BY\n sales_partner\nORDER BY\n \"Total Commission:Currency:120\"", "ref_doctype": "Sales Invoice", "report_name": "Sales Partners Commission", "report_type": "Query Report", @@ -26,5 +27,6 @@ { "role": "Accounts User" } - ] -} \ No newline at end of file + ], + "timeout": 0 +} diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 447e264ad75..5f4c3672228 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -413,3 +413,4 @@ erpnext.patches.v15_0.update_pick_list_fields erpnext.patches.v15_0.update_pegged_currencies erpnext.patches.v15_0.set_company_on_pos_inv_merge_log erpnext.patches.v15_0.rename_price_list_to_buying_price_list +erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice diff --git a/erpnext/patches/v15_0/remove_sales_partner_from_consolidated_sales_invoice.py b/erpnext/patches/v15_0/remove_sales_partner_from_consolidated_sales_invoice.py new file mode 100644 index 00000000000..ac1daeef44d --- /dev/null +++ b/erpnext/patches/v15_0/remove_sales_partner_from_consolidated_sales_invoice.py @@ -0,0 +1,19 @@ +import frappe + + +def execute(): + SalesInvoice = frappe.qb.DocType("Sales Invoice") + + query = ( + frappe.qb.update(SalesInvoice) + .set(SalesInvoice.sales_partner, "") + .set(SalesInvoice.commission_rate, 0) + .set(SalesInvoice.total_commission, 0) + .where(SalesInvoice.is_consolidated == 1) + ) + + # For develop/version-16 + if frappe.db.has_column("Sales Invoice", "is_created_using_pos"): + query = query.where(SalesInvoice.is_created_using_pos == 0) + + query.run() diff --git a/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.js b/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.js index 4a1ebabbccf..1150de86b80 100644 --- a/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.js +++ b/erpnext/selling/report/sales_partner_commission_summary/sales_partner_commission_summary.js @@ -13,7 +13,7 @@ frappe.query_reports["Sales Partner Commission Summary"] = { fieldname: "doctype", label: __("Document Type"), fieldtype: "Select", - options: "Sales Order\nDelivery Note\nSales Invoice", + options: "Sales Order\nDelivery Note\nSales Invoice\nPOS Invoice", default: "Sales Order", }, { diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js index 6809b38c7ed..8373e886ce5 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js @@ -21,7 +21,7 @@ frappe.query_reports["Sales Partner Target Variance based on Item Group"] = { fieldname: "doctype", label: __("Document Type"), fieldtype: "Select", - options: "Sales Order\nDelivery Note\nSales Invoice", + options: "Sales Order\nDelivery Note\nSales Invoice\nPOS Invoice", default: "Sales Order", }, { diff --git a/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.js b/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.js index b7bca8ae5fa..f6f7c3f3cf3 100644 --- a/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.js +++ b/erpnext/selling/report/sales_partner_transaction_summary/sales_partner_transaction_summary.js @@ -13,7 +13,7 @@ frappe.query_reports["Sales Partner Transaction Summary"] = { fieldname: "doctype", label: __("Document Type"), fieldtype: "Select", - options: "Sales Order\nDelivery Note\nSales Invoice", + options: "Sales Order\nDelivery Note\nSales Invoice\nPOS Invoice", default: "Sales Order", }, { From 56f5ec961fe7e1fbe9f6f124aa81f442137dc266 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 15:48:39 +0530 Subject: [PATCH 15/28] fix: view ledger button of company on chart of accounts (backport #48677) (#48678) fix: view ledger button of company on chart of accounts (cherry picked from commit 98eb115746a845af826f0858221f99d8688c48be) Co-authored-by: diptanilsaha --- erpnext/accounts/doctype/account/account_tree.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 183049c8dfc..84b6239a392 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -291,12 +291,14 @@ frappe.treeview_settings["Account"] = { label: __("View Ledger"), click: function (node, btn) { frappe.route_options = { - account: node.label, from_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], to_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], company: frappe.treeview_settings["Account"].treeview.page.fields_dict.company.get_value(), }; + if (node.parent_label) { + frappe.route_options["account"] = node.label; + } frappe.set_route("query-report", "General Ledger"); }, btnClass: "hidden-xs", From 2b4284837642a5c7091a09ba367d8fe195ecf350 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 16 Jul 2025 14:46:37 +0530 Subject: [PATCH 16/28] feat: consider process less when calculating pending qty in work order (cherry picked from commit 74c4ca68e565d308c8dbddaf3320c38be3e8529b) --- .../doctype/work_order/work_order.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 3db6d165328..3d2d5417604 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -419,7 +419,7 @@ frappe.ui.form.on("Work Order", { frm.doc.material_transferred_for_manufacturing - frm.doc.produced_qty - frm.doc.process_loss_qty; - if (pending_complete) { + if (pending_complete > 0) { var width = (pending_complete / frm.doc.qty) * 100 - added_min; title = __("{0} items in progress", [pending_complete]); bars.push({ @@ -829,6 +829,19 @@ erpnext.work_order = { description: __("Max: {0}", [max]), default: max, }, + { + fieldtype: "Check", + label: __("Consider Process Loss"), + fieldname: "consider_process_loss", + default: 0, + onchange: function () { + if (this.value) { + frm.qty_prompt.set_value("qty", max - frm.doc.process_loss_qty); + } else { + frm.qty_prompt.set_value("qty", max); + } + }, + }, ]; if (purpose === "Disassemble") { @@ -850,7 +863,7 @@ erpnext.work_order = { } return new Promise((resolve, reject) => { - frappe.prompt( + frm.qty_prompt = frappe.prompt( fields, (data) => { max += (frm.doc.qty * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100; From 4794e7acffb018cc9c07876dda3f7b4f05658bf8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:26:21 +0530 Subject: [PATCH 17/28] fix: job card linter error (backport #47561) (#48695) * fix: job card linter error (#47561) (cherry picked from commit 417426909134503a41a11d2bc8b4c130d64b094e) # Conflicts: # erpnext/manufacturing/doctype/work_order/work_order.py * chore: resolve conflicts --------- Co-authored-by: Mihir Kandoi --- .../doctype/work_order/test_work_order.py | 55 ++++++++----------- .../doctype/work_order/work_order.py | 36 ++++++++---- .../work_order_operation.json | 3 +- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 5106ded95e8..c77d34950ec 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2788,56 +2788,45 @@ class TestWorkOrder(FrappeTestCase): fg_warehouse="_Test Warehouse 2 - _TC", ) - # Initial check - self.assertEqual(wo.operations[0].operation, "Test Operation A") - self.assertEqual(wo.operations[1].operation, "Test Operation B") - self.assertEqual(wo.operations[2].operation, "Test Operation C") - self.assertEqual(wo.operations[3].operation, "Test Operation D") - wo = frappe.copy_doc(wo) - wo.operations[3].sequence_id = 2 + wo.operations[3].sequence_id = None + + # Test 1 : If any one operation does not have sequence ID then error will be thrown + self.assertRaises(frappe.ValidationError, wo.submit) + + for op in wo.operations: + op.sequence_id = None wo.submit() - # Test 2 : Sort line items in child table based on sequence ID - self.assertEqual(wo.operations[0].operation, "Test Operation A") - self.assertEqual(wo.operations[1].operation, "Test Operation B") - self.assertEqual(wo.operations[2].operation, "Test Operation D") - self.assertEqual(wo.operations[3].operation, "Test Operation C") + # Test 2 : If none of the operations have sequence ID then they will be sequenced as per their idx + for op in wo.operations: + self.assertEqual(op.sequence_id, op.idx) wo = frappe.copy_doc(wo) - wo.operations[3].sequence_id = 1 - wo.submit() + wo.operations[0].sequence_id = 2 - self.assertEqual(wo.operations[0].operation, "Test Operation A") - self.assertEqual(wo.operations[1].operation, "Test Operation C") - self.assertEqual(wo.operations[2].operation, "Test Operation B") - self.assertEqual(wo.operations[3].operation, "Test Operation D") + # Test 3 : Sequence IDs should not miss the correct sequence of numbers + self.assertRaises(frappe.ValidationError, wo.submit) - wo = frappe.copy_doc(wo) - wo.operations[0].sequence_id = 3 - wo.submit() + wo.operations[1].sequence_id = 1 - self.assertEqual(wo.operations[0].operation, "Test Operation C") - self.assertEqual(wo.operations[1].operation, "Test Operation B") - self.assertEqual(wo.operations[2].operation, "Test Operation D") - self.assertEqual(wo.operations[3].operation, "Test Operation A") - - wo = frappe.copy_doc(wo) - wo.operations[1].sequence_id = 0 - - # Test 3 - Error should be thrown if any one operation does not have sequence id but others do + # Test 4 : Sequence IDs should be in the correct ascending order self.assertRaises(frappe.ValidationError, wo.submit) workstation = frappe.get_doc("Workstation", "Test Workstation A") workstation.production_capacity = 4 workstation.save() - wo = frappe.copy_doc(wo) + wo.operations[0].sequence_id = 1 wo.operations[1].sequence_id = 2 + wo.operations[2].sequence_id = 2 + wo.operations[3].sequence_id = 3 wo.submit() - # Test 4 - If Sequence ID is same then planned start time for both operations should be same - self.assertEqual(wo.operations[1].planned_start_time, wo.operations[2].planned_start_time) + # Test 5 : If two operations have the same sequence ID then the next operation will start 10 mins after the longest previous operation ends + self.assertEqual( + wo.operations[3].planned_start_time, add_to_date(wo.operations[1].planned_end_time, minutes=10) + ) def make_stock_in_entries_and_get_batches(rm_item, source_warehouse, wip_warehouse): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 9b1bf28f997..9d862e84da7 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -168,6 +168,31 @@ class WorkOrder(Document): validate_uom_is_integer(self, "stock_uom", ["required_qty"]) self.set_required_items(reset_only_qty=len(self.get("required_items"))) + self.validate_operations_sequence() + + def validate_operations_sequence(self): + if all([not op.sequence_id for op in self.operations]): + for op in self.operations: + op.sequence_id = op.idx + else: + sequence_id = 1 + for op in self.operations: + if op.idx == 1 and op.sequence_id != 1: + frappe.throw( + _("Row #1: Sequence ID must be 1 for Operation {0}.").format( + frappe.bold(op.operation) + ) + ) + elif op.sequence_id != sequence_id and op.sequence_id != sequence_id + 1: + frappe.throw( + _("Row #{0}: Sequence ID must be {1} or {2} for Operation {3}.").format( + op.idx, + frappe.bold(sequence_id), + frappe.bold(sequence_id + 1), + frappe.bold(op.operation), + ) + ) + sequence_id = op.sequence_id def set_warehouses(self): for row in self.required_items: @@ -637,17 +662,6 @@ class WorkOrder(Document): enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning) plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30 - if all([op.sequence_id for op in self.operations]): - self.operations = sorted(self.operations, key=lambda op: op.sequence_id) - for idx, op in enumerate(self.operations): - op.idx = idx + 1 - elif any([op.sequence_id for op in self.operations]): - frappe.throw( - _( - "Row #{0}: Incorrect Sequence ID. If any single operation has a Sequence ID then all other operations must have one too." - ).format(next((op.idx for op in self.operations if not op.sequence_id), None)) - ) - for idx, row in enumerate(self.operations): qty = self.qty while qty > 0: diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index 0185812a4b6..38b325b73ab 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -194,6 +194,7 @@ "fieldname": "sequence_id", "fieldtype": "Int", "label": "Sequence ID", + "non_negative": 1, "print_hide": 1 }, { @@ -224,7 +225,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-04-09 16:21:47.110564", + "modified": "2025-05-15 15:10:06.885440", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", From 9501149bd85c040d0d6eedea6a9ed864cdce2fe8 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 18 Jul 2025 21:26:28 +0530 Subject: [PATCH 18/28] Revert "fix: job card linter error (backport #47561) (#48695)" This reverts commit 4794e7acffb018cc9c07876dda3f7b4f05658bf8. --- .../doctype/work_order/test_work_order.py | 55 +++++++++++-------- .../doctype/work_order/work_order.py | 36 ++++-------- .../work_order_operation.json | 3 +- 3 files changed, 45 insertions(+), 49 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index c77d34950ec..5106ded95e8 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2788,45 +2788,56 @@ class TestWorkOrder(FrappeTestCase): fg_warehouse="_Test Warehouse 2 - _TC", ) + # Initial check + self.assertEqual(wo.operations[0].operation, "Test Operation A") + self.assertEqual(wo.operations[1].operation, "Test Operation B") + self.assertEqual(wo.operations[2].operation, "Test Operation C") + self.assertEqual(wo.operations[3].operation, "Test Operation D") + wo = frappe.copy_doc(wo) - wo.operations[3].sequence_id = None - - # Test 1 : If any one operation does not have sequence ID then error will be thrown - self.assertRaises(frappe.ValidationError, wo.submit) - - for op in wo.operations: - op.sequence_id = None + wo.operations[3].sequence_id = 2 wo.submit() - # Test 2 : If none of the operations have sequence ID then they will be sequenced as per their idx - for op in wo.operations: - self.assertEqual(op.sequence_id, op.idx) + # Test 2 : Sort line items in child table based on sequence ID + self.assertEqual(wo.operations[0].operation, "Test Operation A") + self.assertEqual(wo.operations[1].operation, "Test Operation B") + self.assertEqual(wo.operations[2].operation, "Test Operation D") + self.assertEqual(wo.operations[3].operation, "Test Operation C") wo = frappe.copy_doc(wo) - wo.operations[0].sequence_id = 2 + wo.operations[3].sequence_id = 1 + wo.submit() - # Test 3 : Sequence IDs should not miss the correct sequence of numbers - self.assertRaises(frappe.ValidationError, wo.submit) + self.assertEqual(wo.operations[0].operation, "Test Operation A") + self.assertEqual(wo.operations[1].operation, "Test Operation C") + self.assertEqual(wo.operations[2].operation, "Test Operation B") + self.assertEqual(wo.operations[3].operation, "Test Operation D") - wo.operations[1].sequence_id = 1 + wo = frappe.copy_doc(wo) + wo.operations[0].sequence_id = 3 + wo.submit() - # Test 4 : Sequence IDs should be in the correct ascending order + self.assertEqual(wo.operations[0].operation, "Test Operation C") + self.assertEqual(wo.operations[1].operation, "Test Operation B") + self.assertEqual(wo.operations[2].operation, "Test Operation D") + self.assertEqual(wo.operations[3].operation, "Test Operation A") + + wo = frappe.copy_doc(wo) + wo.operations[1].sequence_id = 0 + + # Test 3 - Error should be thrown if any one operation does not have sequence id but others do self.assertRaises(frappe.ValidationError, wo.submit) workstation = frappe.get_doc("Workstation", "Test Workstation A") workstation.production_capacity = 4 workstation.save() + wo = frappe.copy_doc(wo) - wo.operations[0].sequence_id = 1 wo.operations[1].sequence_id = 2 - wo.operations[2].sequence_id = 2 - wo.operations[3].sequence_id = 3 wo.submit() - # Test 5 : If two operations have the same sequence ID then the next operation will start 10 mins after the longest previous operation ends - self.assertEqual( - wo.operations[3].planned_start_time, add_to_date(wo.operations[1].planned_end_time, minutes=10) - ) + # Test 4 - If Sequence ID is same then planned start time for both operations should be same + self.assertEqual(wo.operations[1].planned_start_time, wo.operations[2].planned_start_time) def make_stock_in_entries_and_get_batches(rm_item, source_warehouse, wip_warehouse): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 9d862e84da7..9b1bf28f997 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -168,31 +168,6 @@ class WorkOrder(Document): validate_uom_is_integer(self, "stock_uom", ["required_qty"]) self.set_required_items(reset_only_qty=len(self.get("required_items"))) - self.validate_operations_sequence() - - def validate_operations_sequence(self): - if all([not op.sequence_id for op in self.operations]): - for op in self.operations: - op.sequence_id = op.idx - else: - sequence_id = 1 - for op in self.operations: - if op.idx == 1 and op.sequence_id != 1: - frappe.throw( - _("Row #1: Sequence ID must be 1 for Operation {0}.").format( - frappe.bold(op.operation) - ) - ) - elif op.sequence_id != sequence_id and op.sequence_id != sequence_id + 1: - frappe.throw( - _("Row #{0}: Sequence ID must be {1} or {2} for Operation {3}.").format( - op.idx, - frappe.bold(sequence_id), - frappe.bold(sequence_id + 1), - frappe.bold(op.operation), - ) - ) - sequence_id = op.sequence_id def set_warehouses(self): for row in self.required_items: @@ -662,6 +637,17 @@ class WorkOrder(Document): enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning) plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30 + if all([op.sequence_id for op in self.operations]): + self.operations = sorted(self.operations, key=lambda op: op.sequence_id) + for idx, op in enumerate(self.operations): + op.idx = idx + 1 + elif any([op.sequence_id for op in self.operations]): + frappe.throw( + _( + "Row #{0}: Incorrect Sequence ID. If any single operation has a Sequence ID then all other operations must have one too." + ).format(next((op.idx for op in self.operations if not op.sequence_id), None)) + ) + for idx, row in enumerate(self.operations): qty = self.qty while qty > 0: diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index 38b325b73ab..0185812a4b6 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -194,7 +194,6 @@ "fieldname": "sequence_id", "fieldtype": "Int", "label": "Sequence ID", - "non_negative": 1, "print_hide": 1 }, { @@ -225,7 +224,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-05-15 15:10:06.885440", + "modified": "2025-04-09 16:21:47.110564", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", From a139cd4b5e0733c5bc08d100dd0722b5abc92ce0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:26:21 +0530 Subject: [PATCH 19/28] fix: job card linter error (backport #47561) (#48695) * fix: job card linter error (#47561) (cherry picked from commit 417426909134503a41a11d2bc8b4c130d64b094e) # Conflicts: # erpnext/manufacturing/doctype/work_order/work_order.py * chore: resolve conflicts --------- Co-authored-by: Mihir Kandoi (cherry picked from commit 4794e7acffb018cc9c07876dda3f7b4f05658bf8) --- .../doctype/work_order/test_work_order.py | 55 ++++++++----------- .../doctype/work_order/work_order.py | 36 ++++++++---- .../work_order_operation.json | 3 +- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 5106ded95e8..c77d34950ec 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2788,56 +2788,45 @@ class TestWorkOrder(FrappeTestCase): fg_warehouse="_Test Warehouse 2 - _TC", ) - # Initial check - self.assertEqual(wo.operations[0].operation, "Test Operation A") - self.assertEqual(wo.operations[1].operation, "Test Operation B") - self.assertEqual(wo.operations[2].operation, "Test Operation C") - self.assertEqual(wo.operations[3].operation, "Test Operation D") - wo = frappe.copy_doc(wo) - wo.operations[3].sequence_id = 2 + wo.operations[3].sequence_id = None + + # Test 1 : If any one operation does not have sequence ID then error will be thrown + self.assertRaises(frappe.ValidationError, wo.submit) + + for op in wo.operations: + op.sequence_id = None wo.submit() - # Test 2 : Sort line items in child table based on sequence ID - self.assertEqual(wo.operations[0].operation, "Test Operation A") - self.assertEqual(wo.operations[1].operation, "Test Operation B") - self.assertEqual(wo.operations[2].operation, "Test Operation D") - self.assertEqual(wo.operations[3].operation, "Test Operation C") + # Test 2 : If none of the operations have sequence ID then they will be sequenced as per their idx + for op in wo.operations: + self.assertEqual(op.sequence_id, op.idx) wo = frappe.copy_doc(wo) - wo.operations[3].sequence_id = 1 - wo.submit() + wo.operations[0].sequence_id = 2 - self.assertEqual(wo.operations[0].operation, "Test Operation A") - self.assertEqual(wo.operations[1].operation, "Test Operation C") - self.assertEqual(wo.operations[2].operation, "Test Operation B") - self.assertEqual(wo.operations[3].operation, "Test Operation D") + # Test 3 : Sequence IDs should not miss the correct sequence of numbers + self.assertRaises(frappe.ValidationError, wo.submit) - wo = frappe.copy_doc(wo) - wo.operations[0].sequence_id = 3 - wo.submit() + wo.operations[1].sequence_id = 1 - self.assertEqual(wo.operations[0].operation, "Test Operation C") - self.assertEqual(wo.operations[1].operation, "Test Operation B") - self.assertEqual(wo.operations[2].operation, "Test Operation D") - self.assertEqual(wo.operations[3].operation, "Test Operation A") - - wo = frappe.copy_doc(wo) - wo.operations[1].sequence_id = 0 - - # Test 3 - Error should be thrown if any one operation does not have sequence id but others do + # Test 4 : Sequence IDs should be in the correct ascending order self.assertRaises(frappe.ValidationError, wo.submit) workstation = frappe.get_doc("Workstation", "Test Workstation A") workstation.production_capacity = 4 workstation.save() - wo = frappe.copy_doc(wo) + wo.operations[0].sequence_id = 1 wo.operations[1].sequence_id = 2 + wo.operations[2].sequence_id = 2 + wo.operations[3].sequence_id = 3 wo.submit() - # Test 4 - If Sequence ID is same then planned start time for both operations should be same - self.assertEqual(wo.operations[1].planned_start_time, wo.operations[2].planned_start_time) + # Test 5 : If two operations have the same sequence ID then the next operation will start 10 mins after the longest previous operation ends + self.assertEqual( + wo.operations[3].planned_start_time, add_to_date(wo.operations[1].planned_end_time, minutes=10) + ) def make_stock_in_entries_and_get_batches(rm_item, source_warehouse, wip_warehouse): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 9b1bf28f997..9d862e84da7 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -168,6 +168,31 @@ class WorkOrder(Document): validate_uom_is_integer(self, "stock_uom", ["required_qty"]) self.set_required_items(reset_only_qty=len(self.get("required_items"))) + self.validate_operations_sequence() + + def validate_operations_sequence(self): + if all([not op.sequence_id for op in self.operations]): + for op in self.operations: + op.sequence_id = op.idx + else: + sequence_id = 1 + for op in self.operations: + if op.idx == 1 and op.sequence_id != 1: + frappe.throw( + _("Row #1: Sequence ID must be 1 for Operation {0}.").format( + frappe.bold(op.operation) + ) + ) + elif op.sequence_id != sequence_id and op.sequence_id != sequence_id + 1: + frappe.throw( + _("Row #{0}: Sequence ID must be {1} or {2} for Operation {3}.").format( + op.idx, + frappe.bold(sequence_id), + frappe.bold(sequence_id + 1), + frappe.bold(op.operation), + ) + ) + sequence_id = op.sequence_id def set_warehouses(self): for row in self.required_items: @@ -637,17 +662,6 @@ class WorkOrder(Document): enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning) plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30 - if all([op.sequence_id for op in self.operations]): - self.operations = sorted(self.operations, key=lambda op: op.sequence_id) - for idx, op in enumerate(self.operations): - op.idx = idx + 1 - elif any([op.sequence_id for op in self.operations]): - frappe.throw( - _( - "Row #{0}: Incorrect Sequence ID. If any single operation has a Sequence ID then all other operations must have one too." - ).format(next((op.idx for op in self.operations if not op.sequence_id), None)) - ) - for idx, row in enumerate(self.operations): qty = self.qty while qty > 0: diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index 0185812a4b6..38b325b73ab 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -194,6 +194,7 @@ "fieldname": "sequence_id", "fieldtype": "Int", "label": "Sequence ID", + "non_negative": 1, "print_hide": 1 }, { @@ -224,7 +225,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-04-09 16:21:47.110564", + "modified": "2025-05-15 15:10:06.885440", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", From ab79e5d94600bc3bb4f02b6ab542a15cadccfbd8 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Fri, 18 Jul 2025 11:36:12 +0530 Subject: [PATCH 20/28] revert: do not set pay_to_recd_from to None (cherry picked from commit 03d6550db3c1ae10b273deed2110f4897a4ad5d1) --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index d05e9e3b2d1..d7386902c81 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1044,9 +1044,7 @@ class JournalEntry(AccountsController): def set_print_format_fields(self): bank_amount = party_amount = total_amount = 0.0 - currency = ( - bank_account_currency - ) = party_account_currency = pay_to_recd_from = self.pay_to_recd_from = None + currency = bank_account_currency = party_account_currency = pay_to_recd_from = None party_type = None for d in self.get("accounts"): if d.party_type in ["Customer", "Supplier"] and d.party: From e3ccdbb20b3b6fa24225725cf406750eb8e71c2f Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Mon, 21 Jul 2025 18:44:29 +0530 Subject: [PATCH 21/28] test: add test for pay_to_recd_from (cherry picked from commit 7e12332ea541f4634748751af734e47da778a759) --- .../doctype/journal_entry/test_journal_entry.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 4d2d14abea4..0c9a20882c6 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -580,6 +580,18 @@ class TestJournalEntry(unittest.TestCase): ] self.assertEqual(expected, actual) + def test_pay_to_recd_from(self): + jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False) + jv.pay_to_recd_from = "_Test Receiver" + jv.save() + self.assertEqual(jv.pay_to_recd_from, "_Test Receiver") + + jv.pay_to_recd_from = "_Test Receiver 2" + jv.save() + jv.submit() + + self.assertEqual(jv.pay_to_recd_from, "_Test Receiver 2") + def make_journal_entry( account1, From 8f23ca5c6b9d3dd6a291e3500d324353a3f033c1 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Fri, 18 Jul 2025 18:35:16 +0530 Subject: [PATCH 22/28] fix: set delivery date if missing (cherry picked from commit cf6913891a80bcb998dd03c3e6becc720ffa9fd3) # Conflicts: # erpnext/selling/doctype/sales_order/sales_order.py --- erpnext/selling/doctype/sales_order/sales_order.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 81b35c6f063..b89e14b5bcf 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -771,6 +771,14 @@ class SalesOrder(SellingController): voucher_type=self.doctype, voucher_no=self.name, sre_list=sre_list, notify=notify ) + def set_missing_values(self, for_validate=False): + super().set_missing_values(for_validate) + + if self.delivery_date: + for item in self.items: + if not item.delivery_date: + item.delivery_date = self.delivery_date + def get_unreserved_qty(item: object, reserved_qty_details: dict) -> float: """Returns the unreserved quantity for the Sales Order Item.""" From e68370359fc838c5214199eab7c5aeb93f026606 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Sat, 19 Jul 2025 08:15:18 +0530 Subject: [PATCH 23/28] fix(production plan): add company filter to sub assembly warehouse (cherry picked from commit 1728a9511160458c224fde827751a6d4ac92ddf6) --- .../doctype/production_plan/production_plan.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 1d55c64663f..0c535af5be2 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -37,6 +37,14 @@ frappe.ui.form.on("Production Plan", { }; }); + frm.set_query("sub_assembly_warehouse", function (doc) { + return { + filters: { + company: doc.company, + }, + }; + }); + frm.set_query("material_request", "material_requests", function () { return { filters: { From 38b223e7327abdc34b7ba3c1edc02ba675d5ab77 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Thu, 17 Jul 2025 18:15:06 +0530 Subject: [PATCH 24/28] fix: show amount for exchange gain or loss account (cherry picked from commit 4f90f50eb2b7f5ad5bafcefe2823cf9e35738870) --- .../report/general_ledger/general_ledger.py | 2 +- erpnext/accounts/report/utils.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 0bb14604991..91b244f94fa 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -204,7 +204,7 @@ def get_gl_entries(filters, accounting_dimensions): ) if filters.get("presentation_currency"): - return convert_to_presentation_currency(gl_entries, currency_map) + return convert_to_presentation_currency(gl_entries, currency_map, filters) else: return gl_entries diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index 2a72b10e4eb..796c8c75ad2 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -86,7 +86,7 @@ def get_rate_as_at(date, from_currency, to_currency): return rate -def convert_to_presentation_currency(gl_entries, currency_info): +def convert_to_presentation_currency(gl_entries, currency_info, filters=None): """ Take a list of GL Entries and change the 'debit' and 'credit' values to currencies in `currency_info`. @@ -99,6 +99,13 @@ def convert_to_presentation_currency(gl_entries, currency_info): company_currency = currency_info["company_currency"] account_currencies = list(set(entry["account_currency"] for entry in gl_entries)) + exchange_gain_or_loss = False + + if filters: + account_filter = filters.get("account") + gain_loss_account = frappe.db.get_value("Company", filters.company, "exchange_gain_loss_account") + + exchange_gain_or_loss = len(account_filter) == 1 and account_filter[0] == gain_loss_account for entry in gl_entries: debit = flt(entry["debit"]) @@ -107,7 +114,11 @@ def convert_to_presentation_currency(gl_entries, currency_info): credit_in_account_currency = flt(entry["credit_in_account_currency"]) account_currency = entry["account_currency"] - if len(account_currencies) == 1 and account_currency == presentation_currency: + if ( + len(account_currencies) == 1 + and account_currency == presentation_currency + and not exchange_gain_or_loss + ): entry["debit"] = debit_in_account_currency entry["credit"] = credit_in_account_currency else: From 90fa7db13c31009821449ebf08038e91a510ddb2 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Thu, 17 Jul 2025 18:52:41 +0530 Subject: [PATCH 25/28] fix: add validation for account key (cherry picked from commit b6da350c201ff91dfe7dcb887b60d0dba1f0bd95) --- erpnext/accounts/report/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index 796c8c75ad2..136a0acbbb0 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -101,7 +101,7 @@ def convert_to_presentation_currency(gl_entries, currency_info, filters=None): account_currencies = list(set(entry["account_currency"] for entry in gl_entries)) exchange_gain_or_loss = False - if filters: + if filters and isinstance(filters.get("account"), list): account_filter = filters.get("account") gain_loss_account = frappe.db.get_value("Company", filters.company, "exchange_gain_loss_account") From 4bef3cc92ff0e66aefe0204c015aeecbadb9a9f7 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Tue, 22 Jul 2025 01:23:19 +0530 Subject: [PATCH 26/28] fix: add alias for order by field (cherry picked from commit feaf39a8120660e67deaba67613c058e361b1afc) --- .../item_wise_purchase_register.py | 9 ++--- .../item_wise_sales_register.py | 36 +++++++------------ 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index ae822c5b413..559ba4a70ab 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -178,7 +178,7 @@ def get_columns(additional_table_columns, filters): "fieldname": "invoice", "fieldtype": "Link", "options": "Purchase Invoice", - "width": 120, + "width": 150, }, {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120}, ] @@ -310,8 +310,8 @@ def apply_conditions(query, pi, pii, filters): def get_items(filters, additional_table_columns): doctype = "Purchase Invoice" - pi = frappe.qb.DocType(doctype) - pii = frappe.qb.DocType(f"{doctype} Item") + pi = frappe.qb.DocType(doctype).as_("invoice") + pii = frappe.qb.DocType(f"{doctype} Item").as_("invoice_item") Item = frappe.qb.DocType("Item") query = ( frappe.qb.from_(pi) @@ -331,6 +331,7 @@ def get_items(filters, additional_table_columns): pi.unrealized_profit_loss_account, pii.item_code, pii.description, + pii.item_name, pii.item_group, pii.item_name.as_("pi_item_name"), pii.item_group.as_("pi_item_group"), @@ -374,7 +375,7 @@ def get_items(filters, additional_table_columns): if match_conditions: query += " and " + match_conditions - query = apply_order_by_conditions(query, pi, pii, filters) + query = apply_order_by_conditions(query, filters) return frappe.db.sql(query, params, as_dict=True) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index a373b1f0a1e..b4a72c5374f 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -198,7 +198,7 @@ def get_columns(additional_table_columns, filters): "fieldname": "invoice", "fieldtype": "Link", "options": "Sales Invoice", - "width": 120, + "width": 150, }, {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120}, ] @@ -394,15 +394,15 @@ def apply_conditions(query, si, sii, sip, filters, additional_conditions=None): return query -def apply_order_by_conditions(query, si, ii, filters): +def apply_order_by_conditions(query, filters): if not filters.get("group_by"): - query += f" order by {si.posting_date} desc, {ii.item_group} desc" + query += "order by invoice.posting_date desc, invoice_item.item_group desc" elif filters.get("group_by") == "Invoice": - query += f" order by {ii.parent} desc" + query += "order by invoice_item.parent desc" elif filters.get("group_by") == "Item": - query += f" order by {ii.item_code}" + query += "order by invoice_item.item_code" elif filters.get("group_by") == "Item Group": - query += f" order by {ii.item_group}" + query += "order by invoice_item.item_group" elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"): filter_field = frappe.scrub(filters.get("group_by")) query += f" order by {filter_field} desc" @@ -412,9 +412,9 @@ def apply_order_by_conditions(query, si, ii, filters): def get_items(filters, additional_query_columns, additional_conditions=None): doctype = "Sales Invoice" - si = frappe.qb.DocType(doctype) - sip = frappe.qb.DocType(f"{doctype} Payment") - sii = frappe.qb.DocType(f"{doctype} Item") + si = frappe.qb.DocType("Sales Invoice").as_("invoice") + sii = frappe.qb.DocType("Sales Invoice Item").as_("invoice_item") + sip = frappe.qb.DocType("Sales Invoice Payment") item = frappe.qb.DocType("Item") query = ( @@ -492,7 +492,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None): if match_conditions: query += " and " + match_conditions - query = apply_order_by_conditions(query, si, sii, filters) + query = apply_order_by_conditions(query, filters) return frappe.db.sql(query, params, as_dict=True) @@ -764,25 +764,13 @@ def add_total_row( def get_display_value(filters, group_by_field, item): if filters.get("group_by") == "Item": if item.get("item_code") != item.get("item_name"): - value = ( - cstr(item.get("item_code")) - + "

" - + "" - + cstr(item.get("item_name")) - + "" - ) + value = f"{item.get('item_code')}: {item.get('item_name')}" else: value = item.get("item_code", "") elif filters.get("group_by") in ("Customer", "Supplier"): party = frappe.scrub(filters.get("group_by")) if item.get(party) != item.get(party + "_name"): - value = ( - item.get(party) - + "

" - + "" - + item.get(party + "_name") - + "" - ) + value = f"{item.get(party)}: {item.get(party + '_name')}" else: value = item.get(party) else: From 96a1444e922960cd3812bbaa377121703bb2ee08 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Mon, 21 Jul 2025 23:58:19 +0530 Subject: [PATCH 27/28] fix: resolve sql syntax on accounting dimension (cherry picked from commit 1662b7c311a2c852406442811a36b456cb5f8f82) --- .../dimension_wise_accounts_balance_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py index 084ea9b80ea..ed30ad415d0 100644 --- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py @@ -86,7 +86,7 @@ def set_gl_entries_by_account(dimension_list, filters, account, gl_entries_by_ac "finance_book": cstr(filters.get("finance_book")), } - gl_filters["dimensions"] = set(dimension_list) + gl_filters["dimensions"] = tuple(set(dimension_list)) if filters.get("include_default_book_entries"): gl_filters["company_fb"] = frappe.get_cached_value("Company", filters.company, "default_finance_book") @@ -179,7 +179,7 @@ def accumulate_values_into_parents(accounts, accounts_by_name, dimension_list): def get_condition(dimension): conditions = [] - conditions.append(f"{frappe.scrub(dimension)} in (%(dimensions)s)") + conditions.append(f"{frappe.scrub(dimension)} in %(dimensions)s") return " and {}".format(" and ".join(conditions)) if conditions else "" From 0d496bb05fb23e1576dfedf28c214985e7a771e9 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Thu, 17 Jul 2025 14:41:14 +0530 Subject: [PATCH 28/28] fix: update outstanding amount on payment reconcillation (cherry picked from commit 478766c60043b456e90ddda672715d150629233c) --- erpnext/accounts/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9132cb15fd9..30081e275ff 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -512,7 +512,8 @@ def reconcile_against_document( skip_ref_details_update_for_pe=skip_ref_details_update_for_pe, dimensions_dict=dimensions_dict, ) - + if referenced_row.get("outstanding_amount"): + referenced_row.outstanding_amount -= flt(entry.allocated_amount) doc.save(ignore_permissions=True) # re-submit advance entry doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)