From d43c04df8810179db20e0f2d44b88f14acd3e0ea Mon Sep 17 00:00:00 2001 From: creative-paramu Date: Wed, 11 Mar 2026 11:14:33 +0530 Subject: [PATCH 01/63] fix: update item description in Production Plan Assembly Items table (cherry picked from commit 19533551f44bd0527892734d3d9716a598d52e9c) --- .../manufacturing/doctype/production_plan/production_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 947eb45787e..8d1a82b7a9b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1896,7 +1896,7 @@ def get_item_data(item_code): return { "bom_no": item_details.get("bom_no"), "stock_uom": item_details.get("stock_uom"), - # "description": item_details.get("description") + "description": item_details.get("description") } From b9b551f248782c00e3ca96d80a3d1d20d5c384ee Mon Sep 17 00:00:00 2001 From: Parameshwari Palanisamy <101092028+creative-paramu@users.noreply.github.com> Date: Wed, 11 Mar 2026 11:41:24 +0530 Subject: [PATCH 02/63] Update production_plan.py (cherry picked from commit 39e68a9ce7e89583f3c39895c5537fc742b70620) --- .../manufacturing/doctype/production_plan/production_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 8d1a82b7a9b..1819b5363d9 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1896,7 +1896,7 @@ def get_item_data(item_code): return { "bom_no": item_details.get("bom_no"), "stock_uom": item_details.get("stock_uom"), - "description": item_details.get("description") + "description": item_details.get("description"), } From bdc3b0666d552d9421225d93e7e9ffca286f377b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 11 Mar 2026 14:26:16 +0530 Subject: [PATCH 03/63] refactor: make cost center editable in payment entry deduction (cherry picked from commit 078b22d985b5d35025f3ee68258491f791a52108) --- .../payment_entry_deduction/payment_entry_deduction.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json index 59a01bc84ef..8d4e55c970a 100644 --- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json +++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json @@ -22,6 +22,7 @@ "reqd": 1 }, { + "allow_on_submit": 1, "fieldname": "cost_center", "fieldtype": "Link", "in_list_view": 1, @@ -59,7 +60,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-08-13 06:52:46.130142", + "modified": "2026-03-11 14:26:11.312950", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Deduction", From 875114d33ccd270f9477141b38b6292c95976929 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Wed, 11 Mar 2026 14:40:41 +0530 Subject: [PATCH 04/63] fix: Append existing ignored doctypes in Journal Entry on_cancel instead of overwriting (cherry picked from commit 39e10c4ab0c17fccd20e1d70c9d0533f7d2e4778) --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 6428896acae..23fb4fd0825 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -293,6 +293,8 @@ class JournalEntry(AccountsController): # References for this Journal are removed on the `on_cancel` event in accounts_controller super().on_cancel() + + from_doc_events = getattr(self, "ignore_linked_doctypes", ()) self.ignore_linked_doctypes = ( "GL Entry", "Stock Ledger Entry", @@ -306,6 +308,10 @@ class JournalEntry(AccountsController): "Advance Payment Ledger Entry", "Tax Withholding Entry", ) + + if from_doc_events and from_doc_events != self.ignore_linked_doctypes: + self.ignore_linked_doctypes = self.ignore_linked_doctypes + from_doc_events + self.make_gl_entries(1) JournalTaxWithholding(self).on_cancel() self.unlink_advance_entry_reference() From a4d2c34ddebdd6a425f6882d3cd2356351b46e62 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:30:15 +0530 Subject: [PATCH 05/63] fix: update child item schedule_date and prevent past dates (backport #53298) (#53346) Co-authored-by: Pandiyan P fix: update child item schedule_date and prevent past dates (#53298) --- .../buying/doctype/purchase_order/purchase_order.js | 12 ++---------- erpnext/public/js/controllers/buying.js | 7 +++++++ .../doctype/material_request/material_request.js | 12 ++---------- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 87435f19393..bda926b4040 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -67,7 +67,7 @@ frappe.ui.form.on("Purchase Order", { }, transaction_date(frm) { - prevent_past_schedule_dates(frm); + erpnext.buying.prevent_past_schedule_dates(frm); frm.set_value("schedule_date", ""); }, @@ -87,7 +87,7 @@ frappe.ui.form.on("Purchase Order", { if (frm.doc.docstatus == 0) { erpnext.set_unit_price_items_note(frm); } - prevent_past_schedule_dates(frm); + erpnext.buying.prevent_past_schedule_dates(frm); }, get_materials_from_supplier: function (frm) { @@ -779,11 +779,3 @@ frappe.ui.form.on("Purchase Order", "is_subcontracted", function (frm) { erpnext.buying.get_default_bom(frm); } }); - -function prevent_past_schedule_dates(frm) { - if (frm.doc.transaction_date) { - frm.fields_dict["schedule_date"].datepicker?.update({ - minDate: new Date(frm.doc.transaction_date), - }); - } -} diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 873edfb7165..5c3581ac2e4 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -682,3 +682,10 @@ erpnext.buying.get_items_from_product_bundle = function (frm) { dialog.show(); }; +erpnext.buying.prevent_past_schedule_dates = function (frm) { + if (frm.doc.transaction_date) { + frm.fields_dict["schedule_date"].datepicker?.update({ + minDate: new Date(frm.doc.transaction_date), + }); + } +}; diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index b730080ea35..625d6490a9d 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -116,12 +116,12 @@ frappe.ui.form.on("Material Request", { refresh: function (frm) { frm.events.make_custom_buttons(frm); frm.toggle_reqd("customer", frm.doc.material_request_type == "Customer Provided"); - prevent_past_schedule_dates(frm); + erpnext.buying.prevent_past_schedule_dates(frm); frm.trigger("set_warehouse_label"); }, transaction_date(frm) { - prevent_past_schedule_dates(frm); + erpnext.buying.prevent_past_schedule_dates(frm); frm.set_value("schedule_date", ""); }, @@ -681,11 +681,3 @@ function set_schedule_date(frm) { ); } } - -function prevent_past_schedule_dates(frm) { - if (frm.doc.transaction_date) { - frm.fields_dict["schedule_date"].datepicker.update({ - minDate: new Date(frm.doc.transaction_date), - }); - } -} From 541bfb664bc70c4d52d722c0f7c06c69687b8b6f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 05:22:27 +0000 Subject: [PATCH 06/63] fix: re-calculate taxes and totals after resetting bundle item rate (backport #53342) (#53350) Co-authored-by: V Shankar fix: re-calculate taxes and totals after resetting bundle item rate (#53342) --- erpnext/stock/doctype/packed_item/packed_item.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 9477b85131d..f8bb88f6151 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -354,11 +354,19 @@ def update_product_bundle_rate(parent_items_price, pi_row, item_row): def set_product_bundle_rate_amount(doc, parent_items_price): "Set cumulative rate and amount in bundle item." + rate_updated = False for item in doc.get("items"): bundle_rate = parent_items_price.get((item.item_code, item.name)) if bundle_rate and bundle_rate != item.rate: item.rate = bundle_rate item.amount = flt(bundle_rate * item.qty) + item.margin_rate_or_amount = 0 + item.discount_percentage = 0 + item.discount_amount = 0 + rate_updated = True + if rate_updated: + doc.calculate_taxes_and_totals() + doc.set_total_in_words() def on_doctype_update(): From 82b97abbad63b9e3668009c0c3ee5992ac71ab53 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 05:26:02 +0000 Subject: [PATCH 07/63] fix(delivery note): avoid maintaining si_detail on return delivery note (backport #52456) (#53353) Co-authored-by: NaviN <118178330+Navin-S-R@users.noreply.github.com> fix(delivery note): avoid maintaining si_detail on return delivery note (#52456) --- erpnext/controllers/sales_and_purchase_return.py | 1 - erpnext/stock/doctype/delivery_note/delivery_note.py | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index e3c571e71d8..62eabb6f45a 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -596,7 +596,6 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai target_doc.against_sales_order = source_doc.against_sales_order target_doc.against_sales_invoice = source_doc.against_sales_invoice target_doc.so_detail = source_doc.so_detail - target_doc.si_detail = source_doc.si_detail target_doc.expense_account = source_doc.expense_account target_doc.dn_detail = source_doc.name if default_warehouse_for_sales_return: diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 07c1623a182..721711c065f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -395,6 +395,9 @@ class DeliveryNote(SellingController): ) def validate_sales_invoice_references(self): + if self.is_return: + return + self._validate_dependent_item_fields( "against_sales_invoice", "si_detail", _("References to Sales Invoices are Incomplete") ) From 09279f08e3404ebfb5290d96b8a41c4164e1b247 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 05:47:32 +0000 Subject: [PATCH 08/63] Feat/shipment default contact (backport #53029) (#53355) Co-authored-by: David <52141166+sdavidbastos@users.noreply.github.com> --- .../stock/doctype/delivery_note/delivery_note.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 721711c065f..64b9005a3da 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -7,6 +7,7 @@ import json import frappe from frappe import _ from frappe.contacts.doctype.address.address import get_company_address +from frappe.contacts.doctype.contact.contact import get_default_contact from frappe.desk.notifications import clear_doctype_notifications from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values @@ -1106,18 +1107,24 @@ def make_shipment(source_name, target_doc=None): # As we are using session user details in the pickup_contact then pickup_contact_person will be session user target.pickup_contact_person = frappe.session.user - if source.contact_person: + contact_person = source.contact_person or get_default_contact("Customer", source.customer) + if contact_person: contact = frappe.db.get_value( - "Contact", source.contact_person, ["email_id", "phone", "mobile_no"], as_dict=1 + "Contact", contact_person, ["email_id", "phone", "mobile_no"], as_dict=1 ) - delivery_contact_display = f"{source.contact_display}" - if contact: + + delivery_contact_display = source.contact_display or contact_person or "" + if contact and not source.contact_display: if contact.email_id: delivery_contact_display += "
" + contact.email_id if contact.phone: delivery_contact_display += "
" + contact.phone if contact.mobile_no and not contact.phone: delivery_contact_display += "
" + contact.mobile_no + + target.delivery_contact_name = contact_person + if contact and contact.email_id and not target.delivery_contact_email: + target.delivery_contact_email = contact.email_id target.delivery_contact = delivery_contact_display if source.shipping_address_name: From 4a7fdd1f91fe9a91c8ab93d5e3621a360c501024 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 05:51:46 +0000 Subject: [PATCH 09/63] fix: handle NoneType error while creating a item (backport #53313) (#53351) Co-authored-by: Pandiyan P fix: handle NoneType error while creating a item (#53313) --- erpnext/accounts/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index cf6a7c12326..7a23a39497b 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1536,7 +1536,7 @@ def parse_naming_series_variable(doc, variable): getdate(doc.get("posting_date") or doc.get("transaction_date") or doc.get("posting_datetime")) or now_datetime() ) - if frappe.get_single_value("Global Defaults", "use_posting_datetime_for_naming_documents") + if doc and frappe.get_single_value("Global Defaults", "use_posting_datetime_for_naming_documents") else now_datetime() ) return date.strftime(data[variable]) if variable in data else determine_consecutive_week_number(date) From 270a294e81a00b294270bf05864b7741b9c33fa0 Mon Sep 17 00:00:00 2001 From: Ejaaz Khan <67804911+iamejaaz@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:41:53 +0530 Subject: [PATCH 10/63] fix: remove redundant pos print format (#53348) (cherry picked from commit e4d79c62463653bc7cf151921230af090314d155) --- .../print_format/point_of_sale/__init__.py | 0 .../point_of_sale/point_of_sale.json | 23 ------------------- 2 files changed, 23 deletions(-) delete mode 100644 erpnext/accounts/print_format/point_of_sale/__init__.py delete mode 100644 erpnext/accounts/print_format/point_of_sale/point_of_sale.json diff --git a/erpnext/accounts/print_format/point_of_sale/__init__.py b/erpnext/accounts/print_format/point_of_sale/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/accounts/print_format/point_of_sale/point_of_sale.json b/erpnext/accounts/print_format/point_of_sale/point_of_sale.json deleted file mode 100644 index c0c50cb4e26..00000000000 --- a/erpnext/accounts/print_format/point_of_sale/point_of_sale.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "align_labels_right": 0, - "creation": "2016-05-05 17:16:18.564460", - "custom_format": 1, - "disabled": 0, - "doc_type": "Sales Invoice", - "docstatus": 0, - "doctype": "Print Format", - "font": "Default", - "html": "\n\n

\n\t{{ company }}
\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}
\n

\n

\n\t{{ __(\"Customer\") }}: {{ customer }}
\n

\n\n

\n\t{{ __(\"Date\") }}: {{ dateutil.global_date_format(posting_date) }}
\n

\n\n
\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{% for item in items %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endfor %}\n\t\n
{{ __(\"Item\") }}{{ __(\"Qty\") }}{{ __(\"Amount\") }}
\n\t\t\t\t{{ item.item_name }}\n\t\t\t{{ format_number(item.qty, null,precision(\"difference\")) }}
@ {{ format_currency(item.rate, currency) }}
{{ format_currency(item.amount, currency) }}
\n\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endif %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t
\n\t\t\t\t{{ row.description }}\n\t\t\t\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t
\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t
\n\t\t\t\t{{ __(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t
\n\t\t\t\t{{ __(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t
\n\t\t\t\t{{ __(\"Qty Total\") }}\n\t\t\t\n\t\t\t\t{{ qty_total }}\n\t\t\t
\n\n\n
\n

{{ terms }}

\n

{{ __(\"Thank you, please visit again.\") }}

", - "idx": 0, - "line_breaks": 0, - "modified": "2019-09-05 17:20:30.726659", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Point of Sale", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "JS", - "raw_printing": 0, - "show_section_headings": 0, - "standard": "Yes" -} \ No newline at end of file From ac9ec7e34a250e842c5e7c62cdfd519105f012a3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 06:19:40 +0000 Subject: [PATCH 11/63] =?UTF-8?q?refactor:=20supplier=20quotation=20compar?= =?UTF-8?q?ision=20report=20button=20should=20start=20f=E2=80=A6=20(backpo?= =?UTF-8?q?rt=20#53361)=20(#53363)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mihir Kandoi --- .../request_for_quotation/request_for_quotation.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 3171ad595b1..b71d0dd3006 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -165,14 +165,10 @@ frappe.ui.form.on("Request for Quotation", { }, show_supplier_quotation_comparison(frm) { - const today = new Date(); - const oneMonthAgo = new Date(today); - oneMonthAgo.setMonth(today.getMonth() - 1); - frappe.route_options = { company: frm.doc.company, - from_date: moment(oneMonthAgo).format("YYYY-MM-DD"), - to_date: moment(today).format("YYYY-MM-DD"), + from_date: moment(frm.doc.transaction_date).format("YYYY-MM-DD"), + to_date: moment(new Date()).format("YYYY-MM-DD"), request_for_quotation: frm.doc.name, }; frappe.set_route("query-report", "Supplier Quotation Comparison"); From 24910cf2d20a7be3b6c50cf5a1a1534d1dcabc85 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 06:50:55 +0000 Subject: [PATCH 12/63] fix: NoneType error when template description is to be copied to variant (backport #53358) (#53366) Co-authored-by: Mihir Kandoi fix: NoneType error when template description is to be copied to variant (#53358) --- erpnext/controllers/item_variant.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index 77599e3a009..1e2ef75de93 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -360,13 +360,13 @@ def copy_attributes_to_variant(item, variant): else: if item.variant_based_on == "Item Attribute": if variant.attributes: - attributes_description = item.description + " " + attributes_description = item.description or "" for d in variant.attributes: attributes_description += ( "
" + d.attribute + ": " + cstr(d.attribute_value) + "
" ) - if attributes_description not in variant.description: + if attributes_description not in (variant.description or ""): variant.description = attributes_description From 0a5fe99d4a33b181fea42cd57aae4f101918bc82 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Thu, 12 Mar 2026 13:18:00 +0530 Subject: [PATCH 13/63] fix: move show_general_ledger to Asset Repair form events (cherry picked from commit ac124bdc7e76ab5df3ead2afab0b88dae5e0a6df) --- .../doctype/asset_repair/asset_repair.js | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index bdbf60f653e..4d9ef28ceae 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -86,6 +86,26 @@ frappe.ui.form.on("Asset Repair", { } }, + show_general_ledger: function (frm) { + if (frm.doc.docstatus > 0) { + frm.add_custom_button( + __("Accounting Ledger"), + function () { + frappe.route_options = { + voucher_no: frm.doc.name, + from_date: frm.doc.completion_date, + to_date: moment(frm.doc.modified).format("YYYY-MM-DD"), + company: frm.doc.company, + categorize_by: "", + show_cancelled_entries: frm.doc.docstatus === 2, + }; + frappe.set_route("query-report", "General Ledger"); + }, + __("View") + ); + } + }, + repair_status: (frm) => { if (frm.doc.completion_date && frm.doc.repair_status == "Completed") { frappe.call({ @@ -164,26 +184,6 @@ frappe.ui.form.on("Asset Repair Purchase Invoice", { }, }); }, - - show_general_ledger: (frm) => { - if (frm.doc.docstatus > 0) { - frm.add_custom_button( - __("Accounting Ledger"), - function () { - frappe.route_options = { - voucher_no: frm.doc.name, - from_date: frm.doc.posting_date, - to_date: moment(frm.doc.modified).format("YYYY-MM-DD"), - company: frm.doc.company, - categorize_by: "", - show_cancelled_entries: frm.doc.docstatus === 2, - }; - frappe.set_route("query-report", "General Ledger"); - }, - __("View") - ); - } - }, }); frappe.ui.form.on("Asset Repair Consumed Item", { From 6660e643bbd4f3677df09a905d1741c1560d982e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 09:14:50 +0000 Subject: [PATCH 14/63] fix: precision issue in production plan (backport #53370) (#53374) Co-authored-by: Mihir Kandoi fix: precision issue in production plan (#53370) --- .../manufacturing/doctype/production_plan/production_plan.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 1819b5363d9..57b626f1fd3 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1912,6 +1912,7 @@ def get_sub_assembly_items( skip_available_sub_assembly_item=False, ): data = get_bom_children(parent=bom_no) + precision = frappe.get_precision("Production Plan Sub Assembly Item", "qty") for d in data: if d.expandable: parent_item_code = frappe.get_cached_value("BOM", bom_no, "item") @@ -1951,8 +1952,8 @@ def get_sub_assembly_items( "is_sub_contracted_item": d.is_sub_contracted_item, "bom_level": indent, "indent": indent, - "stock_qty": stock_qty, - "required_qty": required_qty, + "stock_qty": flt(stock_qty, precision), + "required_qty": flt(required_qty, precision), "projected_qty": bin_details[d.item_code][0].get("projected_qty", 0) if bin_details.get(d.item_code) else 0, From 42cfbae782d62d916a9efb5842e14edb42051309 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 09:27:04 +0000 Subject: [PATCH 15/63] fix: do not modify rate in the child item merely for comparison (backport #53301) (#53376) Co-authored-by: Mihir Kandoi fix: do not modify rate in the child item merely for comparison (#53301) --- .../stock_reconciliation/stock_reconciliation.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 277ab61865b..113d211320c 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -524,11 +524,11 @@ class StockReconciliation(StockController): return True rate_precision = item.precision("valuation_rate") - item_dict["rate"] = flt(item_dict.get("rate"), rate_precision) - item.valuation_rate = flt(item.valuation_rate, rate_precision) if item.valuation_rate else None + rate = flt(item_dict.get("rate"), rate_precision) + valuation_rate = flt(item.valuation_rate, rate_precision) if item.valuation_rate else None if ( (item.qty is None or item.qty == item_dict.get("qty")) - and (item.valuation_rate is None or item.valuation_rate == item_dict.get("rate")) + and (valuation_rate is None or valuation_rate == rate) and (not item.serial_no or (item.serial_no == item_dict.get("serial_nos"))) ): return False @@ -1008,9 +1008,9 @@ class StockReconciliation(StockController): def set_total_qty_and_amount(self): for d in self.get("items"): - d.amount = flt(d.qty, d.precision("qty")) * flt(d.valuation_rate, d.precision("valuation_rate")) - d.current_amount = flt(d.current_qty, d.precision("current_qty")) * flt( - d.current_valuation_rate, d.precision("current_valuation_rate") + d.amount = flt(flt(d.qty) * flt(d.valuation_rate), d.precision("amount")) + d.current_amount = flt( + flt(d.current_qty) * flt(d.current_valuation_rate), d.precision("current_amount") ) d.quantity_difference = flt(d.qty) - flt(d.current_qty) From 4af54a7ac7a1c630bcb5016893fc438f604e8679 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 09:35:19 +0000 Subject: [PATCH 16/63] fix: add validation in bom creator function (backport #53364) (#53368) * fix: add validation in bom creator function (#53364) (cherry picked from commit 9c0c39381f883c18124d44d0d8bd7aad06a12316) # Conflicts: # erpnext/manufacturing/doctype/bom_creator/bom_creator.py * chore: resolve conflicts --------- Co-authored-by: Mihir Kandoi --- erpnext/manufacturing/doctype/bom_creator/bom_creator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index b8d322f9463..e0f92c7db49 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -562,6 +562,9 @@ def delete_node(**kwargs): @frappe.whitelist() def edit_bom_creator(doctype, docname, data, parent): + if not frappe.has_permission(doctype=doctype, ptype="write", parent_doctype="BOM Creator"): + frappe.throw(_("You do not have permission to edit this document"), frappe.PermissionError) + if isinstance(data, str): data = frappe.parse_json(data) From 82eb200c738f42ff358fd21a97bfc0f43ee6331e Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Thu, 12 Mar 2026 14:51:25 +0530 Subject: [PATCH 17/63] fix: use correct filter to get the composite assets (cherry picked from commit f5a32273494a9869cbcd11c1ca21e73039d994f9) --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 933cd0051e7..65a8717047a 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -19,7 +19,7 @@ frappe.ui.form.on("Purchase Receipt", { frm.set_query("wip_composite_asset", "items", function () { return { - filters: { is_composite_asset: 1, docstatus: 0 }, + filters: { asset_type: "Composite Asset", docstatus: 0 }, }; }); From 41bcf96601c7a2a7dc992e8832f21cd9eeb3a40a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:22:22 +0000 Subject: [PATCH 18/63] fix(regional): rename duplicate Customer fields in Italy setup (backport #50921) (#53398) * fix(regional): rename duplicate Customer fields in Italy setup (#50921) Co-authored-by: Claude Co-authored-by: Claude Opus 4.5 Co-authored-by: Claude Opus 4.6 (cherry picked from commit c6efc403cdae696dafcf6fe4f295e7c668af61e2) # Conflicts: # erpnext/patches.txt * chore: resolve conflicts --------- Co-authored-by: Solede Co-authored-by: Mihir Kandoi --- erpnext/patches.txt | 1 + .../rename_italy_customer_name_fields.py | 65 +++++ erpnext/regional/italy/e-invoice.xml | 4 +- erpnext/regional/italy/setup.py | 6 +- erpnext/tests/test_italy_regional_patch.py | 259 ++++++++++++++++++ 5 files changed, 330 insertions(+), 5 deletions(-) create mode 100644 erpnext/patches/v15_0/rename_italy_customer_name_fields.py create mode 100644 erpnext/tests/test_italy_regional_patch.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c80d2a2969b..e3f1fd3f971 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -470,3 +470,4 @@ erpnext.patches.v16_0.complete_onboarding_steps_for_older_sites #2 erpnext.patches.v16_0.migrate_asset_type_checkboxes_to_select erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po erpnext.patches.v16_0.enable_serial_batch_setting +erpnext.patches.v15_0.rename_italy_customer_name_fields diff --git a/erpnext/patches/v15_0/rename_italy_customer_name_fields.py b/erpnext/patches/v15_0/rename_italy_customer_name_fields.py new file mode 100644 index 00000000000..e56adc8765e --- /dev/null +++ b/erpnext/patches/v15_0/rename_italy_customer_name_fields.py @@ -0,0 +1,65 @@ +import frappe + + +def execute(): + """Rename Italy regional custom fields to avoid conflict with standard Customer fields. + + The Italy regional setup created custom fields 'first_name' and 'last_name' on Customer + which conflict with the standard read-only fields that fetch from customer_primary_contact. + This patch renames them to 'italy_customer_first_name' and 'italy_customer_last_name'. + """ + # Check if old fields exist and are the Italy regional ones + old_first_name_exists = frappe.db.exists("Custom Field", "Customer-first_name") + old_last_name_exists = frappe.db.exists("Custom Field", "Customer-last_name") + + is_italy_first_name = False + is_italy_last_name = False + + if old_first_name_exists: + field_doc = frappe.get_doc("Custom Field", "Customer-first_name") + is_italy_first_name = field_doc.depends_on and "customer_type" in field_doc.depends_on + + if old_last_name_exists: + field_doc = frappe.get_doc("Custom Field", "Customer-last_name") + is_italy_last_name = field_doc.depends_on and "customer_type" in field_doc.depends_on + + # If neither field is the Italy regional one, nothing to do + if not is_italy_first_name and not is_italy_last_name: + return + + # Step 1: Delete old Custom Field documents FIRST (to avoid duplicate field validation error) + if is_italy_first_name: + frappe.delete_doc("Custom Field", "Customer-first_name", force=True) + + if is_italy_last_name: + frappe.delete_doc("Custom Field", "Customer-last_name", force=True) + + # Step 2: Create the new fields and sync database schema + from erpnext.regional.italy.setup import make_custom_fields + + make_custom_fields(update=True) + + # Step 3: Migrate data from old columns to new columns (if old columns still exist in DB) + # Note: We do NOT drop the first_name/last_name columns because they are standard fields + # in Customer doctype (Read Only fields that fetch from customer_primary_contact). + # The Italy regional setup incorrectly created Custom Fields with the same names. + # We only migrate the data and leave the standard columns intact. + if is_italy_first_name and frappe.db.has_column("Customer", "first_name"): + frappe.db.sql( + """ + UPDATE `tabCustomer` + SET italy_customer_first_name = first_name + WHERE first_name IS NOT NULL AND first_name != '' + AND (italy_customer_first_name IS NULL OR italy_customer_first_name = '') + """ + ) + + if is_italy_last_name and frappe.db.has_column("Customer", "last_name"): + frappe.db.sql( + """ + UPDATE `tabCustomer` + SET italy_customer_last_name = last_name + WHERE last_name IS NOT NULL AND last_name != '' + AND (italy_customer_last_name IS NULL OR italy_customer_last_name = '') + """ + ) diff --git a/erpnext/regional/italy/e-invoice.xml b/erpnext/regional/italy/e-invoice.xml index 7c436a2b449..ed132d9998c 100644 --- a/erpnext/regional/italy/e-invoice.xml +++ b/erpnext/regional/italy/e-invoice.xml @@ -97,8 +97,8 @@ {%- if doc.customer_data.customer_type == "Individual" %} {{ doc.customer_data.fiscal_code }} - {{ doc.customer_data.first_name }} - {{ doc.customer_data.last_name }} + {{ doc.customer_data.italy_customer_first_name }} + {{ doc.customer_data.italy_customer_last_name }} {%- else %} diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 9f9115ca12d..989f4da2251 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -232,7 +232,7 @@ def make_custom_fields(update=True): depends_on='eval:doc.customer_type=="Company"', ), dict( - fieldname="first_name", + fieldname="italy_customer_first_name", label="First Name", fieldtype="Data", insert_after="salutation", @@ -240,10 +240,10 @@ def make_custom_fields(update=True): depends_on='eval:doc.customer_type!="Company"', ), dict( - fieldname="last_name", + fieldname="italy_customer_last_name", label="Last Name", fieldtype="Data", - insert_after="first_name", + insert_after="italy_customer_first_name", print_hide=1, depends_on='eval:doc.customer_type!="Company"', ), diff --git a/erpnext/tests/test_italy_regional_patch.py b/erpnext/tests/test_italy_regional_patch.py new file mode 100644 index 00000000000..53034716d0e --- /dev/null +++ b/erpnext/tests/test_italy_regional_patch.py @@ -0,0 +1,259 @@ +"""Test for Italy regional patch: rename_italy_customer_name_fields. + +This test is completely DB-based to avoid dependencies on ERPNext test fixtures. +""" + +import unittest + +import frappe +from frappe.utils import now + + +class TestRenameItalyCustomerNameFields(unittest.TestCase): + """Test the patch that renames Italy regional custom fields on Customer.""" + + OLD_FIRST_NAME_FIELD = "Customer-first_name" + OLD_LAST_NAME_FIELD = "Customer-last_name" + NEW_FIRST_NAME_FIELD = "Customer-italy_customer_first_name" + NEW_LAST_NAME_FIELD = "Customer-italy_customer_last_name" + + @classmethod + def setUpClass(cls): + # Connect to the site + if not frappe.db: + frappe.connect() + cls.test_customer_name = "_Test Italy Patch Customer" + + def setUp(self): + """Set up test scenario: create old fields and test customer.""" + self._cleanup_fields() + self._cleanup_test_customer() + self._create_old_custom_fields_direct() + self._add_old_columns_to_db() + self._create_test_customer_direct() + + def tearDown(self): + """Clean up after test.""" + self._cleanup_test_customer() + self._cleanup_fields() + self._drop_old_columns_if_exist() + # Restore new fields from Italy setup + try: + from erpnext.regional.italy.setup import make_custom_fields + + make_custom_fields(update=True) + except (ImportError, AttributeError, ValueError) as e: + # Ignore setup failures in tearDown, but log for debugging + frappe.logger().warning(f"Failed to restore Italy setup in tearDown: {e}") + frappe.db.rollback() + + def _cleanup_fields(self): + """Remove both old and new custom fields.""" + for field_name in [ + self.OLD_FIRST_NAME_FIELD, + self.OLD_LAST_NAME_FIELD, + self.NEW_FIRST_NAME_FIELD, + self.NEW_LAST_NAME_FIELD, + ]: + if frappe.db.exists("Custom Field", field_name): + frappe.db.delete("Custom Field", {"name": field_name}) + + def _cleanup_test_customer(self): + """Remove test customer if exists.""" + if frappe.db.exists("Customer", self.test_customer_name): + # Delete directly from DB to avoid controller validation + frappe.db.delete("Customer", {"name": self.test_customer_name}) + + def _create_old_custom_fields_direct(self): + """Create the old custom fields directly in DB to bypass validation. + + This simulates the legacy state where Italy regional setup created + fields with names that now conflict with standard Customer fields. + """ + current_time = now() + + # Insert old first_name custom field directly + frappe.db.sql( + """ + INSERT INTO `tabCustom Field` + (name, creation, modified, modified_by, owner, docstatus, + dt, fieldname, fieldtype, label, insert_after, depends_on) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + """, + ( + self.OLD_FIRST_NAME_FIELD, + current_time, + current_time, + "Administrator", + "Administrator", + 0, + "Customer", + "first_name", + "Data", + "First Name", + "customer_name", + "eval:doc.customer_type == 'Individual'", + ), + ) + + # Insert old last_name custom field directly + frappe.db.sql( + """ + INSERT INTO `tabCustom Field` + (name, creation, modified, modified_by, owner, docstatus, + dt, fieldname, fieldtype, label, insert_after, depends_on) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + """, + ( + self.OLD_LAST_NAME_FIELD, + current_time, + current_time, + "Administrator", + "Administrator", + 0, + "Customer", + "last_name", + "Data", + "Last Name", + "first_name", + "eval:doc.customer_type == 'Individual'", + ), + ) + + frappe.db.commit() # nosemgrep: frappe-manual-commit -- required after raw SQL INSERT in test setup + + def _add_old_columns_to_db(self): + """Ensure old columns exist in the database table.""" + frappe.clear_cache() # Clear cache to get fresh column info + if not frappe.db.has_column("Customer", "first_name"): + frappe.db.sql_ddl("ALTER TABLE `tabCustomer` ADD COLUMN `first_name` VARCHAR(140)") + if not frappe.db.has_column("Customer", "last_name"): + frappe.db.sql_ddl("ALTER TABLE `tabCustomer` ADD COLUMN `last_name` VARCHAR(140)") + frappe.clear_cache() # Clear cache after adding columns + + def _drop_old_columns_if_exist(self): + """Drop old columns if they still exist.""" + frappe.clear_cache() # Clear cache to get fresh column info + try: + if frappe.db.has_column("Customer", "first_name"): + frappe.db.sql_ddl("ALTER TABLE `tabCustomer` DROP COLUMN `first_name`") + except frappe.db.InternalError as e: + # Column might already be dropped or locked + frappe.logger().debug(f"Could not drop first_name column: {e}") + try: + if frappe.db.has_column("Customer", "last_name"): + frappe.db.sql_ddl("ALTER TABLE `tabCustomer` DROP COLUMN `last_name`") + except frappe.db.InternalError as e: + # Column might already be dropped or locked + frappe.logger().debug(f"Could not drop last_name column: {e}") + frappe.clear_cache() # Clear cache after dropping columns + + def _create_test_customer_direct(self): + """Create a test customer directly in DB to avoid controller dependencies.""" + current_time = now() + + # Insert customer directly into DB + frappe.db.sql( + """ + INSERT INTO `tabCustomer` + (name, creation, modified, modified_by, owner, docstatus, + customer_name, customer_type, first_name, last_name) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + """, + ( + self.test_customer_name, + current_time, + current_time, + "Administrator", + "Administrator", + 0, + self.test_customer_name, + "Individual", + "Mario", + "Rossi", + ), + ) + frappe.db.commit() # nosemgrep: frappe-manual-commit -- required after raw SQL INSERT in test setup + + def test_patch_renames_fields_and_migrates_data(self): + """Test that the patch renames fields and migrates data correctly.""" + # Verify old fields exist before patch + self.assertTrue(frappe.db.exists("Custom Field", self.OLD_FIRST_NAME_FIELD)) + self.assertTrue(frappe.db.exists("Custom Field", self.OLD_LAST_NAME_FIELD)) + + # Verify old data exists + old_first_name = frappe.db.get_value("Customer", self.test_customer_name, "first_name") + old_last_name = frappe.db.get_value("Customer", self.test_customer_name, "last_name") + self.assertEqual(old_first_name, "Mario") + self.assertEqual(old_last_name, "Rossi") + + # Execute the patch + from erpnext.patches.v15_0.rename_italy_customer_name_fields import execute + + execute() + + # Verify old Custom Field documents are deleted + self.assertFalse(frappe.db.exists("Custom Field", self.OLD_FIRST_NAME_FIELD)) + self.assertFalse(frappe.db.exists("Custom Field", self.OLD_LAST_NAME_FIELD)) + + # Verify new Custom Field documents exist + self.assertTrue(frappe.db.exists("Custom Field", self.NEW_FIRST_NAME_FIELD)) + self.assertTrue(frappe.db.exists("Custom Field", self.NEW_LAST_NAME_FIELD)) + + # Verify data was migrated to new columns + new_first_name = frappe.db.get_value("Customer", self.test_customer_name, "italy_customer_first_name") + new_last_name = frappe.db.get_value("Customer", self.test_customer_name, "italy_customer_last_name") + self.assertEqual(new_first_name, "Mario") + self.assertEqual(new_last_name, "Rossi") + + # Note: first_name/last_name columns are NOT dropped because they are + # standard Customer fields (Read Only, fetch from customer_primary_contact) + + def test_patch_skips_non_italy_fields(self): + """Test that the patch skips fields that are not Italy regional fields.""" + # Delete the Italy regional fields created in setUp + self._cleanup_fields() + self._drop_old_columns_if_exist() + self._cleanup_test_customer() + + current_time = now() + + # Create a custom field with same name but without Italy's depends_on + frappe.db.sql( + """ + INSERT INTO `tabCustom Field` + (name, creation, modified, modified_by, owner, docstatus, + dt, fieldname, fieldtype, label, insert_after) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + """, + ( + self.OLD_FIRST_NAME_FIELD, + current_time, + current_time, + "Administrator", + "Administrator", + 0, + "Customer", + "first_name", + "Data", + "First Name", + "customer_name", + ), + ) + frappe.db.commit() # nosemgrep: frappe-manual-commit -- required after raw SQL INSERT in test setup + + # Execute the patch + from erpnext.patches.v15_0.rename_italy_customer_name_fields import execute + + execute() + + # The non-Italy field should still exist (not renamed) + self.assertTrue(frappe.db.exists("Custom Field", self.OLD_FIRST_NAME_FIELD)) + + # Verify new Italy fields were NOT created (since this wasn't an Italy field) + self.assertFalse(frappe.db.exists("Custom Field", self.NEW_FIRST_NAME_FIELD)) + self.assertFalse(frappe.db.exists("Custom Field", self.NEW_LAST_NAME_FIELD)) + + +if __name__ == "__main__": + unittest.main() From abc463bfb86dafe05e0738d3a6befcdd9485f363 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Thu, 12 Mar 2026 16:19:38 +0530 Subject: [PATCH 19/63] refactor: show company currency in purchase amount label (cherry picked from commit b4c82c0f1ae1d0c1cd40312cc42e18eb0690bed7) --- erpnext/assets/doctype/asset/asset.js | 2 ++ erpnext/assets/doctype/asset/asset.json | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 2a7703495bf..508c986d051 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -87,6 +87,8 @@ frappe.ui.form.on("Asset", { }, refresh: async function (frm) { + frm.set_currency_labels(["net_purchase_amount"], erpnext.get_currency(frm.doc.company)); + frappe.ui.form.trigger("Asset", "asset_type"); frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 9fe15940970..c048e972882 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -9,13 +9,13 @@ "engine": "InnoDB", "field_order": [ "naming_series", - "company", "item_code", "item_name", "asset_name", + "location", "image", "column_break_3", - "location", + "company", "asset_category", "asset_type", "maintenance_required", @@ -533,7 +533,7 @@ "fieldtype": "Currency", "label": "Net Purchase Amount", "mandatory_depends_on": "eval:(doc.asset_type != \"Composite Asset\" || doc.docstatus==1)", - "options": "Company:company:default_currency", + "options": "currency", "read_only_depends_on": "eval: doc.asset_type == \"Composite Asset\"" }, { @@ -626,7 +626,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2026-03-09 17:15:32.819896", + "modified": "2026-03-12 16:07:39.543227", "modified_by": "Administrator", "module": "Assets", "name": "Asset", From bb98acb0c07a38d2e13dcd743574fe02d74a067a Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Thu, 12 Mar 2026 17:58:41 +0530 Subject: [PATCH 20/63] fix: update label on company change (cherry picked from commit 6219a9e6f0d5e5cfc7c2759ef14e58963508dd8e) --- erpnext/assets/doctype/asset/asset.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 508c986d051..5a5f22b5788 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -36,6 +36,7 @@ frappe.ui.form.on("Asset", { }, company: function (frm) { + frm.trigger("set_dynamic_labels"); erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); }, @@ -87,7 +88,7 @@ frappe.ui.form.on("Asset", { }, refresh: async function (frm) { - frm.set_currency_labels(["net_purchase_amount"], erpnext.get_currency(frm.doc.company)); + frm.trigger("set_dynamic_labels"); frappe.ui.form.trigger("Asset", "asset_type"); frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); @@ -229,6 +230,10 @@ frappe.ui.form.on("Asset", { } }, + set_dynamic_labels: function (frm) { + frm.set_currency_labels(["net_purchase_amount"], erpnext.get_currency(frm.doc.company)); + }, + should_show_accounting_ledger: async function (frm) { if (["Capitalized"].includes(frm.doc.status)) { return false; From efce145dad454644c2484d13fb355a4c0059f589 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 12 Mar 2026 20:21:18 +0530 Subject: [PATCH 21/63] fix: correctly group RMs of same phantom from different FG (cherry picked from commit e57de4311c98de1b7d97aa6e8744b6981d9c24ea) --- .../doctype/production_plan/production_plan.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 57b626f1fd3..d1153fdddcf 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -2099,16 +2099,19 @@ def get_raw_materials_of_sub_assembly_items( for item in query.run(as_dict=True): key = (item.item_code, item.bom_no) + existing_key = (item.item_code, item.bom_no or item.main_bom) if item.is_phantom_item: + sub_assembly_items.setdefault(key, 0) sub_assembly_items[key] += item.get("qty") - if (item.bom_no and key not in sub_assembly_items) or ( - (item.item_code, item.bom_no or item.main_bom) in existing_sub_assembly_items - ): + if item.bom_no and not item.is_phantom_item and key not in sub_assembly_items: + continue + + if not item.is_phantom_item and existing_key in existing_sub_assembly_items: continue if item.bom_no: - planned_qty = flt(sub_assembly_items[key]) + planned_qty = flt(item.get("qty")) if item.is_phantom_item else flt(sub_assembly_items[key]) get_raw_materials_of_sub_assembly_items( existing_sub_assembly_items, item_details, @@ -2118,7 +2121,8 @@ def get_raw_materials_of_sub_assembly_items( sub_assembly_items, planned_qty=planned_qty, ) - existing_sub_assembly_items.add((item.item_code, item.bom_no or item.main_bom)) + if not item.is_phantom_item: + existing_sub_assembly_items.add(existing_key) else: if not item.conversion_factor and item.purchase_uom: item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom) From f508f4149f1fffb5e02c4e4488b05e2c3844fdd3 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 12 Mar 2026 20:31:56 +0530 Subject: [PATCH 22/63] test: ensure phantom BOM explosion across all items (cherry picked from commit 1975ae4486ff7bc256b10ad76ef7eb47f52035a8) --- .../production_plan/test_production_plan.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index b7787d6489b..08249de49fb 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -2713,6 +2713,92 @@ class TestProductionPlan(IntegrationTestCase): [item.item_code for item in plan.mr_items], ["Item Level 1-3", "Item Level 2-3", "Item Level 3-1"] ) + def test_phantom_bom_explosion_across_multiple_po_items(self): + """ + Regression: when the same phantom item (BOM) is referenced inside sub-assemblies + of two different production plan items, its raw materials must be fully exploded + for *both* plan items. + """ + # Setup items + fg_a = make_item("FG for Cross-PO Phantom Test A") + fg_b = make_item("FG for Cross-PO Phantom Test B") + sa_a = make_item("SA for Cross-PO Phantom Test A") + sa_b = make_item("SA for Cross-PO Phantom Test B") + phantom = make_item("Phantom for Cross-PO Test") + rm = make_item("RM for Cross-PO Phantom Test") + + # Create the shared phantom BOM + phantom_bom = make_bom(item=phantom.name, raw_materials=[rm.name], do_not_save=True) + phantom_bom.is_phantom_bom = 1 + phantom_bom.save() + phantom_bom.submit() + + # Create SA-A BOM with phantom + sa_a_bom = make_bom(item=sa_a.name, raw_materials=[phantom.name], do_not_save=True) + sa_a_bom.items[0].bom_no = phantom_bom.name + sa_a_bom.save() + sa_a_bom.submit() + + # Create SA-B BOM with the SAME phantom + sa_b_bom = make_bom(item=sa_b.name, raw_materials=[phantom.name], do_not_save=True) + sa_b_bom.items[0].bom_no = phantom_bom.name + sa_b_bom.save() + sa_b_bom.submit() + + # Create FG-A BOM with SA-A + fg_a_bom = make_bom(item=fg_a.name, raw_materials=[sa_a.name], do_not_save=True) + fg_a_bom.items[0].bom_no = sa_a_bom.name + fg_a_bom.save() + fg_a_bom.submit() + + # Create FG-B BOM with SA-B + fg_b_bom = make_bom(item=fg_b.name, raw_materials=[sa_b.name], do_not_save=True) + fg_b_bom.items[0].bom_no = sa_b_bom.name + fg_b_bom.save() + fg_b_bom.submit() + + # Build Production Plan with both FGs + plan = frappe.new_doc("Production Plan") + plan.company = "_Test Company" + plan.posting_date = nowdate() + plan.ignore_existing_ordered_qty = 1 + plan.skip_available_sub_assembly_item = 1 + plan.sub_assembly_warehouse = "_Test Warehouse - _TC" + + for fg_item, bom in [(fg_a.name, fg_a_bom.name), (fg_b.name, fg_b_bom.name)]: + plan.append( + "po_items", + { + "use_multi_level_bom": 1, + "item_code": fg_item, + "bom_no": bom, + "planned_qty": 1, + "planned_start_date": now_datetime(), + "stock_uom": "Nos", + }, + ) + + plan.insert() + plan.get_sub_assembly_items() + + # Verify both sub-assemblies are present + sa_items = {row.production_item for row in plan.sub_assembly_items} + self.assertIn(sa_a.name, sa_items) + self.assertIn(sa_b.name, sa_items) + + plan.submit() + + mr_items = get_items_for_material_requests(plan.as_dict()) + + # Phantom raw material should be counted twice (once per FG → SA → shared phantom) + rm_total_qty = sum(flt(d["quantity"]) for d in mr_items if d["item_code"] == rm.name) + self.assertEqual( + rm_total_qty, + 2.0, + f"Expected RM qty=2 (1 per FG via shared phantom BOM), got {rm_total_qty}. " + "The phantom BOM was not re-exploded for the second po_item.", + ) + def create_production_plan(**args): """ From 89b7e11381cfdcc1233f744636a4a76dc0e65d3f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 12 Mar 2026 21:16:03 +0530 Subject: [PATCH 23/63] chore: phantom qty unused in sub_assembly_items (cherry picked from commit b1e1c65774de7d59d7dee5cc5f38259cdf7dd61d) --- .../doctype/production_plan/production_plan.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index d1153fdddcf..2423dbc1b53 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -2100,9 +2100,6 @@ def get_raw_materials_of_sub_assembly_items( for item in query.run(as_dict=True): key = (item.item_code, item.bom_no) existing_key = (item.item_code, item.bom_no or item.main_bom) - if item.is_phantom_item: - sub_assembly_items.setdefault(key, 0) - sub_assembly_items[key] += item.get("qty") if item.bom_no and not item.is_phantom_item and key not in sub_assembly_items: continue @@ -2111,7 +2108,7 @@ def get_raw_materials_of_sub_assembly_items( continue if item.bom_no: - planned_qty = flt(item.get("qty")) if item.is_phantom_item else flt(sub_assembly_items[key]) + recursion_qty = flt(item.get("qty")) if item.is_phantom_item else flt(sub_assembly_items[key]) get_raw_materials_of_sub_assembly_items( existing_sub_assembly_items, item_details, @@ -2119,7 +2116,7 @@ def get_raw_materials_of_sub_assembly_items( item.bom_no, include_non_stock_items, sub_assembly_items, - planned_qty=planned_qty, + planned_qty=recursion_qty, ) if not item.is_phantom_item: existing_sub_assembly_items.add(existing_key) From 41c59ac1653a4b8fea91dcc13442ec04fe4751ba Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 07:48:28 +0000 Subject: [PATCH 24/63] Revert "fix(regional): rename duplicate Customer fields in Italy setup" (backport #53409) (#53411) * Revert "fix(regional): rename duplicate Customer fields in Italy setup" (#53409) (cherry picked from commit bd87a7e612864e5fea2a4178fc92d929d998bb3e) # Conflicts: # erpnext/patches.txt * chore: resolve conflicts --------- Co-authored-by: Mihir Kandoi --- erpnext/patches.txt | 1 - .../rename_italy_customer_name_fields.py | 65 ----- erpnext/regional/italy/e-invoice.xml | 4 +- erpnext/regional/italy/setup.py | 6 +- erpnext/tests/test_italy_regional_patch.py | 259 ------------------ 5 files changed, 5 insertions(+), 330 deletions(-) delete mode 100644 erpnext/patches/v15_0/rename_italy_customer_name_fields.py delete mode 100644 erpnext/tests/test_italy_regional_patch.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e3f1fd3f971..c80d2a2969b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -470,4 +470,3 @@ erpnext.patches.v16_0.complete_onboarding_steps_for_older_sites #2 erpnext.patches.v16_0.migrate_asset_type_checkboxes_to_select erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po erpnext.patches.v16_0.enable_serial_batch_setting -erpnext.patches.v15_0.rename_italy_customer_name_fields diff --git a/erpnext/patches/v15_0/rename_italy_customer_name_fields.py b/erpnext/patches/v15_0/rename_italy_customer_name_fields.py deleted file mode 100644 index e56adc8765e..00000000000 --- a/erpnext/patches/v15_0/rename_italy_customer_name_fields.py +++ /dev/null @@ -1,65 +0,0 @@ -import frappe - - -def execute(): - """Rename Italy regional custom fields to avoid conflict with standard Customer fields. - - The Italy regional setup created custom fields 'first_name' and 'last_name' on Customer - which conflict with the standard read-only fields that fetch from customer_primary_contact. - This patch renames them to 'italy_customer_first_name' and 'italy_customer_last_name'. - """ - # Check if old fields exist and are the Italy regional ones - old_first_name_exists = frappe.db.exists("Custom Field", "Customer-first_name") - old_last_name_exists = frappe.db.exists("Custom Field", "Customer-last_name") - - is_italy_first_name = False - is_italy_last_name = False - - if old_first_name_exists: - field_doc = frappe.get_doc("Custom Field", "Customer-first_name") - is_italy_first_name = field_doc.depends_on and "customer_type" in field_doc.depends_on - - if old_last_name_exists: - field_doc = frappe.get_doc("Custom Field", "Customer-last_name") - is_italy_last_name = field_doc.depends_on and "customer_type" in field_doc.depends_on - - # If neither field is the Italy regional one, nothing to do - if not is_italy_first_name and not is_italy_last_name: - return - - # Step 1: Delete old Custom Field documents FIRST (to avoid duplicate field validation error) - if is_italy_first_name: - frappe.delete_doc("Custom Field", "Customer-first_name", force=True) - - if is_italy_last_name: - frappe.delete_doc("Custom Field", "Customer-last_name", force=True) - - # Step 2: Create the new fields and sync database schema - from erpnext.regional.italy.setup import make_custom_fields - - make_custom_fields(update=True) - - # Step 3: Migrate data from old columns to new columns (if old columns still exist in DB) - # Note: We do NOT drop the first_name/last_name columns because they are standard fields - # in Customer doctype (Read Only fields that fetch from customer_primary_contact). - # The Italy regional setup incorrectly created Custom Fields with the same names. - # We only migrate the data and leave the standard columns intact. - if is_italy_first_name and frappe.db.has_column("Customer", "first_name"): - frappe.db.sql( - """ - UPDATE `tabCustomer` - SET italy_customer_first_name = first_name - WHERE first_name IS NOT NULL AND first_name != '' - AND (italy_customer_first_name IS NULL OR italy_customer_first_name = '') - """ - ) - - if is_italy_last_name and frappe.db.has_column("Customer", "last_name"): - frappe.db.sql( - """ - UPDATE `tabCustomer` - SET italy_customer_last_name = last_name - WHERE last_name IS NOT NULL AND last_name != '' - AND (italy_customer_last_name IS NULL OR italy_customer_last_name = '') - """ - ) diff --git a/erpnext/regional/italy/e-invoice.xml b/erpnext/regional/italy/e-invoice.xml index ed132d9998c..7c436a2b449 100644 --- a/erpnext/regional/italy/e-invoice.xml +++ b/erpnext/regional/italy/e-invoice.xml @@ -97,8 +97,8 @@ {%- if doc.customer_data.customer_type == "Individual" %} {{ doc.customer_data.fiscal_code }} - {{ doc.customer_data.italy_customer_first_name }} - {{ doc.customer_data.italy_customer_last_name }} + {{ doc.customer_data.first_name }} + {{ doc.customer_data.last_name }} {%- else %} diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 989f4da2251..9f9115ca12d 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -232,7 +232,7 @@ def make_custom_fields(update=True): depends_on='eval:doc.customer_type=="Company"', ), dict( - fieldname="italy_customer_first_name", + fieldname="first_name", label="First Name", fieldtype="Data", insert_after="salutation", @@ -240,10 +240,10 @@ def make_custom_fields(update=True): depends_on='eval:doc.customer_type!="Company"', ), dict( - fieldname="italy_customer_last_name", + fieldname="last_name", label="Last Name", fieldtype="Data", - insert_after="italy_customer_first_name", + insert_after="first_name", print_hide=1, depends_on='eval:doc.customer_type!="Company"', ), diff --git a/erpnext/tests/test_italy_regional_patch.py b/erpnext/tests/test_italy_regional_patch.py deleted file mode 100644 index 53034716d0e..00000000000 --- a/erpnext/tests/test_italy_regional_patch.py +++ /dev/null @@ -1,259 +0,0 @@ -"""Test for Italy regional patch: rename_italy_customer_name_fields. - -This test is completely DB-based to avoid dependencies on ERPNext test fixtures. -""" - -import unittest - -import frappe -from frappe.utils import now - - -class TestRenameItalyCustomerNameFields(unittest.TestCase): - """Test the patch that renames Italy regional custom fields on Customer.""" - - OLD_FIRST_NAME_FIELD = "Customer-first_name" - OLD_LAST_NAME_FIELD = "Customer-last_name" - NEW_FIRST_NAME_FIELD = "Customer-italy_customer_first_name" - NEW_LAST_NAME_FIELD = "Customer-italy_customer_last_name" - - @classmethod - def setUpClass(cls): - # Connect to the site - if not frappe.db: - frappe.connect() - cls.test_customer_name = "_Test Italy Patch Customer" - - def setUp(self): - """Set up test scenario: create old fields and test customer.""" - self._cleanup_fields() - self._cleanup_test_customer() - self._create_old_custom_fields_direct() - self._add_old_columns_to_db() - self._create_test_customer_direct() - - def tearDown(self): - """Clean up after test.""" - self._cleanup_test_customer() - self._cleanup_fields() - self._drop_old_columns_if_exist() - # Restore new fields from Italy setup - try: - from erpnext.regional.italy.setup import make_custom_fields - - make_custom_fields(update=True) - except (ImportError, AttributeError, ValueError) as e: - # Ignore setup failures in tearDown, but log for debugging - frappe.logger().warning(f"Failed to restore Italy setup in tearDown: {e}") - frappe.db.rollback() - - def _cleanup_fields(self): - """Remove both old and new custom fields.""" - for field_name in [ - self.OLD_FIRST_NAME_FIELD, - self.OLD_LAST_NAME_FIELD, - self.NEW_FIRST_NAME_FIELD, - self.NEW_LAST_NAME_FIELD, - ]: - if frappe.db.exists("Custom Field", field_name): - frappe.db.delete("Custom Field", {"name": field_name}) - - def _cleanup_test_customer(self): - """Remove test customer if exists.""" - if frappe.db.exists("Customer", self.test_customer_name): - # Delete directly from DB to avoid controller validation - frappe.db.delete("Customer", {"name": self.test_customer_name}) - - def _create_old_custom_fields_direct(self): - """Create the old custom fields directly in DB to bypass validation. - - This simulates the legacy state where Italy regional setup created - fields with names that now conflict with standard Customer fields. - """ - current_time = now() - - # Insert old first_name custom field directly - frappe.db.sql( - """ - INSERT INTO `tabCustom Field` - (name, creation, modified, modified_by, owner, docstatus, - dt, fieldname, fieldtype, label, insert_after, depends_on) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, - ( - self.OLD_FIRST_NAME_FIELD, - current_time, - current_time, - "Administrator", - "Administrator", - 0, - "Customer", - "first_name", - "Data", - "First Name", - "customer_name", - "eval:doc.customer_type == 'Individual'", - ), - ) - - # Insert old last_name custom field directly - frappe.db.sql( - """ - INSERT INTO `tabCustom Field` - (name, creation, modified, modified_by, owner, docstatus, - dt, fieldname, fieldtype, label, insert_after, depends_on) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, - ( - self.OLD_LAST_NAME_FIELD, - current_time, - current_time, - "Administrator", - "Administrator", - 0, - "Customer", - "last_name", - "Data", - "Last Name", - "first_name", - "eval:doc.customer_type == 'Individual'", - ), - ) - - frappe.db.commit() # nosemgrep: frappe-manual-commit -- required after raw SQL INSERT in test setup - - def _add_old_columns_to_db(self): - """Ensure old columns exist in the database table.""" - frappe.clear_cache() # Clear cache to get fresh column info - if not frappe.db.has_column("Customer", "first_name"): - frappe.db.sql_ddl("ALTER TABLE `tabCustomer` ADD COLUMN `first_name` VARCHAR(140)") - if not frappe.db.has_column("Customer", "last_name"): - frappe.db.sql_ddl("ALTER TABLE `tabCustomer` ADD COLUMN `last_name` VARCHAR(140)") - frappe.clear_cache() # Clear cache after adding columns - - def _drop_old_columns_if_exist(self): - """Drop old columns if they still exist.""" - frappe.clear_cache() # Clear cache to get fresh column info - try: - if frappe.db.has_column("Customer", "first_name"): - frappe.db.sql_ddl("ALTER TABLE `tabCustomer` DROP COLUMN `first_name`") - except frappe.db.InternalError as e: - # Column might already be dropped or locked - frappe.logger().debug(f"Could not drop first_name column: {e}") - try: - if frappe.db.has_column("Customer", "last_name"): - frappe.db.sql_ddl("ALTER TABLE `tabCustomer` DROP COLUMN `last_name`") - except frappe.db.InternalError as e: - # Column might already be dropped or locked - frappe.logger().debug(f"Could not drop last_name column: {e}") - frappe.clear_cache() # Clear cache after dropping columns - - def _create_test_customer_direct(self): - """Create a test customer directly in DB to avoid controller dependencies.""" - current_time = now() - - # Insert customer directly into DB - frappe.db.sql( - """ - INSERT INTO `tabCustomer` - (name, creation, modified, modified_by, owner, docstatus, - customer_name, customer_type, first_name, last_name) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, - ( - self.test_customer_name, - current_time, - current_time, - "Administrator", - "Administrator", - 0, - self.test_customer_name, - "Individual", - "Mario", - "Rossi", - ), - ) - frappe.db.commit() # nosemgrep: frappe-manual-commit -- required after raw SQL INSERT in test setup - - def test_patch_renames_fields_and_migrates_data(self): - """Test that the patch renames fields and migrates data correctly.""" - # Verify old fields exist before patch - self.assertTrue(frappe.db.exists("Custom Field", self.OLD_FIRST_NAME_FIELD)) - self.assertTrue(frappe.db.exists("Custom Field", self.OLD_LAST_NAME_FIELD)) - - # Verify old data exists - old_first_name = frappe.db.get_value("Customer", self.test_customer_name, "first_name") - old_last_name = frappe.db.get_value("Customer", self.test_customer_name, "last_name") - self.assertEqual(old_first_name, "Mario") - self.assertEqual(old_last_name, "Rossi") - - # Execute the patch - from erpnext.patches.v15_0.rename_italy_customer_name_fields import execute - - execute() - - # Verify old Custom Field documents are deleted - self.assertFalse(frappe.db.exists("Custom Field", self.OLD_FIRST_NAME_FIELD)) - self.assertFalse(frappe.db.exists("Custom Field", self.OLD_LAST_NAME_FIELD)) - - # Verify new Custom Field documents exist - self.assertTrue(frappe.db.exists("Custom Field", self.NEW_FIRST_NAME_FIELD)) - self.assertTrue(frappe.db.exists("Custom Field", self.NEW_LAST_NAME_FIELD)) - - # Verify data was migrated to new columns - new_first_name = frappe.db.get_value("Customer", self.test_customer_name, "italy_customer_first_name") - new_last_name = frappe.db.get_value("Customer", self.test_customer_name, "italy_customer_last_name") - self.assertEqual(new_first_name, "Mario") - self.assertEqual(new_last_name, "Rossi") - - # Note: first_name/last_name columns are NOT dropped because they are - # standard Customer fields (Read Only, fetch from customer_primary_contact) - - def test_patch_skips_non_italy_fields(self): - """Test that the patch skips fields that are not Italy regional fields.""" - # Delete the Italy regional fields created in setUp - self._cleanup_fields() - self._drop_old_columns_if_exist() - self._cleanup_test_customer() - - current_time = now() - - # Create a custom field with same name but without Italy's depends_on - frappe.db.sql( - """ - INSERT INTO `tabCustom Field` - (name, creation, modified, modified_by, owner, docstatus, - dt, fieldname, fieldtype, label, insert_after) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, - ( - self.OLD_FIRST_NAME_FIELD, - current_time, - current_time, - "Administrator", - "Administrator", - 0, - "Customer", - "first_name", - "Data", - "First Name", - "customer_name", - ), - ) - frappe.db.commit() # nosemgrep: frappe-manual-commit -- required after raw SQL INSERT in test setup - - # Execute the patch - from erpnext.patches.v15_0.rename_italy_customer_name_fields import execute - - execute() - - # The non-Italy field should still exist (not renamed) - self.assertTrue(frappe.db.exists("Custom Field", self.OLD_FIRST_NAME_FIELD)) - - # Verify new Italy fields were NOT created (since this wasn't an Italy field) - self.assertFalse(frappe.db.exists("Custom Field", self.NEW_FIRST_NAME_FIELD)) - self.assertFalse(frappe.db.exists("Custom Field", self.NEW_LAST_NAME_FIELD)) - - -if __name__ == "__main__": - unittest.main() From c8afc04957e2eed5be11389dfa5de3c75ed2d0b8 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Fri, 13 Mar 2026 14:37:11 +0530 Subject: [PATCH 25/63] feat: show subsidiary companies value in purchase analytics (cherry picked from commit 9f755ad65acb91d20082fb452af0373ed365c21b) --- .../buying/report/purchase_analytics/purchase_analytics.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/buying/report/purchase_analytics/purchase_analytics.js b/erpnext/buying/report/purchase_analytics/purchase_analytics.js index 23a188057d9..b66c1c429d0 100644 --- a/erpnext/buying/report/purchase_analytics/purchase_analytics.js +++ b/erpnext/buying/report/purchase_analytics/purchase_analytics.js @@ -65,6 +65,11 @@ frappe.query_reports["Purchase Analytics"] = { default: "Monthly", reqd: 1, }, + { + fieldname: "show_aggregate_value_from_subsidiary_companies", + label: __("Show Aggregate Value from Subsidiary Companies"), + fieldtype: "Check", + }, ], get_datatable_options(options) { return Object.assign(options, { From 60a9c08f0dccc30675ee8795eff97c6279215728 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 13 Mar 2026 14:36:00 +0530 Subject: [PATCH 26/63] fix: broke cost center filter in get outstanding reference docs (cherry picked from commit 7dfe36fdce9c84e0c6baf54c7f0eff009223dc8d) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index c68672466c2..289c19a5885 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -2556,14 +2556,9 @@ def get_orders_to_be_billed( if not voucher_type: return [] - # Add cost center condition - doc = frappe.get_doc({"doctype": voucher_type}) - condition = "" - if doc and hasattr(doc, "cost_center") and doc.cost_center: - condition = " and cost_center='%s'" % cost_center - # dynamic dimension filters - active_dimensions = get_dimensions()[0] + condition = "" + active_dimensions = get_dimensions(True)[0] for dim in active_dimensions: if filters.get(dim.fieldname): condition += f" and {dim.fieldname}='{filters.get(dim.fieldname)}'" From 5fac73c92091e1edefd6c206cb87a5dbdaff7044 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 13 Mar 2026 13:53:41 +0530 Subject: [PATCH 27/63] refactor: disable total row in trends report (cherry picked from commit 4dbc72b30179cd9f71193fcd90de78373b8924d9) --- .../purchase_invoice_trends/purchase_invoice_trends.json | 6 +++--- .../report/sales_invoice_trends/sales_invoice_trends.json | 4 ++-- .../report/purchase_order_trends/purchase_order_trends.json | 4 ++-- .../selling/report/quotation_trends/quotation_trends.json | 4 ++-- .../report/sales_order_trends/sales_order_trends.json | 4 ++-- .../report/delivery_note_trends/delivery_note_trends.json | 4 ++-- .../purchase_receipt_trends/purchase_receipt_trends.json | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.json b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.json index 2080f51933a..37556b6b4c2 100644 --- a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.json +++ b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.json @@ -7,10 +7,10 @@ "docstatus": 0, "doctype": "Report", "filters": [], - "idx": 3, + "idx": 4, "is_standard": "Yes", - "letterhead": null, - "modified": "2025-11-05 11:55:49.950442", + "letter_head": null, + "modified": "2026-03-13 17:35:39.703838", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Trends", diff --git a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.json b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.json index 1ed34ff4c36..93aa6567f0c 100644 --- a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.json +++ b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.json @@ -9,8 +9,8 @@ "filters": [], "idx": 3, "is_standard": "Yes", - "letterhead": null, - "modified": "2025-11-05 11:55:50.070651", + "letter_head": null, + "modified": "2026-03-13 17:36:13.725601", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Trends", diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.json b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.json index 0047d6ecbe5..e53b8e6d669 100644 --- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.json +++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.json @@ -9,8 +9,8 @@ "filters": [], "idx": 3, "is_standard": "Yes", - "letterhead": null, - "modified": "2025-11-05 11:55:50.058154", + "letter_head": null, + "modified": "2026-03-13 17:36:05.561765", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Trends", diff --git a/erpnext/selling/report/quotation_trends/quotation_trends.json b/erpnext/selling/report/quotation_trends/quotation_trends.json index a4011db4041..8722bf61fd7 100644 --- a/erpnext/selling/report/quotation_trends/quotation_trends.json +++ b/erpnext/selling/report/quotation_trends/quotation_trends.json @@ -9,8 +9,8 @@ "filters": [], "idx": 3, "is_standard": "Yes", - "letterhead": null, - "modified": "2025-11-05 11:55:50.127020", + "letter_head": null, + "modified": "2026-03-13 17:36:37.619715", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Trends", diff --git a/erpnext/selling/report/sales_order_trends/sales_order_trends.json b/erpnext/selling/report/sales_order_trends/sales_order_trends.json index dedec06bcf9..26758f5ab3f 100644 --- a/erpnext/selling/report/sales_order_trends/sales_order_trends.json +++ b/erpnext/selling/report/sales_order_trends/sales_order_trends.json @@ -9,8 +9,8 @@ "filters": [], "idx": 3, "is_standard": "Yes", - "letterhead": null, - "modified": "2025-11-05 11:55:50.096303", + "letter_head": null, + "modified": "2026-03-13 17:36:21.440118", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Trends", diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.json b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.json index cef82c5912d..b4da5b466c3 100644 --- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.json +++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.json @@ -9,8 +9,8 @@ "filters": [], "idx": 3, "is_standard": "Yes", - "letterhead": null, - "modified": "2025-11-05 11:55:50.114173", + "letter_head": null, + "modified": "2026-03-13 17:36:31.552712", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Trends", diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.json b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.json index 03c2a09f3bb..6743b359e30 100644 --- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.json +++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.json @@ -9,8 +9,8 @@ "filters": [], "idx": 3, "is_standard": "Yes", - "letterhead": null, - "modified": "2025-11-05 11:55:49.983683", + "letter_head": null, + "modified": "2026-03-13 17:35:57.060786", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Trends", From 6ca5f355bf8d493541729f7a1b573170fd9a54b1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 05:58:04 +0000 Subject: [PATCH 28/63] feat(stock): implement fallback logic for Delivery Trip address mapping (backport #53260) (#53444) --- erpnext/stock/doctype/delivery_note/delivery_note.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 64b9005a3da..e0154bf13cc 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -982,6 +982,11 @@ def make_sales_invoice(source_name, target_doc=None, args=None): def make_delivery_trip(source_name, target_doc=None, kwargs=None): if not target_doc: target_doc = frappe.new_doc("Delivery Trip") + + def update_address(source_doc, target_doc, source_parent): + target_doc.address = source_doc.shipping_address_name or source_doc.customer_address + target_doc.customer_address = source_doc.shipping_address or source_doc.address_display + doclist = get_mapped_doc( "Delivery Note", source_name, @@ -991,11 +996,10 @@ def make_delivery_trip(source_name, target_doc=None, kwargs=None): "on_parent": target_doc, "field_map": { "name": "delivery_note", - "shipping_address_name": "address", - "shipping_address": "customer_address", "contact_person": "contact", "contact_display": "customer_contact", }, + "postprocess": update_address, }, }, ignore_child_tables=True, From ab8fe146abd20532275c2e6a236421410a9f8f17 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 15 Mar 2026 09:45:54 +0530 Subject: [PATCH 29/63] =?UTF-8?q?fix:=20sales=20order=20indicator=20should?= =?UTF-8?q?=20be=20based=20on=20available=20qty=20rather=20th=E2=80=A6=20(?= =?UTF-8?q?backport=20#53456)=20(#53458)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- erpnext/selling/doctype/sales_order/sales_order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 5ec2418df1f..ff22b0e4c2e 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -26,7 +26,7 @@ frappe.ui.form.on("Sales Order", { let color; if (!doc.qty && frm.doc.has_unit_price_items) { color = "yellow"; - } else if (doc.stock_qty <= doc.delivered_qty) { + } else if (doc.stock_qty <= doc.actual_qty) { color = "green"; } else { color = "orange"; From 3b2ae7ba4f0d34da35d9a502ae020746ac691a31 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 15 Mar 2026 07:44:59 +0000 Subject: [PATCH 30/63] fix(serial_and_batch_bundle_selector): handle CSV attachment properly (backport #53460) (#53462) Co-authored-by: diptanilsaha fix(serial_and_batch_bundle_selector): handle CSV attachment properly (#53460) --- .../serial_and_batch_bundle.py | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index c987a364ade..76031397b01 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1714,17 +1714,34 @@ def upload_csv_file(item_code, file_path): def get_serial_batch_from_csv(item_code, file_path): - if "private" in file_path: - file_path = frappe.get_site_path() + file_path - else: - file_path = frappe.get_site_path() + "/public" + file_path + from frappe.utils.csvutils import read_csv_content serial_nos = [] batch_nos = [] - with open(file_path) as f: - reader = csv.reader(f) - serial_nos, batch_nos = parse_csv_file_to_get_serial_batch(reader) + if not file_path: + return serial_nos, batch_nos + + try: + file = frappe.get_doc("File", {"file_url": file_path}) + except frappe.DoesNotExistError: + frappe.msgprint( + _("File '{0}' not found").format(frappe.bold(file_path)), + alert=True, + indicator="red", + raise_exception=FileNotFoundError, + ) + + if file.file_type != "CSV": + frappe.msgprint( + _("{0} is not a CSV file.").format(frappe.bold(file.file_name)), + alert=True, + indicator="red", + raise_exception=frappe.ValidationError, + ) + + csv_data = read_csv_content(file.get_content()) + serial_nos, batch_nos = parse_csv_file_to_get_serial_batch(csv_data) if serial_nos: make_serial_nos(item_code, serial_nos) @@ -2808,7 +2825,7 @@ def get_auto_batch_nos(kwargs): ) if kwargs.based_on == "Expiry": - available_batches = sorted(available_batches, key=lambda x: (x.expiry_date or getdate("9999-12-31"))) + available_batches = sorted(available_batches, key=lambda x: x.expiry_date or getdate("9999-12-31")) if not kwargs.get("do_not_check_future_batches") and available_batches and kwargs.get("posting_datetime"): filter_zero_near_batches(available_batches, kwargs) From a5e92785b7aaec541301bc779fe042e50fbb704c Mon Sep 17 00:00:00 2001 From: MochaMind Date: Sun, 15 Mar 2026 19:02:05 +0530 Subject: [PATCH 31/63] chore: update POT file (#53465) --- erpnext/locale/main.pot | 599 ++++++++++++++++++++-------------------- 1 file changed, 304 insertions(+), 295 deletions(-) diff --git a/erpnext/locale/main.pot b/erpnext/locale/main.pot index 2016b23d789..1361fc78c39 100644 --- a/erpnext/locale/main.pot +++ b/erpnext/locale/main.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: ERPNext VERSION\n" "Report-Msgid-Bugs-To: hello@frappe.io\n" -"POT-Creation-Date: 2026-03-08 09:42+0000\n" -"PO-Revision-Date: 2026-03-08 09:42+0000\n" +"POT-Creation-Date: 2026-03-15 09:45+0000\n" +"PO-Revision-Date: 2026-03-15 09:45+0000\n" "Last-Translator: hello@frappe.io\n" "Language-Team: hello@frappe.io\n" "MIME-Version: 1.0\n" @@ -157,7 +157,7 @@ msgstr "" msgid "% Delivered" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:992 +#: erpnext/manufacturing/doctype/bom/bom.js:999 #, python-format msgid "% Finished Item Quantity" msgstr "" @@ -282,7 +282,7 @@ msgstr "" msgid "'Default {0} Account' in Company {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1220 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1226 msgid "'Entries' cannot be empty" msgstr "" @@ -1004,7 +1004,7 @@ msgstr "" msgid "A Reconciliation Job {0} is running for the same filters. Cannot reconcile now" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1754 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1760 msgid "A Reverse Journal Entry {0} already exists for this Journal Entry." msgstr "" @@ -1536,7 +1536,7 @@ msgstr "" msgid "Account {0} is invalid. Account Currency must be {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:347 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:353 msgid "Account {0} should be of type Expense" msgstr "" @@ -1560,7 +1560,7 @@ msgstr "" msgid "Account: {0} is capital Work in progress and can not be updated by Journal Entry" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:362 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:368 msgid "Account: {0} can only be updated via Stock Transactions" msgstr "" @@ -1866,15 +1866,15 @@ msgstr "" msgid "Accounting Entry for Service" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1015 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1036 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1054 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1075 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1096 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1124 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1231 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1467 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1489 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1016 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1037 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1055 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1076 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1097 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1125 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1232 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1468 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1490 #: erpnext/controllers/stock_controller.py:727 #: erpnext/controllers/stock_controller.py:744 #: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:930 @@ -1893,8 +1893,8 @@ msgid "Accounting Entry for {0}: {1} can only be made in currency: {2}" msgstr "" #: erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js:193 -#: erpnext/assets/doctype/asset/asset.js:182 -#: erpnext/assets/doctype/asset_repair/asset_repair.js:171 +#: erpnext/assets/doctype/asset/asset.js:185 +#: erpnext/assets/doctype/asset_repair/asset_repair.js:92 #: erpnext/buying/doctype/supplier/supplier.js:98 #: erpnext/public/js/controllers/stock_controller.js:88 #: erpnext/public/js/utils/ledger_preview.js:8 @@ -2083,7 +2083,7 @@ msgstr "" msgid "Accounts Setup" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1319 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1325 msgid "Accounts table cannot be blank." msgstr "" @@ -2117,13 +2117,13 @@ msgstr "" #. Label of the accumulated_depreciation_amount (Currency) field in DocType #. 'Depreciation Schedule' #: erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py:178 -#: erpnext/assets/doctype/asset/asset.js:373 +#: erpnext/assets/doctype/asset/asset.js:380 #: erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json msgid "Accumulated Depreciation Amount" msgstr "" -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:635 -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:653 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:876 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:894 msgid "Accumulated Depreciation as on" msgstr "" @@ -2609,7 +2609,7 @@ msgid "Add Quote" msgstr "" #. Label of the add_raw_materials (Button) field in DocType 'BOM Operation' -#: erpnext/manufacturing/doctype/bom/bom.js:1020 +#: erpnext/manufacturing/doctype/bom/bom.js:1027 #: erpnext/manufacturing/doctype/bom_operation/bom_operation.json msgid "Add Raw Materials" msgstr "" @@ -2670,7 +2670,7 @@ msgstr "" msgid "Add Sub Assembly" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:520 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:516 #: erpnext/public/js/event.js:32 msgid "Add Suppliers" msgstr "" @@ -2965,7 +2965,7 @@ msgstr "" msgid "Additional Information updated successfully." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:784 +#: erpnext/manufacturing/doctype/work_order/work_order.js:811 msgid "Additional Material Transfer" msgstr "" @@ -3270,7 +3270,7 @@ msgstr "" msgid "Advance amount cannot be greater than {0} {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:867 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:873 msgid "Advance paid against {0} {1} cannot be greater than Grand Total {2}" msgstr "" @@ -3398,7 +3398,7 @@ msgstr "" msgid "Against Income Account" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:729 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:735 #: erpnext/accounts/doctype/payment_entry/payment_entry.py:777 msgid "Against Journal Entry {0} does not have any unmatched {1} entry" msgstr "" @@ -3749,7 +3749,7 @@ msgstr "" msgid "All items have already been Invoiced/Returned" msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:1216 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:1230 msgid "All items have already been received" msgstr "" @@ -3779,11 +3779,11 @@ msgstr "" msgid "All the items have been already returned." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1168 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1195 msgid "All the required items (raw materials) will be fetched from BOM and populated in this table. Here you can also change the Source Warehouse for any item. And during the production, you can track transferred raw materials from this table." msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:866 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:870 msgid "All these items have already been Invoiced/Returned" msgstr "" @@ -4335,7 +4335,7 @@ msgstr "" #: erpnext/manufacturing/doctype/bom/bom.js:250 #: erpnext/manufacturing/doctype/work_order/work_order.js:165 #: erpnext/manufacturing/doctype/work_order/work_order.js:180 -#: erpnext/public/js/utils.js:561 +#: erpnext/public/js/utils.js:567 #: erpnext/stock/doctype/stock_entry/stock_entry.js:288 msgid "Alternate Item" msgstr "" @@ -4764,7 +4764,7 @@ msgstr "" msgid "Analytical Accounting" msgstr "" -#: erpnext/public/js/utils.js:158 +#: erpnext/public/js/utils.js:164 msgid "Annual Billing: {0}" msgstr "" @@ -5286,7 +5286,7 @@ msgstr "" #: erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.js:30 #: erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py:141 #: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.js:44 -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:581 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:822 #: erpnext/assets/doctype/asset/asset.json #: erpnext/assets/doctype/asset_activity/asset_activity.json #: erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json @@ -5358,7 +5358,7 @@ msgstr "" #: erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.js:36 #: erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py:197 #: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.js:37 -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:571 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:812 #: erpnext/assets/doctype/asset/asset.json #: erpnext/assets/doctype/asset_category/asset_category.json #: erpnext/assets/doctype/asset_maintenance/asset_maintenance.json @@ -5528,7 +5528,7 @@ msgstr "" #. Label of the asset_name (Data) field in DocType 'Asset Movement Item' #. Label of the asset_name (Read Only) field in DocType 'Asset Repair' #: erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py:148 -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:590 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:831 #: erpnext/assets/doctype/asset/asset.json #: erpnext/assets/doctype/asset_capitalization/asset_capitalization.json #: erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json @@ -5576,7 +5576,7 @@ msgstr "" #. Batch Bundle' #. Label of the asset_repair (Link) field in DocType 'Stock Entry' #. Label of a Workspace Sidebar Item -#: erpnext/assets/doctype/asset/asset.js:105 +#: erpnext/assets/doctype/asset/asset.js:108 #: erpnext/assets/doctype/asset_repair/asset_repair.json #: erpnext/assets/workspace/assets/assets.json #: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json @@ -5628,7 +5628,7 @@ msgstr "" #. Label of the asset_value (Currency) field in DocType 'Asset Capitalization #. Asset Item' #: erpnext/assets/dashboard_fixtures.py:180 -#: erpnext/assets/doctype/asset/asset.js:505 +#: erpnext/assets/doctype/asset/asset.js:512 #: erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json #: erpnext/assets/report/fixed_asset_register/fixed_asset_register.py:209 #: erpnext/assets/report/fixed_asset_register/fixed_asset_register.py:460 @@ -5639,7 +5639,7 @@ msgstr "" #. Name of a DocType #. Label of a Link in the Assets Workspace #. Label of a Workspace Sidebar Item -#: erpnext/assets/doctype/asset/asset.js:97 +#: erpnext/assets/doctype/asset/asset.js:100 #: erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json #: erpnext/assets/workspace/assets/assets.json #: erpnext/workspace_sidebar/assets.json @@ -6313,7 +6313,7 @@ msgstr "" #. 'Pick List Item' #: erpnext/manufacturing/doctype/workstation/workstation.js:505 #: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py:88 -#: erpnext/public/js/utils.js:621 +#: erpnext/public/js/utils.js:627 #: erpnext/stock/doctype/delivery_note_item/delivery_note_item.json #: erpnext/stock/doctype/pick_list_item/pick_list_item.json #: erpnext/stock/report/stock_ageing/stock_ageing.py:169 @@ -7593,7 +7593,7 @@ msgstr "" msgid "Batch No is mandatory" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3351 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3368 msgid "Batch No {0} does not exists" msgstr "" @@ -7616,11 +7616,11 @@ msgstr "" msgid "Batch Nos" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1916 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1933 msgid "Batch Nos are created successfully" msgstr "" -#: erpnext/controllers/sales_and_purchase_return.py:1194 +#: erpnext/controllers/sales_and_purchase_return.py:1193 msgid "Batch Not Available for Return" msgstr "" @@ -7678,7 +7678,7 @@ msgstr "" msgid "Batch {0} and Warehouse" msgstr "" -#: erpnext/controllers/sales_and_purchase_return.py:1193 +#: erpnext/controllers/sales_and_purchase_return.py:1192 msgid "Batch {0} is not available in warehouse {1}" msgstr "" @@ -8928,7 +8928,7 @@ msgstr "" msgid "Can not filter based on Voucher No, if grouped by Voucher" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1378 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1384 #: erpnext/accounts/doctype/payment_entry/payment_entry.py:2879 msgid "Can only make payment against unbilled {0}" msgstr "" @@ -9051,7 +9051,7 @@ msgstr "" msgid "Cannot cancel this Manufacturing Stock Entry as quantity of Finished Good produced cannot be less than quantity delivered in the linked Subcontracting Inward Order." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:569 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:575 msgid "Cannot cancel this document as it is linked with the submitted Asset Value Adjustment {0}. Please cancel the Asset Value Adjustment to continue." msgstr "" @@ -9343,7 +9343,7 @@ msgstr "" msgid "Capital Work in Progress" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:220 +#: erpnext/assets/doctype/asset/asset.js:223 msgid "Capitalize Asset" msgstr "" @@ -9352,7 +9352,7 @@ msgstr "" msgid "Capitalize Repair Cost" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:218 +#: erpnext/assets/doctype/asset/asset.js:221 msgid "Capitalize this asset before submitting." msgstr "" @@ -11246,7 +11246,7 @@ msgstr "" msgid "Consider Minimum Order Qty" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:990 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1017 msgid "Consider Process Loss" msgstr "" @@ -11747,7 +11747,7 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json #: erpnext/manufacturing/doctype/bom_item/bom_item.json #: erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json -#: erpnext/public/js/utils.js:876 +#: erpnext/public/js/utils.js:882 #: erpnext/selling/doctype/delivery_schedule_item/delivery_schedule_item.json #: erpnext/stock/doctype/packed_item/packed_item.json #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -12097,7 +12097,7 @@ msgstr "" msgid "Cost Center is a part of Cost Center Allocation, hence cannot be converted to a group" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1432 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1433 #: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:896 msgid "Cost Center is required in row {0} in Taxes table for type {1}" msgstr "" @@ -12247,7 +12247,7 @@ msgstr "" msgid "Could not auto create Customer due to the following missing mandatory field(s):" msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:688 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:692 msgid "Could not create Credit Note automatically, please uncheck 'Issue Credit Note' and submit again" msgstr "" @@ -12527,7 +12527,7 @@ msgstr "" msgid "Create Payment Request" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:766 +#: erpnext/manufacturing/doctype/work_order/work_order.js:793 msgid "Create Pick List" msgstr "" @@ -12658,7 +12658,7 @@ msgstr "" msgid "Create Supplier" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:184 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:180 msgid "Create Supplier Quotation" msgstr "" @@ -13047,7 +13047,7 @@ msgstr "" msgid "Credit Note will update it's own outstanding amount, even if 'Return Against' is specified." msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:685 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:689 msgid "Credit Note {0} has been created automatically" msgstr "" @@ -13325,11 +13325,6 @@ msgstr "" msgid "Current Exchange Rate" msgstr "" -#. Label of the current_index (Int) field in DocType 'Repost Item Valuation' -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json -msgid "Current Index" -msgstr "" - #. Label of the current_invoice_end (Date) field in DocType 'Subscription' #: erpnext/accounts/doctype/subscription/subscription.json msgid "Current Invoice End Date" @@ -14086,7 +14081,7 @@ msgstr "" #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1145 #: erpnext/selling/doctype/sales_order/sales_order.py:434 -#: erpnext/stock/doctype/delivery_note/delivery_note.py:432 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:436 msgid "Customer {0} does not belong to project {1}" msgstr "" @@ -15427,7 +15422,7 @@ msgstr "" #: erpnext/manufacturing/doctype/master_production_schedule_item/master_production_schedule_item.json #: erpnext/manufacturing/doctype/sales_forecast_item/sales_forecast_item.json #: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1067 -#: erpnext/public/js/utils.js:869 +#: erpnext/public/js/utils.js:875 #: erpnext/selling/doctype/delivery_schedule_item/delivery_schedule_item.json #: erpnext/selling/doctype/sales_order/sales_order.js:624 #: erpnext/selling/doctype/sales_order/sales_order.js:1486 @@ -15735,12 +15730,12 @@ msgstr "" #. Label of the depreciation_amount (Currency) field in DocType 'Depreciation #. Schedule' #: erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py:172 -#: erpnext/assets/doctype/asset/asset.js:372 +#: erpnext/assets/doctype/asset/asset.js:379 #: erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json msgid "Depreciation Amount" msgstr "" -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:641 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:882 msgid "Depreciation Amount during the period" msgstr "" @@ -15756,7 +15751,7 @@ msgstr "" msgid "Depreciation Details" msgstr "" -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:647 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:888 msgid "Depreciation Eliminated due to disposal of assets" msgstr "" @@ -15766,7 +15761,7 @@ msgstr "" #: erpnext/accounts/doctype/journal_entry/journal_entry.json #: erpnext/accounts/doctype/journal_entry_template/journal_entry_template.json #: erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py:190 -#: erpnext/assets/doctype/asset/asset.js:119 +#: erpnext/assets/doctype/asset/asset.js:122 msgid "Depreciation Entry" msgstr "" @@ -15818,7 +15813,7 @@ msgstr "" msgid "Depreciation Posting Date" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:909 +#: erpnext/assets/doctype/asset/asset.js:916 msgid "Depreciation Posting Date cannot be before Available-for-use Date" msgstr "" @@ -15859,7 +15854,7 @@ msgstr "" msgid "Depreciation cannot be calculated for fully depreciated assets" msgstr "" -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:659 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:900 msgid "Depreciation eliminated via reversal" msgstr "" @@ -16730,7 +16725,7 @@ msgstr "" msgid "Do not update variants on save" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:947 +#: erpnext/assets/doctype/asset/asset.js:954 msgid "Do you really want to restore this scrapped asset?" msgstr "" @@ -17630,11 +17625,11 @@ msgstr "" msgid "Employee User Id" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:213 +#: erpnext/setup/doctype/employee/employee.py:211 msgid "Employee cannot report to himself." msgstr "" -#: erpnext/setup/doctype/employee/employee.py:445 +#: erpnext/setup/doctype/employee/employee.py:443 msgid "Employee is required" msgstr "" @@ -17651,7 +17646,7 @@ msgstr "" msgid "Employee {0} is currently working on another workstation. Please assign another employee." msgstr "" -#: erpnext/setup/doctype/employee/employee.py:470 +#: erpnext/setup/doctype/employee/employee.py:468 msgid "Employee {0} not found" msgstr "" @@ -18054,7 +18049,7 @@ msgstr "" msgid "Enter customer's phone number" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:918 +#: erpnext/assets/doctype/asset/asset.js:925 msgid "Enter date to scrap asset" msgstr "" @@ -18093,11 +18088,11 @@ msgstr "" msgid "Enter the opening stock units." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:965 +#: erpnext/manufacturing/doctype/bom/bom.js:972 msgid "Enter the quantity of the Item that will be manufactured from this Bill of Materials." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1130 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1157 msgid "Enter the quantity to manufacture. Raw material Items will be fetched only when this is set." msgstr "" @@ -18876,7 +18871,7 @@ msgstr "" msgid "Failed to parse MT940 format. Error: {0}" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:253 +#: erpnext/assets/doctype/asset/asset.js:260 msgid "Failed to post depreciation entries" msgstr "" @@ -19254,9 +19249,9 @@ msgstr "" msgid "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) " msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:850 -#: erpnext/manufacturing/doctype/work_order/work_order.js:865 -#: erpnext/manufacturing/doctype/work_order/work_order.js:874 +#: erpnext/manufacturing/doctype/work_order/work_order.js:877 +#: erpnext/manufacturing/doctype/work_order/work_order.js:892 +#: erpnext/manufacturing/doctype/work_order/work_order.js:901 msgid "Finish" msgstr "" @@ -19287,7 +19282,7 @@ msgstr "" #. Service Item' #. Label of the fg_item (Link) field in DocType 'Subcontracting Order Service #. Item' -#: erpnext/public/js/utils.js:895 +#: erpnext/public/js/utils.js:901 #: erpnext/subcontracting/doctype/subcontracting_inward_order_service_item/subcontracting_inward_order_service_item.json #: erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json msgid "Finished Good Item" @@ -19300,7 +19295,7 @@ msgstr "" msgid "Finished Good Item Code" msgstr "" -#: erpnext/public/js/utils.js:913 +#: erpnext/public/js/utils.js:919 msgid "Finished Good Item Qty" msgstr "" @@ -19890,7 +19885,7 @@ msgstr "" msgid "For the {0}, no stock is available for the return in the warehouse {1}." msgstr "" -#: erpnext/controllers/sales_and_purchase_return.py:1245 +#: erpnext/controllers/sales_and_purchase_return.py:1244 msgid "For the {0}, the quantity is required to make the return entry" msgstr "" @@ -20772,9 +20767,9 @@ msgstr "" #: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1125 #: erpnext/buying/doctype/purchase_order/purchase_order.js:540 #: erpnext/buying/doctype/purchase_order/purchase_order.js:563 -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:383 -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:405 -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:450 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:379 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:401 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:446 #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.js:75 #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.js:108 #: erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js:80 @@ -20784,7 +20779,7 @@ msgstr "" #: erpnext/manufacturing/doctype/production_plan/production_plan.json #: erpnext/public/js/controllers/buying.js:327 #: erpnext/selling/doctype/quotation/quotation.js:183 -#: erpnext/selling/doctype/sales_order/sales_order.js:196 +#: erpnext/selling/doctype/sales_order/sales_order.js:203 #: erpnext/selling/doctype/sales_order/sales_order.js:1200 #: erpnext/stock/doctype/delivery_note/delivery_note.js:187 #: erpnext/stock/doctype/delivery_note/delivery_note.js:239 @@ -20817,7 +20812,7 @@ msgstr "" msgid "Get Items from BOM" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:422 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:418 msgid "Get Items from Material Requests against this Supplier" msgstr "" @@ -20907,12 +20902,12 @@ msgstr "" msgid "Get Sub Assembly Items" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:464 -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:484 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:460 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:480 msgid "Get Suppliers" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:488 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:484 msgid "Get Suppliers By" msgstr "" @@ -22195,7 +22190,7 @@ msgstr "" msgid "If set, the system does not use the user's Email or the standard outgoing Email account for sending request for quotations." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1163 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1190 msgid "If the BOM results in Scrap material, the Scrap Warehouse needs to be selected." msgstr "" @@ -22214,7 +22209,7 @@ msgstr "" msgid "If the reorder check is set at the Group warehouse level, the available quantity becomes the sum of the projected quantities of all its child warehouses." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1182 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1209 msgid "If the selected BOM has Operations mentioned in it, the system will fetch all Operations from BOM, these values can be changed." msgstr "" @@ -23315,7 +23310,7 @@ msgstr "" msgid "Installation Note Item" msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:639 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:643 msgid "Installation Note {0} has already been submitted" msgstr "" @@ -24820,7 +24815,7 @@ msgstr "" #: erpnext/buying/workspace/buying/buying.json #: erpnext/controllers/taxes_and_totals.py:1212 #: erpnext/manufacturing/doctype/blanket_order/blanket_order.json -#: erpnext/manufacturing/doctype/bom/bom.js:1058 +#: erpnext/manufacturing/doctype/bom/bom.js:1065 #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/plant_floor/plant_floor.js:109 #: erpnext/manufacturing/doctype/workstation/workstation_job_card.html:25 @@ -25125,8 +25120,8 @@ msgstr "" #: erpnext/projects/doctype/timesheet/timesheet.js:214 #: erpnext/public/js/controllers/transaction.js:2904 #: erpnext/public/js/stock_reservation.js:112 -#: erpnext/public/js/stock_reservation.js:318 erpnext/public/js/utils.js:553 -#: erpnext/public/js/utils.js:710 +#: erpnext/public/js/stock_reservation.js:318 erpnext/public/js/utils.js:559 +#: erpnext/public/js/utils.js:716 #: erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json #: erpnext/selling/doctype/delivery_schedule_item/delivery_schedule_item.json #: erpnext/selling/doctype/installation_note_item/installation_note_item.json @@ -25606,7 +25601,7 @@ msgstr "" #: erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py:92 #: erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py:138 #: erpnext/public/js/controllers/transaction.js:2910 -#: erpnext/public/js/utils.js:805 +#: erpnext/public/js/utils.js:811 #: erpnext/selling/doctype/quotation_item/quotation_item.json #: erpnext/selling/doctype/sales_order/sales_order.js:1252 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -26543,8 +26538,8 @@ msgstr "" #: erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json #: erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html:10 #: erpnext/accounts/workspace/invoicing/invoicing.json -#: erpnext/assets/doctype/asset/asset.js:378 -#: erpnext/assets/doctype/asset/asset.js:387 +#: erpnext/assets/doctype/asset/asset.js:385 +#: erpnext/assets/doctype/asset/asset.js:394 #: erpnext/assets/doctype/asset/asset.json #: erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json #: erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json @@ -26578,7 +26573,7 @@ msgstr "" msgid "Journal Entry Type" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:547 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:553 msgid "Journal Entry for Asset scrapping cannot be cancelled. Please restore the Asset." msgstr "" @@ -26587,11 +26582,11 @@ msgstr "" msgid "Journal Entry for Scrap" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:343 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:349 msgid "Journal Entry type should be set as Depreciation Entry for asset depreciation" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:717 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:723 msgid "Journal Entry {0} does not have account {1} or already matched against other voucher" msgstr "" @@ -27282,7 +27277,7 @@ msgstr "" msgid "Link to Material Request" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:455 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:451 #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.js:80 msgid "Link to Material Requests" msgstr "" @@ -27579,7 +27574,7 @@ msgstr "" msgid "Loyalty Points will be calculated from the spent done (via the Sales Invoice), based on collection factor mentioned." msgstr "" -#: erpnext/public/js/utils.js:174 +#: erpnext/public/js/utils.js:180 msgid "Loyalty Points: {0}" msgstr "" @@ -27697,7 +27692,7 @@ msgstr "" msgid "Main Item Code" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:135 +#: erpnext/assets/doctype/asset/asset.js:138 msgid "Maintain Asset" msgstr "" @@ -27921,8 +27916,8 @@ msgstr "" #. Label of the make (Data) field in DocType 'Vehicle' #: erpnext/accounts/doctype/journal_entry/journal_entry.js:123 #: erpnext/manufacturing/doctype/job_card/job_card.js:536 -#: erpnext/manufacturing/doctype/work_order/work_order.js:805 -#: erpnext/manufacturing/doctype/work_order/work_order.js:839 +#: erpnext/manufacturing/doctype/work_order/work_order.js:832 +#: erpnext/manufacturing/doctype/work_order/work_order.js:866 #: erpnext/setup/doctype/vehicle/vehicle.json msgid "Make" msgstr "" @@ -28371,7 +28366,7 @@ msgstr "" msgid "Mapping Subcontracting Order ..." msgstr "" -#: erpnext/public/js/utils.js:1040 +#: erpnext/public/js/utils.js:1046 msgid "Mapping {0} ..." msgstr "" @@ -28517,7 +28512,7 @@ msgstr "" msgid "Material" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:830 +#: erpnext/manufacturing/doctype/work_order/work_order.js:857 msgid "Material Consumption" msgstr "" @@ -28598,7 +28593,7 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json #: erpnext/buying/doctype/purchase_order/purchase_order.js:519 #: erpnext/buying/doctype/purchase_order_item/purchase_order_item.json -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:364 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:360 #: erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.js:56 #: erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json @@ -28909,7 +28904,7 @@ msgstr "" msgid "Max discount allowed for item: {0} is {1}%" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:982 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1009 #: erpnext/stock/doctype/pick_list/pick_list.js:200 msgid "Max: {0}" msgstr "" @@ -29035,7 +29030,7 @@ msgstr "" msgid "Merge Similar Account Heads" msgstr "" -#: erpnext/public/js/utils.js:1072 +#: erpnext/public/js/utils.js:1078 msgid "Merge taxes from multiple documents" msgstr "" @@ -29403,7 +29398,7 @@ msgstr "" msgid "Missing Item" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:445 +#: erpnext/setup/doctype/employee/employee.py:443 msgid "Missing Parameter" msgstr "" @@ -29517,10 +29512,6 @@ msgstr "" msgid "Modes of Payment" msgstr "" -#: erpnext/templates/pages/projects.html:69 -msgid "Modified By" -msgstr "" - #: erpnext/templates/pages/projects.html:49 #: erpnext/templates/pages/projects.html:70 msgid "Modified On" @@ -29917,8 +29908,8 @@ msgstr "" msgid "Net Amount (Company Currency)" msgstr "" -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:665 -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:671 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:906 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:912 msgid "Net Asset value as on" msgstr "" @@ -30442,9 +30433,9 @@ msgstr "" msgid "No POS Profile found. Please create a New POS Profile first" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1564 -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1624 -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1638 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1570 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1630 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1644 #: erpnext/stock/doctype/item/item.py:1388 msgid "No Permission" msgstr "" @@ -30467,7 +30458,7 @@ msgstr "" msgid "No Selection" msgstr "" -#: erpnext/controllers/sales_and_purchase_return.py:973 +#: erpnext/controllers/sales_and_purchase_return.py:972 msgid "No Serial / Batches are available for return" msgstr "" @@ -30491,7 +30482,7 @@ msgstr "" msgid "No Tax withholding account set for Company {0} in Tax Withholding Category {1}." msgstr "" -#: erpnext/accounts/report/gross_profit/gross_profit.py:965 +#: erpnext/accounts/report/gross_profit/gross_profit.py:971 msgid "No Terms" msgstr "" @@ -30757,7 +30748,7 @@ msgstr "" msgid "No {0} found for Inter Company Transactions." msgstr "" -#: erpnext/assets/doctype/asset/asset.js:370 +#: erpnext/assets/doctype/asset/asset.js:377 msgid "No." msgstr "" @@ -30943,7 +30934,7 @@ msgstr "" msgid "Note: To merge the items, create a separate Stock Reconciliation for the old item {0}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1021 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1027 msgid "Note: {0}" msgstr "" @@ -31305,7 +31296,7 @@ msgstr "" msgid "Once set, this invoice will be on hold till the set date" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:717 +#: erpnext/manufacturing/doctype/work_order/work_order.js:744 msgid "Once the Work Order is Closed. It can't be resumed." msgstr "" @@ -31666,7 +31657,7 @@ msgstr "" msgid "Opening Invoice Tool" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1646 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1647 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1993 msgid "Opening Invoice has rounding adjustment of {0}.

'{1}' account is required to post these values. Please set it in Company: {2}.

Or, '{3}' can be enabled to not post any rounding adjustment." msgstr "" @@ -31929,7 +31920,7 @@ msgstr "" #. Label of the opportunity_name (Link) field in DocType 'Customer' #. Label of the opportunity (Link) field in DocType 'Quotation' #. Label of a Workspace Sidebar Item -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:388 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:384 #: erpnext/buying/doctype/request_for_quotation/request_for_quotation.json #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.json #: erpnext/crm/doctype/crm_settings/crm_settings.json @@ -32995,7 +32986,7 @@ msgstr "" msgid "Packing Slip Item" msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:655 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:659 msgid "Packing Slip(s) cancelled" msgstr "" @@ -34407,7 +34398,7 @@ msgstr "" msgid "Payment Unlink Error" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:889 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:895 msgid "Payment against {0} {1} cannot be greater than Outstanding Amount {2}" msgstr "" @@ -35335,12 +35326,12 @@ msgstr "" msgid "Please cancel related transaction." msgstr "" -#: erpnext/assets/doctype/asset/asset.js:85 +#: erpnext/assets/doctype/asset/asset.js:86 #: erpnext/assets/doctype/asset/asset.py:249 msgid "Please capitalize this asset before submitting." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:963 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:969 msgid "Please check Multi Currency option to allow accounts with other currency" msgstr "" @@ -35543,7 +35534,7 @@ msgstr "" msgid "Please enter Receipt Document" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1027 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1033 msgid "Please enter Reference date" msgstr "" @@ -35636,7 +35627,7 @@ msgstr "" msgid "Please enter valid Financial Year Start and End Dates" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:221 +#: erpnext/setup/doctype/employee/employee.py:219 msgid "Please enter {0}" msgstr "" @@ -35802,8 +35793,8 @@ msgstr "" msgid "Please select Finished Good Item for Service Item {0}" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:744 -#: erpnext/assets/doctype/asset/asset.js:759 +#: erpnext/assets/doctype/asset/asset.js:751 +#: erpnext/assets/doctype/asset/asset.js:766 msgid "Please select Item Code first" msgstr "" @@ -35873,7 +35864,7 @@ msgid "Please select a Company" msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.js:268 -#: erpnext/manufacturing/doctype/bom/bom.js:680 +#: erpnext/manufacturing/doctype/bom/bom.js:687 #: erpnext/manufacturing/doctype/bom/bom.py:276 #: erpnext/public/js/controllers/accounts.js:277 #: erpnext/public/js/controllers/transaction.js:3365 @@ -35981,7 +35972,7 @@ msgstr "" msgid "Please select atleast one operation to create Job Card" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1703 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1709 msgid "Please select correct account" msgstr "" @@ -36177,11 +36168,11 @@ msgstr "" msgid "Please set a default Holiday List for Company {0}" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:272 +#: erpnext/setup/doctype/employee/employee.py:270 msgid "Please set a default Holiday List for Employee {0} or Company {1}" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1115 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1116 msgid "Please set account in Warehouse {0}" msgstr "" @@ -36275,7 +36266,7 @@ msgstr "" msgid "Please set the Default Cost Center in {0} company." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:634 +#: erpnext/manufacturing/doctype/work_order/work_order.js:661 msgid "Please set the Item Code first" msgstr "" @@ -36420,7 +36411,7 @@ msgstr "" msgid "Portal Users" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:410 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:406 msgid "Possible Supplier" msgstr "" @@ -36794,7 +36785,7 @@ msgid "Preventive Maintenance" msgstr "" #. Label of the preview (Button) field in DocType 'Request for Quotation' -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:270 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:266 #: erpnext/buying/doctype/request_for_quotation/request_for_quotation.json msgid "Preview Email" msgstr "" @@ -38471,7 +38462,7 @@ msgstr "" msgid "Purchase Invoice {0} is already submitted" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1931 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1932 msgid "Purchase Invoices" msgstr "" @@ -38514,7 +38505,7 @@ msgstr "" #: erpnext/crm/doctype/contract/contract.json #: erpnext/manufacturing/doctype/blanket_order/blanket_order.js:54 #: erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json -#: erpnext/selling/doctype/sales_order/sales_order.js:174 +#: erpnext/selling/doctype/sales_order/sales_order.js:181 #: erpnext/selling/doctype/sales_order/sales_order.js:1097 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/setup/doctype/authorization_rule/authorization_rule.json @@ -38967,7 +38958,7 @@ msgstr "" #: erpnext/controllers/trends.py:268 erpnext/controllers/trends.py:280 #: erpnext/controllers/trends.py:285 #: erpnext/crm/doctype/opportunity_item/opportunity_item.json -#: erpnext/manufacturing/doctype/bom/bom.js:1078 +#: erpnext/manufacturing/doctype/bom/bom.js:1085 #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json #: erpnext/manufacturing/doctype/bom_item/bom_item.json #: erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json @@ -38983,7 +38974,7 @@ msgstr "" #: erpnext/public/js/bom_configurator/bom_configurator.bundle.js:398 #: erpnext/public/js/bom_configurator/bom_configurator.bundle.js:499 #: erpnext/public/js/stock_reservation.js:134 -#: erpnext/public/js/stock_reservation.js:336 erpnext/public/js/utils.js:843 +#: erpnext/public/js/stock_reservation.js:336 erpnext/public/js/utils.js:849 #: erpnext/selling/doctype/delivery_schedule_item/delivery_schedule_item.json #: erpnext/selling/doctype/product_bundle_item/product_bundle_item.json #: erpnext/selling/doctype/sales_order/sales_order.js:390 @@ -39149,7 +39140,7 @@ msgstr "" msgid "Qty for which recursion isn't applicable." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:980 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1007 msgid "Qty for {0}" msgstr "" @@ -39714,7 +39705,7 @@ msgstr "" msgid "Quantity must be greater than zero, and less or equal to {0}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1010 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1037 #: erpnext/stock/doctype/pick_list/pick_list.js:206 msgid "Quantity must not be more than {0}" msgstr "" @@ -40028,7 +40019,7 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_item/bom_item.json #: erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json #: erpnext/manufacturing/doctype/work_order_item/work_order_item.json -#: erpnext/public/js/utils.js:853 +#: erpnext/public/js/utils.js:859 #: erpnext/selling/doctype/product_bundle_item/product_bundle_item.json #: erpnext/selling/doctype/quotation_item/quotation_item.json #: erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -40351,7 +40342,7 @@ msgstr "" #. Label of the section_break_8 (Section Break) field in DocType 'Job Card' #. Label of the mr_items (Table) field in DocType 'Production Plan' #: erpnext/manufacturing/doctype/bom/bom.js:406 -#: erpnext/manufacturing/doctype/bom/bom.js:1051 +#: erpnext/manufacturing/doctype/bom/bom.js:1058 #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/job_card/job_card.json #: erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -40437,7 +40428,7 @@ msgstr "" #: erpnext/buying/doctype/purchase_order/purchase_order.js:369 #: erpnext/manufacturing/doctype/production_plan/production_plan.js:124 -#: erpnext/manufacturing/doctype/work_order/work_order.js:733 +#: erpnext/manufacturing/doctype/work_order/work_order.js:760 #: erpnext/selling/doctype/sales_order/sales_order.js:968 #: erpnext/selling/doctype/sales_order/sales_order_list.js:70 #: erpnext/stock/doctype/material_request/material_request.js:243 @@ -40983,7 +40974,7 @@ msgstr "" msgid "Ref Date" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1025 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1031 msgid "Reference #{0} dated {1}" msgstr "" @@ -41021,7 +41012,7 @@ msgstr "" msgid "Reference No" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:639 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:645 msgid "Reference No & Reference Date is required for {0}" msgstr "" @@ -41029,7 +41020,7 @@ msgstr "" msgid "Reference No and Reference Date is mandatory for Bank transaction" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:644 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:650 msgid "Reference No is mandatory if you entered Reference Date" msgstr "" @@ -41123,11 +41114,11 @@ msgstr "" msgid "References" msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:399 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:403 msgid "References to Sales Invoices are Incomplete" msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:394 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:395 msgid "References to Sales Orders are Incomplete" msgstr "" @@ -41722,7 +41713,7 @@ msgstr "" msgid "Reqd Qty (BOM)" msgstr "" -#: erpnext/public/js/utils.js:869 +#: erpnext/public/js/utils.js:875 msgid "Reqd by date" msgstr "" @@ -41995,8 +41986,8 @@ msgstr "" msgid "Reservation Based On" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:891 -#: erpnext/selling/doctype/sales_order/sales_order.js:92 +#: erpnext/manufacturing/doctype/work_order/work_order.js:918 +#: erpnext/selling/doctype/sales_order/sales_order.js:99 #: erpnext/stock/doctype/pick_list/pick_list.js:150 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:180 msgid "Reserve" @@ -42117,9 +42108,9 @@ msgstr "" #. Label of the reserved_stock (Float) field in DocType 'Bin' #. Name of a report #: erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html:24 -#: erpnext/manufacturing/doctype/work_order/work_order.js:907 +#: erpnext/manufacturing/doctype/work_order/work_order.js:934 #: erpnext/public/js/stock_reservation.js:236 -#: erpnext/selling/doctype/sales_order/sales_order.js:120 +#: erpnext/selling/doctype/sales_order/sales_order.js:127 #: erpnext/selling/doctype/sales_order/sales_order.js:457 #: erpnext/stock/dashboard/item_dashboard_list.html:15 #: erpnext/stock/doctype/bin/bin.json @@ -42345,7 +42336,7 @@ msgstr "" msgid "Restart Subscription" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:175 +#: erpnext/assets/doctype/asset/asset.js:178 msgid "Restore Asset" msgstr "" @@ -43311,11 +43302,11 @@ msgstr "" msgid "Row #{0}: For Customer Provided Item {1}, Source Warehouse must be {2}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:687 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:693 msgid "Row #{0}: For {1}, you can select reference document only if account gets credited" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:697 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:703 msgid "Row #{0}: For {1}, you can select reference document only if account gets debited" msgstr "" @@ -43609,7 +43600,7 @@ msgstr "" msgid "Row #{0}: Status is mandatory" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:449 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:455 msgid "Row #{0}: Status must be {1} for Invoice Discounting {2}" msgstr "" @@ -43629,7 +43620,7 @@ msgstr "" msgid "Row #{0}: Stock is already reserved for the Item {1}." msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:553 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:557 msgid "Row #{0}: Stock is reserved for item {1} in warehouse {2}." msgstr "" @@ -43823,7 +43814,7 @@ msgstr "" msgid "Row {0}: Accepted Qty and Rejected Qty can't be zero at the same time." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:602 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:608 msgid "Row {0}: Account {1} and Party Type {2} have different account types" msgstr "" @@ -43831,11 +43822,11 @@ msgstr "" msgid "Row {0}: Activity Type is mandatory." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:668 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:674 msgid "Row {0}: Advance against Customer must be credit" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:670 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:676 msgid "Row {0}: Advance against Supplier must be debit" msgstr "" @@ -43855,7 +43846,7 @@ msgstr "" msgid "Row {0}: Bill of Materials not found for the Item {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:921 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:927 msgid "Row {0}: Both Debit and Credit values cannot be zero" msgstr "" @@ -43877,7 +43868,7 @@ msgstr "" msgid "Row {0}: Cost center is required for an item {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:767 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:773 msgid "Row {0}: Credit entry can not be linked with a {1}" msgstr "" @@ -43885,7 +43876,7 @@ msgstr "" msgid "Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:762 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:768 msgid "Row {0}: Debit entry can not be linked with a {1}" msgstr "" @@ -43905,7 +43896,7 @@ msgstr "" msgid "Row {0}: Either Delivery Note Item or Packed Item reference is mandatory." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1012 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1018 #: erpnext/controllers/taxes_and_totals.py:1340 msgid "Row {0}: Exchange Rate is mandatory" msgstr "" @@ -43959,7 +43950,7 @@ msgstr "" msgid "Row {0}: Hours value must be greater than zero." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:787 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:793 msgid "Row {0}: Invalid reference {1}" msgstr "" @@ -43987,7 +43978,7 @@ msgstr "" msgid "Row {0}: Item {1}'s quantity cannot be higher than the available quantity." msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:610 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:614 msgid "Row {0}: Packed Qty must be equal to {1} Qty." msgstr "" @@ -43995,11 +43986,11 @@ msgstr "" msgid "Row {0}: Packing Slip is already created for Item {1}." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:813 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:819 msgid "Row {0}: Party / Account does not match with {1} / {2} in {3} {4}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:591 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:597 msgid "Row {0}: Party Type and Party is required for Receivable / Payable account {1}" msgstr "" @@ -44007,11 +43998,11 @@ msgstr "" msgid "Row {0}: Payment Term is mandatory" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:661 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:667 msgid "Row {0}: Payment against Sales/Purchase Order should always be marked as advance" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:654 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:660 msgid "Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry." msgstr "" @@ -44091,7 +44082,7 @@ msgstr "" msgid "Row {0}: Task {1} does not belong to Project {2}" msgstr "" -#: erpnext/assets/doctype/asset_repair/asset_repair.js:158 +#: erpnext/assets/doctype/asset_repair/asset_repair.js:178 msgid "Row {0}: The entire expense amount for account {1} in {2} has already been allocated." msgstr "" @@ -44136,7 +44127,7 @@ msgstr "" msgid "Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:827 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:833 msgid "Row {0}: {1} {2} does not match with {3}" msgstr "" @@ -44261,7 +44252,7 @@ msgstr "" msgid "SLA Paused On" msgstr "" -#: erpnext/public/js/utils.js:1233 +#: erpnext/public/js/utils.js:1239 msgid "SLA is on hold since {0}" msgstr "" @@ -44560,7 +44551,7 @@ msgstr "" msgid "Sales Invoice mode is activated in POS. Please create Sales Invoice instead." msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:630 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:634 msgid "Sales Invoice {0} has already been submitted" msgstr "" @@ -44758,7 +44749,7 @@ msgstr "" msgid "Sales Order Trends" msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:282 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:283 msgid "Sales Order required for Item {0}" msgstr "" @@ -45031,7 +45022,7 @@ msgstr "" msgid "Sales Representative" msgstr "" -#: erpnext/accounts/report/gross_profit/gross_profit.py:964 +#: erpnext/accounts/report/gross_profit/gross_profit.py:970 #: erpnext/stock/doctype/delivery_note/delivery_note.js:270 msgid "Sales Return" msgstr "" @@ -45308,7 +45299,7 @@ msgstr "" #. Label of the schedule_date (Date) field in DocType 'Depreciation Schedule' #. Label of the schedule_date (Datetime) field in DocType 'Production Plan Sub #. Assembly Item' -#: erpnext/assets/doctype/asset/asset.js:371 +#: erpnext/assets/doctype/asset/asset.js:378 #: erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json #: erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json msgid "Schedule Date" @@ -45421,7 +45412,7 @@ msgstr "" msgid "Scrap & Process Loss" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:160 +#: erpnext/assets/doctype/asset/asset.js:163 msgid "Scrap Asset" msgstr "" @@ -45572,7 +45563,7 @@ msgstr "" msgid "Select Accounting Dimension." msgstr "" -#: erpnext/public/js/utils.js:529 +#: erpnext/public/js/utils.js:535 msgid "Select Alternate Item" msgstr "" @@ -45721,11 +45712,11 @@ msgstr "" msgid "Select Payment Schedule" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:414 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:410 msgid "Select Possible Supplier" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1016 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1043 #: erpnext/stock/doctype/pick_list/pick_list.js:216 msgid "Select Quantity" msgstr "" @@ -45863,11 +45854,11 @@ msgstr "" msgid "Select the Default Workstation where the Operation will be performed. This will be fetched in BOMs and Work Orders." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1118 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1145 msgid "Select the Item to be manufactured." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:958 +#: erpnext/manufacturing/doctype/bom/bom.js:965 msgid "Select the Item to be manufactured. The Item name, UoM, Company, and Currency will be fetched automatically." msgstr "" @@ -45880,7 +45871,7 @@ msgstr "" msgid "Select the customer or supplier." msgstr "" -#: erpnext/assets/doctype/asset/asset.js:921 +#: erpnext/assets/doctype/asset/asset.js:928 msgid "Select the date" msgstr "" @@ -45888,7 +45879,7 @@ msgstr "" msgid "Select the date and your timezone" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:977 +#: erpnext/manufacturing/doctype/bom/bom.js:984 msgid "Select the raw materials (Items) required to manufacture the Item" msgstr "" @@ -45947,22 +45938,22 @@ msgstr "" msgid "Self delivery" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:632 +#: erpnext/assets/doctype/asset/asset.js:639 #: erpnext/stock/doctype/batch/batch_dashboard.py:9 #: erpnext/stock/doctype/item/item_dashboard.py:20 msgid "Sell" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:168 -#: erpnext/assets/doctype/asset/asset.js:621 +#: erpnext/assets/doctype/asset/asset.js:171 +#: erpnext/assets/doctype/asset/asset.js:628 msgid "Sell Asset" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:626 +#: erpnext/assets/doctype/asset/asset.js:633 msgid "Sell Qty" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:642 +#: erpnext/assets/doctype/asset/asset.js:649 msgid "Sell quantity cannot exceed the asset quantity" msgstr "" @@ -45970,7 +45961,7 @@ msgstr "" msgid "Sell quantity cannot exceed the asset quantity. Asset {0} has only {1} item(s)." msgstr "" -#: erpnext/assets/doctype/asset/asset.js:638 +#: erpnext/assets/doctype/asset/asset.js:645 msgid "Sell quantity must be greater than zero" msgstr "" @@ -46165,7 +46156,7 @@ msgstr "" msgid "Serial / Batch No" msgstr "" -#: erpnext/public/js/utils.js:191 +#: erpnext/public/js/utils.js:197 msgid "Serial / Batch Nos" msgstr "" @@ -46278,7 +46269,7 @@ msgstr "" msgid "Serial No Range" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2579 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2596 msgid "Serial No Reserved" msgstr "" @@ -46364,7 +46355,7 @@ msgstr "" msgid "Serial No {0} does not exist" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3345 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3362 msgid "Serial No {0} does not exists" msgstr "" @@ -46418,7 +46409,7 @@ msgstr "" msgid "Serial Nos and Batches" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1865 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1882 msgid "Serial Nos are created successfully" msgstr "" @@ -46499,11 +46490,11 @@ msgstr "" msgid "Serial and Batch Bundle" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2087 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2104 msgid "Serial and Batch Bundle created" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2159 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2176 msgid "Serial and Batch Bundle updated" msgstr "" @@ -46985,7 +46976,7 @@ msgstr "" msgid "Set Posting Date" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:1004 +#: erpnext/manufacturing/doctype/bom/bom.js:1011 msgid "Set Process Loss Item Quantity" msgstr "" @@ -47108,7 +47099,7 @@ msgstr "" msgid "Set fieldname from which you want to fetch the data from the parent form." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:994 +#: erpnext/manufacturing/doctype/bom/bom.js:1001 msgid "Set quantity of process loss item:" msgstr "" @@ -47124,7 +47115,7 @@ msgstr "" msgid "Set targets Item Group-wise for this Sales Person." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1175 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1202 msgid "Set the Planned Start Date (an Estimated Date at which you want the Production to begin)" msgstr "" @@ -47354,7 +47345,7 @@ msgid "Shelf Life in Days" msgstr "" #. Label of the shift (Link) field in DocType 'Depreciation Schedule' -#: erpnext/assets/doctype/asset/asset.js:384 +#: erpnext/assets/doctype/asset/asset.js:391 #: erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json msgid "Shift" msgstr "" @@ -47426,7 +47417,7 @@ msgstr "" msgid "Shipment details" msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:801 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:805 msgid "Shipments" msgstr "" @@ -47613,6 +47604,7 @@ msgstr "" msgid "Shortage Qty" msgstr "" +#: erpnext/buying/report/purchase_analytics/purchase_analytics.js:70 #: erpnext/selling/report/sales_analytics/sales_analytics.js:103 msgid "Show Aggregate Value from Subsidiary Companies" msgstr "" @@ -48206,15 +48198,15 @@ msgstr "" msgid "Spending for Account {0} ({1}) between {2} and {3} has already exceeded the new allocated budget. Spent: {4}, Budget: {5}" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:682 +#: erpnext/assets/doctype/asset/asset.js:689 #: erpnext/stock/doctype/batch/batch.js:91 #: erpnext/stock/doctype/batch/batch.js:183 #: erpnext/support/doctype/issue/issue.js:114 msgid "Split" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:144 -#: erpnext/assets/doctype/asset/asset.js:666 +#: erpnext/assets/doctype/asset/asset.js:147 +#: erpnext/assets/doctype/asset/asset.js:673 msgid "Split Asset" msgstr "" @@ -48237,7 +48229,7 @@ msgstr "" msgid "Split Issue" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:672 +#: erpnext/assets/doctype/asset/asset.js:679 msgid "Split Qty" msgstr "" @@ -48507,8 +48499,8 @@ msgstr "" #: erpnext/accounts/doctype/account/account.json #: erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py:96 #: erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py:158 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1357 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1383 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1358 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1384 #: erpnext/accounts/report/account_balance/account_balance.js:58 msgid "Stock Adjustment" msgstr "" @@ -48686,7 +48678,7 @@ msgstr "" msgid "Stock Entry {0} has created" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1306 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1312 msgid "Stock Entry {0} is not submitted" msgstr "" @@ -48915,14 +48907,14 @@ msgstr "" #: erpnext/manufacturing/doctype/production_plan/production_plan.js:289 #: erpnext/manufacturing/doctype/production_plan/production_plan.js:297 #: erpnext/manufacturing/doctype/production_plan/production_plan.js:303 -#: erpnext/manufacturing/doctype/work_order/work_order.js:893 -#: erpnext/manufacturing/doctype/work_order/work_order.js:902 -#: erpnext/manufacturing/doctype/work_order/work_order.js:909 +#: erpnext/manufacturing/doctype/work_order/work_order.js:920 +#: erpnext/manufacturing/doctype/work_order/work_order.js:929 +#: erpnext/manufacturing/doctype/work_order/work_order.js:936 #: erpnext/manufacturing/doctype/work_order/work_order_dashboard.py:14 #: erpnext/public/js/stock_reservation.js:12 -#: erpnext/selling/doctype/sales_order/sales_order.js:94 -#: erpnext/selling/doctype/sales_order/sales_order.js:109 -#: erpnext/selling/doctype/sales_order/sales_order.js:122 +#: erpnext/selling/doctype/sales_order/sales_order.js:101 +#: erpnext/selling/doctype/sales_order/sales_order.js:116 +#: erpnext/selling/doctype/sales_order/sales_order.js:129 #: erpnext/selling/doctype/sales_order/sales_order.js:250 #: erpnext/stock/doctype/pick_list/pick_list.js:152 #: erpnext/stock/doctype/pick_list/pick_list.js:167 @@ -48954,7 +48946,7 @@ msgid "Stock Reservation Entries Cancelled" msgstr "" #: erpnext/controllers/subcontracting_inward_controller.py:1003 -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:2234 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:2236 #: erpnext/manufacturing/doctype/work_order/work_order.py:2124 #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:1777 msgid "Stock Reservation Entries Created" @@ -48983,7 +48975,7 @@ msgstr "" msgid "Stock Reservation Entry created against a Pick List cannot be updated. If you need to make changes, we recommend canceling the existing entry and creating a new one." msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:563 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:567 msgid "Stock Reservation Warehouse Mismatch" msgstr "" @@ -49169,6 +49161,10 @@ msgstr "" msgid "Stock Uom" msgstr "" +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:739 +msgid "Stock Update Not Allowed" +msgstr "" + #. Name of a role #: erpnext/accounts/doctype/fiscal_year/fiscal_year.json #: erpnext/assets/doctype/location/location.json @@ -49262,10 +49258,6 @@ msgstr "" msgid "Stock cannot be reserved in the group warehouse {0}." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:736 -msgid "Stock cannot be updated against Purchase Receipt {0}" -msgstr "" - #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1228 msgid "Stock cannot be updated against the following Delivery Notes: {0}" msgstr "" @@ -49274,6 +49266,10 @@ msgstr "" msgid "Stock cannot be updated because the invoice contains a drop shipping item. Please disable 'Update Stock' or remove the drop shipping item." msgstr "" +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:736 +msgid "Stock cannot be updated for Purchase Invoice {0} because a Purchase Receipt {1} has already been created for this transaction. Please disable the 'Update Stock' checkbox in the Purchase Invoice and save the invoice." +msgstr "" + #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:1131 msgid "Stock has been unreserved for work order {0}." msgstr "" @@ -50081,8 +50077,8 @@ msgstr "" #: erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js:37 #: erpnext/assets/doctype/asset/asset.json #: erpnext/buying/doctype/purchase_order/purchase_order.json -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:188 -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:273 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:184 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:269 #: erpnext/buying/doctype/request_for_quotation/request_for_quotation.json #: erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json #: erpnext/buying/doctype/supplier/supplier.json @@ -50109,7 +50105,7 @@ msgstr "" #: erpnext/regional/report/irs_1099/irs_1099.py:77 #: erpnext/selling/doctype/customer/customer.js:253 #: erpnext/selling/doctype/party_specific_item/party_specific_item.json -#: erpnext/selling/doctype/sales_order/sales_order.js:182 +#: erpnext/selling/doctype/sales_order/sales_order.js:189 #: erpnext/selling/doctype/sales_order/sales_order.js:1660 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/selling/doctype/sms_center/sms_center.json @@ -50215,7 +50211,7 @@ msgstr "" #: erpnext/accounts/report/purchase_register/purchase_register.py:186 #: erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js:55 #: erpnext/buying/doctype/purchase_order/purchase_order.json -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:506 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:502 #: erpnext/buying/doctype/supplier/supplier.json #: erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py:105 #: erpnext/buying/workspace/buying/buying.json @@ -50269,7 +50265,7 @@ msgstr "" msgid "Supplier Invoice No" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1773 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1774 msgid "Supplier Invoice No exists in Purchase Invoice {0}" msgstr "" @@ -50699,7 +50695,7 @@ msgstr "" msgid "TDS Computation Summary" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1534 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1535 msgid "TDS Deducted" msgstr "" @@ -51944,7 +51940,7 @@ msgstr "" msgid "The Serial No at Row #{0}: {1} is not available in warehouse {2}." msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2576 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2593 msgid "The Serial No {0} is reserved against the {1} {2} and cannot be used for any other transaction." msgstr "" @@ -51986,7 +51982,7 @@ msgstr "" msgid "The current POS opening entry is outdated. Please close it and create a new one." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1123 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1150 msgid "The default BOM for that item will be fetched by the system. You can also change the BOM." msgstr "" @@ -52011,7 +52007,7 @@ msgstr "" msgid "The field To Shareholder cannot be blank" msgstr "" -#: erpnext/stock/doctype/delivery_note/delivery_note.py:413 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:417 msgid "The field {0} in row {1} is not set" msgstr "" @@ -52172,7 +52168,7 @@ msgstr "" msgid "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units." msgstr "" -#: erpnext/public/js/utils.js:941 +#: erpnext/public/js/utils.js:947 msgid "The reserved stock will be released when you update items. Are you certain you wish to proceed?" msgstr "" @@ -52196,7 +52192,7 @@ msgstr "" msgid "The selected item cannot have Batch" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:647 +#: erpnext/assets/doctype/asset/asset.js:654 msgid "The sell quantity is less than the total asset quantity. The remaining quantity will be split into a new asset. This action cannot be undone.

Do you want to continue?" msgstr "" @@ -52291,15 +52287,15 @@ msgstr "" msgid "The value {0} is already assigned to an existing Item {1}." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1151 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1178 msgid "The warehouse where you store finished Items before they are shipped." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1144 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1171 msgid "The warehouse where you store your raw materials. Each required item can have a separate source warehouse. Group warehouse also can be selected as source warehouse. On submission of the Work Order, the raw materials will be reserved in these warehouses for production usage." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1156 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1183 msgid "The warehouse where your Items will be transferred when you begin production. Group Warehouse can also be selected as a Work in Progress warehouse." msgstr "" @@ -52501,7 +52497,7 @@ msgstr "" msgid "This is a location where scraped materials are stored." msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:322 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:318 msgid "This is a preview of the email to be sent. A PDF of the document will automatically be attached with the email." msgstr "" @@ -52553,7 +52549,7 @@ msgstr "" msgid "This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1137 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1164 msgid "This is enabled by default. If you want to plan materials for sub-assemblies of the Item you're manufacturing leave this enabled. If you plan and manufacture the sub-assemblies separately, you can disable this checkbox." msgstr "" @@ -53098,7 +53094,7 @@ msgstr "" msgid "To Warehouse (Optional)" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:972 +#: erpnext/manufacturing/doctype/bom/bom.js:979 msgid "To add Operations tick the 'With Operations' checkbox." msgstr "" @@ -53494,7 +53490,7 @@ msgstr "" msgid "Total Credit" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:336 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:342 msgid "Total Credit/ Debit Amount should be same as linked Journal Entry" msgstr "" @@ -53503,7 +53499,7 @@ msgstr "" msgid "Total Debit" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:927 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:933 msgid "Total Debit must be equal to Total Credit. The difference is {0}" msgstr "" @@ -53937,7 +53933,7 @@ msgstr "" msgid "Total Time in Mins" msgstr "" -#: erpnext/public/js/utils.js:167 +#: erpnext/public/js/utils.js:173 msgid "Total Unpaid: {0}" msgstr "" @@ -54332,7 +54328,7 @@ msgstr "" msgid "Transfer" msgstr "" -#: erpnext/assets/doctype/asset/asset.js:152 +#: erpnext/assets/doctype/asset/asset.js:155 msgid "Transfer Asset" msgstr "" @@ -54697,7 +54693,7 @@ msgstr "" #: erpnext/manufacturing/doctype/workstation/workstation.js:480 #: erpnext/manufacturing/report/bom_explorer/bom_explorer.py:70 #: erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py:110 -#: erpnext/public/js/stock_analytics.js:94 erpnext/public/js/utils.js:814 +#: erpnext/public/js/stock_analytics.js:94 erpnext/public/js/utils.js:820 #: erpnext/quality_management/doctype/quality_goal_objective/quality_goal_objective.json #: erpnext/quality_management/doctype/quality_review_objective/quality_review_objective.json #: erpnext/selling/doctype/delivery_schedule_item/delivery_schedule_item.json @@ -55107,8 +55103,8 @@ msgstr "" msgid "Unreconciled Entries" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:900 -#: erpnext/selling/doctype/sales_order/sales_order.js:107 +#: erpnext/manufacturing/doctype/work_order/work_order.js:927 +#: erpnext/selling/doctype/sales_order/sales_order.js:114 #: erpnext/stock/doctype/pick_list/pick_list.js:158 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:193 msgid "Unreserve" @@ -55315,9 +55311,9 @@ msgstr "" #: erpnext/buying/doctype/purchase_order/purchase_order.js:324 #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.js:43 -#: erpnext/public/js/utils.js:920 +#: erpnext/public/js/utils.js:926 #: erpnext/selling/doctype/quotation/quotation.js:135 -#: erpnext/selling/doctype/sales_order/sales_order.js:75 +#: erpnext/selling/doctype/sales_order/sales_order.js:82 #: erpnext/selling/doctype/sales_order/sales_order.js:940 msgid "Update Items" msgstr "" @@ -55417,7 +55413,7 @@ msgstr "" msgid "Updating Variants..." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1099 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1126 msgid "Updating Work Order status" msgstr "" @@ -55660,7 +55656,7 @@ msgstr "" msgid "User has not applied rule on the invoice {0}" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:190 +#: erpnext/setup/doctype/employee/employee.py:187 msgid "User {0} does not exist" msgstr "" @@ -55668,15 +55664,15 @@ msgstr "" msgid "User {0} doesn't have any default POS Profile. Check Default at Row {1} for this User." msgstr "" -#: erpnext/setup/doctype/employee/employee.py:207 +#: erpnext/setup/doctype/employee/employee.py:205 msgid "User {0} is already assigned to Employee {1}" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:245 +#: erpnext/setup/doctype/employee/employee.py:243 msgid "User {0}: Removed Employee Self Service role as there is no mapped employee." msgstr "" -#: erpnext/setup/doctype/employee/employee.py:240 +#: erpnext/setup/doctype/employee/employee.py:238 msgid "User {0}: Removed Employee role as there is no mapped employee." msgstr "" @@ -56061,8 +56057,8 @@ msgstr "" msgid "Value Type" msgstr "" -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:599 -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:629 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:840 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:870 msgid "Value as on" msgstr "" @@ -56075,19 +56071,19 @@ msgstr "" msgid "Value of Goods" msgstr "" -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:623 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:864 msgid "Value of New Capitalized Asset" msgstr "" -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:605 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:846 msgid "Value of New Purchase" msgstr "" -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:617 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:858 msgid "Value of Scrapped Asset" msgstr "" -#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:611 +#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py:852 msgid "Value of Sold Asset" msgstr "" @@ -56784,7 +56780,7 @@ msgid "Warehouse not found against the account {0}" msgstr "" #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1218 -#: erpnext/stock/doctype/delivery_note/delivery_note.py:440 +#: erpnext/stock/doctype/delivery_note/delivery_note.py:444 msgid "Warehouse required for stock Item {0}" msgstr "" @@ -56926,7 +56922,7 @@ msgstr "" msgid "Warning!" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1312 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1318 msgid "Warning: Another {0} # {1} exists against stock entry {2}" msgstr "" @@ -57093,13 +57089,6 @@ msgstr "" msgid "Weekly Time to send" msgstr "" -#. Label of the task_weight (Float) field in DocType 'Task' -#. Label of the weight (Float) field in DocType 'Task Type' -#: erpnext/projects/doctype/task/task.json -#: erpnext/projects/doctype/task_type/task_type.json -msgid "Weight" -msgstr "" - #. Label of the weight (Float) field in DocType 'Shipment Parcel' #. Label of the weight (Float) field in DocType 'Shipment Parcel Template' #: erpnext/stock/doctype/shipment_parcel/shipment_parcel.json @@ -57821,7 +57810,7 @@ msgstr "" msgid "You can change the parent account to a Balance Sheet account or select a different account." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:703 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:709 msgid "You can not enter current voucher in 'Against Journal Entry' column" msgstr "" @@ -57862,7 +57851,7 @@ msgstr "" msgid "You can't redeem Loyalty Points having more value than the Total Amount." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:728 +#: erpnext/manufacturing/doctype/bom/bom.js:735 msgid "You cannot change the rate if BOM is mentioned against any Item." msgstr "" @@ -57878,7 +57867,7 @@ msgstr "" msgid "You cannot create/amend any accounting entries till this date." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:936 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:942 msgid "You cannot credit and debit same account at the same time" msgstr "" @@ -57922,6 +57911,10 @@ msgstr "" msgid "You cannot {0} this document because another Period Closing Entry {1} exists after {2}" msgstr "" +#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:566 +msgid "You do not have permission to edit this document" +msgstr "" + #: erpnext/controllers/accounts_controller.py:3837 msgid "You do not have permissions to {} items in a {}." msgstr "" @@ -57938,7 +57931,7 @@ msgstr "" msgid "You had {} errors while creating opening invoices. Check {} for more details" msgstr "" -#: erpnext/public/js/utils.js:1020 +#: erpnext/public/js/utils.js:1026 msgid "You have already selected items from {0} {1}" msgstr "" @@ -58058,7 +58051,7 @@ msgstr "" msgid "as Title" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:996 +#: erpnext/manufacturing/doctype/bom/bom.js:1003 msgid "as a percentage of finished item quantity" msgstr "" @@ -58211,7 +58204,7 @@ msgstr "" msgid "paid to" msgstr "" -#: erpnext/public/js/utils.js:437 +#: erpnext/public/js/utils.js:443 msgid "payments app is not installed. Please install it from {0} or {1}" msgstr "" @@ -58398,7 +58391,7 @@ msgstr "" msgid "{0} Operating Cost for operation {1}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:528 +#: erpnext/manufacturing/doctype/work_order/work_order.js:555 msgid "{0} Operations: {1}" msgstr "" @@ -58426,19 +58419,19 @@ msgstr "" msgid "{0} account not found while submitting purchase receipt" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1056 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1062 msgid "{0} against Bill {1} dated {2}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1065 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1071 msgid "{0} against Purchase Order {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1032 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1038 msgid "{0} against Sales Invoice {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1039 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1045 msgid "{0} against Sales Order {1}" msgstr "" @@ -58579,6 +58572,10 @@ msgstr "" msgid "{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}." msgstr "" +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1737 +msgid "{0} is not a CSV file." +msgstr "" + #: erpnext/selling/doctype/customer/customer.py:234 msgid "{0} is not a company bank account" msgstr "" @@ -58623,18 +58620,30 @@ msgstr "" msgid "{0} is open. Close the POS or cancel the existing POS Opening Entry to create a new POS Opening Entry." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:483 +#: erpnext/manufacturing/doctype/work_order/work_order.js:520 +msgid "{0} items disassembled" +msgstr "" + +#: erpnext/manufacturing/doctype/work_order/work_order.js:484 msgid "{0} items in progress" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:494 +#: erpnext/manufacturing/doctype/work_order/work_order.js:508 msgid "{0} items lost during process." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:464 +#: erpnext/manufacturing/doctype/work_order/work_order.js:465 msgid "{0} items produced" msgstr "" +#: erpnext/manufacturing/doctype/work_order/work_order.js:488 +msgid "{0} items returned" +msgstr "" + +#: erpnext/manufacturing/doctype/work_order/work_order.js:491 +msgid "{0} items to return" +msgstr "" + #: erpnext/controllers/sales_and_purchase_return.py:218 msgid "{0} must be negative in return document" msgstr "" @@ -58785,7 +58794,7 @@ msgstr "" msgid "{0} {1} is cancelled so the action cannot be completed" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:851 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:857 msgid "{0} {1} is closed" msgstr "" @@ -58797,7 +58806,7 @@ msgstr "" msgid "{0} {1} is frozen" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:848 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:854 msgid "{0} {1} is fully billed" msgstr "" @@ -58813,8 +58822,8 @@ msgstr "" msgid "{0} {1} is not in any active Fiscal Year" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:845 -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:884 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:851 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:890 msgid "{0} {1} is not submitted" msgstr "" From 9e40f8733982ca28a4d5cdff807761965cdbee55 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 15 Mar 2026 15:06:59 +0100 Subject: [PATCH 32/63] chore: add docs to project URLs (backport #53467) (#53469) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index d8acb83723b..960efd87a63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,4 +81,5 @@ docstring-code-format = true [project.urls] Homepage = "https://frappe.io/erpnext" Repository = "https://github.com/frappe/erpnext.git" +Documentation = "https://docs.frappe.io/erpnext" "Bug Reports" = "https://github.com/frappe/erpnext/issues" From 36030483e5d2723e8ea406389171dd1a92863c13 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 04:33:36 +0000 Subject: [PATCH 33/63] fix: remove supplier selection dialog when creating Purchase Order from Material Request (backport #53391) (#53476) Co-authored-by: jeraldin2003 Co-authored-by: Nikhil Kothari Co-authored-by: Jeraldin P J <113301032+jeraldin2003@users.noreply.github.com> Co-authored-by: Mihir Kandoi fix: remove supplier selection dialog when creating Purchase Order from Material Request (#53391) --- .../material_request/material_request.js | 32 +++----------- .../material_request/material_request.py | 43 ------------------- 2 files changed, 5 insertions(+), 70 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 625d6490a9d..e0bbff4cbda 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -410,33 +410,11 @@ frappe.ui.form.on("Material Request", { }, make_purchase_order: function (frm) { - frappe.prompt( - { - label: __("For Default Supplier (Optional)"), - fieldname: "default_supplier", - fieldtype: "Link", - options: "Supplier", - description: __( - "Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only." - ), - get_query: () => { - return { - query: "erpnext.stock.doctype.material_request.material_request.get_default_supplier_query", - filters: { doc: frm.doc.name }, - }; - }, - }, - (values) => { - frappe.model.open_mapped_doc({ - method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order", - frm: frm, - args: { default_supplier: values.default_supplier }, - run_link_triggers: true, - }); - }, - __("Enter Supplier"), - __("Create") - ); + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order", + frm: frm, + run_link_triggers: true, + }); }, make_request_for_quotation: function (frm) { diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 15e13561afc..09d33a36463 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -18,7 +18,6 @@ from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, new_line_se from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items from erpnext.controllers.buying_controller import BuyingController from erpnext.manufacturing.doctype.work_order.work_order import get_item_details -from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.stock.stock_balance import get_indented_qty, update_bin_qty from erpnext.subcontracting.doctype.subcontracting_bom.subcontracting_bom import ( get_subcontracting_boms_for_finished_goods, @@ -501,17 +500,6 @@ def make_purchase_order(source_name, target_doc=None, args=None): def postprocess(source, target_doc): target_doc.is_subcontracted = is_subcontracted - if frappe.flags.args and frappe.flags.args.default_supplier: - # items only for given default supplier - supplier_items = [] - for d in target_doc.items: - if is_subcontracted and not d.item_code: - continue - default_supplier = get_item_defaults(d.item_code, target_doc.company).get("default_supplier") - if frappe.flags.args.default_supplier == default_supplier: - supplier_items.append(d) - target_doc.items = supplier_items - set_missing_values(source, target_doc) def select_item(d): @@ -689,37 +677,6 @@ def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, pa return material_requests -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def get_default_supplier_query(doctype, txt, searchfield, start, page_len, filters): - doc = frappe.get_doc("Material Request", filters.get("doc")) - item_list = [] - for d in doc.items: - item_list.append(d.item_code) - - supplier = frappe.qb.DocType("Supplier") - item_default = frappe.qb.DocType("Item Default") - query = ( - frappe.qb.from_(supplier) - .left_join(item_default) - .on(supplier.name == item_default.default_supplier) - .select(item_default.default_supplier) - .where( - (item_default.parent.isin(item_list)) - & (item_default.default_supplier.notnull()) - & (supplier[searchfield].like(f"%{txt}%")) - ) - .offset(start) - .limit(page_len) - ) - - meta = frappe.get_meta("Supplier") - if meta.show_title_field_in_link and meta.title_field: - query = query.select(supplier[meta.title_field]) - - return query.run(as_dict=False) - - @frappe.whitelist() def make_supplier_quotation(source_name, target_doc=None): def postprocess(source, target_doc): From 31ab75787e0050938d492fa5e816c3cc3747b8c4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 11:05:11 +0530 Subject: [PATCH 34/63] fix: use bom source warehouse in WO created from PP (backport #53478) (#53480) Co-authored-by: Mihir Kandoi --- .../manufacturing/doctype/production_plan/production_plan.py | 2 ++ .../production_plan_sub_assembly_item.json | 2 +- erpnext/manufacturing/doctype/work_order/work_order.json | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 2423dbc1b53..30b3968fc80 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -731,6 +731,7 @@ class ProductionPlan(Document): "description": d.description, "stock_uom": d.stock_uom, "company": self.company, + "source_warehouse": frappe.get_value("BOM", d.bom_no, "default_source_warehouse"), "fg_warehouse": d.warehouse, "production_plan": self.name, "production_plan_item": d.name, @@ -807,6 +808,7 @@ class ProductionPlan(Document): continue work_order_data = { + "source_warehouse": frappe.get_value("BOM", row.bom_no, "default_source_warehouse"), "wip_warehouse": default_warehouses.get("wip_warehouse"), "fg_warehouse": default_warehouses.get("fg_warehouse"), "scrap_warehouse": default_warehouses.get("scrap_warehouse"), diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json index 26d65116396..35f7bc39799 100644 --- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json @@ -268,7 +268,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2026-02-11 13:00:09.092676", + "modified": "2026-03-16 10:28:41.879801", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Sub Assembly Item", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 4365a501ae3..6e5295fbfa0 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -570,7 +570,8 @@ { "fieldname": "production_plan_sub_assembly_item", "fieldtype": "Data", - "label": "Production Plan Sub-assembly Item", + "hidden": 1, + "label": "Production Plan Sub Assembly Item", "no_copy": 1, "print_hide": 1, "read_only": 1 @@ -704,7 +705,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2026-02-06 17:53:11.295600", + "modified": "2026-03-16 10:15:28.708688", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", From 1ecc04c7a2583ef3e5977c74ab980506a024796e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 12:53:48 +0530 Subject: [PATCH 35/63] fix(p&l_statement): disable accumulated value filter by default (backport #53488) (#53490) Co-authored-by: diptanilsaha fix(p&l_statement): disable accumulated value filter by default (#53488) --- .../profit_and_loss_statement/profit_and_loss_statement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js index e679f1f5728..5c9aaa62c94 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -39,7 +39,7 @@ frappe.query_reports[PL_REPORT_NAME]["filters"].push( fieldname: "accumulated_values", label: __("Accumulated Values"), fieldtype: "Check", - default: 1, + default: 0, }, { fieldname: "include_default_book_entries", From cecdcffce8f564fa14a40fbe339658d0b31dd5c3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 16 Mar 2026 14:44:31 +0530 Subject: [PATCH 36/63] fix: stock adjustment entry (cherry picked from commit af3067ee2324c15265f97fe48ec532deb9016d56) --- erpnext/stock/stock_ledger.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 43ac974a88b..dadc586edb4 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -925,7 +925,10 @@ class update_entries_after: if ( sle.is_adjustment_entry and flt(sle.qty_after_transaction, self.flt_precision) == 0 - and flt(sle.stock_value, self.currency_precision) != 0 + and ( + flt(sle.stock_value, self.currency_precision) != 0 + or flt(sle.stock_value_difference, self.currency_precision) == 0 + ) ): sle.stock_value_difference = ( get_stock_value_difference( From b7400b9b3594b115276141eada8522cbdfdef419 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 12:10:19 +0000 Subject: [PATCH 37/63] fix(support-settings): disable the auto-close tickets feature if `close_issue_after_days` is set to 0 (backport #53499) (#53505) Co-authored-by: diptanilsaha fix(support-settings): disable the auto-close tickets feature if `close_issue_after_days` is set to 0 (#53499) --- erpnext/support/doctype/issue/issue.py | 10 ++++++++-- .../doctype/support_settings/support_settings.json | 6 ++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 22db45e36ac..70ced430257 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -227,8 +227,14 @@ def set_status(name, status): def auto_close_tickets(): - """Auto-close replied support tickets after 7 days""" - auto_close_after_days = frappe.db.get_single_value("Support Settings", "close_issue_after_days") or 7 + """ + Auto-close replied support tickets as defined on `close_issue_after_days` in Support Settings. + Disables the feature if `close_issue_after_days` is set to 0. + """ + auto_close_after_days = frappe.db.get_single_value("Support Settings", "close_issue_after_days") + + if not auto_close_after_days: + return table = frappe.qb.DocType("Issue") issues = ( diff --git a/erpnext/support/doctype/support_settings/support_settings.json b/erpnext/support/doctype/support_settings/support_settings.json index a47f97b3889..536ce45f4e1 100644 --- a/erpnext/support/doctype/support_settings/support_settings.json +++ b/erpnext/support/doctype/support_settings/support_settings.json @@ -37,9 +37,11 @@ }, { "default": "7", + "description": "Set this value to 0 to disable the feature.", "fieldname": "close_issue_after_days", "fieldtype": "Int", - "label": "Close Issue After Days" + "label": "Close Issue After Days", + "non_negative": 1 }, { "fieldname": "portal_sb", @@ -157,7 +159,7 @@ "hide_toolbar": 1, "issingle": 1, "links": [], - "modified": "2026-01-02 18:18:11.475222", + "modified": "2026-03-16 16:33:45.859541", "modified_by": "Administrator", "module": "Support", "name": "Support Settings", From 7af4acab297182be5eeeaaee1fe330d4c20b991a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 16 Mar 2026 23:08:55 +0530 Subject: [PATCH 38/63] fix: do not set valuation rate for invoice without update stock (cherry picked from commit bec9e48435b6294138002f3b5f864ce631ce500a) --- erpnext/controllers/selling_controller.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 742d8627fdf..8138a94693c 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -526,6 +526,9 @@ class SellingController(StockController): if self.doctype not in ("Delivery Note", "Sales Invoice"): return + if self.doctype == "Sales Invoice" and not self.update_stock and not self.is_internal_transfer(): + return + from erpnext.stock.serial_batch_bundle import get_batch_nos, get_serial_nos allow_at_arms_length_price = frappe.get_cached_value( From e72f398b7ca2ec677fbace115fcdd5f65bd4239c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 18:26:52 +0000 Subject: [PATCH 39/63] fix: change "Date" label to "Posting Date" in Sales Invoice and Purchase Invoice (backport #53503) (#53517) Co-authored-by: Abdus Samad <120767334+Samad-11@users.noreply.github.com> fix: change "Date" label to "Posting Date" in Sales Invoice and Purchase Invoice (#53503) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json | 2 +- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 24c32fca451..6291cf49bae 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -321,7 +321,7 @@ "fieldtype": "Date", "in_list_view": 1, "in_standard_filter": 1, - "label": "Date", + "label": "Posting Date", "oldfieldname": "posting_date", "oldfieldtype": "Date", "print_hide": 1, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index b67552788fa..50734582dad 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -383,7 +383,7 @@ "hide_seconds": 1, "in_list_view": 1, "in_standard_filter": 1, - "label": "Date", + "label": "Posting Date", "no_copy": 1, "oldfieldname": "posting_date", "oldfieldtype": "Date", From 8753ed9992a6deac6d3897f69bfe256ea4606217 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 18:51:35 +0000 Subject: [PATCH 40/63] feat: Adding requested qty in packed item (backport #53486) (#53521) * feat: Adding requested qty in packed item (#53486) * feat: Adding requested qty in packed item * fix: correct import path --------- Co-authored-by: Nishka Gosalia Co-authored-by: Mihir Kandoi (cherry picked from commit 953f089c063980502d1735dbfca7ca17b1d115c5) # Conflicts: # erpnext/patches.txt * chore: resolve conflicts --------- Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com> Co-authored-by: Mihir Kandoi --- erpnext/controllers/status_updater.py | 5 +- erpnext/patches.txt | 1 + .../v16_0/update_requested_qty_packed_item.py | 24 +++++++++ .../doctype/sales_order/sales_order.py | 50 +++++++++---------- .../doctype/sales_order/test_sales_order.py | 26 ++++++++++ .../material_request/material_request.py | 11 +++- .../doctype/packed_item/packed_item.json | 12 ++++- .../stock/doctype/packed_item/packed_item.py | 1 + 8 files changed, 100 insertions(+), 30 deletions(-) create mode 100644 erpnext/patches/v16_0/update_requested_qty_packed_item.py diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 4ec5e739143..fe0f8a831aa 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -444,7 +444,10 @@ class StatusUpdater(Document): ): return - if args["source_dt"] != "Pick List Item" and args["target_dt"] != "Quotation Item": + if args["source_dt"] != "Pick List Item" and args["target_dt"] not in [ + "Quotation Item", + "Packed Item", + ]: if qty_or_amount == "qty": action_msg = _( 'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.' diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c80d2a2969b..40ff506d3e8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -470,3 +470,4 @@ erpnext.patches.v16_0.complete_onboarding_steps_for_older_sites #2 erpnext.patches.v16_0.migrate_asset_type_checkboxes_to_select erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po erpnext.patches.v16_0.enable_serial_batch_setting +erpnext.patches.v16_0.update_requested_qty_packed_item diff --git a/erpnext/patches/v16_0/update_requested_qty_packed_item.py b/erpnext/patches/v16_0/update_requested_qty_packed_item.py new file mode 100644 index 00000000000..82a636d79bf --- /dev/null +++ b/erpnext/patches/v16_0/update_requested_qty_packed_item.py @@ -0,0 +1,24 @@ +import frappe +from frappe.query_builder.functions import Sum + + +def execute(): + MaterialRequestItem = frappe.qb.DocType("Material Request Item") + + mri_query = ( + frappe.qb.from_(MaterialRequestItem) + .select(MaterialRequestItem.packed_item, Sum(MaterialRequestItem.qty)) + .where((MaterialRequestItem.packed_item.isnotnull()) & (MaterialRequestItem.docstatus == 1)) + .groupby(MaterialRequestItem.packed_item) + ) + + mri_data = mri_query.run() + + if not mri_data: + return + + updates_against_mr = {data[0]: {"requested_qty": data[1]} for data in mri_data} + + frappe.db.auto_commit_on_many_writes = True + frappe.db.bulk_update("Packed Item", updates_against_mr) + frappe.db.auto_commit_on_many_writes = False diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 5c6f64278af..7918dced389 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1003,20 +1003,19 @@ def close_or_unclose_sales_orders(names, status): def get_requested_item_qty(sales_order): result = {} - for d in frappe.db.get_all( - "Material Request Item", - filters={"docstatus": 1, "sales_order": sales_order}, - fields=[ - "sales_order_item", - "packed_item", - {"SUM": "qty", "as": "qty"}, - {"SUM": "received_qty", "as": "received_qty"}, - ], - group_by="sales_order_item, packed_item", - ): - result[d.sales_order_item or d.packed_item] = frappe._dict( - {"qty": d.qty, "received_qty": d.received_qty} - ) + + so = frappe.get_doc("Sales Order", sales_order) + + for item in so.items: + if is_product_bundle(item.item_code): + for packed_item in so.get("packed_items"): + if ( + packed_item.parent_item == item.item_code + and packed_item.parent_detail_docname == item.name + ): + result[packed_item.name] = frappe._dict({"qty": packed_item.requested_qty}) + else: + result[item.name] = frappe._dict({"qty": item.requested_qty}) return result @@ -1035,8 +1034,7 @@ def make_material_request(source_name, target_doc=None): flt(so_item.qty) - flt(requested_item_qty.get(so_item.name, {}).get("qty")) - max( - flt(so_item.get("delivered_qty")) - - flt(requested_item_qty.get(so_item.name, {}).get("received_qty")), + flt(so_item.get("delivered_qty")), 0, ) ) @@ -1051,16 +1049,12 @@ def make_material_request(source_name, target_doc=None): ) return flt( - ( - flt(so_item.qty) - - flt(requested_item_qty.get(so_item.name, {}).get("qty")) - - max( - flt(delivered_qty) * flt(bundle_item_qty) - - flt(requested_item_qty.get(so_item.name, {}).get("received_qty")), - 0, - ) + flt(so_item.qty) + - flt(requested_item_qty.get(so_item.name, {}).get("qty")) + - max( + flt(delivered_qty) * flt(bundle_item_qty), + 0, ) - * bundle_item_qty ) def update_item(source, target, source_parent): @@ -1122,8 +1116,10 @@ def make_material_request(source_name, target_doc=None): target_doc, postprocess, ) - - return doc + if doc and doc.items: + return doc + else: + frappe.throw(_("Material Request already created for the ordered quantity")) @frappe.whitelist() diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 6c7ae9c0660..405583aeca0 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -57,6 +57,32 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase): frappe.db.rollback() frappe.set_user("Administrator") + def test_sales_order_with_product_bundle_for_partial_material_request(self): + product_bundle = make_product_bundle( + "_Test Product Bundle Item", ["_Test Item", "_Test Item Home Desktop 100"] + ) + so = make_sales_order(item_code=product_bundle.name, qty=2) + mr = make_material_request(so.name) + mr.items[0].qty = 4 + mr.items[1].qty = 2 + mr.items[0].schedule_date = today() + mr.items[1].schedule_date = today() + mr.save() + mr.submit() + mr.reload() + self.assertEqual(mr.items[0].qty, 4) + mr = make_material_request(so.name) + self.assertEqual(mr.items[0].qty, 6) + + def test_sales_order_with_full_material_request(self): + so = make_sales_order(item_code="_Test Item", qty=5, do_not_submit=True) + so.submit() + mr = make_material_request(so.name) + mr.save() + mr.submit() + mr.reload() + self.assertRaises(frappe.ValidationError, make_material_request, so.name) + def test_sales_order_skip_delivery_note(self): so = make_sales_order(do_not_submit=True) so.order_type = "Maintenance" diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 09d33a36463..24e8ac4b1a9 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -95,7 +95,16 @@ class MaterialRequest(BuyingController): "join_field": "sales_order_item", "target_ref_field": "stock_qty", "source_field": "stock_qty", - } + }, + { + "source_dt": "Material Request Item", + "target_dt": "Packed Item", + "target_field": "requested_qty", + "target_parent_dt": "Sales Order", + "join_field": "packed_item", + "target_ref_field": "qty", + "source_field": "qty", + }, ] def check_if_already_pulled(self): diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json index 8938d503356..1614bfe2ab7 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.json +++ b/erpnext/stock/doctype/packed_item/packed_item.json @@ -34,6 +34,7 @@ "projected_qty", "ordered_qty", "packed_qty", + "requested_qty", "column_break_16", "incoming_rate", "picked_qty", @@ -298,13 +299,22 @@ "fieldtype": "Check", "label": "Supplier delivers to Customer", "read_only": 1 + }, + { + "default": "0", + "fieldname": "requested_qty", + "fieldtype": "Float", + "label": "Requested Qty", + "non_negative": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-07-09 19:12:45.850219", + "modified": "2026-03-16 18:10:47.511381", "modified_by": "Administrator", "module": "Stock", "name": "Packed Item", diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index f8bb88f6151..5a6531f1526 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -45,6 +45,7 @@ class PackedItem(Document): projected_qty: DF.Float qty: DF.Float rate: DF.Currency + requested_qty: DF.Float serial_and_batch_bundle: DF.Link | None serial_no: DF.Text | None target_warehouse: DF.Link | None From c9efcb782206799a55ed61e92b24a1412daacb71 Mon Sep 17 00:00:00 2001 From: Nikhil Kothari Date: Tue, 17 Mar 2026 10:17:32 +0530 Subject: [PATCH 41/63] fix(banking): include paid purchase invoices in reports and bank clearance (#52675) * fix(banking): include paid purchase invoices in reports and bank clearance * fix: condition for amounts not reflected in system * fix: set Sales Invoice to be the payment document in bank rec * fix: add additional filter for `is_paid` * fix: added is_paid * fix: added invoice number in bank clearance tool * chore: make requested changes * fix: exclude opening JEs * fix: bring back banking icon in desktop (cherry picked from commit ef3262216651515548a61c1a0d870bb3a3aa16f9) # Conflicts: # erpnext/accounts/doctype/bank_clearance/bank_clearance.py --- .../doctype/bank_clearance/bank_clearance.py | 239 ++++++++++------ .../bank_clearance_summary.py | 119 +++++--- .../bank_reconciliation_statement.py | 256 +++++++++++++----- erpnext/desktop_icon/banking.json | 2 +- 4 files changed, 427 insertions(+), 189 deletions(-) diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py index 231e9cdf522..ae39c946dc6 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py @@ -5,8 +5,14 @@ import frappe from frappe import _, msgprint from frappe.model.document import Document +from frappe.query_builder import Case from frappe.query_builder.custom import ConstantColumn +<<<<<<< HEAD from frappe.utils import cint, flt, fmt_money, get_link_to_form, getdate +======= +from frappe.query_builder.functions import Coalesce, Sum +from frappe.utils import cint, flt, fmt_money, getdate +>>>>>>> ef32622166 (fix(banking): include paid purchase invoices in reports and bank clearance (#52675)) from pypika import Order import erpnext @@ -182,65 +188,162 @@ def get_payment_entries_for_bank_clearance( ): entries = [] - condition = "" - pe_condition = "" + journal_entry = frappe.qb.DocType("Journal Entry") + journal_entry_account = frappe.qb.DocType("Journal Entry Account") + + journal_entry_query = ( + frappe.qb.from_(journal_entry_account) + .inner_join(journal_entry) + .on(journal_entry_account.parent == journal_entry.name) + .select( + ConstantColumn("Journal Entry").as_("payment_document"), + journal_entry.name.as_("payment_entry"), + journal_entry.cheque_no.as_("cheque_number"), + journal_entry.cheque_date, + Sum(journal_entry_account.debit_in_account_currency).as_("debit"), + Sum(journal_entry_account.credit_in_account_currency).as_("credit"), + journal_entry.posting_date, + journal_entry_account.against_account, + journal_entry.clearance_date, + journal_entry_account.account_currency, + ) + .where( + (journal_entry_account.account == account) + & (journal_entry.docstatus == 1) + & (journal_entry.posting_date >= from_date) + & (journal_entry.posting_date <= to_date) + & (journal_entry.is_opening == "No") + ) + ) + if not include_reconciled_entries: - condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')" - pe_condition = "and (pe.clearance_date IS NULL or pe.clearance_date='0000-00-00')" + journal_entry_query = journal_entry_query.where( + (journal_entry.clearance_date.isnull()) | (journal_entry.clearance_date == "0000-00-00") + ) - journal_entries = frappe.db.sql( - f""" - select - "Journal Entry" as payment_document, t1.name as payment_entry, - t1.cheque_no as cheque_number, t1.cheque_date, - sum(t2.debit_in_account_currency) as debit, sum(t2.credit_in_account_currency) as credit, - t1.posting_date, t2.against_account, t1.clearance_date, t2.account_currency - from - `tabJournal Entry` t1, `tabJournal Entry Account` t2 - where - t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1 - and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s - and ifnull(t1.is_opening, 'No') = 'No' {condition} - group by t2.account, t1.name - order by t1.posting_date ASC, t1.name DESC - """, - {"account": account, "from": from_date, "to": to_date}, - as_dict=1, + journal_entries = ( + journal_entry_query.groupby(journal_entry_account.account, journal_entry.name) + .orderby(journal_entry.posting_date) + .orderby(journal_entry.name, order=Order.desc) + ).run(as_dict=True) + + pe = frappe.qb.DocType("Payment Entry") + company = frappe.qb.DocType("Company") + payment_entry_query = ( + frappe.qb.from_(pe) + .join(company) + .on(pe.company == company.name) + .select( + ConstantColumn("Payment Entry").as_("payment_document"), + pe.name.as_("payment_entry"), + pe.reference_no.as_("cheque_number"), + pe.reference_date.as_("cheque_date"), + ( + Case() + .when( + pe.paid_from == account, + ( + pe.paid_amount + + ( + Case() + .when( + (pe.payment_type == "Pay") + & (company.default_currency == pe.paid_from_account_currency), + pe.base_total_taxes_and_charges, + ) + .else_(pe.total_taxes_and_charges) + ) + ), + ) + .else_(0) + ).as_("credit"), + ( + Case() + .when(pe.paid_from == account, 0) + .else_( + pe.received_amount + + ( + Case() + .when( + company.default_currency == pe.paid_to_account_currency, + pe.base_total_taxes_and_charges, + ) + .else_(pe.total_taxes_and_charges) + ) + ) + ).as_("debit"), + pe.posting_date, + Coalesce(pe.party, Case().when(pe.paid_from == account, pe.paid_to).else_(pe.paid_from)).as_( + "against_account" + ), + pe.clearance_date, + ( + Case() + .when(pe.paid_to == account, pe.paid_to_account_currency) + .else_(pe.paid_from_account_currency) + ).as_("account_currency"), + ) + .where( + ((pe.paid_from == account) | (pe.paid_to == account)) + & (pe.docstatus == 1) + & (pe.posting_date >= from_date) + & (pe.posting_date <= to_date) + ) ) - payment_entries = frappe.db.sql( - f""" - select - "Payment Entry" as payment_document, pe.name as payment_entry, - pe.reference_no as cheque_number, pe.reference_date as cheque_date, - if(pe.paid_from=%(account)s, pe.paid_amount + if(pe.payment_type = 'Pay' and c.default_currency = pe.paid_from_account_currency, pe.base_total_taxes_and_charges, pe.total_taxes_and_charges) , 0) as credit, - if(pe.paid_from=%(account)s, 0, pe.received_amount + pe.total_taxes_and_charges) as debit, - pe.posting_date, ifnull(pe.party,if(pe.paid_from=%(account)s,pe.paid_to,pe.paid_from)) as against_account, pe.clearance_date, - if(pe.paid_to=%(account)s, pe.paid_to_account_currency, pe.paid_from_account_currency) as account_currency - from `tabPayment Entry` as pe - join `tabCompany` c on c.name = pe.company - where - (pe.paid_from=%(account)s or pe.paid_to=%(account)s) and pe.docstatus=1 - and pe.posting_date >= %(from)s and pe.posting_date <= %(to)s - {pe_condition} - order by - pe.posting_date ASC, pe.name DESC - """, - { - "account": account, - "from": from_date, - "to": to_date, - }, - as_dict=1, + if not include_reconciled_entries: + payment_entry_query = payment_entry_query.where( + (pe.clearance_date.isnull()) | (pe.clearance_date == "0000-00-00") + ) + + payment_entries = (payment_entry_query.orderby(pe.posting_date).orderby(pe.name, order=Order.desc)).run( + as_dict=True ) - pos_sales_invoices, pos_purchase_invoices = [], [] + acc = frappe.qb.DocType("Account") + + pi = frappe.qb.DocType("Purchase Invoice") + + paid_purchase_invoices_query = ( + frappe.qb.from_(pi) + .inner_join(acc) + .on(pi.cash_bank_account == acc.name) + .select( + ConstantColumn("Purchase Invoice").as_("payment_document"), + pi.name.as_("payment_entry"), + pi.paid_amount.as_("credit"), + pi.posting_date, + pi.supplier.as_("against_account"), + pi.bill_no.as_("cheque_number"), + pi.clearance_date, + acc.account_currency, + ConstantColumn(0).as_("debit"), + ) + .where( + (pi.docstatus == 1) + & (pi.is_paid == 1) + & (pi.cash_bank_account == account) + & (pi.posting_date >= from_date) + & (pi.posting_date <= to_date) + ) + ) + + if not include_reconciled_entries: + paid_purchase_invoices_query = paid_purchase_invoices_query.where( + (pi.clearance_date.isnull()) | (pi.clearance_date == "0000-00-00") + ) + + paid_purchase_invoices = ( + paid_purchase_invoices_query.orderby(pi.posting_date).orderby(pi.name, order=Order.desc) + ).run(as_dict=True) + + pos_sales_invoices = [] + if include_pos_transactions: si_payment = frappe.qb.DocType("Sales Invoice Payment") si = frappe.qb.DocType("Sales Invoice") - acc = frappe.qb.DocType("Account") - pos_sales_invoices = ( + pos_sales_invoices_query = ( frappe.qb.from_(si_payment) .inner_join(si) .on(si_payment.parent == si.name) @@ -263,38 +366,22 @@ def get_payment_entries_for_bank_clearance( & (si.posting_date >= from_date) & (si.posting_date <= to_date) ) - .orderby(si.posting_date) - .orderby(si.name, order=Order.desc) - ).run(as_dict=True) + ) - pi = frappe.qb.DocType("Purchase Invoice") + if not include_reconciled_entries: + pos_sales_invoices_query = pos_sales_invoices_query.where( + (si_payment.clearance_date.isnull()) | (si_payment.clearance_date == "0000-00-00") + ) - pos_purchase_invoices = ( - frappe.qb.from_(pi) - .inner_join(acc) - .on(pi.cash_bank_account == acc.name) - .select( - ConstantColumn("Purchase Invoice").as_("payment_document"), - pi.name.as_("payment_entry"), - pi.paid_amount.as_("credit"), - pi.posting_date, - pi.supplier.as_("against_account"), - pi.clearance_date, - acc.account_currency, - ConstantColumn(0).as_("debit"), - ) - .where( - (pi.docstatus == 1) - & (pi.cash_bank_account == account) - & (pi.posting_date >= from_date) - & (pi.posting_date <= to_date) - ) - .orderby(pi.posting_date) - .orderby(pi.name, order=Order.desc) + pos_sales_invoices = ( + pos_sales_invoices_query.orderby(si.posting_date).orderby(si.name, order=Order.desc) ).run(as_dict=True) entries = ( - list(payment_entries) + list(journal_entries) + list(pos_sales_invoices) + list(pos_purchase_invoices) + list(payment_entries) + + list(journal_entries) + + list(pos_sales_invoices) + + list(paid_purchase_invoices) ) return entries diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py index ae675670446..5320de2b66c 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py @@ -4,7 +4,10 @@ import frappe from frappe import _ -from frappe.utils import getdate, nowdate +from frappe.query_builder import Case +from frappe.query_builder.custom import ConstantColumn +from frappe.utils import getdate +from pypika import Order def execute(filters=None): @@ -48,17 +51,6 @@ def get_columns(): return columns -def get_conditions(filters): - conditions = "" - - if filters.get("from_date"): - conditions += " and posting_date>=%(from_date)s" - if filters.get("to_date"): - conditions += " and posting_date<=%(to_date)s" - - return conditions - - def get_entries(filters): entries = [] @@ -73,41 +65,90 @@ def get_entries(filters): return sorted( entries, - key=lambda k: k[2].strftime("%H%M%S") or getdate(nowdate()), + key=lambda k: getdate(k[2]), ) def get_entries_for_bank_clearance_summary(filters): entries = [] - conditions = get_conditions(filters) + je = frappe.qb.DocType("Journal Entry") + jea = frappe.qb.DocType("Journal Entry Account") - journal_entries = frappe.db.sql( - f"""SELECT - "Journal Entry", jv.name, jv.posting_date, jv.cheque_no, - jv.clearance_date, jvd.against_account, jvd.debit - jvd.credit - FROM - `tabJournal Entry Account` jvd, `tabJournal Entry` jv - WHERE - jvd.parent = jv.name and jv.docstatus=1 and jvd.account = %(account)s {conditions} - order by posting_date DESC, jv.name DESC""", - filters, - as_list=1, - ) + journal_entries = ( + frappe.qb.from_(jea) + .inner_join(je) + .on(jea.parent == je.name) + .select( + ConstantColumn("Journal Entry").as_("payment_document"), + je.name.as_("payment_entry"), + je.posting_date, + je.cheque_no, + je.clearance_date, + jea.against_account, + jea.debit_in_account_currency - jea.credit_in_account_currency, + ) + .where( + (jea.account == filters.account) + & (je.docstatus == 1) + & (je.posting_date >= filters.from_date) + & (je.posting_date <= filters.to_date) + & ((je.is_opening == "No") | (je.is_opening.isnull())) + ) + .orderby(je.posting_date, order=Order.desc) + .orderby(je.name, order=Order.desc) + ).run(as_list=True) - payment_entries = frappe.db.sql( - f"""SELECT - "Payment Entry", name, posting_date, reference_no, clearance_date, party, - if(paid_from=%(account)s, ((paid_amount * -1) - total_taxes_and_charges) , received_amount) - FROM - `tabPayment Entry` - WHERE - docstatus=1 and (paid_from = %(account)s or paid_to = %(account)s) {conditions} - order by posting_date DESC, name DESC""", - filters, - as_list=1, - ) + pe = frappe.qb.DocType("Payment Entry") + payment_entries = ( + frappe.qb.from_(pe) + .select( + ConstantColumn("Payment Entry").as_("payment_document"), + pe.name.as_("payment_entry"), + pe.posting_date, + pe.reference_no.as_("cheque_no"), + pe.clearance_date, + pe.party.as_("against_account"), + Case() + .when( + (pe.paid_from == filters.account), + ((pe.paid_amount * -1) - pe.total_taxes_and_charges), + ) + .else_(pe.received_amount), + ) + .where((pe.paid_from == filters.account) | (pe.paid_to == filters.account)) + .where( + (pe.docstatus == 1) + & (pe.posting_date >= filters.from_date) + & (pe.posting_date <= filters.to_date) + ) + .orderby(pe.posting_date, order=Order.desc) + .orderby(pe.name, order=Order.desc) + ).run(as_list=True) - entries = journal_entries + payment_entries + pi = frappe.qb.DocType("Purchase Invoice") + purchase_invoices = ( + frappe.qb.from_(pi) + .select( + ConstantColumn("Purchase Invoice").as_("payment_document"), + pi.name.as_("payment_entry"), + pi.posting_date, + pi.bill_no.as_("cheque_no"), + pi.clearance_date, + pi.supplier.as_("against_account"), + (pi.paid_amount * -1).as_("amount"), + ) + .where( + (pi.docstatus == 1) + & (pi.is_paid == 1) + & (pi.cash_bank_account == filters.account) + & (pi.posting_date >= filters.from_date) + & (pi.posting_date <= filters.to_date) + ) + .orderby(pi.posting_date, order=Order.desc) + .orderby(pi.name, order=Order.desc) + ).run(as_list=True) + + entries = journal_entries + payment_entries + purchase_invoices return entries diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py index bfc2f2d56ff..474e25c5474 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py @@ -4,7 +4,11 @@ import frappe from frappe import _ +from frappe.query_builder import Case +from frappe.query_builder.custom import ConstantColumn +from frappe.query_builder.functions import Coalesce, Sum from frappe.utils import flt, getdate +from pypika import Order from erpnext.accounts.utils import get_balance_on @@ -123,73 +127,143 @@ def get_entries_for_bank_reconciliation_statement(filters): payment_entries = get_payment_entries(filters) + purchase_invoices = get_purchase_invoices(filters) + pos_entries = [] if filters.include_pos_transactions: pos_entries = get_pos_entries(filters) - return list(journal_entries) + list(payment_entries) + list(pos_entries) + return list(journal_entries) + list(payment_entries) + list(pos_entries) + list(purchase_invoices) def get_journal_entries(filters): - return frappe.db.sql( - """ - select "Journal Entry" as payment_document, jv.posting_date, - jv.name as payment_entry, jvd.debit_in_account_currency as debit, - jvd.credit_in_account_currency as credit, jvd.against_account, - jv.cheque_no as reference_no, jv.cheque_date as ref_date, jv.clearance_date, jvd.account_currency - from - `tabJournal Entry Account` jvd, `tabJournal Entry` jv - where jvd.parent = jv.name and jv.docstatus=1 - and jvd.account = %(account)s and jv.posting_date <= %(report_date)s - and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s - and ifnull(jv.is_opening, 'No') = 'No' - and jv.company = %(company)s """, - filters, - as_dict=1, - ) + je = frappe.qb.DocType("Journal Entry") + jea = frappe.qb.DocType("Journal Entry Account") + return ( + frappe.qb.from_(jea) + .join(je) + .on(jea.parent == je.name) + .select( + ConstantColumn("Journal Entry").as_("payment_document"), + je.name.as_("payment_entry"), + je.posting_date, + jea.debit_in_account_currency.as_("debit"), + jea.credit_in_account_currency.as_("credit"), + jea.against_account, + je.cheque_no.as_("reference_no"), + je.cheque_date.as_("ref_date"), + je.clearance_date, + jea.account_currency, + ) + .where( + (je.docstatus == 1) + & (jea.account == filters.account) + & (je.posting_date <= filters.report_date) + & (je.clearance_date.isnull() | (je.clearance_date > filters.report_date)) + & (je.company == filters.company) + & ((je.is_opening.isnull()) | (je.is_opening == "No")) + ) + .orderby(je.posting_date) + .orderby(je.name, order=Order.desc) + ).run(as_dict=True) def get_payment_entries(filters): - return frappe.db.sql( - """ - select - "Payment Entry" as payment_document, name as payment_entry, - reference_no, reference_date as ref_date, - if(paid_to=%(account)s, received_amount_after_tax, 0) as debit, - if(paid_from=%(account)s, paid_amount_after_tax, 0) as credit, - posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date, - if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency - from `tabPayment Entry` - where - (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1 - and posting_date <= %(report_date)s - and ifnull(clearance_date, '4000-01-01') > %(report_date)s - and company = %(company)s - """, - filters, - as_dict=1, - ) + pe = frappe.qb.DocType("Payment Entry") + return ( + frappe.qb.from_(pe) + .select( + ConstantColumn("Payment Entry").as_("payment_document"), + pe.name.as_("payment_entry"), + pe.reference_no.as_("reference_no"), + pe.reference_date.as_("ref_date"), + Case().when(pe.paid_to == filters.account, pe.received_amount_after_tax).else_(0).as_("debit"), + Case().when(pe.paid_from == filters.account, pe.paid_amount_after_tax).else_(0).as_("credit"), + pe.posting_date, + Coalesce( + pe.party, Case().when(pe.paid_from == filters.account, pe.paid_to).else_(pe.paid_from) + ).as_("against_account"), + pe.clearance_date, + ( + Case() + .when(pe.paid_to == filters.account, pe.paid_to_account_currency) + .else_(pe.paid_from_account_currency) + ).as_("account_currency"), + ) + .where( + (pe.docstatus == 1) + & ((pe.paid_from == filters.account) | (pe.paid_to == filters.account)) + & (pe.posting_date <= filters.report_date) + & (pe.clearance_date.isnull() | (pe.clearance_date > filters.report_date)) + & (pe.company == filters.company) + ) + .orderby(pe.posting_date) + .orderby(pe.name, order=Order.desc) + ).run(as_dict=True) + + +def get_purchase_invoices(filters): + pi = frappe.qb.DocType("Purchase Invoice") + acc = frappe.qb.DocType("Account") + return ( + frappe.qb.from_(pi) + .inner_join(acc) + .on(pi.cash_bank_account == acc.name) + .select( + ConstantColumn("Purchase Invoice").as_("payment_document"), + pi.name.as_("payment_entry"), + pi.bill_no.as_("reference_no"), + pi.posting_date.as_("ref_date"), + Case().when(pi.paid_amount < 0, pi.paid_amount * -1).else_(0).as_("debit"), + Case().when(pi.paid_amount > 0, pi.paid_amount).else_(0).as_("credit"), + pi.posting_date, + pi.supplier.as_("against_account"), + pi.clearance_date, + acc.account_currency, + ) + .where( + (pi.docstatus == 1) + & (pi.is_paid == 1) + & (pi.cash_bank_account == filters.account) + & (pi.posting_date <= filters.report_date) + & (pi.clearance_date.isnull() | (pi.clearance_date > filters.report_date)) + & (pi.company == filters.company) + ) + .orderby(pi.posting_date) + .orderby(pi.name, order=Order.desc) + ).run(as_dict=True) def get_pos_entries(filters): - return frappe.db.sql( - """ - select - "Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit, - si.posting_date, si.debit_to as against_account, sip.clearance_date, - account.account_currency, 0 as credit - from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account - where - sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name - and account.name = sip.account and si.posting_date <= %(report_date)s and - ifnull(sip.clearance_date, '4000-01-01') > %(report_date)s - and si.company = %(company)s - order by - si.posting_date ASC, si.name DESC - """, - filters, - as_dict=1, - ) + si = frappe.qb.DocType("Sales Invoice") + si_payment = frappe.qb.DocType("Sales Invoice Payment") + acc = frappe.qb.DocType("Account") + return ( + frappe.qb.from_(si_payment) + .join(si) + .on(si_payment.parent == si.name) + .join(acc) + .on(si_payment.account == acc.name) + .select( + ConstantColumn("Sales Invoice").as_("payment_document"), + si.name.as_("payment_entry"), + si_payment.amount.as_("debit"), + si.posting_date, + si.debit_to.as_("against_account"), + si_payment.clearance_date, + acc.account_currency, + ConstantColumn(0).as_("credit"), + ) + .where( + (si_payment.account == filters.account) + & (si.docstatus == 1) + & (si.posting_date <= filters.report_date) + & (si_payment.clearance_date.isnull() | (si_payment.clearance_date > filters.report_date)) + & (si.company == filters.company) + ) + .orderby(si.posting_date) + .orderby(si_payment.name, order=Order.desc) + ).run(as_dict=True) def get_amounts_not_reflected_in_system(filters): @@ -205,30 +279,66 @@ def get_amounts_not_reflected_in_system(filters): def get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters): - je_amount = frappe.db.sql( - """ - select sum(jvd.debit_in_account_currency - jvd.credit_in_account_currency) - from `tabJournal Entry Account` jvd, `tabJournal Entry` jv - where jvd.parent = jv.name and jv.docstatus=1 and jvd.account=%(account)s - and jv.posting_date > %(report_date)s and jv.clearance_date <= %(report_date)s - and ifnull(jv.is_opening, 'No') = 'No' """, - filters, + je = frappe.qb.DocType("Journal Entry") + jea = frappe.qb.DocType("Journal Entry Account") + + je_amount = ( + frappe.qb.from_(jea) + .inner_join(je) + .on(jea.parent == je.name) + .select( + Sum(jea.debit_in_account_currency - jea.credit_in_account_currency).as_("amount"), + ) + .where( + (je.docstatus == 1) + & (jea.account == filters.account) + & (je.posting_date > filters.report_date) + & (je.clearance_date <= filters.report_date) + & (je.company == filters.company) + & ((je.is_opening.isnull()) | (je.is_opening == "No")) + ) + .run(as_dict=True) ) + je_amount = flt(je_amount[0].amount) if je_amount else 0.0 - je_amount = flt(je_amount[0][0]) if je_amount else 0.0 - - pe_amount = frappe.db.sql( - """ - select sum(if(paid_from=%(account)s, paid_amount, received_amount)) - from `tabPayment Entry` - where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1 - and posting_date > %(report_date)s and clearance_date <= %(report_date)s""", - filters, + pe = frappe.qb.DocType("Payment Entry") + pe_amount = ( + frappe.qb.from_(pe) + .select( + Sum(Case().when(pe.paid_from == filters.account, pe.paid_amount).else_(pe.received_amount)).as_( + "amount" + ), + ) + .where( + ((pe.paid_from == filters.account) | (pe.paid_to == filters.account)) + & (pe.docstatus == 1) + & (pe.posting_date > filters.report_date) + & (pe.clearance_date <= filters.report_date) + & (pe.company == filters.company) + ) + .run(as_dict=True) ) + pe_amount = flt(pe_amount[0].amount) if pe_amount else 0.0 - pe_amount = flt(pe_amount[0][0]) if pe_amount else 0.0 + pi = frappe.qb.DocType("Purchase Invoice") + pi_amount = ( + frappe.qb.from_(pi) + .select( + Sum(pi.paid_amount).as_("amount"), + ) + .where( + (pi.docstatus == 1) + & (pi.is_paid == 1) + & (pi.cash_bank_account == filters.account) + & (pi.posting_date > filters.report_date) + & (pi.clearance_date <= filters.report_date) + & (pi.company == filters.company) + ) + ).run(as_dict=True) - return je_amount + pe_amount + pi_amount = flt(pi_amount[0].amount) if pi_amount else 0.0 + + return je_amount + pe_amount + pi_amount def get_balance_row(label, amount, account_currency): diff --git a/erpnext/desktop_icon/banking.json b/erpnext/desktop_icon/banking.json index 66ee8c7b318..6e55cfa0d02 100644 --- a/erpnext/desktop_icon/banking.json +++ b/erpnext/desktop_icon/banking.json @@ -10,7 +10,7 @@ "label": "Banking", "link_to": "Banking", "link_type": "Workspace Sidebar", - "modified": "2026-01-12 12:29:48.687545", + "modified": "2026-02-12 12:29:48.687545", "modified_by": "Administrator", "name": "Banking", "owner": "Administrator", From c8bb55f35bf528acb43eca7b4600edd299c94d05 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:42:37 +0530 Subject: [PATCH 42/63] fix: correct overlap detection in JobCard.has_overlap (backport #53473) (#53523) Co-authored-by: Sanjesh-Raju Co-authored-by: Sanjesh Co-authored-by: Tridots Tech fix: correct overlap detection in JobCard.has_overlap (#53473) --- erpnext/manufacturing/doctype/job_card/job_card.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index d283bc8dadb..0f4c9d569fa 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -383,9 +383,8 @@ class JobCard(Document): # if key number reaches/crosses to production_capacity means capacity is full and overlap error generated # this will store last to_time of sequential job cards alloted_capacity = {1: time_logs[0]["to_time"]} - # flag for sequential Job card found - sequential_job_card_found = False for i in range(1, len(time_logs)): + sequential_job_card_found = False # scanning for all Existing keys for key in alloted_capacity.keys(): # if current Job Card from time is greater than last to_time in that key means these job card are sequential From afe8e3a023cda96af8a6bdedcf8a437cd41e1875 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 05:32:00 +0000 Subject: [PATCH 43/63] fix: enable logs to track changes in doctype. (backport #53491) (#53524) * fix: enable logs to track changes in doctype. (#53491) Co-authored-by: jeraldin2003 Co-authored-by: Mihir Kandoi (cherry picked from commit e2667ab098975cca5edc65271bac02e8ee19de91) # Conflicts: # erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json * chore: resolve conflicts --------- Co-authored-by: Jeraldin P J <113301032+jeraldin2003@users.noreply.github.com> Co-authored-by: Mihir Kandoi --- .../currency_exchange_settings.json | 4 ++-- erpnext/accounts/doctype/pos_settings/pos_settings.json | 4 ++-- .../repost_accounting_ledger_settings.json | 4 ++-- .../doctype/subscription_settings/subscription_settings.json | 4 ++-- erpnext/buying/doctype/buying_settings/buying_settings.json | 4 ++-- .../appointment_booking_settings.json | 4 ++-- erpnext/crm/doctype/crm_settings/crm_settings.json | 4 ++-- .../manufacturing_settings/manufacturing_settings.json | 4 ++-- .../projects/doctype/projects_settings/projects_settings.json | 4 ++-- .../selling/doctype/selling_settings/selling_settings.json | 4 ++-- erpnext/setup/doctype/global_defaults/global_defaults.json | 4 ++-- .../stock/doctype/delivery_settings/delivery_settings.json | 4 ++-- .../doctype/item_variant_settings/item_variant_settings.json | 4 ++-- .../stock_reposting_settings/stock_reposting_settings.json | 4 ++-- erpnext/stock/doctype/stock_settings/stock_settings.json | 4 ++-- .../support/doctype/support_settings/support_settings.json | 2 +- 16 files changed, 31 insertions(+), 31 deletions(-) diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json index ae3dbd59d69..9f0852bb686 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json @@ -101,11 +101,11 @@ "label": "Use HTTP Protocol" } ], - "hide_toolbar": 1, + "hide_toolbar": 0, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-01-02 18:19:02.873815", + "modified": "2026-03-16 13:28:21.075743", "modified_by": "Administrator", "module": "Accounts", "name": "Currency Exchange Settings", diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json index 7afc19423d8..8e9adc56f14 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.json +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json @@ -50,10 +50,10 @@ "options": "1" } ], - "hide_toolbar": 1, + "hide_toolbar": 0, "issingle": 1, "links": [], - "modified": "2026-01-09 17:30:41.476806", + "modified": "2026-03-16 13:28:19.677217", "modified_by": "Administrator", "module": "Accounts", "name": "POS Settings", diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json index ba8188c1440..808986bba23 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json @@ -15,11 +15,11 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "in_create": 1, "issingle": 1, "links": [], - "modified": "2026-01-02 18:19:08.888368", + "modified": "2026-03-16 13:28:21.312607", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Accounting Ledger Settings", diff --git a/erpnext/accounts/doctype/subscription_settings/subscription_settings.json b/erpnext/accounts/doctype/subscription_settings/subscription_settings.json index 326fee345dc..06112d51fd1 100644 --- a/erpnext/accounts/doctype/subscription_settings/subscription_settings.json +++ b/erpnext/accounts/doctype/subscription_settings/subscription_settings.json @@ -31,10 +31,10 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "issingle": 1, "links": [], - "modified": "2026-01-02 18:18:34.671062", + "modified": "2026-03-16 13:28:20.485964", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Settings", diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index fbe17f3dbbc..5a0edbdf5d1 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -282,13 +282,13 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "icon": "fa fa-cog", "idx": 1, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-01-02 18:16:35.885540", + "modified": "2026-03-16 13:28:19.432589", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json index d1f70af86f1..b79e974e301 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -102,10 +102,10 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "issingle": 1, "links": [], - "modified": "2026-01-02 18:18:46.617101", + "modified": "2026-03-16 13:28:21.198138", "modified_by": "Administrator", "module": "CRM", "name": "Appointment Booking Settings", diff --git a/erpnext/crm/doctype/crm_settings/crm_settings.json b/erpnext/crm/doctype/crm_settings/crm_settings.json index 3f231d460ab..4760d504a57 100644 --- a/erpnext/crm/doctype/crm_settings/crm_settings.json +++ b/erpnext/crm/doctype/crm_settings/crm_settings.json @@ -101,12 +101,12 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "icon": "fa fa-cog", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-01-02 18:18:52.204988", + "modified": "2026-03-16 13:28:19.573964", "modified_by": "Administrator", "module": "CRM", "name": "CRM Settings", diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index f9eb0e40b20..1a150dc864f 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -239,12 +239,12 @@ "label": "Allow Editing of Items and Quantities in Work Order" } ], - "hide_toolbar": 1, + "hide_toolbar": 0, "icon": "icon-wrench", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-01-02 18:18:05.759229", + "modified": "2026-03-16 13:28:20.714576", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing Settings", diff --git a/erpnext/projects/doctype/projects_settings/projects_settings.json b/erpnext/projects/doctype/projects_settings/projects_settings.json index 08ec43fe391..5cf29d6e208 100644 --- a/erpnext/projects/doctype/projects_settings/projects_settings.json +++ b/erpnext/projects/doctype/projects_settings/projects_settings.json @@ -43,10 +43,10 @@ "label": "Fetch Timesheet in Sales Invoice" } ], - "hide_toolbar": 1, + "hide_toolbar": 0, "issingle": 1, "links": [], - "modified": "2026-01-02 18:18:28.414222", + "modified": "2026-03-16 13:28:20.265634", "modified_by": "Administrator", "module": "Projects", "name": "Projects Settings", diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 6304a55c7d8..b7896b58dff 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -323,13 +323,13 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "icon": "fa fa-cog", "idx": 1, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-02-27 00:47:46.003305", + "modified": "2026-03-16 13:28:18.988883", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.json b/erpnext/setup/doctype/global_defaults/global_defaults.json index 92bff6d4fbe..9178bb64768 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.json +++ b/erpnext/setup/doctype/global_defaults/global_defaults.json @@ -91,13 +91,13 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "icon": "fa fa-cog", "idx": 1, "in_create": 1, "issingle": 1, "links": [], - "modified": "2026-01-12 09:45:59.819161", + "modified": "2026-03-16 13:28:20.155574", "modified_by": "Administrator", "module": "Setup", "name": "Global Defaults", diff --git a/erpnext/stock/doctype/delivery_settings/delivery_settings.json b/erpnext/stock/doctype/delivery_settings/delivery_settings.json index dbcc4fcc3ed..66320e0a214 100644 --- a/erpnext/stock/doctype/delivery_settings/delivery_settings.json +++ b/erpnext/stock/doctype/delivery_settings/delivery_settings.json @@ -50,10 +50,10 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "issingle": 1, "links": [], - "modified": "2026-01-02 18:18:39.931428", + "modified": "2026-03-16 13:28:20.371015", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Settings", diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.json b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.json index 393a0b52e75..bea8feb554a 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.json +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.json @@ -49,10 +49,10 @@ "label": "Allow Variant UOM to be different from Template UOM" } ], - "hide_toolbar": 1, + "hide_toolbar": 0, "issingle": 1, "links": [], - "modified": "2026-01-02 18:18:23.005859", + "modified": "2026-03-16 13:28:20.597912", "modified_by": "Administrator", "module": "Stock", "name": "Item Variant Settings", diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json index f72b50dc131..ccca2fc9819 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json @@ -101,11 +101,11 @@ "label": "Enable Separate Reposting for GL" } ], - "hide_toolbar": 1, + "hide_toolbar": 0, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-02-25 14:11:33.461173", + "modified": "2026-03-16 13:28:20.978007", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reposting Settings", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index ba195104564..cd9ced97baf 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -558,13 +558,13 @@ "label": "Enable Serial / Batch No for Item" } ], - "hide_toolbar": 1, + "hide_toolbar": 0, "icon": "icon-cog", "idx": 1, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-02-25 10:56:34.105949", + "modified": "2026-03-16 13:28:19.254641", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", diff --git a/erpnext/support/doctype/support_settings/support_settings.json b/erpnext/support/doctype/support_settings/support_settings.json index 536ce45f4e1..eb17f97c5e7 100644 --- a/erpnext/support/doctype/support_settings/support_settings.json +++ b/erpnext/support/doctype/support_settings/support_settings.json @@ -156,7 +156,7 @@ } ], "grid_page_length": 50, - "hide_toolbar": 1, + "hide_toolbar": 0, "issingle": 1, "links": [], "modified": "2026-03-16 16:33:45.859541", From a695a111306ff3d97584522162e460912965b3eb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 05:43:50 +0000 Subject: [PATCH 44/63] fix: add item_name to quick entry fields in Item doctype (backport #53530) (#53533) Co-authored-by: Abdus Samad <120767334+Samad-11@users.noreply.github.com> fix: add item_name to quick entry fields in Item doctype (#53530) --- erpnext/stock/doctype/item/item.json | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 444c2ec0e66..fd897f734a4 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -167,6 +167,7 @@ "set_only_once": 1 }, { + "allow_in_quick_entry": 1, "bold": 1, "fieldname": "item_name", "fieldtype": "Data", From 427d5cf502c29262746801d253654e458a775601 Mon Sep 17 00:00:00 2001 From: Nikhil Kothari Date: Tue, 17 Mar 2026 11:26:28 +0530 Subject: [PATCH 45/63] chore: remove conflicts --- erpnext/accounts/doctype/bank_clearance/bank_clearance.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py index ae39c946dc6..f7451830e1d 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py @@ -7,12 +7,8 @@ from frappe import _, msgprint from frappe.model.document import Document from frappe.query_builder import Case from frappe.query_builder.custom import ConstantColumn -<<<<<<< HEAD -from frappe.utils import cint, flt, fmt_money, get_link_to_form, getdate -======= from frappe.query_builder.functions import Coalesce, Sum from frappe.utils import cint, flt, fmt_money, getdate ->>>>>>> ef32622166 (fix(banking): include paid purchase invoices in reports and bank clearance (#52675)) from pypika import Order import erpnext From 8f31b0dccd6e0fb2abfab6cac0fef2e5e4da2c79 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 16 Mar 2026 16:28:38 +0530 Subject: [PATCH 46/63] fix: valuation rate for no Use Batch wise Valuation batches (cherry picked from commit 4befa1519807236e8f093ddc0be7480d029143c2) --- erpnext/stock/deprecated_serial_batch.py | 36 +++++ .../test_serial_and_batch_bundle.py | 130 ++++++++++++++++++ .../doctype/stock_settings/stock_settings.py | 16 +++ 3 files changed, 182 insertions(+) diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index f383562e4e9..210898e2df3 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -251,6 +251,7 @@ class DeprecatedBatchNoValuation: .select( sle.batch_no, Sum(sle.actual_qty).as_("batch_qty"), + Sum(sle.stock_value_difference).as_("batch_value"), ) .where( (sle.item_code == self.sle.item_code) @@ -267,9 +268,24 @@ class DeprecatedBatchNoValuation: if self.sle.name: query = query.where(sle.name != self.sle.name) + # Moving Average items with no Use Batch wise Valuation but want to use batch wise valuation + moving_avg_item_non_batch_value = False + if valuation_method := self.get_valuation_method(self.sle.item_code): + if valuation_method == "Moving Average" and not frappe.db.get_single_value( + "Stock Settings", "do_not_use_batchwise_valuation" + ): + query = query.where(batch.use_batchwise_valuation == 0) + moving_avg_item_non_batch_value = True + batch_data = query.run(as_dict=True) for d in batch_data: self.available_qty[d.batch_no] += flt(d.batch_qty) + if moving_avg_item_non_batch_value: + self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty) + self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value) + + if moving_avg_item_non_batch_value: + return for d in batch_data: if self.available_qty.get(d.batch_no): @@ -378,9 +394,24 @@ class DeprecatedBatchNoValuation: query = query.where(bundle.voucher_type != "Pick List") + # Moving Average items with no Use Batch wise Valuation but want to use batch wise valuation + moving_avg_item_non_batch_value = False + if valuation_method := self.get_valuation_method(self.sle.item_code): + if valuation_method == "Moving Average" and not frappe.db.get_single_value( + "Stock Settings", "do_not_use_batchwise_valuation" + ): + query = query.where(batch.use_batchwise_valuation == 0) + moving_avg_item_non_batch_value = True + batch_data = query.run(as_dict=True) for d in batch_data: self.available_qty[d.batch_no] += flt(d.batch_qty) + if moving_avg_item_non_batch_value: + self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty) + self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value) + + if moving_avg_item_non_batch_value: + return if not self.last_sle: return @@ -388,3 +419,8 @@ class DeprecatedBatchNoValuation: for batch_no in self.available_qty: self.non_batchwise_balance_value[batch_no] = flt(self.last_sle.stock_value) self.non_batchwise_balance_qty[batch_no] = flt(self.last_sle.qty_after_transaction) + + def get_valuation_method(self, item_code): + from erpnext.stock.utils import get_valuation_method + + return get_valuation_method(item_code, self.sle.company) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index 8cbb59599b7..d02affd68f0 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -359,6 +359,136 @@ class TestSerialandBatchBundle(IntegrationTestCase): self.assertFalse(json.loads(sle.stock_queue or "[]")) self.assertEqual(flt(sle.stock_value), 0.0) + def test_old_moving_avg_item_with_without_batchwise_valuation(self): + frappe.flags.ignore_serial_batch_bundle_validation = True + frappe.flags.use_serial_and_batch_fields = True + batch_item_code = "Old Batch Item Valuation 2" + make_item( + batch_item_code, + { + "has_batch_no": 1, + "batch_number_series": "TEST-OLD2-BAT-VAL-.#####", + "create_new_batch": 1, + "is_stock_item": 1, + "valuation_method": "Moving Average", + }, + ) + + non_batchwise_val_batches = [ + "TEST-OLD2-BAT-VAL-00001", + "TEST-OLD2-BAT-VAL-00002", + "TEST-OLD2-BAT-VAL-00003", + "TEST-OLD2-BAT-VAL-00004", + ] + + for batch_id in non_batchwise_val_batches: + if not frappe.db.exists("Batch", batch_id): + batch_doc = frappe.get_doc( + { + "doctype": "Batch", + "batch_id": batch_id, + "item": batch_item_code, + "use_batchwise_valuation": 0, + } + ).insert(ignore_permissions=True) + + self.assertTrue(batch_doc.use_batchwise_valuation) + batch_doc.db_set( + { + "use_batchwise_valuation": 0, + "batch_qty": 20, + } + ) + + qty_after_transaction = 0 + balance_value = 0 + i = 0 + for batch_id in non_batchwise_val_batches: + i += 1 + qty = 20 + valuation = 100 * i + qty_after_transaction += qty + balance_value += qty * valuation + + doc = frappe.get_doc( + { + "doctype": "Stock Ledger Entry", + "posting_date": today(), + "posting_time": nowtime(), + "batch_no": batch_id, + "incoming_rate": valuation, + "qty_after_transaction": qty_after_transaction, + "stock_value_difference": valuation * qty, + "stock_value": balance_value, + "balance_value": balance_value, + "valuation_rate": balance_value / qty_after_transaction, + "actual_qty": qty, + "item_code": batch_item_code, + "warehouse": "_Test Warehouse - _TC", + } + ) + + doc.set_posting_datetime() + doc.flags.ignore_permissions = True + doc.flags.ignore_mandatory = True + doc.flags.ignore_links = True + doc.flags.ignore_validate = True + doc.submit() + doc.reload() + + frappe.flags.ignore_serial_batch_bundle_validation = False + frappe.flags.use_serial_and_batch_fields = False + + se = make_stock_entry( + item_code=batch_item_code, + target="_Test Warehouse - _TC", + qty=30, + rate=355, + use_serial_batch_fields=True, + ) + + se = make_stock_entry( + item_code=batch_item_code, + source="_Test Warehouse - _TC", + qty=70, + use_serial_batch_fields=True, + ) + + sle = frappe.db.get_value( + "Stock Ledger Entry", + {"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": se.name}, + ["qty_after_transaction", "stock_value"], + as_dict=True, + ) + + self.assertEqual(flt(sle.stock_value), 14000.0) + self.assertEqual(flt(sle.qty_after_transaction), 40.0) + + se = make_stock_entry( + item_code=batch_item_code, + target="_Test Warehouse - _TC", + qty=10, + rate=200, + use_serial_batch_fields=True, + ) + + se = make_stock_entry( + item_code=batch_item_code, + source="_Test Warehouse - _TC", + qty=50, + use_serial_batch_fields=True, + ) + + sle = frappe.db.get_value( + "Stock Ledger Entry", + {"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": se.name}, + ["qty_after_transaction", "stock_value"], + as_dict=True, + ) + + self.assertEqual(flt(sle.stock_value), 0.0) + self.assertEqual(flt(sle.qty_after_transaction), 0.0) + def test_old_serial_no_valuation(self): from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index 8ec3e9865d9..dc92bfc8289 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -114,6 +114,22 @@ class StockSettings(Document): self.validate_auto_insert_price_list_rate_if_missing() self.change_precision_for_for_sales() self.change_precision_for_purchase() + self.validate_do_not_use_batchwise_valuation() + + def validate_do_not_use_batchwise_valuation(self): + doc_before_save = self.get_doc_before_save() + if not doc_before_save: + return + + if not frappe.get_all("Serial and Batch Bundle", filters={"docstatus": 1}, limit=1, pluck="name"): + return + + if doc_before_save.do_not_use_batchwise_valuation and not self.do_not_use_batchwise_valuation: + frappe.throw( + _("Cannot disable {0} as it may lead to incorrect stock valuation.").format( + frappe.bold(_("Do Not Use Batchwise Valuation")) + ) + ) def validate_serial_and_batch_no_settings(self): doc_before_save = self.get_doc_before_save() From e2a34ab3fe824ff96dabddcffb45bbcb1103f72e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:24:35 +0000 Subject: [PATCH 47/63] fix: Creating new item price incase of changes in expired item price (backport #53534) (#53545) Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com> Co-authored-by: Nishka Gosalia fix: Creating new item price incase of changes in expired item price (#53534) --- .../doctype/sales_order/test_sales_order.py | 39 ++++++++++++ erpnext/stock/get_item_details.py | 61 +++++++++++++++---- 2 files changed, 88 insertions(+), 12 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 405583aeca0..e4a372454be 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -57,6 +57,45 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase): frappe.db.rollback() frappe.set_user("Administrator") + @IntegrationTestCase.change_settings( + "Stock Settings", + { + "auto_insert_price_list_rate_if_missing": 1, + "update_existing_price_list_rate": 1, + "update_price_list_based_on": "Rate", + }, + ) + def test_sales_order_expired_item_price(self): + price_list = "_Test Price List" + + item_1 = make_item("_Test Expired Item 1", {"is_stock_item": 1}) + + frappe.db.delete("Item Price", {"item_code": item_1.item_code}) + + item_price = frappe.new_doc("Item Price") + item_price.item_code = item_1.item_code + item_price.price_list = price_list + item_price.price_list_rate = 100 + item_price.valid_from = add_days(today(), -10) + item_price.valid_upto = add_days(today(), -5) + item_price.save() + + so = make_sales_order( + item_code=item_1.item_code, qty=1, rate=1000, selling_price_list=price_list, do_not_save=True + ) + so.save() + so.reload() + + self.assertEqual(frappe.db.get_value("Item Price", item_price.name, "price_list_rate"), 100) + self.assertEqual( + frappe.db.count("Item Price", {"item_code": item_1.item_code, "price_list": price_list}), + 2, + ) + all_item_prices = frappe.get_all( + "Item Price", filters={"item_code": item_1.item_code}, order_by="valid_from desc" + ) + self.assertEqual(frappe.db.get_value("Item Price", all_item_prices[0].name, "price_list_rate"), 1000) + def test_sales_order_with_product_bundle_for_partial_material_request(self): product_bundle = make_product_bundle( "_Test Product Bundle Item", ["_Test Item", "_Test Item Home Desktop 100"] diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index f2ac54898a7..2b9fd98dd53 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -13,7 +13,7 @@ from frappe.model.document import Document from frappe.model.meta import get_field_precision from frappe.model.utils import get_fetch_values from frappe.query_builder.functions import IfNull, Sum -from frappe.utils import add_days, add_months, cint, cstr, flt, getdate, parse_json +from frappe.utils import add_days, add_months, cint, cstr, flt, get_link_to_form, getdate, parse_json import erpnext from erpnext import get_company_currency @@ -1055,16 +1055,30 @@ def insert_item_price(ctx: ItemDetailsCtx): ): return - item_price = frappe.db.get_value( + transaction_date = ( + getdate(ctx.get("posting_date") or ctx.get("transaction_date") or ctx.get("posting_datetime")) + or getdate() + ) + + item_prices = frappe.get_all( "Item Price", - { + filters={ "item_code": ctx.item_code, "price_list": ctx.price_list, "currency": ctx.currency, "uom": ctx.stock_uom, }, - ["name", "price_list_rate"], - as_dict=1, + fields=["name", "price_list_rate", "valid_from", "valid_upto"], + order_by="valid_from desc, creation desc", + ) + item_price = next( + ( + row + for row in item_prices + if (not row.valid_from or getdate(row.valid_from) <= transaction_date) + and (not row.valid_upto or getdate(row.valid_upto) >= transaction_date) + ), + item_prices[0] if item_prices else None, ) update_based_on_price_list_rate = stock_settings.update_price_list_based_on == "Price List Rate" @@ -1079,11 +1093,33 @@ def insert_item_price(ctx: ItemDetailsCtx): if not price_list_rate or item_price.price_list_rate == price_list_rate: return - frappe.db.set_value("Item Price", item_price.name, "price_list_rate", price_list_rate) - frappe.msgprint( - _("Item Price updated for {0} in Price List {1}").format(ctx.item_code, ctx.price_list), - alert=True, - ) + is_price_valid_for_transaction = ( + not item_price.valid_from or getdate(item_price.valid_from) <= transaction_date + ) and (not item_price.valid_upto or getdate(item_price.valid_upto) >= transaction_date) + if is_price_valid_for_transaction: + frappe.db.set_value("Item Price", item_price.name, "price_list_rate", price_list_rate) + frappe.msgprint( + _("Item Price updated for {0} in Price List {1}").format(ctx.item_code, ctx.price_list), + alert=True, + ) + else: + # if price is not valid for the transaction date, insert a new price list rate with updated price and future validity + + item_price = frappe.new_doc( + "Item Price", + item_code=ctx.item_code, + price_list_rate=price_list_rate, + currency=ctx.currency, + uom=ctx.stock_uom, + price_list=ctx.price_list, + ) + item_price.insert() + frappe.msgprint( + _("Item Price Added for {0} in Price List {1}").format( + get_link_to_form("Item", ctx.item_code), ctx.price_list + ), + alert=True, + ) else: rate_to_consider = ( (flt(ctx.price_list_rate) or flt(ctx.rate)) if update_based_on_price_list_rate else flt(ctx.rate) @@ -1102,8 +1138,9 @@ def insert_item_price(ctx: ItemDetailsCtx): ) item_price.insert() frappe.msgprint( - _("Item Price added for {0} in Price List {1}").format(ctx.item_code, ctx.price_list), - alert=True, + _("Item Price added for {0} in Price List {1}").format( + get_link_to_form("Item", ctx.item_code), ctx.price_list + ) ) From d3523226889a15b61d8bf9e28d0f3aa6e5f7e63a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 17 Mar 2026 15:23:05 +0530 Subject: [PATCH 48/63] chore: remove incorrect import (cherry picked from commit fc2edfbdedb59077d105b82d4bd1b00784cec57a) # Conflicts: # erpnext/controllers/queries.py --- erpnext/controllers/queries.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index e96d155701e..a75d9e4348b 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -615,9 +615,13 @@ def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs +<<<<<<< HEAD def get_income_account(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond +======= +def get_income_account(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict): +>>>>>>> fc2edfbded (chore: remove incorrect import) # income account can be any Credit account, # but can also be a Asset account with account_type='Income Account' in special circumstances. # Hence the first condition is an "OR" @@ -703,9 +707,13 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters, @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs +<<<<<<< HEAD def get_expense_account(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond +======= +def get_expense_account(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict): +>>>>>>> fc2edfbded (chore: remove incorrect import) if not filters: filters = {} From 236f1bbfdb6e392b7209219dd71db9a147581ac7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 17 Mar 2026 15:48:47 +0530 Subject: [PATCH 49/63] fix: use qb to prevent incorrect sql due to user permissions (cherry picked from commit 04b967bd6de59a2041b06d8acf33f9b92d098432) --- erpnext/controllers/queries.py | 94 ++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index a75d9e4348b..b7845bc5698 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -15,6 +15,7 @@ from frappe.utils import cint, nowdate, today, unique from pypika import Order import erpnext +from erpnext.accounts.utils import build_qb_match_conditions from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template @@ -615,39 +616,38 @@ def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs -<<<<<<< HEAD def get_income_account(doctype, txt, searchfield, start, page_len, filters): - from erpnext.controllers.queries import get_match_cond - -======= -def get_income_account(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict): ->>>>>>> fc2edfbded (chore: remove incorrect import) # income account can be any Credit account, # but can also be a Asset account with account_type='Income Account' in special circumstances. # Hence the first condition is an "OR" + if not filters: filters = {} - doctype = "Account" - condition = "" + dt = "Account" + + acc = qb.DocType(dt) + condition = [ + (acc.report_type.eq("Profit and Loss") | acc.account_type.isin(["Income Account", "Temporary"])), + acc.is_group.eq(0), + acc.disabled.eq(0), + ] + if txt: + condition.append(acc.name.like(f"%{txt}%")) + if filters.get("company"): - condition += "and tabAccount.company = %(company)s" + condition.append(acc.company.eq(filters.get("company"))) - condition += " and tabAccount.disabled = %(disabled)s" + user_perms = build_qb_match_conditions(dt) + condition.extend(user_perms) - return frappe.db.sql( - f"""select tabAccount.name from `tabAccount` - where (tabAccount.report_type = "Profit and Loss" - or tabAccount.account_type in ("Income Account", "Temporary")) - and tabAccount.is_group=0 - and tabAccount.`{searchfield}` LIKE %(txt)s - {condition} {get_match_cond(doctype)} - order by idx desc, name""", - { - "txt": "%" + txt + "%", - "company": filters.get("company", ""), - "disabled": cint(filters.get("disabled", 0)), - }, + return ( + qb.from_(acc) + .select(acc.name) + .where(Criterion.all(condition)) + .orderby(acc.idx, order=Order.desc) + .orderby(acc.name) + .run() ) @@ -707,31 +707,39 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters, @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs -<<<<<<< HEAD def get_expense_account(doctype, txt, searchfield, start, page_len, filters): - from erpnext.controllers.queries import get_match_cond - -======= -def get_expense_account(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict): ->>>>>>> fc2edfbded (chore: remove incorrect import) if not filters: filters = {} - doctype = "Account" - condition = "" - if filters.get("company"): - condition += "and tabAccount.company = %(company)s" + dt = "Account" - return frappe.db.sql( - f"""select tabAccount.name from `tabAccount` - where (tabAccount.report_type = "Profit and Loss" - or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress")) - and tabAccount.is_group=0 - and tabAccount.disabled = 0 - and tabAccount.{searchfield} LIKE %(txt)s - {condition} {get_match_cond(doctype)}""", - {"company": filters.get("company", ""), "txt": "%" + txt + "%"}, - ) + acc = qb.DocType(dt) + condition = [ + ( + acc.report_type.eq("Profit and Loss") + | acc.account_type.isin( + [ + "Expense Account", + "Fixed Asset", + "Temporary", + "Asset Received But Not Billed", + "Capital Work in Progress", + ] + ) + ), + acc.is_group.eq(0), + acc.disabled.eq(0), + ] + if txt: + condition.append(acc.name.like(f"%{txt}%")) + + if filters.get("company"): + condition.append(acc.company.eq(filters.get("company"))) + + user_perms = build_qb_match_conditions(dt) + condition.extend(user_perms) + + return qb.from_(acc).select(acc.name).where(Criterion.all(condition)).run() @frappe.whitelist() From 0c9ce9306f6d473515ffbef6d8519fcce3b47564 Mon Sep 17 00:00:00 2001 From: sokumon Date: Wed, 11 Mar 2026 13:29:58 +0530 Subject: [PATCH 50/63] fix: add icon in clear demo data (cherry picked from commit 413b119ec66d20ac41f0bc653f2ef54dd0a0e7e6) --- erpnext/hooks.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 0780822b191..f3b9a146ac5 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -221,11 +221,12 @@ website_route_rules = [ standard_navbar_items = [ { - "item_label": "Clear Demo Data", + "item_label": "Erase Demo Data", "item_type": "Action", "action": "erpnext.demo.clear_demo();", "is_standard": 1, - "condition": "eval: frappe.boot.sysdefaults.demo_company", + "condition": "eval: frappe.boot.sysdefaults.demo_company && frappe.boot.sysdefaults.demo_company.length > 0", + "icon": "trash", }, ] From 10cb6f83060f043a3cb17490d70fad9f4e9f1298 Mon Sep 17 00:00:00 2001 From: sokumon Date: Tue, 17 Mar 2026 15:48:24 +0530 Subject: [PATCH 51/63] fix: use same label (cherry picked from commit ed3444de5a91e9b66fcf406445126ed631f63bd2) --- erpnext/hooks.py | 2 +- erpnext/public/js/utils/demo.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index f3b9a146ac5..962fe3e98aa 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -221,7 +221,7 @@ website_route_rules = [ standard_navbar_items = [ { - "item_label": "Erase Demo Data", + "item_label": "Delete Demo Data", "item_type": "Action", "action": "erpnext.demo.clear_demo();", "is_standard": 1, diff --git a/erpnext/public/js/utils/demo.js b/erpnext/public/js/utils/demo.js index db15834ae95..980ac530baf 100644 --- a/erpnext/public/js/utils/demo.js +++ b/erpnext/public/js/utils/demo.js @@ -2,7 +2,7 @@ frappe.provide("erpnext.demo"); $(document).on("desktop_screen", function (event, data) { data.desktop.add_menu_item({ - label: __("Clear Demo Data"), + label: __("Delete Demo Data"), icon: "trash", condition: function () { return frappe.boot.sysdefaults.demo_company; From c48a8868c281d6e8d35ae5c3d112ca101cb8f0c0 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Tue, 17 Mar 2026 12:36:57 +0530 Subject: [PATCH 52/63] fix: remove payables and receivables workspace (cherry picked from commit 26a9646407c258b6d1360cd6283e9da95ebd9124) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 1 + .../patches/v16_0/remove_payables_receivables_workspace.py | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 erpnext/patches/v16_0/remove_payables_receivables_workspace.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 40ff506d3e8..4b1fc449473 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -471,3 +471,4 @@ erpnext.patches.v16_0.migrate_asset_type_checkboxes_to_select erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po erpnext.patches.v16_0.enable_serial_batch_setting erpnext.patches.v16_0.update_requested_qty_packed_item +erpnext.patches.v16_0.remove_payables_receivables_workspace diff --git a/erpnext/patches/v16_0/remove_payables_receivables_workspace.py b/erpnext/patches/v16_0/remove_payables_receivables_workspace.py new file mode 100644 index 00000000000..74c6e3ce3c7 --- /dev/null +++ b/erpnext/patches/v16_0/remove_payables_receivables_workspace.py @@ -0,0 +1,6 @@ +import frappe + + +def execute(): + for ws in ["Receivables", "Payables"]: + frappe.delete_doc_if_exists("Workspace", ws) From 4e5703c22344ddf545dcef00ef22f3f129552d01 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Tue, 17 Mar 2026 18:23:40 +0530 Subject: [PATCH 53/63] fix: remove workspace sidebar for payables and receivables --- erpnext/patches/v16_0/remove_payables_receivables_workspace.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v16_0/remove_payables_receivables_workspace.py b/erpnext/patches/v16_0/remove_payables_receivables_workspace.py index 74c6e3ce3c7..bd1bd96fe77 100644 --- a/erpnext/patches/v16_0/remove_payables_receivables_workspace.py +++ b/erpnext/patches/v16_0/remove_payables_receivables_workspace.py @@ -3,4 +3,5 @@ import frappe def execute(): for ws in ["Receivables", "Payables"]: + frappe.delete_doc_if_exists("Workspace Sidebar", ws) frappe.delete_doc_if_exists("Workspace", ws) From 748467f27f054eeda067c29dcefc8d02898dc224 Mon Sep 17 00:00:00 2001 From: "[Kesavan-001]" <[kesavanp0395@gmail.com]> Date: Sat, 14 Mar 2026 12:39:48 +0530 Subject: [PATCH 54/63] Fix:Cost center mapping issue (cherry picked from commit 10fe8580d59b4bde13fbbeb38bc784c2ddee51b0) --- erpnext/controllers/buying_controller.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 86cfbc01172..4c6e5dba53e 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -1078,6 +1078,7 @@ class BuyingController(SubcontractingController): "asset_category": item_data.get("asset_category"), "location": row.asset_location, "company": self.company, + "cost_center": row.cost_center or self.cost_center or None, "status": "Draft", "supplier": self.supplier, "purchase_date": self.posting_date, @@ -1094,7 +1095,8 @@ class BuyingController(SubcontractingController): for dimension in accounting_dimensions[0]: asset.update( { - dimension["fieldname"]: self.get(dimension["fieldname"]) + dimension["fieldname"]: row.get(dimension["fieldname"]) + or self.get(dimension["fieldname"]) or dimension.get("default_dimension") } ) From 3e75d4ea6083b2cc74f5521f8e15e1f6abdc07e8 Mon Sep 17 00:00:00 2001 From: "[Kesavan-001]" <[kesavanp0395@gmail.com]> Date: Sat, 14 Mar 2026 12:50:47 +0530 Subject: [PATCH 55/63] Fix:Cost center mapping issue (cherry picked from commit a084feba96f1fdfeaedc6829eaa02c5bec27c6a0) --- 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 4c6e5dba53e..23874ad41fe 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -1078,7 +1078,6 @@ class BuyingController(SubcontractingController): "asset_category": item_data.get("asset_category"), "location": row.asset_location, "company": self.company, - "cost_center": row.cost_center or self.cost_center or None, "status": "Draft", "supplier": self.supplier, "purchase_date": self.posting_date, @@ -1095,8 +1094,8 @@ class BuyingController(SubcontractingController): for dimension in accounting_dimensions[0]: asset.update( { - dimension["fieldname"]: row.get(dimension["fieldname"]) - or self.get(dimension["fieldname"]) + dimension["fieldname"]: row.get(dimension["fieldname"]) + or self.get(dimension["fieldname"]) or dimension.get("default_dimension") } ) From 2df491bf22d23c0dc2e30a2a0a02ebc4ff4f2c34 Mon Sep 17 00:00:00 2001 From: "[Kesavan-001]" <[kesavanp0395@gmail.com]> Date: Sat, 14 Mar 2026 13:26:46 +0530 Subject: [PATCH 56/63] Fix:Cost center mapping issue (cherry picked from commit e0fb31f81eb7d705f2fc0e4e1c854ddea4c7e7f6) --- erpnext/controllers/buying_controller.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 23874ad41fe..07349a3363f 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -1092,13 +1092,9 @@ class BuyingController(SubcontractingController): } ) for dimension in accounting_dimensions[0]: - asset.update( - { - dimension["fieldname"]: row.get(dimension["fieldname"]) - or self.get(dimension["fieldname"]) - or dimension.get("default_dimension") - } - ) + fieldname = dimension["fieldname"] + default_dimension = accounting_dimensions[1].get(self.company, {}).get(fieldname) + asset.update({fieldname: row.get(fieldname) or self.get(fieldname) or default_dimension}) asset.flags.ignore_validate = True asset.flags.ignore_mandatory = True From 5515346e2457752c96172f4627c7c57294649546 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:01:00 +0530 Subject: [PATCH 57/63] chore: add documentation link in valuation method field (backport #53564) (#53571) Co-authored-by: Mihir Kandoi --- erpnext/stock/doctype/item/item.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index fd897f734a4..b957b905258 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -358,6 +358,7 @@ }, { "depends_on": "is_stock_item", + "documentation_url": "https://docs.frappe.io/erpnext/change-valuation-method", "fieldname": "valuation_method", "fieldtype": "Select", "label": "Valuation Method", @@ -988,7 +989,7 @@ "image_field": "image", "links": [], "make_attachments_public": 1, - "modified": "2026-03-05 16:29:31.653447", + "modified": "2026-03-17 20:39:05.218344", "modified_by": "Administrator", "module": "Stock", "name": "Item", From 9ccafd30be279f2a0d843a4767a969312ec88bb9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:07:43 +0530 Subject: [PATCH 58/63] fix(stock): fix the property setter (backport #53422) (#53574) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> fix(stock): fix the property setter (#53422) --- erpnext/public/js/utils/landed_taxes_and_charges_common.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7d801ca91e6..751d831a6f7 100644 --- a/erpnext/public/js/utils/landed_taxes_and_charges_common.js +++ b/erpnext/public/js/utils/landed_taxes_and_charges_common.js @@ -42,9 +42,9 @@ erpnext.landed_cost_taxes_and_charges = { if (row.account_currency == company_currency) { row.exchange_rate = 1; - frm.set_df_property("taxes", "hidden", 1, row.name, "exchange_rate"); + frm.set_df_property("taxes", "hidden", 1, frm.docname, "exchange_rate", cdn); } else if (!row.exchange_rate || row.exchange_rate == 1) { - frm.set_df_property("taxes", "hidden", 0, row.name, "exchange_rate"); + frm.set_df_property("taxes", "hidden", 0, frm.docname, "exchange_rate", cdn); frappe.call({ method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_exchange_rate", args: { From 49ac166f9e819861c37a3bfc747c3b50d2f5d68f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:08:44 +0530 Subject: [PATCH 59/63] fix(manufacturing): update working hours validation (backport #53559) (#53567) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> fix(manufacturing): update working hours validation (#53559) --- erpnext/manufacturing/doctype/workstation/workstation.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py index f9427049f15..1867b567da0 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.py +++ b/erpnext/manufacturing/doctype/workstation/workstation.py @@ -101,9 +101,6 @@ class Workstation(Document): self.total_working_hours += row.hours def validate_working_hours(self, row): - if not (row.start_time and row.end_time): - frappe.throw(_("Row #{0}: Start Time and End Time are required").format(row.idx)) - if get_time(row.start_time) >= get_time(row.end_time): frappe.throw(_("Row #{0}: Start Time must be before End Time").format(row.idx)) From 535543d406f0309c69b799be7fedf6bc0709f792 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:09:25 +0530 Subject: [PATCH 60/63] fix(italy): fix e-invoice ScontoMaggiorazione structure and included_in_print_rate support (backport #53334) (#53569) Co-authored-by: Arturo fix(italy): fix e-invoice ScontoMaggiorazione structure and included_in_print_rate support (#53334) --- erpnext/regional/italy/e-invoice.xml | 34 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/erpnext/regional/italy/e-invoice.xml b/erpnext/regional/italy/e-invoice.xml index 7c436a2b449..ef1e94ff27b 100644 --- a/erpnext/regional/italy/e-invoice.xml +++ b/erpnext/regional/italy/e-invoice.xml @@ -18,25 +18,27 @@ {{ address.country_code }} {%- endmacro %} -{%- macro render_discount_or_margin(item) -%} -{%- if (item.discount_percentage and item.discount_percentage > 0.0) or item.margin_type %} +{%- macro render_discount_or_margin(item, tax_divisor) -%} +{%- if item.discount_percentage and item.discount_percentage > 0.0 %} - {%- if item.discount_percentage > 0.0 %} SC {{ format_float(item.discount_percentage) }} - {%- endif %} - {%- if item.margin_rate_or_amount > 0.0 -%} - MG - {%- if item.margin_type == "Percentage" -%} - {{ format_float(item.margin_rate_or_amount) }} - {%- elif item.margin_type == "Amount" -%} - {{ format_float(item.margin_rate_or_amount) }} - {%- endif -%} - {%- endif %} -{%- endif -%} +{%- endif %} +{%- if item.margin_rate_or_amount and item.margin_rate_or_amount > 0.0 %} + + MG + {%- if item.margin_type == "Percentage" -%} + {{ format_float(item.margin_rate_or_amount) }} + {%- elif item.margin_type == "Amount" -%} + {{ format_float(item.margin_rate_or_amount / tax_divisor) }} + {%- endif -%} + +{%- endif %} {%- endmacro -%} +{%- set has_inclusive_tax = doc.taxes | selectattr("included_in_print_rate") | list | length > 0 -%} + {%- for item in doc.e_invoice_items %} + {%- set tax_divisor = (1 + item.tax_rate / 100) if has_inclusive_tax and item.tax_rate else 1 %} {{ item.idx }} @@ -188,8 +191,9 @@ {{ html2text(item.description or '') or item.item_name }} {{ format_float(item.qty) }} {{ item.stock_uom }} - {{ format_float(item.net_rate or item.price_list_rate or item.rate, item_meta.get_field("rate").precision) }} - {{ render_discount_or_margin(item) }} + {%- set item_unit_net_price = (item.price_list_rate / tax_divisor) or (item.net_rate) or (item.rate / tax_divisor) %} + {{ format_float(item_unit_net_price, item_meta.get_field("rate").precision) }} + {{ render_discount_or_margin(item, tax_divisor) }} {{ 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 %} From 4d197b1b7cad526c353a911b36976e0124c6282d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:21:11 +0000 Subject: [PATCH 61/63] fix(sales_invoice): reset payment methods on `pos_profile` change (backport #53514) (#53561) Co-authored-by: diptanilsaha fix(sales_invoice): reset payment methods on `pos_profile` change (#53514) --- .../accounts/doctype/sales_invoice/sales_invoice.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index fb32b4db364..75a9fae92e9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -919,11 +919,9 @@ class SalesInvoice(SellingController): if self.pos_profile: pos = frappe.get_doc("POS Profile", self.pos_profile) - if not self.get("payments") and not for_validate: - update_multi_mode_option(self, pos) - if pos: if not for_validate: + update_multi_mode_option(self, pos) self.tax_category = pos.get("tax_category") if not for_validate and not self.customer: @@ -3008,6 +3006,8 @@ def update_multi_mode_option(doc, pos_profile): payment.account = payment_mode.default_account payment.type = payment_mode.type + mop_refetched = bool(doc.payments) and not doc.is_created_using_pos + doc.set("payments", []) invalid_modes = [] mode_of_payments = [d.mode_of_payment for d in pos_profile.get("payments")] @@ -3029,6 +3029,12 @@ def update_multi_mode_option(doc, pos_profile): msg = _("Please set default Cash or Bank account in Mode of Payments {}") frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account")) + if mop_refetched: + frappe.toast( + _("Payment methods refreshed. Please review before proceeding."), + indicator="orange", + ) + def get_all_mode_of_payments(doc): return frappe.db.sql( From 573a402ee54c1c5d26ab15a4e614ffcb3e37574a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:00:33 +0530 Subject: [PATCH 62/63] fix: initialize all tax columns to resolve Key error in `item_wise_sales_register` and `item_wise_purchase_register` reports (backport #53323) (#53582) Co-authored-by: Lakshit Jain fix: initialize all tax columns to resolve Key error in `item_wise_sales_register` and `item_wise_purchase_register` reports (#53323) --- .../item_wise_purchase_register.py | 5 ++ .../item_wise_sales_register.py | 7 ++ .../test_item_wise_sales_register.py | 66 ++++++++++++++++++- 3 files changed, 76 insertions(+), 2 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 7166e5da691..0f0121f4e6c 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 @@ -32,6 +32,7 @@ def _execute(filters=None, additional_table_columns=None): item_list = get_items(filters, additional_table_columns) aii_account_map = get_aii_accounts() + default_taxes = {} if item_list: itemised_tax, tax_columns = get_tax_accounts( item_list, @@ -40,6 +41,9 @@ def _execute(filters=None, additional_table_columns=None): doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges", ) + for tax in tax_columns: + default_taxes[f"{tax}_rate"] = 0 + default_taxes[f"{tax}_amount"] = 0 po_pr_map = get_purchase_receipts_against_purchase_order(item_list) @@ -87,6 +91,7 @@ def _execute(filters=None, additional_table_columns=None): total_tax = 0 total_other_charges = 0 + row.update(default_taxes.copy()) for tax, details in itemised_tax.get(d.name, {}).items(): row.update( { 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 d77eb56525f..d849aa7e285 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 @@ -33,6 +33,10 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= return columns, [], None, None, None, 0 itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) + default_taxes = {} + for tax in tax_columns: + default_taxes[f"{tax}_rate"] = 0 + default_taxes[f"{tax}_amount"] = 0 mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list)) so_dn_map = get_delivery_notes_against_sales_order(item_list) @@ -90,6 +94,9 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= total_tax = 0 total_other_charges = 0 + + row.update(default_taxes.copy()) + for tax, details in itemised_tax.get(d.name, {}).items(): row.update( { diff --git a/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py index 34ec398e7f3..09a2b4a46d6 100644 --- a/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py @@ -16,9 +16,11 @@ class TestItemWiseSalesRegister(AccountsTestMixin, IntegrationTestCase): def tearDown(self): frappe.db.rollback() - def create_sales_invoice(self, do_not_submit=False): + def create_sales_invoice(self, item=None, taxes=None, do_not_submit=False): si = create_sales_invoice( - item=self.item, + item=item or self.item, + item_name=item or self.item, + description=item or self.item, company=self.company, customer=self.customer, debit_to=self.debit_to, @@ -29,6 +31,19 @@ class TestItemWiseSalesRegister(AccountsTestMixin, IntegrationTestCase): price_list_rate=100, do_not_save=1, ) + + for tax in taxes or []: + si.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": tax["account_head"], + "cost_center": self.cost_center, + "description": tax["description"], + "rate": tax["rate"], + }, + ) + si = si.save() if not do_not_submit: si = si.submit() @@ -62,3 +77,50 @@ class TestItemWiseSalesRegister(AccountsTestMixin, IntegrationTestCase): report_output = {k: v for k, v in report[1][0].items() if k in expected_result} self.assertDictEqual(report_output, expected_result) + + def test_grouped_report_handles_different_tax_descriptions(self): + self.create_item(item_name="_Test Item Tax Description A") + first_item = self.item + self.create_item(item_name="_Test Item Tax Description B") + second_item = self.item + + first_tax_description = "Tax Description A" + second_tax_description = "Tax Description B" + first_tax_amount_field = f"{frappe.scrub(first_tax_description)}_amount" + second_tax_amount_field = f"{frappe.scrub(second_tax_description)}_amount" + + self.create_sales_invoice( + item=first_item, + taxes=[ + { + "account_head": "_Test Account VAT - _TC", + "description": first_tax_description, + "rate": 5, + } + ], + ) + self.create_sales_invoice( + item=second_item, + taxes=[ + { + "account_head": "_Test Account Service Tax - _TC", + "description": second_tax_description, + "rate": 2, + } + ], + ) + + filters = frappe._dict( + { + "from_date": today(), + "to_date": today(), + "company": self.company, + "group_by": "Customer", + } + ) + _, data, _, _, _, _ = execute(filters) + + grand_total_row = next(row for row in data if row.get("bold") and row.get("item_code") == "Total") + + self.assertEqual(grand_total_row[first_tax_amount_field], 5.0) + self.assertEqual(grand_total_row[second_tax_amount_field], 2.0) From dd0013e84438381578b84a531b5def9fcbabc9e9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:08:46 +0530 Subject: [PATCH 63/63] chore: make supplier data expanded by default in PI (backport #53565) (#53579) Co-authored-by: Mihir Kandoi --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 6291cf49bae..d2021e0f9a4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -391,7 +391,7 @@ }, { "collapsible": 1, - "collapsible_depends_on": "bill_no", + "collapsible_depends_on": "posting_date", "fieldname": "supplier_invoice_details", "fieldtype": "Section Break", "label": "Supplier Invoice" @@ -1693,7 +1693,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2026-03-09 17:15:27.014131", + "modified": "2026-03-17 20:44:00.221219", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice",