From ab0e215290609fa887d42480931694c6670630f7 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 18 Mar 2026 15:26:57 +0530 Subject: [PATCH 001/168] feat: default print format for Request for Quotation (cherry picked from commit 2af0d9cf6c5a05095b46cb21fb3823ed673e2bdb) --- .../request_for_quotation.json | 33 ++++++++++++++++--- .../request_for_quotation.py | 2 ++ .../__init__.py | 0 ...request_for_quotation_with_item_image.json | 33 +++++++++++++++++++ erpnext/controllers/accounts_controller.py | 3 ++ erpnext/public/js/print.js | 2 ++ erpnext/setup/install.py | 1 + 7 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 erpnext/buying/print_format/request_for_quotation_with_item_image/__init__.py create mode 100644 erpnext/buying/print_format/request_for_quotation_with_item_image/request_for_quotation_with_item_image.json diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json index 18e1356b263..de8b4d28547 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json @@ -9,8 +9,6 @@ "field_order": [ "naming_series", "company", - "billing_address", - "billing_address_display", "vendor", "column_break1", "transaction_date", @@ -43,7 +41,13 @@ "select_print_heading", "letter_head", "more_info", - "opportunity" + "opportunity", + "address_and_contact_tab", + "billing_address", + "billing_address_display", + "column_break_czul", + "shipping_address", + "shipping_address_display" ], "fields": [ { @@ -346,6 +350,27 @@ "fieldtype": "Check", "hidden": 1, "label": "Use HTML" + }, + { + "fieldname": "address_and_contact_tab", + "fieldtype": "Tab Break", + "label": "Address & Contact" + }, + { + "fieldname": "column_break_czul", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_address", + "fieldtype": "Link", + "label": "Company Shipping Address", + "options": "Address" + }, + { + "fieldname": "shipping_address_display", + "fieldtype": "Text Editor", + "label": "Shipping Address Details", + "read_only": 1 } ], "grid_page_length": 50, @@ -353,7 +378,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2026-03-09 17:15:29.774614", + "modified": "2026-03-19 15:27:56.730649", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation", diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 38ab9af4eab..1fc2cdf3386 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -56,6 +56,8 @@ class RequestforQuotation(BuyingController): select_print_heading: DF.Link | None send_attached_files: DF.Check send_document_print: DF.Check + shipping_address: DF.Link | None + shipping_address_display: DF.TextEditor | None status: DF.Literal["", "Draft", "Submitted", "Cancelled"] subject: DF.Data suppliers: DF.Table[RequestforQuotationSupplier] diff --git a/erpnext/buying/print_format/request_for_quotation_with_item_image/__init__.py b/erpnext/buying/print_format/request_for_quotation_with_item_image/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/buying/print_format/request_for_quotation_with_item_image/request_for_quotation_with_item_image.json b/erpnext/buying/print_format/request_for_quotation_with_item_image/request_for_quotation_with_item_image.json new file mode 100644 index 00000000000..57b37290504 --- /dev/null +++ b/erpnext/buying/print_format/request_for_quotation_with_item_image/request_for_quotation_with_item_image.json @@ -0,0 +1,33 @@ +{ + "absolute_value": 0, + "align_labels_right": 0, + "creation": "2026-03-19 15:17:39.094444", + "custom_format": 1, + "default_print_language": "en", + "disabled": 0, + "doc_type": "Request for Quotation", + "docstatus": 0, + "doctype": "Print Format", + "font_size": 14, + "html": "{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None, print_heading_template=None) -%}\n\n{% if letter_head and not no_letterhead %}\n
{{ letter_head }}
\n{% endif %}\n{% if print_heading_template %}\n{{ frappe.render_template(print_heading_template, {\"doc\":doc}) }}\n{% endif %}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}\n\t
\n\t{%- if doc.meta.is_submittable and doc.docstatus==2-%}\n\t\t
\n\t\t\t

{{ _(\"CANCELLED\") }}

\n\t\t
\n\t{%- endif -%}\n\t{%- if doc.meta.is_submittable and doc.docstatus==0 and (print_settings==None or print_settings.add_draft_heading) -%}\n\t\t
\n\t\t\t

{{ _(\"DRAFT\") }}

\n\t\t
\n\t{%- endif -%}\n\n\t\n\n\t
\n\t\t\n\t\t\t\n\t\t\t\t\n\n\t\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Supplier Name:\") }}
\n\t\t\t\t\t\t
{{ _(\"Shipping Address:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ doc.suppliers[0].supplier }}
\n\t\t\t\t\t\t
\n \t\t\t\t\t{% if doc.shipping_address %}\n \t\t\t\t\t\t{% set shipping_address = frappe.db.get_value(\"Address\", doc.shipping_address, [\"address_line1\", \"address_line2\", \"city\", \"state\", \"pincode\", \"country\"], as_dict=True) %}\n {{ doc.shipping_address }}
\n \t\t\t\t\t\t{{ shipping_address.address_line1 or \"\" }}
\n \t\t\t\t\t\t{% if shipping_address.address_line2 %}{{ shipping_address.address_line2 }}
{% endif %}\n \t\t\t\t\t\t{{ shipping_address.city or \"\" }} {{ shipping_address.state or \"\" }} {{ shipping_address.pincode or \"\" }} {{ shipping_address.country or \"\" }}
\n \t\t\t\t\t{% endif %}\n\t\t\t\t\t\t
\n\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Order Date:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ frappe.utils.format_date(doc.transaction_date) }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Required By:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ frappe.utils.format_date(doc.schedule_date) }}
\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\n\t\t{% set item_naming_by = frappe.db.get_single_value(\"Stock Settings\", \"item_naming_by\") %}\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t{% for item in doc.items %}\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t{% endfor %}\n\t\t\t\n\t\t
{{ _(\"No\") }}{{ _(\"Item\") }}{{ _(\"Item Code\") }}{{ _(\"Quantity\") }}
{{ loop.index }}\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{% if item.image %}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{% endif %}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{{ item.item_name }}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t
{{ item.item_code }}{{ item.get_formatted(\"qty\", 0) }} {{ item.uom }}
\n\n\n\t\t\n\t\t{% if doc.terms %}\n\t\t
\n\t\t\t
{{ _(\"Terms and Conditions\") }}
\n\t\t\t{{ doc.terms}}\n\t\t
\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", + "idx": 0, + "line_breaks": 0, + "margin_bottom": 15.0, + "margin_left": 15.0, + "margin_right": 15.0, + "margin_top": 15.0, + "modified": "2026-03-19 15:39:20.868219", + "modified_by": "Administrator", + "module": "Buying", + "name": "Request for Quotation with Item Image", + "owner": "Administrator", + "page_number": "Hide", + "pdf_generator": "wkhtmltopdf", + "print_format_builder": 0, + "print_format_builder_beta": 0, + "print_format_for": "DocType", + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 24819c3ee19..17d5d929bb5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -4324,6 +4324,8 @@ def get_missing_company_details(doctype, docname): company = frappe.db.get_value(doctype, docname, "company") if doctype in ["Purchase Order", "Purchase Invoice"]: company_address = frappe.db.get_value(doctype, docname, "billing_address") + elif doctype in ["Request for Quotation"]: + company_address = frappe.db.get_value(doctype, docname, "shipping_address") else: company_address = frappe.db.get_value(doctype, docname, "company_address") @@ -4423,6 +4425,7 @@ def update_doc_company_address(current_doctype, docname, company_address, detail "Sales Invoice": ("company_address", "company_address_display"), "Delivery Note": ("company_address", "company_address_display"), "POS Invoice": ("company_address", "company_address_display"), + "Request for Quotation": ("shipping_address", "shipping_address_display"), } address_field, display_field = address_field_map.get( diff --git a/erpnext/public/js/print.js b/erpnext/public/js/print.js index 4f397ef2047..43ba9afc148 100644 --- a/erpnext/public/js/print.js +++ b/erpnext/public/js/print.js @@ -5,6 +5,7 @@ const doctype_list = [ "Purchase Order", "Purchase Invoice", "POS Invoice", + "Request for Quotation", ]; const allowed_print_formats = [ "Sales Order Standard", @@ -19,6 +20,7 @@ const allowed_print_formats = [ "Purchase Invoice with Item Image", "POS Invoice Standard", "POS Invoice with Item Image", + "Request for Quotation with Item Image", ]; const allowed_letterheads = ["Company Letterhead", "Company Letterhead - Grey"]; diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 726906ac6cb..69fbe650f2a 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -310,6 +310,7 @@ def set_default_print_formats(): "Purchase Order": "Purchase Order with Item Image", "Purchase Invoice": "Purchase Invoice with Item Image", "POS Invoice": "POS Invoice with Item Image", + "Request for Quotation": "Request for Quotation with Item Image", } for doctype, print_format in default_map.items(): From a5250f88275399725a29474b4eb91bced2407bca Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Mon, 23 Mar 2026 15:03:11 +0530 Subject: [PATCH 002/168] fix: set default print format for when downlod pdf (cherry picked from commit 6b9fb777720dc889f2a9c34e7d149bed0c6854c8) --- .../doctype/request_for_quotation/request_for_quotation.js | 1 + .../request_for_quotation_with_item_image.json | 4 ++-- 2 files changed, 3 insertions(+), 2 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 b71d0dd3006..8baeba950b9 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -100,6 +100,7 @@ frappe.ui.form.on("Request for Quotation", { fieldname: "print_format", options: "Print Format", placeholder: "Standard", + default: frappe.get_meta("Request for Quotation").default_print_format || "", get_query: () => { return { filters: { diff --git a/erpnext/buying/print_format/request_for_quotation_with_item_image/request_for_quotation_with_item_image.json b/erpnext/buying/print_format/request_for_quotation_with_item_image/request_for_quotation_with_item_image.json index 57b37290504..26f131aec5b 100644 --- a/erpnext/buying/print_format/request_for_quotation_with_item_image/request_for_quotation_with_item_image.json +++ b/erpnext/buying/print_format/request_for_quotation_with_item_image/request_for_quotation_with_item_image.json @@ -9,14 +9,14 @@ "docstatus": 0, "doctype": "Print Format", "font_size": 14, - "html": "{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None, print_heading_template=None) -%}\n\n{% if letter_head and not no_letterhead %}\n
{{ letter_head }}
\n{% endif %}\n{% if print_heading_template %}\n{{ frappe.render_template(print_heading_template, {\"doc\":doc}) }}\n{% endif %}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}\n\t
\n\t{%- if doc.meta.is_submittable and doc.docstatus==2-%}\n\t\t
\n\t\t\t

{{ _(\"CANCELLED\") }}

\n\t\t
\n\t{%- endif -%}\n\t{%- if doc.meta.is_submittable and doc.docstatus==0 and (print_settings==None or print_settings.add_draft_heading) -%}\n\t\t
\n\t\t\t

{{ _(\"DRAFT\") }}

\n\t\t
\n\t{%- endif -%}\n\n\t\n\n\t
\n\t\t\n\t\t\t\n\t\t\t\t\n\n\t\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Supplier Name:\") }}
\n\t\t\t\t\t\t
{{ _(\"Shipping Address:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ doc.suppliers[0].supplier }}
\n\t\t\t\t\t\t
\n \t\t\t\t\t{% if doc.shipping_address %}\n \t\t\t\t\t\t{% set shipping_address = frappe.db.get_value(\"Address\", doc.shipping_address, [\"address_line1\", \"address_line2\", \"city\", \"state\", \"pincode\", \"country\"], as_dict=True) %}\n {{ doc.shipping_address }}
\n \t\t\t\t\t\t{{ shipping_address.address_line1 or \"\" }}
\n \t\t\t\t\t\t{% if shipping_address.address_line2 %}{{ shipping_address.address_line2 }}
{% endif %}\n \t\t\t\t\t\t{{ shipping_address.city or \"\" }} {{ shipping_address.state or \"\" }} {{ shipping_address.pincode or \"\" }} {{ shipping_address.country or \"\" }}
\n \t\t\t\t\t{% endif %}\n\t\t\t\t\t\t
\n\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Order Date:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ frappe.utils.format_date(doc.transaction_date) }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Required By:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ frappe.utils.format_date(doc.schedule_date) }}
\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\n\t\t{% set item_naming_by = frappe.db.get_single_value(\"Stock Settings\", \"item_naming_by\") %}\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t{% for item in doc.items %}\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t{% endfor %}\n\t\t\t\n\t\t
{{ _(\"No\") }}{{ _(\"Item\") }}{{ _(\"Item Code\") }}{{ _(\"Quantity\") }}
{{ loop.index }}\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{% if item.image %}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{% endif %}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{{ item.item_name }}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t
{{ item.item_code }}{{ item.get_formatted(\"qty\", 0) }} {{ item.uom }}
\n\n\n\t\t\n\t\t{% if doc.terms %}\n\t\t
\n\t\t\t
{{ _(\"Terms and Conditions\") }}
\n\t\t\t{{ doc.terms}}\n\t\t
\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", + "html": "{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None, print_heading_template=None) -%}\n\n{% if letter_head and not no_letterhead %}\n
{{ letter_head }}
\n{% endif %}\n{% if print_heading_template %}\n{{ frappe.render_template(print_heading_template, {\"doc\":doc}) }}\n{% endif %}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}\n\t
\n\t{%- if doc.meta.is_submittable and doc.docstatus==2-%}\n\t\t
\n\t\t\t

{{ _(\"CANCELLED\") }}

\n\t\t
\n\t{%- endif -%}\n\t{%- if doc.meta.is_submittable and doc.docstatus==0 and (print_settings==None or print_settings.add_draft_heading) -%}\n\t\t
\n\t\t\t

{{ _(\"DRAFT\") }}

\n\t\t
\n\t{%- endif -%}\n\n\t\n\n\t
\n\t\t\n\t\t\t\n\t\t\t\t\n\n\t\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Supplier Name:\") }}
\n\t\t\t\t\t\t
{{ _(\"Shipping Address:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ doc.vendor }}
\n\t\t\t\t\t\t
\n \t\t\t\t\t{% if doc.shipping_address %}\n \t\t\t\t\t\t{% set shipping_address = frappe.db.get_value(\"Address\", doc.shipping_address, [\"address_line1\", \"address_line2\", \"city\", \"state\", \"pincode\", \"country\"], as_dict=True) %}\n {{ doc.shipping_address }}
\n \t\t\t\t\t\t{{ shipping_address.address_line1 or \"\" }}
\n \t\t\t\t\t\t{% if shipping_address.address_line2 %}{{ shipping_address.address_line2 }}
{% endif %}\n \t\t\t\t\t\t{{ shipping_address.city or \"\" }} {{ shipping_address.state or \"\" }} {{ shipping_address.pincode or \"\" }} {{ shipping_address.country or \"\" }}
\n \t\t\t\t\t{% endif %}\n\t\t\t\t\t\t
\n\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Order Date:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ frappe.utils.format_date(doc.transaction_date) }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ _(\"Required By:\") }}
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
{{ frappe.utils.format_date(doc.schedule_date) }}
\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\n\t\t{% set item_naming_by = frappe.db.get_single_value(\"Stock Settings\", \"item_naming_by\") %}\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t{% for item in doc.items %}\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t{% endfor %}\n\t\t\t\n\t\t
{{ _(\"No\") }}{{ _(\"Item\") }}{{ _(\"Item Code\") }}{{ _(\"Quantity\") }}
{{ loop.index }}\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{% if item.image %}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{% endif %}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{{ item.item_name }}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t
{{ item.item_code }}{{ item.get_formatted(\"qty\", 0) }} {{ item.uom }}
\n\n\n\t\t\n\t\t{% if doc.terms %}\n\t\t
\n\t\t\t
{{ _(\"Terms and Conditions\") }}
\n\t\t\t{{ doc.terms}}\n\t\t
\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", "idx": 0, "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2026-03-19 15:39:20.868219", + "modified": "2026-03-23 14:29:41.591636", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation with Item Image", From 0571830720b31d276349e134e27be6fbe3429699 Mon Sep 17 00:00:00 2001 From: nishkagosalia Date: Tue, 24 Mar 2026 11:02:23 +0530 Subject: [PATCH 003/168] refactor: item master ux improvements (cherry picked from commit be5508275132feaf8c72518b089474ac7379bfaa) --- erpnext/stock/doctype/item/item.json | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index b957b905258..e4996462278 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -47,7 +47,6 @@ "column_break_cqdk", "valuation_rate", "inventory_settings_section", - "shelf_life_in_days", "end_of_life", "default_material_request_type", "column_break1", @@ -64,6 +63,7 @@ "create_new_batch", "batch_number_series", "has_expiry_date", + "shelf_life_in_days", "retain_sample", "sample_quantity", "column_break_37", @@ -334,6 +334,7 @@ "options": "fa fa-truck" }, { + "depends_on": "has_expiry_date", "fieldname": "shelf_life_in_days", "fieldtype": "Int", "label": "Shelf Life In Days", @@ -343,11 +344,13 @@ { "default": "2099-12-31", "depends_on": "is_stock_item", + "description": "Defines the date after which the item can no longer be used in transactions or manufacturing", "fieldname": "end_of_life", "fieldtype": "Date", "label": "End of Life", "oldfieldname": "end_of_life", - "oldfieldtype": "Date" + "oldfieldtype": "Date", + "show_description_on_click": 1 }, { "default": "Purchase", @@ -467,9 +470,12 @@ { "default": "0", "depends_on": "has_batch_no", + "description": "Enable to reserve a small sample from each batch for any analysis arising ahead", + "documentation_url": "https://docs.frappe.io/erpnext/retain-sample-stock", "fieldname": "retain_sample", "fieldtype": "Check", - "label": "Retain Sample" + "label": "Retain Sample", + "show_description_on_click": 1 }, { "depends_on": "eval: (doc.retain_sample && doc.has_batch_no)", @@ -989,7 +995,7 @@ "image_field": "image", "links": [], "make_attachments_public": 1, - "modified": "2026-03-17 20:39:05.218344", + "modified": "2026-03-24 15:45:40.207531", "modified_by": "Administrator", "module": "Stock", "name": "Item", From 2693ffe6801ad6bfb5ae510a99c2795c3fb86e3a Mon Sep 17 00:00:00 2001 From: Jatin3128 Date: Mon, 2 Feb 2026 12:46:49 +0530 Subject: [PATCH 004/168] fix(Payment Entry): split orders as per the schedules in the refrence table (cherry picked from commit a9e52833fec42b01c7a2ba8d7be2d8d5433e1683) --- .../doctype/payment_entry/payment_entry.py | 30 +++---- .../payment_entry/test_payment_entry.py | 86 +++++++++++++++++++ 2 files changed, 101 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 289c19a5885..d60f77120b2 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -2376,9 +2376,7 @@ def get_outstanding_reference_documents(args, validate=False): vouchers=args.get("vouchers") or None, ) - outstanding_invoices = split_invoices_based_on_payment_terms( - outstanding_invoices, args.get("company") - ) + outstanding_invoices = split_refdocs_based_on_payment_terms(outstanding_invoices, args.get("company")) for d in outstanding_invoices: d["exchange_rate"] = 1 @@ -2416,6 +2414,8 @@ def get_outstanding_reference_documents(args, validate=False): filters=args, ) + orders_to_be_billed = split_refdocs_based_on_payment_terms(orders_to_be_billed, args.get("company")) + data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed if not data: @@ -2438,13 +2438,13 @@ def get_outstanding_reference_documents(args, validate=False): return data -def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list: +def split_refdocs_based_on_payment_terms(refdocs, company) -> list: """Split a list of invoices based on their payment terms.""" - exc_rates = get_currency_data(outstanding_invoices, company) + exc_rates = get_currency_data(refdocs, company) - outstanding_invoices_after_split = [] - for entry in outstanding_invoices: - if entry.voucher_type in ["Sales Invoice", "Purchase Invoice"]: + outstanding_refdoc_after_split = [] + for entry in refdocs: + if entry.voucher_type in ["Sales Invoice", "Purchase Invoice", "Sales Order", "Purchase Order"]: if payment_term_template := frappe.db.get_value( entry.voucher_type, entry.voucher_no, "payment_terms_template" ): @@ -2459,25 +2459,25 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list ), alert=True, ) - outstanding_invoices_after_split += split_rows + outstanding_refdoc_after_split += split_rows continue # If not an invoice or no payment terms template, add as it is - outstanding_invoices_after_split.append(entry) + outstanding_refdoc_after_split.append(entry) - return outstanding_invoices_after_split + return outstanding_refdoc_after_split -def get_currency_data(outstanding_invoices: list, company: str | None = None) -> dict: +def get_currency_data(outstanding_refdocs: list, company: str | None = None) -> dict: """Get currency and conversion data for a list of invoices.""" exc_rates = frappe._dict() company_currency = frappe.db.get_value("Company", company, "default_currency") if company else None - for doctype in ["Sales Invoice", "Purchase Invoice"]: - invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype] + for doctype in ["Sales Invoice", "Purchase Invoice", "Sales Order", "Purchase Order"]: + refdoc = [x.voucher_no for x in outstanding_refdocs if x.voucher_type == doctype] for x in frappe.db.get_all( doctype, - filters={"name": ["in", invoices]}, + filters={"name": ["in", refdoc]}, fields=["name", "currency", "conversion_rate", "party_account_currency"], ): exc_rates[x.name] = frappe._dict( diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 12f5276fbfb..9d2890f5e79 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -2019,6 +2019,92 @@ class TestPaymentEntry(ERPNextTestSuite): self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, pe.doctype, pe.name) self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, "Journal Entry", jv[0]) + def test_outstanding_orders_split_by_payment_terms(self): + create_payment_terms_template() + + so = make_sales_order(do_not_save=1, qty=1, rate=200) + so.payment_terms_template = "Test Receivable Template" + so.save().submit() + + args = { + "posting_date": nowdate(), + "company": so.company, + "party_type": "Customer", + "payment_type": "Receive", + "party": so.customer, + "party_account": "Debtors - _TC", + "get_orders_to_be_billed": True, + } + + references = get_outstanding_reference_documents(args) + + self.assertEqual(len(references), 2) + self.assertEqual(references[0].voucher_no, so.name) + self.assertEqual(references[1].voucher_no, so.name) + self.assertEqual(references[0].payment_term, "Basic Amount Receivable") + self.assertEqual(references[1].payment_term, "Tax Receivable") + + def test_outstanding_orders_no_split_when_allocate_disabled(self): + create_payment_terms_template() + + template = frappe.get_doc("Payment Terms Template", "Test Receivable Template") + template.allocate_payment_based_on_payment_terms = 0 + template.save() + + so = make_sales_order(do_not_save=1, qty=1, rate=200) + so.payment_terms_template = "Test Receivable Template" + so.save().submit() + + args = { + "posting_date": nowdate(), + "company": so.company, + "party_type": "Customer", + "payment_type": "Receive", + "party": so.customer, + "party_account": "Debtors - _TC", + "get_orders_to_be_billed": True, + } + + references = get_outstanding_reference_documents(args) + + self.assertEqual(len(references), 1) + self.assertIsNone(references[0].payment_term) + + template.allocate_payment_based_on_payment_terms = 1 + template.save() + + def test_outstanding_multicurrency_sales_order_split(self): + create_payment_terms_template() + + so = make_sales_order( + customer="_Test Customer USD", + currency="USD", + qty=1, + rate=100, + do_not_submit=True, + ) + so.payment_terms_template = "Test Receivable Template" + so.conversion_rate = 50 + so.save().submit() + + args = { + "posting_date": nowdate(), + "company": so.company, + "party_type": "Customer", + "payment_type": "Receive", + "party": so.customer, + "party_account": "Debtors - _TC", + "get_orders_to_be_billed": True, + } + + references = get_outstanding_reference_documents(args) + + # Should split without throwing currency errors + self.assertEqual(len(references), 2) + for ref in references: + self.assertEqual(ref.voucher_no, so.name) + self.assertIsNotNone(ref.payment_term) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From 9669a2c56fc5498ce18e241998f8958fefc14c61 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 20 Mar 2026 11:46:43 +0530 Subject: [PATCH 005/168] refactor(test): move commits inside test guard clause (cherry picked from commit ed76d6699afbc35cfdb0cc5a9b6bf100f7525e02) --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 3 ++- erpnext/accounts/doctype/ledger_merge/ledger_merge.py | 3 ++- .../opening_invoice_creation_tool.py | 3 ++- .../accounts/doctype/payment_request/payment_request.py | 3 ++- erpnext/accounts/doctype/subscription/subscription.py | 3 ++- erpnext/assets/doctype/asset/depreciation.py | 7 +++++-- erpnext/buying/doctype/purchase_order/purchase_order.py | 3 ++- erpnext/crm/doctype/appointment/appointment.py | 3 ++- erpnext/setup/doctype/company/company.py | 6 ++++-- erpnext/stock/stock_balance.py | 3 ++- erpnext/telephony/doctype/call_log/call_log.py | 4 +++- 11 files changed, 28 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 3abfa176622..452164c728c 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -489,4 +489,5 @@ def rename_temporarily_named_docs(doctype): for hook in frappe.get_hooks(hook_type): frappe.call(hook, newname=newname, oldname=oldname) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py index 008b4115f5f..3dd3883608c 100644 --- a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py +++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py @@ -71,7 +71,8 @@ def start_merge(docname): ledger_merge.account, ) row.db_set("merged", 1) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() successful_merges += 1 frappe.publish_realtime( "ledger_merge_progress", diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index 6a3df141f52..e1e70e0f6cb 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -274,7 +274,8 @@ def start_import(invoices): doc.flags.ignore_mandatory = True doc.insert(set_name=invoice_number) doc.submit() - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() names.append(doc.name) except Exception: errors += 1 diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index c95945bf6e2..e16e132957f 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -750,7 +750,8 @@ def make_payment_request(**args): pr.submit() if args.order_type == "Shopping Cart": - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() frappe.local.response["type"] = "redirect" frappe.local.response["location"] = pr.get_payment_url() diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 0b3da559e39..642f918c3b1 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -772,7 +772,8 @@ def process_all(subscription: list, posting_date: DateTimeLikeObject | None = No try: subscription = frappe.get_doc("Subscription", subscription_name) subscription.process(posting_date) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() except frappe.ValidationError: frappe.db.rollback() subscription.log_error("Subscription failed") diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 832e3736e7d..1a9788be48e 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -61,7 +61,9 @@ def book_depreciation_entries(date): accounting_dimensions, ) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() + except Exception as e: frappe.db.rollback() failed_assets.append(asset_name) @@ -71,7 +73,8 @@ def book_depreciation_entries(date): if failed_assets: set_depr_entry_posting_status_for_failed_assets(failed_assets) notify_depr_entry_posting_error(failed_assets, error_logs) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() def get_depreciable_assets_data(date): diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index a0daeca51f2..bd22a21bd99 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -780,7 +780,8 @@ def make_purchase_invoice_from_portal(purchase_order_name): if frappe.session.user not in frappe.get_all("Portal User", {"parent": doc.supplier}, pluck="user"): frappe.throw(_("Not Permitted"), frappe.PermissionError) doc.save() - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() frappe.response["type"] = "redirect" frappe.response.location = "/purchase-invoices/" + doc.name diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index b41064ce9e3..9a213aea5fc 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -120,7 +120,8 @@ class Appointment(Document): self.auto_assign() self.create_calendar_event() self.save(ignore_permissions=True) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() def create_lead_and_link(self): # Return if already linked diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 5da3ca40904..8111935a339 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -927,7 +927,7 @@ def update_transactions_annual_history(company, commit=False): transactions_history = get_all_transactions_annual_history(company) frappe.db.set_value("Company", company, "transactions_annual_history", json.dumps(transactions_history)) - if commit: + if commit and not frappe.in_test: frappe.db.commit() @@ -936,7 +936,9 @@ def cache_companies_monthly_sales_history(): for company in companies: update_company_monthly_sales(company) update_transactions_annual_history(company) - frappe.db.commit() + + if not frappe.in_test: + frappe.db.commit() @frappe.whitelist() diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index b2401da4f8f..7f6deda9b8c 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -31,7 +31,8 @@ def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False, for d in item_warehouses: try: repost_stock(d[0], d[1], allow_zero_rate, only_actual, only_bin, allow_negative_stock) - frappe.db.commit() + if not frappe.in_test: + frappe.db.commit() except Exception: frappe.db.rollback() diff --git a/erpnext/telephony/doctype/call_log/call_log.py b/erpnext/telephony/doctype/call_log/call_log.py index 0b5fd5dc368..b2ae785e110 100644 --- a/erpnext/telephony/doctype/call_log/call_log.py +++ b/erpnext/telephony/doctype/call_log/call_log.py @@ -190,7 +190,9 @@ def link_existing_conversations(doc, state): call_log = frappe.get_doc("Call Log", log) call_log.add_link(link_type=doc.doctype, link_name=doc.name) call_log.save(ignore_permissions=True) - frappe.db.commit() + + if not frappe.in_test: + frappe.db.commit() except Exception: frappe.log_error(title=_("Error during caller information update")) From 941375877e57b1e1cdb95dc52180ab6bb7f7bfd1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 20 Mar 2026 11:50:01 +0530 Subject: [PATCH 006/168] refactor(test): move dimension setup to test data bootstrap and remove create_dimension() and disable_dimension() (cherry picked from commit 342ce654019ad845282f4e6a1be5ea861c6e1492) --- .../test_accounting_dimension.py | 63 ------------------- .../test_accounting_dimension_filter.py | 5 -- .../test_opening_invoice_creation_tool.py | 6 +- .../test_pos_closing_entry.py | 6 -- .../sales_invoice/test_sales_invoice.py | 7 --- .../tests/test_accounts_controller.py | 18 ------ erpnext/tests/utils.py | 31 +++++++++ 7 files changed, 32 insertions(+), 104 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index 81e639dc6b2..250442a3cd4 100644 --- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -10,9 +10,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestAccountingDimension(ERPNextTestSuite): - def setUp(self): - create_dimension() - def test_dimension_against_sales_invoice(self): si = create_sales_invoice(do_not_save=1) @@ -77,63 +74,3 @@ class TestAccountingDimension(ERPNextTestSuite): si.save() self.assertRaises(frappe.ValidationError, si.submit) - - -def create_dimension(): - frappe.set_user("Administrator") - - if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}): - dimension = frappe.get_doc( - { - "doctype": "Accounting Dimension", - "document_type": "Department", - } - ) - dimension.append( - "dimension_defaults", - { - "company": "_Test Company", - "reference_document": "Department", - "default_dimension": "_Test Department - _TC", - }, - ) - dimension.insert() - dimension.save() - else: - dimension = frappe.get_doc("Accounting Dimension", "Department") - dimension.disabled = 0 - dimension.save() - - if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}): - dimension1 = frappe.get_doc( - { - "doctype": "Accounting Dimension", - "document_type": "Location", - } - ) - - dimension1.append( - "dimension_defaults", - { - "company": "_Test Company", - "reference_document": "Location", - "default_dimension": "Block 1", - }, - ) - - dimension1.insert() - dimension1.save() - else: - dimension1 = frappe.get_doc("Accounting Dimension", "Location") - dimension1.disabled = 0 - dimension1.save() - - -def disable_dimension(): - dimension1 = frappe.get_doc("Accounting Dimension", "Department") - dimension1.disabled = 1 - dimension1.save() - - dimension2 = frappe.get_doc("Accounting Dimension", "Location") - dimension2.disabled = 1 - dimension2.save() diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py index 9bed10824bb..fe7d4706967 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py @@ -5,10 +5,6 @@ import unittest import frappe -from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( - create_dimension, - disable_dimension, -) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError from erpnext.tests.utils import ERPNextTestSuite @@ -16,7 +12,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestAccountingDimensionFilter(ERPNextTestSuite): def setUp(self): - create_dimension() create_accounting_dimension_filter() self.invoice_list = [] diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index 3d57c781983..1a61adad4cd 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -3,10 +3,6 @@ import frappe -from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( - create_dimension, - disable_dimension, -) from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import ( get_temporary_opening_account, ) @@ -15,9 +11,9 @@ from erpnext.tests.utils import ERPNextTestSuite class TestOpeningInvoiceCreationTool(ERPNextTestSuite): def setUp(self): + # TODO: move to bootstrap if not frappe.db.exists("Company", "_Test Opening Invoice Company"): make_company() - create_dimension() def make_invoices( self, diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py index af5f73a39ec..05e24d16a3a 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -4,10 +4,6 @@ import unittest import frappe -from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( - create_dimension, - disable_dimension, -) from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import ( make_closing_entry_from_opening, ) @@ -162,7 +158,6 @@ class TestPOSClosingEntry(ERPNextTestSuite): test case to check whether we can create POS Closing Entry without mandatory accounting dimension """ - create_dimension() location = frappe.get_doc("Accounting Dimension", "Location") location.dimension_defaults[0].mandatory_for_bs = True location.save() @@ -198,7 +193,6 @@ class TestPOSClosingEntry(ERPNextTestSuite): ) accounting_dimension_department.mandatory_for_bs = 0 accounting_dimension_department.save() - disable_dimension() def test_merging_into_sales_invoice_for_batched_item(self): frappe.flags.print_message = False diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 3616f196bb7..002cdc4a43c 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2246,13 +2246,6 @@ class TestSalesInvoice(ERPNextTestSuite): @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": True}) def test_rounding_adjustment_3(self): - from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension - - # Dimension creates custom field, which does an implicit DB commit as it is a DDL command - # Ensure dimension don't have any mandatory fields - create_dimension() - - # rollback from tearDown() happens till here si = create_sales_invoice(do_not_save=True) si.items = [] for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]: diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 6bb9b2f91fc..021e034a70d 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -1567,25 +1567,10 @@ class TestAccountsController(ERPNextTestSuite): frappe.db.set_value("Company", self.company, "cost_center", cc) - def setup_dimensions(self): - # create dimension - from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( - create_dimension, - ) - - create_dimension() - # make it non-mandatory - loc = frappe.get_doc("Accounting Dimension", "Location") - for x in loc.dimension_defaults: - x.mandatory_for_bs = False - x.mandatory_for_pl = False - loc.save() - def test_90_dimensions_filter(self): """ Test workings of dimension filters """ - self.setup_dimensions() rate_in_account_currency = 1 # Invoices @@ -1653,7 +1638,6 @@ class TestAccountsController(ERPNextTestSuite): self.assertEqual(len(pr.payments), 1) def test_91_cr_note_should_inherit_dimension(self): - self.setup_dimensions() rate_in_account_currency = 1 # Invoice @@ -1698,7 +1682,6 @@ class TestAccountsController(ERPNextTestSuite): def test_92_dimension_inhertiance_exc_gain_loss(self): # Sales Invoice in Foreign Currency - self.setup_dimensions() rate_in_account_currency = 1 dpt = "Research & Development - _TC" @@ -1734,7 +1717,6 @@ class TestAccountsController(ERPNextTestSuite): ) def test_93_dimension_inheritance_on_advance(self): - self.setup_dimensions() dpt = "Research & Development - _TC" adv = self.create_payment_entry(amount=1, source_exc_rate=85) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 9485eb9af42..f5f9d7428cf 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -246,6 +246,10 @@ class BootStrapTestData: frappe.db.commit() # nosemgrep + # Dimensions + # DDL commands have implicit commit + self.make_dimensions() + # custom doctype # DDL commands have implicit commit self.make_custom_doctype() @@ -2794,6 +2798,33 @@ class BootStrapTestData: ] self.make_records(["address_title", "address_type"], records) + def make_dimensions(self): + records = [ + { + "doctype": "Accounting Dimension", + "document_type": "Department", + "dimension_defaults": [ + { + "company": "_Test Company", + "reference_document": "Department", + "default_dimension": "_Test Department - _TC", + } + ], + }, + { + "doctype": "Accounting Dimension", + "document_type": "Location", + "dimension_defaults": [ + { + "company": "_Test Company", + "reference_document": "Location", + "default_dimension": "Block 1", + } + ], + }, + ] + self.make_records(["document_type"], records) + BootStrapTestData() From ee72ed94d566133bdf636f37f9202ae93a7f3702 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 20 Mar 2026 12:02:02 +0530 Subject: [PATCH 007/168] refactor(test): move company setup to bootstrap (cherry picked from commit 9ed072ac834e6986adae28100367ab9e0dbdc55b) --- .../test_opening_invoice_creation_tool.py | 18 ------------------ .../setup/doctype/company/test_records.json | 8 ++++++++ 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index 1a61adad4cd..c01ada6d317 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -10,11 +10,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestOpeningInvoiceCreationTool(ERPNextTestSuite): - def setUp(self): - # TODO: move to bootstrap - if not frappe.db.exists("Company", "_Test Opening Invoice Company"): - make_company() - def make_invoices( self, invoice_type="Sales", @@ -179,19 +174,6 @@ def get_opening_invoice_creation_dict(**args): return invoice_dict -def make_company(): - if frappe.db.exists("Company", "_Test Opening Invoice Company"): - return frappe.get_doc("Company", "_Test Opening Invoice Company") - - company = frappe.new_doc("Company") - company.company_name = "_Test Opening Invoice Company" - company.abbr = "_TOIC" - company.default_currency = "INR" - company.country = "Pakistan" - company.insert() - return company - - def make_customer(customer=None): customer_name = customer or "Opening Customer" customer = frappe.get_doc( diff --git a/erpnext/setup/doctype/company/test_records.json b/erpnext/setup/doctype/company/test_records.json index 1b51d98413e..f26caa50fde 100644 --- a/erpnext/setup/doctype/company/test_records.json +++ b/erpnext/setup/doctype/company/test_records.json @@ -180,5 +180,13 @@ "default_currency": "ZAR", "doctype": "Company", "create_chart_of_accounts_based_on": "Standard Template" + }, + { + "abbr": "_TOIC", + "company_name": "_Test Opening Invoice Company", + "country": "Pakistan", + "default_currency": "INR", + "doctype": "Company", + "create_chart_of_accounts_based_on": "Standard Template" } ] From bb42d3ddbe24023d9e48d025c71e96e516dfc20a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 20 Mar 2026 16:20:05 +0530 Subject: [PATCH 008/168] refactor(test): move purchase invoice dimension setup to bootstrap (cherry picked from commit 31ce09204f5a6bfdec64fa45a0c61bdba0bd137a) --- .../purchase_invoice/test_purchase_invoice.py | 18 ++++++++++-------- erpnext/tests/utils.py | 4 ++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 33117c639dc..09febdfd915 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2189,11 +2189,6 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): def test_offsetting_entries_for_accounting_dimensions(self): from erpnext.accounts.doctype.account.test_account import create_account - from erpnext.accounts.report.trial_balance.test_trial_balance import ( - clear_dimension_defaults, - create_accounting_dimension, - disable_dimension, - ) create_account( account_name="Offsetting", @@ -2201,7 +2196,16 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): parent_account="Temporary Accounts - _TC", ) - create_accounting_dimension(company="_Test Company", offsetting_account="Offsetting - _TC") + dim = frappe.get_doc("Accounting Dimension", "Branch") + dim.append( + "dimension_defaults", + { + "company": "_Test Company", + "reference_document": "Branch", + "offsetting_account": "Offsetting - _TC", + }, + ) + dim.save() branch1 = frappe.new_doc("Branch") branch1.branch = "Location 1" @@ -2238,8 +2242,6 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): voucher_type="Purchase Invoice", additional_columns=["branch"], ) - clear_dimension_defaults("Branch") - disable_dimension() def test_repost_accounting_entries(self): # update repost settings diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index f5f9d7428cf..bfc0d5d89f6 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -2822,6 +2822,10 @@ class BootStrapTestData: } ], }, + { + "doctype": "Accounting Dimension", + "document_type": "Branch", + }, ] self.make_records(["document_type"], records) From ebe45add4c7207107f65b9c24731ff592fcb2800 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 20 Mar 2026 16:38:55 +0530 Subject: [PATCH 009/168] refactor(test): move trial company creation to bootstrap (cherry picked from commit 11fb00c21d5d6caeb636ab7a2bcd00b8681eb0de) --- .../trial_balance/test_trial_balance.py | 69 ++++--------------- .../setup/doctype/company/test_records.json | 8 +++ 2 files changed, 20 insertions(+), 57 deletions(-) diff --git a/erpnext/accounts/report/trial_balance/test_trial_balance.py b/erpnext/accounts/report/trial_balance/test_trial_balance.py index 42cf62af0a0..c37f9d5a46a 100644 --- a/erpnext/accounts/report/trial_balance/test_trial_balance.py +++ b/erpnext/accounts/report/trial_balance/test_trial_balance.py @@ -14,7 +14,6 @@ class TestTrialBalance(ERPNextTestSuite): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.utils import get_fiscal_year - self.company = create_company() create_cost_center( cost_center_name="Test Cost Center", company="Trial Balance Company", @@ -26,7 +25,16 @@ class TestTrialBalance(ERPNextTestSuite): parent_account="Temporary Accounts - TBC", ) self.fiscal_year = get_fiscal_year(today(), company="Trial Balance Company")[0] - create_accounting_dimension() + dim = frappe.get_doc("Accounting Dimension", "Branch") + dim.append( + "dimension_defaults", + { + "company": "Trial Balance Company", + "automatically_post_balancing_accounting_entry": 1, + "offsetting_account": "Offsetting - TBC", + }, + ) + dim.save() def test_offsetting_entries_for_accounting_dimensions(self): """ @@ -45,7 +53,7 @@ class TestTrialBalance(ERPNextTestSuite): branch2.insert(ignore_if_duplicate=True) si = create_sales_invoice( - company=self.company, + company="Trial Balance Company", debit_to="Debtors - TBC", cost_center="Test Cost Center - TBC", income_account="Sales - TBC", @@ -57,60 +65,7 @@ class TestTrialBalance(ERPNextTestSuite): si.submit() filters = frappe._dict( - {"company": self.company, "fiscal_year": self.fiscal_year, "branch": ["Location 1"]} + {"company": "Trial Balance Company", "fiscal_year": self.fiscal_year, "branch": ["Location 1"]} ) total_row = execute(filters)[1][-1] self.assertEqual(total_row["debit"], total_row["credit"]) - - -def create_company(**args): - args = frappe._dict(args) - company = frappe.get_doc( - { - "doctype": "Company", - "company_name": args.company_name or "Trial Balance Company", - "country": args.country or "India", - "default_currency": args.currency or "INR", - "parent_company": args.get("parent_company"), - "is_group": args.get("is_group"), - } - ) - company.insert(ignore_if_duplicate=True) - return company.name - - -def create_accounting_dimension(**args): - args = frappe._dict(args) - document_type = args.document_type or "Branch" - if frappe.db.exists("Accounting Dimension", document_type): - accounting_dimension = frappe.get_doc("Accounting Dimension", document_type) - accounting_dimension.disabled = 0 - else: - accounting_dimension = frappe.new_doc("Accounting Dimension") - accounting_dimension.document_type = document_type - accounting_dimension.insert() - - accounting_dimension.set("dimension_defaults", []) - accounting_dimension.append( - "dimension_defaults", - { - "company": args.company or "Trial Balance Company", - "automatically_post_balancing_accounting_entry": 1, - "offsetting_account": args.offsetting_account or "Offsetting - TBC", - }, - ) - accounting_dimension.save() - - -def disable_dimension(**args): - args = frappe._dict(args) - document_type = args.document_type or "Branch" - dimension = frappe.get_doc("Accounting Dimension", document_type) - dimension.disabled = 1 - dimension.save() - - -def clear_dimension_defaults(dimension_name): - accounting_dimension = frappe.get_doc("Accounting Dimension", dimension_name) - accounting_dimension.dimension_defaults = [] - accounting_dimension.save() diff --git a/erpnext/setup/doctype/company/test_records.json b/erpnext/setup/doctype/company/test_records.json index f26caa50fde..0378ba939c3 100644 --- a/erpnext/setup/doctype/company/test_records.json +++ b/erpnext/setup/doctype/company/test_records.json @@ -188,5 +188,13 @@ "default_currency": "INR", "doctype": "Company", "create_chart_of_accounts_based_on": "Standard Template" + }, + { + "abbr": "TBC", + "company_name": "Trial Balance Company", + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "create_chart_of_accounts_based_on": "Standard Template" } ] From d41e7098bda8da839ca7031a7731491e58352fcf Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 20 Mar 2026 17:05:39 +0530 Subject: [PATCH 010/168] refactor(test): move tax category custom field creation to bootstrap (cherry picked from commit 4454af8efd57b702c328dd54806858e3a3375fec) --- .../test_tax_withholding_category.py | 16 -------------- erpnext/tests/utils.py | 21 +++++++++++++++++-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 66dc090f7c7..bd633c94dc9 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -18,7 +18,6 @@ class TestTaxWithholdingCategory(ERPNextTestSuite): # create relevant supplier, etc create_records() create_tax_withholding_category_records() - make_pan_no_field() def validate_tax_withholding_entries(self, doctype, docname, expected_entries): """Validate tax withholding entries for a document""" @@ -3998,18 +3997,3 @@ def create_lower_deduction_certificate( "certificate_limit": limit, } ).insert() - - -def make_pan_no_field(): - pan_field = { - "Supplier": [ - { - "fieldname": "pan", - "label": "PAN", - "fieldtype": "Data", - "translatable": 0, - } - ] - } - - create_custom_fields(pan_field, update=1) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index bfc0d5d89f6..b5c8e19f648 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -8,6 +8,7 @@ from typing import Any, NewType import frappe from frappe import _ from frappe.core.doctype.report.report import get_report_module_dotted_path +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.tests.utils import load_test_records_for from frappe.utils import now_datetime, today @@ -246,14 +247,16 @@ class BootStrapTestData: frappe.db.commit() # nosemgrep - # Dimensions # DDL commands have implicit commit + # Dimensions self.make_dimensions() # custom doctype - # DDL commands have implicit commit self.make_custom_doctype() + # custom field + self.make_custom_field() + def update_system_settings(self): system_settings = frappe.get_doc("System Settings") system_settings.time_zone = "Asia/Kolkata" @@ -2829,6 +2832,20 @@ class BootStrapTestData: ] self.make_records(["document_type"], records) + def make_custom_field(self): + pan_field = { + "Supplier": [ + { + "fieldname": "pan", + "label": "PAN", + "fieldtype": "Data", + "translatable": 0, + } + ] + } + + create_custom_fields(pan_field, update=1) + BootStrapTestData() From cdc77caf6a0f6c460cb5fa3f38686a5887d8df5d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 20 Mar 2026 18:43:46 +0530 Subject: [PATCH 011/168] refactor(test): move custom doctype data setup to bootstrap (cherry picked from commit 934740205aa6729c93b75f7ea57d735d11628df1) --- .../test_inventory_dimension.py | 26 +-------- erpnext/tests/utils.py | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index cbf6059a812..fb62b0eb5c0 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -22,9 +22,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestInventoryDimension(ERPNextTestSuite): - def setUp(self): - prepare_test_data() - def test_validate_inventory_dimension(self): # Can not be child doc inv_dim1 = create_inventory_dimension( @@ -77,6 +74,7 @@ class TestInventoryDimension(ERPNextTestSuite): self.assertFalse(custom_field) def test_inventory_dimension(self): + create_warehouse("Shelf Warehouse") warehouse = "Shelf Warehouse - _TC" item_code = "_Test Item" @@ -556,28 +554,6 @@ def get_voucher_sl_entries(voucher_no, fields): ) -def prepare_test_data(): - for shelf in ["Shelf 1", "Shelf 2"]: - if not frappe.db.exists("Shelf", shelf): - frappe.get_doc({"doctype": "Shelf", "shelf_name": shelf}).insert(ignore_permissions=True) - - create_warehouse("Shelf Warehouse") - - for rack in ["Rack 1", "Rack 2"]: - if not frappe.db.exists("Rack", rack): - frappe.get_doc({"doctype": "Rack", "rack_name": rack}).insert(ignore_permissions=True) - - create_warehouse("Rack Warehouse") - - for site in ["Site 1", "Site 2"]: - if not frappe.db.exists("Inv Site", site): - frappe.get_doc({"doctype": "Inv Site", "site_name": site}).insert(ignore_permissions=True) - - for store in ["Store 1", "Store 2"]: - if not frappe.db.exists("Store", store): - frappe.get_doc({"doctype": "Store", "store_name": store}).insert(ignore_permissions=True) - - def create_inventory_dimension(**args): args = frappe._dict(args) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index b5c8e19f648..a9ab25bff1a 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -254,6 +254,12 @@ class BootStrapTestData: # custom doctype self.make_custom_doctype() + # data on custom doctype + self.make_shelf() + self.make_rack() + self.make_inv_site() + self.make_store() + # custom field self.make_custom_field() @@ -2846,6 +2852,58 @@ class BootStrapTestData: create_custom_fields(pan_field, update=1) + def make_shelf(self): + records = [ + { + "doctype": "Shelf", + "shelf_name": "Shelf 1", + }, + { + "doctype": "Shelf", + "shelf_name": "Shelf 2", + }, + ] + self.make_records(["shelf_name"], records) + + def make_rack(self): + records = [ + { + "doctype": "Rack", + "rack_name": "Rack 1", + }, + { + "doctype": "Rack", + "rack_name": "Rack 2", + }, + ] + self.make_records(["rack_name"], records) + + def make_inv_site(self): + records = [ + { + "doctype": "Inv Site", + "site_name": "Site 1", + }, + { + "doctype": "Inv Site", + "site_name": "Site 2", + }, + ] + self.make_records(["site_name"], records) + + def make_store(self): + records = [ + { + "doctype": "Store", + "store_name": "Store 1", + }, + { + "doctype": "Store", + "store_name": "Store 2", + }, + ] + self.make_records(["store_name"], records) + BootStrapTestData() From ad2cf0624f434ec0016233a2545b5f7303735203 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 23 Mar 2026 14:10:06 +0530 Subject: [PATCH 012/168] refactor(test): move webform custom dt creation to boostrap (cherry picked from commit 426b7db3c85c949b62179f11b23bb75ee9fde74d) --- erpnext/tests/test_webform.py | 37 -------------------------------- erpnext/tests/utils.py | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/erpnext/tests/test_webform.py b/erpnext/tests/test_webform.py index 8b4ed9ceec9..9ba780e4805 100644 --- a/erpnext/tests/test_webform.py +++ b/erpnext/tests/test_webform.py @@ -22,7 +22,6 @@ class TestWebsite(ERPNextTestSuite): po1 = create_purchase_order(supplier="Supplier1") po2 = create_purchase_order(supplier="Supplier2") - create_custom_doctype() create_webform() create_order_assignment(supplier="Supplier1", po=po1.name) create_order_assignment(supplier="Supplier2", po=po2.name) @@ -62,42 +61,6 @@ def create_user(name, email): ).insert(ignore_if_duplicate=True) -def create_custom_doctype(): - frappe.get_doc( - { - "doctype": "DocType", - "name": "Order Assignment", - "module": "Buying", - "custom": 1, - "autoname": "field:po", - "fields": [ - {"label": "PO", "fieldname": "po", "fieldtype": "Link", "options": "Purchase Order"}, - { - "label": "Supplier", - "fieldname": "supplier", - "fieldtype": "Data", - "fetch_from": "po.supplier", - }, - ], - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1, - }, - {"read": 1, "role": "Supplier"}, - ], - } - ).insert(ignore_if_duplicate=True) - - def create_webform(): frappe.get_doc( { diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index a9ab25bff1a..6b591cd9500 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -2756,6 +2756,46 @@ class BootStrapTestData: } ).insert(ignore_permissions=True) + if not frappe.db.exists("DocType", "Order Assignment"): + frappe.get_doc( + { + "doctype": "DocType", + "name": "Order Assignment", + "module": "Buying", + "custom": 1, + "autoname": "field:po", + "fields": [ + { + "label": "PO", + "fieldname": "po", + "fieldtype": "Link", + "options": "Purchase Order", + }, + { + "label": "Supplier", + "fieldname": "supplier", + "fieldtype": "Data", + "fetch_from": "po.supplier", + }, + ], + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1, + }, + {"read": 1, "role": "Supplier"}, + ], + } + ).insert(ignore_if_duplicate=True) + def make_address(self): records = [ { From 0ba03ce8516ea6528496c59ee5569b019b9d4f47 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 23 Mar 2026 15:37:08 +0530 Subject: [PATCH 013/168] refactor(test): SLA move company creation to bootstrap (cherry picked from commit 77f41e120d43ff804ae6cbbeac04b5c44d0d8154) --- .../setup/doctype/company/test_records.json | 10 +++++ .../test_service_level_agreement.py | 37 +++---------------- erpnext/tests/utils.py | 6 +++ 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/erpnext/setup/doctype/company/test_records.json b/erpnext/setup/doctype/company/test_records.json index 0378ba939c3..bfcddfe0208 100644 --- a/erpnext/setup/doctype/company/test_records.json +++ b/erpnext/setup/doctype/company/test_records.json @@ -196,5 +196,15 @@ "default_currency": "INR", "doctype": "Company", "create_chart_of_accounts_based_on": "Standard Template" + }, + { + "abbr": "_TSS", + "company_name": "_Test Support SLA", + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "chart_of_accounts": "Standard", + "create_chart_of_accounts_based_on": "Standard Template" } + ] diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py index d7ada387f82..0f6c1262b69 100644 --- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py @@ -14,32 +14,6 @@ from erpnext.tests.utils import ERPNextTestSuite class TestServiceLevelAgreement(ERPNextTestSuite): - def setUp(self): - self.create_company() - frappe.db.set_single_value("Support Settings", "track_service_level_agreement", 1) - lead = frappe.qb.DocType("Lead") - frappe.qb.from_(lead).delete().where(lead.company == self.company).run() - - def create_company(self): - name = "_Test Support SLA" - company = None - if frappe.db.exists("Company", name): - company = frappe.get_doc("Company", name) - else: - company = frappe.get_doc( - { - "doctype": "Company", - "company_name": name, - "country": "India", - "default_currency": "INR", - "create_chart_of_accounts_based_on": "Standard Template", - "chart_of_accounts": "Standard", - } - ) - company = company.save() - - self.company = company.name - def test_service_level_agreement(self): # Default Service Level Agreement create_default_service_level_agreement = create_service_level_agreement( @@ -220,10 +194,9 @@ class TestServiceLevelAgreement(ERPNextTestSuite): doctype=doctype, sla_fulfilled_on=[{"status": "Converted"}], ) - # make lead with default SLA creation = datetime.datetime(2019, 3, 4, 12, 0) - lead = make_lead(creation=creation, index=1, company=self.company) + lead = make_lead(creation=creation, index=1, company="_Test Support SLA") self.assertEqual(lead.service_level_agreement, lead_sla.name) self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0)) @@ -251,7 +224,7 @@ class TestServiceLevelAgreement(ERPNextTestSuite): ) creation = datetime.datetime(2020, 3, 4, 4, 0) - lead = make_lead(creation, index=2, company=self.company) + lead = make_lead(creation, index=2, company="_Test Support SLA") frappe.flags.current_time = datetime.datetime(2020, 3, 4, 4, 15) lead.reload() @@ -285,7 +258,7 @@ class TestServiceLevelAgreement(ERPNextTestSuite): ) creation = datetime.datetime(2019, 3, 4, 12, 0) - lead = make_lead(creation=creation, index=1, company=self.company) + lead = make_lead(creation=creation, index=1, company="_Test Support SLA") self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0)) # failed with response time only @@ -312,7 +285,7 @@ class TestServiceLevelAgreement(ERPNextTestSuite): # fulfilled with response time only creation = datetime.datetime(2019, 3, 4, 12, 0) - lead = make_lead(creation=creation, index=2, company=self.company) + lead = make_lead(creation=creation, index=2, company="_Test Support SLA") self.assertEqual(lead.service_level_agreement, lead_sla.name) self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0)) @@ -339,7 +312,7 @@ class TestServiceLevelAgreement(ERPNextTestSuite): apply_sla_for_resolution=0, ) creation = datetime.datetime(2019, 3, 4, 12, 0) - lead = make_lead(creation=creation, index=4, company=self.company) + lead = make_lead(creation=creation, index=4, company="_Test Support SLA") applied_sla = frappe.db.get_value("Lead", lead.name, "service_level_agreement") self.assertFalse(applied_sla) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 6b591cd9500..df2d6c135d4 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -241,6 +241,7 @@ class BootStrapTestData: self.make_sales_person() self.make_activity_type() self.make_address() + self.update_support_settings() self.update_selling_settings() self.update_stock_settings() self.update_system_settings() @@ -271,6 +272,11 @@ class BootStrapTestData: system_settings.rounding_method = "Banker's Rounding" system_settings.save() + def update_support_settings(self): + support_settings = frappe.get_doc("Support Settings") + support_settings.track_service_level_agreement = True + support_settings.save() + def update_selling_settings(self): selling_settings = frappe.get_doc("Selling Settings") selling_settings.selling_price_list = "Standard Selling" From 8ea9133caa734c026c1ede0ec9cadfeed6685d25 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 23 Mar 2026 15:46:02 +0530 Subject: [PATCH 014/168] refactor(test): make ledger merge deterministic (cherry picked from commit d3cf8cb85173cab7007a1af821e1f41f8ea4ccb6) --- erpnext/accounts/doctype/ledger_merge/ledger_merge.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py index 3dd3883608c..dc3fd5a9d04 100644 --- a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py +++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py @@ -79,7 +79,8 @@ def start_merge(docname): {"ledger_merge": ledger_merge.name, "current": successful_merges, "total": total}, ) except Exception: - frappe.db.rollback() + if not frappe.in_test: + frappe.db.rollback() ledger_merge.log_error("Ledger merge failed") finally: if successful_merges == total: From 37ad0665c6a9e7a97fd895f2024fd729f57a493f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 23 Mar 2026 15:55:52 +0530 Subject: [PATCH 015/168] refactor(test): make asset capitalization deterministic (cherry picked from commit 2c53cf3902cb67f0bb1b91817a3db3fa93cecd91) --- .../doctype/asset_capitalization/test_asset_capitalization.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index f245ac4f0a2..e37ac4c2bf3 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -380,6 +380,7 @@ class TestAssetCapitalization(ERPNextTestSuite): "asset_type": "Composite Component", "purchase_date": pr.posting_date, "available_for_use_date": pr.posting_date, + "location": "Test Location", } ) consumed_asset_doc.save() From f0aa82cc6daf77896ed518101c11e116d71486d2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 23 Mar 2026 16:14:11 +0530 Subject: [PATCH 016/168] refactor(test): make stock entry deterministic (cherry picked from commit 8fd65d7afa56b1c6b2b08d3b5c38eb0a824551c9) --- erpnext/setup/doctype/company/test_records.json | 10 +++++++++- erpnext/stock/doctype/stock_entry/test_stock_entry.py | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/setup/doctype/company/test_records.json b/erpnext/setup/doctype/company/test_records.json index bfcddfe0208..74615e60162 100644 --- a/erpnext/setup/doctype/company/test_records.json +++ b/erpnext/setup/doctype/company/test_records.json @@ -205,6 +205,14 @@ "doctype": "Company", "chart_of_accounts": "Standard", "create_chart_of_accounts_based_on": "Standard Template" + }, + { + "abbr": "TQC", + "company_name": "Test Quality Company", + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "chart_of_accounts": "Standard", + "create_chart_of_accounts_based_on": "Standard Template" } - ] diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 5d8ebdda56f..48488a7c5b6 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -2422,12 +2422,11 @@ class TestStockEntry(ERPNextTestSuite): Unit test case to check the document naming rule with company condition For Quality Inspection, when created from Stock Entry. """ - from erpnext.accounts.report.trial_balance.test_trial_balance import create_company from erpnext.controllers.stock_controller import make_quality_inspections from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse # create a separate company to handle document naming rule with company condition - qc_company = create_company(company_name="Test Quality Company") + qc_company = "Test Quality Company" # create document naming rule based on that for Quality Inspection Doctype qc_naming_rule = frappe.new_doc( From 7f29245eb6f27c979a07903c8f908325c54c738c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 23 Mar 2026 16:14:24 +0530 Subject: [PATCH 017/168] refactor(test): move location creation to bootstrap in asset movement (cherry picked from commit fd2b76a4d21f727c413b4db28587684ad60bf121) --- .../asset_movement/test_asset_movement.py | 17 ----------------- erpnext/tests/utils.py | 1 + 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py index 88dd93a93cb..76d37d3abb4 100644 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -16,7 +16,6 @@ class TestAssetMovement(ERPNextTestSuite): frappe.db.set_value( "Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC" ) - make_location() def test_movement(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -40,10 +39,6 @@ class TestAssetMovement(ERPNextTestSuite): if asset.docstatus == 0: asset.submit() - # check asset movement is created - if not frappe.db.exists("Location", "Test Location 2"): - frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert() - create_asset_movement( purpose="Transfer", company=asset.company, @@ -122,9 +117,6 @@ class TestAssetMovement(ERPNextTestSuite): if asset.docstatus == 0: asset.submit() - if not frappe.db.exists("Location", "Test Location 2"): - frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert() - movement = frappe.get_doc({"doctype": "Asset Movement", "reference_name": pr.name}) self.assertRaises(frappe.ValidationError, movement.cancel) @@ -150,9 +142,6 @@ class TestAssetMovement(ERPNextTestSuite): asset = create_asset(item_code="Macbook Pro", do_not_save=1) asset.save().submit() - if not frappe.db.exists("Location", "Test Location 2"): - frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert() - asset_creation_date = frappe.db.get_value( "Asset Movement", [["Asset Movement Item", "asset", "=", asset.name], ["docstatus", "=", 1]], @@ -197,9 +186,3 @@ def create_asset_movement(**args): movement.submit() return movement - - -def make_location(): - for location in ["Pune", "Mumbai", "Nagpur"]: - if not frappe.db.exists("Location", location): - frappe.get_doc({"doctype": "Location", "location_name": location}).insert(ignore_permissions=True) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index df2d6c135d4..6aefc4da247 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -975,6 +975,7 @@ class BootStrapTestData: def make_location(self): records = [ {"doctype": "Location", "location_name": "Test Location"}, + {"doctype": "Location", "location_name": "Test Location 2"}, {"doctype": "Location", "location_name": "Test Location Area", "is_group": 1, "is_container": 1}, { "doctype": "Location", From e91cbd94b403330fb47edbaa6ee116e61708233b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 24 Mar 2026 16:05:54 +0530 Subject: [PATCH 018/168] refactor(test): process statement of acc remove commit (cherry picked from commit bc2b8da59716febb61fd217a53f63684c9e08952) --- .../process_statement_of_accounts.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 9cf27216b1e..a40cd03240e 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -563,10 +563,10 @@ def send_emails(document_name, from_scheduler=False, posting_date=None): new_from_date = add_months(new_to_date, -1 * doc.filter_duration) doc.add_comment("Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now())) if doc.report == "General Ledger": - doc.db_set("to_date", new_to_date, commit=True) - doc.db_set("from_date", new_from_date, commit=True) + frappe.db.set_value(doc.doctype, doc.name, "to_date", new_to_date) + frappe.db.set_value(doc.doctype, doc.name, "from_date", new_from_date) else: - doc.db_set("posting_date", new_to_date, commit=True) + frappe.db.set_value(doc.doctype, doc.name, "posting_date", new_to_date) return True else: return False From 1872dccb0a59ba2b0d68d3a67a05a6fa95ff5239 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 11:10:16 +0530 Subject: [PATCH 019/168] fix: do not check for sub assembly reference for rm of fg (backport #53758) (#53759) Co-authored-by: Mihir Kandoi fix: do not check for sub assembly reference for rm of fg (#53758) --- .../doctype/production_plan/production_plan.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 30b3968fc80..1dfc064b2a4 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -616,7 +616,12 @@ class ProductionPlan(Document): None, ): item.db_set("sub_assembly_item_reference", reference) - elif self.reserve_stock and item.main_item_code and item.from_bom: + elif ( + self.reserve_stock + and item.main_item_code + and item.from_bom + and item.main_item_code != frappe.get_cached_value("BOM", item.from_bom, "item") + ): frappe.throw( _( "Sub assembly item references are missing. Please fetch the sub assemblies and raw materials again." From 37b68a07aa22385cecde0b7f2d296d19b573fac3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 05:52:27 +0000 Subject: [PATCH 020/168] fix(manufacturing): apply work order status filter in job card (backport #53766) (#53768) Co-authored-by: Pandiyan P fix(manufacturing): apply work order status filter in job card (#53766) --- erpnext/manufacturing/doctype/job_card/job_card.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index b392a2aa02b..fd652a3a60f 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -40,6 +40,14 @@ frappe.ui.form.on("Job Card", { }; }); + frm.set_query("work_order", function () { + return { + filters: { + status: ["not in", ["Cancelled", "Closed", "Stopped"]], + }, + }; + }); + frm.events.set_company_filters(frm, "target_warehouse"); frm.events.set_company_filters(frm, "source_warehouse"); frm.events.set_company_filters(frm, "wip_warehouse"); From a93d7159162f3e3a691ac344bbd402973c28ee02 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 06:29:08 +0000 Subject: [PATCH 021/168] fix(manufacturing): update condition for base hour rate calculation (backport #53753) (#53771) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> fix(manufacturing): update condition for base hour rate calculation (#53753) --- erpnext/manufacturing/doctype/bom/bom.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 58fce82c208..2ee62b06ad5 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -944,12 +944,14 @@ class BOM(WebsiteGenerator): hour_rate / flt(self.conversion_rate) if self.conversion_rate and hour_rate else hour_rate ) - if row.hour_rate and row.time_in_mins: + if row.hour_rate: row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate) - row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0 - row.base_operating_cost = flt(row.operating_cost) * flt(self.conversion_rate) - row.cost_per_unit = row.operating_cost / (row.batch_size or 1.0) - row.base_cost_per_unit = row.base_operating_cost / (row.batch_size or 1.0) + + if row.time_in_mins: + row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0 + row.base_operating_cost = flt(row.operating_cost) * flt(self.conversion_rate) + row.cost_per_unit = row.operating_cost / (row.batch_size or 1.0) + row.base_cost_per_unit = row.base_operating_cost / (row.batch_size or 1.0) if update_hour_rate: row.db_update() From 38bc5d69cdeccbd1030fcac72831e0eb36e92a72 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Wed, 25 Mar 2026 14:46:50 +0530 Subject: [PATCH 022/168] fix(templates): escape attachment `file_url` and `file_name` in `order.html` and `projects.html` (cherry picked from commit d9760bbf4f539628540f0b8f6a6706b0ca09f4f1) --- erpnext/templates/pages/order.html | 2 +- erpnext/templates/pages/projects.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 0805a32ae33..5563a58b730 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -140,7 +140,7 @@
{% for attachment in attachments %}

- {{ attachment.file_name }} + {{ attachment.file_name|e }}

{% endfor %}
diff --git a/erpnext/templates/pages/projects.html b/erpnext/templates/pages/projects.html index d88088c9819..6527036bb22 100644 --- a/erpnext/templates/pages/projects.html +++ b/erpnext/templates/pages/projects.html @@ -82,11 +82,11 @@
{% for attachment in doc.attachments %}
- +
- {{ attachment.file_name }} + {{ attachment.file_name|e }}
{{ attachment.file_size }} From c3cb9cc003a17c6b88a22687532652647755b8a6 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Wed, 25 Mar 2026 14:50:08 +0530 Subject: [PATCH 023/168] fix(templates): using correct syntax of `include` in `projects.html` (cherry picked from commit bc6561cdd05cb99329b1412d823dd384ea984447) --- erpnext/templates/pages/projects.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/templates/pages/projects.html b/erpnext/templates/pages/projects.html index 6527036bb22..e671e91db2f 100644 --- a/erpnext/templates/pages/projects.html +++ b/erpnext/templates/pages/projects.html @@ -101,8 +101,8 @@
{% endblock %} From 737cb371d799066ca8f3dd4a8c63866aed6673fb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:26:34 +0000 Subject: [PATCH 024/168] fix(contract_template): restrict `create`, `write` and `delete` access only to `System Manager` (backport #53787) (#53789) Co-authored-by: diptanilsaha fix(contract_template): restrict `create`, `write` and `delete` access only to `System Manager` (#53787) --- .../contract_template/contract_template.json | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/erpnext/crm/doctype/contract_template/contract_template.json b/erpnext/crm/doctype/contract_template/contract_template.json index 223464d3eb8..baa6b289005 100644 --- a/erpnext/crm/doctype/contract_template/contract_template.json +++ b/erpnext/crm/doctype/contract_template/contract_template.json @@ -56,7 +56,7 @@ } ], "links": [], - "modified": "2024-03-27 13:06:46.495091", + "modified": "2026-03-25 19:27:19.162421", "modified_by": "Administrator", "module": "CRM", "name": "Contract Template", @@ -75,44 +75,36 @@ "write": 1 }, { - "create": 1, - "delete": 1, "email": 1, "export": 1, "print": 1, "read": 1, "report": 1, "role": "Sales Manager", - "share": 1, - "write": 1 + "share": 1 }, { - "create": 1, - "delete": 1, "email": 1, "export": 1, "print": 1, "read": 1, "report": 1, "role": "Purchase Manager", - "share": 1, - "write": 1 + "share": 1 }, { - "create": 1, - "delete": 1, "email": 1, "export": 1, "print": 1, "read": 1, "report": 1, "role": "HR Manager", - "share": 1, - "write": 1 + "share": 1 } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From 72efbc2b4257a2fba6760cc981409641be833160 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 04:24:43 +0530 Subject: [PATCH 025/168] fix: purchase invoice for internal transfers should not require PO (backport #53791) (#53793) Co-authored-by: Mihir Kandoi fix: purchase invoice for internal transfers should not require PO (#53791) --- .../doctype/purchase_invoice/purchase_invoice.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 527c33225c6..c22a20510c9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -617,12 +617,13 @@ class PurchaseInvoice(BuyingController): frappe.db.set_value(self.doctype, self.name, "against_expense_account", self.against_expense_account) def po_required(self): - if frappe.db.get_single_value("Buying Settings", "po_required") == "Yes": - if frappe.get_value( + if ( + frappe.db.get_single_value("Buying Settings", "po_required") == "Yes" + and not self.is_internal_transfer() + and not frappe.get_value( "Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_order" - ): - return - + ) + ): for d in self.get("items"): if not d.purchase_order: msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code)) From 09a4f630e13ab8755517a3f0bf4fa2d18319fed6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 07:42:54 +0000 Subject: [PATCH 026/168] fix: keep from and to time blank until added explicitly (backport #53798) (#53801) Co-authored-by: Mihir Kandoi fix: keep from and to time blank until added explicitly (#53798) --- erpnext/manufacturing/doctype/job_card/job_card.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index fd652a3a60f..9fb7dcb51b2 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -788,26 +788,12 @@ frappe.ui.form.on("Job Card Time Log", { frm.events.set_total_completed_qty(frm); }, - - time_in_mins(frm, cdt, cdn) { - let d = locals[cdt][cdn]; - if (d.time_in_mins) { - d.to_time = add_mins_to_time(d.from_time, d.time_in_mins); - frappe.model.set_value(cdt, cdn, "to_time", d.to_time); - } - }, }); function get_seconds_diff(d1, d2) { return moment(d1).diff(d2, "seconds"); } -function add_mins_to_time(datetime, mins) { - let new_date = moment(datetime).add(mins, "minutes"); - - return new_date.format("YYYY-MM-DD HH:mm:ss"); -} - function get_last_completed_row(time_logs) { let completed_rows = time_logs.filter((d) => d.to_time); From f2195fa67d6507e049c0d9edd305c8186fa665ae Mon Sep 17 00:00:00 2001 From: Pandiyan P Date: Thu, 26 Mar 2026 19:00:10 +0530 Subject: [PATCH 027/168] fix(accounts): set supplier name as title field in Purchase Invoice (#53710) fix(accounts): update title field in purchase order and purchase invoice (cherry picked from commit 5b1fa81451319f4de0856ada37812a921b261136) # Conflicts: # erpnext/buying/doctype/purchase_order/purchase_order.json --- .../purchase_invoice/purchase_invoice.json | 15 ++------------- .../purchase_invoice/purchase_invoice.py | 1 - .../doctype/purchase_order/purchase_order.json | 17 +++++------------ .../doctype/purchase_order/purchase_order.py | 1 - 4 files changed, 7 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index d2021e0f9a4..adb7dad6726 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -8,7 +8,6 @@ "email_append_to": 1, "engine": "InnoDB", "field_order": [ - "title", "naming_series", "supplier", "supplier_name", @@ -209,16 +208,6 @@ "connections_tab" ], "fields": [ - { - "allow_on_submit": 1, - "default": "{supplier_name}", - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "label": "Title", - "no_copy": 1, - "print_hide": 1 - }, { "fieldname": "naming_series", "fieldtype": "Select", @@ -1693,7 +1682,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2026-03-17 20:44:00.221219", + "modified": "2026-03-25 11:45:38.696888", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", @@ -1756,6 +1745,6 @@ "sort_order": "DESC", "states": [], "timeline_field": "supplier", - "title_field": "title", + "title_field": "supplier_name", "track_changes": 1 } diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index c22a20510c9..7c076e197a5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -203,7 +203,6 @@ class PurchaseInvoice(BuyingController): taxes_and_charges_deducted: DF.Currency tc_name: DF.Link | None terms: DF.TextEditor | None - title: DF.Data | None to_date: DF.Date | None total: DF.Currency total_advance: DF.Currency diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 2a1b37aae2a..a4e50467baa 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -9,7 +9,11 @@ "engine": "InnoDB", "field_order": [ "supplier_section", +<<<<<<< HEAD "title", +======= + "company", +>>>>>>> 5b1fa81451 (fix(accounts): set supplier name as title field in Purchase Invoice (#53710)) "naming_series", "supplier", "supplier_name", @@ -172,17 +176,6 @@ "fieldtype": "Section Break", "options": "fa fa-user" }, - { - "allow_on_submit": 1, - "default": "{supplier_name}", - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "label": "Title", - "no_copy": 1, - "print_hide": 1, - "reqd": 1 - }, { "fieldname": "naming_series", "fieldtype": "Select", @@ -1328,7 +1321,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2026-03-09 17:15:29.184682", + "modified": "2026-03-25 11:46:18.748951", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index bd22a21bd99..7e672da22b6 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -159,7 +159,6 @@ class PurchaseOrder(BuyingController): taxes_and_charges_deducted: DF.Currency tc_name: DF.Link | None terms: DF.TextEditor | None - title: DF.Data to_date: DF.Date | None total: DF.Currency total_net_weight: DF.Float From 3d79dce8b3f9b027639c5b49cb040f8bfdfa08d1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:09:13 +0000 Subject: [PATCH 028/168] fix: flaky currency exchange test (backport #53813) (#53817) --- erpnext/selling/doctype/quotation/test_quotation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index b841f456bc9..9c02879284c 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -1029,11 +1029,11 @@ class TestQuotation(ERPNextTestSuite): def test_make_quotation_qar_to_inr(self): quotation = make_quotation( currency="QAR", - transaction_date="2026-06-04", + transaction_date="2026-01-01", ) cache = frappe.cache() - key = "currency_exchange_rate_{}:{}:{}".format("2026-06-04", "QAR", "INR") + key = "currency_exchange_rate_{}:{}:{}".format("2026-01-01", "QAR", "INR") value = cache.get(key) expected_rate = flt(value) / 3.64 From 10f58112aea37a235d06ab88ea4f350827d91679 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 27 Mar 2026 09:54:24 +0530 Subject: [PATCH 029/168] test: fixed test case --- .../doctype/work_order/test_work_order.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 3b39c58fbac..bea542b7bfa 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -508,10 +508,28 @@ class TestWorkOrder(ERPNextTestSuite): def test_work_order_material_transferred_qty_with_process_loss(self): stock_entries = [] - bom = frappe.get_doc( - "BOM", {"docstatus": 1, "with_operations": 1, "company": "_Test Company", "has_variants": 0} + item_code = make_item("_Test Item For Process Loss", {"is_stock_item": 1}).name + rm_item_code = make_item("Test Item For Process Loss RM", {"is_stock_item": 1}).name + + bom = make_bom( + item=item_code, + raw_materials=[rm_item_code], + with_operations=1, + do_not_save=True, ) + operation = { + "operation": "_Test Operation 1", + "workstation": "_Test Workstation 1", + "description": "Test Data", + "operating_cost": 100, + "time_in_mins": 40, + } + + bom.append("operations", operation) + bom.save() + bom.submit() + work_order = make_wo_order_test_record( item=bom.item, qty=2, From 00bb07aaa3ee6c6b1d28679d6a4234e692bad289 Mon Sep 17 00:00:00 2001 From: Shllokkk Date: Thu, 12 Mar 2026 00:54:40 +0530 Subject: [PATCH 030/168] fix(email_campaign): prevent unsubscribing entire campaign when email group member unsubscribes (cherry picked from commit 56f597f5ad9e6cbe12c028687d353db80672ef5e) --- .../doctype/email_campaign/email_campaign.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py index 9e24a26caa8..4454ede5310 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.py +++ b/erpnext/crm/doctype/email_campaign/email_campaign.py @@ -204,8 +204,22 @@ def send_mail(entry, email_campaign): # called from hooks on doc_event Email Unsubscribe def unsubscribe_recipient(unsubscribe, method): - if unsubscribe.reference_doctype == "Email Campaign": - frappe.db.set_value("Email Campaign", unsubscribe.reference_name, "status", "Unsubscribed") + if unsubscribe.reference_doctype != "Email Campaign": + return + + email_campaign = frappe.get_doc("Email Campaign", unsubscribe.reference_name) + + if email_campaign.email_campaign_for == "Email Group": + if unsubscribe.email: + frappe.db.set_value( + "Email Group Member", + {"email_group": email_campaign.recipient, "email": unsubscribe.email}, + "unsubscribed", + 1, + ) + else: + # For Lead or Contact + frappe.db.set_value("Email Campaign", email_campaign.name, "status", "Unsubscribed") # called through hooks to update email campaign status daily From 407c3cd5752bdea8adf006dce86620a1a2a85f14 Mon Sep 17 00:00:00 2001 From: Shllokkk Date: Fri, 13 Mar 2026 20:41:41 +0530 Subject: [PATCH 031/168] feat(report): add service start/end date and amount with roll-ups in deferred revenue/expense report (cherry picked from commit 8e5692d8a36a7a36347672e509fd828540d7b451) --- .../deferred_revenue_and_expense.py | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index caa464c5447..22fabeabca6 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -48,6 +48,9 @@ class Deferred_Item: Generate report data for output """ ret_data = frappe._dict({"name": self.item_name}) + ret_data.service_start_date = self.service_start_date + ret_data.service_end_date = self.service_end_date + ret_data.amount = self.base_net_amount for period in self.period_total: ret_data[period.key] = period.total ret_data.indent = 1 @@ -205,6 +208,9 @@ class Deferred_Invoice: for item in self.uniq_items: self.items.append(Deferred_Item(item, self, [x for x in items if x.item == item])) + # roll-up amount from all deferred items + self.amount_total = sum(item.base_net_amount for item in self.items) + def calculate_invoice_revenue_expense_for_period(self): """ calculate deferred revenue/expense for all items in invoice @@ -232,7 +238,7 @@ class Deferred_Invoice: generate report data for invoice, includes invoice total """ ret_data = [] - inv_total = frappe._dict({"name": self.name}) + inv_total = frappe._dict({"name": self.name, "amount": self.amount_total}) for x in self.period_total: inv_total[x.key] = x.total inv_total.indent = 0 @@ -386,6 +392,24 @@ class Deferred_Revenue_and_Expense_Report: def get_columns(self): columns = [] columns.append({"label": _("Name"), "fieldname": "name", "fieldtype": "Data", "read_only": 1}) + columns.append( + { + "label": _("Service Start Date"), + "fieldname": "service_start_date", + "fieldtype": "Date", + "read_only": 1, + } + ) + columns.append( + { + "label": _("Service End Date"), + "fieldname": "service_end_date", + "fieldtype": "Date", + "read_only": 1, + } + ) + columns.append({"label": _("Amount"), "fieldname": "amount", "fieldtype": "Currency", "read_only": 1}) + for period in self.period_list: columns.append( { @@ -415,6 +439,8 @@ class Deferred_Revenue_and_Expense_Report: elif self.filters.type == "Expense": total_row = frappe._dict({"name": "Total Deferred Expense"}) + total_row["amount"] = sum(inv.amount_total for inv in self.deferred_invoices) + for idx, period in enumerate(self.period_list, 0): total_row[period.key] = self.period_total[idx].total ret.append(total_row) From d12b54c50a3899c0c9c68c7ce3f030cb41dda6d7 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Fri, 27 Mar 2026 11:24:35 +0530 Subject: [PATCH 032/168] chore: resolve conflict --- erpnext/buying/doctype/purchase_order/purchase_order.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index a4e50467baa..260dc52ceac 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -9,11 +9,6 @@ "engine": "InnoDB", "field_order": [ "supplier_section", -<<<<<<< HEAD - "title", -======= - "company", ->>>>>>> 5b1fa81451 (fix(accounts): set supplier name as title field in Purchase Invoice (#53710)) "naming_series", "supplier", "supplier_name", From 8a5e2cc0a678a08fc22663f559401d2ac8808d0e Mon Sep 17 00:00:00 2001 From: nishkagosalia Date: Mon, 23 Mar 2026 16:18:59 +0530 Subject: [PATCH 033/168] feat: Bom stock analysis report (cherry picked from commit 5d088350dc328e4551a457dd89a35f6ec46599b4) --- .../report/bom_stock_analysis/__init__.py | 0 .../bom_stock_analysis/bom_stock_analysis.js | 43 +++ .../bom_stock_analysis.json | 31 ++ .../bom_stock_analysis/bom_stock_analysis.py | 282 ++++++++++++++++++ .../test_bom_stock_analysis.py | 119 ++++++++ 5 files changed, 475 insertions(+) create mode 100644 erpnext/manufacturing/report/bom_stock_analysis/__init__.py create mode 100644 erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js create mode 100644 erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.json create mode 100644 erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py create mode 100644 erpnext/manufacturing/report/bom_stock_analysis/test_bom_stock_analysis.py diff --git a/erpnext/manufacturing/report/bom_stock_analysis/__init__.py b/erpnext/manufacturing/report/bom_stock_analysis/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js new file mode 100644 index 00000000000..d97392a5afd --- /dev/null +++ b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js @@ -0,0 +1,43 @@ +// Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.query_reports["BOM Stock Analysis"] = { + filters: [ + { + fieldname: "bom", + label: __("BOM"), + fieldtype: "Link", + options: "BOM", + reqd: 1, + }, + { + fieldname: "warehouse", + label: __("Warehouse"), + fieldtype: "Link", + options: "Warehouse", + }, + { + fieldname: "qty_to_make", + label: __("FG Items to Make"), + fieldtype: "Float", + }, + { + fieldname: "show_exploded_view", + label: __("Show availability of exploded items"), + fieldtype: "Check", + default: false, + }, + ], + formatter: function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (column.id == "producible_fg_item") { + if (data["producible_fg_item"] >= data["required_qty"]) { + value = `
${data["producible_fg_item"]}`; + } else { + value = `${data["producible_fg_item"]}`; + } + } + return value; + }, +}; diff --git a/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.json b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.json new file mode 100644 index 00000000000..b0e68f77ba7 --- /dev/null +++ b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.json @@ -0,0 +1,31 @@ +{ + "add_total_row": 0, + "add_translate_data": 0, + "columns": [], + "creation": "2026-03-23 15:42:06.064606", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letter_head": null, + "modified": "2026-03-23 15:48:56.933892", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "BOM Stock Analysis", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "BOM", + "report_name": "BOM Stock Analysis", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing Manager" + }, + { + "role": "Manufacturing User" + } + ], + "timeout": 0 +} diff --git a/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py new file mode 100644 index 00000000000..d3220ee35b5 --- /dev/null +++ b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py @@ -0,0 +1,282 @@ +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.query_builder.functions import Floor, IfNull, Sum +from frappe.utils.data import comma_and +from pypika.terms import ExistsCriterion + + +def execute(filters=None): + qty_to_make = filters.get("qty_to_make") + + if qty_to_make: + columns = get_columns_with_qty_to_make() + data = get_data_with_qty_to_make(filters) + return columns, data + else: + data = [] + columns = get_columns_without_qty_to_make() + bom_data = get_producible_fg_items(filters) + for row in bom_data: + data.append(row) + + return columns, data + + +def get_data_with_qty_to_make(filters): + data = [] + bom_data = get_bom_data(filters) + manufacture_details = get_manufacturer_records() + + for row in bom_data: + required_qty = filters.get("qty_to_make") * row.qty_per_unit + last_purchase_rate = frappe.db.get_value("Item", row.item_code, "last_purchase_rate") + + data.append(get_report_data(last_purchase_rate, required_qty, row, manufacture_details)) + + return data + + +def get_report_data(last_purchase_rate, required_qty, row, manufacture_details): + qty_per_unit = row.qty_per_unit if row.qty_per_unit > 0 else 0 + difference_qty = row.actual_qty - required_qty + return [ + row.item_code, + row.description, + row.from_bom_no, + comma_and(manufacture_details.get(row.item_code, {}).get("manufacturer", []), add_quotes=False), + comma_and(manufacture_details.get(row.item_code, {}).get("manufacturer_part", []), add_quotes=False), + qty_per_unit, + row.actual_qty, + required_qty, + difference_qty, + last_purchase_rate, + row.actual_qty // qty_per_unit if qty_per_unit else 0, + ] + + +def get_columns_with_qty_to_make(): + return [ + { + "fieldname": "item", + "label": _("Item"), + "fieldtype": "Link", + "options": "Item", + "width": 120, + }, + { + "fieldname": "description", + "label": _("Description"), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "from_bom_no", + "label": _("From BOM No"), + "fieldtype": "Link", + "options": "BOM", + "width": 150, + }, + { + "fieldname": "manufacturer", + "label": _("Manufacturer"), + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "manufacturer_part_number", + "label": _("Manufacturer Part Number"), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "qty_per_unit", + "label": _("Qty Per Unit"), + "fieldtype": "Float", + "width": 110, + }, + { + "fieldname": "available_qty", + "label": _("Available Qty"), + "fieldtype": "Float", + "width": 120, + }, + { + "fieldname": "required_qty", + "label": _("Required Qty"), + "fieldtype": "Float", + "width": 120, + }, + { + "fieldname": "difference_qty", + "label": _("Difference Qty"), + "fieldtype": "Float", + "width": 130, + }, + { + "fieldname": "last_purchase_rate", + "label": _("Last Purchase Rate"), + "fieldtype": "Float", + "width": 160, + }, + { + "fieldname": "producible_fg_item", + "label": _("Producible FG Item"), + "fieldtype": "Float", + "width": 200, + }, + ] + + +def get_columns_without_qty_to_make(): + return [ + _("Item") + ":Link/Item:150", + _("Item Name") + "::240", + _("Description") + "::300", + _("From BOM No") + "::200", + _("Required Qty") + ":Float:160", + _("Producible FG Item") + ":Float:200", + ] + + +def get_bom_data(filters): + bom_item_table = "BOM Explosion Item" if filters.get("show_exploded_view") else "BOM Item" + + bom_item = frappe.qb.DocType(bom_item_table) + bin = frappe.qb.DocType("Bin") + + query = ( + frappe.qb.from_(bom_item) + .left_join(bin) + .on(bom_item.item_code == bin.item_code) + .select( + bom_item.item_code, + bom_item.description, + bom_item.parent.as_("from_bom_no"), + bom_item.qty_consumed_per_unit.as_("qty_per_unit"), + IfNull(Sum(bin.actual_qty), 0).as_("actual_qty"), + ) + .where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM")) + .groupby(bom_item.item_code) + .orderby(bom_item.idx) + ) + + if filters.get("warehouse"): + warehouse_details = frappe.db.get_value( + "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1 + ) + + if warehouse_details: + wh = frappe.qb.DocType("Warehouse") + query = query.where( + ExistsCriterion( + frappe.qb.from_(wh) + .select(wh.name) + .where( + (wh.lft >= warehouse_details.lft) + & (wh.rgt <= warehouse_details.rgt) + & (bin.warehouse == wh.name) + ) + ) + ) + else: + query = query.where(bin.warehouse == filters.get("warehouse")) + + if bom_item_table == "BOM Item": + query = query.select(bom_item.bom_no, bom_item.is_phantom_item) + + data = query.run(as_dict=True) + return explode_phantom_boms(data, filters) if bom_item_table == "BOM Item" else data + + +def explode_phantom_boms(data, filters): + original_bom = filters.get("bom") + replacements = [] + + for idx, item in enumerate(data): + if not item.is_phantom_item: + continue + + filters["bom"] = item.bom_no + children = get_bom_data(filters) + filters["bom"] = original_bom + + for child in children: + child.qty_per_unit = (child.qty_per_unit or 0) * (item.qty_per_unit or 0) + + replacements.append((idx, children)) + + for idx, children in reversed(replacements): + data.pop(idx) + data[idx:idx] = children + + filters["bom"] = original_bom + return data + + +def get_manufacturer_records(): + details = frappe.get_all( + "Item Manufacturer", fields=["manufacturer", "manufacturer_part_no", "item_code"] + ) + + manufacture_details = frappe._dict() + for detail in details: + dic = manufacture_details.setdefault(detail.get("item_code"), {}) + dic.setdefault("manufacturer", []).append(detail.get("manufacturer")) + dic.setdefault("manufacturer_part", []).append(detail.get("manufacturer_part_no")) + + return manufacture_details + + +def get_producible_fg_items(filters): + BOM_ITEM = frappe.qb.DocType("BOM Item") + BOM = frappe.qb.DocType("BOM") + BIN = frappe.qb.DocType("Bin") + WH = frappe.qb.DocType("Warehouse") + + warehouse = filters.get("warehouse") + warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1) + + if not warehouse: + frappe.throw(_("Warehouse is required to get producible FG Items")) + + if warehouse_details: + bin_subquery = ( + frappe.qb.from_(BIN) + .join(WH) + .on(BIN.warehouse == WH.name) + .select(BIN.item_code, Sum(BIN.actual_qty).as_("actual_qty")) + .where((WH.lft >= warehouse_details.lft) & (WH.rgt <= warehouse_details.rgt)) + .groupby(BIN.item_code) + ) + else: + bin_subquery = ( + frappe.qb.from_(BIN) + .select(BIN.item_code, Sum(BIN.actual_qty).as_("actual_qty")) + .where(BIN.warehouse == warehouse) + .groupby(BIN.item_code) + ) + + query = ( + frappe.qb.from_(BOM_ITEM) + .join(BOM) + .on(BOM_ITEM.parent == BOM.name) + .left_join(bin_subquery) + .on(BOM_ITEM.item_code == bin_subquery.item_code) + .select( + BOM_ITEM.item_code, + BOM_ITEM.item_name, + BOM_ITEM.description, + BOM_ITEM.parent.as_("from_bom_no"), + (BOM_ITEM.stock_qty / BOM.quantity).as_("qty_per_unit"), + Floor(bin_subquery.actual_qty / ((Sum(BOM_ITEM.stock_qty)) / BOM.quantity)), + ) + .where((BOM_ITEM.parent == filters.get("bom")) & (BOM_ITEM.parenttype == "BOM")) + .groupby(BOM_ITEM.item_code) + .orderby(BOM_ITEM.idx) + ) + + data = query.run(as_list=True) + return data diff --git a/erpnext/manufacturing/report/bom_stock_analysis/test_bom_stock_analysis.py b/erpnext/manufacturing/report/bom_stock_analysis/test_bom_stock_analysis.py new file mode 100644 index 00000000000..ebb1b85ac53 --- /dev/null +++ b/erpnext/manufacturing/report/bom_stock_analysis/test_bom_stock_analysis.py @@ -0,0 +1,119 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe + +from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom +from erpnext.manufacturing.report.bom_stock_analysis.bom_stock_analysis import ( + execute as bom_stock_analysis_report, +) +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.tests.utils import ERPNextTestSuite + + +class TestBOMStockAnalysis(ERPNextTestSuite): + def setUp(self): + self.fg_item, self.rm_items = create_items() + self.boms = create_boms(self.fg_item, self.rm_items) + + def test_bom_stock_analysis(self): + qty_to_make = 10 + + # Case 1: When Item(s) Qty and Stock Qty are equal. + data = bom_stock_analysis_report( + filters={ + "qty_to_make": qty_to_make, + "bom": self.boms[0].name, + } + )[1] + expected_data = get_expected_data(self.boms[0], qty_to_make) + self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) + + # Case 2: When Item(s) Qty and Stock Qty are different and BOM Qty is 1. + data = bom_stock_analysis_report( + filters={ + "qty_to_make": qty_to_make, + "bom": self.boms[1].name, + } + )[1] + expected_data = get_expected_data(self.boms[1], qty_to_make) + self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) + + # Case 3: When Item(s) Qty and Stock Qty are different and BOM Qty is greater than 1. + data = bom_stock_analysis_report( + filters={ + "qty_to_make": qty_to_make, + "bom": self.boms[2].name, + } + )[1] + expected_data = get_expected_data(self.boms[2], qty_to_make) + self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) + + +def create_items(): + fg_item = make_item(properties={"is_stock_item": 1}).name + rm_item1 = make_item( + properties={ + "is_stock_item": 1, + "standard_rate": 100, + "opening_stock": 100, + "last_purchase_rate": 100, + "item_defaults": [{"company": "_Test Company", "default_warehouse": "Stores - _TC"}], + } + ).name + rm_item2 = make_item( + properties={ + "is_stock_item": 1, + "standard_rate": 200, + "opening_stock": 200, + "last_purchase_rate": 200, + "item_defaults": [{"company": "_Test Company", "default_warehouse": "Stores - _TC"}], + } + ).name + + return fg_item, [rm_item1, rm_item2] + + +def create_boms(fg_item, rm_items): + def update_bom_items(bom, uom, conversion_factor): + for item in bom.items: + item.uom = uom + item.conversion_factor = conversion_factor + + return bom + + bom1 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10) + + bom2 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10, do_not_submit=True) + bom2 = update_bom_items(bom2, "Box", 10) + bom2.save() + bom2.submit() + + bom3 = make_bom(item=fg_item, quantity=2, raw_materials=rm_items, rm_qty=10, do_not_submit=True) + bom3 = update_bom_items(bom3, "Box", 10) + bom3.save() + bom3.submit() + + return [bom1, bom2, bom3] + + +def get_expected_data(bom, qty_to_make): + expected_data = [] + + for idx in range(len(bom.items)): + expected_data.append( + [ + bom.items[idx].item_code, + bom.items[idx].item_code, + bom.name, + "", + "", + float(bom.items[idx].stock_qty / bom.quantity), + float(100 * (idx + 1)), + float(qty_to_make * (bom.items[idx].stock_qty / bom.quantity)), + float((100 * (idx + 1)) - (qty_to_make * (bom.items[idx].stock_qty / bom.quantity))), + float(100 * (idx + 1)), + ] + ) + + return expected_data From 6d92792634eda965b5269cf161cb5c73d86c9225 Mon Sep 17 00:00:00 2001 From: nishkagosalia Date: Tue, 24 Mar 2026 12:02:09 +0530 Subject: [PATCH 034/168] fix: change in functionality (cherry picked from commit c1874cb7d58f4714f1ef92d705ea53343b4861b9) --- .../bom_stock_analysis/bom_stock_analysis.js | 28 ++- .../bom_stock_analysis/bom_stock_analysis.py | 237 +++++++++++------- .../test_bom_stock_analysis.py | 104 ++++++-- 3 files changed, 240 insertions(+), 129 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js index d97392a5afd..7c6ccfdf743 100644 --- a/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js +++ b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js @@ -28,16 +28,32 @@ frappe.query_reports["BOM Stock Analysis"] = { default: false, }, ], - formatter: function (value, row, column, data, default_formatter) { + formatter(value, row, column, data, default_formatter) { + if (data && data.bold && column.fieldname === "item") { + return value ? `${value}` : ""; + } + value = default_formatter(value, row, column, data); - if (column.id == "producible_fg_item") { - if (data["producible_fg_item"] >= data["required_qty"]) { - value = `${data["producible_fg_item"]}`; - } else { - value = `${data["producible_fg_item"]}`; + if (column.fieldname === "difference_qty" && value !== "" && value !== undefined) { + const numeric = parseFloat(value.replace(/,/g, "")) || 0; + if (numeric < 0) { + value = `${value}`; + } else if (numeric > 0) { + value = `${value}`; } } + + if (data && data.bold) { + if (column.fieldname === "description" || column.fieldname === "item_name") { + const qty_to_make = frappe.query_report.get_filter_value("qty_to_make"); + const producible = parseFloat(value) || 0; + const colour = qty_to_make && producible < qty_to_make ? "red" : "green"; + return `${value}`; + } + return `${value}`; + } + return value; }, }; diff --git a/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py index d3220ee35b5..78aa75aa7fa 100644 --- a/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py +++ b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py @@ -4,74 +4,101 @@ import frappe from frappe import _ from frappe.query_builder.functions import Floor, IfNull, Sum +from frappe.utils import flt, fmt_money from frappe.utils.data import comma_and from pypika.terms import ExistsCriterion def execute(filters=None): - qty_to_make = filters.get("qty_to_make") - - if qty_to_make: + if filters.get("qty_to_make"): columns = get_columns_with_qty_to_make() data = get_data_with_qty_to_make(filters) - return columns, data else: - data = [] columns = get_columns_without_qty_to_make() - bom_data = get_producible_fg_items(filters) - for row in bom_data: - data.append(row) + data = get_data_without_qty_to_make(filters) - return columns, data + return columns, data + + +def fmt_qty(value): + """Format a float quantity for display as a string, so blank rows stay blank.""" + return frappe.utils.fmt_money(value, precision=2, currency=None) + + +def fmt_rate(value): + """Format a currency rate for display as a string.""" + currency = frappe.defaults.get_global_default("currency") + return frappe.utils.fmt_money(value, precision=2, currency=currency) def get_data_with_qty_to_make(filters): - data = [] bom_data = get_bom_data(filters) manufacture_details = get_manufacturer_records() + purchase_rates = batch_fetch_purchase_rates(bom_data) + qty_to_make = filters.get("qty_to_make") + data = [] for row in bom_data: - required_qty = filters.get("qty_to_make") * row.qty_per_unit - last_purchase_rate = frappe.db.get_value("Item", row.item_code, "last_purchase_rate") + qty_per_unit = flt(row.qty_per_unit) if row.qty_per_unit > 0 else 0 + required_qty = qty_to_make * qty_per_unit + difference_qty = flt(row.actual_qty) - required_qty + rate = purchase_rates.get(row.item_code, 0) - data.append(get_report_data(last_purchase_rate, required_qty, row, manufacture_details)) + data.append( + { + "item": row.item_code, + "description": row.description, + "from_bom_no": row.from_bom_no, + "manufacturer": comma_and( + manufacture_details.get(row.item_code, {}).get("manufacturer", []), add_quotes=False + ), + "manufacturer_part_number": comma_and( + manufacture_details.get(row.item_code, {}).get("manufacturer_part", []), add_quotes=False + ), + "qty_per_unit": fmt_qty(qty_per_unit), + "available_qty": fmt_qty(row.actual_qty), + "required_qty": fmt_qty(required_qty), + "difference_qty": fmt_qty(difference_qty), + "last_purchase_rate": fmt_rate(rate), + "_available_qty": flt(row.actual_qty), + "_qty_per_unit": qty_per_unit, + } + ) + + min_producible = ( + min(int(r["_available_qty"] // r["_qty_per_unit"]) for r in data if r["_qty_per_unit"]) if data else 0 + ) + + for row in data: + row.pop("_available_qty", None) + row.pop("_qty_per_unit", None) + + # blank spacer row + data.append({}) + + data.append( + { + "item": _("Maximum Producible Items"), + "description": min_producible, + "from_bom_no": "", + "manufacturer": "", + "manufacturer_part_number": "", + "qty_per_unit": "", + "available_qty": "", + "required_qty": "", + "difference_qty": "", + "last_purchase_rate": "", + "bold": 1, + } + ) return data -def get_report_data(last_purchase_rate, required_qty, row, manufacture_details): - qty_per_unit = row.qty_per_unit if row.qty_per_unit > 0 else 0 - difference_qty = row.actual_qty - required_qty - return [ - row.item_code, - row.description, - row.from_bom_no, - comma_and(manufacture_details.get(row.item_code, {}).get("manufacturer", []), add_quotes=False), - comma_and(manufacture_details.get(row.item_code, {}).get("manufacturer_part", []), add_quotes=False), - qty_per_unit, - row.actual_qty, - required_qty, - difference_qty, - last_purchase_rate, - row.actual_qty // qty_per_unit if qty_per_unit else 0, - ] - - def get_columns_with_qty_to_make(): return [ - { - "fieldname": "item", - "label": _("Item"), - "fieldtype": "Link", - "options": "Item", - "width": 120, - }, - { - "fieldname": "description", - "label": _("Description"), - "fieldtype": "Data", - "width": 150, - }, + {"fieldname": "item", "label": _("Item"), "fieldtype": "Link", "options": "Item", "width": 180}, + {"fieldname": "description", "label": _("Description"), "fieldtype": "Data", "width": 160}, { "fieldname": "from_bom_no", "label": _("From BOM No"), @@ -79,68 +106,89 @@ def get_columns_with_qty_to_make(): "options": "BOM", "width": 150, }, - { - "fieldname": "manufacturer", - "label": _("Manufacturer"), - "fieldtype": "Data", - "width": 120, - }, + {"fieldname": "manufacturer", "label": _("Manufacturer"), "fieldtype": "Data", "width": 130}, { "fieldname": "manufacturer_part_number", "label": _("Manufacturer Part Number"), "fieldtype": "Data", - "width": 150, - }, - { - "fieldname": "qty_per_unit", - "label": _("Qty Per Unit"), - "fieldtype": "Float", - "width": 110, - }, - { - "fieldname": "available_qty", - "label": _("Available Qty"), - "fieldtype": "Float", - "width": 120, - }, - { - "fieldname": "required_qty", - "label": _("Required Qty"), - "fieldtype": "Float", - "width": 120, - }, - { - "fieldname": "difference_qty", - "label": _("Difference Qty"), - "fieldtype": "Float", - "width": 130, + "width": 170, }, + {"fieldname": "qty_per_unit", "label": _("Qty Per Unit"), "fieldtype": "Data", "width": 110}, + {"fieldname": "available_qty", "label": _("Available Qty"), "fieldtype": "Data", "width": 120}, + {"fieldname": "required_qty", "label": _("Required Qty"), "fieldtype": "Data", "width": 120}, + {"fieldname": "difference_qty", "label": _("Difference Qty"), "fieldtype": "Data", "width": 130}, { "fieldname": "last_purchase_rate", "label": _("Last Purchase Rate"), - "fieldtype": "Float", + "fieldtype": "Data", "width": 160, }, - { - "fieldname": "producible_fg_item", - "label": _("Producible FG Item"), - "fieldtype": "Float", - "width": 200, - }, ] +def get_data_without_qty_to_make(filters): + raw_rows = get_producible_fg_items(filters) + + data = [] + for row in raw_rows: + data.append( + { + "item": row[0], + "description": row[1], + "from_bom_no": row[2], + "qty_per_unit": fmt_qty(row[3]), + "available_qty": fmt_qty(row[4]), + } + ) + + min_producible = min((row[5] or 0) for row in raw_rows) if raw_rows else 0 + # blank spacer row + data.append({}) + + data.append( + { + "item": _("Maximum Producible Items"), + "description": min_producible, + "from_bom_no": "", + "qty_per_unit": "", + "available_qty": "", + "bold": 1, + } + ) + + return data + + def get_columns_without_qty_to_make(): return [ - _("Item") + ":Link/Item:150", - _("Item Name") + "::240", - _("Description") + "::300", - _("From BOM No") + "::200", - _("Required Qty") + ":Float:160", - _("Producible FG Item") + ":Float:200", + {"fieldname": "item", "label": _("Item"), "fieldtype": "Link", "options": "Item", "width": 180}, + {"fieldname": "description", "label": _("Description"), "fieldtype": "Data", "width": 200}, + { + "fieldname": "from_bom_no", + "label": _("From BOM No"), + "fieldtype": "Link", + "options": "BOM", + "width": 160, + }, + {"fieldname": "qty_per_unit", "label": _("Qty Per Unit"), "fieldtype": "Data", "width": 120}, + {"fieldname": "available_qty", "label": _("Available Qty"), "fieldtype": "Data", "width": 120}, ] +def batch_fetch_purchase_rates(bom_data): + if not bom_data: + return {} + item_codes = [row.item_code for row in bom_data] + return { + r.name: r.last_purchase_rate + for r in frappe.get_all( + "Item", + filters={"name": ["in", item_codes]}, + fields=["name", "last_purchase_rate"], + ) + } + + def get_bom_data(filters): bom_item_table = "BOM Explosion Item" if filters.get("show_exploded_view") else "BOM Item" @@ -167,7 +215,6 @@ def get_bom_data(filters): warehouse_details = frappe.db.get_value( "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1 ) - if warehouse_details: wh = frappe.qb.DocType("Warehouse") query = query.where( @@ -212,7 +259,6 @@ def explode_phantom_boms(data, filters): data.pop(idx) data[idx:idx] = children - filters["bom"] = original_bom return data @@ -220,13 +266,11 @@ def get_manufacturer_records(): details = frappe.get_all( "Item Manufacturer", fields=["manufacturer", "manufacturer_part_no", "item_code"] ) - manufacture_details = frappe._dict() for detail in details: dic = manufacture_details.setdefault(detail.get("item_code"), {}) dic.setdefault("manufacturer", []).append(detail.get("manufacturer")) dic.setdefault("manufacturer_part", []).append(detail.get("manufacturer_part_no")) - return manufacture_details @@ -237,11 +281,11 @@ def get_producible_fg_items(filters): WH = frappe.qb.DocType("Warehouse") warehouse = filters.get("warehouse") - warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1) - if not warehouse: frappe.throw(_("Warehouse is required to get producible FG Items")) + warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1) + if warehouse_details: bin_subquery = ( frappe.qb.from_(BIN) @@ -267,10 +311,10 @@ def get_producible_fg_items(filters): .on(BOM_ITEM.item_code == bin_subquery.item_code) .select( BOM_ITEM.item_code, - BOM_ITEM.item_name, BOM_ITEM.description, BOM_ITEM.parent.as_("from_bom_no"), (BOM_ITEM.stock_qty / BOM.quantity).as_("qty_per_unit"), + IfNull(bin_subquery.actual_qty, 0).as_("available_qty"), Floor(bin_subquery.actual_qty / ((Sum(BOM_ITEM.stock_qty)) / BOM.quantity)), ) .where((BOM_ITEM.parent == filters.get("bom")) & (BOM_ITEM.parenttype == "BOM")) @@ -278,5 +322,4 @@ def get_producible_fg_items(filters): .orderby(BOM_ITEM.idx) ) - data = query.run(as_list=True) - return data + return query.run(as_list=True) diff --git a/erpnext/manufacturing/report/bom_stock_analysis/test_bom_stock_analysis.py b/erpnext/manufacturing/report/bom_stock_analysis/test_bom_stock_analysis.py index ebb1b85ac53..fd8a52afde0 100644 --- a/erpnext/manufacturing/report/bom_stock_analysis/test_bom_stock_analysis.py +++ b/erpnext/manufacturing/report/bom_stock_analysis/test_bom_stock_analysis.py @@ -1,7 +1,7 @@ -# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - import frappe +from frappe.utils import fmt_money from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.manufacturing.report.bom_stock_analysis.bom_stock_analysis import ( @@ -11,6 +11,15 @@ from erpnext.stock.doctype.item.test_item import make_item from erpnext.tests.utils import ERPNextTestSuite +def fmt_qty(value): + return fmt_money(value, precision=2, currency=None) + + +def fmt_rate(value): + currency = frappe.defaults.get_global_default("currency") + return fmt_money(value, precision=2, currency=currency) + + class TestBOMStockAnalysis(ERPNextTestSuite): def setUp(self): self.fg_item, self.rm_items = create_items() @@ -20,34 +29,62 @@ class TestBOMStockAnalysis(ERPNextTestSuite): qty_to_make = 10 # Case 1: When Item(s) Qty and Stock Qty are equal. - data = bom_stock_analysis_report( + raw_data = bom_stock_analysis_report( filters={ "qty_to_make": qty_to_make, "bom": self.boms[0].name, } )[1] - expected_data = get_expected_data(self.boms[0], qty_to_make) - self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) + + data, footer = split_data_and_footer(raw_data) + expected_data, expected_min = get_expected_data(self.boms[0], qty_to_make) + + self.assertSetEqual( + set(tuple(sorted(r.items())) for r in data), + set(tuple(sorted(r.items())) for r in expected_data), + ) + self.assertEqual(footer.get("description"), expected_min) # Case 2: When Item(s) Qty and Stock Qty are different and BOM Qty is 1. - data = bom_stock_analysis_report( + raw_data = bom_stock_analysis_report( filters={ "qty_to_make": qty_to_make, "bom": self.boms[1].name, } )[1] - expected_data = get_expected_data(self.boms[1], qty_to_make) - self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) + + data, footer = split_data_and_footer(raw_data) + expected_data, expected_min = get_expected_data(self.boms[1], qty_to_make) + + self.assertSetEqual( + set(tuple(sorted(r.items())) for r in data), + set(tuple(sorted(r.items())) for r in expected_data), + ) + self.assertEqual(footer.get("description"), expected_min) # Case 3: When Item(s) Qty and Stock Qty are different and BOM Qty is greater than 1. - data = bom_stock_analysis_report( + raw_data = bom_stock_analysis_report( filters={ "qty_to_make": qty_to_make, "bom": self.boms[2].name, } )[1] - expected_data = get_expected_data(self.boms[2], qty_to_make) - self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) + + data, footer = split_data_and_footer(raw_data) + expected_data, expected_min = get_expected_data(self.boms[2], qty_to_make) + + self.assertSetEqual( + set(tuple(sorted(r.items())) for r in data), + set(tuple(sorted(r.items())) for r in expected_data), + ) + self.assertEqual(footer.get("description"), expected_min) + + +def split_data_and_footer(raw_data): + """Separate component rows from the footer row. Skips blank spacer rows.""" + data = [row for row in raw_data if row and not row.get("bold")] + footer = next((row for row in raw_data if row and row.get("bold")), {}) + return data, footer def create_items(): @@ -79,7 +116,6 @@ def create_boms(fg_item, rm_items): for item in bom.items: item.uom = uom item.conversion_factor = conversion_factor - return bom bom1 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10) @@ -98,22 +134,38 @@ def create_boms(fg_item, rm_items): def get_expected_data(bom, qty_to_make): + """ + Returns (component_rows, min_producible). + Component rows are dicts matching what the report produces. + min_producible is the expected footer value. + """ expected_data = [] + producible_per_item = [] + + for idx, bom_item in enumerate(bom.items): + qty_per_unit = float(bom_item.stock_qty / bom.quantity) + available_qty = float(100 * (idx + 1)) + required_qty = float(qty_to_make * qty_per_unit) + difference_qty = available_qty - required_qty + last_purchase_rate = float(100 * (idx + 1)) - for idx in range(len(bom.items)): expected_data.append( - [ - bom.items[idx].item_code, - bom.items[idx].item_code, - bom.name, - "", - "", - float(bom.items[idx].stock_qty / bom.quantity), - float(100 * (idx + 1)), - float(qty_to_make * (bom.items[idx].stock_qty / bom.quantity)), - float((100 * (idx + 1)) - (qty_to_make * (bom.items[idx].stock_qty / bom.quantity))), - float(100 * (idx + 1)), - ] + { + "item": bom_item.item_code, + "description": bom_item.item_code, # description falls back to item_code in test items + "from_bom_no": bom.name, + "manufacturer": "", + "manufacturer_part_number": "", + "qty_per_unit": fmt_qty(qty_per_unit), + "available_qty": fmt_qty(available_qty), + "required_qty": fmt_qty(required_qty), + "difference_qty": fmt_qty(difference_qty), + "last_purchase_rate": fmt_rate(last_purchase_rate), + } ) - return expected_data + producible_per_item.append(int(available_qty // qty_per_unit) if qty_per_unit else 0) + + min_producible = min(producible_per_item) if producible_per_item else 0 + + return expected_data, min_producible From d1a357191830fb2c4b99184f7d6a6645427e4451 Mon Sep 17 00:00:00 2001 From: nishkagosalia Date: Thu, 26 Mar 2026 15:15:53 +0530 Subject: [PATCH 035/168] chore: Dropping bom stock report and bom stock calculated report (cherry picked from commit 3bedc6cf7ea69ca016681dd9b2b245d86192aca8) # Conflicts: # erpnext/manufacturing/report/bom_stock_calculated/test_bom_stock_calculated.py --- .../report/bom_stock_calculated/__init__.py | 0 .../bom_stock_calculated.js | 33 --- .../bom_stock_calculated.json | 26 --- .../bom_stock_calculated.py | 199 ------------------ .../report/bom_stock_report/__init__.py | 0 .../bom_stock_report/bom_stock_report.html | 27 --- .../bom_stock_report/bom_stock_report.js | 41 ---- .../bom_stock_report/bom_stock_report.json | 28 --- .../bom_stock_report/bom_stock_report.py | 106 ---------- .../bom_stock_report/test_bom_stock_report.py | 112 ---------- 10 files changed, 572 deletions(-) delete mode 100644 erpnext/manufacturing/report/bom_stock_calculated/__init__.py delete mode 100644 erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js delete mode 100644 erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.json delete mode 100644 erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py delete mode 100644 erpnext/manufacturing/report/bom_stock_report/__init__.py delete mode 100644 erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html delete mode 100644 erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js delete mode 100644 erpnext/manufacturing/report/bom_stock_report/bom_stock_report.json delete mode 100644 erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py delete mode 100644 erpnext/manufacturing/report/bom_stock_report/test_bom_stock_report.py diff --git a/erpnext/manufacturing/report/bom_stock_calculated/__init__.py b/erpnext/manufacturing/report/bom_stock_calculated/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js deleted file mode 100644 index 76a95127853..00000000000 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2016, Epoch Consulting and contributors -// For license information, please see license.txt - -frappe.query_reports["BOM Stock Calculated"] = { - filters: [ - { - fieldname: "bom", - label: __("BOM"), - fieldtype: "Link", - options: "BOM", - reqd: 1, - }, - { - fieldname: "warehouse", - label: __("Warehouse"), - fieldtype: "Link", - options: "Warehouse", - }, - { - fieldname: "qty_to_make", - label: __("Quantity to Make"), - fieldtype: "Float", - default: "1.0", - reqd: 1, - }, - { - fieldname: "show_exploded_view", - label: __("Show exploded view"), - fieldtype: "Check", - default: false, - }, - ], -}; diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.json b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.json deleted file mode 100644 index 73421cebf0e..00000000000 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "add_total_row": 0, - "creation": "2018-05-17 12:40:31.355049", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "letter_head": "", - "modified": "2018-06-18 13:33:18.103220", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "BOM Stock Calculated", - "owner": "Administrator", - "ref_doctype": "BOM", - "report_name": "BOM Stock Calculated", - "report_type": "Script Report", - "roles": [ - { - "role": "Manufacturing Manager" - }, - { - "role": "Manufacturing User" - } - ] -} diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py deleted file mode 100644 index 4b5df4df4b2..00000000000 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py +++ /dev/null @@ -1,199 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe import _ -from frappe.query_builder.functions import IfNull, Sum -from frappe.utils.data import comma_and -from pypika.terms import ExistsCriterion - - -def execute(filters=None): - columns = get_columns() - data = [] - - bom_data = get_bom_data(filters) - qty_to_make = filters.get("qty_to_make") - manufacture_details = get_manufacturer_records() - - for row in bom_data: - required_qty = qty_to_make * row.qty_per_unit - last_purchase_rate = frappe.db.get_value("Item", row.item_code, "last_purchase_rate") - - data.append(get_report_data(last_purchase_rate, required_qty, row, manufacture_details)) - - return columns, data - - -def get_report_data(last_purchase_rate, required_qty, row, manufacture_details): - qty_per_unit = row.qty_per_unit if row.qty_per_unit > 0 else 0 - difference_qty = row.actual_qty - required_qty - return [ - row.item_code, - row.description, - row.from_bom_no, - comma_and(manufacture_details.get(row.item_code, {}).get("manufacturer", []), add_quotes=False), - comma_and(manufacture_details.get(row.item_code, {}).get("manufacturer_part", []), add_quotes=False), - qty_per_unit, - row.actual_qty, - required_qty, - difference_qty, - last_purchase_rate, - ] - - -def get_columns(): - return [ - { - "fieldname": "item", - "label": _("Item"), - "fieldtype": "Link", - "options": "Item", - "width": 120, - }, - { - "fieldname": "description", - "label": _("Description"), - "fieldtype": "Data", - "width": 150, - }, - { - "fieldname": "from_bom_no", - "label": _("From BOM No"), - "fieldtype": "Link", - "options": "BOM", - "width": 150, - }, - { - "fieldname": "manufacturer", - "label": _("Manufacturer"), - "fieldtype": "Data", - "width": 120, - }, - { - "fieldname": "manufacturer_part_number", - "label": _("Manufacturer Part Number"), - "fieldtype": "Data", - "width": 150, - }, - { - "fieldname": "qty_per_unit", - "label": _("Qty Per Unit"), - "fieldtype": "Float", - "width": 110, - }, - { - "fieldname": "available_qty", - "label": _("Available Qty"), - "fieldtype": "Float", - "width": 120, - }, - { - "fieldname": "required_qty", - "label": _("Required Qty"), - "fieldtype": "Float", - "width": 120, - }, - { - "fieldname": "difference_qty", - "label": _("Difference Qty"), - "fieldtype": "Float", - "width": 130, - }, - { - "fieldname": "last_purchase_rate", - "label": _("Last Purchase Rate"), - "fieldtype": "Float", - "width": 160, - }, - ] - - -def get_bom_data(filters): - bom_item_table = "BOM Explosion Item" if filters.get("show_exploded_view") else "BOM Item" - - bom_item = frappe.qb.DocType(bom_item_table) - bin = frappe.qb.DocType("Bin") - - query = ( - frappe.qb.from_(bom_item) - .left_join(bin) - .on(bom_item.item_code == bin.item_code) - .select( - bom_item.item_code, - bom_item.description, - bom_item.parent.as_("from_bom_no"), - bom_item.qty_consumed_per_unit.as_("qty_per_unit"), - IfNull(Sum(bin.actual_qty), 0).as_("actual_qty"), - ) - .where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM")) - .groupby(bom_item.item_code) - .orderby(bom_item.idx) - ) - - if filters.get("warehouse"): - warehouse_details = frappe.db.get_value( - "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1 - ) - - if warehouse_details: - wh = frappe.qb.DocType("Warehouse") - query = query.where( - ExistsCriterion( - frappe.qb.from_(wh) - .select(wh.name) - .where( - (wh.lft >= warehouse_details.lft) - & (wh.rgt <= warehouse_details.rgt) - & (bin.warehouse == wh.name) - ) - ) - ) - else: - query = query.where(bin.warehouse == filters.get("warehouse")) - - if bom_item_table == "BOM Item": - query = query.select(bom_item.bom_no, bom_item.is_phantom_item) - - data = query.run(as_dict=True) - return explode_phantom_boms(data, filters) if bom_item_table == "BOM Item" else data - - -def explode_phantom_boms(data, filters): - original_bom = filters.get("bom") - replacements = [] - - for idx, item in enumerate(data): - if not item.is_phantom_item: - continue - - filters["bom"] = item.bom_no - children = get_bom_data(filters) - filters["bom"] = original_bom - - for child in children: - child.qty_per_unit = (child.qty_per_unit or 0) * (item.qty_per_unit or 0) - - replacements.append((idx, children)) - - for idx, children in reversed(replacements): - data.pop(idx) - data[idx:idx] = children - - filters["bom"] = original_bom - return data - - -def get_manufacturer_records(): - details = frappe.get_all( - "Item Manufacturer", fields=["manufacturer", "manufacturer_part_no", "item_code"] - ) - - manufacture_details = frappe._dict() - for detail in details: - dic = manufacture_details.setdefault(detail.get("item_code"), {}) - dic.setdefault("manufacturer", []).append(detail.get("manufacturer")) - dic.setdefault("manufacturer_part", []).append(detail.get("manufacturer_part_no")) - - return manufacture_details diff --git a/erpnext/manufacturing/report/bom_stock_report/__init__.py b/erpnext/manufacturing/report/bom_stock_report/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html deleted file mode 100644 index 2ae8848cc03..00000000000 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html +++ /dev/null @@ -1,27 +0,0 @@ -

{%= __("BOM Stock Report") %}

-
{%= filters.bom %}
-
{%= filters.warehouse %}
-
- - - - - - - - - - - - - {% for(var i=0, l=data.length; i - - - - - - - {% } %} - -
{%= __("Item") %}{%= __("Description") %}{%= __("Required Qty") %}{%= __("In Stock Qty") %}{%= __("Enough Parts to Build") %}
{%= data[i][ __("Item")] %}{%= data[i][ __("Description")] %} {%= data[i][ __("Required Qty")] %} {%= data[i][ __("In Stock Qty")] %} {%= data[i][ __("Enough Parts to Build")] %}
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js deleted file mode 100644 index 91d73d0101c..00000000000 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js +++ /dev/null @@ -1,41 +0,0 @@ -frappe.query_reports["BOM Stock Report"] = { - filters: [ - { - fieldname: "bom", - label: __("BOM"), - fieldtype: "Link", - options: "BOM", - reqd: 1, - }, - { - fieldname: "warehouse", - label: __("Warehouse"), - fieldtype: "Link", - options: "Warehouse", - reqd: 1, - }, - { - fieldname: "show_exploded_view", - label: __("Show exploded view"), - fieldtype: "Check", - }, - { - fieldname: "qty_to_produce", - label: __("Quantity to Produce"), - fieldtype: "Int", - default: "1", - }, - ], - formatter: function (value, row, column, data, default_formatter) { - value = default_formatter(value, row, column, data); - - if (column.id == "item") { - if (data["in_stock_qty"] >= data["required_qty"]) { - value = `${data["item"]}`; - } else { - value = `${data["item"]}`; - } - } - return value; - }, -}; diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.json b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.json deleted file mode 100644 index c563b87686d..00000000000 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2017-01-10 14:00:50.387244", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "letter_head": "", - "modified": "2017-06-23 04:46:43.209008", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "BOM Stock Report", - "owner": "Administrator", - "query": "SELECT \n\tbom_item.item_code as \"Item:Link/Item:200\",\n\tbom_item.description as \"Description:Data:300\",\n\tbom_item.qty as \"Required Qty:Float:100\",\n\tledger.actual_qty as \"In Stock Qty:Float:100\",\n\tFLOOR(ledger.actual_qty /bom_item.qty) as \"Enough Parts to Build:Int:100\"\nFROM\n\t`tabBOM Item` AS bom_item \n\tLEFT JOIN `tabBin` AS ledger\t\n\t\tON bom_item.item_code = ledger.item_code \n\t\tAND ledger.warehouse = %(warehouse)s\nWHERE\n\tbom_item.parent=%(bom)s\n\nGROUP BY bom_item.item_code", - "ref_doctype": "BOM", - "report_name": "BOM Stock Report", - "report_type": "Script Report", - "roles": [ - { - "role": "Manufacturing Manager" - }, - { - "role": "Manufacturing User" - } - ] -} \ No newline at end of file diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py deleted file mode 100644 index eeda32c64c7..00000000000 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import frappe -from frappe import _ -from frappe.query_builder.functions import Floor, Sum -from frappe.utils import cint - - -def execute(filters=None): - if not filters: - filters = {} - - columns = get_columns() - data = get_bom_stock(filters) - - return columns, data - - -def get_columns(): - return [ - _("Item") + ":Link/Item:150", - _("Item Name") + "::240", - _("Description") + "::300", - _("From BOM No") + "::200", - _("BOM Qty") + ":Float:160", - _("BOM UOM") + "::160", - _("Required Qty") + ":Float:120", - _("In Stock Qty") + ":Float:120", - _("Enough Parts to Build") + ":Float:200", - ] - - -def get_bom_stock(filters): - qty_to_produce = filters.get("qty_to_produce") - if cint(qty_to_produce) <= 0: - frappe.throw(_("Quantity to Produce should be greater than zero.")) - - bom_item_table = "BOM Explosion Item" if filters.get("show_exploded_view") else "BOM Item" - - warehouse = filters.get("warehouse") - warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1) - - BOM = frappe.qb.DocType("BOM") - BOM_ITEM = frappe.qb.DocType(bom_item_table) - BIN = frappe.qb.DocType("Bin") - WH = frappe.qb.DocType("Warehouse") - - if warehouse_details: - bin_subquery = ( - frappe.qb.from_(BIN) - .join(WH) - .on(BIN.warehouse == WH.name) - .select(BIN.item_code, Sum(BIN.actual_qty).as_("actual_qty")) - .where((WH.lft >= warehouse_details.lft) & (WH.rgt <= warehouse_details.rgt)) - .groupby(BIN.item_code) - ) - else: - bin_subquery = ( - frappe.qb.from_(BIN) - .select(BIN.item_code, Sum(BIN.actual_qty).as_("actual_qty")) - .where(BIN.warehouse == warehouse) - .groupby(BIN.item_code) - ) - - QUERY = ( - frappe.qb.from_(BOM) - .join(BOM_ITEM) - .on(BOM.name == BOM_ITEM.parent) - .left_join(bin_subquery) - .on(BOM_ITEM.item_code == bin_subquery.item_code) - .select( - BOM_ITEM.item_code, - BOM_ITEM.item_name, - BOM_ITEM.description, - BOM.name, - Sum(BOM_ITEM.stock_qty), - BOM_ITEM.stock_uom, - (Sum(BOM_ITEM.stock_qty) * qty_to_produce) / BOM.quantity, - bin_subquery.actual_qty, - Floor(bin_subquery.actual_qty / ((Sum(BOM_ITEM.stock_qty) * qty_to_produce) / BOM.quantity)), - ) - .where((BOM_ITEM.parent == filters.get("bom")) & (BOM_ITEM.parenttype == "BOM")) - .groupby(BOM_ITEM.item_code) - .orderby(BOM_ITEM.idx) - ) - - if bom_item_table == "BOM Item": - QUERY = QUERY.select(BOM_ITEM.bom_no, BOM_ITEM.is_phantom_item) - - data = QUERY.run(as_list=True) - return explode_phantom_boms(data, filters) if bom_item_table == "BOM Item" else data - - -def explode_phantom_boms(data, filters): - expanded = [] - for row in data: - if row[-1]: # last element is `is_phantom_item` - phantom_filters = filters.copy() - phantom_filters["qty_to_produce"] = row[-5] - phantom_filters["bom"] = row[-2] - expanded.extend(get_bom_stock(phantom_filters)) - else: - expanded.append(row) - - return expanded diff --git a/erpnext/manufacturing/report/bom_stock_report/test_bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/test_bom_stock_report.py deleted file mode 100644 index 43706fcb4de..00000000000 --- a/erpnext/manufacturing/report/bom_stock_report/test_bom_stock_report.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe.exceptions import ValidationError -from frappe.utils import floor - -from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom -from erpnext.manufacturing.report.bom_stock_report.bom_stock_report import ( - get_bom_stock as bom_stock_report, -) -from erpnext.stock.doctype.item.test_item import make_item -from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry -from erpnext.tests.utils import ERPNextTestSuite - - -class TestBomStockReport(ERPNextTestSuite): - def setUp(self): - self.warehouse = "_Test Warehouse - _TC" - self.fg_item, self.rm_items = create_items() - make_stock_entry(target=self.warehouse, item_code=self.rm_items[0], qty=20, basic_rate=100) - make_stock_entry(target=self.warehouse, item_code=self.rm_items[1], qty=40, basic_rate=200) - self.bom = make_bom(item=self.fg_item, quantity=1, raw_materials=self.rm_items, rm_qty=10) - - def test_bom_stock_report(self): - # Test 1: When `qty_to_produce` is 0. - filters = frappe._dict( - { - "bom": self.bom.name, - "warehouse": "Stores - _TC", - "qty_to_produce": 0, - } - ) - self.assertRaises(ValidationError, bom_stock_report, filters) - - # Test 2: When stock is not available. - data = bom_stock_report( - frappe._dict( - { - "bom": self.bom.name, - "warehouse": "Stores - _TC", - "qty_to_produce": 1, - } - ) - ) - expected_data = get_expected_data(self.bom, "Stores - _TC", 1) - self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) - - # Test 3: When stock is available. - data = bom_stock_report( - frappe._dict( - { - "bom": self.bom.name, - "warehouse": self.warehouse, - "qty_to_produce": 1, - } - ) - ) - expected_data = get_expected_data(self.bom, self.warehouse, 1) - self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) - - -def create_items(): - fg_item = make_item(properties={"is_stock_item": 1}).name - rm_item1 = make_item( - properties={ - "is_stock_item": 1, - "standard_rate": 100, - "opening_stock": 100, - "last_purchase_rate": 100, - } - ).name - rm_item2 = make_item( - properties={ - "is_stock_item": 1, - "standard_rate": 200, - "opening_stock": 200, - "last_purchase_rate": 200, - } - ).name - - return fg_item, [rm_item1, rm_item2] - - -def get_expected_data(bom, warehouse, qty_to_produce, show_exploded_view=False): - expected_data = [] - - for item in bom.get("exploded_items") if show_exploded_view else bom.get("items"): - in_stock_qty = frappe.get_cached_value( - "Bin", {"item_code": item.item_code, "warehouse": warehouse}, "actual_qty" - ) - - expected_data.append( - [ - item.item_code, - item.item_name, - item.description, - bom.name, - item.stock_qty, - item.stock_uom, - item.stock_qty * qty_to_produce / bom.quantity, - in_stock_qty, - floor(in_stock_qty / (item.stock_qty * qty_to_produce / bom.quantity)) - if in_stock_qty - else None, - item.bom_no, - item.is_phantom_item, - ] - ) - - return expected_data From 5039f896bf93a7c618cb3edbcff3ad9bad452f84 Mon Sep 17 00:00:00 2001 From: nishkagosalia Date: Thu, 26 Mar 2026 15:40:28 +0530 Subject: [PATCH 036/168] fix: test case (cherry picked from commit 3a78af7f422f81fd64119ab73a59feb30e174e53) --- .../report/bom_stock_analysis/bom_stock_analysis.js | 6 +++--- .../report/bom_stock_analysis/bom_stock_analysis.py | 5 +++-- erpnext/manufacturing/report/test_reports.py | 3 +-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js index 7c6ccfdf743..7629c102d7c 100644 --- a/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js +++ b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js @@ -45,9 +45,9 @@ frappe.query_reports["BOM Stock Analysis"] = { } if (data && data.bold) { - if (column.fieldname === "description" || column.fieldname === "item_name") { - const qty_to_make = frappe.query_report.get_filter_value("qty_to_make"); - const producible = parseFloat(value) || 0; + if (column.fieldname === "description") { + const qty_to_make = Number(frappe.query_report.get_filter_value("qty_to_make")) || 0; + const producible = Number(String(data.description ?? "").replace(/,/g, "")) || 0; const colour = qty_to_make && producible < qty_to_make ? "red" : "green"; return `${value}`; } diff --git a/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py index 78aa75aa7fa..59578127f9f 100644 --- a/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py +++ b/erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py @@ -10,6 +10,7 @@ from pypika.terms import ExistsCriterion def execute(filters=None): + filters = filters or {} if filters.get("qty_to_make"): columns = get_columns_with_qty_to_make() data = get_data_with_qty_to_make(filters) @@ -35,7 +36,7 @@ def get_data_with_qty_to_make(filters): bom_data = get_bom_data(filters) manufacture_details = get_manufacturer_records() purchase_rates = batch_fetch_purchase_rates(bom_data) - qty_to_make = filters.get("qty_to_make") + qty_to_make = flt(filters.get("qty_to_make")) data = [] for row in bom_data: @@ -203,7 +204,7 @@ def get_bom_data(filters): bom_item.item_code, bom_item.description, bom_item.parent.as_("from_bom_no"), - bom_item.qty_consumed_per_unit.as_("qty_per_unit"), + Sum(bom_item.qty_consumed_per_unit).as_("qty_per_unit"), IfNull(Sum(bin.actual_qty), 0).as_("actual_qty"), ) .where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM")) diff --git a/erpnext/manufacturing/report/test_reports.py b/erpnext/manufacturing/report/test_reports.py index adeddd996a5..505a82c3501 100644 --- a/erpnext/manufacturing/report/test_reports.py +++ b/erpnext/manufacturing/report/test_reports.py @@ -21,8 +21,7 @@ class TestManufacturingReports(ERPNextTestSuite): self.REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [ ("BOM Explorer", {"bom": self.last_bom}), ("BOM Operations Time", {}), - ("BOM Stock Calculated", {"bom": self.last_bom, "qty_to_make": 2}), - ("BOM Stock Report", {"bom": self.last_bom, "qty_to_produce": 2}), + ("BOM Stock Analysis", {"bom": self.last_bom, "_optional": ["warehouse"]}), ("Cost of Poor Quality Report", {"item": "_Test Item", "serial_no": "00"}), ("Downtime Analysis", {}), ( From dd0613a4a8be5b54b873e976a3eec79b8d947196 Mon Sep 17 00:00:00 2001 From: kavin-114 Date: Fri, 6 Mar 2026 16:07:34 +0530 Subject: [PATCH 037/168] fix(stock): handle legacy single sle recon entries (cherry picked from commit 7e6bbcc3fb3accca18d21600fe3d8608267715df) --- .../stock/report/stock_ageing/stock_ageing.py | 71 +++++++++++++++++-- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index c6990c9492d..2ea52e91a8d 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -7,6 +7,7 @@ from operator import itemgetter import frappe from frappe import _ +from frappe.query_builder.functions import Count from frappe.utils import cint, date_diff, flt, get_datetime from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -240,9 +241,9 @@ class FIFOSlots: Returns dict of the foll.g structure: Key = Item A / (Item A, Warehouse A) Key: { - 'details' -> Dict: ** item details **, - 'fifo_queue' -> List: ** list of lists containing entries/slots for existing stock, - consumed/updated and maintained via FIFO. ** + 'details' -> Dict: ** item details **, + 'fifo_queue' -> List: ** list of lists containing entries/slots for existing stock, + consumed/updated and maintained via FIFO. ** } """ from erpnext.stock.serial_batch_bundle import get_serial_nos_from_bundle @@ -253,16 +254,33 @@ class FIFOSlots: if stock_ledger_entries is None: bundle_wise_serial_nos = self.__get_bundle_wise_serial_nos() + # prepare single sle voucher detail lookup + self.prepare_stock_reco_voucher_wise_count() + with frappe.db.unbuffered_cursor(): if stock_ledger_entries is None: stock_ledger_entries = self.__get_stock_ledger_entries() for d in stock_ledger_entries: key, fifo_queue, transferred_item_key = self.__init_key_stores(d) + prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0) - if d.voucher_type == "Stock Reconciliation": + if d.voucher_type == "Stock Reconciliation" and ( + not d.batch_no or d.serial_no or d.serial_and_batch_bundle + ): + if d.voucher_detail_no in self.stock_reco_voucher_wise_count: + # for legacy recon with single sle has qty_after_transaction and stock_value_difference without outward entry + # for exisitng handle emptying the existing queue and details. + d.stock_value_difference = flt(d.qty_after_transaction * d.valuation_rate) + d.actual_qty = d.qty_after_transaction + self.item_details[key]["qty_after_transaction"] = 0 + self.item_details[key]["total_qty"] = 0 + fifo_queue.clear() + else: + d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty) + + elif d.voucher_type == "Stock Reconciliation": # get difference in qty shift as actual qty - prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0) d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty) serial_nos = get_serial_nos(d.serial_no) if d.serial_no else [] @@ -280,6 +298,14 @@ class FIFOSlots: self.__update_balances(d, key) + # handle serial nos misconsumption + if d.has_serial_no: + qty_after = cint(self.item_details[key]["qty_after_transaction"]) + if qty_after <= 0: + fifo_queue.clear() + elif len(fifo_queue) > qty_after: + fifo_queue[:] = fifo_queue[:qty_after] + # Note that stock_ledger_entries is an iterator, you can not reuse it like a list del stock_ledger_entries @@ -406,7 +432,6 @@ class FIFOSlots: def __update_balances(self, row: dict, key: tuple | str): self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction - if "total_qty" not in self.item_details[key]: self.item_details[key]["total_qty"] = row.actual_qty else: @@ -462,6 +487,7 @@ class FIFOSlots: sle.posting_date, sle.voucher_type, sle.voucher_no, + sle.voucher_detail_no, sle.serial_no, sle.batch_no, sle.qty_after_transaction, @@ -558,3 +584,36 @@ class FIFOSlots: warehouse_results = [x[0] for x in warehouse_results] return sle_query.where(sle.warehouse.isin(warehouse_results)) + + def prepare_stock_reco_voucher_wise_count(self): + self.stock_reco_voucher_wise_count = frappe._dict() + + doctype = frappe.qb.DocType("Stock Ledger Entry") + item = frappe.qb.DocType("Item") + + query = ( + frappe.qb.from_(doctype) + .inner_join(item) + .on(doctype.item_code == item.name) + .select(doctype.voucher_detail_no, Count(doctype.name).as_("count")) + .where( + (doctype.voucher_type == "Stock Reconciliation") + & (doctype.docstatus < 2) + & (doctype.is_cancelled == 0) + ) + .groupby(doctype.voucher_detail_no) + ) + + data = query.run(as_dict=True) + if not data: + return + + for row in data: + if row.count != 1: + continue + + sr_item = frappe.db.get_value( + "Stock Reconciliation Item", row.voucher_detail_no, ["current_qty", "qty"], as_dict=True + ) + if sr_item.qty and sr_item.current_qty: + self.stock_reco_voucher_wise_count[row.voucher_detail_no] = sr_item.current_qty From 3a8e1e3faa57c016decd7a1e4c99d7b7cf4938e7 Mon Sep 17 00:00:00 2001 From: nishkagosalia Date: Fri, 27 Mar 2026 12:03:20 +0530 Subject: [PATCH 038/168] fix: test file deletion --- .../test_bom_stock_calculated.py | 119 ------------------ 1 file changed, 119 deletions(-) delete mode 100644 erpnext/manufacturing/report/bom_stock_calculated/test_bom_stock_calculated.py diff --git a/erpnext/manufacturing/report/bom_stock_calculated/test_bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/test_bom_stock_calculated.py deleted file mode 100644 index e0105b114c5..00000000000 --- a/erpnext/manufacturing/report/bom_stock_calculated/test_bom_stock_calculated.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import frappe - -from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom -from erpnext.manufacturing.report.bom_stock_calculated.bom_stock_calculated import ( - execute as bom_stock_calculated_report, -) -from erpnext.stock.doctype.item.test_item import make_item -from erpnext.tests.utils import ERPNextTestSuite - - -class TestBOMStockCalculated(ERPNextTestSuite): - def setUp(self): - self.fg_item, self.rm_items = create_items() - self.boms = create_boms(self.fg_item, self.rm_items) - - def test_bom_stock_calculated(self): - qty_to_make = 10 - - # Case 1: When Item(s) Qty and Stock Qty are equal. - data = bom_stock_calculated_report( - filters={ - "qty_to_make": qty_to_make, - "bom": self.boms[0].name, - } - )[1] - expected_data = get_expected_data(self.boms[0], qty_to_make) - self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) - - # Case 2: When Item(s) Qty and Stock Qty are different and BOM Qty is 1. - data = bom_stock_calculated_report( - filters={ - "qty_to_make": qty_to_make, - "bom": self.boms[1].name, - } - )[1] - expected_data = get_expected_data(self.boms[1], qty_to_make) - self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) - - # Case 3: When Item(s) Qty and Stock Qty are different and BOM Qty is greater than 1. - data = bom_stock_calculated_report( - filters={ - "qty_to_make": qty_to_make, - "bom": self.boms[2].name, - } - )[1] - expected_data = get_expected_data(self.boms[2], qty_to_make) - self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) - - -def create_items(): - fg_item = make_item(properties={"is_stock_item": 1}).name - rm_item1 = make_item( - properties={ - "is_stock_item": 1, - "standard_rate": 100, - "opening_stock": 100, - "last_purchase_rate": 100, - "item_defaults": [{"company": "_Test Company", "default_warehouse": "Stores - _TC"}], - } - ).name - rm_item2 = make_item( - properties={ - "is_stock_item": 1, - "standard_rate": 200, - "opening_stock": 200, - "last_purchase_rate": 200, - "item_defaults": [{"company": "_Test Company", "default_warehouse": "Stores - _TC"}], - } - ).name - - return fg_item, [rm_item1, rm_item2] - - -def create_boms(fg_item, rm_items): - def update_bom_items(bom, uom, conversion_factor): - for item in bom.items: - item.uom = uom - item.conversion_factor = conversion_factor - - return bom - - bom1 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10) - - bom2 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10, do_not_submit=True) - bom2 = update_bom_items(bom2, "Box", 10) - bom2.save() - bom2.submit() - - bom3 = make_bom(item=fg_item, quantity=2, raw_materials=rm_items, rm_qty=10, do_not_submit=True) - bom3 = update_bom_items(bom3, "Box", 10) - bom3.save() - bom3.submit() - - return [bom1, bom2, bom3] - - -def get_expected_data(bom, qty_to_make): - expected_data = [] - - for idx in range(len(bom.items)): - expected_data.append( - [ - bom.items[idx].item_code, - bom.items[idx].item_code, - bom.name, - "", - "", - float(bom.items[idx].stock_qty / bom.quantity), - float(100 * (idx + 1)), - float(qty_to_make * (bom.items[idx].stock_qty / bom.quantity)), - float((100 * (idx + 1)) - (qty_to_make * (bom.items[idx].stock_qty / bom.quantity))), - float(100 * (idx + 1)), - ] - ) - - return expected_data From 5bbecbf7c4194969731de58cef363cd140bfb260 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 27 Jan 2026 23:35:35 +0530 Subject: [PATCH 039/168] refactor: reposting for better peformance (cherry picked from commit 20787ef5da3a71e3b4a9970470ef035d7c225786) --- .../repost_item_valuation.py | 2 +- erpnext/stock/stock_ledger.py | 240 ++++++++++-------- 2 files changed, 142 insertions(+), 100 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index f5b4ef3e8f5..c68d3ed329a 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -435,7 +435,7 @@ def repost_sl_entries(doc): ) else: repost_future_sle( - args=[ + item_wh_to_repost=[ frappe._dict( { "item_code": doc.item_code, diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 1e6cec59a5c..6810f45827c 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -16,6 +16,7 @@ from frappe.utils import ( cstr, flt, format_date, + get_datetime, get_link_to_form, getdate, now, @@ -242,15 +243,15 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): def repost_future_sle( - args=None, + item_wh_to_repost=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False, doc=None, ): - if not args: - args = [] # set args to empty list if None to avoid enumerate error + if not item_wh_to_repost: + item_wh_to_repost = [] # set args to empty list if None to avoid enumerate error reposting_data = {} if doc and doc.reposting_data_file: @@ -260,53 +261,34 @@ def repost_future_sle( voucher_type=voucher_type, voucher_no=voucher_no, doc=doc, reposting_data=reposting_data ) if items_to_be_repost: - args = items_to_be_repost - - distinct_item_warehouses = get_distinct_item_warehouse(args, doc, reposting_data=reposting_data) - affected_transactions = get_affected_transactions(doc, reposting_data=reposting_data) + item_wh_to_repost = items_to_be_repost + distinct_item_wh_sles = frappe._dict() + prev_sle_dict = frappe._dict() + vouchers_to_repost = set() + item_wh_idx = 0 i = get_current_index(doc) or 0 - while i < len(args): - validate_item_warehouse(args[i]) - obj = update_entries_after( + while i < len(item_wh_to_repost): + update_entries_after( { - "item_code": args[i].get("item_code"), - "warehouse": args[i].get("warehouse"), - "posting_date": args[i].get("posting_date"), - "posting_time": args[i].get("posting_time"), - "creation": args[i].get("creation"), - "distinct_item_warehouses": distinct_item_warehouses, - "items_to_be_repost": args, - "current_index": i, + "item_code": item_wh_to_repost[i].get("item_code"), + "warehouse": item_wh_to_repost[i].get("warehouse"), + "posting_date": item_wh_to_repost[i].get("posting_date"), + "posting_time": item_wh_to_repost[i].get("posting_time"), + "creation": item_wh_to_repost[i].get("creation"), + "distinct_item_wh_sles": distinct_item_wh_sles, + "prev_sle_dict": prev_sle_dict, + "vouchers_to_repost": vouchers_to_repost, + "item_wh_idx": item_wh_idx, + "current_idx": i, }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher, ) - affected_transactions.update(obj.affected_transactions) - key = (args[i].get("item_code"), args[i].get("warehouse")) - if distinct_item_warehouses.get(key): - distinct_item_warehouses[key].reposting_status = True - - if obj.new_items_found: - for _item_wh, data in distinct_item_warehouses.items(): - if ("args_idx" not in data and not data.reposting_status) or ( - data.sle_changed and data.reposting_status - ): - data.args_idx = len(args) - args.append(data.sle) - elif data.sle_changed and not data.reposting_status: - args[data.args_idx] = data.sle - - data.sle_changed = False i += 1 - if doc: - update_args_in_repost_item_valuation( - doc, i, args, distinct_item_warehouses, affected_transactions - ) - def get_reposting_data(file_path) -> dict: file_name = frappe.db.get_value( @@ -552,6 +534,11 @@ class update_entries_after: self.allow_zero_rate = allow_zero_rate self.via_landed_cost_voucher = via_landed_cost_voucher self.item_code = args.get("item_code") + self.distinct_item_wh_sles = args.get("distinct_item_wh_sles", frappe._dict()) + self.prev_sle_dict = args.get("prev_sle_dict", frappe._dict()) + self.vouchers_to_repost = args.get("vouchers_to_repost", set()) + self.item_wh_idx = args.get("item_wh_idx", 0) + self.current_idx = args.get("current_idx", 0) self.allow_negative_stock = allow_negative_stock or is_negative_stock_allowed( item_code=self.item_code @@ -566,7 +553,6 @@ class update_entries_after: self.valuation_method = get_valuation_method(self.item_code, self.company) self.new_items_found = False - self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict()) self.affected_transactions: set[tuple[str, str]] = set() self.reserved_stock = self.get_reserved_stock() @@ -622,6 +608,8 @@ class update_entries_after: self.data.setdefault(args.warehouse, frappe._dict()) warehouse_dict = self.data[args.warehouse] previous_sle = get_previous_sle_of_current_voucher(args) + self.prev_sle_dict[(args.get("item_code"), args.get("warehouse"))] = previous_sle + warehouse_dict.previous_sle = previous_sle for key in ("qty_after_transaction", "valuation_rate", "stock_value"): @@ -642,28 +630,78 @@ class update_entries_after: self.process_sle_against_current_timestamp() if not future_sle_exists(self.args): self.update_bin() + elif self.vouchers_to_repost: + self.repost_vouchers() else: - entries_to_fix = self.get_future_entries_to_fix() + kwargs = frappe._dict( + { + "item_code": self.item_code, + "warehouse": self.args.warehouse, + "posting_date": self.args.posting_date, + "posting_time": self.args.posting_time, + "creation": self.args.creation, + } + ) - i = 0 - while i < len(entries_to_fix): - sle = entries_to_fix[i] - i += 1 + self.prepare_sles_to_repost(kwargs) + self.repost_vouchers() + self.update_bin() - self.process_sle(sle) - self.update_bin_data(sle) + def repost_vouchers(self): + vouchers = self.vouchers_to_repost or self.get_vouchere_to_repost() + for row in vouchers: + self.process_sle(row) - if sle.dependant_sle_voucher_detail_no: - entries_to_fix = self.get_dependent_entries_to_fix(entries_to_fix, sle) - if sle.voucher_type == "Stock Entry" and is_repack_entry(sle.voucher_no): - # for repack entries, we need to repost both source and target warehouses - self.update_distinct_item_warehouses_for_repack(sle) + def get_vouchere_to_repost(self): + if not self.distinct_item_wh_sles: + return [] - if self.exceptions: - self.raise_exceptions() + sorted_rows = sorted( + (row for rows in self.distinct_item_wh_sles.values() for row in rows), + key=lambda d: (get_datetime(d.get("posting_datetime")), get_datetime(d.get("creation"))), + ) - def update_distinct_item_warehouses_for_repack(self, sle): - sles = ( + return sorted_rows + + def prepare_sles_to_repost(self, kwargs): + sles = self.get_future_entries_to_repost(kwargs) + for sle in sles: + item_wh_key = (sle.item_code, sle.warehouse) + if item_wh_key not in self.prev_sle_dict: + prev_sle = get_previous_sle_of_current_voucher(kwargs) + self.prev_sle_dict[item_wh_key] = prev_sle + + key = (sle.item_code, sle.warehouse, sle.voucher_detail_no) + if key not in self.distinct_item_wh_sles: + self.distinct_item_wh_sles.setdefault(key, []).append(sle) + + if sle.dependant_sle_voucher_detail_no: + self.prepare_dependent_sles_to_repost(sle) + + def prepare_dependent_sles_to_repost(self, sle): + if sle.voucher_type == "Stock Entry" and is_repack_entry(sle.voucher_no): + sles = self.get_sles_for_repack(sle) + for repack_sle in sles: + key = (repack_sle.item_code, repack_sle.warehouse, repack_sle.voucher_detail_no) + if key not in self.distinct_item_wh_sles: + self.distinct_item_wh_sles.setdefault(key, []).append(repack_sle) + + self.prepare_sles_to_repost(repack_sle) + + elif sle.dependant_sle_voucher_detail_no: + dependant_sle = get_sle_by_voucher_detail_no(sle.dependant_sle_voucher_detail_no) + + key = (dependant_sle.item_code, dependant_sle.warehouse, dependant_sle.voucher_detail_no) + if key not in self.distinct_item_wh_sles: + self.distinct_item_wh_sles.setdefault(key, []).append(dependant_sle) + + self.prepare_sles_to_repost(dependant_sle) + + def get_future_entries_to_repost(self, kwargs): + return get_stock_ledger_entries(kwargs, ">=", "asc", for_update=True, check_serial_no=False) + + def get_sles_for_repack(self, sle): + return ( frappe.get_all( "Stock Ledger Entry", filters={ @@ -671,16 +709,13 @@ class update_entries_after: "voucher_no": sle.voucher_no, "actual_qty": (">", 0), "is_cancelled": 0, - "voucher_detail_no": ("!=", sle.dependant_sle_voucher_detail_no), + "dependant_sle_voucher_detail_no": ("!=", sle.dependant_sle_voucher_detail_no), }, fields=["*"], ) or [] ) - for dependant_sle in sles: - self.update_distinct_item_warehouses(dependant_sle) - def has_stock_reco_with_serial_batch(self, sle): if ( sle.voucher_type == "Stock Reconciliation" @@ -691,33 +726,11 @@ class update_entries_after: return False def process_sle_against_current_timestamp(self): - sl_entries = self.get_sle_against_current_voucher() + sl_entries = get_sle_against_current_voucher(self.args) for sle in sl_entries: sle["timestamp"] = sle.posting_datetime self.process_sle(sle) - def get_sle_against_current_voucher(self): - self.args["posting_datetime"] = get_combine_datetime(self.args.posting_date, self.args.posting_time) - doctype = frappe.qb.DocType("Stock Ledger Entry") - - query = ( - frappe.qb.from_(doctype) - .select("*") - .where( - (doctype.item_code == self.args.item_code) - & (doctype.warehouse == self.args.warehouse) - & (doctype.is_cancelled == 0) - & (doctype.posting_datetime == self.args.posting_datetime) - ) - .orderby(doctype.creation, order=Order.asc) - .for_update() - ) - - if not self.args.get("cancelled"): - query = query.where(doctype.creation == self.args.creation) - - return query.run(as_dict=True) - def get_future_entries_to_fix(self): # includes current entry! args = self.data[self.args.warehouse].previous_sle or frappe._dict( @@ -797,7 +810,7 @@ class update_entries_after: return self.distinct_item_warehouses[key].dependent_voucher_detail_nos def validate_previous_sle_qty(self, sle): - previous_sle = self.data[sle.warehouse].previous_sle + previous_sle = self.prev_sle_dict.get((sle.item_code, sle.warehouse)) if previous_sle and previous_sle.get("qty_after_transaction") < 0 and sle.get("actual_qty") > 0: frappe.msgprint( _( @@ -816,7 +829,22 @@ class update_entries_after: def process_sle(self, sle): # previous sle data for this warehouse - self.wh_data = self.data[sle.warehouse] + key = (sle.item_code, sle.warehouse) + self.wh_data = self.prev_sle_dict.get(key) or frappe._dict( + { + "qty_after_transaction": 0.0, + "valuation_rate": 0.0, + "stock_value": 0.0, + "prev_stock_value": 0.0, + "stock_queue": [], + } + ) + + if self.wh_data.stock_queue and isinstance(self.wh_data.stock_queue, str): + self.wh_data.stock_queue = json.loads(self.wh_data.stock_queue) + + if not self.wh_data.prev_stock_value: + self.wh_data.prev_stock_value = self.wh_data.stock_value self.validate_previous_sle_qty(sle) self.affected_transactions.add((sle.voucher_type, sle.voucher_no)) @@ -946,6 +974,8 @@ class update_entries_after: sle.modified = now() frappe.get_doc(sle).db_update() + self.prev_sle_dict[key] = sle + if not self.args.get("sle_id") or ( sle.serial_and_batch_bundle and sle.auto_created_serial_and_batch_bundle ): @@ -1728,8 +1758,8 @@ class update_entries_after: def update_bin(self): # update bin for each warehouse - for warehouse, data in self.data.items(): - bin_name = get_or_make_bin(self.item_code, warehouse) + for (item_code, warehouse), data in self.prev_sle_dict.items(): + bin_name = get_or_make_bin(item_code, warehouse) updated_values = {"actual_qty": data.qty_after_transaction, "stock_value": data.stock_value} if data.valuation_rate is not None: @@ -1737,6 +1767,29 @@ class update_entries_after: frappe.db.set_value("Bin", bin_name, updated_values, update_modified=True) +def get_sle_against_current_voucher(kwargs): + kwargs["posting_datetime"] = get_combine_datetime(kwargs.posting_date, kwargs.posting_time) + doctype = frappe.qb.DocType("Stock Ledger Entry") + + query = ( + frappe.qb.from_(doctype) + .select("*") + .where( + (doctype.item_code == kwargs.item_code) + & (doctype.warehouse == kwargs.warehouse) + & (doctype.is_cancelled == 0) + & (doctype.posting_datetime == kwargs.posting_datetime) + ) + .orderby(doctype.creation, order=Order.asc) + .for_update() + ) + + if not kwargs.get("cancelled"): + query = query.where(doctype.creation == kwargs.creation) + + return query.run(as_dict=True) + + def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_voucher=False): """get stock ledger entries filtered by specific posting datetime conditions""" @@ -1893,18 +1946,7 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None): return frappe.db.get_value( "Stock Ledger Entry", {"voucher_detail_no": voucher_detail_no, "name": ["!=", excluded_sle], "is_cancelled": 0}, - [ - "item_code", - "warehouse", - "actual_qty", - "qty_after_transaction", - "posting_date", - "posting_time", - "voucher_detail_no", - "posting_datetime as timestamp", - "voucher_type", - "voucher_no", - ], + ["*"], as_dict=1, ) From f663f9b27ef9fd7e00c0e4f7f81c221f4715f487 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 28 Jan 2026 17:24:09 +0530 Subject: [PATCH 040/168] refactor: storing of current status of reposting (cherry picked from commit daa2420996f9fe020de3390eb08992386529f442) --- .../repost_item_valuation.js | 40 +- .../repost_item_valuation.json | 121 +++- .../repost_item_valuation.py | 18 +- .../stock_ledger_variance.py | 7 +- erpnext/stock/stock_ledger.py | 543 ++++++++---------- 5 files changed, 393 insertions(+), 336 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js index 0f64949f621..134903f2309 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js @@ -69,9 +69,15 @@ frappe.ui.form.on("Repost Item Valuation", { } if (frm.doc.status == "In Progress") { - frm.doc.current_index = data.current_index; - frm.doc.items_to_be_repost = data.items_to_be_repost; - frm.doc.total_reposting_count = data.total_reposting_count; + if (data.current_index) { + frm.doc.current_index = data.current_index; + frm.doc.items_to_be_repost = data.items_to_be_repost; + } + + if (data.vouchers_posted) { + frm.doc.total_vouchers = data.total_vouchers; + frm.doc.vouchers_posted = data.vouchers_posted; + } frm.dashboard.reset(); frm.trigger("show_reposting_progress"); @@ -108,15 +114,31 @@ frappe.ui.form.on("Repost Item Valuation", { show_reposting_progress: function (frm) { var bars = []; - + let title = ""; + let progress = 0.0; let total_count = frm.doc.items_to_be_repost ? JSON.parse(frm.doc.items_to_be_repost).length : 0; - if (frm.doc?.total_reposting_count) { - total_count = frm.doc.total_reposting_count; + if (total_count > 1) { + progress = flt((cint(frm.doc.current_index) / total_count) * 100, 2) || 0.5; + title = __("Reposting for Item-Wh Completed {0}%", [progress]); + + bars.push({ + title: title, + width: progress + "%", + progress_class: "progress-bar-success", + }); + + frm.dashboard.add_progress(__("Reposting Progress"), bars); } - let progress = flt((cint(frm.doc.current_index) / total_count) * 100, 2) || 0.5; - var title = __("Reposting Completed {0}%", [progress]); + if (!frm.doc.vouchers_posted) { + return; + } + + // Show voucher posting progress if vouchers are being reposted + bars = []; + progress = flt((cint(frm.doc.vouchers_posted) / cint(frm.doc.total_vouchers)) * 100, 2) || 0.5; + title = __("Reposting for Vouchers Completed {0}%", [progress]); bars.push({ title: title, @@ -124,7 +146,7 @@ frappe.ui.form.on("Repost Item Valuation", { progress_class: "progress-bar-success", }); - frm.dashboard.add_progress(__("Reposting Progress"), bars); + frm.dashboard.add_progress(__("Reposting Vouchers Progress"), bars); }, restart_reposting: function (frm) { diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index e1ae6d00cd9..3b4ae7220a5 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -27,14 +27,23 @@ "error_section", "error_log", "reposting_info_section", - "reposting_data_file", "items_to_be_repost", - "distinct_item_and_warehouse", "column_break_o1sj", "total_reposting_count", "current_index", "gl_reposting_index", - "affected_transactions" + "vouchers_based_on_item_and_warehouse_section", + "total_vouchers", + "column_break_yqwo", + "vouchers_posted", + "last_sle_posted_section", + "reposted_item_code", + "reposted_warehouse", + "reposting_data_file", + "column_break_miwc", + "sle_posting_date", + "sle_posting_time", + "reposted_sle_creation" ], "fields": [ { @@ -167,15 +176,6 @@ "print_hide": 1, "read_only": 1 }, - { - "fieldname": "distinct_item_and_warehouse", - "fieldtype": "Code", - "hidden": 1, - "label": "Distinct Item and Warehouse", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, { "fieldname": "current_index", "fieldtype": "Int", @@ -185,14 +185,6 @@ "print_hide": 1, "read_only": 1 }, - { - "fieldname": "affected_transactions", - "fieldtype": "Code", - "hidden": 1, - "label": "Affected Transactions", - "no_copy": 1, - "read_only": 1 - }, { "default": "0", "fieldname": "gl_reposting_index", @@ -205,7 +197,7 @@ { "fieldname": "reposting_info_section", "fieldtype": "Section Break", - "label": "Reposting Info" + "label": "Reposting Item and Warehouse" }, { "fieldname": "column_break_o1sj", @@ -214,14 +206,7 @@ { "fieldname": "total_reposting_count", "fieldtype": "Int", - "label": "Total Reposting Count", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "reposting_data_file", - "fieldtype": "Attach", - "label": "Reposting Data File", + "label": "No of Items to Repost", "no_copy": 1, "read_only": 1 }, @@ -247,13 +232,89 @@ "fieldname": "repost_only_accounting_ledgers", "fieldtype": "Check", "label": "Repost Only Accounting Ledgers" + }, + { + "fieldname": "vouchers_based_on_item_and_warehouse_section", + "fieldtype": "Section Break", + "label": "Reposting Vouchers" + }, + { + "fieldname": "total_vouchers", + "fieldtype": "Int", + "label": "Total Ledgers", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_yqwo", + "fieldtype": "Column Break" + }, + { + "fieldname": "vouchers_posted", + "fieldtype": "Int", + "label": "Ledgers Posted", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "last_sle_posted_section", + "fieldtype": "Section Break", + "label": "Last SLE Posted" + }, + { + "fieldname": "reposted_item_code", + "fieldtype": "Link", + "label": "Reposted Item Code", + "no_copy": 1, + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "reposted_warehouse", + "fieldtype": "Link", + "label": "Reposted Warehouse", + "no_copy": 1, + "options": "Warehouse", + "read_only": 1 + }, + { + "fieldname": "column_break_miwc", + "fieldtype": "Column Break" + }, + { + "fieldname": "reposted_sle_creation", + "fieldtype": "Datetime", + "label": "Reposted SLE Creation", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "sle_posting_date", + "fieldtype": "Date", + "label": "SLE Posting Date", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "sle_posting_time", + "fieldtype": "Time", + "label": "SLE Posting Time", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "reposting_data_file", + "fieldtype": "Attach", + "label": "Reposting Data File", + "no_copy": 1, + "read_only": 1 } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2026-02-25 14:22:21.681549", + "modified": "2026-03-26 13:52:51.895504", "modified_by": "Administrator", "module": "Stock", "name": "Repost Item Valuation", diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index c68d3ed329a..d7397682ee6 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -35,14 +35,12 @@ class RepostItemValuation(Document): if TYPE_CHECKING: from frappe.types import DF - affected_transactions: DF.Code | None allow_negative_stock: DF.Check allow_zero_rate: DF.Check amended_from: DF.Link | None based_on: DF.Literal["Transaction", "Item and Warehouse"] company: DF.Link | None current_index: DF.Int - distinct_item_and_warehouse: DF.Code | None error_log: DF.LongText | None gl_reposting_index: DF.Int item_code: DF.Link | None @@ -51,13 +49,20 @@ class RepostItemValuation(Document): posting_time: DF.Time | None recreate_stock_ledgers: DF.Check repost_only_accounting_ledgers: DF.Check + reposted_item_code: DF.Link | None + reposted_sle_creation: DF.Datetime | None + reposted_warehouse: DF.Link | None reposting_data_file: DF.Attach | None reposting_reference: DF.Data | None + sle_posting_date: DF.Date | None + sle_posting_time: DF.Time | None status: DF.Literal["Queued", "In Progress", "Completed", "Skipped", "Failed", "Cancelled"] total_reposting_count: DF.Int + total_vouchers: DF.Int via_landed_cost_voucher: DF.Check voucher_no: DF.DynamicLink | None voucher_type: DF.Link | None + vouchers_posted: DF.Int warehouse: DF.Link | None # end: auto-generated types @@ -261,6 +266,13 @@ class RepostItemValuation(Document): self.items_to_be_repost = None self.gl_reposting_index = 0 self.total_reposting_count = 0 + self.total_vouchers = 0 + self.vouchers_posted = 0 + self.reposted_item_code = None + self.reposted_warehouse = None + self.sle_posting_date = None + self.sle_posting_time = None + self.reposted_sle_creation = None self.clear_attachment() self.db_update() @@ -435,7 +447,7 @@ def repost_sl_entries(doc): ) else: repost_future_sle( - item_wh_to_repost=[ + items_to_be_repost=[ frappe._dict( { "item_code": doc.item_code, diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py index 97243d57001..e0d39c5dc7a 100644 --- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py @@ -248,12 +248,7 @@ def get_item_warehouse_combinations(filters: dict | None = None) -> dict: bin.warehouse, item.valuation_method, ) - .where( - (item.is_stock_item == 1) - & (item.has_serial_no == 0) - & (warehouse.is_group == 0) - & (warehouse.company == filters.company) - ) + .where((item.is_stock_item == 1) & (warehouse.is_group == 0) & (warehouse.company == filters.company)) ) if filters.item_code: diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 6810f45827c..21ccfb0f206 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -78,9 +78,6 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc future_sle_exists(args, sl_entries) for sle in sl_entries: - if sle.serial_no and not via_landed_cost_voucher: - validate_serial_no(sle) - if cancelled: sle["actual_qty"] = -flt(sle.get("actual_qty")) @@ -161,35 +158,6 @@ def get_args_for_future_sle(row): ) -def validate_serial_no(sle): - from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos - - for sn in get_serial_nos(sle.serial_no): - args = copy.deepcopy(sle) - args.serial_no = sn - args.warehouse = "" - - vouchers = [] - for row in get_stock_ledger_entries(args, ">"): - voucher_type = frappe.bold(row.voucher_type) - voucher_no = frappe.bold(get_link_to_form(row.voucher_type, row.voucher_no)) - vouchers.append(f"{voucher_type} {voucher_no}") - - if vouchers: - serial_no = frappe.bold(sn) - msg = ( - f"""The serial no {serial_no} has been used in the future transactions so you need to cancel them first. - The list of the transactions are as below.""" - + "

  • " - ) - - msg += "
  • ".join(vouchers) - msg += "
" - - title = "Cannot Submit" if not sle.get("is_cancelled") else "Cannot Cancel" - frappe.throw(_(msg), title=_(title), exc=SerialNoExistsInFutureTransaction) - - def validate_cancellation(kargs): if kargs[0].get("is_cancelled"): repost_entry = frappe.db.get_value( @@ -243,127 +211,77 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): def repost_future_sle( - item_wh_to_repost=None, + items_to_be_repost=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False, doc=None, ): - if not item_wh_to_repost: - item_wh_to_repost = [] # set args to empty list if None to avoid enumerate error - reposting_data = {} - if doc and doc.reposting_data_file: - reposting_data = get_reposting_data(doc.reposting_data_file) + if not items_to_be_repost: + items_to_be_repost = get_items_to_be_repost( + voucher_type=voucher_type, voucher_no=voucher_no, doc=doc, reposting_data=reposting_data + ) - items_to_be_repost = get_items_to_be_repost( - voucher_type=voucher_type, voucher_no=voucher_no, doc=doc, reposting_data=reposting_data - ) - if items_to_be_repost: - item_wh_to_repost = items_to_be_repost + repost_affected_transaction = get_affected_transactions(doc) or set() + if not items_to_be_repost: + return - distinct_item_wh_sles = frappe._dict() - prev_sle_dict = frappe._dict() - vouchers_to_repost = set() - item_wh_idx = 0 - i = get_current_index(doc) or 0 - - while i < len(item_wh_to_repost): - update_entries_after( + index = get_current_index(doc) or 0 + while index < len(items_to_be_repost): + obj = update_entries_after( { - "item_code": item_wh_to_repost[i].get("item_code"), - "warehouse": item_wh_to_repost[i].get("warehouse"), - "posting_date": item_wh_to_repost[i].get("posting_date"), - "posting_time": item_wh_to_repost[i].get("posting_time"), - "creation": item_wh_to_repost[i].get("creation"), - "distinct_item_wh_sles": distinct_item_wh_sles, - "prev_sle_dict": prev_sle_dict, - "vouchers_to_repost": vouchers_to_repost, - "item_wh_idx": item_wh_idx, - "current_idx": i, + "item_code": items_to_be_repost[index].get("item_code"), + "warehouse": items_to_be_repost[index].get("warehouse"), + "posting_date": items_to_be_repost[index].get("posting_date"), + "posting_time": items_to_be_repost[index].get("posting_time"), + "creation": items_to_be_repost[index].get("creation"), + "current_idx": index, + "items_to_be_repost": items_to_be_repost, + "repost_doc": doc, + "repost_affected_transaction": repost_affected_transaction, }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher, ) - i += 1 + index += 1 + + repost_affected_transaction.update(obj.repost_affected_transaction) + update_args_in_repost_item_valuation(doc, index, items_to_be_repost, repost_affected_transaction) -def get_reposting_data(file_path) -> dict: - file_name = frappe.db.get_value( - "File", +def update_args_in_repost_item_valuation( + doc, index, items_to_be_repost, repost_affected_transaction, only_affected_transaction=False +): + file_name = "" + has_file = False + if doc.reposting_data_file: + has_file = True + + if doc.reposting_data_file: + file_name = get_reposting_file_name(doc.doctype, doc.name) + # frappe.delete_doc("File", file_name, ignore_permissions=True, delete_permanently=True) + + doc.reposting_data_file = create_json_gz_file( { - "file_url": file_path, - "attached_to_field": "reposting_data_file", + "repost_affected_transaction": repost_affected_transaction, }, - "name", + doc, + file_name, ) - if not file_name: - return frappe._dict() - - attached_file = frappe.get_doc("File", file_name) - - content = attached_file.get_content() - if isinstance(content, str): - content = content.encode("utf-8") - - try: - data = gzip.decompress(content) - except Exception: - return frappe._dict() - - if data := json.loads(data.decode("utf-8")): - data = data - - return parse_json(data) - - -def validate_item_warehouse(args): - for field in ["item_code", "warehouse", "posting_date", "posting_time"]: - if args.get(field) in [None, ""]: - validation_msg = f"The field {frappe.unscrub(field)} is required for the reposting" - frappe.throw(_(validation_msg)) - - -def update_args_in_repost_item_valuation(doc, index, args, distinct_item_warehouses, affected_transactions): - if not doc.items_to_be_repost: - file_name = "" - if doc.reposting_data_file: - file_name = get_reposting_file_name(doc.doctype, doc.name) - # frappe.delete_doc("File", file_name, ignore_permissions=True, delete_permanently=True) - - doc.reposting_data_file = create_json_gz_file( - { - "items_to_be_repost": args, - "distinct_item_and_warehouse": {str(k): v for k, v in distinct_item_warehouses.items()}, - "affected_transactions": affected_transactions, - }, - doc, - file_name, - ) - + if not only_affected_transaction or not has_file: doc.db_set( { "current_index": index, - "total_reposting_count": len(args), + "items_to_be_repost": frappe.as_json(items_to_be_repost), + "total_reposting_count": len(items_to_be_repost), "reposting_data_file": doc.reposting_data_file, } ) - else: - doc.db_set( - { - "items_to_be_repost": json.dumps(args, default=str), - "distinct_item_and_warehouse": json.dumps( - {str(k): v for k, v in distinct_item_warehouses.items()}, default=str - ), - "current_index": index, - "affected_transactions": frappe.as_json(affected_transactions), - } - ) - if not frappe.in_test: frappe.db.commit() @@ -371,9 +289,8 @@ def update_args_in_repost_item_valuation(doc, index, args, distinct_item_warehou "item_reposting_progress", { "name": doc.name, - "items_to_be_repost": json.dumps(args, default=str), "current_index": index, - "total_reposting_count": len(args), + "total_reposting_count": len(items_to_be_repost), }, doctype=doc.doctype, docname=doc.name, @@ -430,23 +347,27 @@ def create_file(doc, compressed_content): return _file.file_url -def get_items_to_be_repost(voucher_type=None, voucher_no=None, doc=None, reposting_data=None): - if not reposting_data and doc and doc.reposting_data_file: - reposting_data = get_reposting_data(doc.reposting_data_file) +def validate_item_warehouse(args): + for field in ["item_code", "warehouse", "posting_date", "posting_time"]: + if args.get(field) in [None, ""]: + validation_msg = f"The field {frappe.unscrub(field)} is required for the reposting" + frappe.throw(_(validation_msg)) + +def get_items_to_be_repost(voucher_type=None, voucher_no=None, doc=None, reposting_data=None): if reposting_data and reposting_data.items_to_be_repost: return reposting_data.items_to_be_repost items_to_be_repost = [] if doc and doc.items_to_be_repost: - items_to_be_repost = json.loads(doc.items_to_be_repost) or [] + items_to_be_repost = json.loads(doc.items_to_be_repost) if not items_to_be_repost and voucher_type and voucher_no: items_to_be_repost = frappe.db.get_all( "Stock Ledger Entry", filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, - fields=["item_code", "warehouse", "posting_date", "posting_time", "creation"], + fields=["item_code", "warehouse", "posting_date", "posting_time", "creation", "posting_datetime"], order_by="creation asc", group_by="item_code, warehouse", ) @@ -454,51 +375,44 @@ def get_items_to_be_repost(voucher_type=None, voucher_no=None, doc=None, reposti return items_to_be_repost or [] -def get_distinct_item_warehouse(args=None, doc=None, reposting_data=None): - if not reposting_data and doc and doc.reposting_data_file: - reposting_data = get_reposting_data(doc.reposting_data_file) - - if reposting_data and reposting_data.distinct_item_and_warehouse: - return parse_distinct_items_and_warehouses(reposting_data.distinct_item_and_warehouse) - - distinct_item_warehouses = {} - - if doc and doc.distinct_item_and_warehouse: - distinct_item_warehouses = json.loads(doc.distinct_item_and_warehouse) - distinct_item_warehouses = { - frappe.safe_eval(k): frappe._dict(v) for k, v in distinct_item_warehouses.items() - } - else: - for i, d in enumerate(args): - distinct_item_warehouses.setdefault( - (d.item_code, d.warehouse), frappe._dict({"reposting_status": False, "sle": d, "args_idx": i}) - ) - - return distinct_item_warehouses - - -def parse_distinct_items_and_warehouses(distinct_items_and_warehouses): - new_dict = frappe._dict({}) - - # convert string keys to tuple - for k, v in distinct_items_and_warehouses.items(): - new_dict[frappe.safe_eval(k)] = frappe._dict(v) - - return new_dict - - def get_affected_transactions(doc, reposting_data=None) -> set[tuple[str, str]]: if not reposting_data and doc and doc.reposting_data_file: reposting_data = get_reposting_data(doc.reposting_data_file) - if reposting_data and reposting_data.affected_transactions: - return {tuple(transaction) for transaction in reposting_data.affected_transactions} + if reposting_data and reposting_data.repost_affected_transaction: + return {tuple(transaction) for transaction in reposting_data.repost_affected_transaction} - if not doc.affected_transactions: - return set() + return set() - transactions = frappe.parse_json(doc.affected_transactions) - return {tuple(transaction) for transaction in transactions} + +def get_reposting_data(file_path) -> dict: + file_name = frappe.db.get_value( + "File", + { + "file_url": file_path, + "attached_to_field": "reposting_data_file", + }, + "name", + ) + + if not file_name: + return frappe._dict() + + attached_file = frappe.get_doc("File", file_name) + + content = attached_file.get_content() + if isinstance(content, str): + content = content.encode("utf-8") + + try: + data = gzip.decompress(content) + except Exception: + return frappe._dict() + + if data := json.loads(data.decode("utf-8")): + data = data + + return parse_json(data) def get_current_index(doc=None): @@ -534,11 +448,11 @@ class update_entries_after: self.allow_zero_rate = allow_zero_rate self.via_landed_cost_voucher = via_landed_cost_voucher self.item_code = args.get("item_code") - self.distinct_item_wh_sles = args.get("distinct_item_wh_sles", frappe._dict()) - self.prev_sle_dict = args.get("prev_sle_dict", frappe._dict()) - self.vouchers_to_repost = args.get("vouchers_to_repost", set()) - self.item_wh_idx = args.get("item_wh_idx", 0) + self.prev_sle_dict = frappe._dict({}) + self.stock_ledgers_to_repost = [] self.current_idx = args.get("current_idx", 0) + self.repost_doc = args.get("repost_doc") or None + self.items_to_be_repost = args.get("items_to_be_repost") or None self.allow_negative_stock = allow_negative_stock or is_negative_stock_allowed( item_code=self.item_code @@ -551,13 +465,16 @@ class update_entries_after: self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company") self.set_precision() self.valuation_method = get_valuation_method(self.item_code, self.company) + self.repost_affected_transaction = args.get("repost_affected_transaction") or set() self.new_items_found = False - self.affected_transactions: set[tuple[str, str]] = set() self.reserved_stock = self.get_reserved_stock() self.data = frappe._dict() - self.initialize_previous_data(self.args) + + if not self.repost_doc or not self.repost_doc.reposted_item_code: + self.initialize_previous_data(self.args) + self.build() def get_reserved_stock(self): @@ -607,8 +524,13 @@ class update_entries_after: """ self.data.setdefault(args.warehouse, frappe._dict()) warehouse_dict = self.data[args.warehouse] + + if self.stock_ledgers_to_repost: + return + previous_sle = get_previous_sle_of_current_voucher(args) - self.prev_sle_dict[(args.get("item_code"), args.get("warehouse"))] = previous_sle + if previous_sle: + self.prev_sle_dict[(args.get("item_code"), args.get("warehouse"))] = previous_sle warehouse_dict.previous_sle = previous_sle @@ -630,59 +552,60 @@ class update_entries_after: self.process_sle_against_current_timestamp() if not future_sle_exists(self.args): self.update_bin() - elif self.vouchers_to_repost: - self.repost_vouchers() else: - kwargs = frappe._dict( - { - "item_code": self.item_code, - "warehouse": self.args.warehouse, - "posting_date": self.args.posting_date, - "posting_time": self.args.posting_time, - "creation": self.args.creation, - } - ) + ledgers_to_repost = self.get_sles_to_repost() + if not ledgers_to_repost: + return - self.prepare_sles_to_repost(kwargs) - self.repost_vouchers() + self.stock_ledgers_to_repost = ledgers_to_repost + self.repost_stock_ledger_entries() self.update_bin() + self.reset_vouchers_and_idx() + self.update_data_in_repost() - def repost_vouchers(self): - vouchers = self.vouchers_to_repost or self.get_vouchere_to_repost() - for row in vouchers: - self.process_sle(row) + if self.exceptions: + self.raise_exceptions() - def get_vouchere_to_repost(self): + def get_sles_to_repost(self): + self.distinct_item_wh_sles = frappe._dict() + + sle_dict = self.get_items_to_be_repost() + self.prepare_sles_to_repost(sle_dict) if not self.distinct_item_wh_sles: return [] - sorted_rows = sorted( + ledgers_to_repost = sorted( (row for rows in self.distinct_item_wh_sles.values() for row in rows), key=lambda d: (get_datetime(d.get("posting_datetime")), get_datetime(d.get("creation"))), ) - return sorted_rows + return ledgers_to_repost - def prepare_sles_to_repost(self, kwargs): - sles = self.get_future_entries_to_repost(kwargs) + def prepare_sles_to_repost(self, sle_dict): + sles = self.get_future_entries_to_repost(sle_dict) for sle in sles: item_wh_key = (sle.item_code, sle.warehouse) if item_wh_key not in self.prev_sle_dict: - prev_sle = get_previous_sle_of_current_voucher(kwargs) + prev_sle = get_previous_sle_of_current_voucher(sle) self.prev_sle_dict[item_wh_key] = prev_sle - key = (sle.item_code, sle.warehouse, sle.voucher_detail_no) + key = (sle.item_code, sle.warehouse, sle.voucher_detail_no, sle.name) if key not in self.distinct_item_wh_sles: self.distinct_item_wh_sles.setdefault(key, []).append(sle) - if sle.dependant_sle_voucher_detail_no: - self.prepare_dependent_sles_to_repost(sle) + if sle.dependant_sle_voucher_detail_no: + self.prepare_dependent_sles_to_repost(sle) def prepare_dependent_sles_to_repost(self, sle): if sle.voucher_type == "Stock Entry" and is_repack_entry(sle.voucher_no): sles = self.get_sles_for_repack(sle) for repack_sle in sles: - key = (repack_sle.item_code, repack_sle.warehouse, repack_sle.voucher_detail_no) + key = ( + repack_sle.item_code, + repack_sle.warehouse, + repack_sle.voucher_detail_no, + repack_sle.name, + ) if key not in self.distinct_item_wh_sles: self.distinct_item_wh_sles.setdefault(key, []).append(repack_sle) @@ -690,13 +613,115 @@ class update_entries_after: elif sle.dependant_sle_voucher_detail_no: dependant_sle = get_sle_by_voucher_detail_no(sle.dependant_sle_voucher_detail_no) + if not dependant_sle: + return - key = (dependant_sle.item_code, dependant_sle.warehouse, dependant_sle.voucher_detail_no) + key = ( + dependant_sle.item_code, + dependant_sle.warehouse, + dependant_sle.voucher_detail_no, + dependant_sle.name, + ) if key not in self.distinct_item_wh_sles: self.distinct_item_wh_sles.setdefault(key, []).append(dependant_sle) self.prepare_sles_to_repost(dependant_sle) + def get_items_to_be_repost(self): + if self.repost_doc and self.repost_doc.reposted_item_code: + return frappe._dict( + { + "item_code": self.repost_doc.reposted_item_code, + "warehouse": self.repost_doc.reposted_warehouse, + "posting_date": self.repost_doc.sle_posting_date, + "posting_time": self.repost_doc.sle_posting_time, + "creation": self.repost_doc.reposted_sle_creation, + } + ) + + return frappe._dict( + { + "item_code": self.args.item_code, + "warehouse": self.args.warehouse, + "posting_date": self.args.posting_date, + "posting_time": self.args.posting_time, + "creation": self.args.creation, + } + ) + + def repost_stock_ledger_entries(self): + i = 0 + while self.stock_ledgers_to_repost: + sle = self.stock_ledgers_to_repost.pop(0) + + if self.args.item_code != sle.item_code or self.args.warehouse != sle.warehouse: + self.repost_affected_transaction.add((sle.voucher_type, sle.voucher_no)) + + if isinstance(sle, dict): + sle = frappe._dict(sle) + + self.process_sle(sle) + i += 1 + if i % 500 == 0: + self.update_data_in_repost(sle, i) + + def reset_vouchers_and_idx(self): + self.stock_ledgers_to_repost = [] + self.prev_sle_dict = frappe._dict() + + def update_data_in_repost(self, sle=None, index=None): + if not self.repost_doc: + return + + values_to_update = { + "total_vouchers": len(self.stock_ledgers_to_repost) + cint(index), + "vouchers_posted": index or 0, + "reposted_item_code": None, + "reposted_warehouse": None, + "sle_posting_date": None, + "sle_posting_time": None, + "reposted_sle_creation": None, + } + + if sle: + values_to_update.update( + { + "reposted_item_code": sle.item_code, + "reposted_warehouse": sle.warehouse, + "sle_posting_date": sle.posting_date, + "sle_posting_time": sle.posting_time, + "reposted_sle_creation": sle.creation, + } + ) + + self.repost_doc.db_set(values_to_update) + + update_args_in_repost_item_valuation( + self.repost_doc, + self.current_idx, + self.items_to_be_repost, + self.repost_affected_transaction, + only_affected_transaction=True, + ) + + if not frappe.in_test: + # To maintain the state of the reposting, so if timeout happens, it can be resumed from the last posted voucher + frappe.db.commit() # nosemgrep + + self.publish_real_time_progress(index=index) + + def publish_real_time_progress(self, index=None): + frappe.publish_realtime( + "item_reposting_progress", + { + "name": self.repost_doc.name, + "total_vouchers": len(self.stock_ledgers_to_repost) + cint(index), + "vouchers_posted": index or 0, + }, + doctype=self.repost_doc.doctype, + docname=self.repost_doc.name, + ) + def get_future_entries_to_repost(self, kwargs): return get_stock_ledger_entries(kwargs, ">=", "asc", for_update=True, check_serial_no=False) @@ -739,76 +764,6 @@ class update_entries_after: return list(self.get_sle_after_datetime(args)) - def get_dependent_entries_to_fix(self, entries_to_fix, sle): - dependant_sle = get_sle_by_voucher_detail_no( - sle.dependant_sle_voucher_detail_no, excluded_sle=sle.name - ) - - if not dependant_sle: - return entries_to_fix - elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse: - return entries_to_fix - elif dependant_sle.item_code != self.item_code: - self.update_distinct_item_warehouses(dependant_sle) - return entries_to_fix - elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data: - return entries_to_fix - else: - self.initialize_previous_data(dependant_sle) - self.update_distinct_item_warehouses(dependant_sle) - return entries_to_fix - - def update_distinct_item_warehouses(self, dependant_sle): - key = (dependant_sle.item_code, dependant_sle.warehouse) - val = frappe._dict({"sle": dependant_sle}) - - if key not in self.distinct_item_warehouses: - self.distinct_item_warehouses[key] = val - self.new_items_found = True - else: - existing_sle = self.distinct_item_warehouses[key].get("sle", {}) - if getdate(existing_sle.get("posting_date")) > getdate(dependant_sle.posting_date): - self.distinct_item_warehouses[key] = val - self.new_items_found = True - elif ( - dependant_sle.actual_qty > 0 - and dependant_sle.voucher_type == "Stock Entry" - and is_transfer_stock_entry(dependant_sle.voucher_no) - ): - if self.distinct_item_warehouses[key].get("transfer_entry_to_repost"): - return - - val["transfer_entry_to_repost"] = True - self.distinct_item_warehouses[key] = val - self.new_items_found = True - - def is_dependent_voucher_reposted(self, dependant_sle) -> bool: - # Return False if the dependent voucher is not reposted - - if self.args.items_to_be_repost and self.args.current_index: - index = self.args.current_index - while index < len(self.args.items_to_be_repost): - if ( - self.args.items_to_be_repost[index].get("item_code") == dependant_sle.item_code - and self.args.items_to_be_repost[index].get("warehouse") == dependant_sle.warehouse - ): - if getdate(self.args.items_to_be_repost[index].get("posting_date")) > getdate( - dependant_sle.posting_date - ): - self.args.items_to_be_repost[index]["posting_date"] = dependant_sle.posting_date - - return False - - index += 1 - - return True - - def get_dependent_voucher_detail_nos(self, key): - if "dependent_voucher_detail_nos" not in self.distinct_item_warehouses[key]: - self.distinct_item_warehouses[key].dependent_voucher_detail_nos = [] - - return self.distinct_item_warehouses[key].dependent_voucher_detail_nos - def validate_previous_sle_qty(self, sle): previous_sle = self.prev_sle_dict.get((sle.item_code, sle.warehouse)) if previous_sle and previous_sle.get("qty_after_transaction") < 0 and sle.get("actual_qty") > 0: @@ -830,15 +785,23 @@ class update_entries_after: def process_sle(self, sle): # previous sle data for this warehouse key = (sle.item_code, sle.warehouse) - self.wh_data = self.prev_sle_dict.get(key) or frappe._dict( - { - "qty_after_transaction": 0.0, - "valuation_rate": 0.0, - "stock_value": 0.0, - "prev_stock_value": 0.0, - "stock_queue": [], - } - ) + if key not in self.prev_sle_dict: + prev_sle = get_previous_sle_of_current_voucher(sle) + if prev_sle: + self.prev_sle_dict[key] = prev_sle + + if not self.prev_sle_dict.get(key): + self.prev_sle_dict[key] = frappe._dict( + { + "qty_after_transaction": 0.0, + "valuation_rate": 0.0, + "stock_value": 0.0, + "prev_stock_value": 0.0, + "stock_queue": [], + } + ) + + self.wh_data = self.prev_sle_dict.get(key) if self.wh_data.stock_queue and isinstance(self.wh_data.stock_queue, str): self.wh_data.stock_queue = json.loads(self.wh_data.stock_queue) @@ -847,7 +810,6 @@ class update_entries_after: self.wh_data.prev_stock_value = self.wh_data.stock_value self.validate_previous_sle_qty(sle) - self.affected_transactions.add((sle.voucher_type, sle.voucher_no)) if (sle.serial_no and not self.via_landed_cost_voucher) or not cint(self.allow_negative_stock): # validate negative stock for serialized items, fifo valuation @@ -950,6 +912,7 @@ class update_entries_after: sle.stock_queue = json.dumps(self.wh_data.stock_queue) sle.stock_value_difference = stock_value_difference + if ( sle.is_adjustment_entry and flt(sle.qty_after_transaction, self.flt_precision) == 0 @@ -1761,9 +1724,13 @@ class update_entries_after: for (item_code, warehouse), data in self.prev_sle_dict.items(): bin_name = get_or_make_bin(item_code, warehouse) - updated_values = {"actual_qty": data.qty_after_transaction, "stock_value": data.stock_value} + updated_values = { + "actual_qty": flt(data.qty_after_transaction), + "stock_value": flt(data.stock_value), + } if data.valuation_rate is not None: - updated_values["valuation_rate"] = data.valuation_rate + updated_values["valuation_rate"] = flt(data.valuation_rate) + frappe.db.set_value("Bin", bin_name, updated_values, update_modified=True) From 15739b5d81fa25fc0aa687af5f32fa4705a1c9d0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 26 Mar 2026 16:42:48 +0530 Subject: [PATCH 041/168] fix: pick correct dependant sle during reposting --- erpnext/stock/stock_ledger.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 21ccfb0f206..5d4c52312a4 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1909,10 +1909,14 @@ def get_stock_ledger_entries( ) -def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None): +def get_sle_by_voucher_detail_no(voucher_detail_no): return frappe.db.get_value( "Stock Ledger Entry", - {"voucher_detail_no": voucher_detail_no, "name": ["!=", excluded_sle], "is_cancelled": 0}, + { + "voucher_detail_no": voucher_detail_no, + "is_cancelled": 0, + "dependant_sle_voucher_detail_no": ("is", "not set"), + }, ["*"], as_dict=1, ) From 675b94b7a20305f00af6670215c176fce2f0a7f3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:42:46 +0530 Subject: [PATCH 042/168] fix: support translated search in get_party_type and refactor raw sql to qb (backport #53191) (#53832) * fix: support translated search in get_party_type and refactor raw sql to qb (cherry picked from commit d9876880586e5f98b5a991cc1327db370a30396e) # Conflicts: # erpnext/setup/doctype/party_type/party_type.py * fix: resolve merge conflicts in party_type.py --------- Co-authored-by: Shllokkk <140623894+Shllokkk@users.noreply.github.com> --- .../setup/doctype/party_type/party_type.py | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/erpnext/setup/doctype/party_type/party_type.py b/erpnext/setup/doctype/party_type/party_type.py index 6730d1cbdce..cf72eb3bbdc 100644 --- a/erpnext/setup/doctype/party_type/party_type.py +++ b/erpnext/setup/doctype/party_type/party_type.py @@ -4,6 +4,7 @@ import frappe from frappe.model.document import Document +from frappe.query_builder import DocType class PartyType(Document): @@ -24,29 +25,36 @@ class PartyType(Document): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs -def get_party_type(doctype, txt, searchfield, start, page_len, filters): - cond = "" - account_type = None +def get_party_type(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict): + PartyType = DocType("Party Type") + get_party_type_query = frappe.qb.from_(PartyType).select(PartyType.name).orderby(PartyType.name) + + condition_list = [] if filters and filters.get("account"): account_type = frappe.db.get_value("Account", filters.get("account"), "account_type") if account_type: if account_type in ["Receivable", "Payable"]: # Include Employee regardless of its configured account_type, but still respect the text filter - cond = "and (account_type = %(account_type)s or name = 'Employee')" + condition_list.append( + (PartyType.account_type == account_type) | (PartyType.name == "Employee") + ) else: - cond = "and account_type = %(account_type)s" + condition_list.append(PartyType.account_type == account_type) - # Build parameters dictionary - params = {"txt": "%" + txt + "%", "start": start, "page_len": page_len} - if account_type: - params["account_type"] = account_type + for condition in condition_list: + get_party_type_query = get_party_type_query.where(condition) - result = frappe.db.sql( - f"""select name from `tabParty Type` - where `{searchfield}` LIKE %(txt)s {cond} - order by name limit %(page_len)s offset %(start)s""", - params, - ) + if frappe.local.lang == "en": + get_party_type_query = get_party_type_query.where(getattr(PartyType, searchfield).like(f"%{txt}%")) + get_party_type_query = get_party_type_query.limit(page_len) + get_party_type_query = get_party_type_query.offset(start) + + result = get_party_type_query.run() + else: + result = get_party_type_query.run() + test_str = txt.lower() + result = [row for row in result if test_str in frappe._(row[0]).lower()] + result = result[start : start + page_len] return result or [] From c9953580b2d50199ecc30911d52e8f830c132666 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 08:32:29 +0000 Subject: [PATCH 043/168] ci: semgrep to prevent test regression (backport #53837) (#53840) ci: semgrep to prevent test regression (cherry picked from commit be4496e4ab4258ab317984caeb8b3d9f1e5bd5fc) Co-authored-by: ruthra kumar --- .github/workflows/linters.yml | 3 +++ semgrep/test-correctness.yml | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 semgrep/test-correctness.yml diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 6701673cc7f..37d8363beaa 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -43,3 +43,6 @@ jobs: - name: Run Semgrep rules run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness + + - name: Semgrep for Test Correctness + run: semgrep ci --include=**/test_*.py --config ./semgrep/test-correctness.yml diff --git a/semgrep/test-correctness.yml b/semgrep/test-correctness.yml new file mode 100644 index 00000000000..34eb82fa1d6 --- /dev/null +++ b/semgrep/test-correctness.yml @@ -0,0 +1,18 @@ +rules: +- id: Dont-commit + pattern: frappe.db.commit() + message: Commiting inside test breaks idempotency. + languages: [python] + severity: ERROR +- id: Implicit-commit + pattern: frappe.db.truncate() + message: DB truncation does implict commit which breaks test idempotency. + languages: [python] + severity: ERROR +- id: Dont-override-teardown + pattern: | + def tearDown(...): + ... + message: ERPNextTestSuite forces rollback on each tearDown, which ensures idempotency. Don't override tearDown. + languages: [python] + severity: ERROR From 9a2851f221fc708d818e4f42f2836266128c3d85 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:34:18 +0000 Subject: [PATCH 044/168] fix: validate if quantity greater than 0 in item dashboard (backport #53846) (#53848) Co-authored-by: Mihir Kandoi fix: validate if quantity greater than 0 in item dashboard (#53846) --- erpnext/stock/dashboard/item_dashboard.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index 17f65ce270c..018a4f86fa8 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -281,8 +281,13 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, stoc } dialog.set_primary_action(__("Create Stock Entry"), function () { - if (source && (dialog.get_value("qty") == 0 || dialog.get_value("qty") > actual_qty)) { - frappe.msgprint(__("Quantity must be greater than zero, and less or equal to {0}", [actual_qty])); + if (flt(dialog.get_value("qty")) <= 0) { + frappe.msgprint(__("Quantity must be greater than zero")); + return; + } + + if (source && dialog.get_value("qty") > actual_qty) { + frappe.msgprint(__("Quantity must be less than or equal to {0}", [actual_qty])); return; } From 1c1369fea8a20e7a62fc13bea669ad40dd481ec8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 12:45:50 +0000 Subject: [PATCH 045/168] fix: invalid dynamic link filter for address doctype (backport #53849) (#53852) --- erpnext/controllers/queries.py | 23 +++++++++++++++++++ .../stock/doctype/stock_entry/stock_entry.js | 8 +++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index b7845bc5698..9b663c0d2b9 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -1002,3 +1002,26 @@ def get_item_uom_query(doctype, txt, searchfield, start, page_len, filters): limit_page_length=page_len, as_list=1, ) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_warehouse_address(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict): + table = frappe.qb.DocType(doctype) + child_table = frappe.qb.DocType("Dynamic Link") + + query = ( + frappe.qb.from_(table) + .inner_join(child_table) + .on((table.name == child_table.parent) & (child_table.parenttype == doctype)) + .select(table.name) + .where( + (child_table.link_name == filters.get("warehouse")) + & (table.disabled == 0) + & (child_table.link_doctype == "Warehouse") + & (table.name.like(f"%{txt}%")) + ) + .offset(start) + .limit(page_len) + ) + return query.run(as_list=1) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 4fdd9df1adf..f71b67e1127 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -38,18 +38,18 @@ frappe.ui.form.on("Stock Entry", { frm.set_query("source_warehouse_address", function () { return { + query: "erpnext.controllers.queries.get_warehouse_address", filters: { - link_doctype: "Warehouse", - link_name: frm.doc.from_warehouse, + warehouse: frm.doc.from_warehouse, }, }; }); frm.set_query("target_warehouse_address", function () { return { + query: "erpnext.controllers.queries.get_warehouse_address", filters: { - link_doctype: "Warehouse", - link_name: frm.doc.to_warehouse, + warehouse: frm.doc.to_warehouse, }, }; }); From 8fbb86d53e6bea3ea10b582252c4babbaae978ed Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 27 Mar 2026 19:02:31 +0530 Subject: [PATCH 046/168] fix: corrected logic to retry reposting if timeout occurs after dependent SLE processing (cherry picked from commit 90b9ab0bc86ad2f6ac1ca694a3a343dcc2afb67a) --- .../repost_item_valuation.json | 60 +--- .../repost_item_valuation.py | 10 - erpnext/stock/stock_ledger.py | 276 ++++++++++-------- 3 files changed, 159 insertions(+), 187 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index 3b4ae7220a5..ce43ae3a54f 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -32,18 +32,11 @@ "total_reposting_count", "current_index", "gl_reposting_index", + "reposting_data_file", "vouchers_based_on_item_and_warehouse_section", "total_vouchers", "column_break_yqwo", - "vouchers_posted", - "last_sle_posted_section", - "reposted_item_code", - "reposted_warehouse", - "reposting_data_file", - "column_break_miwc", - "sle_posting_date", - "sle_posting_time", - "reposted_sle_creation" + "vouchers_posted" ], "fields": [ { @@ -236,6 +229,7 @@ { "fieldname": "vouchers_based_on_item_and_warehouse_section", "fieldtype": "Section Break", + "hidden": 1, "label": "Reposting Vouchers" }, { @@ -256,52 +250,6 @@ "no_copy": 1, "read_only": 1 }, - { - "fieldname": "last_sle_posted_section", - "fieldtype": "Section Break", - "label": "Last SLE Posted" - }, - { - "fieldname": "reposted_item_code", - "fieldtype": "Link", - "label": "Reposted Item Code", - "no_copy": 1, - "options": "Item", - "read_only": 1 - }, - { - "fieldname": "reposted_warehouse", - "fieldtype": "Link", - "label": "Reposted Warehouse", - "no_copy": 1, - "options": "Warehouse", - "read_only": 1 - }, - { - "fieldname": "column_break_miwc", - "fieldtype": "Column Break" - }, - { - "fieldname": "reposted_sle_creation", - "fieldtype": "Datetime", - "label": "Reposted SLE Creation", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "sle_posting_date", - "fieldtype": "Date", - "label": "SLE Posting Date", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "sle_posting_time", - "fieldtype": "Time", - "label": "SLE Posting Time", - "no_copy": 1, - "read_only": 1 - }, { "fieldname": "reposting_data_file", "fieldtype": "Attach", @@ -314,7 +262,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2026-03-26 13:52:51.895504", + "modified": "2026-03-27 18:59:58.637964", "modified_by": "Administrator", "module": "Stock", "name": "Repost Item Valuation", diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index d7397682ee6..2b4d5c28692 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -49,13 +49,8 @@ class RepostItemValuation(Document): posting_time: DF.Time | None recreate_stock_ledgers: DF.Check repost_only_accounting_ledgers: DF.Check - reposted_item_code: DF.Link | None - reposted_sle_creation: DF.Datetime | None - reposted_warehouse: DF.Link | None reposting_data_file: DF.Attach | None reposting_reference: DF.Data | None - sle_posting_date: DF.Date | None - sle_posting_time: DF.Time | None status: DF.Literal["Queued", "In Progress", "Completed", "Skipped", "Failed", "Cancelled"] total_reposting_count: DF.Int total_vouchers: DF.Int @@ -268,11 +263,6 @@ class RepostItemValuation(Document): self.total_reposting_count = 0 self.total_vouchers = 0 self.vouchers_posted = 0 - self.reposted_item_code = None - self.reposted_warehouse = None - self.sle_posting_date = None - self.sle_posting_time = None - self.reposted_sle_creation = None self.clear_attachment() self.db_update() diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 5d4c52312a4..c811e510b0c 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -4,6 +4,7 @@ import copy import gzip import json +from collections import deque import frappe from frappe import _, bold, scrub @@ -224,7 +225,13 @@ def repost_future_sle( voucher_type=voucher_type, voucher_no=voucher_no, doc=doc, reposting_data=reposting_data ) - repost_affected_transaction = get_affected_transactions(doc) or set() + if doc and doc.reposting_data_file: + reposting_data = get_reposting_data(doc.reposting_data_file) + + repost_affected_transaction = get_affected_transactions(doc, reposting_data) or set() + resume_item_wh_wise_last_posted_sle = ( + get_item_wh_wise_last_posted_sle_from_reposting_data(doc, reposting_data) or {} + ) if not items_to_be_repost: return @@ -241,6 +248,7 @@ def repost_future_sle( "items_to_be_repost": items_to_be_repost, "repost_doc": doc, "repost_affected_transaction": repost_affected_transaction, + "item_wh_wise_last_posted_sle": resume_item_wh_wise_last_posted_sle, }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher, @@ -248,15 +256,25 @@ def repost_future_sle( index += 1 + resume_item_wh_wise_last_posted_sle = {} repost_affected_transaction.update(obj.repost_affected_transaction) update_args_in_repost_item_valuation(doc, index, items_to_be_repost, repost_affected_transaction) def update_args_in_repost_item_valuation( - doc, index, items_to_be_repost, repost_affected_transaction, only_affected_transaction=False + doc, + index, + items_to_be_repost, + repost_affected_transaction, + item_wh_wise_last_posted_sle=None, + only_affected_transaction=False, ): file_name = "" has_file = False + + if not item_wh_wise_last_posted_sle: + item_wh_wise_last_posted_sle = {} + if doc.reposting_data_file: has_file = True @@ -267,6 +285,8 @@ def update_args_in_repost_item_valuation( doc.reposting_data_file = create_json_gz_file( { "repost_affected_transaction": repost_affected_transaction, + "item_wh_wise_last_posted_sle": {str(k): v for k, v in item_wh_wise_last_posted_sle.items()} + or {}, }, doc, file_name, @@ -385,6 +405,16 @@ def get_affected_transactions(doc, reposting_data=None) -> set[tuple[str, str]]: return set() +def get_item_wh_wise_last_posted_sle_from_reposting_data(doc, reposting_data=None): + if not reposting_data and doc and doc.reposting_data_file: + reposting_data = get_reposting_data(doc.reposting_data_file) + + if reposting_data and reposting_data.item_wh_wise_last_posted_sle: + return frappe._dict(reposting_data.item_wh_wise_last_posted_sle) + + return frappe._dict() + + def get_reposting_data(file_path) -> dict: file_name = frappe.db.get_value( "File", @@ -448,7 +478,6 @@ class update_entries_after: self.allow_zero_rate = allow_zero_rate self.via_landed_cost_voucher = via_landed_cost_voucher self.item_code = args.get("item_code") - self.prev_sle_dict = frappe._dict({}) self.stock_ledgers_to_repost = [] self.current_idx = args.get("current_idx", 0) self.repost_doc = args.get("repost_doc") or None @@ -462,6 +491,7 @@ class update_entries_after: if self.args.sle_id: self.args["name"] = self.args.sle_id + self.prev_sle_dict = frappe._dict({}) self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company") self.set_precision() self.valuation_method = get_valuation_method(self.item_code, self.company) @@ -472,7 +502,7 @@ class update_entries_after: self.data = frappe._dict() - if not self.repost_doc or not self.repost_doc.reposted_item_code: + if not self.repost_doc or not self.args.get("item_wh_wise_last_posted_sle"): self.initialize_previous_data(self.args) self.build() @@ -553,12 +583,14 @@ class update_entries_after: if not future_sle_exists(self.args): self.update_bin() else: - ledgers_to_repost = self.get_sles_to_repost() - if not ledgers_to_repost: - return + self.item_wh_wise_last_posted_sle = self.get_item_wh_wise_last_posted_sle() + _item_wh_sle = self.sort_sles(self.item_wh_wise_last_posted_sle.values()) + + while _item_wh_sle: + self.initialize_reposting() + sle_dict = _item_wh_sle.pop(0) + self.repost_stock_ledgers(sle_dict) - self.stock_ledgers_to_repost = ledgers_to_repost - self.repost_stock_ledger_entries() self.update_bin() self.reset_vouchers_and_idx() self.update_data_in_repost() @@ -566,134 +598,129 @@ class update_entries_after: if self.exceptions: self.raise_exceptions() - def get_sles_to_repost(self): - self.distinct_item_wh_sles = frappe._dict() + def initialize_reposting(self): + self._sles = [] + self.distinct_sles = set() + self.distinct_dependant_sle = set() + self.prev_sle_dict = frappe._dict({}) - sle_dict = self.get_items_to_be_repost() - self.prepare_sles_to_repost(sle_dict) - if not self.distinct_item_wh_sles: - return [] + def get_item_wh_wise_last_posted_sle(self): + if self.args and self.args.get("item_wh_wise_last_posted_sle"): + _sles = {} + for key, sle in self.args.get("item_wh_wise_last_posted_sle").items(): + _sles[frappe.safe_eval(key)] = frappe._dict(sle) - ledgers_to_repost = sorted( - (row for rows in self.distinct_item_wh_sles.values() for row in rows), - key=lambda d: (get_datetime(d.get("posting_datetime")), get_datetime(d.get("creation"))), - ) + return _sles - return ledgers_to_repost - - def prepare_sles_to_repost(self, sle_dict): - sles = self.get_future_entries_to_repost(sle_dict) - for sle in sles: - item_wh_key = (sle.item_code, sle.warehouse) - if item_wh_key not in self.prev_sle_dict: - prev_sle = get_previous_sle_of_current_voucher(sle) - self.prev_sle_dict[item_wh_key] = prev_sle - - key = (sle.item_code, sle.warehouse, sle.voucher_detail_no, sle.name) - if key not in self.distinct_item_wh_sles: - self.distinct_item_wh_sles.setdefault(key, []).append(sle) - - if sle.dependant_sle_voucher_detail_no: - self.prepare_dependent_sles_to_repost(sle) - - def prepare_dependent_sles_to_repost(self, sle): - if sle.voucher_type == "Stock Entry" and is_repack_entry(sle.voucher_no): - sles = self.get_sles_for_repack(sle) - for repack_sle in sles: - key = ( - repack_sle.item_code, - repack_sle.warehouse, - repack_sle.voucher_detail_no, - repack_sle.name, - ) - if key not in self.distinct_item_wh_sles: - self.distinct_item_wh_sles.setdefault(key, []).append(repack_sle) - - self.prepare_sles_to_repost(repack_sle) - - elif sle.dependant_sle_voucher_detail_no: - dependant_sle = get_sle_by_voucher_detail_no(sle.dependant_sle_voucher_detail_no) - if not dependant_sle: - return - - key = ( - dependant_sle.item_code, - dependant_sle.warehouse, - dependant_sle.voucher_detail_no, - dependant_sle.name, - ) - if key not in self.distinct_item_wh_sles: - self.distinct_item_wh_sles.setdefault(key, []).append(dependant_sle) - - self.prepare_sles_to_repost(dependant_sle) - - def get_items_to_be_repost(self): - if self.repost_doc and self.repost_doc.reposted_item_code: - return frappe._dict( + return { + (self.args.item_code, self.args.warehouse): frappe._dict( { - "item_code": self.repost_doc.reposted_item_code, - "warehouse": self.repost_doc.reposted_warehouse, - "posting_date": self.repost_doc.sle_posting_date, - "posting_time": self.repost_doc.sle_posting_time, - "creation": self.repost_doc.reposted_sle_creation, + "item_code": self.args.item_code, + "warehouse": self.args.warehouse, + "posting_datetime": get_combine_datetime(self.args.posting_date, self.args.posting_time), + "posting_date": self.args.posting_date, + "posting_time": self.args.posting_time, + "creation": self.args.creation, } ) + } - return frappe._dict( - { - "item_code": self.args.item_code, - "warehouse": self.args.warehouse, - "posting_date": self.args.posting_date, - "posting_time": self.args.posting_time, - "creation": self.args.creation, - } + def repost_stock_ledgers(self, sle_dict=None): + self._sles = self.get_future_entries_to_repost(sle_dict) + + if not isinstance(self._sles, deque): + self._sles = deque(self._sles) + + i = 0 + while self._sles: + sle = self._sles.popleft() + i += 1 + if sle.name in self.distinct_sles: + continue + + item_wh_key = (sle.item_code, sle.warehouse) + if item_wh_key not in self.prev_sle_dict: + self.prev_sle_dict[item_wh_key] = get_previous_sle_of_current_voucher(sle) + + if ( + sle.dependant_sle_voucher_detail_no + and sle.dependant_sle_voucher_detail_no not in self.distinct_dependant_sle + ): + self._sles.append(sle) + self.distinct_dependant_sle.add(sle.dependant_sle_voucher_detail_no) + self.include_dependant_sle_in_reposting(sle) + continue + + self.repost_stock_ledger_entry(sle) + + # To avoid duplicate reposting of same sle in case of multiple dependant sle + self.distinct_sles.add(sle.name) + + # if i % 1000 == 0: + self.update_data_in_repost(len(self._sles), i) + + def sort_sles(self, sles): + return sorted( + sles, + key=lambda d: ( + get_datetime(d.posting_datetime), + get_datetime(d.creation), + ), ) - def repost_stock_ledger_entries(self): - i = 0 - while self.stock_ledgers_to_repost: - sle = self.stock_ledgers_to_repost.pop(0) + def include_dependant_sle_in_reposting(self, sle): + if sle.voucher_type == "Stock Entry" and is_repack_entry(sle.voucher_no): + repack_sles = self.get_sles_for_repack(sle) + for repack_sle in repack_sles: + self._sles.extend(self.get_future_entries_to_repost(repack_sle)) + else: + dependant_sles = get_sle_by_voucher_detail_no(sle.dependant_sle_voucher_detail_no) + for depend_sle in dependant_sles: + self._sles.extend(self.get_future_entries_to_repost(depend_sle)) - if self.args.item_code != sle.item_code or self.args.warehouse != sle.warehouse: - self.repost_affected_transaction.add((sle.voucher_type, sle.voucher_no)) + self._sles = deque(self.sort_sles(self._sles)) - if isinstance(sle, dict): - sle = frappe._dict(sle) + def repost_stock_ledger_entry(self, sle): + if self.args.item_code != sle.item_code or self.args.warehouse != sle.warehouse: + self.repost_affected_transaction.add((sle.voucher_type, sle.voucher_no)) - self.process_sle(sle) - i += 1 - if i % 500 == 0: - self.update_data_in_repost(sle, i) + if isinstance(sle, dict): + sle = frappe._dict(sle) + + self.process_sle(sle) + self.update_item_wh_wise_last_posted_sle(sle) + + def update_item_wh_wise_last_posted_sle(self, sle): + if not self._sles: + self.item_wh_wise_last_posted_sle = frappe._dict() + return + + self.item_wh_wise_last_posted_sle[(sle.item_code, sle.warehouse)] = frappe._dict( + { + "item_code": sle.item_code, + "warehouse": sle.warehouse, + "posting_date": sle.posting_date, + "posting_time": sle.posting_time, + "posting_datetime": sle.posting_datetime + or get_combine_datetime(sle.posting_date, sle.posting_time), + "creation": sle.creation, + } + ) def reset_vouchers_and_idx(self): self.stock_ledgers_to_repost = [] self.prev_sle_dict = frappe._dict() + self.item_wh_wise_last_posted_sle = frappe._dict() - def update_data_in_repost(self, sle=None, index=None): + def update_data_in_repost(self, total_sles=None, index=None): if not self.repost_doc: return values_to_update = { - "total_vouchers": len(self.stock_ledgers_to_repost) + cint(index), + "total_vouchers": cint(total_sles) + cint(index), "vouchers_posted": index or 0, - "reposted_item_code": None, - "reposted_warehouse": None, - "sle_posting_date": None, - "sle_posting_time": None, - "reposted_sle_creation": None, } - if sle: - values_to_update.update( - { - "reposted_item_code": sle.item_code, - "reposted_warehouse": sle.warehouse, - "sle_posting_date": sle.posting_date, - "sle_posting_time": sle.posting_time, - "reposted_sle_creation": sle.creation, - } - ) - self.repost_doc.db_set(values_to_update) update_args_in_repost_item_valuation( @@ -701,6 +728,7 @@ class update_entries_after: self.current_idx, self.items_to_be_repost, self.repost_affected_transaction, + self.item_wh_wise_last_posted_sle, only_affected_transaction=True, ) @@ -708,14 +736,14 @@ class update_entries_after: # To maintain the state of the reposting, so if timeout happens, it can be resumed from the last posted voucher frappe.db.commit() # nosemgrep - self.publish_real_time_progress(index=index) + self.publish_real_time_progress(total_sles=total_sles, index=index) - def publish_real_time_progress(self, index=None): + def publish_real_time_progress(self, total_sles=None, index=None): frappe.publish_realtime( "item_reposting_progress", { "name": self.repost_doc.name, - "total_vouchers": len(self.stock_ledgers_to_repost) + cint(index), + "total_vouchers": cint(total_sles) + cint(index), "vouchers_posted": index or 0, }, doctype=self.repost_doc.doctype, @@ -736,7 +764,14 @@ class update_entries_after: "is_cancelled": 0, "dependant_sle_voucher_detail_no": ("!=", sle.dependant_sle_voucher_detail_no), }, - fields=["*"], + fields=[ + "item_code", + "warehouse", + "posting_date", + "posting_time", + "posting_datetime", + "creation", + ], ) or [] ) @@ -1910,15 +1945,14 @@ def get_stock_ledger_entries( def get_sle_by_voucher_detail_no(voucher_detail_no): - return frappe.db.get_value( + return frappe.get_all( "Stock Ledger Entry", - { + filters={ "voucher_detail_no": voucher_detail_no, "is_cancelled": 0, "dependant_sle_voucher_detail_no": ("is", "not set"), }, - ["*"], - as_dict=1, + fields=["item_code", "warehouse", "posting_date", "posting_time", "posting_datetime", "creation"], ) From 2907c411f344053804f154358650939853f20ffa Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 07:10:14 +0000 Subject: [PATCH 047/168] fix: change shipment parcel dimension fields from Int to Float (backport #53867) (#53873) Co-authored-by: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> fix: change shipment parcel dimension fields from Int to Float (#53867) --- .../stock/doctype/shipment_parcel/shipment_parcel.json | 8 ++++---- .../shipment_parcel_template.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json index 321599e2b4b..32d0df2c873 100644 --- a/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json +++ b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json @@ -14,19 +14,19 @@ "fields": [ { "fieldname": "length", - "fieldtype": "Int", + "fieldtype": "Float", "in_list_view": 1, "label": "Length (cm)" }, { "fieldname": "width", - "fieldtype": "Int", + "fieldtype": "Float", "in_list_view": 1, "label": "Width (cm)" }, { "fieldname": "height", - "fieldtype": "Int", + "fieldtype": "Float", "in_list_view": 1, "label": "Height (cm)" }, @@ -49,7 +49,7 @@ ], "istable": 1, "links": [], - "modified": "2024-03-27 13:10:41.396354", + "modified": "2026-03-29 00:00:00.000000", "modified_by": "Administrator", "module": "Stock", "name": "Shipment Parcel", diff --git a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json index 9eb9ba46762..6e55b59a497 100644 --- a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json +++ b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json @@ -15,21 +15,21 @@ "fields": [ { "fieldname": "length", - "fieldtype": "Int", + "fieldtype": "Float", "in_list_view": 1, "label": "Length (cm)", "reqd": 1 }, { "fieldname": "width", - "fieldtype": "Int", + "fieldtype": "Float", "in_list_view": 1, "label": "Width (cm)", "reqd": 1 }, { "fieldname": "height", - "fieldtype": "Int", + "fieldtype": "Float", "in_list_view": 1, "label": "Height (cm)", "reqd": 1 @@ -52,7 +52,7 @@ } ], "links": [], - "modified": "2024-03-27 13:10:41.521126", + "modified": "2026-03-29 00:00:00.000000", "modified_by": "Administrator", "module": "Stock", "name": "Shipment Parcel Template", From 544c91441ba2c9ba6eaed19b2b4246c7712b8503 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 29 Mar 2026 15:53:46 +0530 Subject: [PATCH 048/168] fix: maintain state during reposting (cherry picked from commit f8738a791bb0b059057bbf6e99d265053133381f) --- erpnext/stock/stock_ledger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c811e510b0c..838244a7fc2 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -656,8 +656,8 @@ class update_entries_after: # To avoid duplicate reposting of same sle in case of multiple dependant sle self.distinct_sles.add(sle.name) - # if i % 1000 == 0: - self.update_data_in_repost(len(self._sles), i) + if i % 1000 == 0: + self.update_data_in_repost(len(self._sles), i) def sort_sles(self, sles): return sorted( From 83cac1575549b5ae3b92f4939dcdb6f3396e307e Mon Sep 17 00:00:00 2001 From: MochaMind Date: Sun, 29 Mar 2026 18:25:38 +0530 Subject: [PATCH 049/168] chore: update POT file (#53875) --- erpnext/locale/main.pot | 1007 ++++++++++++++++++++------------------- 1 file changed, 529 insertions(+), 478 deletions(-) diff --git a/erpnext/locale/main.pot b/erpnext/locale/main.pot index f627ccf1b41..21fb2c6830e 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-22 09:44+0000\n" -"PO-Revision-Date: 2026-03-22 09:44+0000\n" +"POT-Creation-Date: 2026-03-29 09:46+0000\n" +"PO-Revision-Date: 2026-03-29 09:46+0000\n" "Last-Translator: hello@frappe.io\n" "Language-Team: hello@frappe.io\n" "MIME-Version: 1.0\n" @@ -262,7 +262,7 @@ msgstr "" msgid "% of materials delivered against this Sales Order" msgstr "" -#: erpnext/controllers/accounts_controller.py:2371 +#: erpnext/controllers/accounts_controller.py:2381 msgid "'Account' in the Accounting section of Customer {0}" msgstr "" @@ -278,7 +278,7 @@ msgstr "" msgid "'Days Since Last Order' must be greater than or equal to zero" msgstr "" -#: erpnext/controllers/accounts_controller.py:2376 +#: erpnext/controllers/accounts_controller.py:2386 msgid "'Default {0} Account' in Company {1}" msgstr "" @@ -865,11 +865,6 @@ msgstr "" msgid "Masters & Reports" msgstr "" -#. Header text in the Selling Workspace -#: erpnext/selling/workspace/selling/selling.json -msgid "Quick Access" -msgstr "" - #. Header text in the Invoicing Workspace #. Header text in the Assets Workspace #. Header text in the Buying Workspace @@ -915,11 +910,11 @@ msgstr "" msgid "Your Shortcuts" msgstr "" -#: erpnext/accounts/doctype/payment_request/payment_request.py:1133 +#: erpnext/accounts/doctype/payment_request/payment_request.py:1134 msgid "Grand Total: {0}" msgstr "" -#: erpnext/accounts/doctype/payment_request/payment_request.py:1134 +#: erpnext/accounts/doctype/payment_request/payment_request.py:1135 msgid "Outstanding Amount: {0}" msgstr "" @@ -1335,7 +1330,7 @@ msgid "Account Manager" msgstr "" #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1007 -#: erpnext/controllers/accounts_controller.py:2380 +#: erpnext/controllers/accounts_controller.py:2390 msgid "Account Missing" msgstr "" @@ -1568,7 +1563,7 @@ msgstr "" msgid "Account: {0} is not permitted under Payment Entry" msgstr "" -#: erpnext/controllers/accounts_controller.py:3269 +#: erpnext/controllers/accounts_controller.py:3279 msgid "Account: {0} with currency: {1} can not be selected" msgstr "" @@ -1862,7 +1857,7 @@ msgstr "" msgid "Accounting Entry for Landed Cost Voucher for SCR {0}" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:840 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:844 msgid "Accounting Entry for Service" msgstr "" @@ -1877,18 +1872,18 @@ msgstr "" #: 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:933 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:937 #: erpnext/stock/doctype/stock_entry/stock_entry.py:1904 #: erpnext/stock/doctype/stock_entry/stock_entry.py:1918 #: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:708 msgid "Accounting Entry for Stock" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:737 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:741 msgid "Accounting Entry for {0}" msgstr "" -#: erpnext/controllers/accounts_controller.py:2421 +#: erpnext/controllers/accounts_controller.py:2431 msgid "Accounting Entry for {0}: {1} can only be made in currency: {2}" msgstr "" @@ -1961,7 +1956,7 @@ msgstr "" #: erpnext/setup/doctype/email_digest/email_digest.json #: erpnext/setup/doctype/incoterm/incoterm.json #: erpnext/setup/doctype/supplier_group/supplier_group.json -#: erpnext/setup/install.py:368 +#: erpnext/setup/install.py:369 msgid "Accounts" msgstr "" @@ -2414,7 +2409,7 @@ msgstr "" msgid "Actual Operation Time" msgstr "" -#: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py:430 +#: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py:456 msgid "Actual Posting" msgstr "" @@ -2670,7 +2665,7 @@ msgstr "" msgid "Add Sub Assembly" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:516 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:517 #: erpnext/public/js/event.js:32 msgid "Add Suppliers" msgstr "" @@ -3015,6 +3010,8 @@ msgstr "" #. Invoice' #. Label of the address_and_contact_tab (Tab Break) field in DocType 'Purchase #. Order' +#. Label of the address_and_contact_tab (Tab Break) field in DocType 'Request +#. for Quotation' #. Label of the contact_and_address_tab (Tab Break) field in DocType 'Supplier' #. Label of the address_and_contact_tab (Tab Break) field in DocType 'Supplier #. Quotation' @@ -3035,6 +3032,7 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json #: erpnext/buying/doctype/purchase_order/purchase_order.json +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.json #: erpnext/buying/doctype/supplier/supplier.json #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.json #: erpnext/crm/doctype/opportunity/opportunity.json @@ -3153,7 +3151,7 @@ msgstr "" msgid "Adjustment Against" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:665 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:669 msgid "Adjustment based on Purchase Invoice rate" msgstr "" @@ -3310,12 +3308,6 @@ msgstr "" msgid "Aerospace" msgstr "" -#. Label of the affected_transactions (Code) field in DocType 'Repost Item -#. Valuation' -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json -msgid "Affected Transactions" -msgstr "" - #. Label of the against (Text) field in DocType 'GL Entry' #: erpnext/accounts/doctype/gl_entry/gl_entry.json #: erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html:20 @@ -3440,7 +3432,7 @@ msgstr "" msgid "Against Stock Entry" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:332 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:331 msgid "Against Supplier Invoice {0}" msgstr "" @@ -3741,11 +3733,11 @@ msgstr "" msgid "All communications including and above this shall be moved into the new Issue" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:967 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:972 msgid "All items are already requested" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1426 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1430 msgid "All items have already been Invoiced/Returned" msgstr "" @@ -4510,6 +4502,7 @@ msgstr "" #: erpnext/accounts/print_format/sales_invoice_print/sales_invoice_print.html:93 #: erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py:48 #: erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py:79 +#: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py:411 #: erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py:44 #: erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py:273 #: erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py:327 @@ -4727,8 +4720,8 @@ msgstr "" msgid "Ampere-Second" msgstr "" -#: erpnext/controllers/trends.py:269 erpnext/controllers/trends.py:281 -#: erpnext/controllers/trends.py:290 +#: erpnext/controllers/trends.py:277 erpnext/controllers/trends.py:289 +#: erpnext/controllers/trends.py:298 msgid "Amt" msgstr "" @@ -4737,7 +4730,7 @@ msgstr "" msgid "An Item Group is a way to classify items based on types." msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:535 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:537 msgid "An error has been appeared while reposting item valuation via {0}" msgstr "" @@ -4803,7 +4796,7 @@ msgstr "" msgid "Another Cost Center Allocation record {0} applicable from {1}, hence this allocation will be applicable upto {2}" msgstr "" -#: erpnext/accounts/doctype/payment_request/payment_request.py:883 +#: erpnext/accounts/doctype/payment_request/payment_request.py:884 msgid "Another Payment Request is already processed" msgstr "" @@ -5242,11 +5235,11 @@ msgstr "" msgid "As there are reserved stock, you cannot disable {0}." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1087 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1092 msgid "As there are sufficient Sub Assembly Items, Work Order is not required for Warehouse {0}." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1827 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1832 msgid "As there are sufficient raw materials, Material Request is not required for Warehouse {0}." msgstr "" @@ -5664,7 +5657,7 @@ msgstr "" msgid "Asset cannot be cancelled, as it is already {0}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:393 +#: erpnext/assets/doctype/asset/depreciation.py:396 msgid "Asset cannot be scrapped before the last depreciation entry." msgstr "" @@ -5696,7 +5689,7 @@ msgstr "" msgid "Asset received at Location {0} and issued to Employee {1}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:454 +#: erpnext/assets/doctype/asset/depreciation.py:457 msgid "Asset restored" msgstr "" @@ -5708,11 +5701,11 @@ msgstr "" msgid "Asset returned" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:441 +#: erpnext/assets/doctype/asset/depreciation.py:444 msgid "Asset scrapped" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:443 +#: erpnext/assets/doctype/asset/depreciation.py:446 msgid "Asset scrapped via Journal Entry {0}" msgstr "" @@ -5737,7 +5730,7 @@ msgstr "" msgid "Asset updated due to Asset Repair {0} {1}." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:375 +#: erpnext/assets/doctype/asset/depreciation.py:378 msgid "Asset {0} cannot be scrapped, as it is already {1}" msgstr "" @@ -5778,7 +5771,7 @@ msgstr "" msgid "Asset {0} is not submitted. Please submit the asset before proceeding." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:373 +#: erpnext/assets/doctype/asset/depreciation.py:376 msgid "Asset {0} must be submitted" msgstr "" @@ -5828,7 +5821,7 @@ msgstr "" msgid "Assets {assets_link} created for {item_code}" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:232 +#: erpnext/manufacturing/doctype/job_card/job_card.js:240 msgid "Assign Job to Employee" msgstr "" @@ -6195,6 +6188,10 @@ msgstr "" msgid "Auto Tax Settings Error" msgstr "" +#: erpnext/setup/doctype/employee/employee.py:170 +msgid "Auto User Creation Error" +msgstr "" + #. Description of the 'Close Replied Opportunity After Days' (Int) field in #. DocType 'CRM Settings' #: erpnext/crm/doctype/crm_settings/crm_settings.json @@ -6312,7 +6309,8 @@ msgstr "" #. Label of the available_quantity_section (Section Break) field in DocType #. 'Pick List Item' #: erpnext/manufacturing/doctype/workstation/workstation.js:505 -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py:88 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:118 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:175 #: 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 @@ -6530,8 +6528,7 @@ msgstr "" #: erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json #: erpnext/manufacturing/report/bom_explorer/bom_explorer.js:8 #: erpnext/manufacturing/report/bom_explorer/bom_explorer.py:67 -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js:8 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js:5 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js:8 #: erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py:109 #: erpnext/manufacturing/workspace/manufacturing/manufacturing.json #: erpnext/selling/doctype/sales_order/sales_order.js:1415 @@ -6551,7 +6548,7 @@ msgstr "" msgid "BOM 1" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1757 +#: erpnext/manufacturing/doctype/bom/bom.py:1760 msgid "BOM 1 {0} and BOM 2 {1} should not be same" msgstr "" @@ -6692,10 +6689,6 @@ msgstr "" msgid "BOM Operations Time" msgstr "" -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py:26 -msgid "BOM Qty" -msgstr "" - #: erpnext/stock/report/item_prices/item_prices.py:60 msgid "BOM Rate" msgstr "" @@ -6715,15 +6708,12 @@ msgid "BOM Search" msgstr "" #. Name of a report -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.json -msgid "BOM Stock Calculated" +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.json +msgid "BOM Stock Analysis" msgstr "" -#. Name of a report #. Label of a Link in the Manufacturing Workspace #. Label of a Workspace Sidebar Item -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html:1 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.json #: erpnext/manufacturing/workspace/manufacturing/manufacturing.json #: erpnext/workspace_sidebar/manufacturing.json msgid "BOM Stock Report" @@ -6734,10 +6724,6 @@ msgstr "" msgid "BOM Tree" msgstr "" -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py:27 -msgid "BOM UOM" -msgstr "" - #. Name of a DocType #: erpnext/manufacturing/doctype/bom_update_batch/bom_update_batch.json msgid "BOM Update Batch" @@ -6816,15 +6802,15 @@ msgstr "" msgid "BOM recursion: {1} cannot be parent or child of {0}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1492 +#: erpnext/manufacturing/doctype/bom/bom.py:1494 msgid "BOM {0} does not belong to Item {1}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1474 +#: erpnext/manufacturing/doctype/bom/bom.py:1476 msgid "BOM {0} must be active" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1477 +#: erpnext/manufacturing/doctype/bom/bom.py:1479 msgid "BOM {0} must be submitted" msgstr "" @@ -7754,7 +7740,7 @@ msgstr "" #. Label of a Card Break in the Manufacturing Workspace #. Label of a Link in the Manufacturing Workspace #. Label of a Workspace Sidebar Item -#: erpnext/manufacturing/doctype/bom/bom.py:1324 +#: erpnext/manufacturing/doctype/bom/bom.py:1326 #: erpnext/manufacturing/workspace/manufacturing/manufacturing.json #: erpnext/stock/doctype/material_request/material_request.js:139 #: erpnext/stock/doctype/stock_entry/stock_entry.js:695 @@ -8934,7 +8920,7 @@ msgid "Can only make payment against unbilled {0}" msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.js:1517 -#: erpnext/controllers/accounts_controller.py:3178 +#: erpnext/controllers/accounts_controller.py:3188 #: erpnext/public/js/controllers/accounts.js:103 msgid "Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'" msgstr "" @@ -8999,7 +8985,7 @@ msgstr "" msgid "Cannot Optimize Route as Driver Address is Missing." msgstr "" -#: erpnext/setup/doctype/employee/employee.py:181 +#: erpnext/setup/doctype/employee/employee.py:295 msgid "Cannot Relieve Employee" msgstr "" @@ -9043,7 +9029,7 @@ msgstr "" msgid "Cannot cancel because submitted Stock Entry {0} exists" msgstr "" -#: erpnext/stock/stock_ledger.py:209 +#: erpnext/stock/stock_ledger.py:179 msgid "Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet." msgstr "" @@ -9055,7 +9041,7 @@ msgstr "" 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 "" -#: erpnext/controllers/buying_controller.py:1136 +#: erpnext/controllers/buying_controller.py:1137 msgid "Cannot cancel this document as it is linked with the submitted asset {asset_link}. Please cancel the asset to continue." msgstr "" @@ -9103,7 +9089,7 @@ msgstr "" msgid "Cannot covert to Group because Account Type is selected." msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1014 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1018 msgid "Cannot create Stock Reservation Entries for future dated Purchase Receipts." msgstr "" @@ -9120,7 +9106,7 @@ msgstr "" msgid "Cannot create return for consolidated invoice {0}." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1175 +#: erpnext/manufacturing/doctype/bom/bom.py:1177 msgid "Cannot deactivate or cancel BOM as it is linked with other BOMs" msgstr "" @@ -9141,7 +9127,7 @@ msgstr "" msgid "Cannot delete Serial No {0}, as it is used in stock transactions" msgstr "" -#: erpnext/controllers/accounts_controller.py:3774 +#: erpnext/controllers/accounts_controller.py:3784 msgid "Cannot delete an item which has been ordered" msgstr "" @@ -9191,7 +9177,7 @@ msgstr "" msgid "Cannot find Item with this Barcode" msgstr "" -#: erpnext/controllers/accounts_controller.py:3726 +#: erpnext/controllers/accounts_controller.py:3736 msgid "Cannot find a default warehouse for item {0}. Please set one in the Item Master or in Stock Settings." msgstr "" @@ -9215,12 +9201,12 @@ msgstr "" msgid "Cannot receive from customer against negative outstanding" msgstr "" -#: erpnext/controllers/accounts_controller.py:3922 +#: erpnext/controllers/accounts_controller.py:3932 msgid "Cannot reduce quantity than ordered or purchased quantity" msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.js:1530 -#: erpnext/controllers/accounts_controller.py:3193 +#: erpnext/controllers/accounts_controller.py:3203 #: erpnext/public/js/controllers/accounts.js:120 msgid "Cannot refer row number greater than or equal to current row number for this Charge type" msgstr "" @@ -9236,7 +9222,7 @@ msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.js:1523 #: erpnext/accounts/doctype/payment_entry/payment_entry.js:1701 #: erpnext/accounts/doctype/payment_entry/payment_entry.py:1827 -#: erpnext/controllers/accounts_controller.py:3183 +#: erpnext/controllers/accounts_controller.py:3193 #: erpnext/public/js/controllers/accounts.js:112 #: erpnext/public/js/controllers/taxes_and_totals.js:531 msgid "Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row" @@ -9254,11 +9240,11 @@ msgstr "" msgid "Cannot set multiple Item Defaults for a company." msgstr "" -#: erpnext/controllers/accounts_controller.py:3888 +#: erpnext/controllers/accounts_controller.py:3898 msgid "Cannot set quantity less than delivered quantity" msgstr "" -#: erpnext/controllers/accounts_controller.py:3889 +#: erpnext/controllers/accounts_controller.py:3899 msgid "Cannot set quantity less than received quantity" msgstr "" @@ -9270,7 +9256,7 @@ msgstr "" msgid "Cannot start deletion. Another deletion {0} is already queued/running. Please wait for it to complete." msgstr "" -#: erpnext/controllers/accounts_controller.py:3916 +#: erpnext/controllers/accounts_controller.py:3926 msgid "Cannot update rate as item {0} is already ordered or purchased against this quotation" msgstr "" @@ -9451,7 +9437,7 @@ msgstr "" msgid "Cash In Hand" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:322 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:321 msgid "Cash or Bank Account is mandatory for making payment entry" msgstr "" @@ -9545,8 +9531,8 @@ msgstr "" msgid "Category-wise Asset Value" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:298 -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:140 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:297 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:142 msgid "Caution" msgstr "" @@ -9683,7 +9669,7 @@ msgid "Channel Partner" msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.py:2256 -#: erpnext/controllers/accounts_controller.py:3246 +#: erpnext/controllers/accounts_controller.py:3256 msgid "Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount" msgstr "" @@ -10857,7 +10843,7 @@ msgstr "" msgid "Company Address Name" msgstr "" -#: erpnext/controllers/accounts_controller.py:4340 +#: erpnext/controllers/accounts_controller.py:4352 msgid "Company Address is missing. You don't have permission to update it. Please contact your System Manager." msgstr "" @@ -10927,7 +10913,7 @@ msgid "Company Field" msgstr "" #. Label of the company_logo (Attach Image) field in DocType 'Company' -#: erpnext/public/js/print.js:75 erpnext/setup/doctype/company/company.json +#: erpnext/public/js/print.js:77 erpnext/setup/doctype/company/company.json msgid "Company Logo" msgstr "" @@ -10939,7 +10925,10 @@ msgstr "" msgid "Company Not Linked" msgstr "" +#. Label of the shipping_address (Link) field in DocType 'Request for +#. Quotation' #. Label of the shipping_address (Link) field in DocType 'Subcontracting Order' +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.json #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json msgid "Company Shipping Address" msgstr "" @@ -10988,6 +10977,10 @@ msgstr "" msgid "Company of asset {0} and purchase document {1} doesn't matches." msgstr "" +#: erpnext/setup/doctype/employee/employee.py:168 +msgid "Company or Personal Email is mandatory when 'Create User Automatically' is enabled" +msgstr "" + #. Description of the 'Registration Details' (Code) field in DocType 'Company' #: erpnext/setup/doctype/company/company.json msgid "Company registration numbers for your reference. Tax numbers etc." @@ -11058,7 +11051,7 @@ msgstr "" msgid "Competitors" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:269 +#: erpnext/manufacturing/doctype/job_card/job_card.js:277 #: erpnext/manufacturing/doctype/workstation/workstation.js:151 msgid "Complete Job" msgstr "" @@ -11105,8 +11098,8 @@ msgstr "" msgid "Completed Qty cannot be greater than 'Qty to Manufacture'" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:317 -#: erpnext/manufacturing/doctype/job_card/job_card.js:438 +#: erpnext/manufacturing/doctype/job_card/job_card.js:325 +#: erpnext/manufacturing/doctype/job_card/job_card.js:446 #: erpnext/manufacturing/doctype/workstation/workstation.js:296 msgid "Completed Quantity" msgstr "" @@ -11787,15 +11780,15 @@ msgstr "" msgid "Conversion factor for item {0} has been reset to 1.0 as the uom {1} is same as stock uom {2}." msgstr "" -#: erpnext/controllers/accounts_controller.py:2961 +#: erpnext/controllers/accounts_controller.py:2971 msgid "Conversion rate cannot be 0" msgstr "" -#: erpnext/controllers/accounts_controller.py:2968 +#: erpnext/controllers/accounts_controller.py:2978 msgid "Conversion rate is 1.00, but document currency is different from company currency" msgstr "" -#: erpnext/controllers/accounts_controller.py:2964 +#: erpnext/controllers/accounts_controller.py:2974 msgid "Conversion rate must be 1.00 if document currency is same as company currency" msgstr "" @@ -11861,13 +11854,13 @@ msgstr "" msgid "Corrective Action" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:495 +#: erpnext/manufacturing/doctype/job_card/job_card.js:503 msgid "Corrective Job Card" msgstr "" #. Label of the corrective_operation_section (Tab Break) field in DocType 'Job #. Card' -#: erpnext/manufacturing/doctype/job_card/job_card.js:502 +#: erpnext/manufacturing/doctype/job_card/job_card.js:510 #: erpnext/manufacturing/doctype/job_card/job_card.json msgid "Corrective Operation" msgstr "" @@ -12106,7 +12099,7 @@ msgid "Cost Center is a part of Cost Center Allocation, hence cannot be converte msgstr "" #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1433 -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:899 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:903 msgid "Cost Center is required in row {0} in Taxes table for type {1}" msgstr "" @@ -12428,7 +12421,7 @@ msgstr "" msgid "Create Inter Company Journal Entry" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js:54 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js:55 msgid "Create Invoices" msgstr "" @@ -12666,7 +12659,7 @@ msgstr "" msgid "Create Supplier" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:180 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:181 msgid "Create Supplier Quotation" msgstr "" @@ -12697,13 +12690,19 @@ msgstr "" msgid "Create Transfer Entry" msgstr "" -#. Label of the create_user (Button) field in DocType 'Employee' -#: erpnext/setup/doctype/employee/employee.json +#: erpnext/setup/doctype/employee/employee.js:50 +#: erpnext/setup/doctype/employee/employee.js:52 #: erpnext/utilities/activation.py:117 msgid "Create User" msgstr "" +#. Label of the create_user_automatically (Check) field in DocType 'Employee' +#: erpnext/setup/doctype/employee/employee.json +msgid "Create User Automatically" +msgstr "" + #. Label of the create_user_permission (Check) field in DocType 'Employee' +#: erpnext/setup/doctype/employee/employee.js:65 #: erpnext/setup/doctype/employee/employee.json msgid "Create User Permission" msgstr "" @@ -12741,7 +12740,7 @@ msgstr "" msgid "Create a variant with the template image." msgstr "" -#: erpnext/stock/stock_ledger.py:2018 +#: erpnext/stock/stock_ledger.py:2065 msgid "Create an incoming stock transaction for the Item." msgstr "" @@ -12760,12 +12759,6 @@ msgstr "" msgid "Create in Draft Status" msgstr "" -#. Description of the 'Create Missing Party' (Check) field in DocType 'Opening -#. Invoice Creation Tool' -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json -msgid "Create missing customer or supplier." -msgstr "" - #. Label of an action in the Onboarding Step 'Create Supplier' #: erpnext/buying/onboarding_step/create_supplier/create_supplier.json msgid "Create supplier" @@ -12785,6 +12778,12 @@ msgstr "" msgid "Created {0} scorecards for {1} between:" msgstr "" +#. Description of the 'Create User Automatically' (Check) field in DocType +#. 'Employee' +#: erpnext/setup/doctype/employee/employee.json +msgid "Creates a User account for this employee using the Preferred, Company, or Personal email." +msgstr "" + #: erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js:140 msgid "Creating Accounts..." msgstr "" @@ -12809,7 +12808,7 @@ msgstr "" msgid "Creating Packing Slip ..." msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js:60 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js:61 msgid "Creating Purchase Invoices ..." msgstr "" @@ -12823,7 +12822,7 @@ msgstr "" msgid "Creating Purchase Receipt ..." msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js:58 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js:59 msgid "Creating Sales Invoices ..." msgstr "" @@ -12844,11 +12843,11 @@ msgstr "" msgid "Creating Subcontracting Receipt ..." msgstr "" -#: erpnext/setup/doctype/employee/employee.js:92 +#: erpnext/setup/doctype/employee/employee.js:85 msgid "Creating User..." msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:287 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:300 msgid "Creating {} out of {} {}" msgstr "" @@ -13061,9 +13060,9 @@ msgstr "" #. Label of the credit_to (Link) field in DocType 'Purchase Invoice' #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:379 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:387 -#: erpnext/controllers/accounts_controller.py:2360 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:378 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:386 +#: erpnext/controllers/accounts_controller.py:2370 msgid "Credit To" msgstr "" @@ -14283,7 +14282,7 @@ msgstr "" msgid "Date of Birth" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:146 +#: erpnext/setup/doctype/employee/employee.py:260 msgid "Date of Birth cannot be greater than today." msgstr "" @@ -14535,7 +14534,7 @@ msgstr "" #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1011 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1022 -#: erpnext/controllers/accounts_controller.py:2360 +#: erpnext/controllers/accounts_controller.py:2370 msgid "Debit To" msgstr "" @@ -14713,7 +14712,7 @@ msgstr "" msgid "Default BOM for {0} not found" msgstr "" -#: erpnext/controllers/accounts_controller.py:3960 +#: erpnext/controllers/accounts_controller.py:3970 msgid "Default BOM not found for FG Item {0}" msgstr "" @@ -15202,6 +15201,11 @@ msgstr "" msgid "Define Project type." msgstr "" +#. Description of the 'End of Life' (Date) field in DocType 'Item' +#: erpnext/stock/doctype/item/item.json +msgid "Defines the date after which the item can no longer be used in transactions or manufacturing" +msgstr "" + #. Name of a UOM #: erpnext/setup/setup_wizard/data/uom_data.json msgid "Dekagram/Litre" @@ -15788,7 +15792,7 @@ msgstr "" msgid "Depreciation Entry against asset {0}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:255 +#: erpnext/assets/doctype/asset/depreciation.py:258 msgid "Depreciation Entry against {0} worth {1}" msgstr "" @@ -15800,7 +15804,7 @@ msgstr "" msgid "Depreciation Expense Account" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:302 +#: erpnext/assets/doctype/asset/depreciation.py:305 msgid "Depreciation Expense Account should be an Income or Expense Account." msgstr "" @@ -16003,7 +16007,7 @@ msgstr "" msgid "Difference Posting Date" msgstr "" -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py:100 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:120 msgid "Difference Qty" msgstr "" @@ -16572,7 +16576,7 @@ msgstr "" msgid "Disposal Date" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:832 +#: erpnext/assets/doctype/asset/depreciation.py:835 msgid "Disposal date {0} cannot be before {1} date {2} of the asset." msgstr "" @@ -16610,12 +16614,6 @@ msgstr "" msgid "Distance from top edge" msgstr "" -#. Label of the distinct_item_and_warehouse (Code) field in DocType 'Repost -#. Item Valuation' -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json -msgid "Distinct Item and Warehouse" -msgstr "" - #. Description of a DocType #: erpnext/stock/doctype/serial_no/serial_no.json msgid "Distinct unit of an Item" @@ -16767,7 +16765,7 @@ msgstr "" msgid "Do you want to submit the material request" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:103 +#: erpnext/manufacturing/doctype/job_card/job_card.js:111 msgid "Do you want to submit the stock entry?" msgstr "" @@ -16823,10 +16821,6 @@ msgstr "" msgid "Document Type already used as a dimension" msgstr "" -#: erpnext/setup/install.py:189 -msgid "Documentation" -msgstr "" - #. Description of the 'Reconciliation Queue Size' (Int) field in DocType #. 'Accounts Settings' #: erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -16884,7 +16878,7 @@ msgstr "" msgid "Download CSV Template" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:144 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:145 msgid "Download PDF for Supplier" msgstr "" @@ -17450,10 +17444,18 @@ msgstr "" msgid "Email Receipt" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:371 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:373 msgid "Email Sent to Supplier {0}" msgstr "" +#: erpnext/setup/doctype/employee/employee.py:433 +msgid "Email is required to create a user" +msgstr "" + +#: erpnext/setup/doctype/employee/employee.js:72 +msgid "Email is required to create a user." +msgstr "" + #: erpnext/stock/doctype/shipment/shipment.js:174 msgid "Email or Phone/Mobile of the Contact are mandatory to continue." msgstr "" @@ -17584,11 +17586,6 @@ msgstr "" msgid "Employee Education" msgstr "" -#. Label of the exit (Tab Break) field in DocType 'Employee' -#: erpnext/setup/doctype/employee/employee.json -msgid "Employee Exit" -msgstr "" - #. Name of a DocType #: erpnext/setup/doctype/employee_external_work_history/employee_external_work_history.json msgid "Employee External Work History" @@ -17637,11 +17634,11 @@ msgstr "" msgid "Employee User Id" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:211 +#: erpnext/setup/doctype/employee/employee.py:325 msgid "Employee cannot report to himself." msgstr "" -#: erpnext/setup/doctype/employee/employee.py:443 +#: erpnext/setup/doctype/employee/employee.py:567 msgid "Employee is required" msgstr "" @@ -17649,6 +17646,10 @@ msgstr "" msgid "Employee is required while issuing Asset {0}" msgstr "" +#: erpnext/setup/doctype/employee/employee.py:430 +msgid "Employee {0} already has a linked user" +msgstr "" + #: erpnext/assets/doctype/asset_movement/asset_movement.py:92 #: erpnext/assets/doctype/asset_movement/asset_movement.py:113 msgid "Employee {0} does not belong to the company {1}" @@ -17658,7 +17659,7 @@ msgstr "" msgid "Employee {0} is currently working on another workstation. Please assign another employee." msgstr "" -#: erpnext/setup/doctype/employee/employee.py:468 +#: erpnext/setup/doctype/employee/employee.py:592 msgid "Employee {0} not found" msgstr "" @@ -17874,6 +17875,11 @@ msgstr "" msgid "Enable to apply SLA on every {0}" msgstr "" +#. Description of the 'Retain Sample' (Check) field in DocType 'Item' +#: erpnext/stock/doctype/item/item.json +msgid "Enable to reserve a small sample from each batch for any analysis arising ahead" +msgstr "" + #. Label of the enable_tracking_sales_commissions (Check) field in DocType #. 'Selling Settings' #: erpnext/selling/doctype/selling_settings/selling_settings.json @@ -17927,8 +17933,8 @@ msgstr "" #. Label of the end_time (Time) field in DocType 'Stock Reposting Settings' #. Label of the end_time (Time) field in DocType 'Service Day' #. Label of the end_time (Datetime) field in DocType 'Call Log' -#: erpnext/manufacturing/doctype/job_card/job_card.js:375 -#: erpnext/manufacturing/doctype/job_card/job_card.js:445 +#: erpnext/manufacturing/doctype/job_card/job_card.js:383 +#: erpnext/manufacturing/doctype/job_card/job_card.js:453 #: erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json #: erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json #: erpnext/support/doctype/service_day/service_day.json @@ -17987,12 +17993,6 @@ msgstr "" msgid "Engineer" msgstr "" -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html:13 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html:23 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py:30 -msgid "Enough Parts to Build" -msgstr "" - #. Label of the ensure_delivery_based_on_produced_serial_no (Check) field in #. DocType 'Sales Order Item' #: erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -18003,11 +18003,11 @@ msgstr "" msgid "Enter API key in Google Settings." msgstr "" -#: erpnext/public/js/print.js:62 +#: erpnext/public/js/print.js:64 msgid "Enter Company Details" msgstr "" -#: erpnext/setup/doctype/employee/employee.js:108 +#: erpnext/setup/doctype/employee/employee.js:148 msgid "Enter First and Last name of Employee, based on Which Full Name will be updated. IN transactions, it will be Full Name which will be fetched." msgstr "" @@ -18019,8 +18019,8 @@ msgstr "" msgid "Enter Serial Nos" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:402 -#: erpnext/manufacturing/doctype/job_card/job_card.js:471 +#: erpnext/manufacturing/doctype/job_card/job_card.js:410 +#: erpnext/manufacturing/doctype/job_card/job_card.js:479 #: erpnext/manufacturing/doctype/workstation/workstation.js:312 msgid "Enter Value" msgstr "" @@ -18161,11 +18161,11 @@ msgstr "" msgid "Error Description" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:277 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:290 msgid "Error Occurred" msgstr "" -#: erpnext/telephony/doctype/call_log/call_log.py:195 +#: erpnext/telephony/doctype/call_log/call_log.py:197 msgid "Error during caller information update" msgstr "" @@ -18181,7 +18181,7 @@ msgstr "" msgid "Error in party matching for Bank Transaction {0}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:319 +#: erpnext/assets/doctype/asset/depreciation.py:322 msgid "Error while posting depreciation entries" msgstr "" @@ -18189,7 +18189,7 @@ msgstr "" msgid "Error while processing deferred accounting for {0}" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:531 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:533 msgid "Error while reposting item valuation" msgstr "" @@ -18262,7 +18262,7 @@ msgstr "" msgid "Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions, then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item, leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings." msgstr "" -#: erpnext/stock/stock_ledger.py:2281 +#: erpnext/stock/stock_ledger.py:2328 msgid "Example: Serial No {0} reserved in {1}." msgstr "" @@ -18486,12 +18486,17 @@ msgstr "" msgid "Existing Customer" msgstr "" +#. Label of the exit (Tab Break) field in DocType 'Employee' +#: erpnext/setup/doctype/employee/employee.json +msgid "Exit" +msgstr "" + #. Label of the held_on (Date) field in DocType 'Employee' #: erpnext/setup/doctype/employee/employee.json msgid "Exit Interview Held On" msgstr "" -#: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py:444 +#: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py:470 msgid "Expected" msgstr "" @@ -18605,7 +18610,7 @@ msgstr "" #: erpnext/accounts/doctype/cashier_closing/cashier_closing.json #: erpnext/accounts/doctype/ledger_merge/ledger_merge.json #: erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:605 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:604 #: erpnext/accounts/report/account_balance/account_balance.js:28 #: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js:89 #: erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py:183 @@ -18676,13 +18681,13 @@ msgstr "" msgid "Expense Head" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:499 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:523 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:543 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:498 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:522 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:542 msgid "Expense Head Changed" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:601 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:600 msgid "Expense account is mandatory for item {0}" msgstr "" @@ -18804,6 +18809,10 @@ msgstr "" msgid "FG / Semi FG Item" msgstr "" +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js:21 +msgid "FG Items to Make" +msgstr "" + #. Option for the 'Default Stock Valuation Method' (Select) field in DocType #. 'Company' #. Option for the 'Valuation Method' (Select) field in DocType 'Item' @@ -19004,7 +19013,7 @@ msgstr "" msgid "Fetched only {0} available serial numbers." msgstr "" -#: erpnext/edi/doctype/code_list/code_list_import.py:27 +#: erpnext/edi/doctype/code_list/code_list_import.py:28 msgid "Fetching Error" msgstr "" @@ -19316,15 +19325,15 @@ msgstr "" msgid "Finished Good Item Quantity" msgstr "" -#: erpnext/controllers/accounts_controller.py:3946 +#: erpnext/controllers/accounts_controller.py:3956 msgid "Finished Good Item is not specified for service item {0}" msgstr "" -#: erpnext/controllers/accounts_controller.py:3963 +#: erpnext/controllers/accounts_controller.py:3973 msgid "Finished Good Item {0} Qty can not be zero" msgstr "" -#: erpnext/controllers/accounts_controller.py:3957 +#: erpnext/controllers/accounts_controller.py:3967 msgid "Finished Good Item {0} must be a sub-contracted item" msgstr "" @@ -19725,7 +19734,7 @@ msgid "For Job Card" msgstr "" #. Label of the for_operation (Link) field in DocType 'Job Card' -#: erpnext/manufacturing/doctype/job_card/job_card.js:515 +#: erpnext/manufacturing/doctype/job_card/job_card.js:523 #: erpnext/manufacturing/doctype/job_card/job_card.json msgid "For Operation" msgstr "" @@ -19857,7 +19866,7 @@ msgstr "" msgid "For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1716 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1721 msgid "For row {0}: Enter Planned Qty" msgstr "" @@ -20034,8 +20043,8 @@ msgstr "" msgid "From BOM" msgstr "" -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py:63 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py:25 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:105 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:169 msgid "From BOM No" msgstr "" @@ -20468,7 +20477,7 @@ msgstr "" msgid "Future Payments" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:382 +#: erpnext/assets/doctype/asset/depreciation.py:385 msgid "Future date is not allowed" msgstr "" @@ -20771,9 +20780,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: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/request_for_quotation/request_for_quotation.js:380 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:402 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:447 #: 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 @@ -20816,7 +20825,7 @@ msgstr "" msgid "Get Items from BOM" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:418 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:419 msgid "Get Items from Material Requests against this Supplier" msgstr "" @@ -20906,12 +20915,12 @@ msgstr "" msgid "Get Sub Assembly Items" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:460 -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:480 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:461 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:481 msgid "Get Suppliers" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:484 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:485 msgid "Get Suppliers By" msgstr "" @@ -21558,8 +21567,8 @@ msgstr "" msgid "Hectopascal" msgstr "" -#. Label of the height (Int) field in DocType 'Shipment Parcel' -#. Label of the height (Int) field in DocType 'Shipment Parcel Template' +#. Label of the height (Float) field in DocType 'Shipment Parcel' +#. Label of the height (Float) field in DocType 'Shipment Parcel Template' #: erpnext/stock/doctype/shipment_parcel/shipment_parcel.json #: erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json msgid "Height (cm)" @@ -21585,11 +21594,11 @@ msgstr "" msgid "Helps you distribute the Budget/Target across months if you have seasonality in your business." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:349 +#: erpnext/assets/doctype/asset/depreciation.py:352 msgid "Here are the error logs for the aforementioned failed depreciation entries: {0}" msgstr "" -#: erpnext/stock/stock_ledger.py:2003 +#: erpnext/stock/stock_ledger.py:2050 msgid "Here are the options to proceed:" msgstr "" @@ -21604,7 +21613,7 @@ msgstr "" msgid "Here you can maintain height, weight, allergies, medical concerns etc" msgstr "" -#: erpnext/setup/doctype/employee/employee.js:134 +#: erpnext/setup/doctype/employee/employee.js:174 msgid "Here, you can select a senior of this Employee. Based on this, Organization Chart will be populated." msgstr "" @@ -21617,7 +21626,7 @@ msgstr "" msgid "Hertz" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:533 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:535 msgid "Hi," msgstr "" @@ -22174,10 +22183,16 @@ msgstr "" msgid "If no taxes are set, and Taxes and Charges Template is selected, the system will automatically apply the taxes from the chosen template." msgstr "" -#: erpnext/stock/stock_ledger.py:2013 +#: erpnext/stock/stock_ledger.py:2060 msgid "If not, you can Cancel / Submit this entry" msgstr "" +#. Description of the 'Create Missing Party' (Check) field in DocType 'Opening +#. Invoice Creation Tool' +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json +msgid "If party does not exist, create it using the Party Name field." +msgstr "" + #. Description of the 'Free Item Rate' (Currency) field in DocType 'Pricing #. Rule' #: erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -22203,7 +22218,7 @@ msgstr "" msgid "If the account is frozen, entries are allowed to restricted users." msgstr "" -#: erpnext/stock/stock_ledger.py:2006 +#: erpnext/stock/stock_ledger.py:2053 msgid "If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table." msgstr "" @@ -22300,11 +22315,11 @@ msgstr "" msgid "If you need to reconcile particular transactions against each other, then please select accordingly. If not, all the transactions will be allocated in FIFO order." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1092 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1097 msgid "If you still want to proceed, please disable 'Skip Available Sub Assembly Items' checkbox." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1832 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1837 msgid "If you still want to proceed, please enable {0}." msgstr "" @@ -22384,7 +22399,7 @@ msgstr "" msgid "Ignore Existing Ordered Qty" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1824 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1829 msgid "Ignore Existing Projected Quantity" msgstr "" @@ -22496,6 +22511,10 @@ msgstr "" msgid "Import Data" msgstr "" +#: erpnext/setup/doctype/employee/employee_list.js:16 +msgid "Import Employees" +msgstr "" + #: erpnext/edi/doctype/code_list/code_list.js:7 #: erpnext/edi/doctype/code_list/code_list_list.js:3 #: erpnext/edi/doctype/common_code/common_code_list.js:3 @@ -22607,12 +22626,6 @@ msgstr "" msgid "In Stock" msgstr "" -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html:12 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html:22 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py:29 -msgid "In Stock Qty" -msgstr "" - #. Option for the 'Status' (Select) field in DocType 'Delivery Trip' #. Option for the 'Transfer Status' (Select) field in DocType 'Material #. Request' @@ -23354,8 +23367,8 @@ msgstr "" msgid "Insufficient Capacity" msgstr "" -#: erpnext/controllers/accounts_controller.py:3840 -#: erpnext/controllers/accounts_controller.py:3864 +#: erpnext/controllers/accounts_controller.py:3850 +#: erpnext/controllers/accounts_controller.py:3874 msgid "Insufficient Permissions" msgstr "" @@ -23364,12 +23377,12 @@ msgstr "" #: erpnext/stock/doctype/pick_list/pick_list.py:162 #: erpnext/stock/doctype/pick_list/pick_list.py:1055 #: erpnext/stock/doctype/stock_entry/stock_entry.py:956 -#: erpnext/stock/serial_batch_bundle.py:1205 erpnext/stock/stock_ledger.py:1713 -#: erpnext/stock/stock_ledger.py:2172 +#: erpnext/stock/serial_batch_bundle.py:1205 erpnext/stock/stock_ledger.py:1741 +#: erpnext/stock/stock_ledger.py:2219 msgid "Insufficient Stock" msgstr "" -#: erpnext/stock/stock_ledger.py:2187 +#: erpnext/stock/stock_ledger.py:2234 msgid "Insufficient Stock for Batch" msgstr "" @@ -23604,14 +23617,14 @@ msgstr "" msgid "Interval should be between 1 to 59 MInutes" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:380 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:388 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:379 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:387 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1017 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1027 #: erpnext/assets/doctype/asset_category/asset_category.py:69 #: erpnext/assets/doctype/asset_category/asset_category.py:97 -#: erpnext/controllers/accounts_controller.py:3207 -#: erpnext/controllers/accounts_controller.py:3215 +#: erpnext/controllers/accounts_controller.py:3217 +#: erpnext/controllers/accounts_controller.py:3225 msgid "Invalid Account" msgstr "" @@ -23620,7 +23633,7 @@ msgid "Invalid Accounting Dimension" msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.py:400 -#: erpnext/accounts/doctype/payment_request/payment_request.py:1004 +#: erpnext/accounts/doctype/payment_request/payment_request.py:1005 msgid "Invalid Allocated Amount" msgstr "" @@ -23662,7 +23675,7 @@ msgstr "" #: erpnext/assets/doctype/asset/asset.py:361 #: erpnext/assets/doctype/asset/asset.py:368 -#: erpnext/controllers/accounts_controller.py:3230 +#: erpnext/controllers/accounts_controller.py:3240 msgid "Invalid Cost Center" msgstr "" @@ -23696,7 +23709,7 @@ msgid "Invalid Group By" msgstr "" #: erpnext/accounts/doctype/pos_invoice/pos_invoice.py:499 -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:955 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:960 msgid "Invalid Item" msgstr "" @@ -23748,7 +23761,7 @@ msgstr "" msgid "Invalid Priority" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1230 +#: erpnext/manufacturing/doctype/bom/bom.py:1232 msgid "Invalid Process Loss Configuration" msgstr "" @@ -23756,8 +23769,8 @@ msgstr "" msgid "Invalid Purchase Invoice" msgstr "" -#: erpnext/controllers/accounts_controller.py:3884 -#: erpnext/controllers/accounts_controller.py:3898 +#: erpnext/controllers/accounts_controller.py:3894 +#: erpnext/controllers/accounts_controller.py:3908 msgid "Invalid Qty" msgstr "" @@ -23865,7 +23878,7 @@ msgid "Invalid {0}: {1}" msgstr "" #. Label of the inventory_section (Tab Break) field in DocType 'Item' -#: erpnext/setup/install.py:358 erpnext/stock/doctype/item/item.json +#: erpnext/setup/install.py:359 erpnext/stock/doctype/item/item.json msgid "Inventory" msgstr "" @@ -24824,10 +24837,8 @@ msgstr "" #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/plant_floor/plant_floor.js:109 #: erpnext/manufacturing/doctype/workstation/workstation_job_card.html:25 -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py:50 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html:9 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html:19 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py:22 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:101 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:165 #: erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js:68 #: erpnext/manufacturing/report/process_loss_report/process_loss_report.js:15 #: erpnext/manufacturing/report/process_loss_report/process_loss_report.py:74 @@ -25217,7 +25228,7 @@ msgstr "" msgid "Item Code cannot be changed for Serial No." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:455 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:454 msgid "Item Code required at Row No {0}" msgstr "" @@ -25596,7 +25607,6 @@ msgstr "" #: erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.html:8 #: erpnext/manufacturing/report/bom_explorer/bom_explorer.py:66 #: erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py:109 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py:23 #: erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py:106 #: erpnext/manufacturing/report/job_card_summary/job_card_summary.py:158 #: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:958 @@ -26033,7 +26043,7 @@ msgstr "" msgid "Item operation" msgstr "" -#: erpnext/controllers/accounts_controller.py:3938 +#: erpnext/controllers/accounts_controller.py:3948 msgid "Item qty can not be updated as raw materials are already processed." msgstr "" @@ -26108,7 +26118,7 @@ msgstr "" msgid "Item {0} has reached its end of life on {1}" msgstr "" -#: erpnext/stock/stock_ledger.py:118 +#: erpnext/stock/stock_ledger.py:117 msgid "Item {0} ignored since it is not a stock item" msgstr "" @@ -26132,7 +26142,7 @@ msgstr "" msgid "Item {0} is not a stock Item" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:954 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:959 msgid "Item {0} is not a subcontracted item" msgstr "" @@ -26164,7 +26174,7 @@ msgstr "" msgid "Item {0} not found." msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:325 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:324 msgid "Item {0}: Ordered qty {1} cannot be less than minimum order qty {2} (defined in Item)." msgstr "" @@ -26242,7 +26252,7 @@ msgstr "" msgid "Items Filter" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1678 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1683 #: erpnext/selling/doctype/sales_order/sales_order.js:1676 msgid "Items Required" msgstr "" @@ -26266,11 +26276,11 @@ msgstr "" msgid "Items and Pricing" msgstr "" -#: erpnext/controllers/accounts_controller.py:4198 +#: erpnext/controllers/accounts_controller.py:4208 msgid "Items cannot be updated as Subcontracting Inward Order(s) exist against this Subcontracted Sales Order." msgstr "" -#: erpnext/controllers/accounts_controller.py:4191 +#: erpnext/controllers/accounts_controller.py:4201 msgid "Items cannot be updated as Subcontracting Order is created against the Purchase Order {0}." msgstr "" @@ -26292,7 +26302,7 @@ msgstr "" msgid "Items to Be Repost" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1677 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1682 msgid "Items to Manufacture are required to pull the Raw Materials associated with it." msgstr "" @@ -26868,7 +26878,7 @@ msgstr "" #: erpnext/buying/doctype/purchase_order_item/purchase_order_item.json #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/bom_creator/bom_creator.json -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py:106 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:123 #: erpnext/stock/doctype/item/item.json #: erpnext/stock/report/item_prices/item_prices.py:56 msgid "Last Purchase Rate" @@ -27140,6 +27150,11 @@ msgstr "" msgid "Ledgers" msgstr "" +#. Label of the vouchers_posted (Int) field in DocType 'Repost Item Valuation' +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +msgid "Ledgers Posted" +msgstr "" + #. Label of the left_child (Link) field in DocType 'Bisect Nodes' #: erpnext/accounts/doctype/bisect_nodes/bisect_nodes.json msgid "Left Child" @@ -27170,8 +27185,8 @@ msgstr "" msgid "Legend" msgstr "" -#. Label of the length (Int) field in DocType 'Shipment Parcel' -#. Label of the length (Int) field in DocType 'Shipment Parcel Template' +#. Label of the length (Float) field in DocType 'Shipment Parcel' +#. Label of the length (Float) field in DocType 'Shipment Parcel Template' #: erpnext/stock/doctype/shipment_parcel/shipment_parcel.json #: erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json msgid "Length (cm)" @@ -27286,7 +27301,7 @@ msgstr "" msgid "Link to Material Request" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:451 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:452 #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.js:80 msgid "Link to Material Requests" msgstr "" @@ -27924,7 +27939,7 @@ 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/job_card/job_card.js:544 #: erpnext/manufacturing/doctype/work_order/work_order.js:832 #: erpnext/manufacturing/doctype/work_order/work_order.js:866 #: erpnext/setup/doctype/vehicle/vehicle.json @@ -27984,12 +27999,12 @@ msgstr "" msgid "Make Serial No / Batch from Work Order" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:101 +#: erpnext/manufacturing/doctype/job_card/job_card.js:109 #: erpnext/stock/doctype/purchase_receipt/purchase_receipt.js:256 msgid "Make Stock Entry" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:410 +#: erpnext/manufacturing/doctype/job_card/job_card.js:418 msgid "Make Subcontracting PO" msgstr "" @@ -28193,7 +28208,7 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json #: erpnext/buying/doctype/purchase_order_item/purchase_order_item.json #: erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py:70 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:110 #: erpnext/stock/doctype/item/item.json #: erpnext/stock/doctype/item_manufacturer/item_manufacturer.json #: erpnext/stock/doctype/manufacturer/manufacturer.json @@ -28223,7 +28238,7 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json #: erpnext/buying/doctype/purchase_order_item/purchase_order_item.json #: erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py:76 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:113 #: erpnext/stock/doctype/item_manufacturer/item_manufacturer.json #: erpnext/stock/doctype/material_request_item/material_request_item.json #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -28257,7 +28272,7 @@ msgstr "" #: erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json #: erpnext/manufacturing/workspace/manufacturing/manufacturing.json #: erpnext/selling/doctype/sales_order/sales_order_dashboard.py:29 -#: erpnext/setup/doctype/company/company.json erpnext/setup/install.py:363 +#: erpnext/setup/doctype/company/company.json erpnext/setup/install.py:364 #: erpnext/setup/setup_wizard/data/industry_type.txt:31 #: erpnext/stock/doctype/batch/batch.json erpnext/stock/doctype/item/item.json #: erpnext/stock/doctype/item_lead_time/item_lead_time.json @@ -28606,14 +28621,14 @@ 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:360 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:361 #: 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 #: erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js:33 #: erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py:184 #: erpnext/buying/workspace/buying/buying.json -#: erpnext/manufacturing/doctype/job_card/job_card.js:160 +#: erpnext/manufacturing/doctype/job_card/job_card.js:168 #: erpnext/manufacturing/doctype/production_plan/production_plan.js:159 #: erpnext/manufacturing/doctype/production_plan/production_plan.json #: erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -28728,7 +28743,7 @@ msgstr "" msgid "Material Request used to make this Stock Entry" msgstr "" -#: erpnext/controllers/subcontracting_controller.py:1337 +#: erpnext/controllers/subcontracting_controller.py:1343 msgid "Material Request {0} is cancelled or stopped" msgstr "" @@ -28782,7 +28797,7 @@ msgstr "" #. Option for the 'Purpose' (Select) field in DocType 'Pick List' #. Option for the 'Purpose' (Select) field in DocType 'Stock Entry' #. Option for the 'Purpose' (Select) field in DocType 'Stock Entry Type' -#: erpnext/manufacturing/doctype/job_card/job_card.js:174 +#: erpnext/manufacturing/doctype/job_card/job_card.js:182 #: erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json #: erpnext/setup/setup_wizard/operations/install_fixtures.py:83 #: erpnext/stock/doctype/item/item.json @@ -28847,7 +28862,7 @@ msgstr "" msgid "Materials To Be Transferred" msgstr "" -#: erpnext/controllers/subcontracting_controller.py:1570 +#: erpnext/controllers/subcontracting_controller.py:1576 msgid "Materials are already received against the {0} {1}" msgstr "" @@ -28943,6 +28958,11 @@ msgstr "" msgid "Maximum Payment Amount" msgstr "" +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:82 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:151 +msgid "Maximum Producible Items" +msgstr "" + #: erpnext/stock/doctype/stock_entry/stock_entry.py:3870 msgid "Maximum Samples - {0} can be retained for Batch {1} and Item {2}." msgstr "" @@ -29002,7 +29022,7 @@ msgstr "" msgid "Megawatt" msgstr "" -#: erpnext/stock/stock_ledger.py:2019 +#: erpnext/stock/stock_ledger.py:2066 msgid "Mention Valuation Rate in the Item master." msgstr "" @@ -29375,7 +29395,7 @@ msgstr "" #: erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py:97 #: erpnext/accounts/doctype/pos_profile/pos_profile.py:200 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:597 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:596 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2422 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3030 #: erpnext/assets/doctype/asset_category/asset_category.py:116 @@ -29415,7 +29435,7 @@ msgstr "" msgid "Missing Item" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:443 +#: erpnext/setup/doctype/employee/employee.py:567 msgid "Missing Parameter" msgstr "" @@ -29439,7 +29459,7 @@ msgstr "" msgid "Missing required filter: {0}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1183 +#: erpnext/manufacturing/doctype/bom/bom.py:1185 #: erpnext/manufacturing/doctype/work_order/work_order.py:1476 msgid "Missing value" msgstr "" @@ -29721,7 +29741,7 @@ msgstr "" #: erpnext/manufacturing/doctype/work_order/work_order.py:1423 #: erpnext/setup/doctype/uom/uom.json #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:267 -#: erpnext/utilities/transaction_base.py:567 +#: erpnext/utilities/transaction_base.py:568 msgid "Must be Whole Number" msgstr "" @@ -30426,7 +30446,7 @@ msgstr "" msgid "No Item with Serial No {0}" msgstr "" -#: erpnext/controllers/subcontracting_controller.py:1488 +#: erpnext/controllers/subcontracting_controller.py:1494 msgid "No Items selected for transfer." msgstr "" @@ -30461,7 +30481,7 @@ msgstr "" msgid "No Permission" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:787 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:792 msgid "No Purchase Orders were created" msgstr "" @@ -30470,7 +30490,7 @@ msgstr "" msgid "No Records for these settings." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:337 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:336 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1105 msgid "No Remarks" msgstr "" @@ -30515,12 +30535,12 @@ msgstr "" msgid "No Unreconciled Payments found for this party" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:784 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:789 #: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.py:249 msgid "No Work Orders were created" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:829 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:833 #: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:860 msgid "No accounting entries for the following warehouses" msgstr "" @@ -30569,7 +30589,7 @@ msgstr "" msgid "No employee was scheduled for call popup" msgstr "" -#: erpnext/controllers/subcontracting_controller.py:1379 +#: erpnext/controllers/subcontracting_controller.py:1385 msgid "No item available for transfer." msgstr "" @@ -30594,7 +30614,7 @@ msgstr "" msgid "No matches occurred via auto reconciliation" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1036 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1041 msgid "No material request created" msgstr "" @@ -30627,6 +30647,12 @@ msgstr "" msgid "No of Interactions" msgstr "" +#. Label of the total_reposting_count (Int) field in DocType 'Repost Item +#. Valuation' +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +msgid "No of Items to Repost" +msgstr "" + #. Label of the no_of_months_exp (Int) field in DocType 'Item' #: erpnext/stock/doctype/item/item.json msgid "No of Months (Expense)" @@ -30806,7 +30832,7 @@ msgstr "" msgid "Non Profit" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1590 +#: erpnext/manufacturing/doctype/bom/bom.py:1593 msgid "Non stock items" msgstr "" @@ -31650,7 +31676,7 @@ msgstr "" msgid "Opening Entry can not be created after Period Closing Voucher is created." msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:286 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:299 msgid "Opening Invoice Creation In Progress" msgstr "" @@ -31669,7 +31695,7 @@ msgstr "" msgid "Opening Invoice Creation Tool Item" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:100 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:101 msgid "Opening Invoice Item" msgstr "" @@ -31687,7 +31713,7 @@ msgstr "" msgid "Opening Invoices" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js:140 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js:142 msgid "Opening Invoices Summary" msgstr "" @@ -31767,7 +31793,7 @@ msgstr "" msgid "Operating Cost Per BOM Quantity" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1677 +#: erpnext/manufacturing/doctype/bom/bom.py:1680 msgid "Operating Cost as per Work Order / BOM" msgstr "" @@ -31858,7 +31884,7 @@ msgstr "" msgid "Operation time does not depend on quantity to produce" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:578 +#: erpnext/manufacturing/doctype/job_card/job_card.js:586 msgid "Operation {0} added multiple times in the work order {1}" msgstr "" @@ -31892,7 +31918,7 @@ msgstr "" msgid "Operations Routing" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1192 +#: erpnext/manufacturing/doctype/bom/bom.py:1194 msgid "Operations cannot be left blank" msgstr "" @@ -31941,7 +31967,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:384 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:385 #: 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 @@ -32457,7 +32483,7 @@ msgstr "" msgid "Over Billing Allowance (%)" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1317 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1321 msgid "Over Billing Allowance exceeded for Purchase Receipt Item {0} ({1}) by {2}%" msgstr "" @@ -32560,7 +32586,7 @@ msgstr "" msgid "Overlap in scoring between {0} and {1}" msgstr "" -#: erpnext/accounts/doctype/shipping_rule/shipping_rule.py:199 +#: erpnext/accounts/doctype/shipping_rule/shipping_rule.py:201 msgid "Overlapping conditions found between:" msgstr "" @@ -33098,7 +33124,7 @@ msgstr "" msgid "Paid To Account Type" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:327 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:326 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1151 msgid "Paid amount + Write Off Amount can not be greater than Grand Total" msgstr "" @@ -33302,7 +33328,7 @@ msgstr "" msgid "Parsed file is not in valid MT940 format or contains no transactions." msgstr "" -#: erpnext/edi/doctype/code_list/code_list_import.py:39 +#: erpnext/edi/doctype/code_list/code_list_import.py:45 msgid "Parsing Error" msgstr "" @@ -33475,8 +33501,6 @@ msgstr "" #. Label of the party (Dynamic Link) field in DocType 'Journal Entry Account' #. Label of the party (Dynamic Link) field in DocType 'Journal Entry Template #. Account' -#. Label of the party (Dynamic Link) field in DocType 'Opening Invoice Creation -#. Tool Item' #. Label of the party (Dynamic Link) field in DocType 'Payment Entry' #. Label of the party (Dynamic Link) field in DocType 'Payment Ledger Entry' #. Label of the party (Dynamic Link) field in DocType 'Payment Reconciliation' @@ -33495,7 +33519,6 @@ msgstr "" #: erpnext/accounts/doctype/gl_entry/gl_entry.json #: erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json #: erpnext/accounts/doctype/journal_entry_template_account/journal_entry_template_account.json -#: erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json #: erpnext/accounts/doctype/payment_entry/payment_entry.json #: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json #: erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -33567,7 +33590,7 @@ msgstr "" msgid "Party Account No. (Bank Statement)" msgstr "" -#: erpnext/controllers/accounts_controller.py:2452 +#: erpnext/controllers/accounts_controller.py:2462 msgid "Party Account {0} currency ({1}) and document currency ({2}) should be same" msgstr "" @@ -33595,6 +33618,12 @@ msgstr "" msgid "Party IBAN (Bank Statement)" msgstr "" +#. Label of the party (Dynamic Link) field in DocType 'Opening Invoice Creation +#. Tool Item' +#: erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json +msgid "Party ID" +msgstr "" + #. Label of the section_break_7 (Section Break) field in DocType 'Pricing Rule' #. Label of the section_break_8 (Section Break) field in DocType 'Promotional #. Scheme' @@ -33617,10 +33646,13 @@ msgstr "" msgid "Party Mismatch" msgstr "" +#. Label of the party_name (Data) field in DocType 'Opening Invoice Creation +#. Tool Item' #. Label of the party_name (Data) field in DocType 'Payment Entry' #. Label of the party_name (Data) field in DocType 'Payment Request' #. Label of the party_name (Dynamic Link) field in DocType 'Contract' #. Label of the party (Dynamic Link) field in DocType 'Party Specific Item' +#: erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json #: erpnext/accounts/doctype/payment_entry/payment_entry.json #: erpnext/accounts/doctype/payment_request/payment_request.json #: erpnext/accounts/report/general_ledger/general_ledger.js:110 @@ -33780,7 +33812,7 @@ msgstr "" msgid "Pause" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:255 +#: erpnext/manufacturing/doctype/job_card/job_card.js:263 msgid "Pause Job" msgstr "" @@ -34274,7 +34306,7 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json #: erpnext/buying/doctype/purchase_order/purchase_order.json -#: erpnext/controllers/accounts_controller.py:2732 +#: erpnext/controllers/accounts_controller.py:2742 #: erpnext/selling/doctype/quotation/quotation.json #: erpnext/selling/doctype/sales_order/sales_order.json msgid "Payment Schedule" @@ -34902,7 +34934,7 @@ msgstr "" #. Label of the phone_no (Data) field in DocType 'Company' #. Label of the phone_no (Data) field in DocType 'Warehouse' -#: erpnext/public/js/print.js:77 erpnext/setup/doctype/company/company.json +#: erpnext/public/js/print.js:79 erpnext/setup/doctype/company/company.json #: erpnext/stock/doctype/warehouse/warehouse.json msgid "Phone No" msgstr "" @@ -35278,7 +35310,7 @@ msgstr "" msgid "Please Set Priority" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:155 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:166 msgid "Please Set Supplier Group in Buying Settings." msgstr "" @@ -35298,7 +35330,7 @@ msgstr "" msgid "Please add Operations first." msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:210 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:212 msgid "Please add Request for Quotation to the sidebar in Portal Settings." msgstr "" @@ -35306,7 +35338,7 @@ msgstr "" msgid "Please add Root Account for - {0}" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:302 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:315 msgid "Please add a Temporary Opening account in Chart of Accounts" msgstr "" @@ -35372,7 +35404,7 @@ msgstr "" msgid "Please check the 'Enable Serial and Batch No for Item' checkbox in the {0} to make Serial and Batch Bundle for the item." msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:539 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:541 msgid "Please check the error message and take necessary actions to fix the error and then restart the reposting again." msgstr "" @@ -35437,7 +35469,7 @@ msgstr "" msgid "Please delete Product Bundle {0}, before merging {1} into {2}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:556 +#: erpnext/assets/doctype/asset/depreciation.py:559 msgid "Please disable workflow temporarily for Journal Entry {0}" msgstr "" @@ -35473,11 +35505,11 @@ msgstr "" msgid "Please enable {} in {} to allow same item in multiple rows" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:377 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:376 msgid "Please ensure that the {0} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:385 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:384 msgid "Please ensure that the {0} account {1} is a Payable account. You can change the account type to Payable or select a different account." msgstr "" @@ -35543,10 +35575,6 @@ msgstr "" msgid "Please enter Planned Qty for Item {0} at row {1}" msgstr "" -#: erpnext/setup/doctype/employee/employee.js:83 -msgid "Please enter Preferred Contact Email" -msgstr "" - #: erpnext/manufacturing/doctype/work_order/work_order.js:73 msgid "Please enter Production Item first" msgstr "" @@ -35604,7 +35632,7 @@ msgstr "" msgid "Please enter company name first" msgstr "" -#: erpnext/controllers/accounts_controller.py:2958 +#: erpnext/controllers/accounts_controller.py:2968 msgid "Please enter default currency in Company Master" msgstr "" @@ -35624,7 +35652,7 @@ msgstr "" msgid "Please enter quantity for item {0}" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:183 +#: erpnext/setup/doctype/employee/employee.py:297 msgid "Please enter relieving date." msgstr "" @@ -35644,7 +35672,7 @@ msgstr "" msgid "Please enter the phone number first" msgstr "" -#: erpnext/controllers/buying_controller.py:1184 +#: erpnext/controllers/buying_controller.py:1185 msgid "Please enter the {schedule_date}." msgstr "" @@ -35652,7 +35680,7 @@ msgstr "" msgid "Please enter valid Financial Year Start and End Dates" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:219 +#: erpnext/setup/doctype/employee/employee.py:333 msgid "Please enter {0}" msgstr "" @@ -35692,7 +35720,7 @@ msgstr "" msgid "Please import accounts against parent company or enable {} in company master." msgstr "" -#: erpnext/setup/doctype/employee/employee.py:180 +#: erpnext/setup/doctype/employee/employee.py:294 msgid "Please make sure the employees above report to another Active employee." msgstr "" @@ -35847,7 +35875,7 @@ msgstr "" msgid "Please select Posting Date first" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1243 +#: erpnext/manufacturing/doctype/bom/bom.py:1245 msgid "Please select Price List" msgstr "" @@ -35875,11 +35903,11 @@ msgstr "" msgid "Please select Subcontracting Order instead of Purchase Order {0}" msgstr "" -#: erpnext/controllers/accounts_controller.py:2807 +#: erpnext/controllers/accounts_controller.py:2817 msgid "Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1498 +#: erpnext/manufacturing/doctype/bom/bom.py:1500 msgid "Please select a BOM" msgstr "" @@ -36035,7 +36063,7 @@ msgstr "" msgid "Please select rows to create Reposting Entries" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:92 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:93 msgid "Please select the Company" msgstr "" @@ -36079,11 +36107,11 @@ msgstr "" msgid "Please set 'Apply Additional Discount On'" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:783 +#: erpnext/assets/doctype/asset/depreciation.py:786 msgid "Please set 'Asset Depreciation Cost Center' in Company {0}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:781 +#: erpnext/assets/doctype/asset/depreciation.py:784 msgid "Please set 'Gain/Loss Account on Asset Disposal' in Company {0}" msgstr "" @@ -36125,7 +36153,7 @@ msgstr "" msgid "Please set Customer Address to determine if the transaction is an export." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:745 +#: erpnext/assets/doctype/asset/depreciation.py:748 msgid "Please set Depreciation related Accounts in Asset Category {0} or Company {1}" msgstr "" @@ -36143,11 +36171,11 @@ msgstr "" msgid "Please set Fiscal Code for the public administration '%s'" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:731 +#: erpnext/assets/doctype/asset/depreciation.py:734 msgid "Please set Fixed Asset Account in Asset Category {0}" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:594 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:593 msgid "Please set Fixed Asset Account in {} against {}." msgstr "" @@ -36193,7 +36221,7 @@ msgstr "" msgid "Please set a default Holiday List for Company {0}" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:270 +#: erpnext/setup/doctype/employee/employee.py:384 msgid "Please set a default Holiday List for Employee {0} or Company {1}" msgstr "" @@ -36271,7 +36299,7 @@ msgstr "" msgid "Please set filter based on Item or Warehouse" msgstr "" -#: erpnext/controllers/accounts_controller.py:2368 +#: erpnext/controllers/accounts_controller.py:2378 msgid "Please set one of the following:" msgstr "" @@ -36287,7 +36315,7 @@ msgstr "" msgid "Please set the Customer Address" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:170 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:182 msgid "Please set the Default Cost Center in {0} company." msgstr "" @@ -36346,7 +36374,7 @@ msgstr "" msgid "Please setup and enable a group account with the Account Type - {0} for the company {1}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:354 +#: erpnext/assets/doctype/asset/depreciation.py:357 msgid "Please share this email with your support team so that they can find and fix the issue." msgstr "" @@ -36360,7 +36388,7 @@ msgstr "" msgid "Please specify Company to proceed" msgstr "" -#: erpnext/controllers/accounts_controller.py:3189 +#: erpnext/controllers/accounts_controller.py:3199 #: erpnext/public/js/controllers/accounts.js:117 msgid "Please specify a valid Row ID for row {0} in table {1}" msgstr "" @@ -36436,7 +36464,7 @@ msgstr "" msgid "Portal Users" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:406 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:407 msgid "Possible Supplier" msgstr "" @@ -36605,7 +36633,7 @@ msgstr "" msgid "Posting Date Inheritance for Exchange Gain / Loss" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:269 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:270 #: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:142 msgid "Posting Date cannot be future date" msgstr "" @@ -36814,7 +36842,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:266 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:267 #: erpnext/buying/doctype/request_for_quotation/request_for_quotation.json msgid "Preview Email" msgstr "" @@ -37449,7 +37477,7 @@ msgstr "" msgid "Process Loss" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1226 +#: erpnext/manufacturing/doctype/bom/bom.py:1228 msgid "Process Loss Percentage cannot be greater than 100" msgstr "" @@ -37471,7 +37499,7 @@ msgstr "" msgid "Process Loss Qty" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:332 +#: erpnext/manufacturing/doctype/job_card/job_card.js:340 msgid "Process Loss Quantity" msgstr "" @@ -38050,7 +38078,7 @@ msgstr "" msgid "Project wise Stock Tracking " msgstr "" -#: erpnext/controllers/trends.py:421 +#: erpnext/controllers/trends.py:429 msgid "Project-wise data is not available for Quotation" msgstr "" @@ -38328,7 +38356,7 @@ msgstr "" #: erpnext/accounts/doctype/tax_rule/tax_rule.json #: erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json #: erpnext/projects/doctype/project/project_dashboard.py:16 -#: erpnext/setup/doctype/company/company.py:463 erpnext/setup/install.py:377 +#: erpnext/setup/doctype/company/company.py:463 erpnext/setup/install.py:378 #: erpnext/stock/doctype/item/item.json #: erpnext/stock/doctype/item_lead_time/item_lead_time.json #: erpnext/stock/doctype/item_reorder/item_reorder.json @@ -38483,8 +38511,8 @@ msgstr "" msgid "Purchase Invoice cannot be made against an existing asset {0}" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:449 -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:463 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:453 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:467 msgid "Purchase Invoice {0} is already submitted" msgstr "" @@ -38637,7 +38665,7 @@ msgstr "" msgid "Purchase Order already created for all Sales Order items" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:335 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:339 msgid "Purchase Order number required for Item {0}" msgstr "" @@ -38664,7 +38692,7 @@ msgstr "" msgid "Purchase Orders Items Overdue" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:286 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:285 msgid "Purchase Orders are not allowed for {0} due to a scorecard standing of {1}." msgstr "" @@ -38981,8 +39009,8 @@ msgstr "" #: erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.json #: erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py:240 #: erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py:224 -#: erpnext/controllers/trends.py:268 erpnext/controllers/trends.py:280 -#: erpnext/controllers/trends.py:285 +#: erpnext/controllers/trends.py:276 erpnext/controllers/trends.py:288 +#: erpnext/controllers/trends.py:293 #: erpnext/crm/doctype/opportunity_item/opportunity_item.json #: erpnext/manufacturing/doctype/bom/bom.js:1086 #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json @@ -39098,7 +39126,8 @@ msgstr "" msgid "Qty In Stock" msgstr "" -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py:82 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:117 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:174 msgid "Qty Per Unit" msgstr "" @@ -39217,7 +39246,7 @@ msgstr "" msgid "Qty to Fetch" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:304 +#: erpnext/manufacturing/doctype/job_card/job_card.js:312 #: erpnext/manufacturing/doctype/job_card/job_card.py:871 msgid "Qty to Manufacture" msgstr "" @@ -39728,7 +39757,11 @@ msgid "Quantity is required" msgstr "" #: erpnext/stock/dashboard/item_dashboard.js:285 -msgid "Quantity must be greater than zero, and less or equal to {0}" +msgid "Quantity must be greater than zero" +msgstr "" + +#: erpnext/stock/dashboard/item_dashboard.js:290 +msgid "Quantity must be less than or equal to {0}" msgstr "" #: erpnext/manufacturing/doctype/work_order/work_order.js:1037 @@ -39746,16 +39779,12 @@ msgid "Quantity required for Item {0} in row {1}" msgstr "" #: erpnext/manufacturing/doctype/bom/bom.py:678 -#: erpnext/manufacturing/doctype/job_card/job_card.js:385 -#: erpnext/manufacturing/doctype/job_card/job_card.js:455 +#: erpnext/manufacturing/doctype/job_card/job_card.js:393 +#: erpnext/manufacturing/doctype/job_card/job_card.js:463 #: erpnext/manufacturing/doctype/workstation/workstation.js:303 msgid "Quantity should be greater than 0" msgstr "" -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js:21 -msgid "Quantity to Make" -msgstr "" - #: erpnext/manufacturing/doctype/work_order/work_order.js:343 msgid "Quantity to Manufacture" msgstr "" @@ -39768,14 +39797,6 @@ msgstr "" msgid "Quantity to Manufacture must be greater than 0." msgstr "" -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js:24 -msgid "Quantity to Produce" -msgstr "" - -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py:37 -msgid "Quantity to Produce should be greater than zero." -msgstr "" - #: erpnext/public/js/utils/barcode_scanner.js:257 msgid "Quantity to Scan" msgstr "" @@ -39950,7 +39971,7 @@ msgstr "" msgid "RFQ and Purchase Order Settings" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:129 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:131 msgid "RFQs are not allowed for {0} due to a scorecard standing of {1}" msgstr "" @@ -40227,7 +40248,7 @@ msgstr "" msgid "Rate at which this tax is applied" msgstr "" -#: erpnext/controllers/accounts_controller.py:4064 +#: erpnext/controllers/accounts_controller.py:4074 msgid "Rate of '{}' items cannot be changed" msgstr "" @@ -41281,7 +41302,7 @@ msgstr "" msgid "Release Date" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:318 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:317 msgid "Release date must be in the future" msgstr "" @@ -41623,7 +41644,7 @@ msgstr "" msgid "Repost Item Valuation" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:344 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:346 msgid "Repost Item Valuation restarted for selected failed records." msgstr "" @@ -41663,10 +41684,6 @@ msgstr "" msgid "Repost started in the background" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:119 -msgid "Reposting Completed {0}%" -msgstr "" - #. Label of the reposting_data_file (Attach) field in DocType 'Repost Item #. Valuation' #: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -41676,10 +41693,10 @@ msgstr "" #. Label of the reposting_info_section (Section Break) field in DocType 'Repost #. Item Valuation' #: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json -msgid "Reposting Info" +msgid "Reposting Item and Warehouse" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:127 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:131 msgid "Reposting Progress" msgstr "" @@ -41689,12 +41706,30 @@ msgstr "" msgid "Reposting Reference" msgstr "" +#. Label of the vouchers_based_on_item_and_warehouse_section (Section Break) +#. field in DocType 'Repost Item Valuation' +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +msgid "Reposting Vouchers" +msgstr "" + +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:149 +msgid "Reposting Vouchers Progress" +msgstr "" + #: erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py:216 #: erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py:327 msgid "Reposting entries created: {0}" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:103 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:123 +msgid "Reposting for Item-Wh Completed {0}%" +msgstr "" + +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:141 +msgid "Reposting for Vouchers Completed {0}%" +msgstr "" + +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:109 msgid "Reposting has been started in the background." msgstr "" @@ -41781,8 +41816,8 @@ msgstr "" #. Label of a Workspace Sidebar Item #: erpnext/buying/doctype/buying_settings/buying_settings.json #: erpnext/buying/doctype/request_for_quotation/request_for_quotation.json -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:324 -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:426 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:326 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:428 #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.js:88 #: erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json #: erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js:70 @@ -41929,10 +41964,7 @@ msgstr "" #: erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json #: erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json #: erpnext/manufacturing/doctype/work_order_item/work_order_item.json -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py:94 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html:11 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html:21 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py:28 +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:119 #: erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py:58 #: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1057 #: erpnext/manufacturing/report/production_planning_report/production_planning_report.py:426 @@ -42129,7 +42161,7 @@ msgstr "" msgid "Reserved Quantity for Production" msgstr "" -#: erpnext/stock/stock_ledger.py:2287 +#: erpnext/stock/stock_ledger.py:2334 msgid "Reserved Serial No." msgstr "" @@ -42145,13 +42177,13 @@ msgstr "" #: erpnext/stock/doctype/pick_list/pick_list.js:170 #: erpnext/stock/report/reserved_stock/reserved_stock.json #: erpnext/stock/report/stock_balance/stock_balance.py:572 -#: erpnext/stock/stock_ledger.py:2271 +#: erpnext/stock/stock_ledger.py:2318 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:205 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:333 msgid "Reserved Stock" msgstr "" -#: erpnext/stock/stock_ledger.py:2316 +#: erpnext/stock/stock_ledger.py:2363 msgid "Reserved Stock for Batch" msgstr "" @@ -42352,7 +42384,7 @@ msgstr "" msgid "Rest Of The World" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:84 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:90 msgid "Restart" msgstr "" @@ -42417,7 +42449,7 @@ msgstr "" msgid "Resume" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:239 +#: erpnext/manufacturing/doctype/job_card/job_card.js:247 msgid "Resume Job" msgstr "" @@ -43143,7 +43175,7 @@ msgstr "" msgid "Row #{0}: Asset {1} is already sold" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:334 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:333 msgid "Row #{0}: BOM is not specified for subcontracting item {0}" msgstr "" @@ -43179,27 +43211,27 @@ msgstr "" msgid "Row #{0}: Cannot create entry with different taxable AND withholding document links." msgstr "" -#: erpnext/controllers/accounts_controller.py:3767 +#: erpnext/controllers/accounts_controller.py:3777 msgid "Row #{0}: Cannot delete item {1} which has already been billed." msgstr "" -#: erpnext/controllers/accounts_controller.py:3741 +#: erpnext/controllers/accounts_controller.py:3751 msgid "Row #{0}: Cannot delete item {1} which has already been delivered" msgstr "" -#: erpnext/controllers/accounts_controller.py:3760 +#: erpnext/controllers/accounts_controller.py:3770 msgid "Row #{0}: Cannot delete item {1} which has already been received" msgstr "" -#: erpnext/controllers/accounts_controller.py:3747 +#: erpnext/controllers/accounts_controller.py:3757 msgid "Row #{0}: Cannot delete item {1} which has work order assigned to it." msgstr "" -#: erpnext/controllers/accounts_controller.py:3753 +#: erpnext/controllers/accounts_controller.py:3763 msgid "Row #{0}: Cannot delete item {1} which is already ordered against this Sales Order." msgstr "" -#: erpnext/controllers/accounts_controller.py:4074 +#: erpnext/controllers/accounts_controller.py:4084 msgid "Row #{0}: Cannot set Rate if the billed amount is greater than the amount for Item {1}." msgstr "" @@ -43282,7 +43314,7 @@ msgstr "" msgid "Row #{0}: Dates overlapping with other row in group {1}" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:358 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:357 msgid "Row #{0}: Default BOM not found for FG Item {1}" msgstr "" @@ -43306,17 +43338,17 @@ msgstr "" msgid "Row #{0}: Expense account {1} is not valid for Purchase Invoice {2}. Only expense accounts from non-stock items are allowed." msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:363 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:362 #: erpnext/selling/doctype/sales_order/sales_order.py:303 msgid "Row #{0}: Finished Good Item Qty can not be zero" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:345 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:344 #: erpnext/selling/doctype/sales_order/sales_order.py:283 msgid "Row #{0}: Finished Good Item is not specified for service item {1}" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:352 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:351 #: erpnext/selling/doctype/sales_order/sales_order.py:290 msgid "Row #{0}: Finished Good Item {1} must be a sub-contracted item" msgstr "" @@ -43444,11 +43476,11 @@ msgstr "" msgid "Row #{0}: Overconsumption of Customer Provided Item {1} against Work Order {2} is not allowed in the Subcontracting Inward process." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1051 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1056 msgid "Row #{0}: Please select Item Code in Assembly Items" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1054 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1059 msgid "Row #{0}: Please select the BOM No in Assembly Items" msgstr "" @@ -43456,7 +43488,7 @@ msgstr "" msgid "Row #{0}: Please select the Finished Good Item against which this Customer Provided Item will be used." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1048 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1053 msgid "Row #{0}: Please select the Sub Assembly Warehouse" msgstr "" @@ -43596,7 +43628,7 @@ msgstr "" msgid "Row #{0}: Set Supplier for item {1}" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1058 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1063 msgid "Row #{0}: Since 'Track Semi Finished Goods' is enabled, the BOM {1} cannot be used for Sub Assembly Items" msgstr "" @@ -43717,7 +43749,7 @@ msgstr "" msgid "Row #{0}: {1} is not a valid reading field. Please refer to the field description." msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:115 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:126 msgid "Row #{0}: {1} is required to create the Opening {2} Invoices" msgstr "" @@ -43725,7 +43757,7 @@ msgstr "" msgid "Row #{0}: {1} of {2} should be {3}. Please update the {1} or select a different account." msgstr "" -#: erpnext/controllers/accounts_controller.py:3881 +#: erpnext/controllers/accounts_controller.py:3891 msgid "Row #{0}:Quantity for Item {1} cannot be zero." msgstr "" @@ -43761,7 +43793,7 @@ msgstr "" msgid "Row #{idx}: {from_warehouse_field} and {to_warehouse_field} cannot be same." msgstr "" -#: erpnext/controllers/buying_controller.py:1176 +#: erpnext/controllers/buying_controller.py:1177 msgid "Row #{idx}: {schedule_date} cannot be before {transaction_date}." msgstr "" @@ -43769,6 +43801,10 @@ msgstr "" msgid "Row #{}: Currency of {} - {} doesn't matches company currency." msgstr "" +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:108 +msgid "Row #{}: Either Party ID or Party Name is required" +msgstr "" + #: erpnext/assets/doctype/asset/asset.py:421 msgid "Row #{}: Finance Book should not be empty since you're using multiple." msgstr "" @@ -43785,6 +43821,10 @@ msgstr "" msgid "Row #{}: POS Invoice {} is not submitted yet" msgstr "" +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:118 +msgid "Row #{}: Party ID is required" +msgstr "" + #: erpnext/assets/doctype/asset_maintenance/asset_maintenance.py:41 msgid "Row #{}: Please assign task to a member." msgstr "" @@ -43814,7 +43854,7 @@ msgstr "" msgid "Row #{}: {}" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:110 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:121 msgid "Row #{}: {} {} does not exist." msgstr "" @@ -43822,7 +43862,7 @@ msgstr "" msgid "Row #{}: {} {} doesn't belong to Company {}. Please select valid {}." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:444 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:443 msgid "Row No {0}: Warehouse is required. Please set a Default Warehouse for Item {1} and Company {2}" msgstr "" @@ -43888,7 +43928,7 @@ msgstr "" msgid "Row {0}: Conversion Factor is mandatory" msgstr "" -#: erpnext/controllers/accounts_controller.py:3227 +#: erpnext/controllers/accounts_controller.py:3237 msgid "Row {0}: Cost Center {1} does not belong to Company {2}" msgstr "" @@ -43916,7 +43956,7 @@ msgstr "" msgid "Row {0}: Delivery Warehouse cannot be same as Customer Warehouse for Item {1}." msgstr "" -#: erpnext/controllers/accounts_controller.py:2720 +#: erpnext/controllers/accounts_controller.py:2730 msgid "Row {0}: Due Date in the Payment Terms table cannot be before Posting Date" msgstr "" @@ -43941,19 +43981,19 @@ msgstr "" msgid "Row {0}: Expense Account {1} is linked to company {2}. Please select an account belonging to company {3}." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:534 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:533 msgid "Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:491 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:490 msgid "Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:516 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:515 msgid "Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:152 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:154 msgid "Row {0}: For Supplier {1}, Email Address is Required to send an email" msgstr "" @@ -44006,7 +44046,7 @@ msgstr "" msgid "Row {0}: Item {1}'s quantity cannot be higher than the available quantity." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1209 +#: erpnext/manufacturing/doctype/bom/bom.py:1211 msgid "Row {0}: Operation time should be greater than 0 for operation {1}" msgstr "" @@ -44122,7 +44162,7 @@ msgstr "" msgid "Row {0}: The item {1}, quantity must be positive number" msgstr "" -#: erpnext/controllers/accounts_controller.py:3204 +#: erpnext/controllers/accounts_controller.py:3214 msgid "Row {0}: The {3} Account {1} does not belong to the company {2}" msgstr "" @@ -44146,7 +44186,7 @@ msgstr "" msgid "Row {0}: Warehouse {1} is linked to company {2}. Please select a warehouse belonging to company {3}." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1203 +#: erpnext/manufacturing/doctype/bom/bom.py:1205 #: erpnext/manufacturing/doctype/work_order/work_order.py:415 msgid "Row {0}: Workstation or Workstation Type is mandatory for an operation {1}" msgstr "" @@ -44179,7 +44219,7 @@ msgstr "" msgid "Row {0}: {2} Item {1} does not exist in {2} {3}" msgstr "" -#: erpnext/utilities/transaction_base.py:562 +#: erpnext/utilities/transaction_base.py:563 msgid "Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}." msgstr "" @@ -44209,7 +44249,7 @@ msgstr "" msgid "Rows with Same Account heads will be merged on Ledger" msgstr "" -#: erpnext/controllers/accounts_controller.py:2731 +#: erpnext/controllers/accounts_controller.py:2741 msgid "Rows with duplicate due dates in other rows were found: {0}" msgstr "" @@ -44391,7 +44431,7 @@ msgstr "" #: erpnext/setup/doctype/company/company.py:649 #: erpnext/setup/doctype/company/company_dashboard.py:9 #: erpnext/setup/doctype/sales_person/sales_person_dashboard.py:12 -#: erpnext/setup/install.py:372 +#: erpnext/setup/install.py:373 #: erpnext/setup/setup_wizard/operations/install_fixtures.py:297 #: erpnext/stock/doctype/item/item.json #: erpnext/stock/doctype/pick_list/pick_list_dashboard.py:16 @@ -45215,7 +45255,7 @@ msgstr "" msgid "Same item cannot be entered multiple times." msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:121 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:123 msgid "Same supplier has been entered multiple times" msgstr "" @@ -45505,7 +45545,7 @@ msgstr "" msgid "Scrap Warehouse" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:384 +#: erpnext/assets/doctype/asset/depreciation.py:387 msgid "Scrap date cannot be before purchase date" msgstr "" @@ -45653,11 +45693,11 @@ msgstr "" msgid "Select Company" msgstr "" -#: erpnext/public/js/print.js:113 +#: erpnext/public/js/print.js:115 msgid "Select Company Address" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:533 +#: erpnext/manufacturing/doctype/job_card/job_card.js:541 msgid "Select Corrective Operation" msgstr "" @@ -45667,11 +45707,11 @@ msgstr "" msgid "Select Customers By" msgstr "" -#: erpnext/setup/doctype/employee/employee.js:120 +#: erpnext/setup/doctype/employee/employee.js:160 msgid "Select Date of Birth. This will validate Employees age and prevent hiring of under-age staff." msgstr "" -#: erpnext/setup/doctype/employee/employee.js:127 +#: erpnext/setup/doctype/employee/employee.js:167 msgid "Select Date of joining. It will have impact on the first salary calculation, Leave allocation on pro-rata bases." msgstr "" @@ -45693,7 +45733,7 @@ msgstr "" msgid "Select Dispatch Address " msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:221 +#: erpnext/manufacturing/doctype/job_card/job_card.js:229 msgid "Select Employees" msgstr "" @@ -45752,7 +45792,7 @@ msgstr "" msgid "Select Payment Schedule" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:410 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:411 msgid "Select Possible Supplier" msgstr "" @@ -45815,7 +45855,7 @@ msgstr "" msgid "Select a Company" msgstr "" -#: erpnext/setup/doctype/employee/employee.js:115 +#: erpnext/setup/doctype/employee/employee.js:155 msgid "Select a Company this Employee belongs to." msgstr "" @@ -45869,7 +45909,7 @@ msgstr "" msgid "Select company name first." msgstr "" -#: erpnext/controllers/accounts_controller.py:2979 +#: erpnext/controllers/accounts_controller.py:2989 msgid "Select finance book for the item {0} at row {1}" msgstr "" @@ -46449,7 +46489,7 @@ msgstr "" msgid "Serial Nos are created successfully" msgstr "" -#: erpnext/stock/stock_ledger.py:2277 +#: erpnext/stock/stock_ledger.py:2324 msgid "Serial Nos are reserved in Stock Reservation Entries, you need to unreserve them before proceeding." msgstr "" @@ -46744,6 +46784,7 @@ msgstr "" #: erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json #: erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json #: erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +#: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py:405 msgid "Service End Date" msgstr "" @@ -46887,6 +46928,7 @@ msgstr "" #: erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json #: erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json #: erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +#: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py:397 msgid "Service Start Date" msgstr "" @@ -46945,8 +46987,8 @@ msgstr "" msgid "Set Delivery Warehouse" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:404 -#: erpnext/manufacturing/doctype/job_card/job_card.js:473 +#: erpnext/manufacturing/doctype/job_card/job_card.js:412 +#: erpnext/manufacturing/doctype/job_card/job_card.js:481 msgid "Set Finished Good Quantity" msgstr "" @@ -47246,7 +47288,7 @@ msgstr "" msgid "Setting up company" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1182 +#: erpnext/manufacturing/doctype/bom/bom.py:1184 #: erpnext/manufacturing/doctype/work_order/work_order.py:1475 msgid "Setting {0} is required" msgstr "" @@ -47471,10 +47513,13 @@ msgstr "" #. Label of the shipping_address_display (Text Editor) field in DocType #. 'Purchase Order' #. Label of the shipping_address_display (Text Editor) field in DocType +#. 'Request for Quotation' +#. Label of the shipping_address_display (Text Editor) field in DocType #. 'Supplier Quotation' #. Label of the shipping_address_display (Text Editor) field in DocType #. 'Subcontracting Order' #: erpnext/buying/doctype/purchase_order/purchase_order.json +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.json #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.json #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json msgid "Shipping Address Details" @@ -47829,9 +47874,8 @@ msgstr "" msgid "Show Warehouse-wise Stock" msgstr "" -#: erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js:28 -#: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js:19 -msgid "Show exploded view" +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js:26 +msgid "Show availability of exploded items" msgstr "" #: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.js:88 @@ -48061,7 +48105,7 @@ msgstr "" msgid "Solvency Ratios" msgstr "" -#: erpnext/controllers/accounts_controller.py:4332 +#: erpnext/controllers/accounts_controller.py:4344 msgid "Some required Company details are missing. You don't have permission to update them. Please contact your System Manager." msgstr "" @@ -48191,7 +48235,7 @@ msgstr "" msgid "Source and target warehouse cannot be same for row {0}" msgstr "" -#: erpnext/stock/dashboard/item_dashboard.js:290 +#: erpnext/stock/dashboard/item_dashboard.js:295 msgid "Source and target warehouse must be different" msgstr "" @@ -48344,7 +48388,7 @@ msgstr "" #: erpnext/setup/setup_wizard/operations/defaults_setup.py:70 #: erpnext/setup/setup_wizard/operations/install_fixtures.py:485 -#: erpnext/tests/utils.py:297 +#: erpnext/tests/utils.py:316 msgid "Standard Buying" msgstr "" @@ -48358,8 +48402,8 @@ msgstr "" #: erpnext/setup/setup_wizard/operations/defaults_setup.py:70 #: erpnext/setup/setup_wizard/operations/install_fixtures.py:493 -#: erpnext/stock/doctype/item/item.py:267 erpnext/tests/utils.py:305 -#: erpnext/tests/utils.py:2494 +#: erpnext/stock/doctype/item/item.py:267 erpnext/tests/utils.py:324 +#: erpnext/tests/utils.py:2514 msgid "Standard Selling" msgstr "" @@ -48415,7 +48459,7 @@ msgstr "" msgid "Start Date should be lower than End Date" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:215 +#: erpnext/manufacturing/doctype/job_card/job_card.js:223 #: erpnext/manufacturing/doctype/workstation/workstation.js:124 msgid "Start Job" msgstr "" @@ -48424,7 +48468,7 @@ msgstr "" msgid "Start Merge" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:99 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js:105 msgid "Start Reposting" msgstr "" @@ -48991,7 +49035,7 @@ msgid "Stock Reservation Entries Cancelled" msgstr "" #: erpnext/controllers/subcontracting_inward_controller.py:1003 -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:2238 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:2243 #: 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" @@ -49365,7 +49409,7 @@ msgstr "" #: erpnext/setup/doctype/company/company.py:384 #: erpnext/setup/setup_wizard/operations/defaults_setup.py:33 #: erpnext/setup/setup_wizard/operations/install_fixtures.py:537 -#: erpnext/stock/doctype/item/item.py:304 erpnext/tests/utils.py:270 +#: erpnext/stock/doctype/item/item.py:304 erpnext/tests/utils.py:289 msgid "Stores" msgstr "" @@ -49423,7 +49467,7 @@ msgstr "" #. Label of the operation (Link) field in DocType 'Job Card Time Log' #. Name of a DocType -#: erpnext/manufacturing/doctype/job_card/job_card.js:349 +#: erpnext/manufacturing/doctype/job_card/job_card.js:357 #: erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json #: erpnext/manufacturing/doctype/sub_operation/sub_operation.json msgid "Sub Operation" @@ -49447,7 +49491,7 @@ msgstr "" msgid "Sub Total" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:621 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:626 msgid "Sub assembly item references are missing. Please fetch the sub assemblies and raw materials again." msgstr "" @@ -49661,7 +49705,7 @@ msgstr "" #. Receipt Supplied Item' #. Label of a Workspace Sidebar Item #: erpnext/buying/doctype/purchase_order/purchase_order.js:399 -#: erpnext/controllers/subcontracting_controller.py:1167 +#: erpnext/controllers/subcontracting_controller.py:1173 #: erpnext/manufacturing/workspace/manufacturing/manufacturing.json #: erpnext/stock/doctype/stock_entry/stock_entry.json #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json @@ -49813,7 +49857,7 @@ msgstr "" msgid "Submit this Work Order for further processing." msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:306 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:308 msgid "Submit your Quotation" msgstr "" @@ -50122,8 +50166,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:184 -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:269 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:185 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:270 #: 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 @@ -50256,7 +50300,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:502 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:503 #: 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 @@ -50455,7 +50499,7 @@ msgstr "" #. Name of a report #. Label of a Link in the Buying Workspace #. Label of a Workspace Sidebar Item -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:154 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:155 #: erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.json #: erpnext/buying/workspace/buying/buying.json #: erpnext/workspace_sidebar/buying.json @@ -50470,7 +50514,7 @@ msgstr "" msgid "Supplier Quotation Item" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:495 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:497 msgid "Supplier Quotation {0} Created" msgstr "" @@ -50559,7 +50603,7 @@ msgstr "" #. Label of the supplier_warehouse (Link) field in DocType 'Purchase Receipt' #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json #: erpnext/buying/doctype/purchase_order/purchase_order.json -#: erpnext/manufacturing/doctype/job_card/job_card.js:89 +#: erpnext/manufacturing/doctype/job_card/job_card.js:97 #: erpnext/stock/doctype/purchase_receipt/purchase_receipt.json msgid "Supplier Warehouse" msgstr "" @@ -51920,7 +51964,7 @@ msgstr "" msgid "The 'From Package No.' field must neither be empty nor it's value less than 1." msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:411 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:413 msgid "The Access to Request for Quotation From Portal is Disabled. To Allow Access, Enable it in Portal Settings." msgstr "" @@ -51961,7 +52005,7 @@ msgstr "" msgid "The Loyalty Program isn't valid for the selected company" msgstr "" -#: erpnext/accounts/doctype/payment_request/payment_request.py:1106 +#: erpnext/accounts/doctype/payment_request/payment_request.py:1107 msgid "The Payment Request {0} is already paid, cannot process payment twice" msgstr "" @@ -52003,7 +52047,7 @@ msgstr "" msgid "The account head under Liability or Equity, in which Profit/Loss will be booked" msgstr "" -#: erpnext/accounts/doctype/payment_request/payment_request.py:1001 +#: erpnext/accounts/doctype/payment_request/payment_request.py:1002 msgid "The allocated amount is greater than the outstanding amount of Payment Request {0}" msgstr "" @@ -52076,7 +52120,7 @@ msgstr "" msgid "The following Purchase Invoices are not submitted:" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:344 +#: erpnext/assets/doctype/asset/depreciation.py:347 msgid "The following assets have failed to automatically post depreciation entries: {0}" msgstr "" @@ -52092,7 +52136,7 @@ msgstr "" msgid "The following deleted attributes exist in Variants but not in the Template. You can either delete the Variants or keep the attribute(s) in template." msgstr "" -#: erpnext/setup/doctype/employee/employee.py:175 +#: erpnext/setup/doctype/employee/employee.py:289 msgid "The following employees are currently still reporting to {0}:" msgstr "" @@ -52123,7 +52167,7 @@ msgstr "" msgid "The holiday on {0} is not between From Date and To Date" msgstr "" -#: erpnext/controllers/buying_controller.py:1243 +#: erpnext/controllers/buying_controller.py:1244 msgid "The item {item} is not marked as {type_of} item. You can enable it as {type_of} item from its Item master." msgstr "" @@ -52131,7 +52175,7 @@ msgstr "" msgid "The items {0} and {1} are present in the following {2} :" msgstr "" -#: erpnext/controllers/buying_controller.py:1236 +#: erpnext/controllers/buying_controller.py:1237 msgid "The items {items} are not marked as {type_of} item. You can enable them as {type_of} item from their Item masters." msgstr "" @@ -52266,7 +52310,7 @@ msgstr "" msgid "The shares don't exist with the {0}" msgstr "" -#: erpnext/stock/stock_ledger.py:803 +#: erpnext/stock/stock_ledger.py:806 msgid "The stock for the item {0} in the {1} warehouse was negative on the {2}. You should create a positive entry {3} before the date {4} and time {5} to post the correct valuation rate. For more details, please read the documentation." msgstr "" @@ -52304,7 +52348,7 @@ msgstr "" msgid "The uploaded file does not appear to be in valid MT940 format." msgstr "" -#: erpnext/edi/doctype/code_list/code_list_import.py:48 +#: erpnext/edi/doctype/code_list/code_list_import.py:54 msgid "The uploaded file does not match the selected Code List." msgstr "" @@ -52542,7 +52586,7 @@ msgstr "" msgid "This is a location where scraped materials are stored." msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:318 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.js:319 msgid "This is a preview of the email to be sent. A PDF of the document will automatically be attached with the email." msgstr "" @@ -52590,7 +52634,7 @@ msgstr "" msgid "This is considered dangerous from accounting point of view." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:540 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:539 msgid "This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice" msgstr "" @@ -52640,7 +52684,7 @@ msgstr "" msgid "This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:458 +#: erpnext/assets/doctype/asset/depreciation.py:461 msgid "This schedule was created when Asset {0} was restored." msgstr "" @@ -52648,7 +52692,7 @@ msgstr "" msgid "This schedule was created when Asset {0} was returned through Sales Invoice {1}." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:417 +#: erpnext/assets/doctype/asset/depreciation.py:420 msgid "This schedule was created when Asset {0} was scrapped." msgstr "" @@ -53198,7 +53242,7 @@ msgid "To include sub-assembly costs and scrap items in Finished Goods on a work msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.py:2247 -#: erpnext/controllers/accounts_controller.py:3237 +#: erpnext/controllers/accounts_controller.py:3247 msgid "To include tax in row {0} in Item rate, taxes in rows {1} must also be included" msgstr "" @@ -53648,6 +53692,11 @@ msgstr "" msgid "Total Landed Cost (Company Currency)" msgstr "" +#. Label of the total_vouchers (Int) field in DocType 'Repost Item Valuation' +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +msgid "Total Ledgers" +msgstr "" + #: erpnext/accounts/report/balance_sheet/balance_sheet.py:219 msgid "Total Liability" msgstr "" @@ -53753,7 +53802,7 @@ msgstr "" msgid "Total Paid Amount" msgstr "" -#: erpnext/controllers/accounts_controller.py:2785 +#: erpnext/controllers/accounts_controller.py:2795 msgid "Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total" msgstr "" @@ -53842,12 +53891,6 @@ msgstr "" msgid "Total Repair Cost" msgstr "" -#. Label of the total_reposting_count (Int) field in DocType 'Repost Item -#. Valuation' -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json -msgid "Total Reposting Count" -msgstr "" - #: erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py:44 msgid "Total Revenue" msgstr "" @@ -54211,7 +54254,7 @@ msgstr "" msgid "Transaction Date" msgstr "" -#: erpnext/setup/doctype/company/company.py:1104 +#: erpnext/setup/doctype/company/company.py:1106 msgid "Transaction Deletion Document {0} has been triggered for company {1}" msgstr "" @@ -54821,7 +54864,7 @@ msgstr "" msgid "UOM Conversion Factor" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1463 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1468 msgid "UOM Conversion factor ({0} -> {1}) not found for item: {2}" msgstr "" @@ -54997,7 +55040,7 @@ msgstr "" msgid "Unit" msgstr "" -#: erpnext/controllers/accounts_controller.py:4064 +#: erpnext/controllers/accounts_controller.py:4074 msgid "Unit Price" msgstr "" @@ -55462,7 +55505,7 @@ msgstr "" msgid "Updating Work Order status" msgstr "" -#: erpnext/public/js/print.js:151 +#: erpnext/public/js/print.js:153 msgid "Updating details." msgstr "" @@ -55701,7 +55744,7 @@ msgstr "" msgid "User has not applied rule on the invoice {0}" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:187 +#: erpnext/setup/doctype/employee/employee.py:301 msgid "User {0} does not exist" msgstr "" @@ -55709,15 +55752,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:205 +#: erpnext/setup/doctype/employee/employee.py:319 msgid "User {0} is already assigned to Employee {1}" msgstr "" -#: erpnext/setup/doctype/employee/employee.py:243 +#: erpnext/setup/doctype/employee/employee.py:357 msgid "User {0}: Removed Employee Self Service role as there is no mapped employee." msgstr "" -#: erpnext/setup/doctype/employee/employee.py:238 +#: erpnext/setup/doctype/employee/employee.py:352 msgid "User {0}: Removed Employee role as there is no mapped employee." msgstr "" @@ -56010,11 +56053,11 @@ msgstr "" msgid "Valuation Rate (In / Out)" msgstr "" -#: erpnext/stock/stock_ledger.py:2022 +#: erpnext/stock/stock_ledger.py:2069 msgid "Valuation Rate Missing" msgstr "" -#: erpnext/stock/stock_ledger.py:2000 +#: erpnext/stock/stock_ledger.py:2047 msgid "Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}." msgstr "" @@ -56046,7 +56089,7 @@ msgid "Valuation rate for the item as per Sales Invoice (Only for Internal Trans msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.py:2271 -#: erpnext/controllers/accounts_controller.py:3261 +#: erpnext/controllers/accounts_controller.py:3271 msgid "Valuation type charges can not be marked as Inclusive" msgstr "" @@ -56820,6 +56863,10 @@ msgstr "" msgid "Warehouse is mandatory" msgstr "" +#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:286 +msgid "Warehouse is required to get producible FG Items" +msgstr "" + #: erpnext/stock/doctype/warehouse/warehouse.py:259 msgid "Warehouse not found against the account {0}" msgstr "" @@ -56959,7 +57006,7 @@ msgstr "" msgid "Warning - Row {0}: Billing Hours are more than Actual Hours" msgstr "" -#: erpnext/stock/stock_ledger.py:813 +#: erpnext/stock/stock_ledger.py:816 msgid "Warning on Negative Stock" msgstr "" @@ -57273,8 +57320,8 @@ msgstr "" msgid "Widowed" msgstr "" -#. Label of the width (Int) field in DocType 'Shipment Parcel' -#. Label of the width (Int) field in DocType 'Shipment Parcel Template' +#. Label of the width (Float) field in DocType 'Shipment Parcel' +#. Label of the width (Float) field in DocType 'Shipment Parcel Template' #: erpnext/stock/doctype/shipment_parcel/shipment_parcel.json #: erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json msgid "Width (cm)" @@ -57819,7 +57866,7 @@ msgstr "" msgid "You are importing data for the code list:" msgstr "" -#: erpnext/controllers/accounts_controller.py:3861 +#: erpnext/controllers/accounts_controller.py:3871 msgid "You are not allowed to update as per the conditions set in {} Workflow." msgstr "" @@ -57960,7 +58007,7 @@ msgstr "" msgid "You do not have permission to edit this document" msgstr "" -#: erpnext/controllers/accounts_controller.py:3837 +#: erpnext/controllers/accounts_controller.py:3847 msgid "You do not have permissions to {} items in a {}." msgstr "" @@ -57972,7 +58019,7 @@ msgstr "" msgid "You don't have enough points to redeem." msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:273 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:286 msgid "You had {} errors while creating opening invoices. Check {} for more details" msgstr "" @@ -58012,7 +58059,7 @@ msgstr "" msgid "You need to cancel POS Closing Entry {} to be able to cancel this document." msgstr "" -#: erpnext/controllers/accounts_controller.py:3212 +#: erpnext/controllers/accounts_controller.py:3222 msgid "You selected the account group {1} as {2} Account in row {0}. Please select a single account." msgstr "" @@ -58080,7 +58127,7 @@ msgstr "" msgid "`Allow Negative rates for Items`" msgstr "" -#: erpnext/stock/stock_ledger.py:2014 +#: erpnext/stock/stock_ledger.py:2061 msgid "after" msgstr "" @@ -58120,7 +58167,7 @@ msgstr "" msgid "cannot be greater than 100" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:334 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:333 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1102 msgid "dated {0}" msgstr "" @@ -58270,7 +58317,7 @@ msgstr "" msgid "per hour" msgstr "" -#: erpnext/stock/stock_ledger.py:2015 +#: erpnext/stock/stock_ledger.py:2062 msgid "performing either one below:" msgstr "" @@ -58404,7 +58451,7 @@ msgstr "" msgid "{0} {1} has submitted Assets. Remove Item {2} from table to continue." msgstr "" -#: erpnext/controllers/accounts_controller.py:2367 +#: erpnext/controllers/accounts_controller.py:2377 msgid "{0} Account not found against Customer {1}." msgstr "" @@ -58432,7 +58479,7 @@ msgstr "" msgid "{0} Number {1} is already used in {2} {3}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1635 +#: erpnext/manufacturing/doctype/bom/bom.py:1638 msgid "{0} Operating Cost for operation {1}" msgstr "" @@ -58460,7 +58507,7 @@ msgstr "" msgid "{0} account is not of type {1}" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:515 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:519 msgid "{0} account not found while submitting purchase receipt" msgstr "" @@ -58493,6 +58540,10 @@ msgstr "" msgid "{0} asset cannot be transferred" msgstr "" +#: erpnext/controllers/trends.py:60 +msgid "{0} can be either {1} or {2}." +msgstr "" + #: erpnext/accounts/doctype/pricing_rule/pricing_rule.py:279 msgid "{0} can not be negative" msgstr "" @@ -58509,8 +58560,8 @@ msgstr "" msgid "{0} cannot be zero" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:918 -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1034 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:923 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1039 #: erpnext/stock/doctype/pick_list/pick_list.py:1297 #: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.py:322 msgid "{0} created" @@ -58524,11 +58575,11 @@ msgstr "" msgid "{0} currency must be same as company's default currency. Please select another account." msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:295 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:294 msgid "{0} currently has a {1} Supplier Scorecard standing, and Purchase Orders to this supplier should be issued with caution." msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:137 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:139 msgid "{0} currently has a {1} Supplier Scorecard standing, and RFQs to this supplier should be issued with caution." msgstr "" @@ -58570,7 +58621,7 @@ msgstr "" msgid "{0} hours" msgstr "" -#: erpnext/controllers/accounts_controller.py:2725 +#: erpnext/controllers/accounts_controller.py:2735 msgid "{0} in row {1}" msgstr "" @@ -58613,7 +58664,7 @@ msgstr "" msgid "{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}" msgstr "" -#: erpnext/controllers/accounts_controller.py:3169 +#: erpnext/controllers/accounts_controller.py:3179 msgid "{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}." msgstr "" @@ -58729,16 +58780,16 @@ msgstr "" msgid "{0} units of {1} are required in {2} with the inventory dimension: {3} on {4} {5} for {6} to complete the transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:1686 erpnext/stock/stock_ledger.py:2163 -#: erpnext/stock/stock_ledger.py:2177 +#: erpnext/stock/stock_ledger.py:1714 erpnext/stock/stock_ledger.py:2210 +#: erpnext/stock/stock_ledger.py:2224 msgid "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:2264 erpnext/stock/stock_ledger.py:2309 +#: erpnext/stock/stock_ledger.py:2311 erpnext/stock/stock_ledger.py:2356 msgid "{0} units of {1} needed in {2} on {3} {4} to complete this transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:1680 +#: erpnext/stock/stock_ledger.py:1708 msgid "{0} units of {1} needed in {2} to complete this transaction." msgstr "" @@ -58804,7 +58855,7 @@ msgstr "" msgid "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts." msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:435 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:434 #: erpnext/selling/doctype/sales_order/sales_order.py:598 #: erpnext/stock/doctype/material_request/material_request.py:255 msgid "{0} {1} has been modified. Please refresh." @@ -58827,7 +58878,7 @@ msgid "{0} {1} is associated with {2}, but Party Account is {3}" msgstr "" #: erpnext/controllers/selling_controller.py:495 -#: erpnext/controllers/subcontracting_controller.py:1167 +#: erpnext/controllers/subcontracting_controller.py:1173 msgid "{0} {1} is cancelled or closed" msgstr "" From f6fa9726f9a5d068e5ce0afdd46cd9519445d45f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 16:43:26 +0000 Subject: [PATCH 050/168] fix(manufacturing): update the qty precision (backport #53874) (#53885) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> fix(manufacturing): update the qty precision (#53874) --- .../manufacturing/doctype/production_plan/production_plan.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 1dfc064b2a4..36364f6740b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1783,8 +1783,10 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d ) sales_order = data.get("sales_order") + qty_precision = frappe.get_precision("Material Request Plan Item", "quantity") for key, details in item_details.items(): + details.qty = flt(details.qty, qty_precision) so_item_details.setdefault(sales_order, frappe._dict()) if key in so_item_details.get(sales_order, {}): so_item_details[sales_order][key]["qty"] = so_item_details[sales_order][key].get( From b2cba0286e8a1672436b999e81516e0539ceb293 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 17:05:36 +0000 Subject: [PATCH 051/168] refactor: setup wizard stages and demo data creation (backport #53866) (#53868) Co-authored-by: diptanilsaha --- erpnext/hooks.py | 1 - erpnext/setup/demo.py | 56 ++++++++------ erpnext/setup/doctype/company/test_company.py | 4 +- erpnext/setup/setup_wizard/setup_wizard.py | 73 +++++++------------ 4 files changed, 64 insertions(+), 70 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index c997443b41d..cc4a08d8b67 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -62,7 +62,6 @@ welcome_email = "erpnext.setup.utils.welcome_email" # setup wizard setup_wizard_requires = "assets/erpnext/js/setup_wizard.js" setup_wizard_stages = "erpnext.setup.setup_wizard.setup_wizard.get_setup_stages" -setup_wizard_complete = "erpnext.setup.setup_wizard.setup_wizard.setup_demo" after_install = "erpnext.setup.install.after_install" diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py index ec48a3e1447..c460b1520c4 100644 --- a/erpnext/setup/demo.py +++ b/erpnext/setup/demo.py @@ -7,7 +7,7 @@ from random import randint import frappe from frappe import _ -from frappe.utils import add_days, getdate +from frappe.utils import add_days, get_url_to_form, getdate from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.utils import get_fiscal_year @@ -16,21 +16,44 @@ from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account -def setup_demo_data(): +def setup_demo_data(company_name): from frappe.utils.telemetry import capture capture("demo_data_creation_started", "erpnext") try: - company = create_demo_company() + frappe.db.savepoint("demo_data") + company = create_demo_company(company_name) process_masters() make_transactions(company) - frappe.cache.delete_keys("bootinfo") - frappe.publish_realtime("demo_data_complete") + capture("demo_data_creation_completed", "erpnext") + frappe.clear_messages() except Exception: - frappe.log_error("Failed to create demo data") + frappe.db.rollback(save_point="demo_data") + error_log = frappe.log_error("Failed to create demo data") + log_demo_data_failed_notification(error_log) capture("demo_data_creation_failed", "erpnext", properties={"exception": frappe.get_traceback()}) - raise - capture("demo_data_creation_completed", "erpnext") + + +def log_demo_data_failed_notification(error_log): + from frappe.core.doctype.role.role import get_users + from frappe.desk.doctype.notification_log.notification_log import make_notification_logs + + frappe.msgprint( + _("Demo data creation failed. Check notifications for more info."), + alert=True, + indicator="red", + realtime=True, + ) + + users = get_users("System Manager") + + notif_log_doc = { + "subject": _("Demo Data creation failed."), + "type": "Alert", + "link": get_url_to_form("Error Log", error_log.name), + } + + make_notification_logs(notif_log_doc, users) @frappe.whitelist() @@ -56,21 +79,8 @@ def clear_demo_data(): ) -def create_demo_company(): - if frappe.flags.in_test: - hash = frappe.generate_hash(length=3) - company_doc = frappe._dict( - { - "company_name": "Test Company" + " " + hash, - "abbr": "TC" + hash, - "default_currency": "INR", - "country": "India", - "chart_of_accounts": "Standard", - } - ) - else: - company = frappe.db.get_all("Company")[0].name - company_doc = frappe.get_doc("Company", company).as_dict() +def create_demo_company(company): + company_doc = frappe.get_doc("Company", company).as_dict() # Make a dummy company new_company = frappe.new_doc("Company") diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index c3fb5dd6ff2..36606e90755 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -199,7 +199,9 @@ class TestCompany(ERPNextTestSuite): def test_demo_data(self): from erpnext.setup.demo import clear_demo_data, setup_demo_data - setup_demo_data() + self.load_test_records("Company") + + setup_demo_data(self.globalTestRecords["Company"][0]["company_name"]) company_name = frappe.db.get_value("Company", {"name": ("like", "%(Demo)")}) self.assertTrue(company_name) diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index 9a49af2b10e..20330d89631 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -10,39 +10,34 @@ from erpnext.setup.setup_wizard.operations import install_fixtures as fixtures def get_setup_stages(args=None): - if frappe.db.sql("select name from tabCompany"): - stages = [ + stages = [ + { + "status": _("Installing presets"), + "fail_msg": _("Failed to install presets"), + "tasks": [{"fn": stage_fixtures, "args": args, "fail_msg": _("Failed to install presets")}], + }, + { + "status": _("Setting up company"), + "fail_msg": _("Failed to setup company"), + "tasks": [{"fn": setup_company, "args": args, "fail_msg": _("Failed to setup company")}], + }, + { + "status": _("Setting defaults"), + "fail_msg": _("Failed to set defaults"), + "tasks": [ + {"fn": setup_defaults, "args": args, "fail_msg": _("Failed to setup defaults")}, + ], + }, + ] + + if args.get("setup_demo"): + stages.append( { - "status": _("Wrapping up"), - "fail_msg": _("Failed to login"), - "tasks": [{"fn": fin, "args": args, "fail_msg": _("Failed to login")}], + "status": _("Creating demo data"), + "fail_msg": _("Failed to create demo data"), + "tasks": [{"fn": setup_demo, "args": args, "fail_msg": _("Failed to create demo data")}], } - ] - else: - stages = [ - { - "status": _("Installing presets"), - "fail_msg": _("Failed to install presets"), - "tasks": [{"fn": stage_fixtures, "args": args, "fail_msg": _("Failed to install presets")}], - }, - { - "status": _("Setting up company"), - "fail_msg": _("Failed to setup company"), - "tasks": [{"fn": setup_company, "args": args, "fail_msg": _("Failed to setup company")}], - }, - { - "status": _("Setting defaults"), - "fail_msg": "Failed to set defaults", - "tasks": [ - {"fn": setup_defaults, "args": args, "fail_msg": _("Failed to setup defaults")}, - ], - }, - { - "status": _("Wrapping up"), - "fail_msg": _("Failed to login"), - "tasks": [{"fn": fin, "args": args, "fail_msg": _("Failed to login")}], - }, - ] + ) return stages @@ -59,19 +54,8 @@ def setup_defaults(args): fixtures.install_defaults(frappe._dict(args)) -def fin(args): - frappe.local.message_log = [] - login_as_first_user(args) - - -def setup_demo(args): - if args.get("setup_demo"): - frappe.enqueue(setup_demo_data, enqueue_after_commit=True, at_front=True) - - -def login_as_first_user(args): - if args.get("email") and hasattr(frappe.local, "login_manager"): - frappe.local.login_manager.login_as(args.get("email")) +def setup_demo(args): # nosemgrep + setup_demo_data(args.get("company_name")) # Only for programmatical use @@ -79,4 +63,3 @@ def setup_complete(args=None): stage_fixtures(args) setup_company(args) setup_defaults(args) - fin(args) From 319ba31b7708fa76365f8dc9616db84330c91048 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 06:59:29 +0000 Subject: [PATCH 052/168] fix(stock): ignore qty validation for pick list (backport #53871) (#53892) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> fix(stock): ignore qty validation for pick list (#53871) --- .../stock/doctype/material_request/test_material_request.py | 4 +++- erpnext/stock/doctype/pick_list/pick_list.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index e72637901b5..c25a6ecd62d 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -1083,7 +1083,9 @@ class TestMaterialRequest(ERPNextTestSuite): pl.locations[0].qty = 2 pl.locations[0].stock_qty = 2 - self.assertRaises(frappe.ValidationError, pl.submit) + + # System should allow picking qty for excess transfer + pl.submit() def test_mr_status_with_partial_and_excess_end_transit(self): material_request = make_material_request( diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index ef80966c2a6..9d01bb28fc3 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -86,6 +86,7 @@ class PickList(TransactionBase): "join_field": "material_request_item", "target_ref_field": "stock_qty", "source_field": "stock_qty", + "validate_qty": False, } ] From 9d46d8151a92e3d098c546504a62fdc8468d3d93 Mon Sep 17 00:00:00 2001 From: Sudharsanan11 Date: Thu, 26 Mar 2026 12:56:59 +0530 Subject: [PATCH 053/168] fix(stock): update company validation for expense account in lcv (cherry picked from commit 913168e8b603c024eb5f4cf131df121d0c144ab3) --- .../stock/doctype/landed_cost_voucher/landed_cost_voucher.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index fc4bc589f9d..4332b7429a6 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -10,6 +10,7 @@ from frappe.query_builder.custom import ConstantColumn from frappe.utils import cint, flt import erpnext +from erpnext import is_perpetual_inventory_enabled from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -175,6 +176,9 @@ class LandedCostVoucher(Document): ) def validate_expense_accounts(self): + if not is_perpetual_inventory_enabled(self.company): + return + for t in self.taxes: company = frappe.get_cached_value("Account", t.expense_account, "company") From ad966468b16e8b009e97880970e8aa4c4b0065d0 Mon Sep 17 00:00:00 2001 From: Sudharsanan11 Date: Thu, 26 Mar 2026 15:04:33 +0530 Subject: [PATCH 054/168] fix(test): enable perpetual inventory (cherry picked from commit 875a2e494769e779d494916979df8001945ef6f4) --- .../test_landed_cost_voucher.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index ee2b2051e8b..26df7f59135 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -180,6 +180,8 @@ class TestLandedCostVoucher(ERPNextTestSuite): self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0) def test_lcv_validates_company(self): + from erpnext import is_perpetual_inventory_enabled + from erpnext.accounts.doctype.account.test_account import create_account from erpnext.stock.doctype.landed_cost_voucher.landed_cost_voucher import ( IncorrectCompanyValidationError, ) @@ -187,6 +189,20 @@ class TestLandedCostVoucher(ERPNextTestSuite): company_a = "_Test Company" company_b = "_Test Company with perpetual inventory" + srbnb = create_account( + account_name="Stock Received But Not Billed", + account_type="Stock Received But Not Billed", + parent_account="Stock Liabilities - _TC", + company=company_a, + account_currency="INR", + ) + + epi = is_perpetual_inventory_enabled(company_a) + company_doc = frappe.get_doc("Company", company_a) + company_doc.enable_perpetual_inventory = 1 + company_doc.stock_received_but_not_billed = srbnb + company_doc.save() + pr = make_purchase_receipt( company=company_a, warehouse="Stores - _TC", @@ -212,6 +228,9 @@ class TestLandedCostVoucher(ERPNextTestSuite): distribute_landed_cost_on_items(lcv) lcv.submit() + frappe.db.set_value("Company", company_a, "enable_perpetual_inventory", epi) + frappe.local.enable_perpetual_inventory = {} + def test_landed_cost_voucher_for_zero_purchase_rate(self): "Test impact of LCV on future stock balances." from erpnext.stock.doctype.item.test_item import make_item From ad3c1e520e3855eeb4ac116c0541151e3570b39d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 08:12:48 +0000 Subject: [PATCH 055/168] fix(stock): add warehouse filter to pick work order raw materials (backport #53748) (#53898) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> fix(stock): add warehouse filter to pick work order raw materials (#53748) --- erpnext/stock/doctype/pick_list/pick_list.py | 40 ++++++++++++++-- .../stock/doctype/pick_list/test_pick_list.py | 47 +++++++++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 9d01bb28fc3..7c31ec7a672 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -523,8 +523,26 @@ class PickList(TransactionBase): self.item_location_map = frappe._dict() from_warehouses = [self.parent_warehouse] if self.parent_warehouse else [] - if self.parent_warehouse: - from_warehouses.extend(get_descendants_of("Warehouse", self.parent_warehouse)) + + if self.work_order: + root_warehouse = frappe.db.get_value( + "Warehouse", {"company": self.company, "parent_warehouse": ["IS", "NOT SET"], "is_group": 1} + ) + + from_warehouses = [root_warehouse] + + if from_warehouses: + from_warehouses.extend(get_descendants_of("Warehouse", from_warehouses[0])) + + item_warehouse_dict = frappe._dict() + if self.work_order: + item_warehouse_list = frappe.get_all( + "Work Order Item", + filters={"parent": self.work_order}, + fields=["item_code", "source_warehouse"], + ) + if item_warehouse_list: + item_warehouse_dict = {item.item_code: item.source_warehouse for item in item_warehouse_list} # Create replica before resetting, to handle empty table on update after submit. locations_replica = self.get("locations") @@ -542,6 +560,13 @@ class PickList(TransactionBase): len_idx = len(self.get("locations")) or 0 for item_doc in items: item_code = item_doc.item_code + priority_warehouses = [] + + if self.work_order and item_warehouse_dict.get(item_code): + source_warehouse = item_warehouse_dict.get(item_code) + priority_warehouses = [source_warehouse] + priority_warehouses.extend(get_descendants_of("Warehouse", source_warehouse)) + from_warehouses = list(dict.fromkeys(priority_warehouses + from_warehouses)) self.item_location_map.setdefault( item_code, @@ -552,6 +577,7 @@ class PickList(TransactionBase): self.company, picked_item_details=picked_items_details.get(item_code), consider_rejected_warehouses=self.consider_rejected_warehouses, + priority_warehouses=priority_warehouses, ), ) @@ -969,6 +995,7 @@ def get_available_item_locations( ignore_validation=False, picked_item_details=None, consider_rejected_warehouses=False, + priority_warehouses=None, ): locations = [] @@ -1009,7 +1036,7 @@ def get_available_item_locations( locations = filter_locations_by_picked_materials(locations, picked_item_details) if locations: - locations = get_locations_based_on_required_qty(locations, required_qty) + locations = get_locations_based_on_required_qty(locations, required_qty, priority_warehouses) if not ignore_validation: validate_picked_materials(item_code, required_qty, locations, picked_item_details) @@ -1017,9 +1044,14 @@ def get_available_item_locations( return locations -def get_locations_based_on_required_qty(locations, required_qty): +def get_locations_based_on_required_qty(locations, required_qty, priority_warehouses): filtered_locations = [] + if priority_warehouses: + priority_locations = [loc for loc in locations if loc.warehouse in priority_warehouses] + fallback_locations = [loc for loc in locations if loc.warehouse not in priority_warehouses] + locations = priority_locations + fallback_locations + for location in locations: if location.qty >= required_qty: location.qty = required_qty diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 283e0207d60..85a45f1686b 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -1050,6 +1050,53 @@ class TestPickList(ERPNextTestSuite): pl = create_pick_list(so.name) self.assertFalse(pl.locations) + def test_pick_list_warehouse_for_work_order(self): + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + from erpnext.manufacturing.doctype.work_order.work_order import create_pick_list, make_work_order + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + # Create Warehouses for Work Order + source_warehouse = create_warehouse("_Test WO Warehouse") + wip_warehouse = create_warehouse("_Test WIP Warehouse", company="_Test Company") + fg_warehouse = create_warehouse("_Test Finished Goods Warehouse", company="_Test Company") + + # Create Finished Good Item + fg_item = make_item("Test Work Order Finished Good Item", properties={"is_stock_item": 1}).name + + # Create Raw Material Item + rm_item = make_item("Test Work Order Raw Material Item", properties={"is_stock_item": 1}).name + + # Create BOM + bom = make_bom(item=fg_item, rate=100, raw_materials=[rm_item]) + + # Create Inward entry for Raw Material + make_stock_entry(item=rm_item, to_warehouse=wip_warehouse, qty=10) + make_stock_entry(item=rm_item, to_warehouse=source_warehouse, qty=10) + + # Create Work Order + wo = make_work_order(item=fg_item, qty=5, bom_no=bom.name, company="_Test Company") + wo.required_items[0].source_warehouse = source_warehouse + wo.fg_warehouse = fg_warehouse + wo.skip_transfer = True + wo.submit() + + # Create Pick List + pl = create_pick_list(wo.name, for_qty=wo.qty) + + # System prioritises the Source Warehouse + self.assertEqual(pl.locations[0].warehouse, source_warehouse) + self.assertEqual(pl.locations[0].item_code, rm_item) + self.assertEqual(pl.locations[0].qty, 5) + + # Create Outward Entry from Source Warehouse + make_stock_entry(item=rm_item, from_warehouse=source_warehouse, qty=10) + pl.set_item_locations() + + # System should pick other available warehouses + self.assertEqual(pl.locations[0].warehouse, wip_warehouse) + self.assertEqual(pl.locations[0].item_code, rm_item) + self.assertEqual(pl.locations[0].qty, 5) + def test_pick_list_validation_for_serial_no(self): warehouse = "_Test Warehouse - _TC" item = make_item( From f01f7e7974149f64bd7622d0752cb5673704354b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:48:25 +0530 Subject: [PATCH 056/168] fix(warehouse_capacity_dashboard): escaping `warehouse`, `item_code` and `company` on `get_data` (backport #53894) (#53900) Co-authored-by: diptanilsaha fix(warehouse_capacity_dashboard): escaping `warehouse`, `item_code` and `company` on `get_data` (#53894) --- erpnext/stock/dashboard/warehouse_capacity_dashboard.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/dashboard/warehouse_capacity_dashboard.py b/erpnext/stock/dashboard/warehouse_capacity_dashboard.py index 75b2951e30b..39701ed3f0d 100644 --- a/erpnext/stock/dashboard/warehouse_capacity_dashboard.py +++ b/erpnext/stock/dashboard/warehouse_capacity_dashboard.py @@ -1,6 +1,6 @@ import frappe from frappe.desk.reportview import build_match_conditions -from frappe.utils import flt, nowdate +from frappe.utils import escape_html, flt, nowdate from erpnext.stock.utils import get_stock_balance @@ -75,6 +75,9 @@ def get_warehouse_capacity_data(filters, start): balance_qty = get_stock_balance(entry.item_code, entry.warehouse, nowdate()) or 0 entry.update( { + "warehouse": escape_html(entry.warehouse), + "item_code": escape_html(entry.item_code), + "company": escape_html(entry.company), "actual_qty": balance_qty, "percent_occupied": flt((flt(balance_qty) / flt(entry.stock_capacity)) * 100, 0), } From 8d2c4da9312c5b326e5818557a1262dff64e4bff Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 30 Mar 2026 13:23:36 +0530 Subject: [PATCH 057/168] fix: item-wh reposting, code cleanup (cherry picked from commit e0ca34ae392b66e440035a56a5201749cd859b59) --- erpnext/stock/stock_ledger.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 838244a7fc2..c9114355a58 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -601,7 +601,7 @@ class update_entries_after: def initialize_reposting(self): self._sles = [] self.distinct_sles = set() - self.distinct_dependant_sle = set() + self.distinct_dependant_item_wh = set() self.prev_sle_dict = frappe._dict({}) def get_item_wh_wise_last_posted_sle(self): @@ -642,20 +642,15 @@ class update_entries_after: if item_wh_key not in self.prev_sle_dict: self.prev_sle_dict[item_wh_key] = get_previous_sle_of_current_voucher(sle) - if ( - sle.dependant_sle_voucher_detail_no - and sle.dependant_sle_voucher_detail_no not in self.distinct_dependant_sle - ): - self._sles.append(sle) - self.distinct_dependant_sle.add(sle.dependant_sle_voucher_detail_no) - self.include_dependant_sle_in_reposting(sle) - continue - self.repost_stock_ledger_entry(sle) # To avoid duplicate reposting of same sle in case of multiple dependant sle self.distinct_sles.add(sle.name) + if sle.dependant_sle_voucher_detail_no: + self.include_dependant_sle_in_reposting(sle) + self.update_item_wh_wise_last_posted_sle(sle) + if i % 1000 == 0: self.update_data_in_repost(len(self._sles), i) @@ -669,16 +664,28 @@ class update_entries_after: ) def include_dependant_sle_in_reposting(self, sle): + repost_dependant_sle = False if sle.voucher_type == "Stock Entry" and is_repack_entry(sle.voucher_no): repack_sles = self.get_sles_for_repack(sle) for repack_sle in repack_sles: + if (repack_sle.item_code, repack_sle.warehouse) in self.distinct_dependant_item_wh: + continue + + repost_dependant_sle = True + self.distinct_dependant_item_wh.add((repack_sle.item_code, repack_sle.warehouse)) self._sles.extend(self.get_future_entries_to_repost(repack_sle)) else: dependant_sles = get_sle_by_voucher_detail_no(sle.dependant_sle_voucher_detail_no) for depend_sle in dependant_sles: + if (depend_sle.item_code, depend_sle.warehouse) in self.distinct_dependant_item_wh: + continue + + repost_dependant_sle = True + self.distinct_dependant_item_wh.add((depend_sle.item_code, depend_sle.warehouse)) self._sles.extend(self.get_future_entries_to_repost(depend_sle)) - self._sles = deque(self.sort_sles(self._sles)) + if repost_dependant_sle: + self._sles = deque(self.sort_sles(self._sles)) def repost_stock_ledger_entry(self, sle): if self.args.item_code != sle.item_code or self.args.warehouse != sle.warehouse: From bfb51326edef822a169ccf88766ddc9a416a2e72 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 30 Mar 2026 13:50:12 +0530 Subject: [PATCH 058/168] fix: purchase invoice missing item (cherry picked from commit af994c1a229ae067c456cc834968a16837b06f9d) --- .../doctype/purchase_order/purchase_order.py | 28 +++++++++++-------- .../purchase_order/test_purchase_order.py | 28 +++++++++++++++++++ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 7e672da22b6..2cb285c14f3 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -802,18 +802,18 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions target.set_payment_schedule() target.credit_to = get_party_account("Supplier", source.supplier, source.company) + def get_billed_qty(po_item_name): + from frappe.query_builder.functions import Sum + + table = frappe.qb.DocType("Purchase Invoice Item") + query = ( + frappe.qb.from_(table) + .select(Sum(table.qty).as_("qty")) + .where((table.docstatus == 1) & (table.po_detail == po_item_name)) + ) + return query.run(pluck="qty")[0] or 0 + def update_item(obj, target, source_parent): - def get_billed_qty(po_item_name): - from frappe.query_builder.functions import Sum - - table = frappe.qb.DocType("Purchase Invoice Item") - query = ( - frappe.qb.from_(table) - .select(Sum(table.qty).as_("qty")) - .where((table.docstatus == 1) & (table.po_detail == po_item_name)) - ) - return query.run(pluck="qty")[0] or 0 - billed_qty = flt(get_billed_qty(obj.name)) target.qty = flt(obj.qty) - billed_qty @@ -853,7 +853,11 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions "wip_composite_asset": "wip_composite_asset", }, "postprocess": update_item, - "condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)) + "condition": lambda doc: ( + doc.base_amount == 0 + or abs(doc.billed_amt) < abs(doc.amount) + or doc.qty > flt(get_billed_qty(doc.name)) + ) and select_item(doc), }, "Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "reset_value": True}, diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index fe4bb12c3db..e6956111ea0 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -1386,6 +1386,34 @@ class TestPurchaseOrder(ERPNextTestSuite): self.assertEqual(pi_2.status, "Paid") self.assertEqual(po.status, "Completed") + def test_purchase_order_over_billing_missing_item(self): + item1 = make_item( + "_Test Item for Overbilling", + ).name + + item2 = make_item( + "_Test Item for Overbilling 2", + ).name + + po = create_purchase_order(qty=10, rate=1000, item_code=item1, do_not_save=1) + po.append("items", {"item_code": item2, "qty": 5, "rate": 20, "warehouse": "_Test Warehouse - _TC"}) + po.taxes = [] + po.insert() + po.submit() + + pi1 = make_pi_from_po(po.name) + pi1.items[0].qty = 8 + pi1.items[0].rate = 1250 + pi1.remove(pi1.items[1]) + pi1.insert() + pi1.submit() + + self.assertEqual(pi1.grand_total, 10000.0) + self.assertTrue(len(pi1.items) == 1) + + pi2 = make_pi_from_po(po.name) + self.assertEqual(len(pi2.items), 2) + def create_po_for_sc_testing(): from erpnext.controllers.tests.test_subcontracting_controller import ( From 4ac6347cc56fc556fd83e3789d1a3cecb30300ad Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:52:17 +0000 Subject: [PATCH 059/168] fix(item_dashboard): escaping `warehouse`, `item_code`, `stock_uom` and `item_name` on `get_data` (backport #53904) (#53914) Co-authored-by: diptanilsaha fix(item_dashboard): escaping `warehouse`, `item_code`, `stock_uom` and `item_name` on `get_data` (#53904) --- erpnext/stock/dashboard/item_dashboard.py | 8 +++++--- erpnext/stock/dashboard/item_dashboard_list.html | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py index d77ed7a6212..5de54c55461 100644 --- a/erpnext/stock/dashboard/item_dashboard.py +++ b/erpnext/stock/dashboard/item_dashboard.py @@ -1,6 +1,6 @@ import frappe from frappe.desk.reportview import build_match_conditions -from frappe.utils import cint, flt +from frappe.utils import cint, escape_html, flt from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( get_sre_reserved_qty_for_items_and_warehouses as get_reserved_stock_details, @@ -70,8 +70,10 @@ def get_data( for item in items: item.update( { - "item_name": frappe.get_cached_value("Item", item.item_code, "item_name"), - "stock_uom": frappe.get_cached_value("Item", item.item_code, "stock_uom"), + "item_code": escape_html(item.item_code), + "item_name": escape_html(frappe.get_cached_value("Item", item.item_code, "item_name")), + "stock_uom": escape_html(frappe.get_cached_value("Item", item.item_code, "stock_uom")), + "warehouse": escape_html(item.warehouse), "disable_quick_entry": frappe.get_cached_value("Item", item.item_code, "has_batch_no") or frappe.get_cached_value("Item", item.item_code, "has_serial_no"), "projected_qty": flt(item.projected_qty, precision), diff --git a/erpnext/stock/dashboard/item_dashboard_list.html b/erpnext/stock/dashboard/item_dashboard_list.html index ae90ff80686..34d51814b2f 100644 --- a/erpnext/stock/dashboard/item_dashboard_list.html +++ b/erpnext/stock/dashboard/item_dashboard_list.html @@ -50,15 +50,15 @@ data-warehouse="{{ d.warehouse }}" data-actual_qty="{{ d.actual_qty }}" data-stock-uom="{{ d.stock_uom }}" - data-item="{{ escape(d.item_code) }}">{{ __("Move") }} + data-item="{{ d.item_code }}">{{ __("Move") }} {% endif %}
{% endif %}
From 8c35a939cb5e9a715402dc0ad4d697c0efeba320 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 12:47:25 +0000 Subject: [PATCH 060/168] fix(opening_invoice_creation_tool): sanitize summary content for dashboard (backport #53917) (#53924) Co-authored-by: diptanilsaha fix(opening_invoice_creation_tool): sanitize summary content for dashboard (#53917) --- .../opening_invoice_creation_tool.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index e1e70e0f6cb..3949e242567 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -5,7 +5,7 @@ import frappe from frappe import _, scrub from frappe.model.document import Document -from frappe.utils import flt, nowdate +from frappe.utils import escape_html, flt, nowdate from frappe.utils.background_jobs import enqueue, is_job_enqueued from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -86,6 +86,11 @@ class OpeningInvoiceCreationTool(Document): ) prepare_invoice_summary(doctype, invoices) + invoices_summary_companies = list(invoices_summary.keys()) + + for company in invoices_summary_companies: + invoices_summary[escape_html(company)] = invoices_summary.pop(company) + return invoices_summary, max_count def validate_company(self): From c6fe5be95a5f5b4bc4a8334ae5f017ea82999d36 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 13 Mar 2026 11:53:08 +0530 Subject: [PATCH 061/168] fix: correct item valuation when "Deduct" is used in Purchase Invoice and Receipt. (cherry picked from commit e68f149d3afa469d92ab6dc20a34ff8981ea4fdd) --- erpnext/controllers/buying_controller.py | 8 ++- .../purchase_receipt/test_purchase_receipt.py | 59 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 67ccc4c7fe4..6383049be9c 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -503,11 +503,15 @@ class BuyingController(SubcontractingController): if d.category not in ["Valuation", "Valuation and Total"]: continue + amount = flt(d.base_tax_amount_after_discount_amount) * ( + -1 if d.get("add_deduct_tax") == "Deduct" else 1 + ) + if d.charge_type == "On Net Total": - total_valuation_amount += flt(d.base_tax_amount_after_discount_amount) + total_valuation_amount += amount tax_accounts.append(d.account_head) else: - total_actual_tax_amount += flt(d.base_tax_amount_after_discount_amount) + total_actual_tax_amount += amount return tax_accounts, total_valuation_amount, total_actual_tax_amount diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 74cdfb38f78..6eba41c3883 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1240,6 +1240,65 @@ class TestPurchaseReceipt(ERPNextTestSuite): pr.cancel() + def test_item_valuation_with_deduct_valuation_and_total_tax(self): + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + supplier_warehouse="Work In Progress - TCP1", + qty=5, + rate=100, + do_not_save=1, + ) + + pr.append( + "taxes", + { + "charge_type": "Actual", + "add_deduct_tax": "Deduct", + "account_head": "_Test Account Shipping Charges - TCP1", + "category": "Valuation and Total", + "cost_center": "Main - TCP1", + "description": "Valuation Discount", + "tax_amount": 20, + }, + ) + + pr.insert() + + self.assertAlmostEqual(pr.items[0].item_tax_amount, -20.0, places=2) + self.assertAlmostEqual(pr.items[0].valuation_rate, 96.0, places=2) + + pr.delete() + + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + supplier_warehouse="Work In Progress - TCP1", + qty=5, + rate=100, + do_not_save=1, + ) + + pr.append( + "taxes", + { + "charge_type": "On Net Total", + "add_deduct_tax": "Deduct", + "account_head": "_Test Account Shipping Charges - TCP1", + "category": "Valuation and Total", + "cost_center": "Main - TCP1", + "description": "Valuation Discount", + "rate": 10, + }, + ) + + pr.insert() + + self.assertAlmostEqual(pr.items[0].item_tax_amount, -50.0, places=2) + self.assertAlmostEqual(pr.items[0].valuation_rate, 90.0, places=2) + + pr.delete() + def test_po_to_pi_and_po_to_pr_worflow_full(self): """Test following behaviour: - Create PO From 3592637b5c88249ff9c0a721eb7d8d3b7df646c6 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 30 Mar 2026 17:58:47 +0530 Subject: [PATCH 062/168] fix(taxes): increase rounding threshold for tax breakup calculations (cherry picked from commit 7f87a5e5c6fb7c283b59484da8e4d9cd798aea95) --- erpnext/controllers/taxes_and_totals.py | 2 +- .../tests/test_item_wise_tax_details.py | 104 ++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 9d98eed668d..fa55aa1daa5 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -522,7 +522,7 @@ class calculate_taxes_and_totals: diff = flt(expected_amount - actual_breakup, 5) # TODO: fix rounding difference issues - if abs(diff) <= 0.5: + if abs(diff) <= 1: detail_row = self.doc._item_wise_tax_details[last_idx] detail_row["amount"] = flt(detail_row["amount"] + diff, 5) diff --git a/erpnext/controllers/tests/test_item_wise_tax_details.py b/erpnext/controllers/tests/test_item_wise_tax_details.py index f6d94c61eca..d4a560284ef 100644 --- a/erpnext/controllers/tests/test_item_wise_tax_details.py +++ b/erpnext/controllers/tests/test_item_wise_tax_details.py @@ -124,3 +124,107 @@ class TestTaxesAndTotals(ERPNextTestSuite): ] self.assertEqual(actual_values, expected_values) + + def test_item_wise_tax_detail_with_multi_currency(self): + """ + For multi-item, multi-currency invoices, item-wise tax breakup should + still reconcile with base tax totals. + """ + doc = frappe.get_doc( + { + "doctype": "Sales Invoice", + "customer": "_Test Customer", + "company": "_Test Company", + "currency": "USD", + "debit_to": "_Test Receivable USD - _TC", + "conversion_rate": 129.99, + "items": [ + { + "item_code": "_Test Item", + "qty": 1, + "rate": 47.41, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + { + "item_code": "_Test Item", + "qty": 2, + "rate": 33.33, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + ], + "taxes": [ + { + "charge_type": "On Net Total", + "account_head": "_Test Account VAT - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "rate": 16, + }, + { + "charge_type": "On Previous Row Amount", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 10, + "row_id": 1, + }, + ], + } + ) + doc.save() + + details_by_tax = {} + for detail in doc.item_wise_tax_details: + bucket = details_by_tax.setdefault(detail.tax_row, {"amount": 0.0, "taxable_amount": 0.0}) + bucket["amount"] += detail.amount + + for tax in doc.taxes: + detail_totals = details_by_tax[tax.name] + self.assertAlmostEqual( + detail_totals["amount"], tax.base_tax_amount_after_discount_amount, places=2 + ) + + def test_item_wise_tax_detail_with_multi_currency_with_single_item(self): + """ + When the tax amount (in transaction currency) has more decimals than + the field precision, rounding must happen *before* multiplying by + conversion_rate — the same order used by _set_in_company_currency. + """ + doc = frappe.get_doc( + { + "doctype": "Sales Invoice", + "customer": "_Test Customer", + "company": "_Test Company", + "currency": "USD", + "debit_to": "_Test Receivable USD - _TC", + "conversion_rate": 129.99, + "items": [ + { + "item_code": "_Test Item", + "qty": 1, + "rate": 47.41, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + } + ], + "taxes": [ + { + "charge_type": "On Net Total", + "account_head": "_Test Account VAT - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "rate": 16, + }, + ], + } + ) + doc.save() + + tax = doc.taxes[0] + detail = doc.item_wise_tax_details[0] + self.assertEqual(detail.amount, tax.base_tax_amount_after_discount_amount) From 6689b17b882f2bd8e5ea3a7ac10aba9e90d90345 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 30 Mar 2026 18:37:07 +0530 Subject: [PATCH 063/168] fix(tests): update item code and quantity in tax detail test case (cherry picked from commit 3449ab063aac954f38bf3dbf7048f13457f60dcb) --- erpnext/controllers/tests/test_item_wise_tax_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/tests/test_item_wise_tax_details.py b/erpnext/controllers/tests/test_item_wise_tax_details.py index d4a560284ef..6b259429cc5 100644 --- a/erpnext/controllers/tests/test_item_wise_tax_details.py +++ b/erpnext/controllers/tests/test_item_wise_tax_details.py @@ -148,7 +148,7 @@ class TestTaxesAndTotals(ERPNextTestSuite): "cost_center": "_Test Cost Center - _TC", }, { - "item_code": "_Test Item", + "item_code": "_Test Item 2", "qty": 2, "rate": 33.33, "income_account": "Sales - _TC", From 6ad5e8960782f761ce4dcca305c2c94ea88b11fc Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 30 Mar 2026 19:37:59 +0530 Subject: [PATCH 064/168] fix(taxes): improve tax calculation accuracy and update test assertions (cherry picked from commit a18196f584d4d234e669e23842223f6042360c1d) --- erpnext/controllers/taxes_and_totals.py | 29 +++++++++++++++---- .../tests/test_item_wise_tax_details.py | 7 ++--- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index fa55aa1daa5..f0da61ad900 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -285,6 +285,13 @@ class calculate_taxes_and_totals: self.doc._item_wise_tax_details = item_wise_tax_details self.doc.item_wise_tax_details = [] + for tax in self.doc.get("taxes"): + if not tax.get("dont_recompute_tax"): + tax._running_txn_tax_total = 0.0 + tax._running_base_tax_total = 0.0 + tax._running_txn_taxable_total = 0.0 + tax._running_base_taxable_total = 0.0 + def determine_exclusive_rate(self): if not any(cint(tax.included_in_print_rate) for tax in self.doc.get("taxes")): return @@ -521,8 +528,7 @@ class calculate_taxes_and_totals: actual_breakup = tax._total_tax_breakup diff = flt(expected_amount - actual_breakup, 5) - # TODO: fix rounding difference issues - if abs(diff) <= 1: + if abs(diff) <= 0.5: detail_row = self.doc._item_wise_tax_details[last_idx] detail_row["amount"] = flt(detail_row["amount"] + diff, 5) @@ -597,14 +603,25 @@ class calculate_taxes_and_totals: def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount, current_net_amount): # store tax breakup for each item multiplier = -1 if tax.get("add_deduct_tax") == "Deduct" else 1 - item_wise_tax_amount = flt( - current_tax_amount * self.doc.conversion_rate * multiplier, tax.precision("tax_amount") + + # Error diffusion: derive each item's base amount as a delta of the running cumulative total + # so the sum always equals base_tax_amount_after_discount_amount. + tax._running_txn_tax_total += current_tax_amount * multiplier + new_base_tax_total = flt( + flt(tax._running_txn_tax_total, tax.precision("tax_amount")) * self.doc.conversion_rate, + tax.precision("base_tax_amount"), ) + item_wise_tax_amount = new_base_tax_total - tax._running_base_tax_total + tax._running_base_tax_total = new_base_tax_total if tax.charge_type != "On Item Quantity": - item_wise_taxable_amount = flt( - current_net_amount * self.doc.conversion_rate * multiplier, tax.precision("tax_amount") + tax._running_txn_taxable_total += current_net_amount * multiplier + new_base_taxable_total = flt( + flt(tax._running_txn_taxable_total, tax.precision("net_amount")) * self.doc.conversion_rate, + tax.precision("base_net_amount"), ) + item_wise_taxable_amount = new_base_taxable_total - tax._running_base_taxable_total + tax._running_base_taxable_total = new_base_taxable_total else: item_wise_taxable_amount = 0.0 diff --git a/erpnext/controllers/tests/test_item_wise_tax_details.py b/erpnext/controllers/tests/test_item_wise_tax_details.py index 6b259429cc5..cce4f2ce024 100644 --- a/erpnext/controllers/tests/test_item_wise_tax_details.py +++ b/erpnext/controllers/tests/test_item_wise_tax_details.py @@ -179,14 +179,11 @@ class TestTaxesAndTotals(ERPNextTestSuite): details_by_tax = {} for detail in doc.item_wise_tax_details: - bucket = details_by_tax.setdefault(detail.tax_row, {"amount": 0.0, "taxable_amount": 0.0}) + bucket = details_by_tax.setdefault(detail.tax_row, {"amount": 0.0}) bucket["amount"] += detail.amount for tax in doc.taxes: - detail_totals = details_by_tax[tax.name] - self.assertAlmostEqual( - detail_totals["amount"], tax.base_tax_amount_after_discount_amount, places=2 - ) + self.assertEqual(details_by_tax[tax.name]["amount"], tax.base_tax_amount_after_discount_amount) def test_item_wise_tax_detail_with_multi_currency_with_single_item(self): """ From 5922d25210d434478a0947a0b6a68fda23164e0f Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 30 Mar 2026 20:04:53 +0530 Subject: [PATCH 065/168] test: update item-wise tax detail test for high conversion rates (cherry picked from commit fc8437c499e3746c82cc64c26751f8b546fd8d94) # Conflicts: # erpnext/controllers/tests/test_item_wise_tax_details.py --- .../tests/test_item_wise_tax_details.py | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/erpnext/controllers/tests/test_item_wise_tax_details.py b/erpnext/controllers/tests/test_item_wise_tax_details.py index cce4f2ce024..c7a61fcb47d 100644 --- a/erpnext/controllers/tests/test_item_wise_tax_details.py +++ b/erpnext/controllers/tests/test_item_wise_tax_details.py @@ -2,8 +2,12 @@ import json import frappe +<<<<<<< HEAD from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals from erpnext.tests.utils import ERPNextTestSuite +======= +from erpnext.tests.utils import ERPNextTestSuite, change_settings +>>>>>>> fc8437c499 (test: update item-wise tax detail test for high conversion rates) class TestTaxesAndTotals(ERPNextTestSuite): @@ -125,10 +129,22 @@ class TestTaxesAndTotals(ERPNextTestSuite): self.assertEqual(actual_values, expected_values) - def test_item_wise_tax_detail_with_multi_currency(self): + @change_settings("Selling Settings", {"allow_multiple_items": 1}) + def test_item_wise_tax_detail_high_conversion_rate(self): """ - For multi-item, multi-currency invoices, item-wise tax breakup should - still reconcile with base tax totals. + With a high conversion rate (e.g. USD -> KRW ~1300), independently rounding + each item's base tax amount causes per-item errors that accumulate and exceed + the 0.5-unit safety threshold, raising a validation error. + + Error diffusion fixes this: the cumulative base total after the last item + equals base_tax_amount_after_discount_amount exactly, so the sum of all + per-item amounts is always exact regardless of item count or rate magnitude. + + Analytically with conversion_rate=1300, rate=7.77 x3 items, VAT 16%: + per-item txn tax = 1.2432 + OLD independent: flt(1.2432 * 1300, 2) = 1616.16 -> sum 4848.48 + expected base: flt(flt(3.7296, 2) * 1300, 0) = flt(3.73 * 1300, 0) = 4849 + diff = 0.52 -> exceeds 0.5 threshold -> would throw with old code """ doc = frappe.get_doc( { @@ -137,20 +153,28 @@ class TestTaxesAndTotals(ERPNextTestSuite): "company": "_Test Company", "currency": "USD", "debit_to": "_Test Receivable USD - _TC", - "conversion_rate": 129.99, + "conversion_rate": 1300, "items": [ { "item_code": "_Test Item", "qty": 1, - "rate": 47.41, + "rate": 7.77, "income_account": "Sales - _TC", "expense_account": "Cost of Goods Sold - _TC", "cost_center": "_Test Cost Center - _TC", }, { - "item_code": "_Test Item 2", - "qty": 2, - "rate": 33.33, + "item_code": "_Test Item", + "qty": 1, + "rate": 7.77, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + { + "item_code": "_Test Item", + "qty": 1, + "rate": 7.77, "income_account": "Sales - _TC", "expense_account": "Cost of Goods Sold - _TC", "cost_center": "_Test Cost Center - _TC", @@ -179,11 +203,11 @@ class TestTaxesAndTotals(ERPNextTestSuite): details_by_tax = {} for detail in doc.item_wise_tax_details: - bucket = details_by_tax.setdefault(detail.tax_row, {"amount": 0.0}) - bucket["amount"] += detail.amount + bucket = details_by_tax.setdefault(detail.tax_row, 0.0) + details_by_tax[detail.tax_row] = bucket + detail.amount for tax in doc.taxes: - self.assertEqual(details_by_tax[tax.name]["amount"], tax.base_tax_amount_after_discount_amount) + self.assertEqual(details_by_tax[tax.name], tax.base_tax_amount_after_discount_amount) def test_item_wise_tax_detail_with_multi_currency_with_single_item(self): """ From 8cb8f66b22af3618f3e769b13665a67537162c4d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 20:56:31 +0530 Subject: [PATCH 066/168] fix(bank_account): added validation to fetch bank account details using `get_bank_account_details` (backport #53926) (#53930) Co-authored-by: diptanilsaha fix(bank_account): added validation to fetch bank account details using `get_bank_account_details` (#53926) --- erpnext/accounts/doctype/bank_account/bank_account.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index c0dc6467f8f..9fe5b4ba3fb 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -116,6 +116,7 @@ def get_default_company_bank_account(company, party_type, party): @frappe.whitelist() def get_bank_account_details(bank_account): + frappe.has_permission("Bank Account", doc=bank_account, ptype="read", throw=True) return frappe.get_cached_value( "Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1 ) From a478fb713139c0359c5b605581cabcc8a5352027 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:33:22 +0530 Subject: [PATCH 067/168] fix(warehouse_capacity_dashboard): removed `escape` from template (backport #53907) (#53909) Co-authored-by: diptanilsaha fix(warehouse_capacity_dashboard): removed `escape` from template (#53907) --- .../warehouse_capacity_summary.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html index adab4786403..5a69c405364 100644 --- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html +++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html @@ -32,8 +32,8 @@ class="btn btn-default btn-xs btn-edit" style="margin: 4px 0; float: left;" data-warehouse="{{ d.warehouse }}" - data-item="{{ escape(d.item_code) }}" - data-company="{{ escape(d.company) }}"> + data-item="{{ d.item_code }}" + data-company="{{ d.company }}"> {{ __("Edit Capacity") }}
From 0dcacad79321878c7d660804d2249b85f3f16f7a Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 31 Mar 2026 11:05:46 +0530 Subject: [PATCH 068/168] chore: resolve conflicts --- erpnext/controllers/tests/test_item_wise_tax_details.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/controllers/tests/test_item_wise_tax_details.py b/erpnext/controllers/tests/test_item_wise_tax_details.py index c7a61fcb47d..30f1e51d7f4 100644 --- a/erpnext/controllers/tests/test_item_wise_tax_details.py +++ b/erpnext/controllers/tests/test_item_wise_tax_details.py @@ -2,12 +2,7 @@ import json import frappe -<<<<<<< HEAD -from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals -from erpnext.tests.utils import ERPNextTestSuite -======= from erpnext.tests.utils import ERPNextTestSuite, change_settings ->>>>>>> fc8437c499 (test: update item-wise tax detail test for high conversion rates) class TestTaxesAndTotals(ERPNextTestSuite): From 8be7793f89fe64f837ed15b321e3da8441d2b656 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 06:09:44 +0000 Subject: [PATCH 069/168] chore: remove inter warehouse transfer settings (backport #53860) (#53941) Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com> --- .../stock_settings/stock_settings.json | 27 +------------ .../doctype/stock_settings/stock_settings.py | 39 ------------------- 2 files changed, 1 insertion(+), 65 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index cd9ced97baf..8e42e4d3177 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -74,10 +74,6 @@ "auto_indent", "column_break_27", "reorder_email_notify", - "inter_warehouse_transfer_settings_section", - "allow_from_dn", - "column_break_31", - "allow_from_pr", "stock_closing_tab", "control_historical_stock_transactions_section", "stock_frozen_upto", @@ -225,23 +221,6 @@ "fieldtype": "Data", "label": "Naming Series Prefix" }, - { - "fieldname": "inter_warehouse_transfer_settings_section", - "fieldtype": "Section Break", - "label": "Inter Warehouse Transfer Settings" - }, - { - "default": "0", - "fieldname": "allow_from_dn", - "fieldtype": "Check", - "label": "Allow Material Transfer from Delivery Note to Sales Invoice" - }, - { - "default": "0", - "fieldname": "allow_from_pr", - "fieldtype": "Check", - "label": "Allow Material Transfer from Purchase Receipt to Purchase Invoice" - }, { "description": "If mentioned, the system will allow only the users with this Role to create or modify any stock transaction earlier than the latest stock transaction for a specific item and warehouse. If set as blank, it allows all users to create/edit back-dated transactions.", "fieldname": "role_allowed_to_create_edit_back_dated_transactions", @@ -289,10 +268,6 @@ "fieldname": "column_break_27", "fieldtype": "Column Break" }, - { - "fieldname": "column_break_31", - "fieldtype": "Column Break" - }, { "fieldname": "quality_inspection_settings_section", "fieldtype": "Section Break", @@ -564,7 +539,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-03-16 13:28:19.254641", + "modified": "2026-03-27 22:39:16.812184", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index f2d54794094..2d85675f2ea 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -26,8 +26,6 @@ class StockSettings(Document): action_if_quality_inspection_is_not_submitted: DF.Literal["Stop", "Warn"] action_if_quality_inspection_is_rejected: DF.Literal["Stop", "Warn"] allow_existing_serial_no: DF.Check - allow_from_dn: DF.Check - allow_from_pr: DF.Check allow_internal_transfer_at_arms_length_price: DF.Check allow_negative_stock: DF.Check allow_negative_stock_for_batch: DF.Check @@ -261,9 +259,6 @@ class StockSettings(Document): ) ) - def on_update(self): - self.toggle_warehouse_field_for_inter_warehouse_transfer() - def change_precision_for_for_sales(self): doc_before_save = self.get_doc_before_save() if doc_before_save and ( @@ -314,40 +309,6 @@ class StockSettings(Document): validate_fields_for_doctype=False, ) - def toggle_warehouse_field_for_inter_warehouse_transfer(self): - make_property_setter( - "Sales Invoice Item", - "target_warehouse", - "hidden", - 1 - cint(self.allow_from_dn), - "Check", - validate_fields_for_doctype=False, - ) - make_property_setter( - "Delivery Note Item", - "target_warehouse", - "hidden", - 1 - cint(self.allow_from_dn), - "Check", - validate_fields_for_doctype=False, - ) - make_property_setter( - "Purchase Invoice Item", - "from_warehouse", - "hidden", - 1 - cint(self.allow_from_pr), - "Check", - validate_fields_for_doctype=False, - ) - make_property_setter( - "Purchase Receipt Item", - "from_warehouse", - "hidden", - 1 - cint(self.allow_from_pr), - "Check", - validate_fields_for_doctype=False, - ) - def clean_all_descriptions(): for item in frappe.get_all("Item", ["name", "description"]): From 4705f53d2c53b597df3cd6e5a4bf5fd321cfcfbe Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 31 Mar 2026 01:52:01 +0530 Subject: [PATCH 070/168] fix: dynamic labels on invoice type change (cherry picked from commit 820bd15e1e1ddb63442fb2052621e2bf60b15045) --- .../opening_invoice_creation_tool.js | 32 +++++++++++++++++-- .../opening_invoice_creation_tool.json | 12 +++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js index 466b38126d7..22d977dd60f 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js @@ -70,9 +70,7 @@ frappe.ui.form.on("Opening Invoice Creation Tool", { }); }); - if (frm.doc.create_missing_party) { - frm.set_df_property("party", "fieldtype", "Data", frm.doc.name, "invoices"); - } + frm.trigger("update_party_labels"); }, setup_company_filters: function (frm) { @@ -127,7 +125,9 @@ frappe.ui.form.on("Opening Invoice Creation Tool", { frappe.model.set_value(row.doctype, row.name, "party", ""); frappe.model.set_value(row.doctype, row.name, "party_name", ""); }); + frm.clear_table("invoices"); frm.refresh_fields(); + frm.trigger("update_party_labels"); }, make_dashboard: function (frm) { @@ -175,6 +175,32 @@ frappe.ui.form.on("Opening Invoice Creation Tool", { } frm.refresh_field("invoices"); }, + + update_party_labels: function (frm) { + let is_sales = frm.doc.invoice_type == "Sales"; + + frm.fields_dict["invoices"].grid.update_docfield_property( + "party", + "label", + is_sales ? "Customer ID" : "Supplier ID" + ); + frm.fields_dict["invoices"].grid.update_docfield_property( + "party_name", + "label", + is_sales ? "Customer Name" : "Supplier Name" + ); + + frm.set_df_property( + "create_missing_party", + "description", + is_sales + ? __("If party does not exist, create it using the Customer Name field.") + : __("If party does not exist, create it using the Supplier Name field.") + ); + + frm.refresh_field("invoices"); + frm.refresh_field("create_missing_party"); + }, }); frappe.ui.form.on("Opening Invoice Creation Tool Item", { diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json index 8d1c3e87ba1..535b7384b4d 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json @@ -7,10 +7,11 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "section_break_ynel", "company", + "create_missing_party", "column_break_3", "invoice_type", - "create_missing_party", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -25,11 +26,11 @@ "in_list_view": 1, "label": "Company", "options": "Company", + "remember_last_selected_value": 1, "reqd": 1 }, { "default": "0", - "description": "If party does not exist, create it using the Party Name field.", "fieldname": "create_missing_party", "fieldtype": "Check", "label": "Create Missing Party" @@ -79,12 +80,17 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_ynel", + "fieldtype": "Section Break", + "hide_border": 1 } ], "hide_toolbar": 1, "issingle": 1, "links": [], - "modified": "2026-03-23 00:32:15.600086", + "modified": "2026-03-31 01:47:20.360352", "modified_by": "Administrator", "module": "Accounts", "name": "Opening Invoice Creation Tool", From 5ade905ee85b92d724e1d7ba515cb9c70e24812c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:26:29 +0530 Subject: [PATCH 071/168] feat(Payment Request): Added a toggle for using the payment schedule (backport #53922) (#53928) * feat(Payment Request): Added a toggle for using the payment schedule (cherry picked from commit 8ec15b537ec1da26d097378bc67b63629dbc758f) --------- Co-authored-by: Jatin3128 Co-authored-by: Jatin3128 <140256508+Jatin3128@users.noreply.github.com> --- .../accounts_settings/accounts_settings.json | 16 +++++++++++++++- .../accounts_settings/accounts_settings.py | 1 + erpnext/public/js/controllers/transaction.js | 6 +++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index ee734184452..29673e89b6c 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -65,6 +65,7 @@ "payment_options_section", "enable_loyalty_point_program", "column_break_ctam", + "fetch_payment_schedule_in_payment_request", "invoicing_settings_tab", "accounts_transactions_settings_section", "over_billing_allowance", @@ -688,6 +689,19 @@ "fieldname": "enable_accounting_dimensions", "fieldtype": "Check", "label": "Enable Accounting Dimensions" + }, + { + "default": "1", + "description": "Enable Subscription tracking in invoice", + "fieldname": "enable_subscription", + "fieldtype": "Check", + "label": "Enable Subscription" + }, + { + "default": "1", + "fieldname": "fetch_payment_schedule_in_payment_request", + "fieldtype": "Check", + "label": "Fetch Payment Schedule In Payment Request" } ], "grid_page_length": 50, @@ -697,7 +711,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-02-27 01:04:09.415288", + "modified": "2026-03-30 07:32:58.182018", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index e75b8ad1710..94b35eba00a 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -73,6 +73,7 @@ class AccountsSettings(Document): enable_loyalty_point_program: DF.Check enable_party_matching: DF.Check exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"] + fetch_payment_schedule_in_payment_request: DF.Check fetch_valuation_rate_for_internal_transaction: DF.Check general_ledger_remarks_length: DF.Int ignore_account_closing_balance: DF.Check diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 1100ab7a581..f0be33b6a87 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -459,8 +459,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe reference_name: frm.doc.name, }, }); + const value = await frappe.db.get_single_value( + "Accounts Settings", + "fetch_payment_schedule_in_payment_request" + ); - if (!schedules.length) { + if (!value || !schedules.length) { this.make_payment_request(); return; } From a8d0fb5ac92c50f52e9a07fb88ccec42a412973b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 26 Mar 2026 11:32:00 +0530 Subject: [PATCH 072/168] refactor(test): erpnext testsuite should be primary superclass (cherry picked from commit f3148e052c8312c5a1592da74de53a20562d2afb) --- .../test_advance_payment_ledger_entry.py | 2 +- .../test_bank_reconciliation_tool.py | 2 +- .../doctype/bank_transaction/test_bank_transaction_fees.py | 5 +++-- .../test_exchange_rate_revaluation.py | 2 +- erpnext/accounts/doctype/ledger_health/test_ledger_health.py | 2 +- .../test_process_statement_of_accounts.py | 2 +- .../test_repost_accounting_ledger.py | 2 +- .../doctype/unreconcile_payment/test_unreconcile_payment.py | 2 +- .../report/accounts_payable/test_accounts_payable.py | 2 +- .../report/accounts_receivable/test_accounts_receivable.py | 2 +- .../test_accounts_receivable_summary.py | 2 +- .../customer_ledger_summary/test_customer_ledger_summary.py | 2 +- .../test_item_wise_purchase_register.py | 2 +- .../test_item_wise_sales_register.py | 2 +- .../test_profit_and_loss_statement.py | 2 +- .../accounts/report/sales_register/test_sales_register.py | 2 +- .../supplier_ledger_summary/test_supplier_ledger_summary.py | 2 +- .../tax_withholding_details/test_tax_withholding_details.py | 2 +- erpnext/controllers/tests/test_distributed_discount.py | 2 +- erpnext/controllers/tests/test_reactivity.py | 2 +- erpnext/edi/doctype/code_list/test_code_list.py | 4 ++-- erpnext/edi/doctype/common_code/test_common_code.py | 4 ++-- .../address_template/test_regional_address_template.py | 5 ++--- erpnext/selling/doctype/sales_order/test_sales_order.py | 2 +- 24 files changed, 29 insertions(+), 29 deletions(-) diff --git a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py index 53047b61718..d910bef3d94 100644 --- a/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py @@ -15,7 +15,7 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde from erpnext.tests.utils import ERPNextTestSuite -class TestAdvancePaymentLedgerEntry(AccountsTestMixin, ERPNextTestSuite): +class TestAdvancePaymentLedgerEntry(ERPNextTestSuite, AccountsTestMixin): """ Integration tests for AdvancePaymentLedgerEntry. Use this class for testing interactions between multiple components. diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py index 5354aa0c4dd..3a55b3fc1d8 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py @@ -15,7 +15,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestBankReconciliationTool(AccountsTestMixin, ERPNextTestSuite): +class TestBankReconciliationTool(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction_fees.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction_fees.py index 95fc615d91d..e0ea8cd441a 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction_fees.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction_fees.py @@ -2,10 +2,11 @@ # See license.txt import frappe -from frappe.tests import UnitTestCase + +from erpnext.tests.utils import ERPNextTestSuite -class TestBankTransactionFees(UnitTestCase): +class TestBankTransactionFees(ERPNextTestSuite): def test_included_fee_throws(self): """A fee that's part of a withdrawal cannot be bigger than the withdrawal itself.""" diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py index 1310a8b482b..a6adba537e2 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py @@ -13,7 +13,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestExchangeRateRevaluation(AccountsTestMixin, ERPNextTestSuite): +class TestExchangeRateRevaluation(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_usd_receivable_account() diff --git a/erpnext/accounts/doctype/ledger_health/test_ledger_health.py b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py index 84fd3925ded..d9d4249ca69 100644 --- a/erpnext/accounts/doctype/ledger_health/test_ledger_health.py +++ b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py @@ -10,7 +10,7 @@ from erpnext.accounts.utils import run_ledger_health_checks from erpnext.tests.utils import ERPNextTestSuite -class TestLedgerHealth(AccountsTestMixin, ERPNextTestSuite): +class TestLedgerHealth(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py index c16933c7836..6e2f2300054 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py @@ -14,7 +14,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestProcessStatementOfAccounts(AccountsTestMixin, ERPNextTestSuite): +class TestProcessStatementOfAccounts(ERPNextTestSuite, AccountsTestMixin): def setUp(self): frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0) letterhead = frappe.get_doc("Letter Head", "Company Letterhead - Grey") diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index b60c13fc8b8..08075b2b0be 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -16,7 +16,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_ from erpnext.tests.utils import ERPNextTestSuite -class TestRepostAccountingLedger(AccountsTestMixin, ERPNextTestSuite): +class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index 7f931b7556e..e3bfed7de55 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -14,7 +14,7 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde from erpnext.tests.utils import ERPNextTestSuite -class TestUnreconcilePayment(AccountsTestMixin, ERPNextTestSuite): +class TestUnreconcilePayment(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index 87a4b989661..a8074468f55 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -7,7 +7,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestAccountsPayable(AccountsTestMixin, ERPNextTestSuite): +class TestAccountsPayable(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index d38ce924cf0..a739502074e 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -10,7 +10,7 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde from erpnext.tests.utils import ERPNextTestSuite -class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite): +class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py index 96fa4ae8b64..5b8065eef0c 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py @@ -8,7 +8,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestAccountsReceivable(AccountsTestMixin, ERPNextTestSuite): +class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.maxDiff = None self.create_company() diff --git a/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py index 0b114dd96d2..624e007c1b0 100644 --- a/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py @@ -10,7 +10,7 @@ from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.tests.utils import ERPNextTestSuite -class TestCustomerLedgerSummary(AccountsTestMixin, ERPNextTestSuite): +class TestCustomerLedgerSummary(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py index eed45ea60bb..64b0dfc739d 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py @@ -7,7 +7,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestItemWisePurchaseRegister(AccountsTestMixin, ERPNextTestSuite): +class TestItemWisePurchaseRegister(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_supplier() 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 689edeac1c4..708bf1ffe89 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 @@ -7,7 +7,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestItemWiseSalesRegister(AccountsTestMixin, ERPNextTestSuite): +class TestItemWiseSalesRegister(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/accounts/report/profit_and_loss_statement/test_profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/test_profit_and_loss_statement.py index 90d28033f19..4a509f63843 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/test_profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/test_profit_and_loss_statement.py @@ -12,7 +12,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestProfitAndLossStatement(AccountsTestMixin, ERPNextTestSuite): +class TestProfitAndLossStatement(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/accounts/report/sales_register/test_sales_register.py b/erpnext/accounts/report/sales_register/test_sales_register.py index ca284cc636c..c6926d57dea 100644 --- a/erpnext/accounts/report/sales_register/test_sales_register.py +++ b/erpnext/accounts/report/sales_register/test_sales_register.py @@ -7,7 +7,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestItemWiseSalesRegister(AccountsTestMixin, ERPNextTestSuite): +class TestItemWiseSalesRegister(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/accounts/report/supplier_ledger_summary/test_supplier_ledger_summary.py b/erpnext/accounts/report/supplier_ledger_summary/test_supplier_ledger_summary.py index 418d86abb66..4ee0ea04677 100644 --- a/erpnext/accounts/report/supplier_ledger_summary/test_supplier_ledger_summary.py +++ b/erpnext/accounts/report/supplier_ledger_summary/test_supplier_ledger_summary.py @@ -7,7 +7,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestSupplierLedgerSummary(AccountsTestMixin, ERPNextTestSuite): +class TestSupplierLedgerSummary(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_supplier() diff --git a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py index a2a732e8de6..49e50b7ff32 100644 --- a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py @@ -16,7 +16,7 @@ from erpnext.accounts.utils import get_fiscal_year from erpnext.tests.utils import ERPNextTestSuite -class TestTaxWithholdingDetails(AccountsTestMixin, ERPNextTestSuite): +class TestTaxWithholdingDetails(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.clear_old_entries() diff --git a/erpnext/controllers/tests/test_distributed_discount.py b/erpnext/controllers/tests/test_distributed_discount.py index 4f4911c8537..94b5f43d29f 100644 --- a/erpnext/controllers/tests/test_distributed_discount.py +++ b/erpnext/controllers/tests/test_distributed_discount.py @@ -4,7 +4,7 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde from erpnext.tests.utils import ERPNextTestSuite -class TestTaxesAndTotals(AccountsTestMixin, ERPNextTestSuite): +class TestTaxesAndTotals(ERPNextTestSuite, AccountsTestMixin): @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": 1}) def test_distributed_discount_amount(self): so = make_sales_order(do_not_save=1) diff --git a/erpnext/controllers/tests/test_reactivity.py b/erpnext/controllers/tests/test_reactivity.py index fa3007087e1..1fdc9c8be73 100644 --- a/erpnext/controllers/tests/test_reactivity.py +++ b/erpnext/controllers/tests/test_reactivity.py @@ -7,7 +7,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestReactivity(AccountsTestMixin, ERPNextTestSuite): +class TestReactivity(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/edi/doctype/code_list/test_code_list.py b/erpnext/edi/doctype/code_list/test_code_list.py index d37b1ee8f5a..7c9ec54a627 100644 --- a/erpnext/edi/doctype/code_list/test_code_list.py +++ b/erpnext/edi/doctype/code_list/test_code_list.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests.utils import FrappeTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestCodeList(FrappeTestCase): +class TestCodeList(ERPNextTestSuite): pass diff --git a/erpnext/edi/doctype/common_code/test_common_code.py b/erpnext/edi/doctype/common_code/test_common_code.py index e9c67b2cc82..19e0f02d99f 100644 --- a/erpnext/edi/doctype/common_code/test_common_code.py +++ b/erpnext/edi/doctype/common_code/test_common_code.py @@ -2,8 +2,8 @@ # See license.txt # import frappe -from frappe.tests.utils import FrappeTestCase +from erpnext.tests.utils import ERPNextTestSuite -class TestCommonCode(FrappeTestCase): +class TestCommonCode(ERPNextTestSuite): pass diff --git a/erpnext/regional/address_template/test_regional_address_template.py b/erpnext/regional/address_template/test_regional_address_template.py index 952748b3338..76e63d40f81 100644 --- a/erpnext/regional/address_template/test_regional_address_template.py +++ b/erpnext/regional/address_template/test_regional_address_template.py @@ -1,8 +1,7 @@ -from unittest import TestCase - import frappe from erpnext.regional.address_template.setup import get_address_templates, update_address_template +from erpnext.tests.utils import ERPNextTestSuite def ensure_country(country): @@ -14,7 +13,7 @@ def ensure_country(country): return c -class TestRegionalAddressTemplate(TestCase): +class TestRegionalAddressTemplate(ERPNextTestSuite): def test_get_address_templates(self): """Get the countries and paths from the templates directory.""" templates = get_address_templates() diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index f6f4e8bea4f..a27674b9191 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -35,7 +35,7 @@ from erpnext.stock.get_item_details import get_bin_details from erpnext.tests.utils import ERPNextTestSuite -class TestSalesOrder(AccountsTestMixin, ERPNextTestSuite): +class TestSalesOrder(ERPNextTestSuite, AccountsTestMixin): def setUp(self): self.create_customer("_Test Customer Credit") From 013aea6b7e8663832dd28ca3097105d1c53f5bca Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 26 Mar 2026 12:44:52 +0530 Subject: [PATCH 073/168] refactor(test): move logic from AccountsTestMixin to ERPNextTestSuite (cherry picked from commit 2b37d7514d799ca705222fc5afe46dfd703734ea) --- .../test_repost_accounting_ledger.py | 100 +++++++++--------- erpnext/tests/utils.py | 8 ++ 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index 08075b2b0be..793bde5c99f 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -9,29 +9,25 @@ from frappe.utils import add_days, nowdate, today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.accounts.utils import get_fiscal_year from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries, make_purchase_receipt from erpnext.tests.utils import ERPNextTestSuite -class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): +class TestRepostAccountingLedger(ERPNextTestSuite): def setUp(self): - self.create_company() - self.create_customer() - self.create_item() frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0) update_repost_settings() def test_01_basic_functions(self): si = create_sales_invoice( - item=self.item, - company=self.company, - customer=self.customer, - debit_to=self.debit_to, - parent_cost_center=self.cost_center, - cost_center=self.cost_center, + item="_Test Item", + company="_Test Company", + customer="_Test Customer", + debit_to="Debtors - _TC", + parent_cost_center="Main - _TC", + cost_center="Main - _TC", rate=100, ) @@ -48,7 +44,7 @@ class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): # Test Validation Error ral = frappe.new_doc("Repost Accounting Ledger") - ral.company = self.company + ral.company = "_Test Company" ral.delete_cancelled_entries = True ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) ral.append( @@ -65,7 +61,7 @@ class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): ral.save() # manually set an incorrect debit amount in DB - gle = frappe.db.get_all("GL Entry", filters={"voucher_no": si.name, "account": self.debit_to}) + gle = frappe.db.get_all("GL Entry", filters={"voucher_no": si.name, "account": "Debtors - _TC"}) frappe.db.set_value("GL Entry", gle[0], "debit", 90) gl = qb.DocType("GL Entry") @@ -94,23 +90,23 @@ class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): def test_02_deferred_accounting_valiations(self): si = create_sales_invoice( - item=self.item, - company=self.company, - customer=self.customer, - debit_to=self.debit_to, - parent_cost_center=self.cost_center, - cost_center=self.cost_center, + item="_Test Item", + company="_Test Company", + customer="_Test Customer", + debit_to="Debtors - _TC", + parent_cost_center="Main - _TC", + cost_center="Main - _TC", rate=100, do_not_submit=True, ) si.items[0].enable_deferred_revenue = True - si.items[0].deferred_revenue_account = self.deferred_revenue + si.items[0].deferred_revenue_account = "Deferred Revenue - _TC" si.items[0].service_start_date = nowdate() si.items[0].service_end_date = add_days(nowdate(), 90) si.save().submit() ral = frappe.new_doc("Repost Accounting Ledger") - ral.company = self.company + ral.company = "_Test Company" ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) self.assertRaises(frappe.ValidationError, ral.save) @@ -118,35 +114,35 @@ class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): def test_04_pcv_validation(self): # Clear old GL entries so PCV can be submitted. gl = frappe.qb.DocType("GL Entry") - qb.from_(gl).delete().where(gl.company == self.company).run() + qb.from_(gl).delete().where(gl.company == "_Test Company").run() si = create_sales_invoice( - item=self.item, - company=self.company, - customer=self.customer, - debit_to=self.debit_to, - parent_cost_center=self.cost_center, - cost_center=self.cost_center, + item="_Test Item", + company="_Test Company", + customer="_Test Customer", + debit_to="Debtors - _TC", + parent_cost_center="Main - _TC", + cost_center="Main - _TC", rate=100, ) - fy = get_fiscal_year(today(), company=self.company) + fy = get_fiscal_year(today(), company="_Test Company") pcv = frappe.get_doc( { "doctype": "Period Closing Voucher", "transaction_date": today(), "period_start_date": fy[1], "period_end_date": today(), - "company": self.company, + "company": "_Test Company", "fiscal_year": fy[0], - "cost_center": self.cost_center, - "closing_account_head": self.retained_earnings, + "cost_center": "Main - _TC", + "closing_account_head": "Retained Earnings - _TC", "remarks": "test", } ) pcv.save().submit() ral = frappe.new_doc("Repost Accounting Ledger") - ral.company = self.company + ral.company = "_Test Company" ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) self.assertRaises(frappe.ValidationError, ral.save) @@ -156,12 +152,12 @@ class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): def test_03_deletion_flag_and_preview_function(self): si = create_sales_invoice( - item=self.item, - company=self.company, - customer=self.customer, - debit_to=self.debit_to, - parent_cost_center=self.cost_center, - cost_center=self.cost_center, + item="_Test Item", + company="_Test Company", + customer="_Test Customer", + debit_to="Debtors - _TC", + parent_cost_center="Main - _TC", + cost_center="Main - _TC", rate=100, ) @@ -170,7 +166,7 @@ class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): # with deletion flag set ral = frappe.new_doc("Repost Accounting Ledger") - ral.company = self.company + ral.company = "_Test Company" ral.delete_cancelled_entries = True ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) @@ -181,12 +177,12 @@ class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): def test_05_without_deletion_flag(self): si = create_sales_invoice( - item=self.item, - company=self.company, - customer=self.customer, - debit_to=self.debit_to, - parent_cost_center=self.cost_center, - cost_center=self.cost_center, + item="_Test Item", + company="_Test Company", + customer="_Test Customer", + debit_to="Debtors - _TC", + parent_cost_center="Main - _TC", + cost_center="Main - _TC", rate=100, ) @@ -195,7 +191,7 @@ class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): # without deletion flag set ral = frappe.new_doc("Repost Accounting Ledger") - ral.company = self.company + ral.company = "_Test Company" ral.delete_cancelled_entries = False ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) @@ -210,16 +206,16 @@ class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): provisional_account = create_account( account_name="Provision Account", parent_account="Current Liabilities - _TC", - company=self.company, + company="_Test Company", ) another_provisional_account = create_account( account_name="Another Provision Account", parent_account="Current Liabilities - _TC", - company=self.company, + company="_Test Company", ) - company = frappe.get_doc("Company", self.company) + company = frappe.get_doc("Company", "_Test Company") company.enable_provisional_accounting_for_non_stock_items = 1 company.default_provisional_account = provisional_account company.save() @@ -229,7 +225,7 @@ class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): item = make_item(properties={"is_stock_item": 0}) - pr = make_purchase_receipt(company=self.company, item_code=item.name, rate=1000.0, qty=1.0) + pr = make_purchase_receipt(company="_Test Company", item_code=item.name, rate=1000.0, qty=1.0) pr_gl_entries = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) expected_pr_gles = [ {"account": provisional_account, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, @@ -246,7 +242,7 @@ class TestRepostAccountingLedger(ERPNextTestSuite, AccountsTestMixin): ) repost_doc = frappe.new_doc("Repost Accounting Ledger") - repost_doc.company = self.company + repost_doc.company = "_Test Company" repost_doc.delete_cancelled_entries = True repost_doc.append("vouchers", {"voucher_type": pr.doctype, "voucher_no": pr.name}) repost_doc.save().submit() diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 6aefc4da247..e2a2a7ab195 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -1980,6 +1980,14 @@ class BootStrapTestData: ["_Test Payable", "Current Liabilities", 0, "Payable", None], ["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"], ["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"], + # Deferred Account + ["Deferred Revenue", "Current Liabilities", 0, None, None], + ["Deferred Expense", "Current Assets", 0, None, None], + # Bank + ["HDFC", "Bank Accounts", 0, "Bank", None], + # Advance Account + ["Advance Received", "Current Liabilities", 0, "Receivable", None], + ["Advance Paid", "Current Assets", 0, "Payable", None], # Loyalty Account ["Loyalty", "Direct Expenses", 0, "Expense Account", None], ] From 85b08e4706bb28b3899a532ea4d1dd80d191c829 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 27 Mar 2026 11:56:30 +0530 Subject: [PATCH 074/168] refactor(test): remove AccountsTestMixin from distributed discount (cherry picked from commit 0b6546ea0689aed862b862df9c85c48a679dc1f9) --- erpnext/controllers/tests/test_distributed_discount.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/controllers/tests/test_distributed_discount.py b/erpnext/controllers/tests/test_distributed_discount.py index 94b5f43d29f..e5efe9518b5 100644 --- a/erpnext/controllers/tests/test_distributed_discount.py +++ b/erpnext/controllers/tests/test_distributed_discount.py @@ -1,10 +1,9 @@ -from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.tests.utils import ERPNextTestSuite -class TestTaxesAndTotals(ERPNextTestSuite, AccountsTestMixin): +class TestTaxesAndTotals(ERPNextTestSuite): @ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": 1}) def test_distributed_discount_amount(self): so = make_sales_order(do_not_save=1) From ee61d796313d55c1e2b28f3dfa60373074b409af Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 27 Mar 2026 11:56:52 +0530 Subject: [PATCH 075/168] refactor(test): remove AccountsTestMixin from reactivity (cherry picked from commit d2ee967383aee47b6b6ca0e5066a700b59aa051c) --- erpnext/accounts/test/accounts_mixin.py | 20 ----------- erpnext/controllers/tests/test_reactivity.py | 38 ++++++-------------- 2 files changed, 10 insertions(+), 48 deletions(-) diff --git a/erpnext/accounts/test/accounts_mixin.py b/erpnext/accounts/test/accounts_mixin.py index 5b1c9e6aa57..c7619e8afd9 100644 --- a/erpnext/accounts/test/accounts_mixin.py +++ b/erpnext/accounts/test/accounts_mixin.py @@ -229,23 +229,3 @@ class AccountsTestMixin: ] for doctype in doctype_list: qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run() - - def create_price_list(self): - pl_name = "Mixin Price List" - if not frappe.db.exists("Price List", pl_name): - self.price_list = ( - frappe.get_doc( - { - "doctype": "Price List", - "currency": "INR", - "enabled": True, - "selling": True, - "buying": True, - "price_list_name": pl_name, - } - ) - .insert() - .name - ) - else: - self.price_list = frappe.get_doc("Price List", pl_name).name diff --git a/erpnext/controllers/tests/test_reactivity.py b/erpnext/controllers/tests/test_reactivity.py index 1fdc9c8be73..17f6f480589 100644 --- a/erpnext/controllers/tests/test_reactivity.py +++ b/erpnext/controllers/tests/test_reactivity.py @@ -2,36 +2,17 @@ import frappe from frappe import qb from frappe.utils import today -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import disable_dimension -from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.tests.utils import ERPNextTestSuite -class TestReactivity(ERPNextTestSuite, AccountsTestMixin): - def setUp(self): - self.create_company() - self.create_customer() - self.create_item() - self.create_usd_receivable_account() - self.create_price_list() - self.clear_old_entries() - - def disable_dimensions(self): - res = frappe.db.get_all("Accounting Dimension", filters={"disabled": False}) - for x in res: - dim = frappe.get_doc("Accounting Dimension", x.name) - dim.disabled = True - dim.save() - +class TestReactivity(ERPNextTestSuite): def test_01_basic_item_details(self): - self.disable_dimensions() - # set Item Price frappe.get_doc( { "doctype": "Item Price", - "item_code": self.item, - "price_list": self.price_list, + "item_code": "_Test Item", + "price_list": "Standard Selling", "price_list_rate": 90, "selling": True, "rate": 90, @@ -42,17 +23,18 @@ class TestReactivity(ERPNextTestSuite, AccountsTestMixin): si = frappe.get_doc( { "doctype": "Sales Invoice", - "company": self.company, - "customer": self.customer, - "debit_to": self.debit_to, + "company": "_Test Company", + "customer": "_Test Customer", + "debit_to": "Debtors - _TC", "posting_date": today(), - "cost_center": self.cost_center, + "cost_center": "Main - _TC", + "currency": "INR", "conversion_rate": 1, - "selling_price_list": self.price_list, + "selling_price_list": "Standard Selling", } ) itm = si.append("items") - itm.item_code = self.item + itm.item_code = "_Test Item" si.process_item_selection(itm.idx) self.assertEqual(itm.rate, 90) From 05f47bbf6e5df7f85ff3a27458121cb7d39ca460 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 27 Mar 2026 12:12:48 +0530 Subject: [PATCH 076/168] refactor(test): remove AccountsTestMixin from Sales Order (cherry picked from commit 2aecf0103a6b0d79f743e5214c3bc52ffc26537a) --- .../doctype/sales_order/test_sales_order.py | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index a27674b9191..6616c52b720 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -10,7 +10,6 @@ from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.tests import change_settings from frappe.utils import add_days, flt, nowdate, today -from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.controllers.accounts_controller import InvalidQtyError, get_due_date, update_child_qty_rate from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( make_maintenance_schedule, @@ -35,10 +34,7 @@ from erpnext.stock.get_item_details import get_bin_details from erpnext.tests.utils import ERPNextTestSuite -class TestSalesOrder(ERPNextTestSuite, AccountsTestMixin): - def setUp(self): - self.create_customer("_Test Customer Credit") - +class TestSalesOrder(ERPNextTestSuite): @ERPNextTestSuite.change_settings( "Stock Settings", { @@ -2439,7 +2435,7 @@ class TestSalesOrder(ERPNextTestSuite, AccountsTestMixin): def test_credit_limit_on_so_reopning(self): # set credit limit company = "_Test Company" - customer = frappe.get_doc("Customer", self.customer) + customer = frappe.get_doc("Customer", "_Test Customer") customer.credit_limits = [] customer.append( "credit_limits", {"company": company, "credit_limit": 1000, "bypass_credit_limit_check": False} @@ -2447,35 +2443,33 @@ class TestSalesOrder(ERPNextTestSuite, AccountsTestMixin): customer.save() so1 = make_sales_order(qty=9, rate=100, do_not_submit=True) - so1.customer = self.customer + so1.customer = customer.name so1.save().submit() so1.update_status("Closed") so2 = make_sales_order(qty=9, rate=100, do_not_submit=True) - so2.customer = self.customer + so2.customer = customer.name so2.save().submit() self.assertRaises(frappe.ValidationError, so1.update_status, "Draft") @ERPNextTestSuite.change_settings("Stock Settings", {"enable_stock_reservation": True}) def test_warehouse_mapping_based_on_stock_reservation(self): - self.create_company(company_name="Glass Ceiling", abbr="GC") - self.create_item("Lamy Safari 2", True, self.warehouse_stores, self.company, 2000) - self.create_customer() - self.clear_old_entries() + warehouse = "Stores - _TC" + warehouse_finished = "Finished Goods - _TC" so = frappe.new_doc("Sales Order") - so.company = self.company - so.customer = self.customer + so.company = "_Test Company" + so.customer = "_Test Customer" so.transaction_date = today() so.append( "items", { - "item_code": self.item, + "item_code": "_Test Item", "qty": 10, "rate": 2000, - "warehouse": self.warehouse_stores, + "warehouse": "Stores - _TC", "delivery_date": today(), }, ) @@ -2485,12 +2479,12 @@ class TestSalesOrder(ERPNextTestSuite, AccountsTestMixin): se = frappe.get_doc( { "doctype": "Stock Entry", - "company": self.company, + "company": "_Test Company", "stock_entry_type": "Material Receipt", "posting_date": today(), "items": [ - {"item_code": self.item, "t_warehouse": self.warehouse_stores, "qty": 5}, - {"item_code": self.item, "t_warehouse": self.warehouse_finished_goods, "qty": 5}, + {"item_code": "_Test Item", "t_warehouse": warehouse, "qty": 5}, + {"item_code": "_Test Item", "t_warehouse": warehouse_finished, "qty": 5}, ], } ) @@ -2503,7 +2497,7 @@ class TestSalesOrder(ERPNextTestSuite, AccountsTestMixin): { "sales_order_item": itm.name, "item_code": itm.item_code, - "warehouse": self.warehouse_stores, + "warehouse": warehouse, "qty_to_reserve": 2, } ] @@ -2513,7 +2507,7 @@ class TestSalesOrder(ERPNextTestSuite, AccountsTestMixin): { "sales_order_item": itm.name, "item_code": itm.item_code, - "warehouse": self.warehouse_finished_goods, + "warehouse": warehouse_finished, "qty_to_reserve": 3, } ] @@ -2523,31 +2517,31 @@ class TestSalesOrder(ERPNextTestSuite, AccountsTestMixin): dn = make_delivery_note(so.name, kwargs={"for_reserved_stock": True}) self.assertEqual(2, len(dn.items)) self.assertEqual(dn.items[0].qty, 2) - self.assertEqual(dn.items[0].warehouse, self.warehouse_stores) + self.assertEqual(dn.items[0].warehouse, warehouse) self.assertEqual(dn.items[1].qty, 3) - self.assertEqual(dn.items[1].warehouse, self.warehouse_finished_goods) + self.assertEqual(dn.items[1].warehouse, warehouse_finished) from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse - warehouse = create_warehouse("Test Warehouse 1", company=self.company) + warehouse = create_warehouse("Test Warehouse 1", company="_Test Company") make_stock_entry( - item_code=self.item, + item_code="_Test Item", target=warehouse, qty=5, - company=self.company, + company="_Test Company", ) so = frappe.new_doc("Sales Order") so.reserve_stock = 1 - so.company = self.company - so.customer = self.customer + so.company = "_Test Company" + so.customer = "_Test Customer" so.transaction_date = today() so.currency = "INR" so.append( "items", { - "item_code": self.item, + "item_code": "_Test Item", "qty": 5, "rate": 2000, "warehouse": warehouse, From 2c81f79df7d3a9d5ce88449a3533605d4dabc0e1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 31 Mar 2026 16:09:00 +0530 Subject: [PATCH 077/168] fix: rejected serial no field showing even if serial / batch feature not enabled (cherry picked from commit c2f419ac3d004bff2464fdd1d690c78d8f0ff549) --- erpnext/public/js/utils.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 6fb1b88060c..06ccc25a94e 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -44,7 +44,11 @@ $.extend(erpnext, { } if (["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(frm.doc.doctype)) { - fields.push("add_serial_batch_for_rejected_qty", "rejected_serial_and_batch_bundle"); + fields.push( + "add_serial_batch_for_rejected_qty", + "rejected_serial_and_batch_bundle", + "rejected_serial_no" + ); } let child_name = "items"; From 573a1a0dcb7f11c35cfa75c18d23f71a6a2a41d2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:29:29 +0530 Subject: [PATCH 078/168] fix: do not show inv dimension unnecessarily in stock entry (backport #53946) (#53951) Co-authored-by: Mihir Kandoi fix: do not show inv dimension unnecessarily in stock entry (#53946) --- .../stock/doctype/inventory_dimension/inventory_dimension.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index 469e4d5e53a..53a2e45f1df 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -182,6 +182,7 @@ class InventoryDimension(Document): insert_after="inventory_dimension", options=self.reference_document, label=_(label), + depends_on="eval:doc.s_warehouse" if doctype == "Stock Entry Detail" else "", search_index=1, reqd=self.reqd, mandatory_depends_on=self.mandatory_depends_on, @@ -273,7 +274,7 @@ class InventoryDimension(Document): elif doctype != "Stock Entry Detail": display_depends_on = "eval:parent.is_internal_customer == 1" elif doctype == "Stock Entry Detail": - display_depends_on = "eval:parent.purpose != 'Material Issue'" + display_depends_on = "eval:doc.t_warehouse" fieldname = f"{fieldname_start_with}_{self.source_fieldname}" label = f"{label_start_with} {self.dimension_name}" From 04cced2fb5f8e34d67b23500c7eb0a5a69fef721 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Thu, 26 Mar 2026 16:23:14 +0530 Subject: [PATCH 079/168] fix: prevent selection of group type customer group in customer master (cherry picked from commit 6068dc959f5c0594749f8f6bb3406c77c08e131d) --- erpnext/selling/doctype/customer/customer.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index d5e44e41a7f..08bea658d9d 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -173,6 +173,7 @@ class Customer(TransactionBase): def validate(self): self.flags.is_new_doc = self.is_new() self.flags.old_lead = self.lead_name + self.validate_customer_group() validate_party_accounts(self) self.validate_credit_limit_on_change() self.set_loyalty_program() @@ -356,6 +357,17 @@ class Customer(TransactionBase): frappe.NameError, ) + def validate_customer_group(self): + if not self.customer_group: + return + + is_group = frappe.db.get_value("Customer Group", self.customer_group, "is_group") + if is_group: + frappe.throw( + _("Cannot select a Group type Customer Group. Please select a non-group Customer Group."), + title=_("Invalid Customer Group"), + ) + def validate_credit_limit_on_change(self): if self.get("__islocal") or not self.credit_limits: return From 8674aafc86897bd2cd4ede47bc2a0d0287e548cf Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 31 Mar 2026 15:29:22 +0530 Subject: [PATCH 080/168] fix(test): do not use is_group enabled customer group in test (cherry picked from commit 75fa2b227711eb4e90d5c868db80cc42b25fe2f7) --- .../doctype/bank_transaction/test_bank_transaction.py | 4 ++-- .../test_opening_invoice_creation_tool.py | 2 +- erpnext/controllers/tests/test_qty_based_taxes.py | 2 +- erpnext/stock/doctype/shipment/test_shipment.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index c69d255c51a..c7668a5a592 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -382,7 +382,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"): frappe.get_doc( { "doctype": "Customer", - "customer_group": "All Customer Groups", + "customer_group": "Individual", "customer_type": "Company", "customer_name": "Poore Simon's", } @@ -413,7 +413,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"): frappe.get_doc( { "doctype": "Customer", - "customer_group": "All Customer Groups", + "customer_group": "Individual", "customer_type": "Company", "customer_name": "Fayva", } diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index c01ada6d317..4b64dc57306 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -180,7 +180,7 @@ def make_customer(customer=None): { "doctype": "Customer", "customer_name": customer_name, - "customer_group": "All Customer Groups", + "customer_group": "Individual", "customer_type": "Company", "territory": "All Territories", } diff --git a/erpnext/controllers/tests/test_qty_based_taxes.py b/erpnext/controllers/tests/test_qty_based_taxes.py index e3ddb0d1e1b..d934066a2b4 100644 --- a/erpnext/controllers/tests/test_qty_based_taxes.py +++ b/erpnext/controllers/tests/test_qty_based_taxes.py @@ -68,7 +68,7 @@ class TestTaxes(ERPNextTestSuite): { "doctype": "Customer", "customer_name": uuid4(), - "customer_group": "All Customer Groups", + "customer_group": "Individual", } ).insert() self.supplier = frappe.get_doc( diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index 7e58f942faf..0ee89f62ee8 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -177,7 +177,7 @@ def create_shipment_customer(customer_name): customer = frappe.new_doc("Customer") customer.customer_name = customer_name customer.customer_type = "Company" - customer.customer_group = "All Customer Groups" + customer.customer_group = "Individual" customer.territory = "All Territories" customer.insert() return customer From f42a1e8a147b793cefb13eee45aeee0ed0085b80 Mon Sep 17 00:00:00 2001 From: nishkagosalia Date: Tue, 31 Mar 2026 17:52:41 +0530 Subject: [PATCH 081/168] fix: Party Field only visibile when party type selected (cherry picked from commit e9e510a76e09da7ffed1e6f1e7be996080f585c6) --- erpnext/accounts/report/general_ledger/general_ledger.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 46ce1933834..1f66b2768a6 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -74,6 +74,7 @@ frappe.query_reports["General Ledger"] = { label: __("Party"), fieldtype: "MultiSelectList", options: "party_type", + depends_on: "party_type", get_data: function (txt) { if (!frappe.query_report.filters) return; From c4c76cc1b2e65f510d57fa65a3ef966454ef6266 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 31 Mar 2026 17:23:01 +0530 Subject: [PATCH 082/168] fix: ensure accurate rounding for item-wise tax and taxable amounts (cherry picked from commit 9b37f2d95c343b878fb3872575bf231a96e93416) --- erpnext/controllers/taxes_and_totals.py | 8 ++- .../tests/test_item_wise_tax_details.py | 57 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index f0da61ad900..6f34e6bbb6e 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -611,7 +611,9 @@ class calculate_taxes_and_totals: flt(tax._running_txn_tax_total, tax.precision("tax_amount")) * self.doc.conversion_rate, tax.precision("base_tax_amount"), ) - item_wise_tax_amount = new_base_tax_total - tax._running_base_tax_total + item_wise_tax_amount = flt( + new_base_tax_total - tax._running_base_tax_total, tax.precision("base_tax_amount") + ) tax._running_base_tax_total = new_base_tax_total if tax.charge_type != "On Item Quantity": @@ -620,7 +622,9 @@ class calculate_taxes_and_totals: flt(tax._running_txn_taxable_total, tax.precision("net_amount")) * self.doc.conversion_rate, tax.precision("base_net_amount"), ) - item_wise_taxable_amount = new_base_taxable_total - tax._running_base_taxable_total + item_wise_taxable_amount = flt( + new_base_taxable_total - tax._running_base_taxable_total, tax.precision("base_net_amount") + ) tax._running_base_taxable_total = new_base_taxable_total else: item_wise_taxable_amount = 0.0 diff --git a/erpnext/controllers/tests/test_item_wise_tax_details.py b/erpnext/controllers/tests/test_item_wise_tax_details.py index 30f1e51d7f4..a921442472e 100644 --- a/erpnext/controllers/tests/test_item_wise_tax_details.py +++ b/erpnext/controllers/tests/test_item_wise_tax_details.py @@ -204,6 +204,63 @@ class TestTaxesAndTotals(ERPNextTestSuite): for tax in doc.taxes: self.assertEqual(details_by_tax[tax.name], tax.base_tax_amount_after_discount_amount) + @change_settings("Selling Settings", {"allow_multiple_items": 1}) + def test_rounding_in_item_wise_tax_details(self): + """ + This test verifies the amounts are properly rounded. + """ + doc = frappe.get_doc( + { + "doctype": "Sales Invoice", + "customer": "_Test Customer", + "company": "_Test Company", + "currency": "INR", + "conversion_rate": 1, + "items": [ + { + "item_code": "_Test Item", + "qty": 5, + "rate": 20, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + { + "item_code": "_Test Item", + "qty": 3, + "rate": 19, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + { + "item_code": "_Test Item", + "qty": 1, + "rate": 1000, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + ], + "taxes": [ + { + "charge_type": "On Net Total", + "account_head": "_Test Account VAT - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "rate": 9, + }, + ], + } + ) + doc.save() + + # item 1: taxable=100, tax=9.0; item 2: taxable=57, tax=5.13; item 3: taxable=1000, tax=90.0 + # error diffusion: 14.13 - 9.0 = 5.130000000000001 without rounding + # 3rd item ensures the artifact is on a middle row (not corrected by last-row adjustment) + for detail in doc.item_wise_tax_details: + self.assertEqual(detail.amount, round(detail.amount, 2)) + def test_item_wise_tax_detail_with_multi_currency_with_single_item(self): """ When the tax amount (in transaction currency) has more decimals than From 9386c1328ad933186778677ec1c7e0a5a76de879 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 31 Mar 2026 17:46:46 +0530 Subject: [PATCH 083/168] test: improve test case (cherry picked from commit b73b161cbed791a8eb5e2f2939ed85a9c6534b50) --- erpnext/controllers/tests/test_item_wise_tax_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/tests/test_item_wise_tax_details.py b/erpnext/controllers/tests/test_item_wise_tax_details.py index a921442472e..7e19c1dc057 100644 --- a/erpnext/controllers/tests/test_item_wise_tax_details.py +++ b/erpnext/controllers/tests/test_item_wise_tax_details.py @@ -1,6 +1,7 @@ import json import frappe +from frappe.utils import flt from erpnext.tests.utils import ERPNextTestSuite, change_settings @@ -257,9 +258,8 @@ class TestTaxesAndTotals(ERPNextTestSuite): # item 1: taxable=100, tax=9.0; item 2: taxable=57, tax=5.13; item 3: taxable=1000, tax=90.0 # error diffusion: 14.13 - 9.0 = 5.130000000000001 without rounding - # 3rd item ensures the artifact is on a middle row (not corrected by last-row adjustment) for detail in doc.item_wise_tax_details: - self.assertEqual(detail.amount, round(detail.amount, 2)) + self.assertEqual(detail.amount, flt(detail.amount, detail.precision("amount"))) def test_item_wise_tax_detail_with_multi_currency_with_single_item(self): """ From e230f72e0ba93e49c8564c6f2ffb84b1659eb596 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 15:51:58 +0000 Subject: [PATCH 084/168] fix: include rejected qty in tax (purchase receipt) (backport #53624) (#53972) Co-authored-by: Mihir Kandoi fix: include rejected qty in tax (purchase receipt) (#53624) --- erpnext/controllers/buying_controller.py | 2 +- erpnext/controllers/taxes_and_totals.py | 20 +++++++++++-- .../purchase_receipt/purchase_receipt.py | 2 +- .../purchase_receipt/test_purchase_receipt.py | 29 ++++++++++++++++++- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 6383049be9c..fd86291027e 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -457,7 +457,7 @@ class BuyingController(SubcontractingController): get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0 ) - net_rate = item.base_net_amount + net_rate = item.qty * item.base_net_rate if item.sales_incoming_rate: # for internal transfer net_rate = item.qty * item.sales_incoming_rate diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index f0da61ad900..c9e8ad9be82 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -164,6 +164,9 @@ class calculate_taxes_and_totals: return if not self.discount_amount_applied: + bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value( + "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice" + ) for item in self.doc.items: self.doc.round_floats_in(item) @@ -225,7 +228,13 @@ class calculate_taxes_and_totals: elif not item.qty and self.doc.get("is_debit_note"): item.amount = flt(item.rate, item.precision("amount")) else: - item.amount = flt(item.rate * item.qty, item.precision("amount")) + qty = ( + (item.qty + item.rejected_qty) + if bill_for_rejected_quantity_in_purchase_invoice + and self.doc.doctype == "Purchase Receipt" + else item.qty + ) + item.amount = flt(item.rate * qty, item.precision("amount")) item.net_amount = item.amount @@ -379,9 +388,16 @@ class calculate_taxes_and_totals: self.doc.total ) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0 + bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value( + "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice" + ) for item in self._items: self.doc.total += item.amount - self.doc.total_qty += item.qty + self.doc.total_qty += ( + (item.qty + item.rejected_qty) + if bill_for_rejected_quantity_in_purchase_invoice and self.doc.doctype == "Purchase Receipt" + else item.qty + ) self.doc.base_total += item.base_amount self.doc.net_total += item.net_amount self.doc.base_net_total += item.base_net_amount diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index e78faa9511a..dc8885b1ca4 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -561,7 +561,7 @@ class PurchaseReceipt(BuyingController): else flt(item.net_amount, item.precision("net_amount")) ) - outgoing_amount = item.base_net_amount + outgoing_amount = item.qty * item.base_net_rate if self.is_internal_transfer() and item.valuation_rate: outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse)) credit_amount = outgoing_amount diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 6eba41c3883..50f28a75b18 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -4610,7 +4610,7 @@ class TestPurchaseReceipt(ERPNextTestSuite): self.assertEqual(srbnb_cost, 1500) - def test_valuation_rate_for_rejected_materials_withoout_accepted_materials(self): + def test_valuation_rate_for_rejected_materials_without_accepted_materials(self): item = make_item("Test Item with Rej Material Valuation WO Accepted", {"is_stock_item": 1}) company = "_Test Company with perpetual inventory" @@ -5423,6 +5423,33 @@ class TestPurchaseReceipt(ERPNextTestSuite): self.assertEqual(row.warehouse, "_Test Warehouse 1 - _TC") self.assertEqual(row.incoming_rate, 100) + def test_bill_for_rejected_quantity_in_purchase_invoice(self): + item_code = make_item("Test Rejected Qty", {"is_stock_item": 1}).name + + with self.change_settings("Buying Settings", {"bill_for_rejected_quantity_in_purchase_invoice": 0}): + pr = make_purchase_receipt( + item_code=item_code, + qty=10, + rejected_qty=2, + rate=10, + warehouse="_Test Warehouse - _TC", + ) + + self.assertEqual(pr.total_qty, 10) + self.assertEqual(pr.total, 100) + + with self.change_settings("Buying Settings", {"bill_for_rejected_quantity_in_purchase_invoice": 1}): + pr = make_purchase_receipt( + item_code=item_code, + qty=10, + rejected_qty=2, + rate=10, + warehouse="_Test Warehouse - _TC", + ) + + self.assertEqual(pr.total_qty, 12) + self.assertEqual(pr.total, 120) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 8db397bdae1f273d2f336abadafc4627469381d5 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 1 Apr 2026 13:11:39 +0530 Subject: [PATCH 085/168] feat: co product by product support (#52979) (#53975) --- erpnext/controllers/stock_controller.py | 2 +- .../controllers/subcontracting_controller.py | 26 +- .../subcontracting_inward_controller.py | 109 ++++---- .../tests/test_subcontracting_controller.py | 54 +++- erpnext/manufacturing/doctype/bom/bom.js | 53 +--- erpnext/manufacturing/doctype/bom/bom.json | 113 +++++---- erpnext/manufacturing/doctype/bom/bom.py | 166 ++++++++----- erpnext/manufacturing/doctype/bom/test_bom.py | 2 +- .../doctype/bom/test_records.json | 10 +- .../doctype/bom_creator/bom_creator.py | 1 - .../bom_scrap_item/bom_scrap_item.json | 109 -------- .../__init__.py | 0 .../bom_secondary_item.json | 232 ++++++++++++++++++ .../bom_secondary_item.py} | 21 +- .../doctype/job_card/job_card.js | 6 +- .../doctype/job_card/job_card.json | 34 +-- .../doctype/job_card/job_card.py | 71 ++++-- .../doctype/job_card/test_job_card.py | 187 ++++++++++++++ .../__init__.py | 0 .../job_card_secondary_item.json} | 32 ++- .../job_card_secondary_item.py} | 4 +- .../manufacturing_settings.json | 18 +- .../manufacturing_settings.py | 2 +- .../production_plan/test_production_plan.py | 18 ++ .../doctype/work_order/test_work_order.py | 50 ++-- .../doctype/work_order/work_order.js | 1 + .../doctype/work_order/work_order.py | 16 +- erpnext/patches.txt | 1 + erpnext/patches/v16_0/co_by_product_patch.py | 104 ++++++++ erpnext/public/js/controllers/transaction.js | 14 +- .../selling_settings/selling_settings.json | 16 +- .../selling_settings/selling_settings.py | 2 +- erpnext/setup/doctype/company/company.py | 2 +- .../stock/doctype/stock_entry/stock_entry.js | 6 +- .../stock/doctype/stock_entry/stock_entry.py | 210 +++++++++------- .../doctype/stock_entry/test_stock_entry.py | 55 ++++- .../stock_entry_detail.json | 35 ++- .../stock_entry_detail/stock_entry_detail.py | 4 +- .../stock_entry_type/stock_entry_type.py | 13 +- .../subcontracting_inward_order.json | 22 +- .../subcontracting_inward_order.py | 32 +-- .../test_subcontracting_inward_order.py | 10 +- .../__init__.py | 0 ...tracting_inward_order_secondary_item.json} | 23 +- ...ontracting_inward_order_secondary_item.py} | 3 +- .../subcontracting_order.py | 7 + .../subcontracting_order_item.json | 2 +- .../subcontracting_receipt.js | 17 +- .../subcontracting_receipt.json | 18 +- .../subcontracting_receipt.py | 135 ++++++---- .../test_subcontracting_receipt.py | 23 +- .../subcontracting_receipt_item.json | 94 ++++--- .../subcontracting_receipt_item.py | 6 +- 53 files changed, 1492 insertions(+), 699 deletions(-) delete mode 100644 erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json rename erpnext/manufacturing/doctype/{bom_scrap_item => bom_secondary_item}/__init__.py (100%) create mode 100644 erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json rename erpnext/manufacturing/doctype/{bom_scrap_item/bom_scrap_item.py => bom_secondary_item/bom_secondary_item.py} (50%) rename erpnext/manufacturing/doctype/{job_card_scrap_item => job_card_secondary_item}/__init__.py (100%) rename erpnext/manufacturing/doctype/{job_card_scrap_item/job_card_scrap_item.json => job_card_secondary_item/job_card_secondary_item.json} (73%) rename erpnext/manufacturing/doctype/{job_card_scrap_item/job_card_scrap_item.py => job_card_secondary_item/job_card_secondary_item.py} (78%) create mode 100644 erpnext/patches/v16_0/co_by_product_patch.py rename erpnext/subcontracting/doctype/{subcontracting_inward_order_scrap_item => subcontracting_inward_order_secondary_item}/__init__.py (100%) rename erpnext/subcontracting/doctype/{subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.json => subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json} (83%) rename erpnext/subcontracting/doctype/{subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.py => subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.py} (81%) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 1fed02a23ed..05f2e18c878 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1435,7 +1435,7 @@ class StockController(AccountsController): elif self.doctype == "Stock Entry" and row.t_warehouse: qi_required = True # inward stock needs inspection - if row.get("is_scrap_item"): + if row.get("type") or row.get("is_legacy_scrap_item"): continue if qi_required: # validate row only if inspection is required on item level diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 6d886bd9ecf..e922a0ea9fc 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -160,7 +160,7 @@ class SubcontractingController(StockController): ).format(item.idx, get_link_to_form("Item", item.item_code)) ) - if not item.get("is_scrap_item"): + if not item.get("type") and not item.get("is_legacy_scrap_item"): if not is_sub_contracted_item: frappe.throw( _("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name) @@ -206,7 +206,7 @@ class SubcontractingController(StockController): ).format(item.idx, item.item_name) ) - if self.doctype != "Subcontracting Inward Order": + if self.doctype not in ["Subcontracting Inward Order", "Subcontracting Receipt"]: item.amount = item.qty * item.rate if item.bom: @@ -238,7 +238,7 @@ class SubcontractingController(StockController): and self._doc_before_save ): for row in self._doc_before_save.get("items"): - item_dict[row.name] = (row.item_code, row.qty + (row.get("rejected_qty") or 0)) + item_dict[row.name] = (row.item_code, row.received_qty) return item_dict @@ -264,7 +264,7 @@ class SubcontractingController(StockController): self.__reference_name.append(row.name) if (row.name not in item_dict) or ( row.item_code, - row.qty + (row.get("rejected_qty") or 0), + row.received_qty, ) != item_dict[row.name]: self.__changed_name.append(row.name) @@ -962,7 +962,7 @@ class SubcontractingController(StockController): ): qty = ( flt(bom_item.qty_consumed_per_unit) - * flt(row.qty + (row.get("rejected_qty") or 0)) + * flt(row.get("received_qty") or (row.qty + (row.get("rejected_qty") or 0))) * row.conversion_factor ) bom_item.main_item_code = row.item_code @@ -1285,22 +1285,28 @@ class SubcontractingController(StockController): if self.total_additional_costs: if self.distribute_additional_costs_based_on == "Amount": total_amt = sum( - flt(item.amount) for item in self.get("items") if not item.get("is_scrap_item") + flt(item.amount) + for item in self.get("items") + if not item.get("type") and not item.get("is_legacy_scrap_item") ) for item in self.items: - if not item.get("is_scrap_item"): + if not item.get("type") and not item.get("is_legacy_scrap_item"): item.additional_cost_per_qty = ( (item.amount * self.total_additional_costs) / total_amt ) / item.qty else: - total_qty = sum(flt(item.qty) for item in self.get("items") if not item.get("is_scrap_item")) + total_qty = sum( + flt(item.qty) + for item in self.get("items") + if not item.get("type") and not item.get("is_legacy_scrap_item") + ) additional_cost_per_qty = self.total_additional_costs / total_qty for item in self.items: - if not item.get("is_scrap_item"): + if not item.get("type") and not item.get("is_legacy_scrap_item"): item.additional_cost_per_qty = additional_cost_per_qty else: for item in self.items: - if not item.get("is_scrap_item"): + if not item.get("type") and not item.get("is_legacy_scrap_item"): item.additional_cost_per_qty = 0 @frappe.whitelist() diff --git a/erpnext/controllers/subcontracting_inward_controller.py b/erpnext/controllers/subcontracting_inward_controller.py index 1a3ff66b825..6428ca10822 100644 --- a/erpnext/controllers/subcontracting_inward_controller.py +++ b/erpnext/controllers/subcontracting_inward_controller.py @@ -1,3 +1,5 @@ +from collections import defaultdict + import frappe from frappe import _, bold from frappe.query_builder import Case @@ -18,7 +20,7 @@ class SubcontractingInwardController: def on_submit_subcontracting_inward(self): self.update_inward_order_item() self.update_inward_order_received_items() - self.update_inward_order_scrap_items() + self.update_inward_order_secondary_items() self.create_stock_reservation_entries_for_inward() self.update_inward_order_status() @@ -28,7 +30,7 @@ class SubcontractingInwardController: self.validate_delivery() self.validate_receive_from_customer_cancel() self.update_inward_order_received_items() - self.update_inward_order_scrap_items() + self.update_inward_order_secondary_items() self.remove_reference_for_additional_items() self.update_inward_order_status() @@ -239,7 +241,8 @@ class SubcontractingInwardController: item for item in self.get("items") if not item.is_finished_item - and not item.is_scrap_item + and not item.type + and not item.is_legacy_scrap_item and frappe.get_cached_value("Item", item.item_code, "is_customer_provided_item") ] @@ -368,7 +371,9 @@ class SubcontractingInwardController: if self.subcontracting_inward_order: if self.purpose in ["Subcontracting Delivery", "Subcontracting Return", "Manufacture"]: for item in self.items: - if (item.is_finished_item or item.is_scrap_item) and item.valuation_rate == 0: + if ( + item.is_finished_item or item.type or item.is_legacy_scrap_item + ) and item.valuation_rate == 0: item.allow_zero_valuation_rate = 1 def validate_warehouse_(self): @@ -467,7 +472,7 @@ class SubcontractingInwardController: self.validate_delivery_on_save() else: for item in self.items: - if not item.is_scrap_item: + if not item.type and not item.is_legacy_scrap_item: delivered_qty, returned_qty = frappe.get_value( "Subcontracting Inward Order Item", item.scio_detail, @@ -519,7 +524,7 @@ class SubcontractingInwardController: if max_allowed_qty: max_allowed_qty = max_allowed_qty[0] else: - table = frappe.qb.DocType("Subcontracting Inward Order Scrap Item") + table = frappe.qb.DocType("Subcontracting Inward Order Secondary Item") query = ( frappe.qb.from_(table) .select((table.produced_qty - table.delivered_qty).as_("max_allowed_qty")) @@ -538,8 +543,8 @@ class SubcontractingInwardController: bold( frappe.get_cached_value( "Subcontracting Inward Order Item" - if not item.is_scrap_item - else "Subcontracting Inward Order Scrap Item", + if not item.type and not item.is_legacy_scrap_item + else "Subcontracting Inward Order Secondary Item", item.scio_detail, "stock_uom", ) @@ -590,9 +595,9 @@ class SubcontractingInwardController: ) for item in [item for item in self.items if not item.is_finished_item]: - if item.is_scrap_item: - scio_scrap_item = frappe.get_value( - "Subcontracting Inward Order Scrap Item", + if item.type or item.is_legacy_scrap_item: + scio_secondary_item = frappe.get_value( + "Subcontracting Inward Order Secondary Item", { "docstatus": 1, "item_code": item.item_code, @@ -603,12 +608,13 @@ class SubcontractingInwardController: as_dict=True, ) if ( - scio_scrap_item - and scio_scrap_item.delivered_qty > scio_scrap_item.produced_qty - item.transfer_qty + scio_secondary_item + and scio_secondary_item.delivered_qty + > scio_secondary_item.produced_qty - item.transfer_qty ): frappe.throw( _( - "Row #{0}: Cannot cancel this Manufacturing Stock Entry as quantity of Scrap Item {1} produced cannot be less than quantity delivered." + "Row #{0}: Cannot cancel this Manufacturing Stock Entry as quantity of Secondary Item {1} produced cannot be less than quantity delivered." ).format(item.idx, get_link_to_form("Item", item.item_code)) ) else: @@ -648,8 +654,8 @@ class SubcontractingInwardController: for item in self.items: doctype = ( "Subcontracting Inward Order Item" - if not item.is_scrap_item - else "Subcontracting Inward Order Scrap Item" + if not item.type and not item.is_legacy_scrap_item + else "Subcontracting Inward Order Secondary Item" ) frappe.db.set_value( doctype, @@ -763,7 +769,11 @@ class SubcontractingInwardController: customer_warehouse = frappe.get_cached_value( "Subcontracting Inward Order", self.subcontracting_inward_order, "customer_warehouse" ) - items = [item for item in self.items if not item.is_finished_item and not item.is_scrap_item] + items = [ + item + for item in self.items + if not item.is_finished_item and not item.type and not item.is_legacy_scrap_item + ] item_code_wh = frappe._dict( { ( @@ -860,24 +870,24 @@ class SubcontractingInwardController: doc.insert() doc.submit() - def update_inward_order_scrap_items(self): + def update_inward_order_secondary_items(self): if (scio := self.subcontracting_inward_order) and self.purpose == "Manufacture": - scrap_items_list = [item for item in self.items if item.is_scrap_item] - scrap_items = frappe._dict( - { - (item.item_code, item.t_warehouse): item.transfer_qty - if self._action == "submit" - else -item.transfer_qty - for item in scrap_items_list - } - ) - if scrap_items: - item_codes, warehouses = zip(*list(scrap_items.keys()), strict=True) + secondary_items_list = [item for item in self.items if item.type or item.is_legacy_scrap_item] + + secondary_items = defaultdict(float) + for item in secondary_items_list: + secondary_items[(item.item_code, item.t_warehouse)] += ( + item.transfer_qty if self._action == "submit" else -item.transfer_qty + ) + secondary_items = frappe._dict(secondary_items) + + if secondary_items: + item_codes, warehouses = zip(*list(secondary_items.keys()), strict=True) item_codes = list(item_codes) warehouses = list(warehouses) result = frappe.get_all( - "Subcontracting Inward Order Scrap Item", + "Subcontracting Inward Order Secondary Item", filters={ "item_code": ["in", item_codes], "warehouse": ["in", warehouses], @@ -890,7 +900,7 @@ class SubcontractingInwardController: ) if result: - scrap_item_dict = frappe._dict( + secondary_items_dict = frappe._dict( { (d.item_code, d.warehouse): frappe._dict( {"name": d.name, "produced_qty": d.produced_qty} @@ -900,40 +910,45 @@ class SubcontractingInwardController: ) deleted_docs = [] case_expr = Case() - table = frappe.qb.DocType("Subcontracting Inward Order Scrap Item") - for key, value in scrap_item_dict.items(): - if self._action == "cancel" and value.produced_qty - abs(scrap_items.get(key)) == 0: + table = frappe.qb.DocType("Subcontracting Inward Order Secondary Item") + for key, value in secondary_items_dict.items(): + if ( + self._action == "cancel" + and value.produced_qty - abs(secondary_items.get(key)) == 0 + ): deleted_docs.append(value.name) - frappe.delete_doc("Subcontracting Inward Order Scrap Item", value.name) + frappe.delete_doc("Subcontracting Inward Order Secondary Item", value.name) else: case_expr = case_expr.when( - table.name == value.name, value.produced_qty + scrap_items.get(key) + table.name == value.name, value.produced_qty + secondary_items.get(key) ) if final_list := list( - set([v.name for v in scrap_item_dict.values()]) - set(deleted_docs) + set([v.name for v in secondary_items_dict.values()]) - set(deleted_docs) ): frappe.qb.update(table).set(table.produced_qty, case_expr).where( (table.name.isin(final_list)) & (table.docstatus == 1) ).run() fg_item_code = next(fg for fg in self.items if fg.is_finished_item).item_code - for scrap_item in [ + for secondary_item in [ item - for item in scrap_items_list + for item in secondary_items_list if (item.item_code, item.t_warehouse) not in [(d.item_code, d.warehouse) for d in result] ]: doc = frappe.new_doc( - "Subcontracting Inward Order Scrap Item", + "Subcontracting Inward Order Secondary Item", parent=scio, parenttype="Subcontracting Inward Order", - parentfield="scrap_items", - idx=frappe.db.count("Subcontracting Inward Order Scrap Item", {"parent": scio}) + 1, - item_code=scrap_item.item_code, + parentfield="secondary_items", + idx=frappe.db.count("Subcontracting Inward Order Secondary Item", {"parent": scio}) + + 1, + item_code=secondary_item.item_code, fg_item_code=fg_item_code, - stock_uom=scrap_item.stock_uom, - warehouse=scrap_item.t_warehouse, - produced_qty=scrap_item.transfer_qty, + stock_uom=secondary_item.stock_uom, + warehouse=secondary_item.t_warehouse, + produced_qty=secondary_item.transfer_qty, + type=secondary_item.type, delivered_qty=0, reference_name=frappe.get_value( "Work Order", self.work_order, "subcontracting_inward_order_item" @@ -965,7 +980,7 @@ class SubcontractingInwardController: and ( not frappe.db.exists("Subcontracting Inward Order Received Item", item.scio_detail) and not frappe.db.exists("Subcontracting Inward Order Item", item.scio_detail) - and not frappe.db.exists("Subcontracting Inward Order Scrap Item", item.scio_detail) + and not frappe.db.exists("Subcontracting Inward Order Secondary Item", item.scio_detail) ) ] for item in items: diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index 465b318d09b..0dbacb3c22d 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -501,8 +501,8 @@ class TestSubcontractingController(ERPNextTestSuite): scr1.items[0].qty = 2 add_second_row_in_scr(scr1) scr1.flags.ignore_mandatory = True - scr1.save() scr1.set_missing_values() + scr1.save() scr1.submit() for _key, value in get_supplied_items(scr1).items(): @@ -513,8 +513,8 @@ class TestSubcontractingController(ERPNextTestSuite): scr2.items[0].qty = 2 add_second_row_in_scr(scr2) scr2.flags.ignore_mandatory = True - scr2.save() scr2.set_missing_values() + scr2.save() scr2.submit() for _key, value in get_supplied_items(scr2).items(): @@ -523,8 +523,8 @@ class TestSubcontractingController(ERPNextTestSuite): scr3 = make_subcontracting_receipt(sco.name) scr3.items[0].qty = 2 scr3.flags.ignore_mandatory = True - scr3.save() scr3.set_missing_values() + scr3.save() scr3.submit() for _key, value in get_supplied_items(scr3).items(): @@ -1164,6 +1164,54 @@ class TestSubcontractingController(ERPNextTestSuite): self.assertEqual([item.rm_item_code for item in sco.supplied_items], expected) + def test_co_by_product(self): + frappe.set_value("UOM", "Nos", "must_be_whole_number", 0) + + fg_item = make_item("FG Item", properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name + rm_item = make_item("RM Item", properties={"is_stock_item": 1}).name + scrap_item = make_item("Scrap Item", properties={"is_stock_item": 1}).name + make_bom( + item=fg_item, raw_materials=[rm_item], scrap_items=[scrap_item], process_loss_percentage=10 + ).name + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 11", + "qty": 5, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 5, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.get_secondary_items() + scr1.save() + + self.assertEqual(scr1.items[0].received_qty, 5) + self.assertEqual(scr1.items[0].process_loss_qty, 0.5) + self.assertEqual(scr1.items[0].qty, 4.5) + self.assertEqual(scr1.items[0].rate, 200) + self.assertEqual(scr1.items[0].amount, 900) + + self.assertEqual(scr1.items[1].item_code, scrap_item) + self.assertEqual(scr1.items[1].received_qty, 5) + self.assertEqual(scr1.items[1].process_loss_qty, 0.5) + self.assertEqual(scr1.items[1].qty, 4.5) + self.assertEqual(flt(scr1.items[1].rate, 3), 11.111) + self.assertEqual(scr1.items[1].amount, 50) + + frappe.set_value("UOM", "Nos", "must_be_whole_number", 1) + def add_second_row_in_scr(scr): item_dict = {} diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 454f1934e13..1dc64997198 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -620,10 +620,10 @@ erpnext.bom.BomController = class BomController extends erpnext.TransactionContr } item_code(doc, cdt, cdn) { - var scrap_items = false; + let secondary_items = false; var child = locals[cdt][cdn]; - if (child.doctype == "BOM Scrap Item") { - scrap_items = true; + if (child.doctype == "BOM Secondary Item") { + secondary_items = true; } if (child.bom_no) { @@ -634,7 +634,7 @@ erpnext.bom.BomController = class BomController extends erpnext.TransactionContr child.do_not_explode = 1; } - get_bom_material_detail(doc, cdt, cdn, scrap_items); + get_bom_material_detail(doc, cdt, cdn, secondary_items); } buying_price_list(doc) { @@ -683,7 +683,7 @@ cur_frm.cscript.is_default = function (doc) { if (doc.is_default) cur_frm.set_value("is_active", 1); }; -var get_bom_material_detail = function (doc, cdt, cdn, scrap_items) { +var get_bom_material_detail = function (doc, cdt, cdn, secondary_items) { if (!doc.company) { frappe.throw({ message: __("Please select a Company first."), title: __("Mandatory") }); } @@ -697,7 +697,6 @@ var get_bom_material_detail = function (doc, cdt, cdn, scrap_items) { company: doc.company, item_code: d.item_code, bom_no: d.bom_no != null ? d.bom_no : "", - scrap_items: scrap_items, qty: d.qty, stock_qty: d.stock_qty, include_item_in_manufacturing: d.include_item_in_manufacturing, @@ -706,15 +705,15 @@ var get_bom_material_detail = function (doc, cdt, cdn, scrap_items) { conversion_factor: d.conversion_factor, sourced_by_supplier: d.sourced_by_supplier, do_not_explode: d.do_not_explode, + fetch_rate: !secondary_items, }, callback: function (r) { $.extend(d, r.message); refresh_field("items"); - refresh_field("scrap_items"); + refresh_field("secondary_items"); doc = locals[doc.doctype][doc.name]; erpnext.bom.calculate_rm_cost(doc); - erpnext.bom.calculate_scrap_materials_cost(doc); erpnext.bom.calculate_total(doc); }, freeze: true, @@ -724,20 +723,18 @@ var get_bom_material_detail = function (doc, cdt, cdn, scrap_items) { cur_frm.cscript.qty = function (doc) { erpnext.bom.calculate_rm_cost(doc); - erpnext.bom.calculate_scrap_materials_cost(doc); erpnext.bom.calculate_total(doc); }; cur_frm.cscript.rate = function (doc, cdt, cdn) { var d = locals[cdt][cdn]; - const is_scrap_item = cdt == "BOM Scrap Item"; + const is_secondary_item = cdt == "BOM Secondary Item"; if (d.bom_no) { frappe.msgprint(__("You cannot change the rate if BOM is mentioned against any Item.")); - get_bom_material_detail(doc, cdt, cdn, is_scrap_item); + get_bom_material_detail(doc, cdt, cdn, is_secondary_item); } else { erpnext.bom.calculate_rm_cost(doc); - erpnext.bom.calculate_scrap_materials_cost(doc); erpnext.bom.calculate_total(doc); } }; @@ -745,7 +742,6 @@ cur_frm.cscript.rate = function (doc, cdt, cdn) { erpnext.bom.update_cost = function (doc) { erpnext.bom.calculate_op_cost(doc); erpnext.bom.calculate_rm_cost(doc); - erpnext.bom.calculate_scrap_materials_cost(doc); erpnext.bom.calculate_total(doc); }; @@ -804,34 +800,11 @@ erpnext.bom.calculate_rm_cost = function (doc) { cur_frm.set_value("base_raw_material_cost", base_total_rm_cost); }; -// sm : scrap material -erpnext.bom.calculate_scrap_materials_cost = function (doc) { - var sm = doc.scrap_items || []; - var total_sm_cost = 0; - var base_total_sm_cost = 0; - - for (var i = 0; i < sm.length; i++) { - var base_rate = flt(sm[i].rate) * flt(doc.conversion_rate); - var amount = flt(sm[i].rate) * flt(sm[i].stock_qty); - var base_amount = amount * flt(doc.conversion_rate); - - frappe.model.set_value("BOM Scrap Item", sm[i].name, "base_rate", base_rate); - frappe.model.set_value("BOM Scrap Item", sm[i].name, "amount", amount); - frappe.model.set_value("BOM Scrap Item", sm[i].name, "base_amount", base_amount); - - total_sm_cost += amount; - base_total_sm_cost += base_amount; - } - - cur_frm.set_value("scrap_material_cost", total_sm_cost); - cur_frm.set_value("base_scrap_material_cost", base_total_sm_cost); -}; - // Calculate Total Cost erpnext.bom.calculate_total = function (doc) { - var total_cost = flt(doc.operating_cost) + flt(doc.raw_material_cost) - flt(doc.scrap_material_cost); + var total_cost = flt(doc.operating_cost) + flt(doc.raw_material_cost) - flt(doc.secondary_items_cost); var base_total_cost = - flt(doc.base_operating_cost) + flt(doc.base_raw_material_cost) - flt(doc.base_scrap_material_cost); + flt(doc.base_operating_cost) + flt(doc.base_raw_material_cost) - flt(doc.base_secondary_items_cost); cur_frm.set_value("total_cost", total_cost); cur_frm.set_value("base_total_cost", base_total_cost); @@ -986,7 +959,7 @@ frappe.tour["BOM"] = [ }, ]; -frappe.ui.form.on("BOM Scrap Item", { +frappe.ui.form.on("BOM Secondary Item", { item_code(frm, cdt, cdn) { const { item_code } = locals[cdt][cdn]; }, @@ -1007,7 +980,7 @@ function trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code) { const row = locals[cdt][cdn]; row.stock_qty = (frm.doc.quantity * data.percent) / 100; row.qty = row.stock_qty / (row.conversion_factor || 1); - refresh_field("scrap_items"); + refresh_field("secondary_items"); }, __("Set Process Loss Item Quantity"), __("Set Quantity") diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 491920a0f29..8574e58a498 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -16,6 +16,14 @@ "allow_alternative_item", "set_rate_of_sub_assembly_item_based_on_bom", "is_phantom_bom", + "cost_allocation_section", + "cost_allocation_per", + "column_break_srby", + "cost_allocation", + "process_loss_section", + "process_loss_percentage", + "column_break_ssj2", + "process_loss_qty", "currency_detail", "rm_cost_as_per", "buying_price_list", @@ -38,21 +46,16 @@ "operations", "materials_section", "items", - "scrap_section", - "scrap_items_section", - "scrap_items", - "process_loss_section", - "process_loss_percentage", - "column_break_ssj2", - "process_loss_qty", + "secondary_items_tab", + "secondary_items", "costing", "operating_cost", "raw_material_cost", - "scrap_material_cost", + "secondary_items_cost", "cb1", "base_operating_cost", "base_raw_material_cost", - "base_scrap_material_cost", + "base_secondary_items_cost", "column_break_26", "total_cost", "base_total_cost", @@ -298,19 +301,6 @@ "options": "BOM Item", "reqd": 1 }, - { - "collapsible": 1, - "depends_on": "eval:!doc.is_phantom_bom", - "fieldname": "scrap_section", - "fieldtype": "Tab Break", - "label": "Scrap & Process Loss" - }, - { - "fieldname": "scrap_items", - "fieldtype": "Table", - "label": "Scrap Items", - "options": "BOM Scrap Item" - }, { "fieldname": "costing", "fieldtype": "Tab Break", @@ -332,15 +322,6 @@ "options": "currency", "read_only": 1 }, - { - "depends_on": "eval:!doc.is_phantom_bom", - "fieldname": "scrap_material_cost", - "fieldtype": "Currency", - "label": "Scrap Material Cost", - "options": "currency", - "print_hide": 1, - "read_only": 1 - }, { "fieldname": "cb1", "fieldtype": "Column Break" @@ -362,15 +343,6 @@ "print_hide": 1, "read_only": 1 }, - { - "depends_on": "eval:!doc.is_phantom_bom", - "fieldname": "base_scrap_material_cost", - "fieldtype": "Currency", - "label": "Scrap Material Cost(Company Currency)", - "no_copy": 1, - "options": "Company:company:default_currency", - "read_only": 1 - }, { "fieldname": "total_cost", "fieldtype": "Currency", @@ -602,12 +574,6 @@ "fieldname": "column_break_ivyw", "fieldtype": "Column Break" }, - { - "fieldname": "scrap_items_section", - "fieldtype": "Section Break", - "hide_border": 1, - "label": "Scrap Items" - }, { "default": "0", "fieldname": "fg_based_operating_cost", @@ -706,6 +672,59 @@ "fieldname": "quality_inspection_tab", "fieldtype": "Tab Break", "label": "Quality Inspection" + }, + { + "fieldname": "secondary_items", + "fieldtype": "Table", + "label": "Secondary Items", + "options": "BOM Secondary Item" + }, + { + "depends_on": "eval:!doc.is_phantom_bom", + "fieldname": "secondary_items_cost", + "fieldtype": "Currency", + "label": "Secondary Items Cost", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:!doc.is_phantom_bom", + "fieldname": "base_secondary_items_cost", + "fieldtype": "Currency", + "label": "Secondary Items Cost (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "secondary_items_tab", + "fieldtype": "Tab Break", + "label": "Secondary Items" + }, + { + "fieldname": "cost_allocation_section", + "fieldtype": "Section Break", + "label": "Cost Allocation" + }, + { + "fieldname": "column_break_srby", + "fieldtype": "Column Break" + }, + { + "fieldname": "cost_allocation", + "fieldtype": "Currency", + "label": "Cost Allocation", + "non_negative": 1, + "options": "currency", + "read_only": 1 + }, + { + "default": "100", + "fieldname": "cost_allocation_per", + "fieldtype": "Percent", + "label": "% Cost Allocation", + "non_negative": 1 } ], "icon": "fa fa-sitemap", @@ -713,7 +732,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2026-02-06 17:23:15.255301", + "modified": "2026-02-26 14:13:34.040181", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 2ee62b06ad5..a231eee9d84 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -113,19 +113,21 @@ class BOM(WebsiteGenerator): from erpnext.manufacturing.doctype.bom_explosion_item.bom_explosion_item import BOMExplosionItem from erpnext.manufacturing.doctype.bom_item.bom_item import BOMItem from erpnext.manufacturing.doctype.bom_operation.bom_operation import BOMOperation - from erpnext.manufacturing.doctype.bom_scrap_item.bom_scrap_item import BOMScrapItem + from erpnext.manufacturing.doctype.bom_secondary_item.bom_secondary_item import BOMSecondaryItem allow_alternative_item: DF.Check amended_from: DF.Link | None base_operating_cost: DF.Currency base_raw_material_cost: DF.Currency - base_scrap_material_cost: DF.Currency + base_secondary_items_cost: DF.Currency base_total_cost: DF.Currency bom_creator: DF.Link | None bom_creator_item: DF.Data | None buying_price_list: DF.Link | None company: DF.Link conversion_rate: DF.Float + cost_allocation: DF.Currency + cost_allocation_per: DF.Percent currency: DF.Link default_source_warehouse: DF.Link | None default_target_warehouse: DF.Link | None @@ -155,8 +157,8 @@ class BOM(WebsiteGenerator): rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List"] route: DF.SmallText | None routing: DF.Link | None - scrap_items: DF.Table[BOMScrapItem] - scrap_material_cost: DF.Currency + secondary_items: DF.Table[BOMSecondaryItem] + secondary_items_cost: DF.Currency set_rate_of_sub_assembly_item_based_on_bom: DF.Check show_in_website: DF.Check show_items: DF.Check @@ -284,7 +286,7 @@ class BOM(WebsiteGenerator): self.set_plc_conversion_rate() self.validate_uom_is_interger() self.set_bom_material_details() - self.set_bom_scrap_items_detail() + self.set_secondary_items_details() self.validate_materials() self.validate_transfer_against() self.set_routing_operations() @@ -294,9 +296,12 @@ class BOM(WebsiteGenerator): self.update_stock_qty() self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False) self.set_process_loss_qty() - self.validate_scrap_items() + self.validate_uoms() self.set_default_uom() self.validate_semi_finished_goods() + self.validate_secondary_items() + self.set_fg_cost_allocation() + self.validate_total_cost_allocation() if self.docstatus == 1: self.validate_raw_materials_of_operation() @@ -326,6 +331,22 @@ class BOM(WebsiteGenerator): ), ) + def validate_secondary_items(self): + for item in self.secondary_items: + if not item.qty: + frappe.throw( + _("Row #{0}: Quantity should be greater than 0 for {1} Item {2}").format( + item.idx, item.type, get_link_to_form("Item", item.item_code) + ) + ) + + if item.process_loss_per >= 100: + frappe.throw( + _("Row #{0}: Process Loss Percentage should be less than 100% for {1} Item {2}").format( + item.idx, item.type, get_link_to_form("Item", item.item_code) + ) + ) + def validate_raw_materials_of_operation(self): if not self.track_semi_finished_goods or not self.operations: return @@ -401,6 +422,24 @@ class BOM(WebsiteGenerator): doc = frappe.get_doc("BOM Creator", self.bom_creator) doc.set_status(save=True) + def set_fg_cost_allocation(self): + total_secondary_items_per = 0 + for item in self.secondary_items: + total_secondary_items_per += item.cost_allocation_per + + if self.cost_allocation_per == 100 and total_secondary_items_per: + self.cost_allocation_per -= total_secondary_items_per + + self.cost_allocation = self.raw_material_cost * (self.cost_allocation_per / 100) + + def validate_total_cost_allocation(self): + total_cost_allocation_per = self.cost_allocation_per + for item in self.secondary_items: + total_cost_allocation_per += item.cost_allocation_per + + if total_cost_allocation_per != 100: + frappe.throw(_("Cost allocation between finished goods and secondary items should equal 100%")) + def on_update_after_submit(self): self.validate_bom_links() self.manage_default_bom() @@ -462,6 +501,7 @@ class BOM(WebsiteGenerator): "conversion_factor": item.conversion_factor, "sourced_by_supplier": item.sourced_by_supplier, "do_not_explode": item.do_not_explode, + "fetch_rate": True, } ) @@ -469,13 +509,13 @@ class BOM(WebsiteGenerator): if not item.get(r): item.set(r, ret[r]) - def set_bom_scrap_items_detail(self): - for item in self.get("scrap_items"): + def set_secondary_items_details(self): + for item in self.get("secondary_items"): args = { "item_code": item.item_code, "company": self.company, - "scrap_items": True, - "bom_no": "", + "uom": item.uom, + "fetch_rate": False, } ret = self.get_bom_material_detail(args) for key, value in ret.items(): @@ -495,7 +535,7 @@ class BOM(WebsiteGenerator): item = self.get_item_det(args["item_code"]) - args["bom_no"] = args["bom_no"] or item and cstr(item["default_bom"]) or "" + args["bom_no"] = args.get("bom_no") or item and cstr(item["default_bom"]) or "" args["transfer_for_manufacture"] = ( cstr(args.get("include_item_in_manufacturing", "")) or item @@ -504,7 +544,7 @@ class BOM(WebsiteGenerator): ) args.update(item) - rate = self.get_rm_rate(args) + rate = self.get_rm_rate(args) if args.get("fetch_rate") else 0 ret_item = { "item_name": item and args["item_name"] or "", "description": item and args["description"] or "", @@ -546,9 +586,7 @@ class BOM(WebsiteGenerator): if not self.rm_cost_as_per: self.rm_cost_as_per = "Valuation Rate" - if arg.get("scrap_items"): - rate = get_valuation_rate(arg) - elif arg: + if arg: # Customer Provided parts and Supplier sourced parts will have zero rate if not frappe.db.get_value("Item", arg["item_code"], "is_customer_provided_item") and not arg.get( "sourced_by_supplier" @@ -688,7 +726,7 @@ class BOM(WebsiteGenerator): ) def update_stock_qty(self): - for m in self.get("items"): + for m in self.get("items") + self.get("secondary_items"): if not m.conversion_factor: m.conversion_factor = flt(get_conversion_factor(m.item_code, m.uom)["conversion_factor"]) if m.uom and m.qty: @@ -889,16 +927,16 @@ class BOM(WebsiteGenerator): """Calculate bom totals""" self.calculate_op_cost(update_hour_rate) self.calculate_rm_cost(save=save_updates) - self.calculate_sm_cost(save=save_updates) + self.calculate_secondary_items_costs(save=save_updates) if save_updates: # not via doc event, table is not regenerated and needs updation self.calculate_exploded_cost() old_cost = self.total_cost - self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost + self.total_cost = self.operating_cost + self.raw_material_cost - self.secondary_items_cost self.base_total_cost = ( - self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost + self.base_operating_cost + self.base_raw_material_cost - self.base_secondary_items_cost ) if self.total_cost != old_cost: @@ -997,29 +1035,24 @@ class BOM(WebsiteGenerator): self.raw_material_cost = total_rm_cost self.base_raw_material_cost = base_total_rm_cost - def calculate_sm_cost(self, save=False): + def calculate_secondary_items_costs(self, save=False): """Fetch RM rate as per today's valuation rate and calculate totals""" total_sm_cost = 0 base_total_sm_cost = 0 + precision = self.precision("raw_material_cost") - for d in self.get("scrap_items"): - d.base_rate = flt(d.rate, d.precision("rate")) * flt( - self.conversion_rate, self.precision("conversion_rate") - ) - d.amount = flt( - flt(d.rate, d.precision("rate")) * flt(d.stock_qty, d.precision("stock_qty")), - d.precision("amount"), - ) - d.base_amount = flt(d.amount, d.precision("amount")) * flt( - self.conversion_rate, self.precision("conversion_rate") - ) - total_sm_cost += d.amount - base_total_sm_cost += d.base_amount - if save: - d.db_update() + for d in self.get("secondary_items"): + if not d.is_legacy: + d.cost = flt(self.raw_material_cost * (d.cost_allocation_per / 100), precision) + d.base_cost = flt(d.cost * self.conversion_rate, precision) - self.scrap_material_cost = total_sm_cost - self.base_scrap_material_cost = base_total_sm_cost + total_sm_cost += d.cost + base_total_sm_cost += d.base_cost + if save: + d.db_update() + + self.secondary_items_cost = total_sm_cost + self.base_secondary_items_cost = base_total_sm_cost def calculate_exploded_cost(self): "Set exploded row cost from it's parent BOM." @@ -1221,16 +1254,29 @@ class BOM(WebsiteGenerator): if self.process_loss_percentage: self.process_loss_qty = flt(self.quantity) * flt(self.process_loss_percentage) / 100 - def validate_scrap_items(self): - must_be_whole_number = frappe.get_value("UOM", self.uom, "must_be_whole_number") + for item in self.secondary_items: + item.process_loss_qty = flt( + item.stock_qty * (item.process_loss_per / 100), self.precision("quantity") + ) - if self.process_loss_percentage and self.process_loss_percentage > 100: + def validate_uoms(self): + self.validate_uom(self.item, self.uom, self.process_loss_percentage, self.process_loss_qty) + for item in self.secondary_items: + self.validate_uom(item.item_code, item.stock_uom, item.process_loss_per, item.process_loss_qty) + + def validate_uom(self, item_code, uom, process_loss_per, process_loss_qty): + must_be_whole_number = frappe.get_value("UOM", uom, "must_be_whole_number") + + if process_loss_per and process_loss_per > 100: frappe.throw(_("Process Loss Percentage cannot be greater than 100")) - if self.process_loss_qty and must_be_whole_number and self.process_loss_qty % 1 != 0: - msg = f"Item: {frappe.bold(self.item)} with Stock UOM: {frappe.bold(self.uom)} can't have fractional process loss qty as UOM {frappe.bold(self.uom)} is a whole Number." + if process_loss_qty and must_be_whole_number and process_loss_qty % 1 != 0: + msg = f"Item: {frappe.bold(item_code)} with Stock UOM: {frappe.bold(uom)} can't have fractional process loss qty as UOM {frappe.bold(uom)} is a whole Number." frappe.throw(msg, title=_("Invalid Process Loss Configuration")) + def has_scrap_items(self): + return any(d.get("type") == "Scrap" or d.get("is_legacy") for d in self.get("secondary_items")) + def get_bom_item_rate(args, bom_doc): if bom_doc.rm_cost_as_per == "Valuation Rate": @@ -1332,7 +1378,7 @@ def get_bom_items_as_dict( company, qty=1, fetch_exploded=1, - fetch_scrap_items=0, + fetch_secondary_items=0, include_non_stock_items=False, fetch_qty_in_stock_uom=True, ): @@ -1343,7 +1389,7 @@ def get_bom_items_as_dict( fetch_exploded = 0 group_by_cond = "group by item_code, operation_row_id, stock_uom" - if fetch_scrap_items: + if fetch_secondary_items: fetch_exploded = 0 group_by_cond = "group by item_code" @@ -1355,8 +1401,6 @@ def get_bom_items_as_dict( sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * %(qty)s as qty, item.image, bom.project, - bom_item.rate, - sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * bom_item.rate * %(qty)s as amount, item.stock_uom, item.item_group, item.allow_alternative_item, @@ -1388,17 +1432,18 @@ def get_bom_items_as_dict( group_by_cond=group_by_cond, select_columns=""", bom_item.source_warehouse, bom_item.operation, bom_item.include_item_in_manufacturing, bom_item.description, bom_item.rate, bom_item.sourced_by_supplier, + sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * bom_item.rate * %(qty)s as amount, (Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""", ) items = frappe.db.sql( query, {"parent": bom, "qty": qty, "bom": bom, "company": company}, as_dict=True ) - elif fetch_scrap_items: + elif fetch_secondary_items: query = query.format( - table="BOM Scrap Item", + table="BOM Secondary Item", where_conditions=")", - select_columns=", item.description", + select_columns=", item.description, bom_item.cost_allocation_per, bom_item.process_loss_per, bom_item.type, bom_item.name, bom_item.is_legacy", is_stock_item=is_stock_item, qty_field="stock_qty", group_by_cond=group_by_cond, @@ -1411,8 +1456,9 @@ def get_bom_items_as_dict( where_conditions="or bom_item.is_phantom_item)", is_stock_item=is_stock_item, qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty", - select_columns=""", bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse, + select_columns=""", bom_item.rate, bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse, bom_item.operation, bom_item.include_item_in_manufacturing, bom_item.sourced_by_supplier, + sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * bom_item.rate * %(qty)s as amount, bom_item.description, bom_item.base_rate as rate, bom_item.operation_row_id, bom_item.is_phantom_item , bom_item.bom_no """, group_by_cond=group_by_cond, ) @@ -1432,7 +1478,7 @@ def get_bom_items_as_dict( company, qty=item.get("qty"), fetch_exploded=fetch_exploded, - fetch_scrap_items=fetch_scrap_items, + fetch_secondary_items=fetch_secondary_items, include_non_stock_items=include_non_stock_items, fetch_qty_in_stock_uom=fetch_qty_in_stock_uom, ) @@ -1482,7 +1528,7 @@ def validate_bom_no(item, bom_no): for d in bom.items: if d.item_code.lower() == item.lower(): rm_item_exists = True - for d in bom.scrap_items: + for d in bom.secondary_items: if d.item_code.lower() == item.lower(): rm_item_exists = True if ( @@ -1773,7 +1819,7 @@ def get_bom_diff(bom1, bom2): identifiers = { "operations": "operation", "items": "item_code", - "scrap_items": "item_code", + "secondary_items": "item_code", "exploded_items": "item_code", } @@ -1919,9 +1965,9 @@ def get_op_cost_from_sub_assemblies(bom_no, op_cost=0): return op_cost -def get_scrap_items_from_sub_assemblies(bom_no, company, qty, scrap_items=None): - if not scrap_items: - scrap_items = {} +def get_secondary_items_from_sub_assemblies(bom_no, company, qty, secondary_items=None): + if not secondary_items: + secondary_items = {} bom_items = frappe.get_all( "BOM Item", @@ -1935,9 +1981,9 @@ def get_scrap_items_from_sub_assemblies(bom_no, company, qty, scrap_items=None): continue qty = flt(row.qty) * flt(qty) - items = get_bom_items_as_dict(row.bom_no, company, qty=qty, fetch_exploded=0, fetch_scrap_items=1) - scrap_items.update(items) + items = get_bom_items_as_dict(row.bom_no, company, qty=qty, fetch_exploded=0, fetch_secondary_items=1) + secondary_items.update(items) - get_scrap_items_from_sub_assemblies(row.bom_no, company, qty, scrap_items) + get_secondary_items_from_sub_assemblies(row.bom_no, company, qty, secondary_items) - return scrap_items + return secondary_items diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 68a29d7da4e..3296559afc5 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -895,7 +895,7 @@ def create_bom_with_process_loss_item( if scrap_qty: bom_doc.append( - "scrap_items", + "secondary_items", { "item_code": fg_item.item_code, "qty": scrap_qty, diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json index 27752d85119..7c5c41fec19 100644 --- a/erpnext/manufacturing/doctype/bom/test_records.json +++ b/erpnext/manufacturing/doctype/bom/test_records.json @@ -36,15 +36,17 @@ "quantity": 1.0 }, { - "scrap_items":[ + "secondary_items":[ { "amount": 2000.0, - "doctype": "BOM Scrap Item", + "doctype": "BOM Secondary Item", "item_code": "_Test Item Home Desktop 100", - "parentfield": "scrap_items", + "parentfield": "secondary_items", "stock_qty": 1.0, "rate": 2000.0, - "stock_uom": "_Test UOM" + "stock_uom": "_Test UOM", + "type": "Scrap", + "is_legacy": 1 } ], "items": [ diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index e071dadb998..e3feac1061a 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -356,7 +356,6 @@ class BOMCreator(Document): { "bom_no": bom_no, "allow_alternative_item": 1, - "allow_scrap_items": not item.get("is_phantom_item"), "include_item_in_manufacturing": 1, } ) diff --git a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json deleted file mode 100644 index e782a882e8b..00000000000 --- a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "actions": [], - "creation": "2016-09-26 02:19:21.642081", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "item_code", - "column_break_2", - "item_name", - "quantity_and_rate", - "stock_qty", - "rate", - "amount", - "column_break_6", - "stock_uom", - "base_rate", - "base_amount" - ], - "fields": [ - { - "fieldname": "item_code", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Code", - "options": "Item", - "reqd": 1 - }, - { - "fieldname": "item_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Item Name" - }, - { - "fieldname": "quantity_and_rate", - "fieldtype": "Section Break", - "label": "Quantity and Rate" - }, - { - "fieldname": "stock_qty", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Qty", - "non_negative": 1, - "reqd": 1 - }, - { - "fieldname": "rate", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Rate", - "non_negative": 1, - "options": "currency" - }, - { - "fieldname": "amount", - "fieldtype": "Currency", - "label": "Amount", - "options": "currency", - "read_only": 1 - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "fieldname": "stock_uom", - "fieldtype": "Link", - "label": "Stock UOM", - "options": "UOM", - "read_only": 1 - }, - { - "fieldname": "base_rate", - "fieldtype": "Currency", - "label": "Basic Rate (Company Currency)", - "options": "Company:company:default_currency", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "base_amount", - "fieldtype": "Currency", - "label": "Basic Amount (Company Currency)", - "options": "Company:company:default_currency", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - } - ], - "istable": 1, - "links": [], - "modified": "2025-07-31 16:21:44.047007", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "BOM Scrap Item", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "row_format": "Dynamic", - "sort_field": "creation", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} diff --git a/erpnext/manufacturing/doctype/bom_scrap_item/__init__.py b/erpnext/manufacturing/doctype/bom_secondary_item/__init__.py similarity index 100% rename from erpnext/manufacturing/doctype/bom_scrap_item/__init__.py rename to erpnext/manufacturing/doctype/bom_secondary_item/__init__.py diff --git a/erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json b/erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json new file mode 100644 index 00000000000..39fa55123f4 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json @@ -0,0 +1,232 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2026-02-25 12:44:21.760154", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "type", + "rate", + "column_break_gres", + "is_legacy", + "section_break_sbnk", + "item_code", + "item_name", + "uom", + "column_break_atlf", + "qty", + "stock_uom", + "conversion_factor", + "stock_qty", + "section_break_yith", + "image", + "description", + "column_break_wsra", + "image_nygv", + "section_break_ielf", + "cost_allocation_per", + "process_loss_per", + "column_break_gtbl", + "cost", + "base_cost", + "process_loss_qty" + ], + "fields": [ + { + "depends_on": "eval:!doc.is_legacy", + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "mandatory_depends_on": "eval:!doc.is_legacy", + "options": "\nCo-Product\nBy-Product\nScrap\nAdditional Finished Good" + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1 + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Name", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "cost", + "fieldtype": "Currency", + "label": "Cost", + "no_copy": 1, + "non_negative": 1, + "options": "currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "section_break_sbnk", + "fieldtype": "Section Break" + }, + { + "fetch_from": "item_code.stock_uom", + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + }, + { + "fieldname": "column_break_atlf", + "fieldtype": "Column Break" + }, + { + "fieldname": "uom", + "fieldtype": "Link", + "in_list_view": 1, + "label": "UOM", + "options": "UOM", + "reqd": 1 + }, + { + "default": "1", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor", + "non_negative": 1, + "reqd": 1 + }, + { + "depends_on": "eval:!doc.is_legacy", + "fieldname": "section_break_ielf", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_gtbl", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_yith", + "fieldtype": "Section Break" + }, + { + "fetch_from": "item_code.image", + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image", + "read_only": 1 + }, + { + "fieldname": "column_break_wsra", + "fieldtype": "Column Break" + }, + { + "fieldname": "stock_qty", + "fieldtype": "Float", + "label": "Stock Qty", + "non_negative": 1, + "read_only": 1 + }, + { + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty", + "non_negative": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "cost_allocation_per", + "fieldtype": "Percent", + "label": "Cost Allocation %", + "non_negative": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "process_loss_per", + "fieldtype": "Percent", + "label": "Process Loss %", + "non_negative": 1, + "reqd": 1 + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description" + }, + { + "depends_on": "image", + "fieldname": "image_nygv", + "fieldtype": "Image", + "options": "image", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "base_cost", + "fieldtype": "Currency", + "hidden": 1, + "label": "Base Cost (Company Currency)", + "no_copy": 1, + "non_negative": 1, + "options": "Company:company:default_currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "column_break_gres", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "is_legacy", + "fieldname": "is_legacy", + "fieldtype": "Check", + "label": "Is Legacy", + "no_copy": 1, + "read_only": 1 + }, + { + "depends_on": "eval:doc.is_legacy", + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate", + "no_copy": 1, + "non_negative": 1, + "read_only": 1 + }, + { + "default": "0", + "fieldname": "process_loss_qty", + "fieldtype": "Float", + "label": "Process Loss Qty", + "no_copy": 1, + "non_negative": 1, + "read_only": 1, + "reqd": 1 + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2026-03-11 12:12:29.208031", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "BOM Secondary Item", + "owner": "Administrator", + "permissions": [], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.py b/erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.py similarity index 50% rename from erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.py rename to erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.py index 043bbc63b50..87748fe2269 100644 --- a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.py +++ b/erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.py @@ -1,11 +1,11 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - +# import frappe from frappe.model.document import Document -class BOMScrapItem(Document): +class BOMSecondaryItem(Document): # begin: auto-generated types # This code is auto-generated. Do not modify anything in this block. @@ -14,17 +14,26 @@ class BOMScrapItem(Document): if TYPE_CHECKING: from frappe.types import DF - amount: DF.Currency - base_amount: DF.Currency - base_rate: DF.Currency + base_cost: DF.Currency + conversion_factor: DF.Float + cost: DF.Currency + cost_allocation_per: DF.Percent + description: DF.TextEditor | None + image: DF.AttachImage | None + is_legacy: DF.Check item_code: DF.Link item_name: DF.Data | None parent: DF.Data parentfield: DF.Data parenttype: DF.Data + process_loss_per: DF.Percent + process_loss_qty: DF.Float + qty: DF.Float rate: DF.Currency stock_qty: DF.Float stock_uom: DF.Link | None + type: DF.Literal["", "Co-Product", "By-Product", "Scrap", "Additional Finished Good"] + uom: DF.Link # end: auto-generated types pass diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 9fb7dcb51b2..68d1e3e6214 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -23,7 +23,7 @@ frappe.ui.form.on("Job Card", { }; }); - frm.set_query("item_code", "scrap_items", () => { + frm.set_query("item_code", "secondary_items", () => { return { filters: { disabled: 0, @@ -104,7 +104,7 @@ frappe.ui.form.on("Job Card", { frm.doc.docstatus === 1 && !frm.doc.is_subcontracted && (frm.doc.skip_material_transfer || frm.doc.transferred_qty > 0) && - flt(frm.doc.for_quantity) + flt(frm.doc.process_loss_qty) > flt(frm.doc.manufactured_qty) + flt(frm.doc.manufactured_qty) + flt(frm.doc.process_loss_qty) < flt(frm.doc.for_quantity) ) { frm.add_custom_button(__("Make Stock Entry"), () => { frappe.confirm( @@ -278,8 +278,6 @@ frappe.ui.form.on("Job Card", { frm.trigger("complete_job_card"); }); } - - frm.trigger("make_dashboard"); } } diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 6b34eb7711a..728e8fc27ec 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -59,8 +59,8 @@ "time_logs", "section_break_21", "sub_operations", - "scrap_items_section", - "scrap_items", + "secondary_items_section", + "secondary_items", "corrective_operation_section", "for_job_card", "is_corrective_job_card", @@ -406,20 +406,6 @@ "options": "Batch", "read_only": 1 }, - { - "collapsible": 1, - "fieldname": "scrap_items_section", - "fieldtype": "Tab Break", - "label": "Scrap Items" - }, - { - "fieldname": "scrap_items", - "fieldtype": "Table", - "label": "Scrap Items", - "no_copy": 1, - "options": "Job Card Scrap Item", - "print_hide": 1 - }, { "fetch_from": "operation.quality_inspection_template", "fieldname": "quality_inspection_template", @@ -623,12 +609,26 @@ { "fieldname": "column_break_xhzg", "fieldtype": "Column Break" + }, + { + "fieldname": "secondary_items", + "fieldtype": "Table", + "label": "Secondary Items", + "no_copy": 1, + "options": "Job Card Secondary Item", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "secondary_items_section", + "fieldtype": "Tab Break", + "label": "Secondary Items" } ], "grid_page_length": 50, "is_submittable": 1, "links": [], - "modified": "2026-02-06 18:27:03.178783", + "modified": "2026-02-26 15:13:56.767070", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 0f4c9d569fa..a4eaec8e73f 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -71,7 +71,9 @@ class JobCard(Document): from erpnext.manufacturing.doctype.job_card_scheduled_time.job_card_scheduled_time import ( JobCardScheduledTime, ) - from erpnext.manufacturing.doctype.job_card_scrap_item.job_card_scrap_item import JobCardScrapItem + from erpnext.manufacturing.doctype.job_card_secondary_item.job_card_secondary_item import ( + JobCardSecondaryItem, + ) from erpnext.manufacturing.doctype.job_card_time_log.job_card_time_log import JobCardTimeLog actual_end_date: DF.Datetime | None @@ -110,7 +112,7 @@ class JobCard(Document): remarks: DF.SmallText | None requested_qty: DF.Float scheduled_time_logs: DF.Table[JobCardScheduledTime] - scrap_items: DF.Table[JobCardScrapItem] + secondary_items: DF.Table[JobCardSecondaryItem] semi_fg_bom: DF.Link | None sequence_id: DF.Int serial_and_batch_bundle: DF.Link | None @@ -199,6 +201,7 @@ class JobCard(Document): def set_manufactured_qty(self): table_name = "Stock Entry" + child_name = "Stock Entry Detail" if self.is_subcontracted: table_name = "Subcontracting Receipt Item" @@ -208,8 +211,13 @@ class JobCard(Document): if self.is_subcontracted: query = query.select(Sum(table.qty)) else: - query = query.select(Sum(table.fg_completed_qty)) - query = query.where(table.purpose == "Manufacture") + child = frappe.qb.DocType(child_name) + query = ( + query.join(child) + .on(table.name == child.parent) + .select(Sum(child.transfer_qty)) + .where((table.purpose == "Manufacture") & (child.is_finished_item == 1)) + ) qty = query.run()[0][0] or 0.0 self.manufactured_qty = flt(qty) @@ -267,25 +275,35 @@ class JobCard(Document): row.sub_operation = row.operation self.append("sub_operations", row) - def set_scrap_items(self): - if not self.semi_fg_bom: + def set_secondary_items(self): + if not self.semi_fg_bom and not self.bom_no: return items_dict = get_bom_items_as_dict( - self.semi_fg_bom, self.company, qty=self.for_quantity, fetch_exploded=0, fetch_scrap_items=1 + self.semi_fg_bom or self.bom_no, + self.company, + qty=self.for_quantity, + fetch_exploded=0, + fetch_secondary_items=1, ) for item_code, values in items_dict.items(): values = frappe._dict(values) + secondary_item = { + "item_code": item_code, + "stock_qty": values.qty, + "item_name": values.item_name, + "stock_uom": values.stock_uom, + "type": values.type, + "bom_secondary_item": values.name, + } - self.append( - "scrap_items", - { - "item_code": item_code, - "stock_qty": values.qty, - "item_name": values.item_name, - "stock_uom": values.stock_uom, - }, - ) + if not values.is_legacy: + secondary_item["stock_qty"] -= flt( + secondary_item["stock_qty"] * (values.process_loss_per / 100), + self.precision("for_quantity"), + ) + + self.append("secondary_items", secondary_item) def validate_time_logs(self, save=False): self.total_time_in_mins = 0.0 @@ -1181,7 +1199,7 @@ class JobCard(Document): def set_status(self, update_status=False): self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0] if self.finished_good and self.docstatus == 1: - if self.manufactured_qty >= self.for_quantity: + if (self.manufactured_qty + self.process_loss_qty) >= self.for_quantity: self.status = "Completed" elif self.transferred_qty > 0 or self.skip_material_transfer: self.status = "Work In Progress" @@ -1456,12 +1474,24 @@ class JobCard(Document): ) @frappe.whitelist() - def make_stock_entry_for_semi_fg_item(self, auto_submit=False): + def make_stock_entry_for_semi_fg_item(self, auto_submit: bool = False): + def get_consumed_process_loss(): + table = frappe.qb.DocType("Stock Entry") + query = ( + frappe.qb.from_(table) + .select(Sum(table.process_loss_qty)) + .where( + (table.purpose == "Manufacture") & (table.job_card == self.name) & (table.docstatus == 1) + ) + ) + return query.run()[0][0] or 0 + from erpnext.stock.doctype.stock_entry_type.stock_entry_type import ManufactureEntry ste = ManufactureEntry( { "for_quantity": self.for_quantity - self.manufactured_qty, + "process_loss_qty": max(self.process_loss_qty - get_consumed_process_loss(), 0), "job_card": self.name, "skip_material_transfer": self.skip_material_transfer, "backflush_from_wip_warehouse": self.backflush_from_wip_warehouse, @@ -1481,9 +1511,10 @@ class JobCard(Document): wo_doc = frappe.get_doc("Work Order", self.work_order) add_additional_cost(ste.stock_entry, wo_doc, self) - ste.stock_entry.set_scrap_items() + ste.stock_entry.pro_doc = frappe.get_doc("Work Order", self.work_order) + ste.stock_entry.set_secondary_items_from_job_card() for row in ste.stock_entry.items: - if row.is_scrap_item and not row.t_warehouse: + if (row.type or row.is_legacy_scrap_item) and not row.t_warehouse: row.t_warehouse = self.target_warehouse if auto_submit: diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 556d3911eb3..a25b6e1af3d 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -882,6 +882,193 @@ class TestJobCard(ERPNextTestSuite): s = frappe.get_doc(make_stock_entry_for_wo(wo_doc.name, "Manufacture", 6)) self.assertEqual(s.additional_costs[0].amount, 8) + def test_co_by_product_for_sfg_flow(self): + from erpnext.manufacturing.doctype.operation.test_operation import make_operation + + frappe.db.set_value("UOM", "Nos", "must_be_whole_number", 0) + + def create_bom(raw_material, finished_good, scrap_item, submit=True): + bom = frappe.new_doc("BOM") + bom.company = "_Test Company" + bom.item = finished_good + bom.quantity = 1 + bom.append("items", {"item_code": raw_material, "qty": 1}) + bom.append( + "secondary_items", + { + "item_code": scrap_item, + "qty": 1, + "process_loss_per": 10, + "cost_allocation_per": 5, + "type": "Scrap", + }, + ) + if submit: + bom.insert() + bom.submit() + + return bom + + rm1 = create_item("RM 1") + scrap1 = create_item("Scrap 1") + sfg = create_item("SFG 1") + sfg_bom = create_bom(rm1.name, sfg.name, scrap1.name) + + rm2 = create_item("RM 2") + fg1 = create_item("FG 1") + scrap2 = create_item("Scrap 2") + scrap_extra = create_item("Scrap Extra") + fg_bom = create_bom(rm2.name, fg1.name, scrap2.name, submit=False) + fg_bom.with_operations = 1 + fg_bom.track_semi_finished_goods = 1 + + operation1 = { + "operation": "Test Operation A", + "workstation": "_Test Workstation A", + "finished_good": sfg.name, + "bom_no": sfg_bom.name, + "finished_good_qty": 1, + "sequence_id": 1, + "time_in_mins": 30, + } + operation2 = { + "operation": "Test Operation B", + "workstation": "_Test Workstation A", + "finished_good": fg1.name, + "bom_no": fg_bom.name, + "finished_good_qty": 1, + "is_final_finished_good": 1, + "sequence_id": 2, + "time_in_mins": 30, + } + + make_workstation(operation1) + make_operation(operation1) + make_operation(operation2) + + fg_bom.append("operations", operation1) + fg_bom.append("operations", operation2) + fg_bom.append("items", {"item_code": sfg.name, "qty": 1, "uom": "Nos", "operation_row_id": 2}) + fg_bom.insert() + fg_bom.save() + fg_bom.submit() + + work_order = make_wo_order_test_record( + item=fg1.name, + qty=10, + source_warehouse="Stores - _TC", + fg_warehouse="Finished Goods - _TC", + bom_no=fg_bom.name, + skip_transfer=1, + do_not_save=True, + ) + + work_order.operations[0].time_in_mins = 60 + work_order.operations[1].time_in_mins = 60 + work_order.save() + work_order.submit() + + job_card = frappe.get_doc( + "Job Card", + frappe.db.get_value( + "Job Card", {"work_order": work_order.name, "operation": "Test Operation A"}, "name" + ), + ) + job_card.append( + "time_logs", + { + "from_time": "2009-01-01 12:06:25", + "to_time": "2009-01-01 12:37:25", + "completed_qty": job_card.for_quantity, + }, + ) + job_card.append( + "secondary_items", {"item_code": scrap_extra.name, "stock_qty": 5, "type": "Co-Product"} + ) + job_card.submit() + + for row in sfg_bom.items: + make_stock_entry( + item_code=row.item_code, + target="Stores - _TC", + qty=10, + basic_rate=100, + ) + + manufacturing_entry = frappe.get_doc(job_card.make_stock_entry_for_semi_fg_item()) + manufacturing_entry.submit() + + self.assertEqual(manufacturing_entry.items[2].item_code, scrap1.name) + self.assertEqual(manufacturing_entry.items[2].qty, 9) + self.assertEqual(flt(manufacturing_entry.items[2].basic_rate, 3), 5.556) + self.assertEqual(manufacturing_entry.items[3].item_code, scrap_extra.name) + self.assertEqual(manufacturing_entry.items[3].type, "Co-Product") + self.assertEqual(manufacturing_entry.items[3].qty, 5) + self.assertEqual(manufacturing_entry.items[3].basic_rate, 0) + + job_card = frappe.get_doc( + "Job Card", + frappe.db.get_value( + "Job Card", {"work_order": work_order.name, "operation": "Test Operation B"}, "name" + ), + ) + job_card.append( + "time_logs", + { + "from_time": "2009-02-01 12:06:25", + "to_time": "2009-02-01 12:37:25", + "completed_qty": job_card.for_quantity, + }, + ) + job_card.submit() + + for row in fg_bom.items: + make_stock_entry( + item_code=row.item_code, + target="Stores - _TC", + qty=10, + basic_rate=100, + ) + + manufacturing_entry = frappe.get_doc(job_card.make_stock_entry_for_semi_fg_item()) + manufacturing_entry.submit() + + self.assertEqual(manufacturing_entry.items[2].item_code, scrap2.name) + self.assertEqual(manufacturing_entry.items[2].qty, 9) + self.assertEqual(flt(manufacturing_entry.items[2].basic_rate, 3), 5.556) + + def test_secondary_items_without_sfg(self): + for row in frappe.get_doc("BOM", self.work_order.bom_no).items: + make_stock_entry( + item_code=row.item_code, + target="_Test Warehouse - _TC", + qty=10, + basic_rate=100, + ) + + job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name}) + job_card.append("secondary_items", {"item_code": "_Test Item", "stock_qty": 2, "type": "Scrap"}) + job_card.append( + "time_logs", + { + "from_time": "2009-01-01 12:06:25", + "to_time": "2009-01-01 12:37:25", + "completed_qty": job_card.for_quantity, + }, + ) + job_card.save() + job_card.submit() + + from erpnext.manufacturing.doctype.work_order.work_order import ( + make_stock_entry as make_stock_entry_for_wo, + ) + + s = frappe.get_doc(make_stock_entry_for_wo(self.work_order.name, "Manufacture")) + s.submit() + + self.assertEqual(s.items[3].item_code, "_Test Item") + self.assertEqual(s.items[3].transfer_qty, 2) + def create_bom_with_multiple_operations(): "Create a BOM with multiple operations and Material Transfer against Job Card" diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/__init__.py b/erpnext/manufacturing/doctype/job_card_secondary_item/__init__.py similarity index 100% rename from erpnext/manufacturing/doctype/job_card_scrap_item/__init__.py rename to erpnext/manufacturing/doctype/job_card_secondary_item/__init__.py diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json b/erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json similarity index 73% rename from erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json rename to erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json index fdb8ec44bdc..d9ac0e08ced 100644 --- a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json +++ b/erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json @@ -5,10 +5,12 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "type", + "description", + "column_break_3", "item_code", "item_name", - "column_break_3", - "description", + "bom_secondary_item", "quantity_and_rate", "stock_qty", "column_break_6", @@ -19,7 +21,7 @@ "fieldname": "item_code", "fieldtype": "Link", "in_list_view": 1, - "label": "Scrap Item Code", + "label": "Secondary Item Code", "options": "Item", "reqd": 1 }, @@ -28,7 +30,7 @@ "fieldname": "item_name", "fieldtype": "Data", "in_list_view": 1, - "label": "Scrap Item Name" + "label": "Secondary Item Name" }, { "fieldname": "column_break_3", @@ -65,20 +67,36 @@ "label": "Stock UOM", "options": "UOM", "read_only": 1 + }, + { + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "options": "Co-Product\nBy-Product\nScrap\nAdditional Finished Good", + "reqd": 1 + }, + { + "fieldname": "bom_secondary_item", + "fieldtype": "Data", + "hidden": 1, + "label": "BOM Secondary Item Reference", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-07-29 13:09:57.323835", + "modified": "2026-03-06 13:51:00.492621", "modified_by": "Administrator", "module": "Manufacturing", - "name": "Job Card Scrap Item", + "name": "Job Card Secondary Item", "owner": "Administrator", "permissions": [], "quick_entry": 1, + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py b/erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.py similarity index 78% rename from erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py rename to erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.py index e4b926efc07..3a71ab9d755 100644 --- a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py +++ b/erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.py @@ -4,7 +4,7 @@ from frappe.model.document import Document -class JobCardScrapItem(Document): +class JobCardSecondaryItem(Document): # begin: auto-generated types # This code is auto-generated. Do not modify anything in this block. @@ -13,6 +13,7 @@ class JobCardScrapItem(Document): if TYPE_CHECKING: from frappe.types import DF + bom_secondary_item: DF.Data | None description: DF.SmallText | None item_code: DF.Link item_name: DF.Data | None @@ -21,6 +22,7 @@ class JobCardScrapItem(Document): parenttype: DF.Data stock_qty: DF.Float stock_uom: DF.Link | None + type: DF.Literal["Co-Product", "By-Product", "Scrap", "Additional Finished Good"] # end: auto-generated types pass diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index 1a150dc864f..778334b96d0 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -36,7 +36,7 @@ "capacity_planning_for_days", "mins_between_operations", "other_settings_section", - "set_op_cost_and_scrap_from_sub_assemblies", + "set_op_cost_and_secondary_items_from_sub_assemblies", "column_break_23", "make_serial_no_batch_from_work_order" ], @@ -202,13 +202,6 @@ "fieldtype": "Check", "label": "Validate Components and Quantities Per BOM" }, - { - "default": "0", - "description": "To include sub-assembly costs and scrap items in Finished Goods on a work order without using a job card, when the 'Use Multi-Level BOM' option is enabled.", - "fieldname": "set_op_cost_and_scrap_from_sub_assemblies", - "fieldtype": "Check", - "label": "Set Operating Cost / Scrap Items From Sub-assemblies" - }, { "default": "0", "description": "Enabling this checkbox will force each Job Card Time Log to have From Time and To Time", @@ -237,6 +230,13 @@ "fieldname": "allow_editing_of_items_and_quantities_in_work_order", "fieldtype": "Check", "label": "Allow Editing of Items and Quantities in Work Order" + }, + { + "default": "0", + "description": "To include sub-assembly costs and secondary items in Finished Goods on a work order without using a job card, when the 'Use Multi-Level BOM' option is enabled.", + "fieldname": "set_op_cost_and_secondary_items_from_sub_assemblies", + "fieldtype": "Check", + "label": "Set Operating Cost / Secondary Items From Sub-assemblies" } ], "hide_toolbar": 0, @@ -244,7 +244,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-03-16 13:28:20.714576", + "modified": "2026-03-20 13:28:20.714576", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing Settings", diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py index e60a9627a21..2913d70395d 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py @@ -32,7 +32,7 @@ class ManufacturingSettings(Document): mins_between_operations: DF.Int overproduction_percentage_for_sales_order: DF.Percent overproduction_percentage_for_work_order: DF.Percent - set_op_cost_and_scrap_from_sub_assemblies: DF.Check + set_op_cost_and_secondary_items_from_sub_assemblies: DF.Check transfer_extra_materials_percentage: DF.Percent update_bom_costs_automatically: DF.Check validate_components_quantities_per_bom: DF.Check diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 4612c427714..5d7e2fa2b36 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -2875,6 +2875,7 @@ def make_bom(**args): "company": args.company or "_Test Company", "routing": args.routing, "with_operations": args.with_operations or 0, + "process_loss_percentage": args.process_loss_percentage or 0, } ) @@ -2896,6 +2897,23 @@ def make_bom(**args): }, ) + if args.scrap_items: + for item in args.scrap_items: + item_doc = frappe.get_doc("Item", item) + bom.append( + "secondary_items", + { + "type": "Scrap", + "item_code": item, + "item_name": item, + "uom": item_doc.stock_uom, + "stock_uom": item_doc.stock_uom, + "qty": args.scrap_qty or 1, + "cost_allocation_per": args.scrap_cost_allocation_per or 10, + "process_loss_per": args.scrap_process_loss_per or 10, + }, + ) + if not args.do_not_save: bom.insert(ignore_permissions=True) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index bea542b7bfa..81ee66ecb4f 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -329,7 +329,7 @@ class TestWorkOrder(ERPNextTestSuite): cint(bin1_on_stop_production.projected_qty) + 1, cint(self.bin1_at_start.projected_qty) ) - def test_scrap_material_qty(self): + def test_secondary_material_qty(self): wo_order = make_wo_order_test_record(planned_start_date=now(), qty=2) # add raw materials to stores @@ -354,15 +354,15 @@ class TestWorkOrder(ERPNextTestSuite): "Work Order", wo_order.name, ["scrap_warehouse", "qty", "produced_qty", "bom_no"], as_dict=1 ) - scrap_item_details = get_scrap_item_details(wo_order_details.bom_no) + secondary_item_details = get_secondary_item_details(wo_order_details.bom_no) self.assertEqual(wo_order_details.produced_qty, 2) for item in s.items: - if item.bom_no and item.item_code in scrap_item_details: + if item.bom_no and item.item_code in secondary_item_details: self.assertEqual(wo_order_details.scrap_warehouse, item.t_warehouse) self.assertEqual( - flt(wo_order_details.qty) * flt(scrap_item_details[item.item_code]), item.qty + flt(wo_order_details.qty) * flt(secondary_item_details[item.item_code]), item.qty ) def test_allow_overproduction(self): @@ -1015,7 +1015,7 @@ class TestWorkOrder(ERPNextTestSuite): self.assertEqual(wo.status, "Completed") @timeout(seconds=60) - def test_job_card_scrap_item(self): + def test_job_card_secondary_item(self): items = [ "Test FG Item for Scrap Item Test", "Test RM Item 1 for Scrap Item Test", @@ -1074,7 +1074,7 @@ class TestWorkOrder(ERPNextTestSuite): stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) for row in stock_entry.items: - if row.is_scrap_item: + if row.type or row.is_legacy_scrap_item: self.assertEqual(row.qty, 1) # Partial Job Card 1 with qty 10 @@ -1086,7 +1086,7 @@ class TestWorkOrder(ERPNextTestSuite): stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) for row in stock_entry.items: - if row.is_scrap_item: + if row.type or row.is_legacy_scrap_item: self.assertEqual(row.qty, 2) # Partial Job Card 2 with qty 10 @@ -2134,10 +2134,12 @@ class TestWorkOrder(ERPNextTestSuite): for row in se_doc.additional_costs: self.assertEqual(row.expense_account, operating_cost_account) - def test_op_cost_and_scrap_based_on_sub_assemblies(self): + def test_set_op_cost_and_secondary_items_from_sub_assemblies(self): # Make Sub Assembly BOM 1 - frappe.db.set_single_value("Manufacturing Settings", "set_op_cost_and_scrap_from_sub_assemblies", 1) + frappe.db.set_single_value( + "Manufacturing Settings", "set_op_cost_and_secondary_items_from_sub_assemblies", 1 + ) items = { "Test Final FG Item": 0, @@ -2169,16 +2171,20 @@ class TestWorkOrder(ERPNextTestSuite): se_doc.save() self.assertTrue(se_doc.additional_costs) - scrap_items = [] + secondary_items = [] for item in se_doc.items: - if item.is_scrap_item: - scrap_items.append(item.item_code) + if item.type or item.is_legacy_scrap_item: + secondary_items.append(item.item_code) - self.assertEqual(sorted(scrap_items), sorted(["Test Final Scrap Item 1", "Test Final Scrap Item 2"])) + self.assertEqual( + sorted(secondary_items), sorted(["Test Final Scrap Item 1", "Test Final Scrap Item 2"]) + ) for row in se_doc.additional_costs: self.assertEqual(row.amount, 3000) - frappe.db.set_single_value("Manufacturing Settings", "set_op_cost_and_scrap_from_sub_assemblies", 0) + frappe.db.set_single_value( + "Manufacturing Settings", "set_op_cost_and_secondary_items_from_sub_assemblies", 0 + ) @ERPNextTestSuite.change_settings( "Manufacturing Settings", {"material_consumption": 1, "get_rm_cost_from_consumption_entry": 1} @@ -3951,7 +3957,7 @@ def prepare_boms_for_sub_assembly_test(): do_not_submit=True, ) - bom.append("scrap_items", {"item_code": "Test Final Scrap Item 1", "qty": 1}) + bom.append("secondary_items", {"item_code": "Test Final Scrap Item 1", "qty": 1, "is_legacy": 1}) bom.submit() @@ -3964,7 +3970,7 @@ def prepare_boms_for_sub_assembly_test(): do_not_submit=True, ) - bom.append("scrap_items", {"item_code": "Test Final Scrap Item 2", "qty": 1}) + bom.append("secondary_items", {"item_code": "Test Final Scrap Item 2", "qty": 1, "is_legacy": 1}) bom.submit() @@ -4159,7 +4165,7 @@ def update_job_card(job_card, jc_qty=None, days=None): employee = frappe.db.get_value("Employee", {"status": "Active"}, "name") job_card_doc = frappe.get_doc("Job Card", job_card) job_card_doc.set( - "scrap_items", + "secondary_items", [ {"item_code": "Test RM Item 1 for Scrap Item Test", "stock_qty": 2}, {"item_code": "Test RM Item 2 for Scrap Item Test", "stock_qty": 2}, @@ -4199,17 +4205,17 @@ def update_job_card(job_card, jc_qty=None, days=None): job_card_doc.submit() -def get_scrap_item_details(bom_no): - scrap_items = {} +def get_secondary_item_details(bom_no): + secondary_items = {} for item in frappe.db.sql( - """select item_code, stock_qty from `tabBOM Scrap Item` + """select item_code, stock_qty from `tabBOM Secondary Item` where parent = %s""", bom_no, as_dict=1, ): - scrap_items[item.item_code] = item.stock_qty + secondary_items[item.item_code] = item.stock_qty - return scrap_items + return secondary_items def allow_overproduction(fieldname, percentage): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index f382d1dcb60..18b5be64c10 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -387,6 +387,7 @@ frappe.ui.form.on("Work Order", { args: { work_order: frm.doc.name, operations: selected_rows, + parent_bom: frm.doc.bom_no, }, callback: function () { frm.reload_doc(); diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index f9d380964bc..72fafa03edd 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -2356,7 +2356,7 @@ def check_if_scrap_warehouse_mandatory(bom_no): if bom_no: bom = frappe.get_doc("BOM", bom_no) - if len(bom.scrap_items) > 0: + if bom.has_scrap_items(): res["set_scrap_wh_mandatory"] = True return res @@ -2420,6 +2420,7 @@ def make_stock_entry( stock_entry.set_stock_entry_type() stock_entry.is_additional_transfer_entry = is_additional_transfer_entry stock_entry.get_items() + stock_entry.set_secondary_items_from_job_card() if purpose != "Disassemble": stock_entry.set_serial_no_batch_for_finished_good() @@ -2478,14 +2479,14 @@ def query_sales_order(doctype, txt, searchfield, start, page_len, filters) -> li @frappe.whitelist() -def make_job_card(work_order, operations): +def make_job_card(work_order: str, operations: str | list, parent_bom: str | None = None): if isinstance(operations, str): operations = json.loads(operations) work_order = frappe.get_doc("Work Order", work_order) for row in operations: row = frappe._dict(row) - row.update(get_operation_details(row.name, work_order)) + row.update(get_operation_details(row.name, work_order, parent_bom)) validate_operation_data(row) qty = row.get("qty") @@ -2495,7 +2496,7 @@ def make_job_card(work_order, operations): create_job_card(work_order, row, auto_create=True) -def get_operation_details(name, work_order): +def get_operation_details(name, work_order, parent_bom): for row in work_order.operations: if row.name == name: return { @@ -2505,7 +2506,7 @@ def get_operation_details(name, work_order): "fg_warehouse": row.fg_warehouse, "wip_warehouse": row.wip_warehouse, "finished_good": row.finished_good, - "bom_no": row.get("bom_no"), + "bom_no": row.get("bom_no") or parent_bom, "is_subcontracted": row.get("is_subcontracted"), } @@ -2640,8 +2641,9 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create work_order.transfer_material_against == "Job Card" and not work_order.skip_transfer ): doc.get_required_items() - if work_order.track_semi_finished_goods: - doc.set_scrap_items() + + if work_order.track_semi_finished_goods: + doc.set_secondary_items() if auto_create: doc.flags.ignore_mandatory = True diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4b1fc449473..8e36eaaed40 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -472,3 +472,4 @@ 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 +erpnext.patches.v16_0.co_by_product_patch diff --git a/erpnext/patches/v16_0/co_by_product_patch.py b/erpnext/patches/v16_0/co_by_product_patch.py new file mode 100644 index 00000000000..63f43e85b9e --- /dev/null +++ b/erpnext/patches/v16_0/co_by_product_patch.py @@ -0,0 +1,104 @@ +from collections import defaultdict + +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + copy_doctypes() + rename_fields() + + +def copy_doctypes(): + previous = frappe.db.auto_commit_on_many_writes + frappe.db.auto_commit_on_many_writes = True + try: + insert_into_bom() + insert_into_job_card() + if frappe.db.has_table("Subcontracting Inward Order Scrap Item"): + insert_into_subcontracting_inward() + finally: + frappe.db.auto_commit_on_many_writes = previous + + +def insert_into_bom(): + fields = ["item_code", "item_name", "stock_uom", "stock_qty", "rate"] + data = frappe.get_all("BOM Scrap Item", {"docstatus": ("<", 2)}, ["parent", *fields]) + grouped_data = defaultdict(list) + for item in data: + grouped_data[item.parent].append(item) + + for parent, items in grouped_data.items(): + bom = frappe.get_doc("BOM", parent) + for item in items: + secondary_item = frappe.new_doc( + "BOM Secondary Item", parent_doc=bom, parentfield="secondary_items" + ) + secondary_item.update({field: item[field] for field in fields}) + secondary_item.update( + { + "uom": item.stock_uom, + "conversion_factor": 1, + "qty": item.stock_qty, + "is_legacy": 1, + "type": "Scrap", + } + ) + secondary_item.insert() + + +def insert_into_job_card(): + fields = ["item_code", "item_name", "description", "stock_qty", "stock_uom"] + bulk_insert("Job Card", "Job Card Scrap Item", "Job Card Secondary Item", fields, ["type"], ["Scrap"]) + + +def insert_into_subcontracting_inward(): + fields = [ + "item_code", + "fg_item_code", + "stock_uom", + "warehouse", + "reference_name", + "produced_qty", + "delivered_qty", + ] + bulk_insert( + "Subcontracting Inward Order", + "Subcontracting Inward Order Scrap Item", + "Subcontracting Inward Order Secondary Item", + fields, + ["type"], + ["Scrap"], + ) + + +def bulk_insert(parent_doctype, old_doctype, new_doctype, old_fields, new_fields, new_values): + data = frappe.get_all(old_doctype, {"docstatus": ("<", 2)}, ["parent", *old_fields]) + grouped_data = defaultdict(list) + + for item in data: + grouped_data[item.parent].append(item) + + for parent, items in grouped_data.items(): + parent_doc = frappe.get_doc(parent_doctype, parent) + for item in items: + secondary_item = frappe.new_doc(new_doctype, parent_doc=parent_doc, parentfield="secondary_items") + secondary_item.update({old_field: item[old_field] for old_field in old_fields}) + secondary_item.update( + {new_field: new_value for new_field, new_value in zip(new_fields, new_values, strict=True)} + ) + secondary_item.insert() + + +def rename_fields(): + rename_field("BOM", "scrap_material_cost", "secondary_items_cost") + rename_field("BOM", "base_scrap_material_cost", "base_secondary_items_cost") + rename_field("Stock Entry Detail", "is_scrap_item", "is_legacy_scrap_item") + rename_field( + "Manufacturing Settings", + "set_op_cost_and_scrap_from_sub_assemblies", + "set_op_cost_and_secondary_items_from_sub_assemblies", + ) + rename_field("Selling Settings", "deliver_scrap_items", "deliver_secondary_items") + rename_field("Subcontracting Receipt Item", "is_scrap_item", "is_legacy_scrap_item") + rename_field("Subcontracting Receipt Item", "scrap_cost_per_qty", "secondary_items_cost_per_qty") diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f0be33b6a87..4971f914b1e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1855,7 +1855,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe "base_operating_cost", "base_raw_material_cost", "base_total_cost", - "base_scrap_material_cost", + "base_secondary_items_cost", "base_totals_section", ], company_currency @@ -1873,7 +1873,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe "paid_amount", "write_off_amount", "operating_cost", - "scrap_material_cost", + "secondary_items_cost", "raw_material_cost", "total_cost", "totals_section", @@ -1919,7 +1919,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe "base_operating_cost", "base_raw_material_cost", "base_total_cost", - "base_scrap_material_cost", + "base_secondary_items_cost", "base_rounding_adjustment", ], this.frm.doc.currency != company_currency @@ -1984,11 +1984,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe }); } - if (this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) { - this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items"); - this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items"); + if (this.frm.doc.secondary_items && this.frm.doc.secondary_items.length > 0) { + this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "secondary_items"); + this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "secondary_items"); - var item_grid = this.frm.fields_dict["scrap_items"].grid; + var item_grid = this.frm.fields_dict["secondary_items"].grid; $.each(["base_rate", "base_amount"], function (i, fname) { if (frappe.meta.get_docfield(item_grid.doctype, fname)) item_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index b7896b58dff..d501f8abd51 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -49,7 +49,7 @@ "section_break_zwh6", "allow_delivery_of_overproduced_qty", "column_break_mla9", - "deliver_scrap_items" + "deliver_secondary_items" ], "fields": [ { @@ -260,13 +260,6 @@ "fieldname": "column_break_mla9", "fieldtype": "Column Break" }, - { - "default": "0", - "description": "If enabled, the Scrap Item generated against a Finished Good will also be added in the Stock Entry when delivering that Finished Good.", - "fieldname": "deliver_scrap_items", - "fieldtype": "Check", - "label": "Deliver Scrap Items" - }, { "fieldname": "item_price_tab", "fieldtype": "Tab Break", @@ -320,6 +313,13 @@ "fieldname": "enable_utm", "fieldtype": "Check", "label": "Enable UTM" + }, + { + "default": "0", + "description": "If enabled, the Secondary Items generated against a Finished Good will also be added in the Stock Entry when delivering that Finished Good.", + "fieldname": "deliver_secondary_items", + "fieldtype": "Check", + "label": "Deliver Secondary Items" } ], "grid_page_length": 50, diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index 8621f5f066d..c13d4ce0a6c 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -41,7 +41,7 @@ class SellingSettings(Document): blanket_order_allowance: DF.Float cust_master_name: DF.Literal["Customer Name", "Naming Series", "Auto Name"] customer_group: DF.Link | None - deliver_scrap_items: DF.Check + deliver_secondary_items: DF.Check dn_required: DF.Literal["No", "Yes"] dont_reserve_sales_order_qty_on_sales_return: DF.Check editable_bundle_item_rates: DF.Check diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 8111935a339..51eb71d6f79 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -820,7 +820,7 @@ class Company(NestedSet): boms = frappe.db.sql_list("select name from tabBOM where company=%s", self.name) if boms: frappe.db.sql("delete from tabBOM where company=%s", self.name) - for dt in ("BOM Operation", "BOM Item", "BOM Scrap Item", "BOM Explosion Item"): + for dt in ("BOM Operation", "BOM Item", "BOM Secondary Item", "BOM Explosion Item"): frappe.db.sql( "delete from `tab{}` where parent in ({})".format(dt, ", ".join(["%s"] * len(boms))), tuple(boms), diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index f71b67e1127..dbfad27be26 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1334,13 +1334,13 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle } fg_completed_qty() { - this.get_items(); + if (!this.frm.doc.job_card) { + this.get_items(); + } } get_items() { var me = this; - if (!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no) - frappe.throw(__("BOM and Manufacturing Quantity are required")); if (this.frm.doc.work_order || this.frm.doc.bom_no) { // if work order / bom is mentioned, get items diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 4ce2bda3631..19c00ceacea 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -31,7 +31,7 @@ from erpnext.manufacturing.doctype.bom.bom import ( add_additional_cost, get_bom_items_as_dict, get_op_cost_from_sub_assemblies, - get_scrap_items_from_sub_assemblies, + get_secondary_items_from_sub_assemblies, validate_bom_no, ) from erpnext.setup.doctype.brand.brand import get_brand_defaults @@ -245,7 +245,7 @@ class StockEntry(StockController, SubcontractingInwardController): self.validate_company_in_accounting_dimension() if self.purpose in ("Manufacture", "Repack"): - self.mark_finished_and_scrap_items() + self.mark_finished_and_secondary_items() if not self.job_card: self.validate_finished_goods() else: @@ -272,7 +272,7 @@ class StockEntry(StockController, SubcontractingInwardController): self.validate_component_and_quantities() if self.get("purpose") != "Manufacture": - # ignore scrap item wh difference and empty source/target wh + # ignore other item wh difference and empty source/target wh # in Manufacture Entry self.reset_default_field_value("from_warehouse", "items", "s_warehouse") self.reset_default_field_value("to_warehouse", "items", "t_warehouse") @@ -656,7 +656,7 @@ class StockEntry(StockController, SubcontractingInwardController): item.expense_account = frappe.get_value("Company", self.company, "default_expense_account") def validate_fg_completed_qty(self): - if self.purpose != "Manufacture": + if self.purpose != "Manufacture" or not self.from_bom: return fg_qty = defaultdict(float) @@ -789,7 +789,7 @@ class StockEntry(StockController, SubcontractingInwardController): if self.purpose == "Manufacture": if has_bom: - if d.is_finished_item or d.is_scrap_item: + if d.is_finished_item or d.type or d.is_legacy_scrap_item: d.s_warehouse = None if not d.t_warehouse: frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) @@ -1093,11 +1093,10 @@ class StockEntry(StockController, SubcontractingInwardController): def set_basic_rate(self, reset_outgoing_rate=True, raise_error_if_no_rate=True): """ - Set rate for outgoing, scrapped and finished items + Set rate for outgoing, secondary and finished items """ # Set rate for outgoing items outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate) - finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item) items = [] # Set basic rate for incoming items @@ -1111,11 +1110,19 @@ class StockEntry(StockController, SubcontractingInwardController): elif d.is_finished_item: if self.purpose == "Manufacture": d.basic_rate = self.get_basic_rate_for_manufactured_item( - finished_item_qty, outgoing_items_cost + d.transfer_qty, outgoing_items_cost ) elif self.purpose == "Repack": d.basic_rate = self.get_basic_rate_for_repacked_items(d.transfer_qty, outgoing_items_cost) + if self.bom_no: + d.basic_rate *= frappe.get_value("BOM", self.bom_no, "cost_allocation_per") / 100 + elif d.type and d.bom_secondary_item: + cost_allocation_per = frappe.get_value( + "BOM Secondary Item", d.bom_secondary_item, "cost_allocation_per" + ) + d.basic_rate = (outgoing_items_cost * (cost_allocation_per / 100)) / d.transfer_qty + if not d.basic_rate and not d.allow_zero_valuation_rate: if self.is_new(): raise_error_if_no_rate = False @@ -1198,7 +1205,7 @@ class StockEntry(StockController, SubcontractingInwardController): def get_basic_rate_for_manufactured_item(self, finished_item_qty, outgoing_items_cost=0) -> float: settings = frappe.get_single("Manufacturing Settings") - scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item]) + scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_legacy_scrap_item]) if settings.material_consumption: if settings.get_rm_cost_from_consumption_entry and self.work_order: @@ -1212,7 +1219,7 @@ class StockEntry(StockController, SubcontractingInwardController): }, ): for item in self.items: - if not item.is_finished_item and not item.is_scrap_item: + if not item.is_finished_item and not item.type and not item.is_legacy_scrap_item: label = frappe.get_meta(settings.doctype).get_label( "get_rm_cost_from_consumption_entry" ) @@ -1614,7 +1621,7 @@ class StockEntry(StockController, SubcontractingInwardController): order, ) - def mark_finished_and_scrap_items(self): + def mark_finished_and_secondary_items(self): if self.purpose != "Repack" and any( [d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)] ): @@ -1631,11 +1638,9 @@ class StockEntry(StockController, SubcontractingInwardController): if d.t_warehouse and not d.s_warehouse: if self.purpose == "Repack" or d.item_code == finished_item: d.is_finished_item = 1 - else: - d.is_scrap_item = 1 else: d.is_finished_item = 0 - d.is_scrap_item = 0 + d.type = "" def get_finished_item(self): finished_item = None @@ -2434,7 +2439,7 @@ class StockEntry(StockController, SubcontractingInwardController): self.load_items_from_bom() self.set_serial_batch_from_reserved_entry() - self.set_scrap_items() + self.set_secondary_items() self.set_actual_qty() self.validate_customer_provided_item() self.calculate_rate_and_amount(raise_error_if_no_rate=False) @@ -2579,14 +2584,21 @@ class StockEntry(StockController, SubcontractingInwardController): return query.run(as_dict=True) - def set_scrap_items(self): - if self.purpose != "Send to Subcontractor" and self.purpose in ["Manufacture", "Repack"]: - scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty) - for item in scrap_item_dict.values(): - if self.pro_doc and self.pro_doc.scrap_warehouse: - item["to_warehouse"] = self.pro_doc.scrap_warehouse + def set_secondary_items(self): + if self.purpose in ["Manufacture", "Repack"]: + secondary_items_dict = self.get_secondary_items(self.fg_completed_qty) + for item in secondary_items_dict.values(): + if self.pro_doc and item.type: + if self.pro_doc.scrap_warehouse and item.type == "Scrap": + item["to_warehouse"] = self.pro_doc.scrap_warehouse - self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no) + if item.process_loss_per: + item["qty"] -= flt( + item["qty"] * (item.process_loss_per / 100), + self.precision("fg_completed_qty"), + ) + + self.add_to_stock_entry_detail(secondary_items_dict, bom_no=self.bom_no) def set_process_loss_qty(self): if self.purpose not in ("Manufacture", "Repack"): @@ -2600,7 +2612,7 @@ class StockEntry(StockController, SubcontractingInwardController): fields=[{"MAX": "process_loss_qty", "as": "process_loss_qty"}], ) - if data and data[0].process_loss_qty is not None: + if data and data[0].process_loss_qty: process_loss_qty = data[0].process_loss_qty if flt(self.process_loss_qty, precision) != flt(process_loss_qty, precision): self.process_loss_qty = flt(process_loss_qty, precision) @@ -2632,7 +2644,7 @@ class StockEntry(StockController, SubcontractingInwardController): if not self.pro_doc: self.pro_doc = frappe.get_doc("Work Order", self.work_order) - if self.pro_doc: + if self.pro_doc and not self.pro_doc.track_semi_finished_goods: self.bom_no = self.pro_doc.bom_no else: # invalid work order @@ -2774,54 +2786,59 @@ class StockEntry(StockController, SubcontractingInwardController): return item_dict - def get_bom_scrap_material(self, qty): + def get_secondary_items(self, qty): from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict if ( - frappe.db.get_single_value("Manufacturing Settings", "set_op_cost_and_scrap_from_sub_assemblies") + frappe.db.get_single_value( + "Manufacturing Settings", "set_op_cost_and_secondary_items_from_sub_assemblies" + ) and self.work_order and frappe.get_cached_value("Work Order", self.work_order, "use_multi_level_bom") ): - item_dict = get_scrap_items_from_sub_assemblies(self.bom_no, self.company, qty) + item_dict = get_secondary_items_from_sub_assemblies(self.bom_no, self.company, qty) else: # item dict = { item_code: {qty, description, stock_uom} } item_dict = ( get_bom_items_as_dict( - self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_scrap_items=1 + self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_secondary_items=1 ) or {} ) for item in item_dict.values(): item.from_warehouse = "" - item.is_scrap_item = 1 - - for row in self.get_scrap_items_from_job_card(): - if row.stock_qty <= 0: - continue - - item_row = item_dict.get(row.item_code) - if not item_row: - item_row = frappe._dict({}) - - item_row.update( - { - "uom": row.stock_uom, - "from_warehouse": "", - "qty": row.stock_qty + flt(item_row.stock_qty), - "converison_factor": 1, - "is_scrap_item": 1, - "item_name": row.item_name, - "description": row.description, - "allow_zero_valuation_rate": 1, - } - ) - - item_dict[row.item_code] = item_row return item_dict - def get_scrap_items_from_job_card(self): + def set_secondary_items_from_job_card(self): + if self.purpose not in ["Manufacture", "Repack"]: + return + + item_dict = {} + for row in self.get_secondary_items_from_job_card(): + if row.stock_qty <= 0: + continue + + item_dict[row.item_code] = frappe._dict( + { + "uom": row.stock_uom, + "from_warehouse": "", + "qty": row.stock_qty, + "conversion_factor": 1, + "type": row.type, + "item_name": row.item_name, + "description": row.description, + "bom_secondary_item": row.bom_secondary_item, + } + ) + + for item in item_dict.values(): + item.from_warehouse = "" + + self.add_to_stock_entry_detail(item_dict) + + def get_secondary_items_from_job_card(self): if not hasattr(self, "pro_doc"): self.pro_doc = None @@ -2832,70 +2849,78 @@ class StockEntry(StockController, SubcontractingInwardController): return [] job_card = frappe.qb.DocType("Job Card") - job_card_scrap_item = frappe.qb.DocType("Job Card Scrap Item") + job_card_secondary_item = frappe.qb.DocType("Job Card Secondary Item") - scrap_items = ( + other = ( frappe.qb.from_(job_card) .select( - Sum(job_card_scrap_item.stock_qty).as_("stock_qty"), - job_card_scrap_item.item_code, - job_card_scrap_item.item_name, - job_card_scrap_item.description, - job_card_scrap_item.stock_uom, + Sum(job_card_secondary_item.stock_qty).as_("stock_qty"), + job_card_secondary_item.item_code, + job_card_secondary_item.item_name, + job_card_secondary_item.description, + job_card_secondary_item.stock_uom, + job_card_secondary_item.type, + job_card_secondary_item.bom_secondary_item, ) - .join(job_card_scrap_item) - .on(job_card_scrap_item.parent == job_card.name) + .join(job_card_secondary_item) + .on(job_card_secondary_item.parent == job_card.name) .where( - (job_card_scrap_item.item_code.isnotnull()) + (job_card_secondary_item.item_code.isnotnull()) & (job_card.work_order == self.work_order) & (job_card.docstatus == 1) ) - .groupby(job_card_scrap_item.item_code) + .groupby(job_card_secondary_item.item_code, job_card_secondary_item.type) + .orderby(job_card_secondary_item.idx) ) if self.job_card: - scrap_items = scrap_items.where(job_card.name == self.job_card) + other = other.where(job_card.name == self.job_card) - scrap_items = scrap_items.run(as_dict=1) + other = other.run(as_dict=1) if self.job_card: pending_qty = flt(self.fg_completed_qty) else: pending_qty = flt(self.get_completed_job_card_qty()) - flt(self.pro_doc.produced_qty) - used_scrap_items = self.get_used_scrap_items() - for row in scrap_items: - row.stock_qty -= flt(used_scrap_items.get(row.item_code)) + used_secondary_items = self.get_used_secondary_items() + for row in other: + row.stock_qty -= flt(used_secondary_items.get(row.item_code)) row.stock_qty = (row.stock_qty) * flt(self.fg_completed_qty) / flt(pending_qty) - if used_scrap_items.get(row.item_code): - used_scrap_items[row.item_code] -= row.stock_qty + if used_secondary_items.get(row.item_code): + used_secondary_items[row.item_code] -= row.stock_qty if cint(frappe.get_cached_value("UOM", row.stock_uom, "must_be_whole_number")): row.stock_qty = frappe.utils.ceil(row.stock_qty) - return scrap_items + return other def get_completed_job_card_qty(self): return flt(min([d.completed_qty for d in self.pro_doc.operations])) - def get_used_scrap_items(self): - used_scrap_items = defaultdict(float) - data = frappe.get_all( - "Stock Entry", - fields=["`tabStock Entry Detail`.`item_code`", "`tabStock Entry Detail`.`qty`"], - filters=[ - ["Stock Entry", "work_order", "=", self.work_order], - ["Stock Entry Detail", "is_scrap_item", "=", 1], - ["Stock Entry", "docstatus", "=", 1], - ["Stock Entry", "purpose", "in", ["Repack", "Manufacture"]], - ], - ) + def get_used_secondary_items(self): + used_secondary_items = defaultdict(float) + + StockEntry = frappe.qb.DocType("Stock Entry") + StockEntryDetail = frappe.qb.DocType("Stock Entry Detail") + data = ( + frappe.qb.from_(StockEntry) + .inner_join(StockEntryDetail) + .on(StockEntryDetail.parent == StockEntry.name) + .select(StockEntryDetail.item_code, StockEntryDetail.qty) + .where( + (StockEntry.work_order == self.work_order) + & ((StockEntryDetail.type.isnotnull()) | (StockEntryDetail.is_legacy_scrap_item == 1)) + & (StockEntry.docstatus == 1) + & (StockEntry.purpose.isin(["Repack", "Manufacture"])) + ) + ).run(as_dict=1) for row in data: - used_scrap_items[row.item_code] += row.qty + used_secondary_items[row.item_code] += row.qty - return used_scrap_items + return used_secondary_items def get_unconsumed_raw_materials(self): wo = frappe.get_doc("Work Order", self.work_order) @@ -3187,7 +3212,12 @@ class StockEntry(StockController, SubcontractingInwardController): item_row = item_dict[d] child_qty = flt(item_row["qty"], precision) - if not self.is_return and child_qty <= 0 and not item_row.get("is_scrap_item"): + if ( + not self.is_return + and child_qty <= 0 + and not item_row.get("type") + and not item_row.get("is_legacy_scrap_item") + ): if self.purpose not in ["Receive from Customer", "Send to Subcontractor"]: continue @@ -3205,11 +3235,13 @@ class StockEntry(StockController, SubcontractingInwardController): item_row, company=self.company ) se_child.is_finished_item = item_row.get("is_finished_item", 0) - se_child.is_scrap_item = item_row.get("is_scrap_item", 0) se_child.po_detail = item_row.get("po_detail") se_child.sco_rm_detail = item_row.get("sco_rm_detail") se_child.scio_detail = item_row.get("scio_detail") se_child.sample_quantity = item_row.get("sample_quantity", 0) + se_child.type = item_row.get("type") + se_child.is_legacy_scrap_item = item_row.get("is_legacy") + se_child.bom_secondary_item = item_row.get("name") or item_row.get("bom_secondary_item") for field in [ self.subcontract_data.rm_detail_field, @@ -3686,7 +3718,7 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None): if ( bom_no and frappe.db.get_single_value( - "Manufacturing Settings", "set_op_cost_and_scrap_from_sub_assemblies" + "Manufacturing Settings", "set_op_cost_and_secondary_items_from_sub_assemblies" ) and frappe.get_cached_value("Work Order", work_order.name, "use_multi_level_bom") ): diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 48488a7c5b6..b102e20cfc4 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -909,8 +909,8 @@ class TestStockEntry(ERPNextTestSuite): if d.s_warehouse: rm_cost += d.amount fg_cost = next(filter(lambda x: x.item_code == "_Test FG Item", s.get("items"))).amount - scrap_cost = next(filter(lambda x: x.is_scrap_item, s.get("items"))).amount - self.assertEqual(fg_cost, flt(rm_cost - scrap_cost, 2)) + secondary_item_cost = next(filter(lambda x: x.type or x.is_legacy_scrap_item, s.get("items"))).amount + self.assertEqual(fg_cost, flt(rm_cost - secondary_item_cost, 2)) # When Stock Entry has only FG + Scrap s.items.pop(0) @@ -989,15 +989,15 @@ class TestStockEntry(ERPNextTestSuite): self.assertRaises(frappe.ValidationError, ste.submit) - def test_quality_check_for_scrap_item(self): + def test_quality_check_for_secondary_item(self): from erpnext.manufacturing.doctype.work_order.work_order import ( make_stock_entry as _make_stock_entry, ) - scrap_item = "_Test Scrap Item 1" - make_item(scrap_item, {"is_stock_item": 1, "is_purchase_item": 0}) + secondary_item = "_Test Scrap Item 1" + make_item(secondary_item, {"is_stock_item": 1, "is_purchase_item": 0}) - bom_name = frappe.db.get_value("BOM Scrap Item", {"docstatus": 1}, "parent") + bom_name = frappe.db.get_value("BOM Secondary Item", {"docstatus": 1}, "parent") production_item = frappe.db.get_value("BOM", bom_name, "item") work_order = frappe.new_doc("Work Order") @@ -1027,18 +1027,18 @@ class TestStockEntry(ERPNextTestSuite): basic_rate=row.basic_rate or 100, ) - if row.is_scrap_item: - row.item_code = scrap_item - row.uom = frappe.db.get_value("Item", scrap_item, "stock_uom") - row.stock_uom = frappe.db.get_value("Item", scrap_item, "stock_uom") + if row.type or row.is_legacy_scrap_item: + row.item_code = secondary_item + row.uom = frappe.db.get_value("Item", secondary_item, "stock_uom") + row.stock_uom = frappe.db.get_value("Item", secondary_item, "stock_uom") stock_entry.inspection_required = 1 stock_entry.save() - self.assertTrue([row.item_code for row in stock_entry.items if row.is_scrap_item]) + self.assertTrue([row.item_code for row in stock_entry.items if row.type or row.is_legacy_scrap_item]) for row in stock_entry.items: - if not row.is_scrap_item: + if not row.type and not row.is_legacy_scrap_item: qc = frappe.get_doc( { "doctype": "Quality Inspection", @@ -1058,7 +1058,7 @@ class TestStockEntry(ERPNextTestSuite): stock_entry.reload() stock_entry.submit() for row in stock_entry.items: - if row.is_scrap_item: + if row.type or row.is_legacy_scrap_item: self.assertFalse(row.quality_inspection) else: self.assertTrue(row.quality_inspection) @@ -2464,6 +2464,35 @@ class TestStockEntry(ERPNextTestSuite): # delete naming rule frappe.delete_doc("Document Naming Rule", qc_naming_rule.name) + def test_co_by_product(self): + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + + frappe.set_value("UOM", "Nos", "must_be_whole_number", 0) + + fg_item = make_item("FG Item", properties={"is_stock_item": 1}).name + rm_item = make_item("RM Item", properties={"is_stock_item": 1}).name + scrap_item = make_item("Scrap Item", properties={"is_stock_item": 1}).name + warehouse = "_Test Warehouse - _TC" + make_stock_entry(item_code=rm_item, target=warehouse, qty=5, rate=10, purpose="Material Receipt") + + bom_no = make_bom( + item=fg_item, raw_materials=[rm_item], scrap_items=[scrap_item], process_loss_percentage=10 + ).name + se = make_stock_entry(item_code=fg_item, qty=5, purpose="Manufacture", do_not_save=True) + se.from_bom = 1 + se.bom_no = bom_no + se.fg_completed_qty = 5 + se.from_warehouse = warehouse + se.to_warehouse = "_Test Warehouse 1 - _TC" + se.get_items() + se.save() + se.reload() + + self.assertEqual(se.items[1].qty, 4.5) + self.assertEqual(se.items[1].amount, 45) + self.assertEqual(se.items[2].qty, 4.5) + self.assertEqual(se.items[2].amount, 5) + def make_serialized_item(self, **args): args = frappe._dict(args) diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index eceba634bf3..f28f5e25a66 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -18,7 +18,8 @@ "item_name", "col_break2", "is_finished_item", - "is_scrap_item", + "is_legacy_scrap_item", + "type", "quality_inspection", "subcontracted_item", "against_fg", @@ -81,7 +82,8 @@ "putaway_rule", "column_break_51", "reference_purchase_receipt", - "job_card_item" + "job_card_item", + "bom_secondary_item" ], "fields": [ { @@ -558,12 +560,7 @@ }, { "default": "0", - "fieldname": "is_scrap_item", - "fieldtype": "Check", - "label": "Is Scrap Item" - }, - { - "default": "0", + "depends_on": "eval:!doc.is_legacy_scrap_item && !doc.type", "fieldname": "is_finished_item", "fieldtype": "Check", "label": "Is Finished Item", @@ -654,6 +651,28 @@ "no_copy": 1, "options": "Subcontracting Inward Order Item", "set_only_once": 1 + }, + { + "depends_on": "eval:parent.purpose == \"Manufacture\" && doc.t_warehouse && !doc.is_finished_item && !doc.is_legacy_scrap_item", + "fieldname": "type", + "fieldtype": "Select", + "label": "Type", + "options": "\nCo-Product\nBy-Product\nScrap\nAdditional Finished Good" + }, + { + "fieldname": "bom_secondary_item", + "fieldtype": "Data", + "hidden": 1, + "label": "BOM Secondary Item", + "read_only": 1 + }, + { + "default": "0", + "depends_on": "is_legacy_scrap_item", + "fieldname": "is_legacy_scrap_item", + "fieldtype": "Check", + "label": "Is Legacy Scrap Item", + "read_only": 1 } ], "grid_page_length": 50, diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py index 95bb7181a0f..0c1a21fefce 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py @@ -26,6 +26,7 @@ class StockEntryDetail(Document): basic_rate: DF.Currency batch_no: DF.Link | None bom_no: DF.Link | None + bom_secondary_item: DF.Data | None conversion_factor: DF.Float cost_center: DF.Link | None customer_provided_item_cost: DF.Currency @@ -34,7 +35,7 @@ class StockEntryDetail(Document): has_item_scanned: DF.Check image: DF.Attach | None is_finished_item: DF.Check - is_scrap_item: DF.Check + is_legacy_scrap_item: DF.Check item_code: DF.Link item_group: DF.Data | None item_name: DF.Data | None @@ -66,6 +67,7 @@ class StockEntryDetail(Document): t_warehouse: DF.Link | None transfer_qty: DF.Float transferred_qty: DF.Float + type: DF.Literal["", "Co-Product", "By-Product", "Scrap", "Additional Finished Good"] uom: DF.Link use_serial_batch_fields: DF.Check valuation_rate: DF.Currency diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py index 4a768ee94fd..f02c06810f0 100644 --- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py +++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py @@ -75,13 +75,18 @@ class ManufactureEntry: self.stock_entry = frappe.new_doc("Stock Entry") self.stock_entry.purpose = self.purpose self.stock_entry.company = self.company - self.stock_entry.from_bom = 1 - self.stock_entry.bom_no = self.bom_no - self.stock_entry.use_multi_level_bom = 1 + + if self.bom_no: + self.stock_entry.from_bom = 1 + self.stock_entry.bom_no = self.bom_no + self.stock_entry.use_multi_level_bom = 1 + self.stock_entry.fg_completed_qty = self.for_quantity + self.stock_entry.process_loss_qty = self.process_loss_qty self.stock_entry.project = self.project self.stock_entry.job_card = self.job_card self.stock_entry.set_stock_entry_type() + self.stock_entry.work_order = self.work_order self.prepare_source_warehouse() self.add_raw_materials() @@ -303,7 +308,7 @@ class ManufactureEntry: args = { "to_warehouse": self.fg_warehouse, "from_warehouse": "", - "qty": self.for_quantity, + "qty": self.for_quantity - self.process_loss_qty, "item_name": item.item_name, "description": item.description, "stock_uom": item.stock_uom, diff --git a/erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.json b/erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.json index 95ac21ac71b..a0b163f4271 100644 --- a/erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.json +++ b/erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.json @@ -25,7 +25,7 @@ "raw_materials_received_section", "received_items", "scrap_items_generated_section", - "scrap_items", + "secondary_items", "service_items_section", "service_items", "tab_other_info", @@ -252,17 +252,10 @@ "reqd": 1 }, { - "depends_on": "scrap_items", + "depends_on": "secondary_items", "fieldname": "scrap_items_generated_section", "fieldtype": "Section Break", - "label": "Scrap Items Generated" - }, - { - "fieldname": "scrap_items", - "fieldtype": "Table", - "label": "Scrap Items", - "no_copy": 1, - "options": "Subcontracting Inward Order Scrap Item" + "label": "Secondary Items Generated" }, { "fieldname": "per_returned", @@ -300,13 +293,20 @@ "label": "Customer Currency", "options": "Currency", "read_only": 1 + }, + { + "fieldname": "secondary_items", + "fieldtype": "Table", + "label": "Secondary Items", + "no_copy": 1, + "options": "Subcontracting Inward Order Secondary Item" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-12-09 15:52:55.781346", + "modified": "2026-02-26 17:16:21.697846", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Inward Order", diff --git a/erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.py b/erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.py index b516518bfcb..aea08e18b34 100644 --- a/erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.py @@ -25,8 +25,8 @@ class SubcontractingInwardOrder(SubcontractingController): from erpnext.subcontracting.doctype.subcontracting_inward_order_received_item.subcontracting_inward_order_received_item import ( SubcontractingInwardOrderReceivedItem, ) - from erpnext.subcontracting.doctype.subcontracting_inward_order_scrap_item.subcontracting_inward_order_scrap_item import ( - SubcontractingInwardOrderScrapItem, + from erpnext.subcontracting.doctype.subcontracting_inward_order_secondary_item.subcontracting_inward_order_secondary_item import ( + SubcontractingInwardOrderSecondaryItem, ) from erpnext.subcontracting.doctype.subcontracting_inward_order_service_item.subcontracting_inward_order_service_item import ( SubcontractingInwardOrderServiceItem, @@ -48,7 +48,7 @@ class SubcontractingInwardOrder(SubcontractingController): per_returned: DF.Percent received_items: DF.Table[SubcontractingInwardOrderReceivedItem] sales_order: DF.Link - scrap_items: DF.Table[SubcontractingInwardOrderScrapItem] + secondary_items: DF.Table[SubcontractingInwardOrderSecondaryItem] service_items: DF.Table[SubcontractingInwardOrderServiceItem] set_delivery_warehouse: DF.Link | None status: DF.Literal[ @@ -474,23 +474,25 @@ class SubcontractingInwardOrder(SubcontractingController): stock_entry.add_to_stock_entry_detail(items_dict) if ( - frappe.get_single_value("Selling Settings", "deliver_scrap_items") - and self.scrap_items + frappe.get_single_value("Selling Settings", "deliver_secondary_items") + and self.secondary_items and scio_details ): - scrap_items = [ - scrap_item for scrap_item in self.scrap_items if scrap_item.reference_name in scio_details + secondary_items = [ + secondary_item + for secondary_item in self.secondary_items + if secondary_item.reference_name in scio_details ] - for scrap_item in scrap_items: - qty = scrap_item.produced_qty - scrap_item.delivered_qty + for secondary_item in secondary_items: + qty = secondary_item.produced_qty - secondary_item.delivered_qty if qty > 0: items_dict = { - scrap_item.item_code: { - "qty": scrap_item.produced_qty - scrap_item.delivered_qty, - "from_warehouse": scrap_item.warehouse, - "stock_uom": scrap_item.stock_uom, - "scio_detail": scrap_item.name, - "is_scrap_item": 1, + secondary_item.item_code: { + "qty": secondary_item.produced_qty - secondary_item.delivered_qty, + "from_warehouse": secondary_item.warehouse, + "stock_uom": secondary_item.stock_uom, + "scio_detail": secondary_item.name, + "type": secondary_item.type, } } diff --git a/erpnext/subcontracting/doctype/subcontracting_inward_order/test_subcontracting_inward_order.py b/erpnext/subcontracting/doctype/subcontracting_inward_order/test_subcontracting_inward_order.py index 9463b11bf4c..d035f4ddcb9 100644 --- a/erpnext/subcontracting/doctype/subcontracting_inward_order/test_subcontracting_inward_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_inward_order/test_subcontracting_inward_order.py @@ -323,10 +323,12 @@ class IntegrationTestSubcontractingInwardOrder(ERPNextTestSuite): delivery.items[0].qty = 6 self.assertRaises(frappe.ValidationError, delivery.submit) - @ERPNextTestSuite.change_settings("Selling Settings", {"deliver_scrap_items": 1}) + @ERPNextTestSuite.change_settings("Selling Settings", {"deliver_secondary_items": 1}) def test_secondary_items_delivery(self): new_bom = frappe.copy_doc(frappe.get_doc("BOM", "BOM-Basic FG Item-001")) - new_bom.scrap_items.append(frappe.new_doc("BOM Scrap Item", item_code="Basic RM 2", qty=1)) + new_bom.secondary_items.append( + frappe.new_doc("BOM Secondary Item", item_code="Basic RM 2", qty=1, type="Scrap") + ) new_bom.submit() sc_bom = frappe.get_doc("Subcontracting BOM", "SB-0001") sc_bom.finished_good_bom = new_bom.name @@ -343,12 +345,12 @@ class IntegrationTestSubcontractingInwardOrder(ERPNextTestSuite): frappe.new_doc("Stock Entry").update(make_stock_entry_from_wo(wo.name, "Manufacture")).submit() scio.reload() - self.assertEqual(scio.scrap_items[0].item_code, "Basic RM 2") + self.assertEqual(scio.secondary_items[0].item_code, "Basic RM 2") delivery = frappe.new_doc("Stock Entry").update(scio.make_subcontracting_delivery()) self.assertEqual(delivery.items[-1].item_code, "Basic RM 2") - frappe.db.set_single_value("Selling Settings", "deliver_scrap_items", 0) + frappe.db.set_single_value("Selling Settings", "deliver_secondary_items", 0) delivery = frappe.new_doc("Stock Entry").update(scio.make_subcontracting_delivery()) self.assertNotEqual(delivery.items[-1].item_code, "Basic RM 2") diff --git a/erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/__init__.py b/erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/__init__.py similarity index 100% rename from erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/__init__.py rename to erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/__init__.py diff --git a/erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.json b/erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json similarity index 83% rename from erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.json rename to erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json index 78902701532..94a640b41ce 100644 --- a/erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json @@ -6,13 +6,15 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "column_break_rptg", + "type", + "reference_name", + "column_break_jkzt", "item_code", "fg_item_code", "column_break_hoxe", "stock_uom", "warehouse", - "column_break_rptg", - "reference_name", "section_break_gqk9", "produced_qty", "column_break_n4xc", @@ -93,16 +95,29 @@ { "fieldname": "column_break_n4xc", "fieldtype": "Column Break" + }, + { + "fieldname": "type", + "fieldtype": "Select", + "label": "Type", + "no_copy": 1, + "options": "Co-Product\nBy-Product\nScrap\nAdditional Finished Good", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "column_break_jkzt", + "fieldtype": "Column Break" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-10-14 10:28:30.192350", + "modified": "2026-02-27 15:15:40.009957", "modified_by": "Administrator", "module": "Subcontracting", - "name": "Subcontracting Inward Order Scrap Item", + "name": "Subcontracting Inward Order Secondary Item", "owner": "Administrator", "permissions": [], "row_format": "Dynamic", diff --git a/erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.py b/erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.py similarity index 81% rename from erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.py rename to erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.py index d7aaae229dd..767f216921a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.py +++ b/erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.py @@ -5,7 +5,7 @@ from frappe.model.document import Document -class SubcontractingInwardOrderScrapItem(Document): +class SubcontractingInwardOrderSecondaryItem(Document): # begin: auto-generated types # This code is auto-generated. Do not modify anything in this block. @@ -23,6 +23,7 @@ class SubcontractingInwardOrderScrapItem(Document): produced_qty: DF.Float reference_name: DF.Data stock_uom: DF.Link + type: DF.Literal["Co-Product", "By-Product", "Scrap", "Additional Finished Good"] warehouse: DF.Link # end: auto-generated types diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 1e05afa2fbf..40de8eb39d4 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -439,6 +439,13 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None, items=None): target.purchase_order = source_parent.purchase_order target.purchase_order_item = source.purchase_order_item target.qty = items.get(source.name) or (flt(source.qty) - flt(source.received_qty)) + target.received_qty = target.qty + if process_loss_per := frappe.get_value("BOM", source.bom, "process_loss_percentage"): + target.process_loss_qty = flt( + target.qty * (process_loss_per / 100), target.precision("process_loss_qty") + ) + target.qty -= target.process_loss_qty + target.amount = (flt(source.qty) - flt(source.received_qty)) * flt(source.rate) items = {item["name"]: item["qty"] for item in items} if items else {} diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json index 689b64492f5..44ec2185ce6 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json @@ -425,7 +425,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-11-03 12:29:45.156101", + "modified": "2026-02-27 23:03:36.436504", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Order Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 3339cff689c..5bb7c2f0cc2 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -174,6 +174,7 @@ frappe.ui.form.on("Subcontracting Receipt", { frm.trigger("setup_quality_inspection"); frm.trigger("set_route_options_for_new_doc"); + frm.set_df_property("items", "cannot_add_rows", true); }, set_warehouse: (frm) => { @@ -184,15 +185,15 @@ frappe.ui.form.on("Subcontracting Receipt", { set_warehouse_in_children(frm.doc.items, "rejected_warehouse", frm.doc.rejected_warehouse); }, - get_scrap_items: (frm) => { + get_secondary_items: (frm) => { frappe.call({ doc: frm.doc, - method: "get_scrap_items", + method: "get_secondary_items", args: { recalculate_rate: true, }, freeze: true, - freeze_message: __("Getting Scrap Items"), + freeze_message: __("Getting Secondary Items"), callback: (r) => { if (!r.exc) { frm.refresh(); @@ -422,11 +423,19 @@ frappe.ui.form.on("Subcontracting Receipt Item", { set_missing_values(frm); }, + rejected_qty(frm) { + set_missing_values(frm); + }, + + process_loss_qty(frm) { + set_missing_values(frm); + }, + rate(frm) { set_missing_values(frm); }, - items_delete: (frm) => { + items_delete(frm) { set_missing_values(frm); }, diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index 79b46ec146a..a284f24fd50 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -29,8 +29,8 @@ "col_break_warehouse", "supplier_warehouse", "items_section", - "get_scrap_items", "items", + "get_secondary_items", "section_break0", "total_qty", "column_break_27", @@ -631,13 +631,6 @@ "label": "Edit Posting Date and Time", "print_hide": 1 }, - { - "depends_on": "eval: (!doc.__islocal && doc.docstatus == 0)", - "fieldname": "get_scrap_items", - "fieldtype": "Button", - "label": "Get Scrap Items", - "options": "get_scrap_items" - }, { "fieldname": "supplier_delivery_note", "fieldtype": "Data", @@ -674,12 +667,19 @@ "fieldtype": "Tab Break", "label": "Connections", "show_dashboard": 1 + }, + { + "depends_on": "eval: (!doc.__islocal && doc.docstatus == 0)", + "fieldname": "get_secondary_items", + "fieldtype": "Button", + "label": "Get Secondary Items", + "options": "get_secondary_items" } ], "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2025-10-08 21:43:27.065640", + "modified": "2026-02-27 17:59:44.107193", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 2456e2ef90f..664adf254f8 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -144,12 +144,12 @@ class SubcontractingReceipt(SubcontractingController): super().validate() if self.is_new() and self.get("_action") == "save" and not frappe.in_test: - self.get_scrap_items() + self.get_secondary_items() self.set_missing_values() if self.get("_action") == "submit": - self.validate_scrap_items() + self.validate_secondary_items() self.validate_accepted_warehouse() self.validate_rejected_warehouse() @@ -343,39 +343,66 @@ class SubcontractingReceipt(SubcontractingController): self.update_rate_for_supplied_items() @frappe.whitelist() - def get_scrap_items(self, recalculate_rate=False): - self.remove_scrap_items() + def get_secondary_items(self, recalculate_rate: bool | None = False): + self.remove_secondary_items() for item in list(self.items): if item.bom: bom = frappe.get_doc("BOM", item.bom) - for scrap_item in bom.scrap_items: - qty = flt(item.qty) * (flt(scrap_item.stock_qty) / flt(bom.quantity)) - rate = ( - get_valuation_rate( - scrap_item.item_code, - self.set_warehouse, - self.doctype, - self.name, - currency=erpnext.get_company_currency(self.company), - company=self.company, - ) - or scrap_item.rate + for secondary_item in bom.secondary_items: + per_unit = secondary_item.stock_qty / bom.quantity + received_qty = flt(item.received_qty * per_unit, item.precision("received_qty")) + qty = flt( + item.received_qty * (per_unit - (secondary_item.process_loss_qty / bom.quantity)), + item.precision("qty"), ) + if not secondary_item.is_legacy: + lcv_cost_per_qty = ( + flt(item.landed_cost_voucher_amount) / flt(item.qty) if flt(item.qty) else 0.0 + ) + fg_item_cost = ( + flt(item.rm_cost_per_qty) + + flt(item.secondary_items_cost_per_qty) + + flt(item.additional_cost_per_qty) + + flt(lcv_cost_per_qty) + + flt(item.service_cost_per_qty) + ) * flt(item.received_qty) + rate = ( + (item.amount if self.is_new() else fg_item_cost) + * (secondary_item.cost_allocation_per / 100) + ) / qty + else: + rate = ( + get_valuation_rate( + secondary_item.item_code, + self.set_warehouse, + self.doctype, + self.name, + currency=erpnext.get_company_currency(self.company), + company=self.company, + ) + or secondary_item.rate + ) + self.append( "items", { - "is_scrap_item": 1, + "type": secondary_item.type, + "is_legacy_scrap_item": secondary_item.is_legacy, "reference_name": item.name, - "item_code": scrap_item.item_code, - "item_name": scrap_item.item_name, - "qty": qty, - "stock_uom": scrap_item.stock_uom, + "item_code": secondary_item.item_code, + "item_name": secondary_item.item_name, + "qty": received_qty + if not secondary_item.is_legacy + else flt(item.qty) * (flt(secondary_item.stock_qty) / flt(bom.quantity)), + "received_qty": received_qty, + "process_loss_qty": received_qty - qty, + "stock_uom": secondary_item.stock_uom, "rate": rate, "rm_cost_per_qty": 0, "service_cost_per_qty": 0, "additional_cost_per_qty": 0, - "scrap_cost_per_qty": 0, + "secondary_items_cost_per_qty": 0, "amount": qty * rate, "warehouse": self.set_warehouse, "rejected_warehouse": self.rejected_warehouse, @@ -386,15 +413,12 @@ class SubcontractingReceipt(SubcontractingController): self.calculate_additional_costs() self.calculate_items_qty_and_amount() - def remove_scrap_items(self, recalculate_rate=False): + def remove_secondary_items(self): for item in list(self.items): - if item.is_scrap_item: + if item.type or item.is_legacy_scrap_item: self.remove(item) else: - item.scrap_cost_per_qty = 0 - - if recalculate_rate: - self.calculate_items_qty_and_amount() + item.secondary_items_cost_per_qty = 0 @frappe.whitelist() def set_missing_values(self): @@ -449,30 +473,35 @@ class SubcontractingReceipt(SubcontractingController): else: rm_cost_map[item.reference_name] = item.amount - scrap_cost_map = {} + secondary_items_cost_map = {} for item in self.get("items") or []: - if item.is_scrap_item: - item.amount = flt(item.qty) * flt(item.rate) + if item.type or item.is_legacy_scrap_item: + qty = ( + flt(item.qty) + if item.is_legacy_scrap_item + else (flt(item.received_qty) - flt(item.process_loss_qty)) + ) + item.amount = qty * flt(item.rate) - if item.reference_name in scrap_cost_map: - scrap_cost_map[item.reference_name] += item.amount + if item.reference_name in secondary_items_cost_map: + secondary_items_cost_map[item.reference_name] += item.amount else: - scrap_cost_map[item.reference_name] = item.amount + secondary_items_cost_map[item.reference_name] = item.amount total_qty = total_amount = 0 for item in self.get("items") or []: - if not item.is_scrap_item: + if not item.type and not item.is_legacy_scrap_item: if item.qty: if item.name in rm_cost_map: item.rm_supp_cost = rm_cost_map[item.name] - item.rm_cost_per_qty = item.rm_supp_cost / item.qty + item.rm_cost_per_qty = item.rm_supp_cost / (item.received_qty or item.qty) rm_cost_map.pop(item.name) - if item.name in scrap_cost_map: - item.scrap_cost_per_qty = scrap_cost_map[item.name] / item.qty - scrap_cost_map.pop(item.name) + if item.name in secondary_items_cost_map: + item.secondary_items_cost_per_qty = secondary_items_cost_map[item.name] / item.qty + secondary_items_cost_map.pop(item.name) else: - item.scrap_cost_per_qty = 0 + item.secondary_items_cost_per_qty = 0 lcv_cost_per_qty = 0.0 if item.landed_cost_voucher_amount: @@ -483,36 +512,44 @@ class SubcontractingReceipt(SubcontractingController): + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty) + flt(lcv_cost_per_qty) - - flt(item.scrap_cost_per_qty) ) - item.received_qty = flt(item.qty) + flt(item.rejected_qty) - item.amount = flt(item.qty) * flt(item.rate) + if item.bom: + item.received_qty = flt(item.qty) + flt(item.rejected_qty) + flt(item.process_loss_qty) + item.amount = ( + flt(item.received_qty) + * flt(item.rate) + * (frappe.get_value("BOM", item.bom, "cost_allocation_per") / 100) + ) + item.rate = item.amount / (item.qty or item.rejected_qty) + else: + item.qty = flt(item.received_qty) - flt(item.process_loss_qty) + item.amount = flt(item.qty) * flt(item.rate) - total_qty += flt(item.qty) + total_qty += flt(item.qty) + flt(item.rejected_qty) total_amount += item.amount else: self.total_qty = total_qty self.total = total_amount - def validate_scrap_items(self): + def validate_secondary_items(self): for item in self.items: - if item.is_scrap_item: + if item.type or item.is_legacy_scrap_item: if not item.qty: frappe.throw( - _("Row #{0}: Scrap Item Qty cannot be zero").format(item.idx), + _("Row #{0}: Secondary Item Qty cannot be zero").format(item.idx), ) if item.rejected_qty: frappe.throw( - _("Row #{0}: Rejected Qty cannot be set for Scrap Item {1}.").format( + _("Row #{0}: Rejected Qty cannot be set for Secondary Item {1}.").format( item.idx, frappe.bold(item.item_code) ), ) if not item.reference_name: frappe.throw( - _("Row #{0}: Finished Good reference is mandatory for Scrap Item {1}.").format( + _("Row #{0}: Finished Good reference is mandatory for Secondary Item {1}.").format( item.idx, frappe.bold(item.item_code) ), ) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 53466f7405d..b4b0c930082 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -597,6 +597,7 @@ class TestSubcontractingReceipt(ERPNextTestSuite): scr.items[0].qty = 6 # Accepted Qty scr.items[0].rejected_qty = 4 + scr.set_missing_values() scr.save() # consumed_qty should be (accepted_qty * qty_consumed_per_unit) = (6 * 1) = 6 @@ -1154,7 +1155,7 @@ class TestSubcontractingReceipt(ERPNextTestSuite): # ValidationError should not be raised as `Inspection Required before Purchase` is disabled scr2.submit() - def test_scrap_items_for_subcontracting_receipt(self): + def test_secondary_items_for_subcontracting_receipt(self): set_backflush_based_on("BOM") fg_item = "Subcontracted Item SA1" @@ -1166,9 +1167,9 @@ class TestSubcontractingReceipt(ERPNextTestSuite): ] # Create Scrap Items - scrap_item_1 = make_item(properties={"is_stock_item": 1, "valuation_rate": 10}).name - scrap_item_2 = make_item(properties={"is_stock_item": 1, "valuation_rate": 20}).name - scrap_items = [scrap_item_1, scrap_item_2] + secondary_item_1 = make_item(properties={"is_stock_item": 1, "valuation_rate": 10}).name + secondary_item_2 = make_item(properties={"is_stock_item": 1, "valuation_rate": 20}).name + secondary_items = [secondary_item_1, secondary_item_2] service_items = [ { @@ -1187,13 +1188,14 @@ class TestSubcontractingReceipt(ERPNextTestSuite): ) for idx, item in enumerate(bom.items): item.qty = 1 * (idx + 1) - for idx, item in enumerate(scrap_items): + for idx, item in enumerate(secondary_items): bom.append( - "scrap_items", + "secondary_items", { "item_code": item, "stock_qty": 1 * (idx + 1), "rate": 10 * (idx + 1), + "is_legacy": 1, }, ) bom.save() @@ -1216,12 +1218,13 @@ class TestSubcontractingReceipt(ERPNextTestSuite): # Create Subcontracting Receipt scr = make_subcontracting_receipt(sco.name) scr.save() - scr.get_scrap_items() + scr.get_secondary_items() - # Test - 1: Scrap Items should be fetched from BOM in items table with `is_scrap_item` = 1 - scr_scrap_items = set([item.item_code for item in scr.items if item.is_scrap_item]) + scr_secondary_items = set( + [item.item_code for item in scr.items if item.type or item.is_legacy_scrap_item] + ) self.assertEqual(len(scr.items), 3) # 1 FG Item + 2 Scrap Items - self.assertEqual(scr_scrap_items, set(scrap_items)) + self.assertEqual(scr_secondary_items, set(secondary_items)) scr.submit() diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index 9c1f8e60946..b6d07f66b98 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -8,9 +8,10 @@ "engine": "InnoDB", "field_order": [ "item_code", + "is_legacy_scrap_item", + "type", "column_break_2", "item_name", - "is_scrap_item", "section_break_4", "description", "brand", @@ -22,6 +23,7 @@ "qty", "rejected_qty", "returned_qty", + "process_loss_qty", "col_break2", "stock_uom", "conversion_factor", @@ -33,7 +35,7 @@ "rm_cost_per_qty", "service_cost_per_qty", "additional_cost_per_qty", - "scrap_cost_per_qty", + "secondary_items_cost_per_qty", "rm_supp_cost", "warehouse_and_reference", "warehouse", @@ -144,7 +146,7 @@ "default": "0", "fieldname": "received_qty", "fieldtype": "Float", - "label": "Received Quantity", + "label": "Qty (As per BOM)", "no_copy": 1, "print_hide": 1, "print_width": "100px", @@ -157,22 +159,23 @@ "fieldname": "qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Accepted Quantity", + "label": "Accepted Qty", "no_copy": 1, "print_width": "100px", + "read_only_depends_on": "eval:doc.type || doc.is_legacy_scrap_item", "width": "100px" }, { "columns": 1, - "depends_on": "eval: !parent.is_return", + "depends_on": "eval:!parent.is_return && !doc.type && !doc.is_legacy_scrap_item", "fieldname": "rejected_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Rejected Quantity", + "label": "Rejected Qty", "no_copy": 1, "print_hide": 1, "print_width": "100px", - "read_only_depends_on": "eval: doc.is_scrap_item", + "read_only_depends_on": "eval:doc.type || doc.is_legacy_scrap_item", "width": "100px" }, { @@ -181,6 +184,7 @@ "print_hide": 1 }, { + "fetch_from": "item_code.stock_uom", "fieldname": "stock_uom", "fieldtype": "Link", "label": "Stock UOM", @@ -230,7 +234,7 @@ }, { "default": "0", - "depends_on": "eval: !doc.is_scrap_item", + "depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item", "fieldname": "rm_cost_per_qty", "fieldtype": "Currency", "label": "Raw Material Cost Per Qty", @@ -240,7 +244,7 @@ }, { "default": "0", - "depends_on": "eval: !doc.is_scrap_item", + "depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item", "fieldname": "service_cost_per_qty", "fieldtype": "Currency", "label": "Service Cost Per Qty", @@ -250,7 +254,7 @@ }, { "default": "0", - "depends_on": "eval: !doc.is_scrap_item", + "depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item", "fieldname": "additional_cost_per_qty", "fieldtype": "Currency", "label": "Additional Cost Per Qty", @@ -274,7 +278,7 @@ "width": "100px" }, { - "depends_on": "eval: !parent.is_return", + "depends_on": "eval: !parent.is_return && !doc.type && !doc.is_legacy_scrap_item", "fieldname": "rejected_warehouse", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -283,11 +287,10 @@ "options": "Warehouse", "print_hide": 1, "print_width": "100px", - "read_only_depends_on": "eval: doc.is_scrap_item", "width": "100px" }, { - "depends_on": "eval:!doc.__islocal", + "depends_on": "eval:!doc.__islocal && !doc.type && !doc.is_legacy_scrap_item", "fieldname": "quality_inspection", "fieldtype": "Link", "label": "Quality Inspection", @@ -369,7 +372,7 @@ "no_copy": 1, "options": "BOM", "print_hide": 1, - "read_only_depends_on": "eval: doc.is_scrap_item" + "read_only_depends_on": "eval:doc.type || doc.is_legacy_scrap_item" }, { "fetch_from": "item_code.brand", @@ -496,7 +499,7 @@ "print_hide": 1 }, { - "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1", + "depends_on": "eval:(doc.use_serial_batch_fields === 0 || doc.docstatus === 1) && !doc.type && !doc.is_legacy_scrap_item", "fieldname": "rejected_serial_and_batch_bundle", "fieldtype": "Link", "label": "Rejected Serial and Batch Bundle", @@ -504,26 +507,6 @@ "options": "Serial and Batch Bundle", "print_hide": 1 }, - { - "default": "0", - "depends_on": "eval: !doc.bom", - "fieldname": "is_scrap_item", - "fieldtype": "Check", - "label": "Is Scrap Item", - "no_copy": 1, - "print_hide": 1, - "read_only_depends_on": "eval: doc.bom" - }, - { - "default": "0", - "depends_on": "eval: !doc.is_scrap_item", - "fieldname": "scrap_cost_per_qty", - "fieldtype": "Float", - "label": "Scrap Cost Per Qty", - "no_copy": 1, - "non_negative": 1, - "read_only": 1 - }, { "fieldname": "reference_name", "fieldtype": "Data", @@ -553,6 +536,7 @@ }, { "default": "0", + "depends_on": "eval:doc.bom", "fieldname": "include_exploded_items", "fieldtype": "Check", "label": "Include Exploded Items", @@ -580,7 +564,7 @@ "label": "Add Serial / Batch Bundle" }, { - "depends_on": "eval:doc.use_serial_batch_fields === 0", + "depends_on": "eval:doc.use_serial_batch_fields === 0 && !doc.type && !doc.is_legacy_scrap_item", "fieldname": "add_serial_batch_for_rejected_qty", "fieldtype": "Button", "label": "Add Serial / Batch No (Rejected Qty)" @@ -594,6 +578,7 @@ "search_index": 1 }, { + "depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item", "fieldname": "landed_cost_voucher_amount", "fieldtype": "Currency", "label": "Landed Cost Voucher Amount", @@ -609,13 +594,48 @@ "fieldtype": "Link", "label": "Service Expense Account", "options": "Account" + }, + { + "fieldname": "type", + "fieldtype": "Select", + "label": "Type", + "no_copy": 1, + "options": "\nCo-Product\nBy-Product\nScrap\nAdditional Finished Good", + "print_hide": 1, + "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item", + "fieldname": "secondary_items_cost_per_qty", + "fieldtype": "Currency", + "label": "Secondary Items Cost Per Qty", + "no_copy": 1, + "non_negative": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "default": "0", + "depends_on": "is_legacy_scrap_item", + "fieldname": "is_legacy_scrap_item", + "fieldtype": "Check", + "label": "Is Legacy Scrap Item", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "process_loss_qty", + "fieldtype": "Float", + "label": "Process Loss Qty", + "non_negative": 1 } ], "grid_page_length": 50, "idx": 1, "istable": 1, "links": [], - "modified": "2025-09-26 12:00:38.877638", + "modified": "2026-03-09 15:11:16.977539", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py index e916a90462f..c6233b841a2 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py @@ -25,7 +25,7 @@ class SubcontractingReceiptItem(Document): expense_account: DF.Link | None image: DF.Attach | None include_exploded_items: DF.Check - is_scrap_item: DF.Check + is_legacy_scrap_item: DF.Check item_code: DF.Link item_name: DF.Data | None job_card: DF.Link | None @@ -36,6 +36,7 @@ class SubcontractingReceiptItem(Document): parent: DF.Data parentfield: DF.Data parenttype: DF.Data + process_loss_qty: DF.Float project: DF.Link | None purchase_order: DF.Link | None purchase_order_item: DF.Data | None @@ -52,7 +53,7 @@ class SubcontractingReceiptItem(Document): rm_cost_per_qty: DF.Currency rm_supp_cost: DF.Currency schedule_date: DF.Date | None - scrap_cost_per_qty: DF.Float + secondary_items_cost_per_qty: DF.Currency serial_and_batch_bundle: DF.Link | None serial_no: DF.SmallText | None service_cost_per_qty: DF.Currency @@ -61,6 +62,7 @@ class SubcontractingReceiptItem(Document): subcontracting_order: DF.Link | None subcontracting_order_item: DF.Data | None subcontracting_receipt_item: DF.Data | None + type: DF.Literal["", "Co-Product", "By-Product", "Scrap", "Additional Finished Good"] use_serial_batch_fields: DF.Check warehouse: DF.Link | None # end: auto-generated types From 5a7d0d2765c199d7ddd7b60c7d5c7f02dcee1ea8 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 31 Mar 2026 17:31:36 +0530 Subject: [PATCH 086/168] fix: hide fields related to track Semi-Finished Goods if feature has disabled (cherry picked from commit 399faf0ced853faf5ab3d087b7332da11bdea6d8) --- erpnext/manufacturing/doctype/bom/bom.js | 47 ++++++++++++++++++- .../doctype/bom_operation/bom_operation.json | 3 +- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 1dc64997198..0ff10f8383a 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -19,6 +19,21 @@ frappe.ui.form.on("BOM", { }; }); + frm.set_query("workstation", "operations", function (doc, cdt, cdn) { + let row = locals[cdt][cdn]; + let filters = { + disabled: 0, + }; + + if (row.workstation_type) { + filters.workstation_type = row.workstation_type; + } + + return { + filters: filters, + }; + }); + frm.set_query("operation", "items", function () { if (!frm.doc.operations?.length) { frappe.throw(__("Please add Operations first.")); @@ -123,7 +138,16 @@ frappe.ui.form.on("BOM", { }, toggle_fields_for_semi_finished_goods(frm) { - let fields = ["finished_good", "finished_good_qty", "bom_no"]; + let fields = [ + "finished_good", + "finished_good_qty", + "bom_no", + "skip_material_transfer", + "wip_warehouse", + "fg_warehouse", + "is_subcontracted", + "is_final_finished_good", + ]; fields.forEach((field) => { frm.fields_dict["operations"].grid.update_docfield_property( @@ -131,9 +155,21 @@ frappe.ui.form.on("BOM", { "read_only", !frm.doc.track_semi_finished_goods ); + + frm.fields_dict["operations"].grid.update_docfield_property( + field, + "in_list_view", + frm.doc.track_semi_finished_goods + ); + + frm.fields_dict["operations"].grid.update_docfield_property( + field, + "hidden", + !frm.doc.track_semi_finished_goods + ); }); - refresh_field("operations"); + frm.fields_dict["operations"].grid.reset_grid(); }, with_operations: function (frm) { @@ -173,6 +209,8 @@ frappe.ui.form.on("BOM", { refresh(frm) { frm.toggle_enable("item", frm.doc.__islocal); + frm.trigger("toggle_fields_for_semi_finished_goods"); + frm.set_indicator_formatter("item_code", function (doc) { if (doc.original_item) { return doc.item_code != doc.original_item ? "orange" : ""; @@ -864,6 +902,11 @@ frappe.ui.form.on("BOM Operation", "workstation", function (frm, cdt, cdn) { frappe.ui.form.on("BOM Operation", "workstation_type", function (frm, cdt, cdn) { var d = locals[cdt][cdn]; if (!d.workstation_type) return; + + if (d.workstation) { + frappe.model.set_value(cdt, cdn, "workstation", ""); + } + frappe.call({ method: "frappe.client.get", args: { diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index ad33af6dfff..11c704649a3 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -55,7 +55,6 @@ }, { "columns": 2, - "depends_on": "eval:!doc.workstation_type", "fieldname": "workstation", "fieldtype": "Link", "in_list_view": 1, @@ -297,7 +296,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2026-02-17 15:33:28.495850", + "modified": "2026-03-31 17:09:48.771834", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", From 5aaca83fe460500c73de4d3194891898580d72d3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 08:11:20 +0000 Subject: [PATCH 087/168] fix: remove reference in serial/batch when document is cancelled (backport #53979) (#53989) --- .../serial_and_batch_bundle.py | 38 +++++++++++++++++++ .../test_serial_and_batch_bundle.py | 32 ++++++++++++++++ 2 files changed, 70 insertions(+) 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 45790ed89c4..5220c2b6274 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 @@ -1489,6 +1489,7 @@ class SerialandBatchBundle(Document): def on_cancel(self): self.validate_voucher_no_docstatus() self.validate_batch_quantity() + self.remove_source_document_no() def validate_batch_quantity(self): if not self.has_batch_no: @@ -1507,6 +1508,43 @@ class SerialandBatchBundle(Document): if flt(available_qty, precision) < 0: self.throw_negative_batch(d.batch_no, available_qty, precision) + def remove_source_document_no(self): + if not self.has_serial_no and not self.has_batch_no: + return + + if self.total_qty <= 0: + return + + if self.has_serial_no: + serial_nos = [d.serial_no for d in self.entries if d.serial_no] + sn_table = frappe.qb.DocType("Serial No") + ( + frappe.qb.update(sn_table) + .set(sn_table.reference_doctype, None) + .set(sn_table.reference_name, None) + .set(sn_table.posting_date, None) + .where( + (sn_table.name.isin(serial_nos)) + & (sn_table.reference_doctype == self.voucher_type) + & (sn_table.reference_name == self.voucher_no) + & (sn_table.posting_date == getdate(self.posting_datetime)) + ) + ).run() + + if self.has_batch_no: + batch_nos = [d.batch_no for d in self.entries if d.batch_no] + batch_table = frappe.qb.DocType("Batch") + ( + frappe.qb.update(batch_table) + .set(batch_table.reference_doctype, None) + .set(batch_table.reference_name, None) + .where( + (batch_table.name.isin(batch_nos)) + & (batch_table.reference_doctype == self.voucher_type) + & (batch_table.reference_name == self.voucher_no) + ) + ).run() + def throw_negative_batch(self, batch_no, available_qty, precision, posting_datetime=None): from erpnext.stock.stock_ledger import NegativeStockError 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 c6929fe4cdb..ab360d8133b 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 @@ -1077,6 +1077,38 @@ class TestSerialandBatchBundle(ERPNextTestSuite): self.assertTrue(bundle_doc.docstatus == 0) self.assertRaises(frappe.ValidationError, bundle_doc.submit) + def test_reference_voucher_on_cancel(self): + """ + When a source document is cancelled, the reference voucher field + in the respective serial or batch document should be nullified. + """ + + item_code = make_item( + "Serial Item", + properties={ + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "SERIAL.#####", + }, + ).name + + se = make_stock_entry( + item_code=item_code, + qty=1, + target="_Test Warehouse - _TC", + ) + serial_no = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle)[0] + self.assertEqual(frappe.get_value("Serial No", serial_no, "reference_name"), se.name) + + se.cancel() + self.assertIsNone(frappe.get_value("Serial No", serial_no, "reference_name")) + + se1 = frappe.copy_doc(se, ignore_no_copy=False) + se1.items[0].serial_no = serial_no + se1.submit() + + self.assertEqual(frappe.get_value("Serial No", serial_no, "reference_name"), se1.name) + def get_batch_from_bundle(bundle): from erpnext.stock.serial_batch_bundle import get_batch_nos From fc5a04db2e92e92f05965b24ac5289ab3fa93163 Mon Sep 17 00:00:00 2001 From: kavin-114 Date: Thu, 2 Apr 2026 23:27:24 +0530 Subject: [PATCH 088/168] fix(stock): update stock queue in SABE for return entries (cherry picked from commit 0af8077bcc828422593dfa51b99bcac249a8bbed) --- .../serial_and_batch_bundle.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) 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 5220c2b6274..5daab368156 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 @@ -410,6 +410,25 @@ class SerialandBatchBundle(Document): def set_valuation_rate_for_return_entry(self, return_against, row, save=False, prev_sle=None): if valuation_details := self.get_valuation_rate_for_return_entry(return_against): + from erpnext.stock.utils import get_valuation_method + + valuation_method = get_valuation_method(self.item_code, self.company) + + stock_queue = [] + non_batchwise_batches = [] + if not self.has_serial_no and valuation_method == "FIFO": + non_batchwise_batches = frappe.get_all( + "Batch", + filters={ + "name": ("in", [d.batch_no for d in self.entries if d.batch_no]), + "use_batchwise_valuation": 0, + }, + pluck="name", + ) + + if non_batchwise_batches and prev_sle and prev_sle.stock_queue: + stock_queue = parse_json(prev_sle.stock_queue) + for row in self.entries: if valuation_details: self.validate_returned_serial_batch_no(return_against, row, valuation_details) @@ -431,11 +450,25 @@ class SerialandBatchBundle(Document): row.incoming_rate = flt(valuation_rate) row.stock_value_difference = flt(row.qty) * flt(row.incoming_rate) + if ( + non_batchwise_batches + and row.batch_no in non_batchwise_batches + and row.incoming_rate is not None + ): + if flt(row.qty) > 0: + stock_queue.append([row.qty, row.incoming_rate]) + elif flt(row.qty) < 0: + stock_queue = FIFOValuation(stock_queue) + stock_queue.remove_stock(qty=abs(row.qty)) + stock_queue = stock_queue.state + row.stock_queue = json.dumps(stock_queue) + if save: row.db_set( { "incoming_rate": row.incoming_rate, "stock_value_difference": row.stock_value_difference, + "stock_queue": row.get("stock_queue"), } ) From d3f1bfc628b1651797c22d26adcbaa07a201b388 Mon Sep 17 00:00:00 2001 From: kavin-114 Date: Fri, 3 Apr 2026 00:02:42 +0530 Subject: [PATCH 089/168] test(stock): add unit test to update stock queue for return (cherry picked from commit e537896df882f81fcabd999a9aa74f1cd1aa7462) --- .../test_serial_and_batch_bundle.py | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) 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 ab360d8133b..37d4a45f954 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 @@ -1109,6 +1109,173 @@ class TestSerialandBatchBundle(ERPNextTestSuite): self.assertEqual(frappe.get_value("Serial No", serial_no, "reference_name"), se1.name) + def test_stock_queue_for_return_entry_with_non_batchwise_valuation(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + + batch_item_code = "Old Batch Return Queue Test" + make_item( + batch_item_code, + { + "has_batch_no": 1, + "batch_number_series": "TEST-RET-Q-.#####", + "create_new_batch": 1, + "is_stock_item": 1, + "valuation_method": "FIFO", + }, + ) + + batch_id = "Old Batch Return Queue 1" + 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) + + batch_doc.db_set( + { + "use_batchwise_valuation": 0, + "batch_qty": 0, + } + ) + + # Create initial stock with FIFO queue: [[10, 100], [20, 200]] + make_stock_entry( + item_code=batch_item_code, + target="_Test Warehouse - _TC", + qty=10, + rate=100, + batch_no=batch_id, + use_serial_batch_fields=True, + ) + + make_stock_entry( + item_code=batch_item_code, + target="_Test Warehouse - _TC", + qty=20, + rate=200, + batch_no=batch_id, + use_serial_batch_fields=True, + ) + + # Purchase Receipt: inward 5 @ 300 + pr = make_purchase_receipt( + item_code=batch_item_code, + warehouse="_Test Warehouse - _TC", + qty=5, + rate=300, + batch_no=batch_id, + use_serial_batch_fields=True, + ) + + sle = frappe.db.get_value( + "Stock Ledger Entry", + {"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": pr.name}, + ["stock_queue"], + as_dict=True, + ) + + # Stock queue should now be [[10, 100], [20, 200], [5, 300]] + self.assertEqual(json.loads(sle.stock_queue), [[10, 100], [20, 200], [5, 300]]) + + # Purchase Return: return 5 against the PR + return_pr = make_return_doc("Purchase Receipt", pr.name) + return_pr.submit() + + return_sle = frappe.db.get_value( + "Stock Ledger Entry", + {"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": return_pr.name}, + ["stock_queue"], + as_dict=True, + ) + + # Stock queue should have 5 removed via FIFO from [[10, 100], [20, 200], [5, 300]] + # FIFO removes from front: [10, 100] -> [5, 100], rest unchanged + self.assertEqual(json.loads(return_sle.stock_queue), [[5, 100], [20, 200], [5, 300]]) + + def test_stock_queue_for_return_entry_with_empty_fifo_queue(self): + """Credit note (sales return) against empty FIFO queue should still rebuild stock_queue.""" + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + batch_item_code = "Old Batch Empty Queue Test" + make_item( + batch_item_code, + { + "has_batch_no": 1, + "batch_number_series": "TEST-EQ-.#####", + "create_new_batch": 1, + "is_stock_item": 1, + "valuation_method": "FIFO", + }, + ) + + batch_id = "Old Batch Empty Queue 1" + 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) + + batch_doc.db_set( + { + "use_batchwise_valuation": 0, + "batch_qty": 0, + } + ) + + # Inward 10 @ 100, then outward all 10 to empty the queue + make_stock_entry( + item_code=batch_item_code, + target="_Test Warehouse - _TC", + qty=10, + rate=100, + batch_no=batch_id, + use_serial_batch_fields=True, + ) + + dn = create_delivery_note( + item_code=batch_item_code, + warehouse="_Test Warehouse - _TC", + qty=10, + rate=150, + batch_no=batch_id, + use_serial_batch_fields=True, + ) + + # Verify queue is empty after full outward + sle = frappe.db.get_value( + "Stock Ledger Entry", + {"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": dn.name}, + ["stock_queue"], + as_dict=True, + ) + self.assertFalse(json.loads(sle.stock_queue or "[]")) + + # Sales return (credit note): 5 items come back at original rate 100 + return_dn = make_return_doc("Delivery Note", dn.name) + for row in return_dn.items: + row.qty = -5 + return_dn.save().submit() + + return_sle = frappe.db.get_value( + "Stock Ledger Entry", + {"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": return_dn.name}, + ["stock_queue"], + as_dict=True, + ) + + # Stock queue should have the returned stock: [[5, 100]] + self.assertEqual(json.loads(return_sle.stock_queue), [[5, 100]]) + def get_batch_from_bundle(bundle): from erpnext.stock.serial_batch_bundle import get_batch_nos From 01610b2fa7a0a772edf8a76399ebb0161012b3b5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 5 Apr 2026 07:19:23 +0000 Subject: [PATCH 090/168] fix(manufacturing): handle null cur_dialog in BOM work order dialog (backport #54011) (#54015) --- erpnext/manufacturing/doctype/bom/bom.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 0ff10f8383a..32c543703bc 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -407,6 +407,7 @@ frappe.ui.form.on("BOM", { reqd: 1, default: 1, onchange: () => { + if (!cur_dialog) return; const { quantity, items: rm } = frm.doc; const variant_items_map = rm.reduce((acc, item) => { acc[item.item_code] = item.qty; From bd67ef8d2652ea09155f30db3015eb6dc853fcfb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 4 Apr 2026 13:22:51 +0530 Subject: [PATCH 091/168] fix: screen freezes if consumed qty set in SCR (cherry picked from commit dd7be2b370d958a6429dad1b867a151e1af97908) --- erpnext/public/js/utils.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 06ccc25a94e..935cae7f571 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -21,6 +21,10 @@ $.extend(erpnext, { toggle_serial_batch_fields(frm) { let hide_fields = cint(frappe.user_defaults?.enable_serial_and_batch_no_for_item) === 0 ? 1 : 0; + if (!hide_fields) { + return; + } + let fields = ["serial_and_batch_bundle", "use_serial_batch_fields", "serial_no", "batch_no"]; if ( @@ -60,6 +64,12 @@ $.extend(erpnext, { child_name = "stock_items"; } + let sn_field = frm.fields_dict[child_name].grid.docfields.filter((d) => d.fieldname === "serial_no"); + if (sn_field?.length && sn_field[0].hidden === 1) { + // Already field is hidden + return; + } + fields.forEach((field) => { if (frm.fields_dict[child_name].get_field(field)) { frm.fields_dict[child_name].grid.update_docfield_property(field, "hidden", hide_fields); @@ -72,7 +82,11 @@ $.extend(erpnext, { if ( frm.doc.doctype === "Subcontracting Receipt" && - !["add_serial_batch_for_rejected_qty", "rejected_serial_and_batch_bundle"].includes(field) + ![ + "add_serial_batch_for_rejected_qty", + "rejected_serial_and_batch_bundle", + "rejected_serial_no", + ].includes(field) ) { frm.fields_dict["supplied_items"].grid.update_docfield_property( field, @@ -85,12 +99,14 @@ $.extend(erpnext, { "in_list_view", hide_fields ? 0 : 1 ); - - frm.fields_dict["supplied_items"].grid.reset_grid(); } } }); + if (frm.doc.doctype === "Subcontracting Receipt") { + frm.fields_dict["supplied_items"].grid.reset_grid(); + } + frm.fields_dict[child_name].grid.reset_grid(); }, From 1c0956c6e24230cb05001702c6a43c8733214d4e Mon Sep 17 00:00:00 2001 From: MochaMind Date: Sun, 5 Apr 2026 19:32:11 +0530 Subject: [PATCH 092/168] chore: update POT file (#54017) --- erpnext/locale/main.pot | 1537 +++++++++++++++++++++------------------ 1 file changed, 837 insertions(+), 700 deletions(-) diff --git a/erpnext/locale/main.pot b/erpnext/locale/main.pot index 21fb2c6830e..2ce76e47917 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-29 09:46+0000\n" -"PO-Revision-Date: 2026-03-29 09:46+0000\n" +"POT-Creation-Date: 2026-04-05 09:48+0000\n" +"PO-Revision-Date: 2026-04-05 09:48+0000\n" "Last-Translator: hello@frappe.io\n" "Language-Team: hello@frappe.io\n" "MIME-Version: 1.0\n" @@ -16,7 +16,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1520 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1558 msgid "" "\n" "\t\t\tThe Batch {0} of an item {1} has negative stock in the warehouse {2}{3}.\n" @@ -149,6 +149,11 @@ msgstr "" msgid "% Completed" msgstr "" +#. Label of the cost_allocation_per (Percent) field in DocType 'BOM' +#: erpnext/manufacturing/doctype/bom/bom.json +msgid "% Cost Allocation" +msgstr "" + #. Label of the per_delivered (Percent) field in DocType 'Pick List' #. Label of the per_delivered (Percent) field in DocType 'Subcontracting Inward #. Order' @@ -157,7 +162,7 @@ msgstr "" msgid "% Delivered" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:1000 +#: erpnext/manufacturing/doctype/bom/bom.js:1017 #, python-format msgid "% Finished Item Quantity" msgstr "" @@ -969,7 +974,7 @@ msgstr "" msgid "A - C" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:353 +#: erpnext/selling/doctype/customer/customer.py:354 msgid "A Customer Group exists with same name please change the Customer name or rename the Customer Group" msgstr "" @@ -1165,7 +1170,9 @@ msgid "Acceptance Criteria Value" msgstr "" #. Label of the qty (Float) field in DocType 'Purchase Invoice Item' +#. Label of the qty (Float) field in DocType 'Subcontracting Receipt Item' #: erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json msgid "Accepted Qty" msgstr "" @@ -1177,10 +1184,8 @@ msgid "Accepted Qty in Stock UOM" msgstr "" #. Label of the qty (Float) field in DocType 'Purchase Receipt Item' -#. Label of the qty (Float) field in DocType 'Subcontracting Receipt Item' -#: erpnext/public/js/controllers/transaction.js:2919 +#: erpnext/public/js/controllers/transaction.js:2923 #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json -#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json msgid "Accepted Quantity" msgstr "" @@ -1848,12 +1853,12 @@ msgstr "" msgid "Accounting Entry for Asset" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1959 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1979 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1964 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1984 msgid "Accounting Entry for LCV in Stock Entry {0}" msgstr "" -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:873 +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:910 msgid "Accounting Entry for Landed Cost Voucher for SCR {0}" msgstr "" @@ -1873,9 +1878,9 @@ msgstr "" #: erpnext/controllers/stock_controller.py:727 #: erpnext/controllers/stock_controller.py:744 #: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:937 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1904 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1918 -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:708 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1909 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1923 +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:745 msgid "Accounting Entry for Stock" msgstr "" @@ -2513,7 +2518,7 @@ msgstr "" msgid "Add / Edit Prices" msgstr "" -#: erpnext/accounts/report/general_ledger/general_ledger.js:207 +#: erpnext/accounts/report/general_ledger/general_ledger.js:208 msgid "Add Columns in Transaction Currency" msgstr "" @@ -2604,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:1028 +#: erpnext/manufacturing/doctype/bom/bom.js:1045 #: erpnext/manufacturing/doctype/bom_operation/bom_operation.json msgid "Add Raw Materials" msgstr "" @@ -2689,7 +2694,7 @@ msgid "Add details" msgstr "" #: erpnext/stock/doctype/pick_list/pick_list.js:86 -#: erpnext/stock/doctype/pick_list/pick_list.py:905 +#: erpnext/stock/doctype/pick_list/pick_list.py:932 msgid "Add items in the Item Locations table" msgstr "" @@ -2880,7 +2885,7 @@ msgstr "" msgid "Additional Discount Amount (Company Currency)" msgstr "" -#: erpnext/controllers/taxes_and_totals.py:796 +#: erpnext/controllers/taxes_and_totals.py:833 msgid "Additional Discount Amount ({discount_amount}) cannot exceed the total before such discount ({total_before_discount})" msgstr "" @@ -2917,6 +2922,21 @@ msgstr "" msgid "Additional Discount Percentage" msgstr "" +#. Option for the 'Type' (Select) field in DocType 'BOM Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Job Card Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Stock Entry Detail' +#. Option for the 'Type' (Select) field in DocType 'Subcontracting Inward Order +#. Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Subcontracting Receipt +#. Item' +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json +#: erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json +#: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +#: erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json +#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +msgid "Additional Finished Good" +msgstr "" + #. Label of the addtional_info (Section Break) field in DocType 'Journal Entry' #. Label of the additional_info_section (Section Break) field in DocType #. 'Purchase Invoice' @@ -2960,7 +2980,7 @@ msgstr "" msgid "Additional Information updated successfully." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:811 +#: erpnext/manufacturing/doctype/work_order/work_order.js:812 msgid "Additional Material Transfer" msgstr "" @@ -2997,7 +3017,7 @@ msgstr "" msgid "Additional information regarding the customer." msgstr "" -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:590 +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:627 msgid "Additional {0} {1} of item {2} required as per BOM to complete this transaction" msgstr "" @@ -3264,7 +3284,7 @@ msgstr "" msgid "Advance amount" msgstr "" -#: erpnext/controllers/taxes_and_totals.py:933 +#: erpnext/controllers/taxes_and_totals.py:970 msgid "Advance amount cannot be greater than {0} {1}" msgstr "" @@ -3480,7 +3500,7 @@ msgstr "" msgid "Age (Days)" msgstr "" -#: erpnext/stock/report/stock_ageing/stock_ageing.py:220 +#: erpnext/stock/report/stock_ageing/stock_ageing.py:221 msgid "Age ({0})" msgstr "" @@ -3600,7 +3620,7 @@ msgstr "" msgid "All Activities HTML" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:369 +#: erpnext/manufacturing/doctype/bom/bom.py:390 msgid "All BOMs" msgstr "" @@ -3745,11 +3765,11 @@ msgstr "" msgid "All items have already been received" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3111 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3136 msgid "All items have already been transferred for this Work Order." msgstr "" -#: erpnext/public/js/controllers/transaction.js:3028 +#: erpnext/public/js/controllers/transaction.js:3032 msgid "All items in this document already have a linked Quality Inspection." msgstr "" @@ -3771,7 +3791,7 @@ msgstr "" msgid "All the items have been already returned." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1195 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1196 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 "" @@ -3978,16 +3998,6 @@ msgstr "" msgid "Allow Lead Duplication based on Emails" msgstr "" -#. Label of the allow_from_dn (Check) field in DocType 'Stock Settings' -#: erpnext/stock/doctype/stock_settings/stock_settings.json -msgid "Allow Material Transfer from Delivery Note to Sales Invoice" -msgstr "" - -#. Label of the allow_from_pr (Check) field in DocType 'Stock Settings' -#: erpnext/stock/doctype/stock_settings/stock_settings.json -msgid "Allow Material Transfer from Purchase Receipt to Purchase Invoice" -msgstr "" - #: erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js:9 msgid "Allow Multiple Material Consumption" msgstr "" @@ -4005,8 +4015,8 @@ msgstr "" #: erpnext/stock/doctype/item/item.json #: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json #: erpnext/stock/doctype/stock_settings/stock_settings.json -#: erpnext/stock/doctype/stock_settings/stock_settings.py:217 -#: erpnext/stock/doctype/stock_settings/stock_settings.py:229 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:215 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:227 msgid "Allow Negative Stock" msgstr "" @@ -4308,7 +4318,7 @@ msgstr "" msgid "Allows users to submit Supplier Quotations with zero quantity. Useful when rates are fixed but the quantities are not. Eg. Rate Contracts." msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:1048 +#: erpnext/stock/doctype/pick_list/pick_list.py:1081 msgid "Already Picked" msgstr "" @@ -4324,10 +4334,10 @@ msgstr "" msgid "Also you can't switch back to FIFO after setting the valuation method to Moving Average for this item." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:250 +#: erpnext/manufacturing/doctype/bom/bom.js:288 #: erpnext/manufacturing/doctype/work_order/work_order.js:165 #: erpnext/manufacturing/doctype/work_order/work_order.js:180 -#: erpnext/public/js/utils.js:567 +#: erpnext/public/js/utils.js:571 #: erpnext/stock/doctype/stock_entry/stock_entry.js:288 msgid "Alternate Item" msgstr "" @@ -4429,7 +4439,6 @@ msgstr "" #. Label of the amount (Currency) field in DocType 'BOM Creator Item' #. Label of the amount (Currency) field in DocType 'BOM Explosion Item' #. Label of the amount (Currency) field in DocType 'BOM Item' -#. Label of the amount (Currency) field in DocType 'BOM Scrap Item' #. Label of the amount (Currency) field in DocType 'Work Order Item' #. Option for the 'Margin Type' (Select) field in DocType 'Quotation Item' #. Label of the amount (Currency) field in DocType 'Quotation Item' @@ -4524,9 +4533,8 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json #: erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json #: 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/controllers/transaction.js:506 +#: erpnext/public/js/controllers/transaction.js:510 #: erpnext/selling/doctype/quotation/quotation.js:316 #: erpnext/selling/doctype/quotation_item/quotation_item.json #: erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -4757,7 +4765,7 @@ msgstr "" msgid "Analytical Accounting" msgstr "" -#: erpnext/public/js/utils.js:164 +#: erpnext/public/js/utils.js:168 msgid "Annual Billing: {0}" msgstr "" @@ -5231,7 +5239,7 @@ msgstr "" msgid "As there are existing submitted transactions against item {0}, you can not change the value of {1}." msgstr "" -#: erpnext/stock/doctype/stock_settings/stock_settings.py:242 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:240 msgid "As there are reserved stock, you cannot disable {0}." msgstr "" @@ -5239,12 +5247,12 @@ msgstr "" msgid "As there are sufficient Sub Assembly Items, Work Order is not required for Warehouse {0}." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1832 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1834 msgid "As there are sufficient raw materials, Material Request is not required for Warehouse {0}." msgstr "" -#: erpnext/stock/doctype/stock_settings/stock_settings.py:216 -#: erpnext/stock/doctype/stock_settings/stock_settings.py:228 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:214 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:226 msgid "As {0} is enabled, you can not enable {1}." msgstr "" @@ -5775,7 +5783,7 @@ msgstr "" msgid "Asset {0} must be submitted" msgstr "" -#: erpnext/controllers/buying_controller.py:1030 +#: erpnext/controllers/buying_controller.py:1034 msgid "Asset {assets_link} created for {item_code}" msgstr "" @@ -5813,11 +5821,11 @@ msgstr "" msgid "Assets Setup" msgstr "" -#: erpnext/controllers/buying_controller.py:1048 +#: erpnext/controllers/buying_controller.py:1052 msgid "Assets not created for {item_code}. You will have to create asset manually." msgstr "" -#: erpnext/controllers/buying_controller.py:1035 +#: erpnext/controllers/buying_controller.py:1039 msgid "Assets {assets_link} created for {item_code}" msgstr "" @@ -5841,11 +5849,11 @@ msgstr "" msgid "Associate" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:134 +#: erpnext/stock/doctype/pick_list/pick_list.py:135 msgid "At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} for the batch {4} in the warehouse {5}. Please restock the item." msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:159 +#: erpnext/stock/doctype/pick_list/pick_list.py:160 msgid "At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} in the warehouse {4}." msgstr "" @@ -6184,7 +6192,7 @@ msgstr "" msgid "Auto Reserve Stock for Sales Order on Purchase" msgstr "" -#: erpnext/accounts/doctype/accounts_settings/accounts_settings.py:185 +#: erpnext/accounts/doctype/accounts_settings/accounts_settings.py:186 msgid "Auto Tax Settings Error" msgstr "" @@ -6311,10 +6319,10 @@ msgstr "" #: erpnext/manufacturing/doctype/workstation/workstation.js:505 #: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:118 #: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:175 -#: erpnext/public/js/utils.js:627 +#: erpnext/public/js/utils.js:631 #: 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 +#: erpnext/stock/report/stock_ageing/stock_ageing.py:170 msgid "Available Qty" msgstr "" @@ -6415,8 +6423,8 @@ msgstr "" msgid "Available-for-use Date should be after purchase date" msgstr "" -#: erpnext/stock/report/stock_ageing/stock_ageing.py:170 -#: erpnext/stock/report/stock_ageing/stock_ageing.py:204 +#: erpnext/stock/report/stock_ageing/stock_ageing.py:171 +#: erpnext/stock/report/stock_ageing/stock_ageing.py:205 #: erpnext/stock/report/stock_balance/stock_balance.py:590 msgid "Average Age" msgstr "" @@ -6548,7 +6556,7 @@ msgstr "" msgid "BOM 1" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1760 +#: erpnext/manufacturing/doctype/bom/bom.py:1806 msgid "BOM 1 {0} and BOM 2 {1} should not be same" msgstr "" @@ -6693,11 +6701,6 @@ msgstr "" msgid "BOM Rate" msgstr "" -#. Name of a DocType -#: erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json -msgid "BOM Scrap Item" -msgstr "" - #. Label of a Link in the Manufacturing Workspace #. Name of a report #. Label of a Workspace Sidebar Item @@ -6707,6 +6710,19 @@ msgstr "" msgid "BOM Search" msgstr "" +#. Name of a DocType +#. Label of the bom_secondary_item (Data) field in DocType 'Stock Entry Detail' +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json +#: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +msgid "BOM Secondary Item" +msgstr "" + +#. Label of the bom_secondary_item (Data) field in DocType 'Job Card Secondary +#. Item' +#: erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json +msgid "BOM Secondary Item Reference" +msgstr "" + #. Name of a report #: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.json msgid "BOM Stock Analysis" @@ -6775,14 +6791,10 @@ msgstr "" msgid "BOM Website Operation" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2279 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2284 msgid "BOM and Finished Good Quantity is mandatory for Disassembly" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.js:1343 -msgid "BOM and Manufacturing Quantity are required" -msgstr "" - #. Label of the bom_and_work_order_tab (Tab Break) field in DocType #. 'Manufacturing Settings' #: erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -6798,23 +6810,23 @@ msgstr "" msgid "BOM recursion: {0} cannot be child of {1}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:751 +#: erpnext/manufacturing/doctype/bom/bom.py:789 msgid "BOM recursion: {1} cannot be parent or child of {0}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1494 +#: erpnext/manufacturing/doctype/bom/bom.py:1540 msgid "BOM {0} does not belong to Item {1}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1476 +#: erpnext/manufacturing/doctype/bom/bom.py:1522 msgid "BOM {0} must be active" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1479 +#: erpnext/manufacturing/doctype/bom/bom.py:1525 msgid "BOM {0} must be submitted" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:839 +#: erpnext/manufacturing/doctype/bom/bom.py:877 msgid "BOM {0} not found for the item {1}" msgstr "" @@ -7356,6 +7368,11 @@ msgstr "" msgid "Base Change Amount (Company Currency)" msgstr "" +#. Label of the base_cost (Currency) field in DocType 'BOM Secondary Item' +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json +msgid "Base Cost (Company Currency)" +msgstr "" + #. Label of the base_cost_per_unit (Float) field in DocType 'BOM Operation' #: erpnext/manufacturing/doctype/bom_operation/bom_operation.json msgid "Base Cost Per Unit" @@ -7444,16 +7461,9 @@ msgstr "" msgid "Basic Amount" msgstr "" -#. Label of the base_amount (Currency) field in DocType 'BOM Scrap Item' -#: erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json -msgid "Basic Amount (Company Currency)" -msgstr "" - #. Label of the base_rate (Currency) field in DocType 'BOM Item' -#. Label of the base_rate (Currency) field in DocType 'BOM Scrap Item' #. Label of the base_rate (Currency) field in DocType 'Sales Order Item' #: erpnext/manufacturing/doctype/bom_item/bom_item.json -#: erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json #: erpnext/selling/doctype/sales_order_item/sales_order_item.json msgid "Basic Rate (Company Currency)" msgstr "" @@ -7544,7 +7554,7 @@ msgstr "" #: erpnext/manufacturing/doctype/job_card/job_card.json #: erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js:89 #: erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py:115 -#: erpnext/public/js/controllers/transaction.js:2945 +#: erpnext/public/js/controllers/transaction.js:2949 #: erpnext/public/js/utils/barcode_scanner.js:281 #: erpnext/public/js/utils/serial_no_batch_selector.js:438 #: erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -7579,7 +7589,7 @@ msgstr "" msgid "Batch No is mandatory" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3397 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3435 msgid "Batch No {0} does not exists" msgstr "" @@ -7602,7 +7612,7 @@ msgstr "" msgid "Batch Nos" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1938 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1976 msgid "Batch Nos are created successfully" msgstr "" @@ -7668,12 +7678,12 @@ msgstr "" msgid "Batch {0} is not available in warehouse {1}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3288 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3320 #: erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py:290 msgid "Batch {0} of Item {1} has expired." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3294 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3326 msgid "Batch {0} of Item {1} is disabled." msgstr "" @@ -7740,7 +7750,7 @@ msgstr "" #. Label of a Card Break in the Manufacturing Workspace #. Label of a Link in the Manufacturing Workspace #. Label of a Workspace Sidebar Item -#: erpnext/manufacturing/doctype/bom/bom.py:1326 +#: erpnext/manufacturing/doctype/bom/bom.py:1372 #: erpnext/manufacturing/workspace/manufacturing/manufacturing.json #: erpnext/stock/doctype/material_request/material_request.js:139 #: erpnext/stock/doctype/stock_entry/stock_entry.js:695 @@ -8269,7 +8279,7 @@ msgstr "" msgid "Brokerage" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:193 +#: erpnext/manufacturing/doctype/bom/bom.js:231 msgid "Browse BOM" msgstr "" @@ -8577,6 +8587,21 @@ msgstr "" msgid "By default, the Supplier Name is set as per the Supplier Name entered. If you want Suppliers to be named by a Naming Series choose the 'Naming Series' option." msgstr "" +#. Option for the 'Type' (Select) field in DocType 'BOM Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Job Card Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Stock Entry Detail' +#. Option for the 'Type' (Select) field in DocType 'Subcontracting Inward Order +#. Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Subcontracting Receipt +#. Item' +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json +#: erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json +#: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +#: erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json +#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +msgid "By-Product" +msgstr "" + #. Label of the bypass_credit_limit_check (Check) field in DocType 'Customer #. Credit Limit' #: erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.json @@ -8886,7 +8911,7 @@ msgstr "" msgid "Can be approved by {0}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2529 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2530 msgid "Can not close Work Order. Since {0} Job Cards are in Work In Progress state." msgstr "" @@ -8926,7 +8951,7 @@ msgid "Can refer row only if the charge type is 'On Previous Row Amount' or 'Pre msgstr "" #: erpnext/setup/doctype/company/company.py:207 -#: erpnext/stock/doctype/stock_settings/stock_settings.py:183 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:181 msgid "Can't change the valuation method, as there are transactions against some items which do not have its own valuation method" msgstr "" @@ -9033,7 +9058,7 @@ msgstr "" msgid "Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:587 +#: erpnext/controllers/subcontracting_inward_controller.py:592 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 "" @@ -9041,7 +9066,7 @@ msgstr "" 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 "" -#: erpnext/controllers/buying_controller.py:1137 +#: erpnext/controllers/buying_controller.py:1141 msgid "Cannot cancel this document as it is linked with the submitted asset {asset_link}. Please cancel the asset to continue." msgstr "" @@ -9094,7 +9119,7 @@ msgid "Cannot create Stock Reservation Entries for future dated Purchase Receipt msgstr "" #: erpnext/selling/doctype/sales_order/sales_order.py:1886 -#: erpnext/stock/doctype/pick_list/pick_list.py:254 +#: erpnext/stock/doctype/pick_list/pick_list.py:255 msgid "Cannot create a pick list for Sales Order {0} because it has reserved stock. Please unreserve the stock in order to create a pick list." msgstr "" @@ -9106,7 +9131,7 @@ msgstr "" msgid "Cannot create return for consolidated invoice {0}." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1177 +#: erpnext/manufacturing/doctype/bom/bom.py:1210 msgid "Cannot deactivate or cancel BOM as it is linked with other BOMs" msgstr "" @@ -9140,7 +9165,7 @@ msgstr "" msgid "Cannot delete virtual DocType: {0}. Virtual DocTypes do not have database tables." msgstr "" -#: erpnext/stock/doctype/stock_settings/stock_settings.py:148 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:146 msgid "Cannot disable Serial and Batch No for Item, as there are existing records for serial / batch." msgstr "" @@ -9148,7 +9173,7 @@ msgstr "" msgid "Cannot disable perpetual inventory, as there are existing Stock Ledger Entries for the company {0}. Please cancel the stock transactions first and try again." msgstr "" -#: erpnext/stock/doctype/stock_settings/stock_settings.py:129 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:127 msgid "Cannot disable {0} as it may lead to incorrect stock valuation." msgstr "" @@ -9219,6 +9244,10 @@ msgstr "" msgid "Cannot retrieve link token. Check Error Log for more information" msgstr "" +#: erpnext/selling/doctype/customer/customer.py:367 +msgid "Cannot select a Group type Customer Group. Please select a non-group Customer Group." +msgstr "" + #: erpnext/accounts/doctype/payment_entry/payment_entry.js:1523 #: erpnext/accounts/doctype/payment_entry/payment_entry.js:1701 #: erpnext/accounts/doctype/payment_entry/payment_entry.py:1827 @@ -9485,12 +9514,12 @@ msgstr "" msgid "Categorize By" msgstr "" -#: erpnext/accounts/report/general_ledger/general_ledger.js:116 +#: erpnext/accounts/report/general_ledger/general_ledger.js:117 #: erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js:80 msgid "Categorize by" msgstr "" -#: erpnext/accounts/report/general_ledger/general_ledger.js:129 +#: erpnext/accounts/report/general_ledger/general_ledger.js:130 msgid "Categorize by Account" msgstr "" @@ -9498,7 +9527,7 @@ msgstr "" msgid "Categorize by Item" msgstr "" -#: erpnext/accounts/report/general_ledger/general_ledger.js:133 +#: erpnext/accounts/report/general_ledger/general_ledger.js:134 msgid "Categorize by Party" msgstr "" @@ -9510,14 +9539,14 @@ msgstr "" #. Option for the 'Categorize By' (Select) field in DocType 'Process Statement #. Of Accounts' #: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json -#: erpnext/accounts/report/general_ledger/general_ledger.js:121 +#: erpnext/accounts/report/general_ledger/general_ledger.js:122 msgid "Categorize by Voucher" msgstr "" #. Option for the 'Categorize By' (Select) field in DocType 'Process Statement #. Of Accounts' #: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json -#: erpnext/accounts/report/general_ledger/general_ledger.js:125 +#: erpnext/accounts/report/general_ledger/general_ledger.js:126 msgid "Categorize by Voucher (Consolidated)" msgstr "" @@ -9855,7 +9884,7 @@ msgstr "" #. Label of the reference_date (Date) field in DocType 'Payment Entry' #: erpnext/accounts/doctype/payment_entry/payment_entry.json -#: erpnext/public/js/controllers/transaction.js:2856 +#: erpnext/public/js/controllers/transaction.js:2860 msgid "Cheque/Reference Date" msgstr "" @@ -9909,7 +9938,7 @@ msgstr "" #. Label of the child_row_reference (Data) field in DocType 'Quality #. Inspection' -#: erpnext/public/js/controllers/transaction.js:2951 +#: erpnext/public/js/controllers/transaction.js:2955 #: erpnext/stock/doctype/quality_inspection/quality_inspection.json msgid "Child Row Reference" msgstr "" @@ -10081,7 +10110,7 @@ msgstr "" msgid "Closed Documents" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2452 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2453 msgid "Closed Work Order can not be stopped or Re-opened" msgstr "" @@ -10166,6 +10195,21 @@ msgstr "" msgid "Closing [Opening + Total] " msgstr "" +#. Option for the 'Type' (Select) field in DocType 'BOM Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Job Card Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Stock Entry Detail' +#. Option for the 'Type' (Select) field in DocType 'Subcontracting Inward Order +#. Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Subcontracting Receipt +#. Item' +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json +#: erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json +#: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +#: erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json +#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +msgid "Co-Product" +msgstr "" + #. Name of a DocType #. Label of the code_list (Link) field in DocType 'Common Code' #: erpnext/edi/doctype/code_list/code_list.json @@ -11098,8 +11142,8 @@ msgstr "" msgid "Completed Qty cannot be greater than 'Qty to Manufacture'" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:325 -#: erpnext/manufacturing/doctype/job_card/job_card.js:446 +#: erpnext/manufacturing/doctype/job_card/job_card.js:323 +#: erpnext/manufacturing/doctype/job_card/job_card.js:444 #: erpnext/manufacturing/doctype/workstation/workstation.js:296 msgid "Completed Quantity" msgstr "" @@ -11235,7 +11279,7 @@ msgstr "" msgid "Connection" msgstr "" -#: erpnext/accounts/report/general_ledger/general_ledger.js:175 +#: erpnext/accounts/report/general_ledger/general_ledger.js:176 msgid "Consider Accounting Dimensions" msgstr "" @@ -11245,7 +11289,7 @@ msgstr "" msgid "Consider Minimum Order Qty" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1017 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1018 msgid "Consider Process Loss" msgstr "" @@ -11465,7 +11509,7 @@ msgstr "" msgid "Consumed Stock Total Value" msgstr "" -#: erpnext/stock/doctype/stock_entry_type/stock_entry_type.py:127 +#: erpnext/stock/doctype/stock_entry_type/stock_entry_type.py:132 msgid "Consumed quantity of item {0} exceeds transferred quantity." msgstr "" @@ -11718,6 +11762,7 @@ msgstr "" #. Item Supplied' #. Label of the conversion_factor (Float) field in DocType 'BOM Creator Item' #. Label of the conversion_factor (Float) field in DocType 'BOM Item' +#. Label of the conversion_factor (Float) field in DocType 'BOM Secondary Item' #. Label of the conversion_factor (Float) field in DocType 'Material Request #. Plan Item' #. Label of the conversion_factor (Float) field in DocType 'Delivery Schedule @@ -11745,8 +11790,9 @@ msgstr "" #: erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json #: erpnext/manufacturing/doctype/bom_item/bom_item.json +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json #: erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json -#: erpnext/public/js/utils.js:882 +#: erpnext/public/js/utils.js:886 #: 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 @@ -11854,13 +11900,13 @@ msgstr "" msgid "Corrective Action" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:503 +#: erpnext/manufacturing/doctype/job_card/job_card.js:501 msgid "Corrective Job Card" msgstr "" #. Label of the corrective_operation_section (Tab Break) field in DocType 'Job #. Card' -#: erpnext/manufacturing/doctype/job_card/job_card.js:510 +#: erpnext/manufacturing/doctype/job_card/job_card.js:508 #: erpnext/manufacturing/doctype/job_card/job_card.json msgid "Corrective Operation" msgstr "" @@ -11882,10 +11928,24 @@ msgid "Cosmetics" msgstr "" #. Label of the cost (Currency) field in DocType 'Subscription Plan' +#. Label of the cost (Currency) field in DocType 'BOM Secondary Item' #: erpnext/accounts/doctype/subscription_plan/subscription_plan.json +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json msgid "Cost" msgstr "" +#. Label of the cost_allocation_section (Section Break) field in DocType 'BOM' +#. Label of the cost_allocation (Currency) field in DocType 'BOM' +#: erpnext/manufacturing/doctype/bom/bom.json +msgid "Cost Allocation" +msgstr "" + +#. Label of the cost_allocation_per (Percent) field in DocType 'BOM Secondary +#. Item' +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json +msgid "Cost Allocation %" +msgstr "" + #. Label of the cost_center (Link) field in DocType 'Account Closing Balance' #. Label of the cost_center (Link) field in DocType 'Advance Taxes and Charges' #. Option for the 'Budget Against' (Select) field in DocType 'Budget' @@ -12007,7 +12067,7 @@ msgstr "" #: erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.js:42 #: erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py:204 #: erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js:98 -#: erpnext/accounts/report/general_ledger/general_ledger.js:153 +#: erpnext/accounts/report/general_ledger/general_ledger.js:154 #: erpnext/accounts/report/general_ledger/general_ledger.py:776 #: erpnext/accounts/report/gross_profit/gross_profit.js:68 #: erpnext/accounts/report/gross_profit/gross_profit.py:395 @@ -12145,6 +12205,10 @@ msgstr "" msgid "Cost Per Unit" msgstr "" +#: erpnext/manufacturing/doctype/bom/bom.py:441 +msgid "Cost allocation between finished goods and secondary items should equal 100%" +msgstr "" + #. Title of an incoterm #: erpnext/setup/doctype/incoterm/incoterms.csv:8 msgid "Cost and Freight" @@ -12240,7 +12304,7 @@ msgstr "" msgid "Costing and Billing fields has been updated" msgstr "" -#: erpnext/setup/demo.py:55 +#: erpnext/setup/demo.py:78 msgid "Could Not Delete Demo Data" msgstr "" @@ -12524,11 +12588,11 @@ msgstr "" msgid "Create Payment Entry for Consolidated POS Invoices." msgstr "" -#: erpnext/public/js/controllers/transaction.js:513 +#: erpnext/public/js/controllers/transaction.js:517 msgid "Create Payment Request" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:793 +#: erpnext/manufacturing/doctype/work_order/work_order.js:794 msgid "Create Pick List" msgstr "" @@ -12740,7 +12804,7 @@ msgstr "" msgid "Create a variant with the template image." msgstr "" -#: erpnext/stock/stock_ledger.py:2065 +#: erpnext/stock/stock_ledger.py:2072 msgid "Create an incoming stock transaction for the Item." msgstr "" @@ -12847,7 +12911,11 @@ msgstr "" msgid "Creating User..." msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:300 +#: erpnext/setup/setup_wizard/setup_wizard.py:36 +msgid "Creating demo data" +msgstr "" + +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:305 msgid "Creating {} out of {} {}" msgstr "" @@ -12978,7 +13046,7 @@ msgstr "" msgid "Credit Limit" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:630 +#: erpnext/selling/doctype/customer/customer.py:642 msgid "Credit Limit Crossed" msgstr "" @@ -13071,16 +13139,16 @@ msgstr "" msgid "Credit in Company Currency" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:596 -#: erpnext/selling/doctype/customer/customer.py:651 +#: erpnext/selling/doctype/customer/customer.py:608 +#: erpnext/selling/doctype/customer/customer.py:663 msgid "Credit limit has been crossed for customer {0} ({1}/{2})" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:382 +#: erpnext/selling/doctype/customer/customer.py:394 msgid "Credit limit is already defined for the Company {0}" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:650 +#: erpnext/selling/doctype/customer/customer.py:662 msgid "Credit limit reached for customer {0}" msgstr "" @@ -13135,7 +13203,7 @@ msgstr "" msgid "Criteria weights must add up to 100%" msgstr "" -#: erpnext/accounts/doctype/accounts_settings/accounts_settings.py:172 +#: erpnext/accounts/doctype/accounts_settings/accounts_settings.py:173 msgid "Cron Interval should be between 1 and 59 Min" msgstr "" @@ -13272,7 +13340,7 @@ msgstr "" msgid "Currency of the Closing Account must be {0}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:685 +#: erpnext/manufacturing/doctype/bom/bom.py:723 msgid "Currency of the price list {0} must be {1} or {2}" msgstr "" @@ -15329,9 +15397,10 @@ msgstr "" msgid "Delimiter options" msgstr "" -#. Label of the deliver_scrap_items (Check) field in DocType 'Selling Settings' +#. Label of the deliver_secondary_items (Check) field in DocType 'Selling +#. Settings' #: erpnext/selling/doctype/selling_settings/selling_settings.json -msgid "Deliver Scrap Items" +msgid "Deliver Secondary Items" msgstr "" #. Option for the 'Status' (Select) field in DocType 'Purchase Order' @@ -15394,7 +15463,7 @@ msgstr "" #. Label of the delivered_qty (Float) field in DocType 'Subcontracting Inward #. Order Item' #. Label of the delivered_qty (Float) field in DocType 'Subcontracting Inward -#. Order Scrap Item' +#. Order Secondary Item' #: erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json #: erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json #: erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -15404,7 +15473,7 @@ msgstr "" #: erpnext/stock/report/reserved_stock/reserved_stock.py:131 #: erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py:63 #: erpnext/subcontracting/doctype/subcontracting_inward_order_item/subcontracting_inward_order_item.json -#: erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.json +#: erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json msgid "Delivered Qty" msgstr "" @@ -15440,7 +15509,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:875 +#: erpnext/public/js/utils.js:879 #: 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:1490 @@ -15666,10 +15735,18 @@ msgstr "" msgid "Demo Company" msgstr "" +#: erpnext/setup/demo.py:51 +msgid "Demo Data creation failed." +msgstr "" + #: erpnext/public/js/utils/demo.js:25 msgid "Demo data cleared" msgstr "" +#: erpnext/setup/demo.py:42 +msgid "Demo data creation failed. Check notifications for more info." +msgstr "" + #: erpnext/setup/setup_wizard/data/industry_type.txt:18 msgid "Department Stores" msgstr "" @@ -16208,7 +16285,7 @@ msgstr "" msgid "Disassemble Order" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:443 +#: erpnext/manufacturing/doctype/work_order/work_order.js:444 msgid "Disassemble Qty cannot be less than or equal to 0." msgstr "" @@ -16725,7 +16802,7 @@ msgstr "" msgid "Do Not Use Batch-wise Valuation" msgstr "" -#: erpnext/stock/doctype/stock_settings/stock_settings.py:130 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:128 msgid "Do Not Use Batchwise Valuation" msgstr "" @@ -17237,7 +17314,7 @@ msgstr "" msgid "Each Transaction" msgstr "" -#: erpnext/stock/report/stock_ageing/stock_ageing.py:176 +#: erpnext/stock/report/stock_ageing/stock_ageing.py:177 msgid "Earliest" msgstr "" @@ -17655,7 +17732,7 @@ msgstr "" msgid "Employee {0} does not belong to the company {1}" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:357 +#: erpnext/manufacturing/doctype/job_card/job_card.py:375 msgid "Employee {0} is currently working on another workstation. Please assign another employee." msgstr "" @@ -17832,6 +17909,18 @@ msgstr "" msgid "Enable Stock Reservation" msgstr "" +#. Label of the enable_subscription (Check) field in DocType 'Accounts +#. Settings' +#: erpnext/accounts/doctype/accounts_settings/accounts_settings.json +msgid "Enable Subscription" +msgstr "" + +#. Description of the 'Enable Subscription' (Check) field in DocType 'Accounts +#. Settings' +#: erpnext/accounts/doctype/accounts_settings/accounts_settings.json +msgid "Enable Subscription tracking in invoice" +msgstr "" + #. Label of the enable_utm (Check) field in DocType 'Selling Settings' #: erpnext/selling/doctype/selling_settings/selling_settings.json msgid "Enable UTM" @@ -17933,8 +18022,8 @@ msgstr "" #. Label of the end_time (Time) field in DocType 'Stock Reposting Settings' #. Label of the end_time (Time) field in DocType 'Service Day' #. Label of the end_time (Datetime) field in DocType 'Call Log' -#: erpnext/manufacturing/doctype/job_card/job_card.js:383 -#: erpnext/manufacturing/doctype/job_card/job_card.js:453 +#: erpnext/manufacturing/doctype/job_card/job_card.js:381 +#: erpnext/manufacturing/doctype/job_card/job_card.js:451 #: erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json #: erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json #: erpnext/support/doctype/service_day/service_day.json @@ -18019,8 +18108,8 @@ msgstr "" msgid "Enter Serial Nos" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:410 -#: erpnext/manufacturing/doctype/job_card/job_card.js:479 +#: erpnext/manufacturing/doctype/job_card/job_card.js:408 +#: erpnext/manufacturing/doctype/job_card/job_card.js:477 #: erpnext/manufacturing/doctype/workstation/workstation.js:312 msgid "Enter Value" msgstr "" @@ -18096,11 +18185,11 @@ msgstr "" msgid "Enter the opening stock units." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:973 +#: erpnext/manufacturing/doctype/bom/bom.js:990 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:1157 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1158 msgid "Enter the quantity to manufacture. Raw material Items will be fetched only when this is set." msgstr "" @@ -18161,7 +18250,7 @@ msgstr "" msgid "Error Description" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:290 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:295 msgid "Error Occurred" msgstr "" @@ -18262,7 +18351,7 @@ msgstr "" msgid "Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions, then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item, leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings." msgstr "" -#: erpnext/stock/stock_ledger.py:2328 +#: erpnext/stock/stock_ledger.py:2335 msgid "Example: Serial No {0} reserved in {1}." msgstr "" @@ -18276,7 +18365,7 @@ msgstr "" msgid "Excess Materials Consumed" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:1114 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1132 msgid "Excess Transfer" msgstr "" @@ -18712,7 +18801,7 @@ msgstr "" msgid "Expenses Included In Valuation" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:306 +#: erpnext/stock/doctype/pick_list/pick_list.py:307 #: erpnext/stock/doctype/stock_entry/stock_entry.js:409 msgid "Expired Batches" msgstr "" @@ -18786,7 +18875,7 @@ msgstr "" msgid "Extra Consumed Qty" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:254 +#: erpnext/manufacturing/doctype/job_card/job_card.py:262 msgid "Extra Job Card Quantity" msgstr "" @@ -18868,20 +18957,18 @@ msgstr "" msgid "Failed to Authenticate the API key." msgstr "" -#: erpnext/setup/demo.py:54 +#: erpnext/setup/setup_wizard/setup_wizard.py:37 +#: erpnext/setup/setup_wizard/setup_wizard.py:38 +msgid "Failed to create demo data" +msgstr "" + +#: erpnext/setup/demo.py:77 msgid "Failed to erase demo data, please delete the demo company manually." msgstr "" -#: erpnext/setup/setup_wizard/setup_wizard.py:25 -#: erpnext/setup/setup_wizard/setup_wizard.py:26 -msgid "Failed to install presets" -msgstr "" - +#: erpnext/setup/setup_wizard/setup_wizard.py:16 #: erpnext/setup/setup_wizard/setup_wizard.py:17 -#: erpnext/setup/setup_wizard/setup_wizard.py:18 -#: erpnext/setup/setup_wizard/setup_wizard.py:42 -#: erpnext/setup/setup_wizard/setup_wizard.py:43 -msgid "Failed to login" +msgid "Failed to install presets" msgstr "" #: erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py:164 @@ -18896,12 +18983,16 @@ msgstr "" msgid "Failed to send email for campaign {0} to {1}" msgstr "" -#: erpnext/setup/setup_wizard/setup_wizard.py:30 -#: erpnext/setup/setup_wizard/setup_wizard.py:31 +#: erpnext/setup/setup_wizard/setup_wizard.py:26 +msgid "Failed to set defaults" +msgstr "" + +#: erpnext/setup/setup_wizard/setup_wizard.py:21 +#: erpnext/setup/setup_wizard/setup_wizard.py:22 msgid "Failed to setup company" msgstr "" -#: erpnext/setup/setup_wizard/setup_wizard.py:37 +#: erpnext/setup/setup_wizard/setup_wizard.py:28 msgid "Failed to setup defaults" msgstr "" @@ -18978,6 +19069,12 @@ msgstr "" msgid "Fetch Overdue Payments" msgstr "" +#. Label of the fetch_payment_schedule_in_payment_request (Check) field in +#. DocType 'Accounts Settings' +#: erpnext/accounts/doctype/accounts_settings/accounts_settings.json +msgid "Fetch Payment Schedule In Payment Request" +msgstr "" + #: erpnext/accounts/doctype/subscription/subscription.js:36 msgid "Fetch Subscription Updates" msgstr "" @@ -19026,7 +19123,7 @@ msgid "Fetching Sales Orders..." msgstr "" #: erpnext/accounts/doctype/dunning/dunning.js:135 -#: erpnext/public/js/controllers/transaction.js:1593 +#: erpnext/public/js/controllers/transaction.js:1597 msgid "Fetching exchange rates ..." msgstr "" @@ -19266,9 +19363,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:877 -#: erpnext/manufacturing/doctype/work_order/work_order.js:892 -#: erpnext/manufacturing/doctype/work_order/work_order.js:901 +#: erpnext/manufacturing/doctype/work_order/work_order.js:878 +#: erpnext/manufacturing/doctype/work_order/work_order.js:893 +#: erpnext/manufacturing/doctype/work_order/work_order.js:902 msgid "Finish" msgstr "" @@ -19299,20 +19396,20 @@ msgstr "" #. Service Item' #. Label of the fg_item (Link) field in DocType 'Subcontracting Order Service #. Item' -#: erpnext/public/js/utils.js:901 +#: erpnext/public/js/utils.js:905 #: 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" msgstr "" #. Label of the fg_item_code (Link) field in DocType 'Subcontracting Inward -#. Order Scrap Item' +#. Order Secondary Item' #: erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py:37 -#: erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.json +#: erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json msgid "Finished Good Item Code" msgstr "" -#: erpnext/public/js/utils.js:919 +#: erpnext/public/js/utils.js:923 msgid "Finished Good Item Qty" msgstr "" @@ -19419,7 +19516,7 @@ msgstr "" msgid "Finished Goods based Operating Cost" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1670 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1675 msgid "Finished Item {0} does not match with Work Order {1}" msgstr "" @@ -19589,7 +19686,7 @@ msgstr "" msgid "Fixed Asset Turnover Ratio" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:742 +#: erpnext/manufacturing/doctype/bom/bom.py:780 msgid "Fixed Asset item {0} cannot be used in BOMs." msgstr "" @@ -19667,7 +19764,7 @@ msgstr "" msgid "Following Material Requests have been raised automatically based on Item's re-order level" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:821 +#: erpnext/selling/doctype/customer/customer.py:833 msgid "Following fields are mandatory to create address:" msgstr "" @@ -19734,7 +19831,7 @@ msgid "For Job Card" msgstr "" #. Label of the for_operation (Link) field in DocType 'Job Card' -#: erpnext/manufacturing/doctype/job_card/job_card.js:523 +#: erpnext/manufacturing/doctype/job_card/job_card.js:521 #: erpnext/manufacturing/doctype/job_card/job_card.json msgid "For Operation" msgstr "" @@ -19823,7 +19920,7 @@ msgstr "" msgid "For individual supplier" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:370 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:374 msgid "For item {0}, only {1} asset have been created or linked to {2}. Please create or link {3} more asset with the respective document." msgstr "" @@ -19831,11 +19928,11 @@ msgstr "" msgid "For item {0}, rate must be a positive number. To Allow negative rates, enable {1} in {2}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:346 +#: erpnext/manufacturing/doctype/bom/bom.py:367 msgid "For operation {0} at row {1}, please add raw materials or set a BOM against it." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2599 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2600 msgid "For operation {0}: Quantity ({1}) can not be greater than pending quantity({2})" msgstr "" @@ -19852,7 +19949,7 @@ msgstr "" msgid "For projected and forecast quantities, the system will consider all child warehouses under the selected parent warehouse." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1702 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1707 msgid "For quantity {0} should not be greater than allowed quantity {1}" msgstr "" @@ -19889,7 +19986,7 @@ msgstr "" msgid "For the item {0}, the consumed quantity should be {1} according to the BOM {2}." msgstr "" -#: erpnext/public/js/controllers/transaction.js:1403 +#: erpnext/public/js/controllers/transaction.js:1407 msgctxt "Clear payment terms template and/or payment schedule when due date is changed" msgid "For the new {0} to take effect, would you like to clear the current {1}?" msgstr "" @@ -20894,10 +20991,10 @@ msgstr "" msgid "Get Sales Orders" msgstr "" -#. Label of the get_scrap_items (Button) field in DocType 'Subcontracting +#. Label of the get_secondary_items (Button) field in DocType 'Subcontracting #. Receipt' #: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json -msgid "Get Scrap Items" +msgid "Get Secondary Items" msgstr "" #. Label of the get_started_sections (Code) field in DocType 'Support Settings' @@ -20941,8 +21038,8 @@ msgstr "" msgid "Get stops from" msgstr "" -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js:195 -msgid "Getting Scrap Items" +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js:196 +msgid "Getting Secondary Items" msgstr "" #. Option for the 'Coupon Type' (Select) field in DocType 'Coupon Code' @@ -20996,7 +21093,7 @@ msgstr "" msgid "Goods Transferred" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2220 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2225 msgid "Goods are already received against the outward entry {0}" msgstr "" @@ -21293,7 +21390,7 @@ msgstr "" msgid "Group Same Items" msgstr "" -#: erpnext/stock/doctype/stock_settings/stock_settings.py:158 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:156 msgid "Group Warehouses cannot be used in transactions. Please change the value of {0}" msgstr "" @@ -21598,7 +21695,7 @@ msgstr "" msgid "Here are the error logs for the aforementioned failed depreciation entries: {0}" msgstr "" -#: erpnext/stock/stock_ledger.py:2050 +#: erpnext/stock/stock_ledger.py:2057 msgid "Here are the options to proceed:" msgstr "" @@ -22073,10 +22170,10 @@ msgstr "" msgid "If enabled, system will set incoming rate as zero for stand-alone credit notes with expired batch item." msgstr "" -#. Description of the 'Deliver Scrap Items' (Check) field in DocType 'Selling -#. Settings' +#. Description of the 'Deliver Secondary Items' (Check) field in DocType +#. 'Selling Settings' #: erpnext/selling/doctype/selling_settings/selling_settings.json -msgid "If enabled, the Scrap Item generated against a Finished Good will also be added in the Stock Entry when delivering that Finished Good." +msgid "If enabled, the Secondary Items generated against a Finished Good will also be added in the Stock Entry when delivering that Finished Good." msgstr "" #. Description of the 'Disable Rounded Total' (Check) field in DocType 'POS @@ -22183,14 +22280,16 @@ msgstr "" msgid "If no taxes are set, and Taxes and Charges Template is selected, the system will automatically apply the taxes from the chosen template." msgstr "" -#: erpnext/stock/stock_ledger.py:2060 +#: erpnext/stock/stock_ledger.py:2067 msgid "If not, you can Cancel / Submit this entry" msgstr "" -#. Description of the 'Create Missing Party' (Check) field in DocType 'Opening -#. Invoice Creation Tool' -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json -msgid "If party does not exist, create it using the Party Name field." +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js:197 +msgid "If party does not exist, create it using the Customer Name field." +msgstr "" + +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js:198 +msgid "If party does not exist, create it using the Supplier Name field." msgstr "" #. Description of the 'Free Item Rate' (Currency) field in DocType 'Pricing @@ -22209,7 +22308,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:1190 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1191 msgid "If the BOM results in Scrap material, the Scrap Warehouse needs to be selected." msgstr "" @@ -22218,7 +22317,7 @@ msgstr "" msgid "If the account is frozen, entries are allowed to restricted users." msgstr "" -#: erpnext/stock/stock_ledger.py:2053 +#: erpnext/stock/stock_ledger.py:2060 msgid "If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table." msgstr "" @@ -22228,7 +22327,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:1209 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1210 msgid "If the selected BOM has Operations mentioned in it, the system will fetch all Operations from BOM, these values can be changed." msgstr "" @@ -22319,7 +22418,7 @@ msgstr "" msgid "If you still want to proceed, please disable 'Skip Available Sub Assembly Items' checkbox." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1837 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1839 msgid "If you still want to proceed, please enable {0}." msgstr "" @@ -22391,7 +22490,7 @@ msgstr "" #. Label of the ignore_exchange_rate_revaluation_journals (Check) field in #. DocType 'Process Statement Of Accounts' #: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json -#: erpnext/accounts/report/general_ledger/general_ledger.js:217 +#: erpnext/accounts/report/general_ledger/general_ledger.js:218 msgid "Ignore Exchange Rate Revaluation and Gain / Loss Journals" msgstr "" @@ -22399,7 +22498,7 @@ msgstr "" msgid "Ignore Existing Ordered Qty" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1829 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1831 msgid "Ignore Existing Projected Quantity" msgstr "" @@ -22443,7 +22542,7 @@ msgstr "" #. Of Accounts' #: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json #: erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js:120 -#: erpnext/accounts/report/general_ledger/general_ledger.js:222 +#: erpnext/accounts/report/general_ledger/general_ledger.js:223 msgid "Ignore System Generated Credit / Debit Notes" msgstr "" @@ -22819,7 +22918,7 @@ msgstr "" #: erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js:131 #: erpnext/accounts/report/consolidated_trial_balance/consolidated_trial_balance.js:85 #: erpnext/accounts/report/custom_financial_statement/custom_financial_statement.js:29 -#: erpnext/accounts/report/general_ledger/general_ledger.js:186 +#: erpnext/accounts/report/general_ledger/general_ledger.js:187 #: erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js:46 #: erpnext/accounts/report/trial_balance/trial_balance.js:105 msgid "Include Default FB Entries" @@ -23050,7 +23149,7 @@ msgstr "" msgid "Incompatible Setting Detected" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:191 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:195 msgid "Incorrect Account" msgstr "" @@ -23067,7 +23166,7 @@ msgstr "" msgid "Incorrect Check in (group) Warehouse for Reorder" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:142 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:143 msgid "Incorrect Company" msgstr "" @@ -23080,7 +23179,7 @@ msgstr "" msgid "Incorrect Date" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:157 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:158 msgid "Incorrect Invoice" msgstr "" @@ -23088,7 +23187,7 @@ msgstr "" msgid "Incorrect Payment Type" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:113 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:114 msgid "Incorrect Reference Document (Purchase Receipt Item)" msgstr "" @@ -23115,9 +23214,9 @@ msgstr "" msgid "Incorrect Type of Transaction" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:186 -#: erpnext/stock/doctype/pick_list/pick_list.py:210 -#: erpnext/stock/doctype/stock_settings/stock_settings.py:161 +#: erpnext/stock/doctype/pick_list/pick_list.py:187 +#: erpnext/stock/doctype/pick_list/pick_list.py:211 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:159 msgid "Incorrect Warehouse" msgstr "" @@ -23274,7 +23373,7 @@ msgid "Inspected By" msgstr "" #: erpnext/controllers/stock_controller.py:1494 -#: erpnext/manufacturing/doctype/job_card/job_card.py:814 +#: erpnext/manufacturing/doctype/job_card/job_card.py:832 msgid "Inspection Rejected" msgstr "" @@ -23298,7 +23397,7 @@ msgid "Inspection Required before Purchase" msgstr "" #: erpnext/controllers/stock_controller.py:1479 -#: erpnext/manufacturing/doctype/job_card/job_card.py:795 +#: erpnext/manufacturing/doctype/job_card/job_card.py:813 msgid "Inspection Submission" msgstr "" @@ -23353,7 +23452,7 @@ msgstr "" msgid "Installed Qty" msgstr "" -#: erpnext/setup/setup_wizard/setup_wizard.py:24 +#: erpnext/setup/setup_wizard/setup_wizard.py:15 msgid "Installing presets" msgstr "" @@ -23373,16 +23472,16 @@ msgid "Insufficient Permissions" msgstr "" #: erpnext/accounts/doctype/pos_invoice/pos_invoice.py:462 -#: erpnext/stock/doctype/pick_list/pick_list.py:144 -#: erpnext/stock/doctype/pick_list/pick_list.py:162 -#: erpnext/stock/doctype/pick_list/pick_list.py:1055 +#: erpnext/stock/doctype/pick_list/pick_list.py:145 +#: erpnext/stock/doctype/pick_list/pick_list.py:163 +#: erpnext/stock/doctype/pick_list/pick_list.py:1088 #: erpnext/stock/doctype/stock_entry/stock_entry.py:956 -#: erpnext/stock/serial_batch_bundle.py:1205 erpnext/stock/stock_ledger.py:1741 -#: erpnext/stock/stock_ledger.py:2219 +#: erpnext/stock/serial_batch_bundle.py:1205 erpnext/stock/stock_ledger.py:1748 +#: erpnext/stock/stock_ledger.py:2226 msgid "Insufficient Stock" msgstr "" -#: erpnext/stock/stock_ledger.py:2234 +#: erpnext/stock/stock_ledger.py:2241 msgid "Insufficient Stock for Batch" msgstr "" @@ -23497,12 +23596,6 @@ msgstr "" msgid "Inter Transfer Reference" msgstr "" -#. Label of the inter_warehouse_transfer_settings_section (Section Break) field -#. in DocType 'Stock Settings' -#: erpnext/stock/doctype/stock_settings/stock_settings.json -msgid "Inter Warehouse Transfer Settings" -msgstr "" - #. Label of the interest (Currency) field in DocType 'Overdue Payment' #: erpnext/accounts/doctype/overdue_payment/overdue_payment.json msgid "Interest" @@ -23543,7 +23636,7 @@ msgstr "" msgid "Internal Customer Accounting" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:254 +#: erpnext/selling/doctype/customer/customer.py:255 msgid "Internal Customer for company {0} already exists" msgstr "" @@ -23653,7 +23746,7 @@ msgstr "" msgid "Invalid Barcode. There is no Item attached to this barcode." msgstr "" -#: erpnext/public/js/controllers/transaction.js:3212 +#: erpnext/public/js/controllers/transaction.js:3216 msgid "Invalid Blanket Order for the selected Customer and Item" msgstr "" @@ -23679,6 +23772,10 @@ msgstr "" msgid "Invalid Cost Center" msgstr "" +#: erpnext/selling/doctype/customer/customer.py:368 +msgid "Invalid Customer Group" +msgstr "" + #: erpnext/selling/doctype/sales_order/sales_order.py:417 msgid "Invalid Delivery Date" msgstr "" @@ -23687,11 +23784,11 @@ msgstr "" msgid "Invalid Discount" msgstr "" -#: erpnext/controllers/taxes_and_totals.py:803 +#: erpnext/controllers/taxes_and_totals.py:840 msgid "Invalid Discount Amount" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:129 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:130 msgid "Invalid Document" msgstr "" @@ -23761,7 +23858,7 @@ msgstr "" msgid "Invalid Priority" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1232 +#: erpnext/manufacturing/doctype/bom/bom.py:1275 msgid "Invalid Process Loss Configuration" msgstr "" @@ -23778,7 +23875,7 @@ msgstr "" msgid "Invalid Quantity" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:475 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:479 msgid "Invalid Query" msgstr "" @@ -23799,7 +23896,7 @@ msgstr "" msgid "Invalid Selling Price" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1745 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1750 msgid "Invalid Serial and Batch Bundle" msgstr "" @@ -23853,7 +23950,7 @@ msgstr "" msgid "Invalid result key. Response:" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:475 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:479 msgid "Invalid search query" msgstr "" @@ -24402,6 +24499,20 @@ msgstr "" msgid "Is Internal Supplier" msgstr "" +#. Label of the is_legacy (Check) field in DocType 'BOM Secondary Item' +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json +msgid "Is Legacy" +msgstr "" + +#. Label of the is_legacy_scrap_item (Check) field in DocType 'Stock Entry +#. Detail' +#. Label of the is_legacy_scrap_item (Check) field in DocType 'Subcontracting +#. Receipt Item' +#: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +msgid "Is Legacy Scrap Item" +msgstr "" + #. Label of the is_mandatory (Check) field in DocType 'Applicable On Account' #: erpnext/accounts/doctype/applicable_on_account/applicable_on_account.json msgid "Is Mandatory" @@ -24552,14 +24663,6 @@ msgstr "" msgid "Is Sales Order Required for Sales Invoice & Delivery Note Creation?" msgstr "" -#. Label of the is_scrap_item (Check) field in DocType 'Stock Entry Detail' -#. Label of the is_scrap_item (Check) field in DocType 'Subcontracting Receipt -#. Item' -#: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json -#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json -msgid "Is Scrap Item" -msgstr "" - #. Label of the is_short_year (Check) field in DocType 'Fiscal Year' #: erpnext/accounts/doctype/fiscal_year/fiscal_year.json msgid "Is Short/Long Year" @@ -24772,11 +24875,11 @@ msgstr "" msgid "It can take upto few hours for accurate stock values to be visible after merging items." msgstr "" -#: erpnext/public/js/controllers/transaction.js:2613 +#: erpnext/public/js/controllers/transaction.js:2617 msgid "It is needed to fetch Item Details." msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:211 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:215 msgid "It's not possible to distribute charges equally when total amount is zero, please set 'Distribute Charges Based On' as 'Quantity'" msgstr "" @@ -24831,9 +24934,9 @@ msgstr "" #: erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js:33 #: erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py:204 #: erpnext/buying/workspace/buying/buying.json -#: erpnext/controllers/taxes_and_totals.py:1212 +#: erpnext/controllers/taxes_and_totals.py:1249 #: erpnext/manufacturing/doctype/blanket_order/blanket_order.json -#: erpnext/manufacturing/doctype/bom/bom.js:1066 +#: erpnext/manufacturing/doctype/bom/bom.js:1083 #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/plant_floor/plant_floor.js:109 #: erpnext/manufacturing/doctype/workstation/workstation_job_card.html:25 @@ -25018,7 +25121,7 @@ msgstr "" #. Label of the item_code (Link) field in DocType 'BOM Creator Item' #. Label of the item_code (Link) field in DocType 'BOM Explosion Item' #. Label of the item_code (Link) field in DocType 'BOM Item' -#. Label of the item_code (Link) field in DocType 'BOM Scrap Item' +#. Label of the item_code (Link) field in DocType 'BOM Secondary Item' #. Label of the item_code (Link) field in DocType 'BOM Website Item' #. Label of the item_code (Link) field in DocType 'Job Card Item' #. Label of the item_code (Link) field in DocType 'Master Production Schedule @@ -25062,7 +25165,7 @@ msgstr "" #. Label of the main_item_code (Link) field in DocType 'Subcontracting Inward #. Order Received Item' #. Label of the item_code (Link) field in DocType 'Subcontracting Inward Order -#. Scrap Item' +#. Secondary Item' #. Label of the item_code (Link) field in DocType 'Subcontracting Inward Order #. Service Item' #. Label of the item_code (Link) field in DocType 'Subcontracting Order Item' @@ -25109,7 +25212,7 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json #: erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json #: erpnext/manufacturing/doctype/bom_item/bom_item.json -#: erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json #: erpnext/manufacturing/doctype/bom_website_item/bom_website_item.json #: erpnext/manufacturing/doctype/job_card_item/job_card_item.json #: erpnext/manufacturing/doctype/master_production_schedule_item/master_production_schedule_item.json @@ -25134,10 +25237,10 @@ msgstr "" #: erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py:86 #: erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py:119 #: erpnext/projects/doctype/timesheet/timesheet.js:214 -#: erpnext/public/js/controllers/transaction.js:2907 +#: erpnext/public/js/controllers/transaction.js:2911 #: erpnext/public/js/stock_reservation.js:112 -#: erpnext/public/js/stock_reservation.js:318 erpnext/public/js/utils.js:559 -#: erpnext/public/js/utils.js:716 +#: erpnext/public/js/stock_reservation.js:318 erpnext/public/js/utils.js:563 +#: erpnext/public/js/utils.js:720 #: 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 @@ -25195,13 +25298,13 @@ msgstr "" #: erpnext/stock/report/serial_no_and_batch_traceability/serial_no_and_batch_traceability.js:8 #: erpnext/stock/report/serial_no_and_batch_traceability/serial_no_and_batch_traceability.py:433 #: erpnext/stock/report/serial_no_ledger/serial_no_ledger.js:7 -#: erpnext/stock/report/stock_ageing/stock_ageing.py:132 +#: erpnext/stock/report/stock_ageing/stock_ageing.py:133 #: erpnext/stock/report/stock_projected_qty/stock_projected_qty.py:104 #: erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.py:25 #: erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py:26 #: erpnext/subcontracting/doctype/subcontracting_inward_order_item/subcontracting_inward_order_item.json #: erpnext/subcontracting/doctype/subcontracting_inward_order_received_item/subcontracting_inward_order_received_item.json -#: erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.json +#: erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json #: erpnext/subcontracting/doctype/subcontracting_inward_order_service_item/subcontracting_inward_order_service_item.json #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:253 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:352 @@ -25394,7 +25497,7 @@ msgstr "" #: erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py:55 #: erpnext/stock/report/product_bundle_balance/product_bundle_balance.js:37 #: erpnext/stock/report/product_bundle_balance/product_bundle_balance.py:100 -#: erpnext/stock/report/stock_ageing/stock_ageing.py:141 +#: erpnext/stock/report/stock_ageing/stock_ageing.py:142 #: erpnext/stock/report/stock_analytics/stock_analytics.js:8 #: erpnext/stock/report/stock_analytics/stock_analytics.py:52 #: erpnext/stock/report/stock_balance/stock_balance.js:32 @@ -25515,7 +25618,7 @@ msgstr "" #. Label of the item_name (Data) field in DocType 'BOM Creator Item' #. Label of the item_name (Data) field in DocType 'BOM Explosion Item' #. Label of the item_name (Data) field in DocType 'BOM Item' -#. Label of the item_name (Data) field in DocType 'BOM Scrap Item' +#. Label of the item_name (Data) field in DocType 'BOM Secondary Item' #. Label of the item_name (Data) field in DocType 'BOM Website Item' #. Label of the item_name (Read Only) field in DocType 'Job Card' #. Label of the item_name (Data) field in DocType 'Job Card Item' @@ -25594,7 +25697,7 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json #: erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json #: erpnext/manufacturing/doctype/bom_item/bom_item.json -#: erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json #: erpnext/manufacturing/doctype/bom_website_item/bom_website_item.json #: erpnext/manufacturing/doctype/job_card/job_card.json #: erpnext/manufacturing/doctype/job_card_item/job_card_item.json @@ -25615,8 +25718,8 @@ msgstr "" #: erpnext/manufacturing/report/production_planning_report/production_planning_report.py:371 #: 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:2913 -#: erpnext/public/js/utils.js:811 +#: erpnext/public/js/controllers/transaction.js:2917 +#: erpnext/public/js/utils.js:815 #: 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 @@ -25654,7 +25757,7 @@ msgstr "" #: erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py:54 #: erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py:131 #: erpnext/stock/report/serial_no_and_batch_traceability/serial_no_and_batch_traceability.py:440 -#: erpnext/stock/report/stock_ageing/stock_ageing.py:138 +#: erpnext/stock/report/stock_ageing/stock_ageing.py:139 #: erpnext/stock/report/stock_analytics/stock_analytics.py:45 #: erpnext/stock/report/stock_balance/stock_balance.py:479 #: erpnext/stock/report/stock_ledger/stock_ledger.py:277 @@ -25770,7 +25873,7 @@ msgstr "" msgid "Item Row" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:167 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:168 msgid "Item Row {0}: {1} {2} does not exist in above '{1}' table" msgstr "" @@ -25993,7 +26096,7 @@ msgstr "" msgid "Item Wise Tax Details" msgstr "" -#: erpnext/controllers/taxes_and_totals.py:537 +#: erpnext/controllers/taxes_and_totals.py:559 msgid "Item Wise Tax Details do not match with Taxes and Charges at the following rows:" msgstr "" @@ -26013,7 +26116,7 @@ msgstr "" msgid "Item and Warranty Details" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3267 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3299 msgid "Item for row {0} does not match Material Request" msgstr "" @@ -26029,7 +26132,7 @@ msgstr "" msgid "Item is removed since no serial / batch no selected." msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:163 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:164 msgid "Item must be added using 'Get Items from Purchase Receipts' button" msgstr "" @@ -26047,7 +26150,7 @@ msgstr "" msgid "Item qty can not be updated as raw materials are already processed." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1148 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1155 msgid "Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {0}" msgstr "" @@ -26090,7 +26193,7 @@ msgstr "" msgid "Item {0} does not exist" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:670 +#: erpnext/manufacturing/doctype/bom/bom.py:708 msgid "Item {0} does not exist in the system or has expired" msgstr "" @@ -26146,7 +26249,7 @@ msgstr "" msgid "Item {0} is not a subcontracted item" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2132 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2137 msgid "Item {0} is not active or end of life has been reached" msgstr "" @@ -26166,7 +26269,7 @@ msgstr "" msgid "Item {0} must be a non-stock item" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1481 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1488 msgid "Item {0} not found in 'Raw Materials Supplied' table in {1} {2}" msgstr "" @@ -26232,7 +26335,7 @@ msgstr "" msgid "Item/Item Code required to get Item Tax Template." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:412 +#: erpnext/manufacturing/doctype/bom/bom.py:451 msgid "Item: {0} does not exist in the system" msgstr "" @@ -26292,7 +26395,7 @@ msgstr "" msgid "Items not found." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1144 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1151 msgid "Items rate has been updated to zero as Allow Zero Valuation Rate is checked for the following items: {0}" msgstr "" @@ -26367,9 +26470,9 @@ msgstr "" #: erpnext/buying/doctype/purchase_order_item/purchase_order_item.json #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/job_card/job_card.json -#: erpnext/manufacturing/doctype/job_card/job_card.py:979 +#: erpnext/manufacturing/doctype/job_card/job_card.py:997 #: erpnext/manufacturing/doctype/operation/operation.json -#: erpnext/manufacturing/doctype/work_order/work_order.js:396 +#: erpnext/manufacturing/doctype/work_order/work_order.js:397 #: erpnext/manufacturing/doctype/work_order/work_order.json #: erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js:29 #: erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py:86 @@ -26407,8 +26510,8 @@ msgid "Job Card Scheduled Time" msgstr "" #. Name of a DocType -#: erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json -msgid "Job Card Scrap Item" +#: erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json +msgid "Job Card Secondary Item" msgstr "" #. Name of a report @@ -26431,7 +26534,7 @@ msgstr "" msgid "Job Card and Capacity Planning" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:1455 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1473 msgid "Job Card {0} has been completed" msgstr "" @@ -26507,7 +26610,7 @@ msgstr "" msgid "Job Worker Warehouse" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2652 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2654 msgid "Job card {0} created" msgstr "" @@ -26723,7 +26826,7 @@ msgstr "" msgid "Kilowatt-Hour" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:981 +#: erpnext/manufacturing/doctype/job_card/job_card.py:999 msgid "Kindly cancel the Manufacturing Entries first against the work order {0}." msgstr "" @@ -26925,7 +27028,7 @@ msgstr "" msgid "Last transacted" msgstr "" -#: erpnext/stock/report/stock_ageing/stock_ageing.py:177 +#: erpnext/stock/report/stock_ageing/stock_ageing.py:178 msgid "Latest" msgstr "" @@ -27598,7 +27701,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:180 +#: erpnext/public/js/utils.js:184 msgid "Loyalty Points: {0}" msgstr "" @@ -27939,9 +28042,9 @@ 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:544 -#: erpnext/manufacturing/doctype/work_order/work_order.js:832 -#: erpnext/manufacturing/doctype/work_order/work_order.js:866 +#: erpnext/manufacturing/doctype/job_card/job_card.js:542 +#: erpnext/manufacturing/doctype/work_order/work_order.js:833 +#: erpnext/manufacturing/doctype/work_order/work_order.js:867 #: erpnext/setup/doctype/vehicle/vehicle.json msgid "Make" msgstr "" @@ -28004,7 +28107,7 @@ msgstr "" msgid "Make Stock Entry" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:418 +#: erpnext/manufacturing/doctype/job_card/job_card.js:416 msgid "Make Subcontracting PO" msgstr "" @@ -28167,8 +28270,8 @@ msgstr "" #: erpnext/stock/doctype/material_request_item/material_request_item.json #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json #: erpnext/stock/doctype/stock_entry/stock_entry.json -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1225 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1241 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1232 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1248 #: erpnext/stock/doctype/stock_entry_type/stock_entry_type.json #: erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json #: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -28317,7 +28420,7 @@ msgstr "" msgid "Manufacturing Manager" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2385 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2390 msgid "Manufacturing Quantity is mandatory" msgstr "" @@ -28394,7 +28497,7 @@ msgstr "" msgid "Mapping Subcontracting Order ..." msgstr "" -#: erpnext/public/js/utils.js:1046 +#: erpnext/public/js/utils.js:1050 msgid "Mapping {0} ..." msgstr "" @@ -28540,7 +28643,7 @@ msgstr "" msgid "Material" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:857 +#: erpnext/manufacturing/doctype/work_order/work_order.js:858 msgid "Material Consumption" msgstr "" @@ -28548,7 +28651,7 @@ msgstr "" #. Option for the 'Purpose' (Select) field in DocType 'Stock Entry Type' #: erpnext/setup/setup_wizard/operations/install_fixtures.py:114 #: erpnext/stock/doctype/stock_entry/stock_entry.json -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1226 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1233 #: erpnext/stock/doctype/stock_entry_type/stock_entry_type.json msgid "Material Consumption for Manufacture" msgstr "" @@ -28743,7 +28846,7 @@ msgstr "" msgid "Material Request used to make this Stock Entry" msgstr "" -#: erpnext/controllers/subcontracting_controller.py:1343 +#: erpnext/controllers/subcontracting_controller.py:1349 msgid "Material Request {0} is cancelled or stopped" msgstr "" @@ -28862,12 +28965,12 @@ msgstr "" msgid "Materials To Be Transferred" msgstr "" -#: erpnext/controllers/subcontracting_controller.py:1576 +#: erpnext/controllers/subcontracting_controller.py:1582 msgid "Materials are already received against the {0} {1}" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:181 -#: erpnext/manufacturing/doctype/job_card/job_card.py:835 +#: erpnext/manufacturing/doctype/job_card/job_card.py:183 +#: erpnext/manufacturing/doctype/job_card/job_card.py:853 msgid "Materials needs to be transferred to the work in progress warehouse for the job card {0}" msgstr "" @@ -28936,7 +29039,7 @@ msgstr "" msgid "Max discount allowed for item: {0} is {1}%" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1009 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1010 #: erpnext/stock/doctype/pick_list/pick_list.js:200 msgid "Max: {0}" msgstr "" @@ -28963,11 +29066,11 @@ msgstr "" msgid "Maximum Producible Items" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3870 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3902 msgid "Maximum Samples - {0} can be retained for Batch {1} and Item {2}." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3861 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3893 msgid "Maximum Samples - {0} have already been retained for Batch {1} and Item {2} in Batch {3}." msgstr "" @@ -29022,7 +29125,7 @@ msgstr "" msgid "Megawatt" msgstr "" -#: erpnext/stock/stock_ledger.py:2066 +#: erpnext/stock/stock_ledger.py:2073 msgid "Mention Valuation Rate in the Item master." msgstr "" @@ -29067,7 +29170,7 @@ msgstr "" msgid "Merge Similar Account Heads" msgstr "" -#: erpnext/public/js/utils.js:1078 +#: erpnext/public/js/utils.js:1082 msgid "Merge taxes from multiple documents" msgstr "" @@ -29385,7 +29488,7 @@ msgstr "" msgid "Miscellaneous Expenses" msgstr "" -#: erpnext/controllers/buying_controller.py:702 +#: erpnext/controllers/buying_controller.py:706 msgid "Mismatch" msgstr "" @@ -29423,7 +29526,7 @@ msgstr "" msgid "Missing Finance Book" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1680 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1685 msgid "Missing Finished Good" msgstr "" @@ -29447,7 +29550,7 @@ msgstr "" msgid "Missing Serial No Bundle" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:170 +#: erpnext/stock/doctype/pick_list/pick_list.py:171 msgid "Missing Warehouse" msgstr "" @@ -29459,7 +29562,7 @@ msgstr "" msgid "Missing required filter: {0}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1185 +#: erpnext/manufacturing/doctype/bom/bom.py:1218 #: erpnext/manufacturing/doctype/work_order/work_order.py:1476 msgid "Missing value" msgstr "" @@ -29695,7 +29798,7 @@ msgstr "" msgid "Multi-level BOM Creator" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:427 +#: erpnext/selling/doctype/customer/customer.py:439 msgid "Multiple Loyalty Programs found for Customer {}. Please select manually." msgstr "" @@ -29729,7 +29832,7 @@ msgstr "" msgid "Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1687 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1692 msgid "Multiple items cannot be marked as finished item" msgstr "" @@ -29866,7 +29969,7 @@ msgstr "" msgid "Negative Quantity is not allowed" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1537 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1575 #: erpnext/stock/serial_batch_bundle.py:1528 msgid "Negative Stock Error" msgstr "" @@ -30337,7 +30440,7 @@ msgstr "" msgid "New Task" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:206 +#: erpnext/manufacturing/doctype/bom/bom.js:244 msgid "New Version" msgstr "" @@ -30350,7 +30453,7 @@ msgstr "" msgid "New Workplace" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:392 +#: erpnext/selling/doctype/customer/customer.py:404 msgid "New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}" msgstr "" @@ -30446,7 +30549,7 @@ msgstr "" msgid "No Item with Serial No {0}" msgstr "" -#: erpnext/controllers/subcontracting_controller.py:1494 +#: erpnext/controllers/subcontracting_controller.py:1500 msgid "No Items selected for transfer." msgstr "" @@ -30541,7 +30644,7 @@ msgid "No Work Orders were created" msgstr "" #: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:833 -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:860 +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:897 msgid "No accounting entries for the following warehouses" msgstr "" @@ -30589,7 +30692,7 @@ msgstr "" msgid "No employee was scheduled for call popup" msgstr "" -#: erpnext/controllers/subcontracting_controller.py:1385 +#: erpnext/controllers/subcontracting_controller.py:1391 msgid "No item available for transfer." msgstr "" @@ -30730,7 +30833,7 @@ msgstr "" msgid "No pending Material Requests found to link for the given items." msgstr "" -#: erpnext/public/js/controllers/transaction.js:468 +#: erpnext/public/js/controllers/transaction.js:472 msgid "No pending payment schedules available." msgstr "" @@ -30832,7 +30935,7 @@ msgstr "" msgid "Non Profit" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1593 +#: erpnext/manufacturing/doctype/bom/bom.py:1639 msgid "Non stock items" msgstr "" @@ -30961,7 +31064,7 @@ msgstr "" msgid "Note: Email will not be sent to disabled users" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:754 +#: erpnext/manufacturing/doctype/bom/bom.py:792 msgid "Note: If you want to use the finished good {0} as a raw material, then enable the 'Do Not Explode' checkbox in the Items table against the same raw material." msgstr "" @@ -31343,7 +31446,7 @@ msgstr "" msgid "Once set, this invoice will be on hold till the set date" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:744 +#: erpnext/manufacturing/doctype/work_order/work_order.js:745 msgid "Once the Work Order is Closed. It can't be resumed." msgstr "" @@ -31435,11 +31538,11 @@ msgstr "" msgid "Only one of Deposit or Withdrawal should be non-zero when applying an Excluded Fee." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:324 +#: erpnext/manufacturing/doctype/bom/bom.py:329 msgid "Only one operation can have 'Is Final Finished Good' checked when 'Track Semi Finished Goods' is enabled." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1240 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1247 msgid "Only one {0} entry can be created against the Work Order {1}" msgstr "" @@ -31676,7 +31779,7 @@ msgstr "" msgid "Opening Entry can not be created after Period Closing Voucher is created." msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:299 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:304 msgid "Opening Invoice Creation In Progress" msgstr "" @@ -31695,7 +31798,7 @@ msgstr "" msgid "Opening Invoice Creation Tool Item" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:101 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:106 msgid "Opening Invoice Item" msgstr "" @@ -31793,7 +31896,7 @@ msgstr "" msgid "Operating Cost Per BOM Quantity" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1680 +#: erpnext/manufacturing/doctype/bom/bom.py:1726 msgid "Operating Cost as per Work Order / BOM" msgstr "" @@ -31884,11 +31987,11 @@ msgstr "" msgid "Operation time does not depend on quantity to produce" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:586 +#: erpnext/manufacturing/doctype/job_card/job_card.js:584 msgid "Operation {0} added multiple times in the work order {1}" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:1228 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1246 msgid "Operation {0} does not belong to the work order {1}" msgstr "" @@ -31918,7 +32021,7 @@ msgstr "" msgid "Operations Routing" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1194 +#: erpnext/manufacturing/doctype/bom/bom.py:1227 msgid "Operations cannot be left blank" msgstr "" @@ -32361,7 +32464,7 @@ msgstr "" msgid "Out of Order" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:603 +#: erpnext/stock/doctype/pick_list/pick_list.py:630 msgid "Out of Stock" msgstr "" @@ -33281,7 +33384,7 @@ msgstr "" msgid "Parent Row No" msgstr "" -#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:534 +#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:533 msgid "Parent Row No not found for {0}" msgstr "" @@ -33655,7 +33758,7 @@ msgstr "" #: erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json #: erpnext/accounts/doctype/payment_entry/payment_entry.json #: erpnext/accounts/doctype/payment_request/payment_request.json -#: erpnext/accounts/report/general_ledger/general_ledger.js:110 +#: erpnext/accounts/report/general_ledger/general_ledger.js:111 #: erpnext/accounts/report/general_ledger/general_ledger.py:761 #: erpnext/crm/doctype/contract/contract.json #: erpnext/selling/doctype/party_specific_item/party_specific_item.json @@ -34316,7 +34419,7 @@ msgstr "" msgid "Payment Schedule based Payment Requests cannot be created because a Payment Entry already exists for this document." msgstr "" -#: erpnext/public/js/controllers/transaction.js:478 +#: erpnext/public/js/controllers/transaction.js:482 msgid "Payment Schedules" msgstr "" @@ -34342,7 +34445,7 @@ msgstr "" #: erpnext/accounts/report/accounts_receivable/accounts_receivable.py:1256 #: erpnext/accounts/report/gross_profit/gross_profit.py:449 #: erpnext/accounts/workspace/invoicing/invoicing.json -#: erpnext/public/js/controllers/transaction.js:492 +#: erpnext/public/js/controllers/transaction.js:496 #: erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py:30 #: erpnext/workspace_sidebar/accounts_setup.json msgid "Payment Term" @@ -34964,7 +35067,7 @@ msgstr "" msgid "Pick List" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:266 +#: erpnext/stock/doctype/pick_list/pick_list.py:267 msgid "Pick List Incomplete" msgstr "" @@ -35283,7 +35386,7 @@ msgstr "" msgid "Plants and Machineries" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:600 +#: erpnext/stock/doctype/pick_list/pick_list.py:627 msgid "Please Restock Items and Update the Pick List to continue. To discontinue, cancel the Pick List." msgstr "" @@ -35310,7 +35413,7 @@ msgstr "" msgid "Please Set Priority" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:166 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:171 msgid "Please Set Supplier Group in Buying Settings." msgstr "" @@ -35326,7 +35429,7 @@ msgstr "" msgid "Please add Mode of payments and opening balance details." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:24 +#: erpnext/manufacturing/doctype/bom/bom.js:39 msgid "Please add Operations first." msgstr "" @@ -35338,7 +35441,7 @@ msgstr "" msgid "Please add Root Account for - {0}" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:315 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:320 msgid "Please add a Temporary Opening account in Chart of Accounts" msgstr "" @@ -35396,7 +35499,7 @@ msgstr "" msgid "Please check Process Deferred Accounting {0} and submit manually after resolving errors." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:105 +#: erpnext/manufacturing/doctype/bom/bom.js:120 msgid "Please check either with operations or FG Based Operating Cost." msgstr "" @@ -35429,7 +35532,7 @@ msgstr "" msgid "Please click on 'Generate Schedule' to get schedule" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:622 +#: erpnext/selling/doctype/customer/customer.py:634 msgid "Please contact any of the following users to extend the credit limits for {0}: {1}" msgstr "" @@ -35437,7 +35540,7 @@ msgstr "" msgid "Please contact any of the following users to {} this transaction." msgstr "" -#: erpnext/selling/doctype/customer/customer.py:615 +#: erpnext/selling/doctype/customer/customer.py:627 msgid "Please contact your administrator to extend the credit limits for {0}." msgstr "" @@ -35449,7 +35552,7 @@ msgstr "" msgid "Please create Customer from Lead {0}." msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:154 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:155 msgid "Please create Landed Cost Vouchers against Invoices that have 'Update Stock' enabled." msgstr "" @@ -35489,7 +35592,7 @@ msgstr "" msgid "Please enable Applicable on Purchase Order and Applicable on Booking Actual Expenses" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:317 +#: erpnext/stock/doctype/pick_list/pick_list.py:318 msgid "Please enable Use Old Serial / Batch Fields to make_bundle" msgstr "" @@ -35559,7 +35662,7 @@ msgstr "" msgid "Please enter Item Code to get Batch Number" msgstr "" -#: erpnext/public/js/controllers/transaction.js:3069 +#: erpnext/public/js/controllers/transaction.js:3073 msgid "Please enter Item Code to get batch no" msgstr "" @@ -35583,7 +35686,7 @@ msgstr "" msgid "Please enter Purchase Receipt first" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:118 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:119 msgid "Please enter Receipt Document" msgstr "" @@ -35672,7 +35775,7 @@ msgstr "" msgid "Please enter the phone number first" msgstr "" -#: erpnext/controllers/buying_controller.py:1185 +#: erpnext/controllers/buying_controller.py:1189 msgid "Please enter the {schedule_date}." msgstr "" @@ -35782,7 +35885,7 @@ msgstr "" msgid "Please select Template Type to download template" msgstr "" -#: erpnext/controllers/taxes_and_totals.py:809 +#: erpnext/controllers/taxes_and_totals.py:846 #: erpnext/public/js/controllers/taxes_and_totals.js:796 msgid "Please select Apply Discount On" msgstr "" @@ -35795,7 +35898,7 @@ msgstr "" msgid "Please select BOM for Item in Row {0}" msgstr "" -#: erpnext/controllers/buying_controller.py:636 +#: erpnext/controllers/buying_controller.py:640 msgid "Please select BOM in BOM field for Item {item_code}." msgstr "" @@ -35875,7 +35978,7 @@ msgstr "" msgid "Please select Posting Date first" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1245 +#: erpnext/manufacturing/doctype/bom/bom.py:1291 msgid "Please select Price List" msgstr "" @@ -35899,7 +36002,7 @@ msgstr "" msgid "Please select Stock Asset Account" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1604 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1611 msgid "Please select Subcontracting Order instead of Purchase Order {0}" msgstr "" @@ -35907,20 +36010,20 @@ msgstr "" msgid "Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1500 +#: erpnext/manufacturing/doctype/bom/bom.py:1546 msgid "Please select a BOM" msgstr "" #: erpnext/accounts/party.py:417 -#: erpnext/stock/doctype/pick_list/pick_list.py:1656 +#: erpnext/stock/doctype/pick_list/pick_list.py:1689 msgid "Please select a Company" msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.js:268 -#: erpnext/manufacturing/doctype/bom/bom.js:688 -#: erpnext/manufacturing/doctype/bom/bom.py:276 +#: erpnext/manufacturing/doctype/bom/bom.js:727 +#: erpnext/manufacturing/doctype/bom/bom.py:278 #: erpnext/public/js/controllers/accounts.js:277 -#: erpnext/public/js/controllers/transaction.js:3368 +#: erpnext/public/js/controllers/transaction.js:3372 msgid "Please select a Company first." msgstr "" @@ -35944,7 +36047,7 @@ msgstr "" msgid "Please select a Warehouse" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:1569 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1600 msgid "Please select a Work Order first." msgstr "" @@ -36013,7 +36116,7 @@ msgstr "" msgid "Please select at least one row with difference value" msgstr "" -#: erpnext/public/js/controllers/transaction.js:520 +#: erpnext/public/js/controllers/transaction.js:524 msgid "Please select at least one schedule." msgstr "" @@ -36063,7 +36166,7 @@ msgstr "" msgid "Please select rows to create Reposting Entries" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:93 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:98 msgid "Please select the Company" msgstr "" @@ -36307,7 +36410,7 @@ msgstr "" msgid "Please set opening number of booked depreciations" msgstr "" -#: erpnext/public/js/controllers/transaction.js:2756 +#: erpnext/public/js/controllers/transaction.js:2760 msgid "Please set recurring after saving" msgstr "" @@ -36315,19 +36418,19 @@ msgstr "" msgid "Please set the Customer Address" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:182 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:187 msgid "Please set the Default Cost Center in {0} company." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:661 +#: erpnext/manufacturing/doctype/work_order/work_order.js:662 msgid "Please set the Item Code first" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:1632 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1663 msgid "Please set the Target Warehouse in the Job Card" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:1636 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1667 msgid "Please set the WIP Warehouse in the Job Card" msgstr "" @@ -36638,7 +36741,7 @@ msgstr "" msgid "Posting Date cannot be future date" msgstr "" -#: erpnext/public/js/controllers/transaction.js:1108 +#: erpnext/public/js/controllers/transaction.js:1112 msgid "Posting Date will change to today's date as Edit Posting Date and Time is unchecked. Are you sure want to proceed?" msgstr "" @@ -36701,7 +36804,7 @@ msgstr "" msgid "Posting Time" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2333 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2338 msgid "Posting date and posting time is mandatory" msgstr "" @@ -37102,7 +37205,7 @@ msgstr "" msgid "Price is not set for the item." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:566 +#: erpnext/manufacturing/doctype/bom/bom.py:604 msgid "Price not found for item {0} in price list {1}" msgstr "" @@ -37477,11 +37580,18 @@ msgstr "" msgid "Process Loss" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1228 +#. Label of the process_loss_per (Percent) field in DocType 'BOM Secondary +#. Item' +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json +msgid "Process Loss %" +msgstr "" + +#: erpnext/manufacturing/doctype/bom/bom.py:1271 msgid "Process Loss Percentage cannot be greater than 100" msgstr "" #. Label of the process_loss_qty (Float) field in DocType 'BOM' +#. Label of the process_loss_qty (Float) field in DocType 'BOM Secondary Item' #. Label of the process_loss_qty (Float) field in DocType 'Job Card' #. Label of the process_loss_qty (Float) field in DocType 'Work Order' #. Label of the process_loss_qty (Float) field in DocType 'Work Order @@ -37489,17 +37599,21 @@ msgstr "" #. Label of the process_loss_qty (Float) field in DocType 'Stock Entry' #. Label of the process_loss_qty (Float) field in DocType 'Subcontracting #. Inward Order Item' +#. Label of the process_loss_qty (Float) field in DocType 'Subcontracting +#. Receipt Item' #: erpnext/manufacturing/doctype/bom/bom.json +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json #: erpnext/manufacturing/doctype/job_card/job_card.json #: erpnext/manufacturing/doctype/work_order/work_order.json #: erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json #: erpnext/manufacturing/report/process_loss_report/process_loss_report.py:94 #: erpnext/stock/doctype/stock_entry/stock_entry.json #: erpnext/subcontracting/doctype/subcontracting_inward_order_item/subcontracting_inward_order_item.json +#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json msgid "Process Loss Qty" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:340 +#: erpnext/manufacturing/doctype/job_card/job_card.js:338 msgid "Process Loss Quantity" msgstr "" @@ -37638,7 +37752,7 @@ msgstr "" #. Label of the produced_qty (Float) field in DocType 'Subcontracting Inward #. Order Item' #. Label of the produced_qty (Float) field in DocType 'Subcontracting Inward -#. Order Scrap Item' +#. Order Secondary Item' #: erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json #: erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json #: erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py:50 @@ -37646,7 +37760,7 @@ msgstr "" #: erpnext/manufacturing/report/work_order_summary/work_order_summary.py:215 #: erpnext/stock/doctype/batch/batch.json #: erpnext/subcontracting/doctype/subcontracting_inward_order_item/subcontracting_inward_order_item.json -#: erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.json +#: erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json msgid "Produced Qty" msgstr "" @@ -38555,7 +38669,7 @@ msgstr "" #: erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js:48 #: erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py:203 #: erpnext/buying/workspace/buying/buying.json -#: erpnext/controllers/buying_controller.py:918 +#: erpnext/controllers/buying_controller.py:922 #: 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 @@ -38630,7 +38744,7 @@ msgstr "" msgid "Purchase Order Item Supplied" msgstr "" -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:975 +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:1012 msgid "Purchase Order Item reference is missing in Subcontracting Receipt {0}" msgstr "" @@ -38677,7 +38791,7 @@ msgstr "" msgid "Purchase Order {0} is not submitted" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:883 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:887 msgid "Purchase Orders" msgstr "" @@ -38818,7 +38932,7 @@ msgstr "" msgid "Purchase Receipt doesn't have any Item for which Retain Sample is enabled." msgstr "" -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:1051 +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:1088 msgid "Purchase Receipt {0} created." msgstr "" @@ -38981,10 +39095,10 @@ msgstr "" #. Label of the qty (Float) field in DocType 'Opportunity Item' #. Label of the qty (Float) field in DocType 'BOM Creator Item' #. Label of the qty (Float) field in DocType 'BOM Item' -#. Label of the stock_qty (Float) field in DocType 'BOM Scrap Item' +#. Label of the qty (Float) field in DocType 'BOM Secondary Item' #. Label of the qty (Float) field in DocType 'BOM Website Item' #. Label of the qty_section (Section Break) field in DocType 'Job Card Item' -#. Label of the stock_qty (Float) field in DocType 'Job Card Scrap Item' +#. Label of the stock_qty (Float) field in DocType 'Job Card Secondary Item' #. Label of the qty (Float) field in DocType 'Production Plan Item Reference' #. Label of the qty_section (Section Break) field in DocType 'Work Order Item' #. Label of the qty (Float) field in DocType 'Delivery Schedule Item' @@ -39012,13 +39126,13 @@ msgstr "" #: erpnext/controllers/trends.py:276 erpnext/controllers/trends.py:288 #: erpnext/controllers/trends.py:293 #: erpnext/crm/doctype/opportunity_item/opportunity_item.json -#: erpnext/manufacturing/doctype/bom/bom.js:1086 +#: erpnext/manufacturing/doctype/bom/bom.js:1103 #: 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 +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json #: erpnext/manufacturing/doctype/bom_website_item/bom_website_item.json #: erpnext/manufacturing/doctype/job_card_item/job_card_item.json -#: erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json +#: erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json #: erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json #: erpnext/manufacturing/doctype/work_order_item/work_order_item.json #: erpnext/manufacturing/doctype/workstation/workstation_job_card.html:28 @@ -39028,7 +39142,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:849 +#: erpnext/public/js/stock_reservation.js:336 erpnext/public/js/utils.js:853 #: 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 @@ -39062,6 +39176,12 @@ msgstr "" msgid "Qty " msgstr "" +#. Label of the received_qty (Float) field in DocType 'Subcontracting Receipt +#. Item' +#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +msgid "Qty (As per BOM)" +msgstr "" + #. Label of the company_total_stock (Float) field in DocType 'Sales Invoice #. Item' #. Label of the company_total_stock (Float) field in DocType 'Quotation Item' @@ -39133,7 +39253,7 @@ msgstr "" #. Label of the for_quantity (Float) field in DocType 'Job Card' #. Label of the qty (Float) field in DocType 'Work Order' -#: erpnext/manufacturing/doctype/bom/bom.js:367 +#: erpnext/manufacturing/doctype/bom/bom.js:405 #: erpnext/manufacturing/doctype/job_card/job_card.json #: erpnext/manufacturing/doctype/work_order/work_order.json #: erpnext/manufacturing/report/process_loss_report/process_loss_report.py:82 @@ -39144,7 +39264,7 @@ msgstr "" msgid "Qty To Manufacture ({0}) cannot be a fraction for the UOM {2}. To allow this, disable '{1}' in the UOM {2}." msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:251 +#: erpnext/manufacturing/doctype/job_card/job_card.py:259 msgid "Qty To Manufacture in the job card cannot be greater than Qty To Manufacture in the work order for the operation {0}.

Solution: Either you can reduce the Qty To Manufacture in the job card or set the 'Overproduction Percentage For Work Order' in the {1}." msgstr "" @@ -39195,7 +39315,7 @@ msgstr "" msgid "Qty for which recursion isn't applicable." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1007 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1008 msgid "Qty for {0}" msgstr "" @@ -39213,7 +39333,7 @@ msgstr "" msgid "Qty of Finished Goods Item" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:647 +#: erpnext/stock/doctype/pick_list/pick_list.py:674 msgid "Qty of Finished Goods Item should be greater than 0." msgstr "" @@ -39246,8 +39366,8 @@ msgstr "" msgid "Qty to Fetch" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:312 -#: erpnext/manufacturing/doctype/job_card/job_card.py:871 +#: erpnext/manufacturing/doctype/job_card/job_card.js:310 +#: erpnext/manufacturing/doctype/job_card/job_card.py:889 msgid "Qty to Manufacture" msgstr "" @@ -39398,7 +39518,7 @@ msgstr "" #: erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json #: erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json #: erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json -#: erpnext/manufacturing/doctype/bom/bom.js:236 +#: erpnext/manufacturing/doctype/bom/bom.js:274 #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/job_card/job_card.json #: erpnext/quality_management/workspace/quality/quality.json @@ -39478,17 +39598,17 @@ msgstr "" msgid "Quality Inspection Template Name" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:780 +#: erpnext/manufacturing/doctype/job_card/job_card.py:798 msgid "Quality Inspection is required for the item {0} before completing the job card {1}" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:791 -#: erpnext/manufacturing/doctype/job_card/job_card.py:800 +#: erpnext/manufacturing/doctype/job_card/job_card.py:809 +#: erpnext/manufacturing/doctype/job_card/job_card.py:818 msgid "Quality Inspection {0} is not submitted for the item: {1}" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:810 -#: erpnext/manufacturing/doctype/job_card/job_card.py:819 +#: erpnext/manufacturing/doctype/job_card/job_card.py:828 +#: erpnext/manufacturing/doctype/job_card/job_card.py:837 msgid "Quality Inspection {0} is rejected for the item: {1}" msgstr "" @@ -39624,7 +39744,7 @@ msgstr "" #: erpnext/buying/report/purchase_analytics/purchase_analytics.js:28 #: erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py:213 #: erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json -#: erpnext/manufacturing/doctype/bom/bom.js:454 +#: erpnext/manufacturing/doctype/bom/bom.js:493 #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/bom_creator/bom_creator.js:69 #: erpnext/manufacturing/doctype/bom_creator/bom_creator.json @@ -39708,10 +39828,8 @@ msgstr "" #. Label of the quantity_and_rate_section (Section Break) field in DocType 'BOM #. Creator Item' #. Label of the quantity_and_rate (Section Break) field in DocType 'BOM Item' -#. Label of the quantity_and_rate (Section Break) field in DocType 'BOM Scrap -#. Item' #. Label of the quantity_and_rate (Section Break) field in DocType 'Job Card -#. Scrap Item' +#. Secondary Item' #. Label of the quantity_and_rate (Section Break) field in DocType 'Quotation #. Item' #. Label of the quantity_and_rate (Section Break) field in DocType 'Sales Order @@ -39728,8 +39846,7 @@ msgstr "" #: erpnext/crm/doctype/opportunity_item/opportunity_item.json #: 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 -#: erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json +#: erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json #: erpnext/selling/doctype/quotation_item/quotation_item.json #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -39764,7 +39881,7 @@ msgstr "" msgid "Quantity must be less than or equal to {0}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1037 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1038 #: erpnext/stock/doctype/pick_list/pick_list.js:206 msgid "Quantity must not be more than {0}" msgstr "" @@ -39774,13 +39891,13 @@ msgstr "" msgid "Quantity of item obtained after manufacturing / repacking from given quantities of raw materials" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:734 +#: erpnext/manufacturing/doctype/bom/bom.py:772 msgid "Quantity required for Item {0} in row {1}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:678 -#: erpnext/manufacturing/doctype/job_card/job_card.js:393 -#: erpnext/manufacturing/doctype/job_card/job_card.js:463 +#: erpnext/manufacturing/doctype/bom/bom.py:716 +#: erpnext/manufacturing/doctype/job_card/job_card.js:391 +#: erpnext/manufacturing/doctype/job_card/job_card.js:461 #: erpnext/manufacturing/doctype/workstation/workstation.js:303 msgid "Quantity should be greater than 0" msgstr "" @@ -39789,7 +39906,7 @@ msgstr "" msgid "Quantity to Manufacture" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2592 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2593 msgid "Quantity to Manufacture can not be zero for the operation {0}" msgstr "" @@ -39826,7 +39943,7 @@ msgstr "" msgid "Query Route String" msgstr "" -#: erpnext/accounts/doctype/accounts_settings/accounts_settings.py:176 +#: erpnext/accounts/doctype/accounts_settings/accounts_settings.py:177 msgid "Queue Size should be between 5 and 100" msgstr "" @@ -40015,7 +40132,7 @@ msgstr "" #. Label of the rate (Currency) field in DocType 'BOM Creator Item' #. Label of the rate (Currency) field in DocType 'BOM Explosion Item' #. Label of the rate (Currency) field in DocType 'BOM Item' -#. Label of the rate (Currency) field in DocType 'BOM Scrap Item' +#. Label of the rate (Currency) field in DocType 'BOM Secondary Item' #. Label of the rate (Currency) field in DocType 'Work Order Item' #. Label of the rate (Float) field in DocType 'Product Bundle Item' #. Label of the rate (Currency) field in DocType 'Quotation Item' @@ -40064,9 +40181,9 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json #: erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json #: erpnext/manufacturing/doctype/bom_item/bom_item.json -#: erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json #: erpnext/manufacturing/doctype/work_order_item/work_order_item.json -#: erpnext/public/js/utils.js:859 +#: erpnext/public/js/utils.js:863 #: 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 @@ -40388,8 +40505,8 @@ msgstr "" #. Label of the materials_section (Section Break) field in DocType 'BOM' #. 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:407 -#: erpnext/manufacturing/doctype/bom/bom.js:1059 +#: erpnext/manufacturing/doctype/bom/bom.js:446 +#: erpnext/manufacturing/doctype/bom/bom.js:1076 #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/job_card/job_card.json #: erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -40453,7 +40570,7 @@ msgstr "" msgid "Raw Materials Supplied Cost" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:726 +#: erpnext/manufacturing/doctype/bom/bom.py:764 msgid "Raw Materials cannot be blank." msgstr "" @@ -40475,7 +40592,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:760 +#: erpnext/manufacturing/doctype/work_order/work_order.js:761 #: 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 @@ -40771,13 +40888,10 @@ msgid "Received Qty in Stock UOM" msgstr "" #. Label of the received_qty (Float) field in DocType 'Purchase Receipt Item' -#. Label of the received_qty (Float) field in DocType 'Subcontracting Receipt -#. Item' #: erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py:119 #: erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py:50 #: erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.html:9 #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json -#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json msgid "Received Quantity" msgstr "" @@ -41025,7 +41139,7 @@ msgstr "" msgid "Reference #{0} dated {1}" msgstr "" -#: erpnext/public/js/controllers/transaction.js:2869 +#: erpnext/public/js/controllers/transaction.js:2873 msgid "Reference Date for Early Payment Discount" msgstr "" @@ -41216,20 +41330,20 @@ msgstr "" msgid "Regular" msgstr "" -#: erpnext/stock/doctype/inventory_dimension/inventory_dimension.py:198 +#: erpnext/stock/doctype/inventory_dimension/inventory_dimension.py:199 msgid "Rejected " msgstr "" #. Label of the rejected_qty (Float) field in DocType 'Purchase Invoice Item' +#. Label of the rejected_qty (Float) field in DocType 'Subcontracting Receipt +#. Item' #: erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json msgid "Rejected Qty" msgstr "" #. Label of the rejected_qty (Float) field in DocType 'Purchase Receipt Item' -#. Label of the rejected_qty (Float) field in DocType 'Subcontracting Receipt -#. Item' #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json -#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json msgid "Rejected Quantity" msgstr "" @@ -41774,7 +41888,7 @@ msgstr "" msgid "Reqd Qty (BOM)" msgstr "" -#: erpnext/public/js/utils.js:875 +#: erpnext/public/js/utils.js:879 msgid "Reqd by date" msgstr "" @@ -42046,7 +42160,7 @@ msgstr "" msgid "Reservation Based On" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:918 +#: erpnext/manufacturing/doctype/work_order/work_order.js:919 #: 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 @@ -42161,14 +42275,14 @@ msgstr "" msgid "Reserved Quantity for Production" msgstr "" -#: erpnext/stock/stock_ledger.py:2334 +#: erpnext/stock/stock_ledger.py:2341 msgid "Reserved Serial No." 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:934 +#: erpnext/manufacturing/doctype/work_order/work_order.js:935 #: erpnext/public/js/stock_reservation.js:236 #: erpnext/selling/doctype/sales_order/sales_order.js:127 #: erpnext/selling/doctype/sales_order/sales_order.js:457 @@ -42177,13 +42291,13 @@ msgstr "" #: erpnext/stock/doctype/pick_list/pick_list.js:170 #: erpnext/stock/report/reserved_stock/reserved_stock.json #: erpnext/stock/report/stock_balance/stock_balance.py:572 -#: erpnext/stock/stock_ledger.py:2318 +#: erpnext/stock/stock_ledger.py:2325 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:205 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:333 msgid "Reserved Stock" msgstr "" -#: erpnext/stock/stock_ledger.py:2363 +#: erpnext/stock/stock_ledger.py:2370 msgid "Reserved Stock for Batch" msgstr "" @@ -42195,7 +42309,7 @@ msgstr "" msgid "Reserved Stock for Sub-assembly" msgstr "" -#: erpnext/controllers/buying_controller.py:645 +#: erpnext/controllers/buying_controller.py:649 msgid "Reserved Warehouse is mandatory for the Item {item_code} in Raw Materials supplied." msgstr "" @@ -43138,11 +43252,11 @@ msgid "Row #{0}: Acceptance Criteria Formula is required." msgstr "" #: erpnext/controllers/subcontracting_controller.py:125 -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:534 +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:571 msgid "Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same" msgstr "" -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:527 +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:564 msgid "Row #{0}: Accepted Warehouse is mandatory for the accepted Item {1}" msgstr "" @@ -43187,7 +43301,7 @@ msgstr "" msgid "Row #{0}: Batch No {1} is already selected." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:430 +#: erpnext/controllers/subcontracting_inward_controller.py:435 msgid "Row #{0}: Batch No(s) {1} is not a part of the linked Subcontracting Inward Order. Please select valid Batch No(s)." msgstr "" @@ -43195,15 +43309,15 @@ msgstr "" msgid "Row #{0}: Cannot allocate more than {1} against payment term {2}" msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:631 +#: erpnext/controllers/subcontracting_inward_controller.py:637 msgid "Row #{0}: Cannot cancel this Manufacturing Stock Entry as billed quantity of Item {1} cannot be greater than consumed quantity." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:610 -msgid "Row #{0}: Cannot cancel this Manufacturing Stock Entry as quantity of Scrap Item {1} produced cannot be less than quantity delivered." +#: erpnext/controllers/subcontracting_inward_controller.py:616 +msgid "Row #{0}: Cannot cancel this Manufacturing Stock Entry as quantity of Secondary Item {1} produced cannot be less than quantity delivered." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:478 +#: erpnext/controllers/subcontracting_inward_controller.py:483 msgid "Row #{0}: Cannot cancel this Stock Entry as returned quantity cannot be greater than delivered quantity for Item {1} in the linked Subcontracting Inward Order" msgstr "" @@ -43235,7 +43349,7 @@ msgstr "" msgid "Row #{0}: Cannot set Rate if the billed amount is greater than the amount for Item {1}." msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:1109 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1127 msgid "Row #{0}: Cannot transfer more than Required Qty {1} for Item {2} against Job Card {3}" msgstr "" @@ -43275,13 +43389,13 @@ msgstr "" msgid "Row #{0}: Cumulative threshold cannot be less than Single Transaction threshold" msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:88 +#: erpnext/controllers/subcontracting_inward_controller.py:90 msgid "Row #{0}: Customer Provided Item {1} against Subcontracting Inward Order Item {2} ({3}) cannot be added multiple times." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:176 -#: erpnext/controllers/subcontracting_inward_controller.py:301 -#: erpnext/controllers/subcontracting_inward_controller.py:349 +#: erpnext/controllers/subcontracting_inward_controller.py:178 +#: erpnext/controllers/subcontracting_inward_controller.py:304 +#: erpnext/controllers/subcontracting_inward_controller.py:352 msgid "Row #{0}: Customer Provided Item {1} cannot be added multiple times in the Subcontracting Inward process." msgstr "" @@ -43293,7 +43407,7 @@ msgstr "" msgid "Row #{0}: Customer Provided Item {1} does not exist in the Required Items table linked to the Subcontracting Inward Order." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:285 +#: erpnext/controllers/subcontracting_inward_controller.py:288 msgid "Row #{0}: Customer Provided Item {1} exceeds quantity available through Subcontracting Inward Order" msgstr "" @@ -43301,12 +43415,12 @@ msgstr "" msgid "Row #{0}: Customer Provided Item {1} has insufficient quantity in the Subcontracting Inward Order. Available quantity is {2}." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:312 +#: erpnext/controllers/subcontracting_inward_controller.py:315 msgid "Row #{0}: Customer Provided Item {1} is not a part of Subcontracting Inward Order {2}" msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:218 -#: erpnext/controllers/subcontracting_inward_controller.py:360 +#: erpnext/controllers/subcontracting_inward_controller.py:220 +#: erpnext/controllers/subcontracting_inward_controller.py:363 msgid "Row #{0}: Customer Provided Item {1} is not a part of Work Order {2}" msgstr "" @@ -43357,12 +43471,12 @@ msgstr "" msgid "Row #{0}: Finished Good must be {1}" msgstr "" -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:515 -msgid "Row #{0}: Finished Good reference is mandatory for Scrap Item {1}." +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:552 +msgid "Row #{0}: Finished Good reference is mandatory for Secondary Item {1}." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:168 -#: erpnext/controllers/subcontracting_inward_controller.py:291 +#: erpnext/controllers/subcontracting_inward_controller.py:170 +#: erpnext/controllers/subcontracting_inward_controller.py:294 msgid "Row #{0}: For Customer Provided Item {1}, Source Warehouse must be {2}" msgstr "" @@ -43382,7 +43496,7 @@ msgstr "" msgid "Row #{0}: From Date cannot be before To Date" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:861 +#: erpnext/manufacturing/doctype/job_card/job_card.py:879 msgid "Row #{0}: From Time and To Time fields are required" msgstr "" @@ -43390,7 +43504,7 @@ msgstr "" msgid "Row #{0}: Item added" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1535 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1542 msgid "Row #{0}: Item {1} cannot be transferred more than {2} against {3} {4}" msgstr "" @@ -43414,7 +43528,7 @@ msgstr "" msgid "Row #{0}: Item {1} in warehouse {2}: Available {3}, Needed {4}." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:63 +#: erpnext/controllers/subcontracting_inward_controller.py:65 msgid "Row #{0}: Item {1} is not a Customer Provided Item." msgstr "" @@ -43422,8 +43536,8 @@ msgstr "" msgid "Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:113 -#: erpnext/controllers/subcontracting_inward_controller.py:491 +#: erpnext/controllers/subcontracting_inward_controller.py:115 +#: erpnext/controllers/subcontracting_inward_controller.py:496 msgid "Row #{0}: Item {1} is not a part of Subcontracting Inward Order {2}" msgstr "" @@ -43435,11 +43549,11 @@ msgstr "" msgid "Row #{0}: Item {1} is not a stock item" msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:77 +#: erpnext/controllers/subcontracting_inward_controller.py:79 msgid "Row #{0}: Item {1} mismatch. Changing of item code is not permitted, add another row instead." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:126 +#: erpnext/controllers/subcontracting_inward_controller.py:128 msgid "Row #{0}: Item {1} mismatch. Changing of item code is not permitted." msgstr "" @@ -43471,8 +43585,8 @@ msgstr "" msgid "Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Work Order {3}. Please update operation status via Job Card {4}." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:206 -#: erpnext/controllers/subcontracting_inward_controller.py:339 +#: erpnext/controllers/subcontracting_inward_controller.py:208 +#: erpnext/controllers/subcontracting_inward_controller.py:342 msgid "Row #{0}: Overconsumption of Customer Provided Item {1} against Work Order {2} is not allowed in the Subcontracting Inward process." msgstr "" @@ -43484,7 +43598,7 @@ msgstr "" msgid "Row #{0}: Please select the BOM No in Assembly Items" msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:104 +#: erpnext/controllers/subcontracting_inward_controller.py:106 msgid "Row #{0}: Please select the Finished Good Item against which this Customer Provided Item will be used." msgstr "" @@ -43500,6 +43614,11 @@ msgstr "" msgid "Row #{0}: Please update deferred revenue/expense account in item row or default account in company master" msgstr "" +#: erpnext/manufacturing/doctype/bom/bom.py:345 +#, python-format +msgid "Row #{0}: Process Loss Percentage should be less than 100% for {1} Item {2}" +msgstr "" + #: erpnext/public/js/utils/barcode_scanner.js:425 msgid "Row #{0}: Qty increased by {1}" msgstr "" @@ -43533,10 +43652,14 @@ msgstr "" msgid "Row #{0}: Quantity for Item {1} cannot be zero." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:532 +#: erpnext/controllers/subcontracting_inward_controller.py:537 msgid "Row #{0}: Quantity of Item {1} cannot be more than {2} {3} against Subcontracting Inward Order {4}" msgstr "" +#: erpnext/manufacturing/doctype/bom/bom.py:338 +msgid "Row #{0}: Quantity should be greater than 0 for {1} Item {2}" +msgstr "" + #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:1696 msgid "Row #{0}: Quantity to reserve for the Item {1} should be greater than 0." msgstr "" @@ -43556,8 +43679,8 @@ msgstr "" msgid "Row #{0}: Reference Document Type must be one of Sales Order, Sales Invoice, Journal Entry or Dunning" msgstr "" -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:508 -msgid "Row #{0}: Rejected Qty cannot be set for Scrap Item {1}." +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:545 +msgid "Row #{0}: Rejected Qty cannot be set for Secondary Item {1}." msgstr "" #: erpnext/controllers/subcontracting_controller.py:118 @@ -43572,16 +43695,16 @@ msgstr "" msgid "Row #{0}: Return Against is required for returning asset" msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:140 +#: erpnext/controllers/subcontracting_inward_controller.py:142 msgid "Row #{0}: Returned quantity cannot be greater than available quantity for Item {1}" msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:153 +#: erpnext/controllers/subcontracting_inward_controller.py:155 msgid "Row #{0}: Returned quantity cannot be greater than available quantity to return for Item {1}" msgstr "" -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:503 -msgid "Row #{0}: Scrap Item Qty cannot be zero" +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:540 +msgid "Row #{0}: Secondary Item Qty cannot be zero" msgstr "" #: erpnext/controllers/selling_controller.py:296 @@ -43608,7 +43731,7 @@ msgstr "" msgid "Row #{0}: Serial No {1} is already selected." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:419 +#: erpnext/controllers/subcontracting_inward_controller.py:424 msgid "Row #{0}: Serial No(s) {1} are not a part of the linked Subcontracting Inward Order. Please select valid Serial No(s)." msgstr "" @@ -43632,7 +43755,7 @@ msgstr "" msgid "Row #{0}: Since 'Track Semi Finished Goods' is enabled, the BOM {1} cannot be used for Sub Assembly Items" msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:398 +#: erpnext/controllers/subcontracting_inward_controller.py:403 msgid "Row #{0}: Source Warehouse must be same as Customer Warehouse {1} from the linked Subcontracting Inward Order" msgstr "" @@ -43697,7 +43820,7 @@ msgstr "" msgid "Row #{0}: Stock quantity {1} ({2}) for item {3} cannot exceed {4}" msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:392 +#: erpnext/controllers/subcontracting_inward_controller.py:397 msgid "Row #{0}: Target Warehouse must be same as Customer Warehouse {1} from the linked Subcontracting Inward Order" msgstr "" @@ -43729,7 +43852,7 @@ msgstr "" msgid "Row #{0}: Withholding Amount {1} does not match calculated amount {2}." msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:572 +#: erpnext/controllers/subcontracting_inward_controller.py:577 msgid "Row #{0}: Work Order exists against full or partial quantity of Item {1}" msgstr "" @@ -43749,7 +43872,7 @@ msgstr "" msgid "Row #{0}: {1} is not a valid reading field. Please refer to the field description." msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:126 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:131 msgid "Row #{0}: {1} is required to create the Opening {2} Invoices" msgstr "" @@ -43769,23 +43892,23 @@ msgstr "" msgid "Row #{idx}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor." msgstr "" -#: erpnext/controllers/buying_controller.py:576 +#: erpnext/controllers/buying_controller.py:580 msgid "Row #{idx}: Item rate has been updated as per valuation rate since its an internal stock transfer." msgstr "" -#: erpnext/controllers/buying_controller.py:1060 +#: erpnext/controllers/buying_controller.py:1064 msgid "Row #{idx}: Please enter a location for the asset item {item_code}." msgstr "" -#: erpnext/controllers/buying_controller.py:699 +#: erpnext/controllers/buying_controller.py:703 msgid "Row #{idx}: Received Qty must be equal to Accepted + Rejected Qty for Item {item_code}." msgstr "" -#: erpnext/controllers/buying_controller.py:712 +#: erpnext/controllers/buying_controller.py:716 msgid "Row #{idx}: {field_label} can not be negative for item {item_code}." msgstr "" -#: erpnext/controllers/buying_controller.py:665 +#: erpnext/controllers/buying_controller.py:669 msgid "Row #{idx}: {field_label} is mandatory." msgstr "" @@ -43793,7 +43916,7 @@ msgstr "" msgid "Row #{idx}: {from_warehouse_field} and {to_warehouse_field} cannot be same." msgstr "" -#: erpnext/controllers/buying_controller.py:1177 +#: erpnext/controllers/buying_controller.py:1181 msgid "Row #{idx}: {schedule_date} cannot be before {transaction_date}." msgstr "" @@ -43801,7 +43924,7 @@ msgstr "" msgid "Row #{}: Currency of {} - {} doesn't matches company currency." msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:108 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:113 msgid "Row #{}: Either Party ID or Party Name is required" msgstr "" @@ -43821,7 +43944,7 @@ msgstr "" msgid "Row #{}: POS Invoice {} is not submitted yet" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:118 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:123 msgid "Row #{}: Party ID is required" msgstr "" @@ -43845,7 +43968,7 @@ msgstr "" msgid "Row #{}: You cannot add positive quantities in a return invoice. Please remove item {} to complete the return." msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:233 +#: erpnext/stock/doctype/pick_list/pick_list.py:234 msgid "Row #{}: item {} has been picked already." msgstr "" @@ -43854,7 +43977,7 @@ msgstr "" msgid "Row #{}: {}" msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:121 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:126 msgid "Row #{}: {} {} does not exist." msgstr "" @@ -43866,15 +43989,15 @@ msgstr "" msgid "Row No {0}: Warehouse is required. Please set a Default Warehouse for Item {1} and Company {2}" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:728 +#: erpnext/manufacturing/doctype/job_card/job_card.py:746 msgid "Row {0} : Operation is required against the raw material item {1}" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:263 +#: erpnext/stock/doctype/pick_list/pick_list.py:264 msgid "Row {0} picked quantity is less than the required quantity, additional {1} {2} required." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1559 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1566 msgid "Row {0}# Item {1} not found in 'Raw Materials Supplied' table in {2} {3}" msgstr "" @@ -43906,7 +44029,7 @@ msgstr "" msgid "Row {0}: Allocated amount {1} must be less than or equal to remaining payment amount {2}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1220 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1227 msgid "Row {0}: As {1} is enabled, raw materials cannot be added to {2} entry. Use {3} entry to consume raw materials." msgstr "" @@ -43918,7 +44041,7 @@ msgstr "" msgid "Row {0}: Both Debit and Credit values cannot be zero" msgstr "" -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:550 +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:587 msgid "" "Row {0}: Consumed Qty {1} {2} must be less than or equal to Available Qty For Consumption\n" "\t\t\t\t\t{3} {4} in Consumed Items Table." @@ -43932,7 +44055,7 @@ msgstr "" msgid "Row {0}: Cost Center {1} does not belong to Company {2}" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:174 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:175 msgid "Row {0}: Cost center is required for an item {1}" msgstr "" @@ -43940,7 +44063,7 @@ msgstr "" msgid "Row {0}: Credit entry can not be linked with a {1}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:538 +#: erpnext/manufacturing/doctype/bom/bom.py:578 msgid "Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2}" msgstr "" @@ -43965,7 +44088,7 @@ msgid "Row {0}: Either Delivery Note Item or Packed Item reference is mandatory. msgstr "" #: erpnext/accounts/doctype/journal_entry/journal_entry.py:1018 -#: erpnext/controllers/taxes_and_totals.py:1340 +#: erpnext/controllers/taxes_and_totals.py:1377 msgid "Row {0}: Exchange Rate is mandatory" msgstr "" @@ -43977,7 +44100,7 @@ msgstr "" msgid "Row {0}: Expected Value After Useful Life must be less than Net Purchase Amount" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:183 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:187 msgid "Row {0}: Expense Account {1} is linked to company {2}. Please select an account belonging to company {3}." msgstr "" @@ -44001,7 +44124,7 @@ msgstr "" msgid "Row {0}: From Time and To Time is mandatory." msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:306 +#: erpnext/manufacturing/doctype/job_card/job_card.py:324 #: erpnext/projects/doctype/timesheet/timesheet.py:225 msgid "Row {0}: From Time and To Time of {1} is overlapping with {2}" msgstr "" @@ -44010,7 +44133,7 @@ msgstr "" msgid "Row {0}: From Warehouse is mandatory for internal transfers" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:297 +#: erpnext/manufacturing/doctype/job_card/job_card.py:315 msgid "Row {0}: From time must be less than to time" msgstr "" @@ -44046,7 +44169,7 @@ msgstr "" msgid "Row {0}: Item {1}'s quantity cannot be higher than the available quantity." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1211 +#: erpnext/manufacturing/doctype/bom/bom.py:1244 msgid "Row {0}: Operation time should be greater than 0 for operation {1}" msgstr "" @@ -44110,7 +44233,7 @@ msgstr "" msgid "Row {0}: Project must be same as the one set in the Timesheet: {1}." msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:151 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:152 msgid "Row {0}: Purchase Invoice {1} has no stock impact." msgstr "" @@ -44142,7 +44265,7 @@ msgstr "" msgid "Row {0}: Shift cannot be changed since the depreciation has already been processed" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1572 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1579 msgid "Row {0}: Subcontracted Item is mandatory for the raw material {1}" msgstr "" @@ -44170,7 +44293,7 @@ msgstr "" msgid "Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3362 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3394 msgid "Row {0}: Transferred quantity cannot be greater than the requested quantity." msgstr "" @@ -44178,15 +44301,15 @@ msgstr "" msgid "Row {0}: UOM Conversion Factor is mandatory" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:169 +#: erpnext/stock/doctype/pick_list/pick_list.py:170 msgid "Row {0}: Warehouse is required" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:178 +#: erpnext/stock/doctype/pick_list/pick_list.py:179 msgid "Row {0}: Warehouse {1} is linked to company {2}. Please select a warehouse belonging to company {3}." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1205 +#: erpnext/manufacturing/doctype/bom/bom.py:1238 #: erpnext/manufacturing/doctype/work_order/work_order.py:415 msgid "Row {0}: Workstation or Workstation Type is mandatory for an operation {1}" msgstr "" @@ -44211,11 +44334,11 @@ msgstr "" msgid "Row {0}: {1} {2} does not match with {3}" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:133 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:134 msgid "Row {0}: {1} {2} is linked to company {3}. Please select a document belonging to company {4}." msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:107 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:108 msgid "Row {0}: {2} Item {1} does not exist in {2} {3}" msgstr "" @@ -44223,7 +44346,7 @@ msgstr "" msgid "Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}." msgstr "" -#: erpnext/controllers/buying_controller.py:1042 +#: erpnext/controllers/buying_controller.py:1046 msgid "Row {idx}: Asset Naming Series is mandatory for the auto creation of assets for item {item_code}." msgstr "" @@ -44332,7 +44455,7 @@ msgstr "" msgid "SLA Paused On" msgstr "" -#: erpnext/public/js/utils.js:1239 +#: erpnext/public/js/utils.js:1243 msgid "SLA is on hold since {0}" msgstr "" @@ -45279,12 +45402,12 @@ msgstr "" #. Label of the sample_size (Float) field in DocType 'Quality Inspection' #: erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py:93 -#: erpnext/public/js/controllers/transaction.js:2926 +#: erpnext/public/js/controllers/transaction.js:2930 #: erpnext/stock/doctype/quality_inspection/quality_inspection.json msgid "Sample Size" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3852 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3884 msgid "Sample quantity {0} cannot be more than received quantity {1}" msgstr "" @@ -45385,7 +45508,7 @@ msgstr "" msgid "Schedule Date" msgstr "" -#: erpnext/public/js/controllers/transaction.js:486 +#: erpnext/public/js/controllers/transaction.js:490 msgid "Schedule Name" msgstr "" @@ -45487,59 +45610,25 @@ msgstr "" msgid "Scoring Standings" msgstr "" -#. Label of the scrap_section (Tab Break) field in DocType 'BOM' -#: erpnext/manufacturing/doctype/bom/bom.json -msgid "Scrap & Process Loss" +#. Option for the 'Type' (Select) field in DocType 'BOM Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Job Card Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Stock Entry Detail' +#. Option for the 'Type' (Select) field in DocType 'Subcontracting Inward Order +#. Secondary Item' +#. Option for the 'Type' (Select) field in DocType 'Subcontracting Receipt +#. Item' +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json +#: erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json +#: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +#: erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json +#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +msgid "Scrap" msgstr "" #: erpnext/assets/doctype/asset/asset.js:163 msgid "Scrap Asset" msgstr "" -#. Label of the scrap_cost_per_qty (Float) field in DocType 'Subcontracting -#. Receipt Item' -#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json -msgid "Scrap Cost Per Qty" -msgstr "" - -#. Label of the item_code (Link) field in DocType 'Job Card Scrap Item' -#: erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json -msgid "Scrap Item Code" -msgstr "" - -#. Label of the item_name (Data) field in DocType 'Job Card Scrap Item' -#: erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json -msgid "Scrap Item Name" -msgstr "" - -#. Label of the scrap_items (Table) field in DocType 'BOM' -#. Label of the scrap_items_section (Section Break) field in DocType 'BOM' -#. Label of the scrap_items_section (Tab Break) field in DocType 'Job Card' -#. Label of the scrap_items (Table) field in DocType 'Job Card' -#. Label of the scrap_items (Table) field in DocType 'Subcontracting Inward -#. Order' -#: erpnext/manufacturing/doctype/bom/bom.json -#: erpnext/manufacturing/doctype/job_card/job_card.json -#: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.json -msgid "Scrap Items" -msgstr "" - -#. Label of the scrap_items_generated_section (Section Break) field in DocType -#. 'Subcontracting Inward Order' -#: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.json -msgid "Scrap Items Generated" -msgstr "" - -#. Label of the scrap_material_cost (Currency) field in DocType 'BOM' -#: erpnext/manufacturing/doctype/bom/bom.json -msgid "Scrap Material Cost" -msgstr "" - -#. Label of the base_scrap_material_cost (Currency) field in DocType 'BOM' -#: erpnext/manufacturing/doctype/bom/bom.json -msgid "Scrap Material Cost(Company Currency)" -msgstr "" - #. Label of the scrap_warehouse (Link) field in DocType 'Work Order' #: erpnext/manufacturing/doctype/work_order/work_order.json msgid "Scrap Warehouse" @@ -45594,6 +45683,50 @@ msgstr "" msgid "Second Email" msgstr "" +#. Label of the item_code (Link) field in DocType 'Job Card Secondary Item' +#: erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json +msgid "Secondary Item Code" +msgstr "" + +#. Label of the item_name (Data) field in DocType 'Job Card Secondary Item' +#: erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json +msgid "Secondary Item Name" +msgstr "" + +#. Label of the secondary_items (Table) field in DocType 'BOM' +#. Label of the secondary_items_tab (Tab Break) field in DocType 'BOM' +#. Label of the secondary_items (Table) field in DocType 'Job Card' +#. Label of the secondary_items_section (Tab Break) field in DocType 'Job Card' +#. Label of the secondary_items (Table) field in DocType 'Subcontracting Inward +#. Order' +#: erpnext/manufacturing/doctype/bom/bom.json +#: erpnext/manufacturing/doctype/job_card/job_card.json +#: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.json +msgid "Secondary Items" +msgstr "" + +#. Label of the secondary_items_cost (Currency) field in DocType 'BOM' +#: erpnext/manufacturing/doctype/bom/bom.json +msgid "Secondary Items Cost" +msgstr "" + +#. Label of the base_secondary_items_cost (Currency) field in DocType 'BOM' +#: erpnext/manufacturing/doctype/bom/bom.json +msgid "Secondary Items Cost (Company Currency)" +msgstr "" + +#. Label of the secondary_items_cost_per_qty (Currency) field in DocType +#. 'Subcontracting Receipt Item' +#: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +msgid "Secondary Items Cost Per Qty" +msgstr "" + +#. Label of the scrap_items_generated_section (Section Break) field in DocType +#. 'Subcontracting Inward Order' +#: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.json +msgid "Secondary Items Generated" +msgstr "" + #. Label of the secondary_party (Dynamic Link) field in DocType 'Party Link' #: erpnext/accounts/doctype/party_link/party_link.json msgid "Secondary Party" @@ -45643,7 +45776,7 @@ msgstr "" msgid "Select Accounting Dimension." msgstr "" -#: erpnext/public/js/utils.js:535 +#: erpnext/public/js/utils.js:539 msgid "Select Alternate Item" msgstr "" @@ -45697,7 +45830,7 @@ msgstr "" msgid "Select Company Address" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:541 +#: erpnext/manufacturing/doctype/job_card/job_card.js:539 msgid "Select Corrective Operation" msgstr "" @@ -45758,7 +45891,7 @@ msgstr "" msgid "Select Items based on Delivery Date" msgstr "" -#: erpnext/public/js/controllers/transaction.js:2965 +#: erpnext/public/js/controllers/transaction.js:2969 msgid "Select Items for Quality Inspection" msgstr "" @@ -45788,7 +45921,7 @@ msgstr "" msgid "Select Loyalty Program" msgstr "" -#: erpnext/public/js/controllers/transaction.js:473 +#: erpnext/public/js/controllers/transaction.js:477 msgid "Select Payment Schedule" msgstr "" @@ -45796,7 +45929,7 @@ msgstr "" msgid "Select Possible Supplier" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1043 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1044 #: erpnext/stock/doctype/pick_list/pick_list.js:216 msgid "Select Quantity" msgstr "" @@ -45917,7 +46050,7 @@ msgstr "" msgid "Select item group" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:434 +#: erpnext/manufacturing/doctype/bom/bom.js:473 msgid "Select template item" msgstr "" @@ -45930,11 +46063,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:1145 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1146 msgid "Select the Item to be manufactured." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:966 +#: erpnext/manufacturing/doctype/bom/bom.js:983 msgid "Select the Item to be manufactured. The Item name, UoM, Company, and Currency will be fetched automatically." msgstr "" @@ -45955,11 +46088,11 @@ msgstr "" msgid "Select the date and your timezone" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:985 +#: erpnext/manufacturing/doctype/bom/bom.js:1002 msgid "Select the raw materials (Items) required to manufacture the Item" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:489 +#: erpnext/manufacturing/doctype/bom/bom.js:528 msgid "Select variant item code for the template item {0}" msgstr "" @@ -46090,7 +46223,7 @@ msgstr "" #: erpnext/selling/doctype/selling_settings/selling_settings.json #: erpnext/selling/workspace/selling/selling.json #: erpnext/setup/workspace/erpnext_settings/erpnext_settings.json -#: erpnext/stock/doctype/stock_settings/stock_settings.py:260 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:258 #: erpnext/workspace_sidebar/erpnext_settings.json msgid "Selling Settings" msgstr "" @@ -46148,7 +46281,7 @@ msgid "Send Emails to Suppliers" msgstr "" #. Label of the send_sms (Button) field in DocType 'SMS Center' -#: erpnext/public/js/controllers/transaction.js:692 +#: erpnext/public/js/controllers/transaction.js:696 #: erpnext/selling/doctype/sms_center/sms_center.json msgid "Send SMS" msgstr "" @@ -46232,7 +46365,7 @@ msgstr "" msgid "Serial / Batch No" msgstr "" -#: erpnext/public/js/utils.js:197 +#: erpnext/public/js/utils.js:201 msgid "Serial / Batch Nos" msgstr "" @@ -46284,7 +46417,7 @@ msgstr "" #: erpnext/manufacturing/doctype/job_card/job_card.json #: erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js:74 #: erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py:114 -#: erpnext/public/js/controllers/transaction.js:2939 +#: erpnext/public/js/controllers/transaction.js:2943 #: erpnext/public/js/utils/serial_no_batch_selector.js:421 #: erpnext/selling/doctype/installation_note_item/installation_note_item.json #: erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -46345,7 +46478,7 @@ msgstr "" msgid "Serial No Range" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2601 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2639 msgid "Serial No Reserved" msgstr "" @@ -46431,7 +46564,7 @@ msgstr "" msgid "Serial No {0} does not exist" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3391 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3429 msgid "Serial No {0} does not exists" msgstr "" @@ -46485,11 +46618,11 @@ msgstr "" msgid "Serial Nos and Batches" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1887 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1925 msgid "Serial Nos are created successfully" msgstr "" -#: erpnext/stock/stock_ledger.py:2324 +#: erpnext/stock/stock_ledger.py:2331 msgid "Serial Nos are reserved in Stock Reservation Entries, you need to unreserve them before proceeding." msgstr "" @@ -46566,11 +46699,11 @@ msgstr "" msgid "Serial and Batch Bundle" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2109 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2147 msgid "Serial and Batch Bundle created" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2181 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2219 msgid "Serial and Batch Bundle updated" msgstr "" @@ -46943,12 +47076,12 @@ msgid "Service Stop Date" msgstr "" #: erpnext/accounts/deferred_revenue.py:45 -#: erpnext/public/js/controllers/transaction.js:1775 +#: erpnext/public/js/controllers/transaction.js:1779 msgid "Service Stop Date cannot be after Service End Date" msgstr "" #: erpnext/accounts/deferred_revenue.py:42 -#: erpnext/public/js/controllers/transaction.js:1772 +#: erpnext/public/js/controllers/transaction.js:1776 msgid "Service Stop Date cannot be before Service Start Date" msgstr "" @@ -46987,8 +47120,8 @@ msgstr "" msgid "Set Delivery Warehouse" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:412 -#: erpnext/manufacturing/doctype/job_card/job_card.js:481 +#: erpnext/manufacturing/doctype/job_card/job_card.js:410 +#: erpnext/manufacturing/doctype/job_card/job_card.js:479 msgid "Set Finished Good Quantity" msgstr "" @@ -47033,10 +47166,10 @@ msgstr "" msgid "Set New Release Date" msgstr "" -#. Label of the set_op_cost_and_scrap_from_sub_assemblies (Check) field in -#. DocType 'Manufacturing Settings' +#. Label of the set_op_cost_and_secondary_items_from_sub_assemblies (Check) +#. field in DocType 'Manufacturing Settings' #: erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json -msgid "Set Operating Cost / Scrap Items From Sub-assemblies" +msgid "Set Operating Cost / Secondary Items From Sub-assemblies" msgstr "" #. Label of the set_cost_based_on_bom_qty (Check) field in DocType 'BOM @@ -47054,7 +47187,7 @@ msgstr "" msgid "Set Posting Date" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:1012 +#: erpnext/manufacturing/doctype/bom/bom.js:1029 msgid "Set Process Loss Item Quantity" msgstr "" @@ -47177,7 +47310,7 @@ msgstr "" msgid "Set fieldname from which you want to fetch the data from the parent form." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:1002 +#: erpnext/manufacturing/doctype/bom/bom.js:1019 msgid "Set quantity of process loss item:" msgstr "" @@ -47193,7 +47326,7 @@ msgstr "" msgid "Set targets Item Group-wise for this Sales Person." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1202 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1203 msgid "Set the Planned Start Date (an Estimated Date at which you want the Production to begin)" msgstr "" @@ -47274,7 +47407,7 @@ msgstr "" msgid "Setting Item Locations..." msgstr "" -#: erpnext/setup/setup_wizard/setup_wizard.py:34 +#: erpnext/setup/setup_wizard/setup_wizard.py:25 msgid "Setting defaults" msgstr "" @@ -47284,11 +47417,11 @@ msgstr "" msgid "Setting the account as a Company Account is necessary for Bank Reconciliation" msgstr "" -#: erpnext/setup/setup_wizard/setup_wizard.py:29 +#: erpnext/setup/setup_wizard/setup_wizard.py:20 msgid "Setting up company" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1184 +#: erpnext/manufacturing/doctype/bom/bom.py:1217 #: erpnext/manufacturing/doctype/work_order/work_order.py:1475 msgid "Setting {0} is required" msgstr "" @@ -47707,7 +47840,7 @@ msgstr "" msgid "Show Barcode Field in Stock Transactions" msgstr "" -#: erpnext/accounts/report/general_ledger/general_ledger.js:192 +#: erpnext/accounts/report/general_ledger/general_ledger.js:193 msgid "Show Cancelled Entries" msgstr "" @@ -47715,7 +47848,7 @@ msgstr "" msgid "Show Completed" msgstr "" -#: erpnext/accounts/report/general_ledger/general_ledger.js:202 +#: erpnext/accounts/report/general_ledger/general_ledger.js:203 msgid "Show Credit / Debit in Company Currency" msgstr "" @@ -47798,7 +47931,7 @@ msgstr "" #. Label of the show_net_values_in_party_account (Check) field in DocType #. 'Process Statement Of Accounts' #: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json -#: erpnext/accounts/report/general_ledger/general_ledger.js:197 +#: erpnext/accounts/report/general_ledger/general_ledger.js:198 msgid "Show Net Values in Party Account" msgstr "" @@ -47806,7 +47939,7 @@ msgstr "" msgid "Show Open" msgstr "" -#: erpnext/accounts/report/general_ledger/general_ledger.js:181 +#: erpnext/accounts/report/general_ledger/general_ledger.js:182 msgid "Show Opening Entries" msgstr "" @@ -47839,7 +47972,7 @@ msgstr "" #: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json #: erpnext/accounts/report/accounts_payable/accounts_payable.js:125 #: erpnext/accounts/report/accounts_receivable/accounts_receivable.js:162 -#: erpnext/accounts/report/general_ledger/general_ledger.js:212 +#: erpnext/accounts/report/general_ledger/general_ledger.js:213 msgid "Show Remarks" msgstr "" @@ -48008,7 +48141,7 @@ msgstr "" msgid "Since there is a process loss of {0} units for the finished good {1}, you should reduce the quantity by {0} units for the finished good {1} in the Items Table." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:317 +#: erpnext/manufacturing/doctype/bom/bom.py:322 msgid "Since you have enabled 'Track Semi Finished Goods', at least one operation must have 'Is Final Finished Good' checked. For that set the FG / Semi FG Item as {0} against an operation." msgstr "" @@ -48189,7 +48322,7 @@ msgstr "" #. Label of the s_warehouse (Link) field in DocType 'Stock Entry Detail' #: erpnext/accounts/doctype/pos_invoice/pos_invoice.json #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json -#: erpnext/manufacturing/doctype/bom/bom.js:461 +#: erpnext/manufacturing/doctype/bom/bom.js:500 #: erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json #: erpnext/manufacturing/doctype/bom_item/bom_item.json #: erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -48382,7 +48515,7 @@ msgstr "" msgid "Stale Days" msgstr "" -#: erpnext/accounts/doctype/accounts_settings/accounts_settings.py:146 +#: erpnext/accounts/doctype/accounts_settings/accounts_settings.py:147 msgid "Stale Days should start from 1." msgstr "" @@ -48403,7 +48536,7 @@ msgstr "" #: erpnext/setup/setup_wizard/operations/defaults_setup.py:70 #: erpnext/setup/setup_wizard/operations/install_fixtures.py:493 #: erpnext/stock/doctype/item/item.py:267 erpnext/tests/utils.py:324 -#: erpnext/tests/utils.py:2514 +#: erpnext/tests/utils.py:2522 msgid "Standard Selling" msgstr "" @@ -48755,7 +48888,7 @@ msgstr "" msgid "Stock Entry Type" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:1475 +#: erpnext/stock/doctype/pick_list/pick_list.py:1508 msgid "Stock Entry has been already created against this Pick List" msgstr "" @@ -48763,7 +48896,7 @@ msgstr "" msgid "Stock Entry {0} created" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:1495 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1526 msgid "Stock Entry {0} has created" msgstr "" @@ -48919,6 +49052,7 @@ msgstr "" #. Label of the stock_qty (Float) field in DocType 'BOM Creator Item' #. Label of the stock_qty (Float) field in DocType 'BOM Explosion Item' #. Label of the stock_qty (Float) field in DocType 'BOM Item' +#. Label of the stock_qty (Float) field in DocType 'BOM Secondary Item' #. Label of the stock_qty (Float) field in DocType 'Delivery Schedule Item' #. Label of the stock_qty (Float) field in DocType 'Material Request Item' #: erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py:257 @@ -48926,6 +49060,7 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json #: erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json #: erpnext/manufacturing/doctype/bom_item/bom_item.json +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json #: erpnext/selling/doctype/delivery_schedule_item/delivery_schedule_item.json #: erpnext/stock/doctype/material_request_item/material_request_item.json #: erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.py:34 @@ -48996,9 +49131,9 @@ 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: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.js:921 +#: erpnext/manufacturing/doctype/work_order/work_order.js:930 +#: erpnext/manufacturing/doctype/work_order/work_order.js:937 #: 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:101 @@ -49019,9 +49154,9 @@ msgstr "" #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:1699 #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:1716 #: erpnext/stock/doctype/stock_settings/stock_settings.json -#: erpnext/stock/doctype/stock_settings/stock_settings.py:217 -#: erpnext/stock/doctype/stock_settings/stock_settings.py:229 -#: erpnext/stock/doctype/stock_settings/stock_settings.py:243 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:215 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:227 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:241 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:182 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:195 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:207 @@ -49034,8 +49169,8 @@ msgstr "" msgid "Stock Reservation Entries Cancelled" msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:1003 -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:2243 +#: erpnext/controllers/subcontracting_inward_controller.py:1018 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:2245 #: 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" @@ -49148,9 +49283,9 @@ msgstr "" #. Label of the stock_uom (Link) field in DocType 'BOM Creator Item' #. Label of the stock_uom (Link) field in DocType 'BOM Explosion Item' #. Label of the stock_uom (Link) field in DocType 'BOM Item' -#. Label of the stock_uom (Link) field in DocType 'BOM Scrap Item' +#. Label of the stock_uom (Link) field in DocType 'BOM Secondary Item' #. Label of the stock_uom (Link) field in DocType 'Job Card Item' -#. Label of the stock_uom (Link) field in DocType 'Job Card Scrap Item' +#. Label of the stock_uom (Link) field in DocType 'Job Card Secondary Item' #. Label of the stock_uom (Link) field in DocType 'Production Plan Sub Assembly #. Item' #. Label of the stock_uom (Link) field in DocType 'Work Order' @@ -49174,7 +49309,7 @@ msgstr "" #. Label of the stock_uom (Link) field in DocType 'Subcontracting Inward Order #. Received Item' #. Label of the stock_uom (Link) field in DocType 'Subcontracting Inward Order -#. Scrap Item' +#. Secondary Item' #. Label of the stock_uom (Link) field in DocType 'Subcontracting Order Item' #. Label of the stock_uom (Link) field in DocType 'Subcontracting Order #. Supplied Item' @@ -49195,9 +49330,9 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json #: erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json #: erpnext/manufacturing/doctype/bom_item/bom_item.json -#: erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json #: erpnext/manufacturing/doctype/job_card_item/job_card_item.json -#: erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json +#: erpnext/manufacturing/doctype/job_card_secondary_item/job_card_secondary_item.json #: erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json #: erpnext/manufacturing/doctype/work_order/work_order.json #: erpnext/manufacturing/doctype/work_order_item/work_order_item.json @@ -49221,7 +49356,7 @@ msgstr "" #: erpnext/stock/report/stock_ledger/stock_ledger.py:279 #: erpnext/subcontracting/doctype/subcontracting_inward_order_item/subcontracting_inward_order_item.json #: erpnext/subcontracting/doctype/subcontracting_inward_order_received_item/subcontracting_inward_order_received_item.json -#: erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.json +#: erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json #: erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json #: erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json #: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -49467,7 +49602,7 @@ msgstr "" #. Label of the operation (Link) field in DocType 'Job Card Time Log' #. Name of a DocType -#: erpnext/manufacturing/doctype/job_card/job_card.js:357 +#: erpnext/manufacturing/doctype/job_card/job_card.js:355 #: erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json #: erpnext/manufacturing/doctype/sub_operation/sub_operation.json msgid "Sub Operation" @@ -49679,8 +49814,8 @@ msgid "Subcontracting Inward Order Received Item" msgstr "" #. Name of a DocType -#: erpnext/subcontracting/doctype/subcontracting_inward_order_scrap_item/subcontracting_inward_order_scrap_item.json -msgid "Subcontracting Inward Order Scrap Item" +#: erpnext/subcontracting/doctype/subcontracting_inward_order_secondary_item/subcontracting_inward_order_secondary_item.json +msgid "Subcontracting Inward Order Secondary Item" msgstr "" #. Name of a DocType @@ -49743,7 +49878,7 @@ msgstr "" msgid "Subcontracting Order Supplied Item" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:920 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:924 msgid "Subcontracting Order {0} created." msgstr "" @@ -49832,8 +49967,8 @@ msgstr "" msgid "Subdivision" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:916 -#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:1047 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:920 +#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:1084 msgid "Submit Action Failed" msgstr "" @@ -50943,7 +51078,7 @@ msgstr "" msgid "Target Warehouse Reservation Error" msgstr "" -#: erpnext/controllers/subcontracting_inward_controller.py:230 +#: erpnext/controllers/subcontracting_inward_controller.py:232 msgid "Target Warehouse for Finished Good must be same as Finished Good Warehouse {1} in Work Order {2} linked to the Subcontracting Inward Order." msgstr "" @@ -51189,7 +51324,7 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json #: erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js:86 -#: erpnext/accounts/report/general_ledger/general_ledger.js:141 +#: erpnext/accounts/report/general_ledger/general_ledger.js:142 #: erpnext/accounts/report/purchase_register/purchase_register.py:192 #: erpnext/accounts/report/sales_register/sales_register.py:215 #: erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js:67 @@ -51432,7 +51567,7 @@ msgstr "" #. Detail' #: erpnext/accounts/doctype/item_wise_tax_detail/item_wise_tax_detail.json #: erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py:157 -#: erpnext/controllers/taxes_and_totals.py:1212 +#: erpnext/controllers/taxes_and_totals.py:1249 msgid "Taxable Amount" msgstr "" @@ -51638,7 +51773,7 @@ msgstr "" msgid "Television" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:413 +#: erpnext/manufacturing/doctype/bom/bom.js:452 msgid "Template Item" msgstr "" @@ -52013,11 +52148,11 @@ msgstr "" msgid "The Payment Term at row {0} is possibly a duplicate." msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:341 +#: erpnext/stock/doctype/pick_list/pick_list.py:342 msgid "The Pick List having Stock Reservation Entries cannot be updated. If you need to make changes, we recommend canceling the existing Stock Reservation Entries before updating the Pick List." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2609 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2621 msgid "The Process Loss Qty has reset as per job cards Process Loss Qty" msgstr "" @@ -52025,15 +52160,15 @@ msgstr "" msgid "The Sales Person is linked with {0}" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:207 +#: erpnext/stock/doctype/pick_list/pick_list.py:208 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:2598 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2636 msgid "The Serial No {0} is reserved against the {1} {2} and cannot be used for any other transaction." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1742 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1747 msgid "The Serial and Batch Bundle {0} is not valid for this transaction. The 'Type of Transaction' should be 'Outward' instead of 'Inward' in Serial and Batch Bundle {0}" msgstr "" @@ -52059,7 +52194,7 @@ msgstr "" msgid "The batch {0} is already reserved in {1} {2}. So, cannot proceed with the {3} {4}, which is created against the {5} {6}." msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:1301 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1319 msgid "The completed quantity {0} of an operation {1} cannot be greater than the completed quantity {2} of a previous operation {3}." msgstr "" @@ -52071,7 +52206,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:1150 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1151 msgid "The default BOM for that item will be fetched by the system. You can also change the BOM." msgstr "" @@ -52124,7 +52259,7 @@ msgstr "" msgid "The following assets have failed to automatically post depreciation entries: {0}" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:305 +#: erpnext/stock/doctype/pick_list/pick_list.py:306 msgid "The following batches are expired, please restock them:
{0}" msgstr "" @@ -52167,7 +52302,7 @@ msgstr "" msgid "The holiday on {0} is not between From Date and To Date" msgstr "" -#: erpnext/controllers/buying_controller.py:1244 +#: erpnext/controllers/buying_controller.py:1248 msgid "The item {item} is not marked as {type_of} item. You can enable it as {type_of} item from its Item master." msgstr "" @@ -52175,7 +52310,7 @@ msgstr "" msgid "The items {0} and {1} are present in the following {2} :" msgstr "" -#: erpnext/controllers/buying_controller.py:1237 +#: erpnext/controllers/buying_controller.py:1241 msgid "The items {items} are not marked as {type_of} item. You can enable them as {type_of} item from their Item masters." msgstr "" @@ -52257,7 +52392,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:947 +#: erpnext/public/js/utils.js:951 msgid "The reserved stock will be released when you update items. Are you certain you wish to proceed?" msgstr "" @@ -52310,7 +52445,7 @@ msgstr "" msgid "The shares don't exist with the {0}" msgstr "" -#: erpnext/stock/stock_ledger.py:806 +#: erpnext/stock/stock_ledger.py:813 msgid "The stock for the item {0} in the {1} warehouse was negative on the {2}. You should create a positive entry {3} before the date {4} and time {5} to post the correct valuation rate. For more details, please read the documentation." msgstr "" @@ -52376,23 +52511,23 @@ msgstr "" msgid "The value {0} is already assigned to an existing Item {1}." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1178 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1179 msgid "The warehouse where you store finished Items before they are shipped." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1171 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1172 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:1183 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1184 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 "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:874 +#: erpnext/manufacturing/doctype/job_card/job_card.py:892 msgid "The {0} ({1}) must be equal to {2} ({3})" msgstr "" -#: erpnext/public/js/controllers/transaction.js:3408 +#: erpnext/public/js/controllers/transaction.js:3412 msgid "The {0} contains Unit Price Items." msgstr "" @@ -52408,7 +52543,7 @@ msgstr "" msgid "The {0} {1} does not match with the {0} {2} in the {3} {4}" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:977 +#: erpnext/manufacturing/doctype/job_card/job_card.py:995 msgid "The {0} {1} is used to calculate the valuation cost for the finished good {2}." msgstr "" @@ -52432,7 +52567,7 @@ msgstr "" msgid "There are no Failed transactions" msgstr "" -#: erpnext/setup/demo.py:120 +#: erpnext/setup/demo.py:130 msgid "There are no active Fiscal Years for which Demo Data can be generated." msgstr "" @@ -52472,7 +52607,7 @@ msgstr "" msgid "There is no batch found against the {0}: {1}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1679 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1684 msgid "There must be atleast 1 Finished Good in this Stock Entry" msgstr "" @@ -52515,7 +52650,7 @@ msgstr "" msgid "This Month's Summary" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:929 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:933 msgid "This Purchase Order has been fully subcontracted." msgstr "" @@ -52561,7 +52696,7 @@ msgstr "" msgid "This invoice has already been paid." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:269 +#: erpnext/manufacturing/doctype/bom/bom.js:307 msgid "This is a Template BOM and will be used to make the work order for {0} of the item {1}" msgstr "" @@ -52638,7 +52773,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:1164 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1165 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 "" @@ -52855,7 +52990,7 @@ msgstr "" msgid "Time in mins." msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:853 +#: erpnext/manufacturing/doctype/job_card/job_card.py:871 msgid "Time logs are required for {0} {1}" msgstr "" @@ -53183,7 +53318,7 @@ msgstr "" msgid "To Warehouse (Optional)" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:980 +#: erpnext/manufacturing/doctype/bom/bom.js:997 msgid "To add Operations tick the 'With Operations' checkbox." msgstr "" @@ -53235,10 +53370,10 @@ msgstr "" msgid "To include non-stock items in the material request planning. i.e. Items for which 'Maintain Stock' checkbox is unticked." msgstr "" -#. Description of the 'Set Operating Cost / Scrap Items From Sub-assemblies' -#. (Check) field in DocType 'Manufacturing Settings' +#. Description of the 'Set Operating Cost / Secondary Items From +#. Sub-assemblies' (Check) field in DocType 'Manufacturing Settings' #: erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json -msgid "To include sub-assembly costs and scrap items in Finished Goods on a work order without using a job card, when the 'Use Multi-Level BOM' option is enabled." +msgid "To include sub-assembly costs and secondary items in Finished Goods on a work order without using a job card, when the 'Use Multi-Level BOM' option is enabled." msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.py:2247 @@ -53442,7 +53577,7 @@ msgstr "" msgid "Total Amount in Words" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:258 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:262 msgid "Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges" msgstr "" @@ -53527,12 +53662,12 @@ msgstr "" #. Label of the total_completed_qty (Float) field in DocType 'Job Card' #: erpnext/manufacturing/doctype/job_card/job_card.json -#: erpnext/manufacturing/doctype/job_card/job_card.py:870 +#: erpnext/manufacturing/doctype/job_card/job_card.py:888 #: erpnext/manufacturing/report/job_card_summary/job_card_summary.py:174 msgid "Total Completed Qty" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:188 +#: erpnext/manufacturing/doctype/job_card/job_card.py:190 msgid "Total Completed Qty is required for Job Card {0}, please start and complete the job card before submission" msgstr "" @@ -54021,7 +54156,7 @@ msgstr "" msgid "Total Time in Mins" msgstr "" -#: erpnext/public/js/utils.js:173 +#: erpnext/public/js/utils.js:177 msgid "Total Unpaid: {0}" msgstr "" @@ -54100,7 +54235,7 @@ msgstr "" msgid "Total allocated percentage for sales team should be 100" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:192 +#: erpnext/selling/doctype/customer/customer.py:193 msgid "Total contribution percentage should be equal to 100" msgstr "" @@ -54136,7 +54271,7 @@ msgstr "" msgid "Total {0} ({1})" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:239 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:243 msgid "Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'" msgstr "" @@ -54368,7 +54503,7 @@ msgstr "" msgid "Transaction from which tax is withheld" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:846 +#: erpnext/manufacturing/doctype/job_card/job_card.py:864 msgid "Transaction not allowed against stopped Work Order {0}" msgstr "" @@ -54724,6 +54859,7 @@ msgstr "" #. Label of the uom (Link) field in DocType 'BOM Creator' #. Label of the uom (Link) field in DocType 'BOM Creator Item' #. Label of the uom (Link) field in DocType 'BOM Item' +#. Label of the uom (Link) field in DocType 'BOM Secondary Item' #. Label of the uom (Link) field in DocType 'Job Card Item' #. Label of the uom (Link) field in DocType 'Master Production Schedule Item' #. Label of the uom (Link) field in DocType 'Material Request Plan Item' @@ -54772,6 +54908,7 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_creator/bom_creator.json #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json #: erpnext/manufacturing/doctype/bom_item/bom_item.json +#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json #: erpnext/manufacturing/doctype/job_card_item/job_card_item.json #: erpnext/manufacturing/doctype/master_production_schedule_item/master_production_schedule_item.json #: erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -54781,7 +54918,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:820 +#: erpnext/public/js/stock_analytics.js:94 erpnext/public/js/utils.js:824 #: 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 @@ -54808,7 +54945,7 @@ msgstr "" #: erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py:87 #: erpnext/stock/report/item_prices/item_prices.py:55 #: erpnext/stock/report/product_bundle_balance/product_bundle_balance.py:94 -#: erpnext/stock/report/stock_ageing/stock_ageing.py:178 +#: erpnext/stock/report/stock_ageing/stock_ageing.py:179 #: erpnext/stock/report/stock_analytics/stock_analytics.py:59 #: erpnext/stock/report/stock_projected_qty/stock_projected_qty.py:134 #: erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py:60 @@ -54877,7 +55014,7 @@ msgstr "" msgid "UOM Name" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3774 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3806 msgid "UOM conversion factor required for UOM: {0} in Item: {1}" msgstr "" @@ -55191,7 +55328,7 @@ msgstr "" msgid "Unreconciled Entries" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:927 +#: erpnext/manufacturing/doctype/work_order/work_order.js:928 #: 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 @@ -55372,7 +55509,7 @@ msgstr "" #. Option for the 'Update Type' (Select) field in DocType 'BOM Update Log' #. Label of the update_cost_section (Section Break) field in DocType 'BOM #. Update Tool' -#: erpnext/manufacturing/doctype/bom/bom.js:185 +#: erpnext/manufacturing/doctype/bom/bom.js:223 #: erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json #: erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json msgid "Update Cost" @@ -55399,7 +55536,7 @@ msgstr "" #: erpnext/buying/doctype/purchase_order/purchase_order.js:324 #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.js:43 -#: erpnext/public/js/utils.js:926 +#: erpnext/public/js/utils.js:930 #: erpnext/selling/doctype/quotation/quotation.js:135 #: erpnext/selling/doctype/sales_order/sales_order.js:82 #: erpnext/selling/doctype/sales_order/sales_order.js:940 @@ -55501,7 +55638,7 @@ msgstr "" msgid "Updating Variants..." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1126 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1127 msgid "Updating Work Order status" msgstr "" @@ -55622,7 +55759,7 @@ msgstr "" #. Label of the use_multi_level_bom (Check) field in DocType 'Work Order' #. Label of the use_multi_level_bom (Check) field in DocType 'Stock Entry' -#: erpnext/manufacturing/doctype/bom/bom.js:395 +#: erpnext/manufacturing/doctype/bom/bom.js:434 #: erpnext/manufacturing/doctype/work_order/work_order.json #: erpnext/stock/doctype/stock_entry/stock_entry.json msgid "Use Multi-Level BOM" @@ -56053,11 +56190,11 @@ msgstr "" msgid "Valuation Rate (In / Out)" msgstr "" -#: erpnext/stock/stock_ledger.py:2069 +#: erpnext/stock/stock_ledger.py:2076 msgid "Valuation Rate Missing" msgstr "" -#: erpnext/stock/stock_ledger.py:2047 +#: erpnext/stock/stock_ledger.py:2054 msgid "Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}." msgstr "" @@ -56101,7 +56238,7 @@ msgstr "" msgid "Value (G - D)" msgstr "" -#: erpnext/stock/report/stock_ageing/stock_ageing.py:221 +#: erpnext/stock/report/stock_ageing/stock_ageing.py:222 msgid "Value ({0})" msgstr "" @@ -56229,7 +56366,7 @@ msgstr "" msgid "Variant Attributes" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:226 +#: erpnext/manufacturing/doctype/bom/bom.js:264 msgid "Variant BOM" msgstr "" @@ -56251,8 +56388,8 @@ msgstr "" msgid "Variant Field" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:349 -#: erpnext/manufacturing/doctype/bom/bom.js:428 +#: erpnext/manufacturing/doctype/bom/bom.js:387 +#: erpnext/manufacturing/doctype/bom/bom.js:467 msgid "Variant Item" msgstr "" @@ -57006,7 +57143,7 @@ msgstr "" msgid "Warning - Row {0}: Billing Hours are more than Actual Hours" msgstr "" -#: erpnext/stock/stock_ledger.py:816 +#: erpnext/stock/stock_ledger.py:823 msgid "Warning on Negative Stock" msgstr "" @@ -57431,7 +57568,7 @@ msgstr "" #. Option for the 'From Voucher Type' (Select) field in DocType 'Stock #. Reservation Entry' #. Label of a Workspace Sidebar Item -#: erpnext/manufacturing/doctype/bom/bom.js:217 +#: erpnext/manufacturing/doctype/bom/bom.js:255 #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/job_card/job_card.json #: erpnext/manufacturing/doctype/work_order/work_order.json @@ -57527,8 +57664,8 @@ msgstr "" msgid "Work Order cannot be raised against a Item Template" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2456 -#: erpnext/manufacturing/doctype/work_order/work_order.py:2536 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2457 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2537 msgid "Work Order has been {0}" msgstr "" @@ -57882,7 +58019,7 @@ msgstr "" msgid "You are not authorized to set Frozen value" msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:512 +#: erpnext/stock/doctype/pick_list/pick_list.py:513 msgid "You are picking more than required quantity for the item {0}. Check if there is any other pick list created for the sales order {1}." msgstr "" @@ -57931,7 +58068,7 @@ msgstr "" msgid "You can use {0} to reconcile against {1} later." msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:1313 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1331 msgid "You can't make any changes to Job Card since Work Order is closed." msgstr "" @@ -57943,7 +58080,7 @@ msgstr "" msgid "You can't redeem Loyalty Points having more value than the Total Amount." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:736 +#: erpnext/manufacturing/doctype/bom/bom.js:773 msgid "You cannot change the rate if BOM is mentioned against any Item." msgstr "" @@ -57971,7 +58108,7 @@ msgstr "" msgid "You cannot edit root node." msgstr "" -#: erpnext/accounts/doctype/accounts_settings/accounts_settings.py:181 +#: erpnext/accounts/doctype/accounts_settings/accounts_settings.py:182 msgid "You cannot enable both the settings '{0}' and '{1}'." msgstr "" @@ -58003,7 +58140,7 @@ 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 +#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:565 msgid "You do not have permission to edit this document" msgstr "" @@ -58019,11 +58156,11 @@ msgstr "" msgid "You don't have enough points to redeem." msgstr "" -#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:286 +#: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py:291 msgid "You had {} errors while creating opening invoices. Check {} for more details" msgstr "" -#: erpnext/public/js/utils.js:1026 +#: erpnext/public/js/utils.js:1030 msgid "You have already selected items from {0} {1}" msgstr "" @@ -58031,7 +58168,7 @@ msgstr "" msgid "You have been invited to collaborate on the project {0}." msgstr "" -#: erpnext/stock/doctype/stock_settings/stock_settings.py:255 +#: erpnext/stock/doctype/stock_settings/stock_settings.py:253 msgid "You have enabled {0} and {1} in {2}. This can lead to prices from the default price list being inserted in the transaction price list." msgstr "" @@ -58127,7 +58264,7 @@ msgstr "" msgid "`Allow Negative rates for Items`" msgstr "" -#: erpnext/stock/stock_ledger.py:2061 +#: erpnext/stock/stock_ledger.py:2068 msgid "after" msgstr "" @@ -58143,11 +58280,11 @@ msgstr "" msgid "as Title" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:1004 +#: erpnext/manufacturing/doctype/bom/bom.js:1021 msgid "as a percentage of finished item quantity" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1518 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1556 msgid "as of {0}" msgstr "" @@ -58296,7 +58433,7 @@ msgstr "" msgid "paid to" msgstr "" -#: erpnext/public/js/utils.js:443 +#: erpnext/public/js/utils.js:447 msgid "payments app is not installed. Please install it from {0} or {1}" msgstr "" @@ -58317,7 +58454,7 @@ msgstr "" msgid "per hour" msgstr "" -#: erpnext/stock/stock_ledger.py:2062 +#: erpnext/stock/stock_ledger.py:2069 msgid "performing either one below:" msgstr "" @@ -58447,7 +58584,7 @@ msgstr "" msgid "{0} ({1}) cannot be greater than planned quantity ({2}) in Work Order {3}" msgstr "" -#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:381 +#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:385 msgid "{0} {1} has submitted Assets. Remove Item {2} from table to continue." msgstr "" @@ -58479,11 +58616,11 @@ msgstr "" msgid "{0} Number {1} is already used in {2} {3}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1638 +#: erpnext/manufacturing/doctype/bom/bom.py:1684 msgid "{0} Operating Cost for operation {1}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:555 +#: erpnext/manufacturing/doctype/work_order/work_order.js:556 msgid "{0} Operations: {1}" msgstr "" @@ -58562,7 +58699,7 @@ msgstr "" #: erpnext/manufacturing/doctype/production_plan/production_plan.py:923 #: erpnext/manufacturing/doctype/production_plan/production_plan.py:1039 -#: erpnext/stock/doctype/pick_list/pick_list.py:1297 +#: erpnext/stock/doctype/pick_list/pick_list.py:1330 #: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.py:322 msgid "{0} created" msgstr "" @@ -58668,11 +58805,11 @@ 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:1742 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1780 msgid "{0} is not a CSV file." msgstr "" -#: erpnext/selling/doctype/customer/customer.py:234 +#: erpnext/selling/doctype/customer/customer.py:235 msgid "{0} is not a company bank account" msgstr "" @@ -58716,27 +58853,27 @@ 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:520 +#: erpnext/manufacturing/doctype/work_order/work_order.js:521 msgid "{0} items disassembled" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:484 +#: erpnext/manufacturing/doctype/work_order/work_order.js:485 msgid "{0} items in progress" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:508 +#: erpnext/manufacturing/doctype/work_order/work_order.js:509 msgid "{0} items lost during process." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:465 +#: erpnext/manufacturing/doctype/work_order/work_order.js:466 msgid "{0} items produced" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:488 +#: erpnext/manufacturing/doctype/work_order/work_order.js:489 msgid "{0} items returned" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:491 +#: erpnext/manufacturing/doctype/work_order/work_order.js:492 msgid "{0} items to return" msgstr "" @@ -58748,7 +58885,7 @@ msgstr "" msgid "{0} not allowed to transact with {1}. Please change the Company or add the Company in the 'Allowed To Transact With'-Section in the Customer record." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:573 +#: erpnext/manufacturing/doctype/bom/bom.py:611 msgid "{0} not found for item {1}" msgstr "" @@ -58768,11 +58905,11 @@ msgstr "" msgid "{0} units are reserved for Item {1} in Warehouse {2}, please un-reserve the same to {3} the Stock Reconciliation." msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:1052 +#: erpnext/stock/doctype/pick_list/pick_list.py:1085 msgid "{0} units of Item {1} is not available in any of the warehouses." msgstr "" -#: erpnext/stock/doctype/pick_list/pick_list.py:1045 +#: erpnext/stock/doctype/pick_list/pick_list.py:1078 msgid "{0} units of Item {1} is not available in any of the warehouses. Other Pick Lists exist for this item." msgstr "" @@ -58780,16 +58917,16 @@ msgstr "" msgid "{0} units of {1} are required in {2} with the inventory dimension: {3} on {4} {5} for {6} to complete the transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:1714 erpnext/stock/stock_ledger.py:2210 -#: erpnext/stock/stock_ledger.py:2224 +#: erpnext/stock/stock_ledger.py:1721 erpnext/stock/stock_ledger.py:2217 +#: erpnext/stock/stock_ledger.py:2231 msgid "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:2311 erpnext/stock/stock_ledger.py:2356 +#: erpnext/stock/stock_ledger.py:2318 erpnext/stock/stock_ledger.py:2363 msgid "{0} units of {1} needed in {2} on {3} {4} to complete this transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:1708 +#: erpnext/stock/stock_ledger.py:1715 msgid "{0} units of {1} needed in {2} to complete this transaction." msgstr "" @@ -58817,7 +58954,7 @@ msgstr "" msgid "{0} will be set as the {1} in subsequently scanned items" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:986 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1004 msgid "{0} {1}" msgstr "" @@ -59015,8 +59152,8 @@ msgstr "" msgid "{0}'s {1} cannot be after {2}'s Expected End Date." msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.py:1285 -#: erpnext/manufacturing/doctype/job_card/job_card.py:1293 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1303 +#: erpnext/manufacturing/doctype/job_card/job_card.py:1311 msgid "{0}, complete the operation {1} before the operation {2}." msgstr "" @@ -59056,15 +59193,15 @@ msgstr "" msgid "{0}: {1} must be less than {2}" msgstr "" -#: erpnext/controllers/buying_controller.py:1019 +#: erpnext/controllers/buying_controller.py:1023 msgid "{count} Assets created for {item_code}" msgstr "" -#: erpnext/controllers/buying_controller.py:917 +#: erpnext/controllers/buying_controller.py:921 msgid "{doctype} {name} is cancelled or closed." msgstr "" -#: erpnext/controllers/buying_controller.py:628 +#: erpnext/controllers/buying_controller.py:632 msgid "{field_label} is mandatory for sub-contracted {doctype}." msgstr "" @@ -59072,7 +59209,7 @@ msgstr "" msgid "{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})" msgstr "" -#: erpnext/controllers/buying_controller.py:725 +#: erpnext/controllers/buying_controller.py:729 msgid "{ref_doctype} {ref_name} is {status}." msgstr "" From bc86e2c1f2fe81a4638babb0690937c9d2e7b421 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 5 Apr 2026 21:16:31 +0530 Subject: [PATCH 093/168] fix: update min date based on transaction_date (backport #53803) (#54025) Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com> fix: update min date based on transaction_date (#53803) --- erpnext/selling/doctype/sales_order/sales_order.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index ff22b0e4c2e..82926bd3855 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -63,6 +63,13 @@ frappe.ui.form.on("Sales Order", { }); } }, + transaction_date(frm) { + prevent_past_delivery_dates(frm); + frm.set_value("delivery_date", ""); + frm.doc.items.forEach((d) => { + frappe.model.set_value(d.doctype, d.name, "delivery_date", ""); + }); + }, refresh: function (frm) { frm.fields_dict["items"].grid.update_docfield_property( From 89e3e3c59e0a6e6ceaee5d3688018744dc4ec9cf Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 4 Apr 2026 12:47:43 +0530 Subject: [PATCH 094/168] fix: do not repost GL if no change in valuation (cherry picked from commit bb53cce22890fdffb7708d2c844ca858ed274b83) --- erpnext/stock/stock_ledger.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c9114355a58..f754cab7650 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -688,9 +688,6 @@ class update_entries_after: self._sles = deque(self.sort_sles(self._sles)) def repost_stock_ledger_entry(self, sle): - if self.args.item_code != sle.item_code or self.args.warehouse != sle.warehouse: - self.repost_affected_transaction.add((sle.voucher_type, sle.voucher_no)) - if isinstance(sle, dict): sle = frappe._dict(sle) @@ -953,6 +950,8 @@ class update_entries_after: sle.stock_value = self.wh_data.stock_value sle.stock_queue = json.dumps(self.wh_data.stock_queue) + old_stock_value_difference = sle.stock_value_difference + sle.stock_value_difference = stock_value_difference if ( @@ -986,6 +985,14 @@ class update_entries_after: ): self.update_outgoing_rate_on_transaction(sle) + if flt(old_stock_value_difference, self.currency_precision) == flt( + sle.stock_value_difference, self.currency_precision + ): + return + + if self.args.item_code != sle.item_code or self.args.warehouse != sle.warehouse: + self.repost_affected_transaction.add((sle.voucher_type, sle.voucher_no)) + def get_serialized_values(self, sle): from erpnext.stock.serial_batch_bundle import SerialNoValuation From ab08162f34ce2cc1f68316757fbabd0cd59d17a1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 05:42:53 +0000 Subject: [PATCH 095/168] fix: show current stock qty in Stock Entry PDF (backport #53761) (#54032) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 19c00ceacea..8078abb2848 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -201,6 +201,13 @@ class StockEntry(StockController, SubcontractingInwardController): ) def onload(self): + self.update_items_from_bin_details() + + def before_print(self, settings=None): + super().before_print(settings) + self.update_items_from_bin_details() + + def update_items_from_bin_details(self): for item in self.get("items"): item.update(get_bin_details(item.item_code, item.s_warehouse or item.t_warehouse)) From 8941699a34aa07103dd8e469ecc95b1aec26adb5 Mon Sep 17 00:00:00 2001 From: Sagar Vora <16315650+sagarvora@users.noreply.github.com> Date: Mon, 6 Apr 2026 12:33:49 +0530 Subject: [PATCH 096/168] fix: skip discount amount validation when not saving (cherry picked from commit 09755833881742fe6f14bddd3c215f5029ab1d28) --- erpnext/controllers/taxes_and_totals.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index ecab30b5013..7f41e8476ea 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -825,7 +825,8 @@ class calculate_taxes_and_totals: discount_amount += total_return_discount # validate that discount amount cannot exceed the total before discount - if ( + # only during save (i.e. when `_action` is set) + if self.doc.get("_action") and ( (grand_total >= 0 and discount_amount > grand_total) or (grand_total < 0 and discount_amount < grand_total) # returns ): From 9bc0e3b2ce28ffafb2079166a52394205d93bda1 Mon Sep 17 00:00:00 2001 From: Sagar Vora <16315650+sagarvora@users.noreply.github.com> Date: Mon, 6 Apr 2026 12:37:32 +0530 Subject: [PATCH 097/168] test: add test for discount amount on partial purchase receipt Co-authored-by: ravibharathi656 <131471282+ravibharathi656@users.noreply.github.com> (cherry picked from commit 135cb5fd670ddd2aa1642282fd5aad300fd006ff) --- .../purchase_order/test_purchase_order.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index e6956111ea0..da06d3fa04a 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -291,6 +291,30 @@ class TestPurchaseOrder(ERPNextTestSuite): # ordered qty should decrease (back to initial) on row deletion self.assertEqual(get_ordered_qty(), existing_ordered_qty) + def test_discount_amount_partial_purchase_receipt(self): + po = create_purchase_order(qty=4, rate=100, do_not_save=1) + po.apply_discount_on = "Grand Total" + po.discount_amount = 120 + po.save() + po.submit() + + self.assertEqual(po.grand_total, 280) + + pr1 = make_purchase_receipt(po.name) + pr1.items[0].qty = 3 + pr1.save() + pr1.submit() + + self.assertEqual(pr1.discount_amount, 120) + self.assertEqual(pr1.grand_total, 180) + + pr2 = make_purchase_receipt(po.name) + pr2.save() + pr2.submit() + + self.assertEqual(pr2.discount_amount, 0) + self.assertEqual(pr2.grand_total, 100) + def test_update_child_perm(self): po = create_purchase_order(item_code="_Test Item", qty=4) From cd983120834802b5a67c30fb0309183c14094f63 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Mon, 6 Apr 2026 14:35:01 +0530 Subject: [PATCH 098/168] fix: print hide unnecessary fields (cherry picked from commit 8f83616b60df061b75d5dc57cd35dfa0b1e54e3a) # Conflicts: # erpnext/selling/doctype/sales_order_item/sales_order_item.json --- .../purchase_invoice_item.json | 41 +++++++++++++------ .../sales_invoice_item.json | 14 +++++-- .../purchase_order_item.json | 18 +++++--- .../sales_order_item/sales_order_item.json | 22 ++++++++-- .../purchase_receipt_item.json | 26 ++++++++---- 5 files changed, 88 insertions(+), 33 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 00951fdd4cc..7a55a5eb141 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -190,6 +190,7 @@ "fieldtype": "Float", "label": "Received Qty", "no_copy": 1, + "print_hide": 1, "read_only": 1 }, { @@ -206,7 +207,8 @@ { "fieldname": "rejected_qty", "fieldtype": "Float", - "label": "Rejected Qty" + "label": "Rejected Qty", + "print_hide": 1 }, { "depends_on": "eval:doc.uom != doc.stock_uom", @@ -226,6 +228,7 @@ "fieldtype": "Link", "label": "UOM", "options": "UOM", + "print_hide": 1, "reqd": 1 }, { @@ -261,14 +264,16 @@ "depends_on": "price_list_rate", "fieldname": "discount_percentage", "fieldtype": "Percent", - "label": "Discount on Price List Rate (%)" + "label": "Discount on Price List Rate (%)", + "print_hide": 1 }, { "depends_on": "price_list_rate", "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Discount Amount", - "options": "currency" + "options": "currency", + "print_hide": 1 }, { "fieldname": "col_break3", @@ -401,12 +406,14 @@ { "fieldname": "weight_per_unit", "fieldtype": "Float", - "label": "Weight Per Unit" + "label": "Weight Per Unit", + "print_hide": 1 }, { "fieldname": "total_weight", "fieldtype": "Float", "label": "Total Weight", + "print_hide": 1, "read_only": 1 }, { @@ -417,7 +424,8 @@ "fieldname": "weight_uom", "fieldtype": "Link", "label": "Weight UOM", - "options": "UOM" + "options": "UOM", + "print_hide": 1 }, { "depends_on": "eval:parent.update_stock", @@ -429,7 +437,8 @@ "fieldname": "warehouse", "fieldtype": "Link", "label": "Accepted Warehouse", - "options": "Warehouse" + "options": "Warehouse", + "print_hide": 1 }, { "fieldname": "rejected_warehouse", @@ -674,7 +683,8 @@ "fieldname": "asset_location", "fieldtype": "Link", "label": "Asset Location", - "options": "Location" + "options": "Location", + "print_hide": 1 }, { "fieldname": "po_detail", @@ -796,6 +806,7 @@ "fieldtype": "Link", "label": "Asset Category", "options": "Asset Category", + "print_hide": 1, "read_only": 1 }, { @@ -828,6 +839,7 @@ "label": "Rate of Stock UOM", "no_copy": 1, "options": "currency", + "print_hide": 1, "read_only": 1 }, { @@ -866,6 +878,7 @@ "fieldtype": "Currency", "label": "Rate With Margin", "options": "currency", + "print_hide": 1, "read_only": 1 }, { @@ -892,7 +905,8 @@ "default": "1", "fieldname": "apply_tds", "fieldtype": "Check", - "label": "Consider for Tax Withholding" + "label": "Consider for Tax Withholding", + "print_hide": 1 }, { "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1", @@ -918,7 +932,8 @@ "fieldname": "wip_composite_asset", "fieldtype": "Link", "label": "WIP Composite Asset", - "options": "Asset" + "options": "Asset", + "print_hide": 1 }, { "depends_on": "eval:doc.use_serial_batch_fields === 0 && doc.docstatus === 0", @@ -930,7 +945,8 @@ "default": "0", "fieldname": "use_serial_batch_fields", "fieldtype": "Check", - "label": "Use Serial No / Batch Fields" + "label": "Use Serial No / Batch Fields", + "print_hide": 1 }, { "depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1", @@ -977,7 +993,8 @@ "fieldname": "distributed_discount_amount", "fieldtype": "Currency", "label": "Distributed Discount Amount", - "options": "currency" + "options": "currency", + "print_hide": 1 }, { "fieldname": "tax_withholding_category", @@ -991,7 +1008,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2026-02-15 21:07:49.455930", + "modified": "2026-03-25 18:03:33.522195", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index c90e1ff42d2..cd18994d0c5 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -207,6 +207,7 @@ "fieldtype": "Link", "label": "Stock UOM", "options": "UOM", + "print_hide": 1, "read_only": 1 }, { @@ -310,7 +311,8 @@ "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Discount Amount", - "options": "currency" + "options": "currency", + "print_hide": 1 }, { "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount", @@ -853,6 +855,7 @@ "fieldtype": "Currency", "label": "Rate of Stock UOM", "no_copy": 1, + "print_hide": 1, "options": "currency", "read_only": 1 }, @@ -869,6 +872,7 @@ "fieldname": "grant_commission", "fieldtype": "Check", "label": "Grant Commission", + "print_hide": 1, "read_only": 1 }, { @@ -926,7 +930,8 @@ "default": "0", "fieldname": "use_serial_batch_fields", "fieldtype": "Check", - "label": "Use Serial No / Batch Fields" + "label": "Use Serial No / Batch Fields", + "print_hide": 1 }, { "depends_on": "eval:doc.use_serial_batch_fields === 1 && parent.update_stock === 1", @@ -941,7 +946,8 @@ "fieldname": "distributed_discount_amount", "fieldtype": "Currency", "label": "Distributed Discount Amount", - "options": "currency" + "options": "currency", + "print_hide": 1 }, { "fieldname": "available_quantity_section", @@ -1010,7 +1016,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2026-02-23 14:37:14.853941", + "modified": "2026-02-24 14:37:16.853941", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 5e5eb7fd55b..2337d6a9fb6 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -280,14 +280,16 @@ "depends_on": "price_list_rate", "fieldname": "discount_percentage", "fieldtype": "Percent", - "label": "Discount on Price List Rate (%)" + "label": "Discount on Price List Rate (%)", + "print_hide": 1 }, { "depends_on": "price_list_rate", "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Discount Amount", - "options": "currency" + "options": "currency", + "print_hide": 1 }, { "fieldname": "col_break3", @@ -428,6 +430,7 @@ "fieldname": "weight_per_unit", "fieldtype": "Float", "label": "Weight Per Unit", + "print_hide": 1, "read_only": 1 }, { @@ -763,6 +766,7 @@ "label": "Rate of Stock UOM", "no_copy": 1, "options": "currency", + "print_hide": 1, "read_only": 1 }, { @@ -779,6 +783,7 @@ "fieldtype": "Float", "label": "Available Qty at Company", "no_copy": 1, + "print_hide": 1, "read_only": 1 }, { @@ -878,7 +883,8 @@ "fieldname": "fg_item_qty", "fieldtype": "Float", "label": "Finished Good Qty", - "mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow" + "mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow", + "print_hide": 1 }, { "depends_on": "eval:parent.is_internal_supplier", @@ -923,7 +929,8 @@ "fieldname": "distributed_discount_amount", "fieldtype": "Currency", "label": "Distributed Discount Amount", - "options": "currency" + "options": "currency", + "print_hide": 1 }, { "allow_on_submit": 1, @@ -934,6 +941,7 @@ "label": "Subcontracted Quantity", "no_copy": 1, "non_negative": 1, + "print_hide": 1, "read_only": 1 } ], @@ -942,7 +950,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-10-30 16:51:56.761673", + "modified": "2025-11-30 16:51:57.761673", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index d98dc8dccc4..af82501dec1 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -343,7 +343,8 @@ "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Discount Amount", - "options": "currency" + "options": "currency", + "print_hide": 1 }, { "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount", @@ -503,12 +504,14 @@ { "fieldname": "weight_per_unit", "fieldtype": "Float", - "label": "Weight Per Unit" + "label": "Weight Per Unit", + "print_hide": 1 }, { "fieldname": "total_weight", "fieldtype": "Float", "label": "Total Weight", + "print_hide": 1, "read_only": 1 }, { @@ -822,6 +825,7 @@ "label": "Rate of Stock UOM", "no_copy": 1, "options": "currency", + "print_hide": 1, "read_only": 1 }, { @@ -830,6 +834,7 @@ "fieldname": "grant_commission", "fieldtype": "Check", "label": "Grant Commission", + "print_hide": 1, "read_only": 1 }, { @@ -837,6 +842,7 @@ "fieldtype": "Float", "label": "Picked Qty (in Stock UOM)", "no_copy": 1, + "print_hide": 1, "read_only": 1 }, { @@ -910,6 +916,7 @@ "fieldtype": "Float", "label": "Production Plan Qty", "no_copy": 1, + "print_hide": 1, "read_only": 1 }, { @@ -926,7 +933,8 @@ "fieldname": "distributed_discount_amount", "fieldtype": "Currency", "label": "Distributed Discount Amount", - "options": "currency" + "options": "currency", + "print_hide": 1 }, { "allow_on_submit": 1, @@ -995,6 +1003,7 @@ "label": "Subcontracted Quantity", "no_copy": 1, "non_negative": 1, + "print_hide": 1, "read_only": 1 }, { @@ -1010,7 +1019,8 @@ "fieldname": "fg_item_qty", "fieldtype": "Float", "label": "Finished Good Qty", - "mandatory_depends_on": "eval:parent.is_subcontracted" + "mandatory_depends_on": "eval:parent.is_subcontracted", + "print_hide": 1 }, { "fieldname": "requested_qty", @@ -1025,7 +1035,11 @@ "idx": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2026-02-21 16:39:00.200328", +======= + "modified": "2026-02-22 16:40:00.200328", +>>>>>>> 8f83616b60 (fix: print hide unnecessary fields) "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index a01a4841e49..e388199c361 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -334,7 +334,8 @@ "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Discount Amount", - "options": "currency" + "options": "currency", + "print_hide": 1 }, { "fieldname": "col_break3", @@ -470,12 +471,14 @@ { "fieldname": "weight_per_unit", "fieldtype": "Float", - "label": "Weight Per Unit" + "label": "Weight Per Unit", + "print_hide": 1 }, { "fieldname": "total_weight", "fieldtype": "Float", "label": "Total Weight", + "print_hide": 1, "read_only": 1 }, { @@ -783,7 +786,8 @@ "fieldname": "expense_account", "fieldtype": "Link", "label": "Expense Account", - "options": "Account" + "options": "Account", + "print_hide": 1 }, { "fieldname": "accounting_dimensions_section", @@ -820,7 +824,8 @@ "fieldname": "asset_location", "fieldtype": "Link", "label": "Asset Location", - "options": "Location" + "options": "Location", + "print_hide": 1 }, { "depends_on": "is_fixed_asset", @@ -829,6 +834,7 @@ "fieldtype": "Link", "label": "Asset Category", "options": "Asset Category", + "print_hide": 1, "read_only": 1 }, { @@ -898,6 +904,7 @@ "label": "Rate of Stock UOM", "no_copy": 1, "options": "currency", + "print_hide": 1, "read_only": 1 }, { @@ -949,7 +956,8 @@ "fieldname": "base_rate_with_margin", "fieldtype": "Currency", "label": "Rate With Margin (Company Currency)", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "print_hide": 1 }, { "fieldname": "purchase_invoice", @@ -1103,7 +1111,8 @@ "default": "0", "fieldname": "use_serial_batch_fields", "fieldtype": "Check", - "label": "Use Serial No / Batch Fields" + "label": "Use Serial No / Batch Fields", + "print_hide": 1 }, { "default": "0", @@ -1126,7 +1135,8 @@ "fieldname": "distributed_discount_amount", "fieldtype": "Currency", "label": "Distributed Discount Amount", - "options": "currency" + "options": "currency", + "print_hide": 1 }, { "fieldname": "amount_difference_with_purchase_invoice", @@ -1140,7 +1150,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2026-02-04 14:42:10.646809", + "modified": "2026-02-07 14:42:11.646809", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 66ee208cb2d63dd5052e8a7017dcdd8c968af248 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 6 Apr 2026 16:19:53 +0530 Subject: [PATCH 099/168] fix: conflicts --- .../selling/doctype/sales_order_item/sales_order_item.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index af82501dec1..0f043a73fa4 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -1035,11 +1035,7 @@ "idx": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2026-02-21 16:39:00.200328", -======= "modified": "2026-02-22 16:40:00.200328", ->>>>>>> 8f83616b60 (fix: print hide unnecessary fields) "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", From 5719992cdaf836762e80ee14177f9ae9b1d2bbc9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 6 Apr 2026 14:15:03 +0530 Subject: [PATCH 100/168] fix: GL entries for different exchange rate in the purchase invoice (cherry picked from commit a953709640259ab53c2c009afe86189884eff9b8) --- .../purchase_invoice/purchase_invoice.py | 11 +- .../purchase_invoice/test_purchase_invoice.py | 13 ++- .../buying/doctype/supplier/test_supplier.py | 9 ++ .../purchase_receipt/purchase_receipt.py | 102 ++++++++++++++---- .../purchase_receipt/test_purchase_receipt.py | 67 ++++++++++++ 5 files changed, 178 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7c076e197a5..8b561730de0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -983,6 +983,10 @@ class PurchaseInvoice(BuyingController): if provisional_accounting_for_non_stock_items: self.get_provisional_accounts() + adjust_incoming_rate = frappe.db.get_single_value( + "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate" + ) + for item in self.get("items"): if flt(item.base_net_amount) or (self.get("update_stock") and item.valuation_rate): if item.item_code: @@ -1161,7 +1165,11 @@ class PurchaseInvoice(BuyingController): ) # check if the exchange rate has changed - if item.get("purchase_receipt") and self.auto_accounting_for_stock: + if ( + not adjust_incoming_rate + and item.get("purchase_receipt") + and self.auto_accounting_for_stock + ): if ( exchange_rate_map[item.purchase_receipt] and self.conversion_rate != exchange_rate_map[item.purchase_receipt] @@ -1198,6 +1206,7 @@ class PurchaseInvoice(BuyingController): item=item, ) ) + if ( self.auto_accounting_for_stock and self.is_opening == "No" diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 09febdfd915..b42574ee206 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -350,6 +350,12 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): make_purchase_invoice as create_purchase_invoice, ) + original_value = frappe.db.get_single_value( + "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate" + ) + + frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0) + pr = make_purchase_receipt( company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", @@ -368,14 +374,19 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): # fetching the latest GL Entry with exchange gain and loss account account amount = frappe.db.get_value( - "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "credit" + "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit" ) + discrepancy_caused_by_exchange_rate_diff = abs( pi.items[0].base_net_amount - pr.items[0].base_net_amount ) self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) + frappe.db.set_single_value( + "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", original_value + ) + def test_purchase_invoice_with_exchange_rate_difference_for_non_stock_item(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as create_purchase_invoice, diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index 663a7b48e46..6a7675ffba9 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -167,6 +167,15 @@ def create_supplier(**args): if not args.without_supplier_group: doc.supplier_group = args.supplier_group or "Services" + if args.get("party_account"): + doc.append( + "accounts", + { + "company": frappe.db.get_value("Account", args.get("party_account"), "company"), + "account": args.get("party_account"), + }, + ) + doc.insert() return doc diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index dc8885b1ca4..dfa30796996 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1259,11 +1259,11 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate total_amount, total_billed_amount, pi_landed_cost_amount = 0, 0, 0 item_wise_returned_qty = get_item_wise_returned_qty(pr_doc) + billed_qty_amt = frappe._dict() if adjust_incoming_rate: - item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc) - - billed_qty_based_on_po = get_billed_qty_against_purchase_order(pr_doc) + billed_qty_amt = get_billed_qty_amount_against_purchase_receipt(pr_doc) + billed_qty_amt_based_on_po = get_billed_qty_amount_against_purchase_order(pr_doc) for item in pr_doc.items: returned_qty = flt(item_wise_returned_qty.get(item.name)) @@ -1293,22 +1293,46 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate item.billed_amt is not None and item.amount is not None and ( - item_wise_billed_qty.get(item.name) - or billed_qty_based_on_po.get(item.purchase_order_item) + billed_qty_amt.get(item.name) or billed_qty_amt_based_on_po.get(item.purchase_order_item) ) ): - qty = item_wise_billed_qty.get(item.name) - if not qty: - if item.qty < billed_qty_based_on_po.get(item.purchase_order_item): + qty = None + if billed_qty_amt.get(item.name): + qty = billed_qty_amt.get(item.name).get("qty") + + if not qty and billed_qty_amt_based_on_po.get(item.purchase_order_item): + if item.qty < billed_qty_amt_based_on_po.get(item.purchase_order_item)["qty"]: qty = item.qty else: - qty = billed_qty_based_on_po.get(item.purchase_order_item) + qty = billed_qty_amt_based_on_po.get(item.purchase_order_item)["qty"] - billed_qty_based_on_po[item.purchase_order_item] -= qty + billed_qty_amt_based_on_po[item.purchase_order_item]["qty"] -= qty - adjusted_amt = (flt(item.billed_amt / qty) - flt(item.rate)) * item.qty + billed_amt = item.billed_amt + if billed_qty_amt.get(item.name): + billed_amt = flt(billed_qty_amt.get(item.name).get("amount")) + elif billed_qty_amt_based_on_po.get(item.purchase_order_item): + total_billed_qty = ( + billed_qty_amt_based_on_po.get(item.purchase_order_item).get("qty") + qty + ) - adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount")) + if total_billed_qty: + billed_amt = flt( + flt(billed_qty_amt_based_on_po.get(item.purchase_order_item).get("amount")) + * (qty / total_billed_qty) + ) + else: + billed_amt = 0.0 + + # Reduce billed amount based on PO for next iterations + billed_qty_amt_based_on_po[item.purchase_order_item]["amount"] -= billed_amt + + if qty: + adjusted_amt = ( + flt(billed_amt / qty) - (flt(item.rate) * flt(pr_doc.conversion_rate)) + ) * item.qty + + adjusted_amt = flt(adjusted_amt, item.precision("amount")) pi_landed_cost_amount += adjusted_amt item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False) elif amount and item.billed_amt > amount: @@ -1337,23 +1361,40 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate adjust_incoming_rate_for_pr(pr_doc) -def get_billed_qty_against_purchase_receipt(pr_doc): +def get_billed_qty_amount_against_purchase_receipt(pr_doc): pr_names = [d.name for d in pr_doc.items] + parent_table = frappe.qb.DocType("Purchase Invoice") table = frappe.qb.DocType("Purchase Invoice Item") query = ( - frappe.qb.from_(table) - .select(table.pr_detail, fn.Sum(table.qty).as_("qty")) + frappe.qb.from_(parent_table) + .inner_join(table) + .on(parent_table.name == table.parent) + .select( + table.pr_detail, + fn.Sum(table.amount * parent_table.conversion_rate).as_("amount"), + fn.Sum(table.qty).as_("qty"), + ) .where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1)) .groupby(table.pr_detail) ) - invoice_data = query.run(as_list=1) + invoice_data = query.run(as_dict=1) if not invoice_data: return frappe._dict() - return frappe._dict(invoice_data) + + billed_qty_amt = frappe._dict() + + for row in invoice_data: + if row.pr_detail not in billed_qty_amt: + billed_qty_amt[row.pr_detail] = {"amount": 0, "qty": 0} + + billed_qty_amt[row.pr_detail]["amount"] += flt(row.amount) + billed_qty_amt[row.pr_detail]["qty"] += flt(row.qty) + + return billed_qty_amt -def get_billed_qty_against_purchase_order(pr_doc): +def get_billed_qty_amount_against_purchase_order(pr_doc): po_names = list( set( [ @@ -1366,15 +1407,32 @@ def get_billed_qty_against_purchase_order(pr_doc): invoice_data_po_based = frappe._dict() if po_names: + parent_table = frappe.qb.DocType("Purchase Invoice") table = frappe.qb.DocType("Purchase Invoice Item") + query = ( - frappe.qb.from_(table) - .select(table.po_detail, fn.Sum(table.qty).as_("qty")) + frappe.qb.from_(parent_table) + .inner_join(table) + .on(parent_table.name == table.parent) + .select( + table.po_detail, + fn.Sum(table.qty).as_("qty"), + fn.Sum(table.amount * parent_table.conversion_rate).as_("amount"), + ) .where((table.po_detail.isin(po_names)) & (table.docstatus == 1) & (table.pr_detail.isnull())) .groupby(table.po_detail) ) - invoice_data_po_based = query.run(as_list=1) - invoice_data_po_based = frappe._dict(invoice_data_po_based) + + invoice_data = query.run(as_dict=1) + if not invoice_data: + return frappe._dict() + + for row in invoice_data: + if row.po_detail not in invoice_data_po_based: + invoice_data_po_based[row.po_detail] = {"amount": 0, "qty": 0} + + invoice_data_po_based[row.po_detail]["amount"] += flt(row.amount) + invoice_data_po_based[row.po_detail]["qty"] += flt(row.qty) return invoice_data_po_based diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 50f28a75b18..828ad603d8e 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -5450,6 +5450,70 @@ class TestPurchaseReceipt(ERPNextTestSuite): self.assertEqual(pr.total_qty, 12) self.assertEqual(pr.total, 120) + def test_different_exchange_rate_in_pr_and_pi(self): + from erpnext.accounts.doctype.account.test_account import create_account + + original_value = frappe.db.get_single_value( + "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate" + ) + + frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1) + + party_account = create_account( + account_name="USD Party Account Creditors", + parent_account="Accounts Payable - TCP1", + account_type="Payable", + company="_Test Company with perpetual inventory", + account_currency="USD", + ) + + supplier = create_supplier( + supplier_name="_Test USD Supplier New 1", default_currency="USD", party_account=party_account + ).name + item_code = make_item("Test Item for Different Exchange Rate", {"is_stock_item": 1}).name + + pr = make_purchase_receipt( + item_code=item_code, + qty=1, + currency="USD", + conversion_rate=80, + rate=100, + company="_Test Company with perpetual inventory", + warehouse=frappe.get_value( + "Warehouse", {"company": "_Test Company with perpetual inventory"}, "name" + ), + supplier=supplier, + ) + + self.assertEqual(pr.currency, "USD") + self.assertEqual(pr.conversion_rate, 80) + + gl_entries = get_gl_entries(pr.doctype, pr.name) + self.assertTrue(len(gl_entries) == 2) + for row in gl_entries: + amount = row.credit or row.debit + self.assertEqual(amount, 8000.0) + + pi = make_purchase_invoice(pr.name) + pi.conversion_rate = 90 + pi.currency = "USD" + + pi.save() + pi.submit() + + gl_entries = get_gl_entries(pi.doctype, pi.name) + self.assertTrue(len(gl_entries) == 2) + + accounts = ["USD Party Account Creditors - TCP1", "Stock Received But Not Billed - TCP1"] + for row in gl_entries: + amount = row.credit or row.debit + self.assertEqual(amount, 9000.0) + self.assertTrue(row.account in accounts) + + frappe.db.set_single_value( + "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", original_value + ) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier @@ -5620,6 +5684,9 @@ def make_purchase_receipt(**args): pr.return_against = args.return_against pr.apply_putaway_rule = args.apply_putaway_rule + if args.get("conversion_rate") is not None: + pr.conversion_rate = args.conversion_rate + qty = args.qty if args.qty is not None else 5 rejected_qty = args.rejected_qty or 0 received_qty = args.received_qty or flt(rejected_qty) + flt(qty) From 273caa38d99d90b9da6a3ed1b62108afc298336d Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 1 Apr 2026 08:43:56 +0530 Subject: [PATCH 101/168] fix(ux): refresh grid to correctly persist the state of fields (cherry picked from commit da778edf488a5a555d32b4cb400b0d327ff430a2) --- .../doctype/work_order/work_order.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 18b5be64c10..7a964a76231 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -244,13 +244,16 @@ frappe.ui.form.on("Work Order", { }, toggle_items_editable(frm) { - if (!frm.doc.__onload?.allow_editing_items) { - frm.set_df_property("required_items", "cannot_delete_rows", true); - frm.set_df_property("required_items", "cannot_add_rows", true); - frm.fields_dict["required_items"].grid.update_docfield_property("item_code", "read_only", 1); - frm.fields_dict["required_items"].grid.update_docfield_property("required_qty", "read_only", 1); - frm.fields_dict["required_items"].grid.refresh(); - } + let allow_edit = true; + if (!frm.doc.__onload?.allow_editing_items) allow_edit = false; + + frm.set_df_property("required_items", "cannot_delete_rows", !allow_edit); + frm.set_df_property("required_items", "cannot_add_rows", !allow_edit); + + const grid = frm.fields_dict["required_items"].grid; + grid.update_docfield_property("item_code", "read_only", !allow_edit); + grid.update_docfield_property("required_qty", "read_only", !allow_edit); + grid.refresh(); }, hide_reserve_stock_button(frm) { From 84382db5ca1911f40b8dec904477686ccc29cdff Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 12:00:04 +0000 Subject: [PATCH 102/168] fix: remove title field from purchase receipt (backport #54051) (#54065) Co-authored-by: Mihir Kandoi fix: remove title field from purchase receipt (#54051) --- .../purchase_receipt/purchase_receipt.json | 17 +++-------------- .../purchase_receipt/purchase_receipt.py | 1 - 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 805b7eef9e6..82745b34bbf 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -3,7 +3,7 @@ "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", - "creation": "2013-05-21 16:16:39", + "creation": "2026-04-06 14:10:33.384946", "doctype": "DocType", "document_type": "Document", "editable_grid": 1, @@ -11,7 +11,6 @@ "field_order": [ "supplier_section", "column_break0", - "title", "naming_series", "supplier", "supplier_name", @@ -171,16 +170,6 @@ "print_width": "50%", "width": "50%" }, - { - "allow_on_submit": 1, - "default": "{supplier_name}", - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "label": "Title", - "no_copy": 1, - "print_hide": 1 - }, { "fieldname": "naming_series", "fieldtype": "Select", @@ -1303,7 +1292,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2026-03-09 17:15:28.602690", + "modified": "2026-04-06 14:11:29.630333", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", @@ -1371,6 +1360,6 @@ "sort_order": "DESC", "states": [], "timeline_field": "supplier", - "title_field": "title", + "title_field": "supplier_name", "track_changes": 1 } diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index dc8885b1ca4..71fff578f06 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -148,7 +148,6 @@ class PurchaseReceipt(BuyingController): taxes_and_charges_deducted: DF.Currency tc_name: DF.Link | None terms: DF.TextEditor | None - title: DF.Data | None total: DF.Currency total_net_weight: DF.Float total_qty: DF.Float From af81ed874b13c0bda088ff8728bab7524410c035 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:17:11 +0000 Subject: [PATCH 103/168] fix: transactions where update stock is 0 should not create SLEs (backport #54035) (#54077) Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com> fix: transactions where update stock is 0 should not create SLEs (#54035) --- .../repost_item_valuation/repost_item_valuation.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 2b4d5c28692..84cf6234dcf 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -82,6 +82,7 @@ class RepostItemValuation(Document): def validate(self): self.reset_repost_only_accounting_ledgers() self.set_company() + self.validate_update_stock() self.validate_period_closing_voucher() self.set_status(write=False) self.reset_field_values() @@ -93,6 +94,18 @@ class RepostItemValuation(Document): if self.repost_only_accounting_ledgers and self.based_on != "Transaction": self.repost_only_accounting_ledgers = 0 + def validate_update_stock(self): + if ( + self.voucher_type in ["Sales Invoice", "Purchase Invoice"] + and not self.repost_only_accounting_ledgers + ): + update_stock = frappe.get_value(self.voucher_type, self.voucher_no, "update_stock") + if not update_stock: + msg = _( + "Since {0} has 'Update Stock' disabled, you cannot create repost item valuation against it" + ).format(get_link_to_form(self.voucher_type, self.voucher_no)) + frappe.throw(msg) + def validate_recreate_stock_ledgers(self): if not self.recreate_stock_ledgers: return From dc58754a60425e455352c148e1c17c8725a6f33e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:18:45 +0000 Subject: [PATCH 104/168] fix: add tax_id handling in Tax Withholding Entry (backport #53598) (#54081) Co-authored-by: Lakshit Jain fix: add tax_id handling in Tax Withholding Entry (#53598) --- .../tax_withholding_category.py | 13 +++--- .../test_tax_withholding_category.py | 42 +++++++++++++++++++ .../tax_withholding_entry.py | 10 +++-- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index c78b026c227..dd8caa60d7d 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -128,6 +128,7 @@ class TaxWithholdingDetails: self.party_type = party_type self.party = party self.company = company + self.tax_id = get_tax_id_for_party(self.party_type, self.party) def get(self) -> list: """ @@ -161,6 +162,7 @@ class TaxWithholdingDetails: disable_cumulative_threshold=doc.disable_cumulative_threshold, disable_transaction_threshold=doc.disable_transaction_threshold, taxable_amount=0, + tax_id=self.tax_id, ) # ldc (only if valid based on posting date) @@ -181,17 +183,13 @@ class TaxWithholdingDetails: if self.party_type != "Supplier": return ldc_details - # NOTE: This can be a configurable option - # To check if filter by tax_id is needed - tax_id = get_tax_id_for_party(self.party_type, self.party) - # ldc details - ldc_records = self.get_valid_ldc_records(tax_id) + ldc_records = self.get_valid_ldc_records(self.tax_id) if not ldc_records: return ldc_details ldc_names = [ldc.name for ldc in ldc_records] - ldc_utilization_map = self.get_ldc_utilization_by_category(ldc_names, tax_id) + ldc_utilization_map = self.get_ldc_utilization_by_category(ldc_names, self.tax_id) # map for ldc in ldc_records: @@ -254,4 +252,5 @@ class TaxWithholdingDetails: @allow_regional def get_tax_id_for_party(party_type, party): - return None + # cannot use tax_id from doc because payment and journal entry do not have tax_id field.\ + return frappe.db.get_value(party_type, party, "tax_id") diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index bd633c94dc9..2d0450107bc 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -2,6 +2,7 @@ # See license.txt import datetime +from unittest.mock import patch import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields @@ -3541,6 +3542,47 @@ class TestTaxWithholdingCategory(ERPNextTestSuite): entry.withholding_amount = 5001 # Should be 5000 (10% of 50000) self.assertRaisesRegex(frappe.ValidationError, "Withholding Amount.*does not match", pi.save) + def test_tax_id_is_set_in_all_generated_entries_from_party_doctype(self): + self.setup_party_with_category("Supplier", "Test TDS Supplier3", "New TDS Category") + frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_id", "ABCTY1234D") + + pi = create_purchase_invoice(supplier="Test TDS Supplier3", rate=40000) + pi.submit() + + entries = frappe.get_all( + "Tax Withholding Entry", + filters={"parenttype": "Purchase Invoice", "parent": pi.name}, + fields=["name", "tax_id"], + ) + + self.assertTrue(entries) + self.assertTrue(all(entry.tax_id == "ABCTY1234D" for entry in entries)) + + def test_threshold_considers_two_parties_with_same_tax_id_with_overrided_hook(self): + self.setup_party_with_category("Supplier", "Test TDS Supplier1", "Cumulative Threshold TDS") + self.setup_party_with_category("Supplier", "Test TDS Supplier2", "Cumulative Threshold TDS") + + with patch( + "erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category.get_tax_id_for_party", + return_value="AAAPL1234C", + ): + pi1 = create_purchase_invoice(supplier="Test TDS Supplier1", rate=20000) + pi1.submit() + + pi2 = create_purchase_invoice(supplier="Test TDS Supplier2", rate=20000) + + pi2.submit() + + entries = frappe.get_all( + "Tax Withholding Entry", + filters={"parenttype": "Purchase Invoice", "parent": pi2.name}, + fields=["status", "withholding_amount"], + ) + + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0].status, "Settled") + self.assertEqual(entries[0].withholding_amount, 2000.0) + def create_purchase_invoice(**args): # return sales invoice doc object diff --git a/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py b/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py index 6aff1116935..8f8ee7898af 100644 --- a/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py +++ b/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py @@ -344,7 +344,6 @@ class TaxWithholdingEntry(Document): from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( TaxWithholdingDetails, - get_tax_id_for_party, ) @@ -646,8 +645,11 @@ class TaxWithholdingController: # NOTE: This can be a configurable option # To check if filter by tax_id is needed - tax_id = get_tax_id_for_party(self.party_type, self.party) - query = query.where(entry.tax_id == tax_id) if tax_id else query.where(entry.party == self.party) + query = ( + query.where(entry.tax_id == category.tax_id) + if category.tax_id + else query.where(entry.party == self.party) + ) return query @@ -686,6 +688,7 @@ class TaxWithholdingController: "company": self.doc.company, "party_type": self.party_type, "party": self.party, + "tax_id": category.tax_id, "tax_withholding_category": category.name, "tax_withholding_group": category.tax_withholding_group, "tax_rate": category.tax_rate, @@ -1052,6 +1055,7 @@ class TaxWithholdingController: "party_type": self.party_type, "party": self.party, "company": self.doc.company, + "tax_id": category.tax_id, } ) return entry From e6722c84fa4d8ebc2f14af64f1dd16bd4b749dfb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 04:14:45 +0000 Subject: [PATCH 105/168] fix: dif_inward_from_outward_workspace_sidebar (backport #54083) (#54088) Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com> fix: dif_inward_from_outward_workspace_sidebar (#54083) --- erpnext/workspace_sidebar/subcontracting.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/workspace_sidebar/subcontracting.json b/erpnext/workspace_sidebar/subcontracting.json index e2aa91fcfa6..60509c5c5cd 100644 --- a/erpnext/workspace_sidebar/subcontracting.json +++ b/erpnext/workspace_sidebar/subcontracting.json @@ -71,7 +71,7 @@ "icon": "", "indent": 0, "keep_closed": 0, - "label": "Subcontracting Order", + "label": "Subcontracting Inward Order", "link_to": "Subcontracting Inward Order", "link_type": "DocType", "show_arrow": 0, @@ -230,7 +230,7 @@ "type": "Link" } ], - "modified": "2026-02-23 22:40:17.130101", + "modified": "2026-04-06 20:22:17.130321", "modified_by": "Administrator", "module": "Buying", "module_onboarding": "Subcontracting Onboarding", From 62b83cacce540a671b85d565d5d277090ba80a44 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:25:13 +0530 Subject: [PATCH 106/168] =?UTF-8?q?fix:=20resolve=20user=20permission=20er?= =?UTF-8?q?ror=20on=20status=20change=20by=20updating=20user=20=E2=80=A6?= =?UTF-8?q?=20(backport=20#54033)=20(#54060)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Krishna Shirsath --- erpnext/setup/doctype/employee/employee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index d66d091320b..81324fb89ba 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -301,7 +301,7 @@ class Employee(NestedSet): frappe.throw(_("User {0} does not exist").format(self.user_id)) if self.status != "Active" and enabled or self.status == "Active" and enabled == 0: - frappe.set_value("User", self.user_id, "enabled", not enabled) + frappe.db.set_value("User", self.user_id, "enabled", not enabled) def validate_duplicate_user_id(self): Employee = frappe.qb.DocType("Employee") From ff262655bb018a42bf1cc920534564e36476eeae Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:25:44 +0530 Subject: [PATCH 107/168] feat: croatian_address_template (backport #53888) (#54058) Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com> --- erpnext/regional/address_template/templates/croatia.html | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 erpnext/regional/address_template/templates/croatia.html diff --git a/erpnext/regional/address_template/templates/croatia.html b/erpnext/regional/address_template/templates/croatia.html new file mode 100644 index 00000000000..0c2ed73f0ae --- /dev/null +++ b/erpnext/regional/address_template/templates/croatia.html @@ -0,0 +1,4 @@ +{{ address_line1 }}
+{% if address_line2 %}{{ address_line2 }}
{% endif -%} +{{ pincode }} {{ city | upper }}
+{{ country | upper }} \ No newline at end of file From 454271ad68073ad85e65e6029266c24548d5d94c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 05:55:55 +0000 Subject: [PATCH 108/168] fix: divide sub-assembly cost by qty to get per-unit rate in BOM Creator (backport #54090) (#54091) --- erpnext/manufacturing/doctype/bom_creator/bom_creator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index e3feac1061a..97849b6f17e 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -203,7 +203,9 @@ class BOMCreator(Document): self, ) else: - row.rate = flt(self.get_raw_material_cost(row.item_code) * row.conversion_factor) + row.rate = flt( + self.get_raw_material_cost(row.item_code) / flt(row.qty or 1) * row.conversion_factor + ) row.amount = flt(row.rate) * flt(row.qty) amount += flt(row.amount) From 995a29e3e1929a8da376cacafa5a53c4fb243e05 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 06:40:45 +0000 Subject: [PATCH 109/168] fix: task gantt popup text not visible in light theme (backport #53882) (#54094) Co-authored-by: Sakthivel Murugan S <129778327+ssakthivelmurugan@users.noreply.github.com> fix: task gantt popup text not visible in light theme (#53882) --- erpnext/projects/doctype/task/task_list.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/doctype/task/task_list.js b/erpnext/projects/doctype/task/task_list.js index 17b0ed2c7fa..2516a327d44 100644 --- a/erpnext/projects/doctype/task/task_list.js +++ b/erpnext/projects/doctype/task/task_list.js @@ -35,30 +35,30 @@ frappe.listview_settings["Task"] = { }, gantt_custom_popup_html: function (ganttobj, task) { let html = ` -
+ ${ganttobj.name} `; if (task.project) { html += `

${__("Project")}: - + ${task.project}

`; } html += `

${__("Progress")}: - ${ganttobj.progress}% + ${ganttobj.progress}%

`; if (task._assign) { const assign_list = JSON.parse(task._assign); const assignment_wrapper = ` Assigned to: - + ${assign_list.map((user) => frappe.user_info(user).fullname).join(", ")} `; From 21f36f5c21dceeb7214d961fe8ff5731bd41106e Mon Sep 17 00:00:00 2001 From: ervishnucs Date: Thu, 12 Mar 2026 17:33:56 +0530 Subject: [PATCH 110/168] fix: remove null from link_filters (cherry picked from commit a518a735f35a6ac0e68f2de0a2e2048264f27f7b) --- erpnext/assets/doctype/asset_repair/asset_repair.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json index 71b9469cfbd..4fc9a31b875 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.json +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -130,7 +130,7 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Asset", - "link_filters": "[[\"Asset\",\"status\",\"not in\",[\"Work In Progress\",\"Capitalized\",\"Fully Depreciated\",\"Sold\",\"Scrapped\",\"Cancelled\",null]]]", + "link_filters": "[[\"Asset\",\"status\",\"not in\",[\"Work In Progress\",\"Capitalized\",\"Fully Depreciated\",\"Sold\",\"Scrapped\",\"Cancelled\"]]]", "options": "Asset", "reqd": 1 }, From b91af5b2b987e04da90c9376261d155b2a665a88 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 25 Mar 2026 10:43:28 +0530 Subject: [PATCH 111/168] fix: create source_stock_entry to refer to original manufacturing entry (cherry picked from commit d4baa9a74af097a47ffdb267e5a0073f4c5d6721) --- erpnext/stock/doctype/stock_entry/stock_entry.json | 10 ++++++++++ erpnext/stock/doctype/stock_entry/stock_entry.py | 1 + 2 files changed, 11 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 7c9dadb9a55..81cbad37c24 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -24,6 +24,7 @@ "work_order", "subcontracting_order", "outgoing_stock_entry", + "source_stock_entry", "bom_info_section", "from_bom", "use_multi_level_bom", @@ -125,6 +126,15 @@ "options": "Stock Entry", "read_only": 1 }, + { + "depends_on": "eval:doc.purpose == 'Disassemble'", + "fieldname": "source_stock_entry", + "fieldtype": "Link", + "label": "Source Stock Entry (Manufacture)", + "no_copy": 1, + "options": "Stock Entry", + "print_hide": 1 + }, { "bold": 1, "fetch_from": "stock_entry_type.purpose", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8078abb2848..8d5c1fe60dc 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -151,6 +151,7 @@ class StockEntry(StockController, SubcontractingInwardController): select_print_heading: DF.Link | None set_posting_time: DF.Check source_address_display: DF.TextEditor | None + source_stock_entry: DF.Link | None source_warehouse_address: DF.Link | None stock_entry_type: DF.Link subcontracting_inward_order: DF.Link | None From c9d03d049c57fdd41af00ea28fe292f511d41ec5 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 25 Mar 2026 16:49:58 +0530 Subject: [PATCH 112/168] fix: disassembly prompt with source stock entry field (cherry picked from commit 68e97808c566cbb34716f1b9dee4820f8d9c28a9) # Conflicts: # erpnext/manufacturing/doctype/work_order/work_order.py --- .../doctype/work_order/work_order.js | 63 ++++++++++++++++++- .../doctype/work_order/work_order.py | 28 +++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 7a964a76231..0e8729bf4ba 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -441,7 +441,7 @@ frappe.ui.form.on("Work Order", { make_disassembly_order(frm) { erpnext.work_order - .show_prompt_for_qty_input(frm, "Disassemble") + .show_disassembly_prompt(frm) .then((data) => { if (flt(data.qty) <= 0) { frappe.msgprint(__("Disassemble Qty cannot be less than or equal to 0.")); @@ -451,11 +451,14 @@ frappe.ui.form.on("Work Order", { work_order_id: frm.doc.name, purpose: "Disassemble", qty: data.qty, + source_stock_entry: data.source_stock_entry, }); }) .then((stock_entry) => { - frappe.model.sync(stock_entry); - frappe.set_route("Form", stock_entry.doctype, stock_entry.name); + if (stock_entry) { + frappe.model.sync(stock_entry); + frappe.set_route("Form", stock_entry.doctype, stock_entry.name); + } }); }, @@ -1002,6 +1005,60 @@ erpnext.work_order = { return flt(max, precision("qty")); }, + show_disassembly_prompt: function (frm) { + let max_qty = flt(frm.doc.produced_qty - frm.doc.disassembled_qty); + + let fields = [ + { + fieldtype: "Link", + label: __("Source Manufacture Entry"), + fieldname: "source_stock_entry", + options: "Stock Entry", + description: __("Optional. Select a specific manufacture entry to reverse."), + get_query: () => { + return { + filters: { + work_order: frm.doc.name, + purpose: "Manufacture", + docstatus: 1, + }, + }; + }, + onchange: async function () { + if (!frm.disassembly_prompt) return; + + let se_name = this.value; + let qty = max_qty; + if (se_name) { + qty = await frappe.xcall( + "erpnext.manufacturing.doctype.work_order.work_order.get_disassembly_available_qty", + { stock_entry_name: se_name } + ); + } + + frm.disassembly_prompt.set_value("qty", qty); + frm.disassembly_prompt.fields_dict.qty.set_description(__("Max: {0}", [qty])); + }, + }, + { + fieldtype: "Float", + label: __("Qty for {0}", [__("Disassemble")]), + fieldname: "qty", + description: __("Max: {0}", [max_qty]), + default: max_qty, + }, + ]; + + return new Promise((resolve, reject) => { + frm.disassembly_prompt = frappe.prompt( + fields, + (data) => resolve(data), + __("Disassemble"), + __("Create") + ); + }); + }, + show_prompt_for_qty_input: function (frm, purpose, qty, additional_transfer_entry) { let max = !additional_transfer_entry ? this.get_max_transferable_qty(frm, purpose) : qty; diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 72fafa03edd..9b6f95f25df 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -2376,6 +2376,7 @@ def make_stock_entry( qty: float | None = None, target_warehouse: str | None = None, is_additional_transfer_entry: bool = False, + source_stock_entry: str | None = None, ): work_order = frappe.get_doc("Work Order", work_order_id) if not frappe.db.get_value("Warehouse", work_order.wip_warehouse, "is_group"): @@ -2416,6 +2417,8 @@ def make_stock_entry( if purpose == "Disassemble": stock_entry.from_warehouse = work_order.fg_warehouse stock_entry.to_warehouse = target_warehouse or work_order.source_warehouse + if source_stock_entry: + stock_entry.source_stock_entry = source_stock_entry stock_entry.set_stock_entry_type() stock_entry.is_additional_transfer_entry = is_additional_transfer_entry @@ -2429,7 +2432,32 @@ def make_stock_entry( @frappe.whitelist() +<<<<<<< HEAD def get_default_warehouse(company): +======= +def get_disassembly_available_qty(stock_entry_name: str) -> float: + se = frappe.db.get_value("Stock Entry", stock_entry_name, ["fg_completed_qty"], as_dict=True) + if not se: + return 0.0 + + already_disassembled = flt( + frappe.db.get_value( + "Stock Entry", + { + "source_stock_entry": stock_entry_name, + "purpose": "Disassemble", + "docstatus": 1, + }, + [{"SUM": "fg_completed_qty"}], + ) + ) + + return flt(se.fg_completed_qty) - already_disassembled + + +@frappe.whitelist() +def get_default_warehouse(company: str): +>>>>>>> 68e97808c5 (fix: disassembly prompt with source stock entry field) wip, fg, scrap = frappe.get_cached_value( "Company", company, ["default_wip_warehouse", "default_fg_warehouse", "default_scrap_warehouse"] ) From 5f67ef70bbc97c98b2ca3d9c220fa98270778371 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 25 Mar 2026 17:04:45 +0530 Subject: [PATCH 113/168] fix: set_query for source stock entry (cherry picked from commit b47dfacb3e10461b6cffff470391ce2fbe4624d0) --- erpnext/stock/doctype/stock_entry/stock_entry.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index dbfad27be26..13e38465681 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -36,6 +36,16 @@ frappe.ui.form.on("Stock Entry", { }; }); + frm.set_query("source_stock_entry", function () { + return { + filters: { + purpose: "Manufacture", + docstatus: 1, + work_order: frm.doc.work_order || undefined, + }, + }; + }); + frm.set_query("source_warehouse_address", function () { return { query: "erpnext.controllers.queries.get_warehouse_address", From 84a063a9bf5876a5b4f395262318fd6acb9f604f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 25 Mar 2026 17:11:11 +0530 Subject: [PATCH 114/168] fix: custom button to disassemble manufactured stock entry with work order (cherry picked from commit b64f86148cc326541709e057684f4ab967a5050f) --- .../stock/doctype/stock_entry/stock_entry.js | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 13e38465681..efaaf475570 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -340,6 +340,55 @@ frappe.ui.form.on("Stock Entry", { __("View") ); } + + if (frm.doc.purpose === "Manufacture" && frm.doc.work_order) { + frm.add_custom_button( + __("Disassemble"), + async function () { + let available_qty = await frappe.xcall( + "erpnext.manufacturing.doctype.work_order.work_order.get_disassembly_available_qty", + { stock_entry_name: frm.doc.name } + ); + frappe.prompt( + // fields + { + fieldtype: "Float", + label: __("Qty to Disassemble"), + fieldname: "qty", + default: available_qty, + description: __("Max: {0}", [available_qty]), + }, + // callback + async (data) => { + if (data.qty > available_qty) { + frappe.throw( + __("Cannot disassemble more than available quantity ({0})", [ + available_qty, + ]) + ); + } + + let stock_entry = await frappe.xcall( + "erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry", + { + work_order_id: frm.doc.work_order, + purpose: "Disassemble", + qty: data.qty, + source_stock_entry: frm.doc.name, + } + ); + if (stock_entry) { + frappe.model.sync(stock_entry); + frappe.set_route("Form", stock_entry.doctype, stock_entry.name); + } + }, + __("Disassemble"), + __("Create") + ); + }, + __("Create") + ); + } } if (frm.doc.docstatus === 0 && !frm.doc.subcontracting_inward_order) { From 1c4b2a7148527d7212c96be3b5eb3680d8571010 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 25 Mar 2026 18:27:50 +0530 Subject: [PATCH 115/168] fix: support creating disassembly (without link of WO) (cherry picked from commit dba82720b6ae5849034a1fbe510f71b2e203a3a7) --- .../stock/doctype/stock_entry/stock_entry.js | 77 +++++++++++++------ 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index efaaf475570..e4c1ffa4d26 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -242,6 +242,30 @@ frappe.ui.form.on("Stock Entry", { }); }, + source_stock_entry: async function (frm) { + if (!frm.doc.source_stock_entry || frm.doc.purpose !== "Disassemble") return; + + if (frm._via_source_stock_entry) { + frm.call({ + doc: frm.doc, + method: "get_items", + callback: function (r) { + if (!r.exc) refresh_field("items"); + }, + }); + frm._via_source_stock_entry = false; + return; + } + + let available_qty = await frappe.xcall( + "erpnext.manufacturing.doctype.work_order.work_order.get_disassembly_available_qty", + { stock_entry_name: frm.doc.source_stock_entry } + ); + + // triggers get_items() via its onchange + await frm.set_value("fg_completed_qty", available_qty); + }, + outgoing_stock_entry: function (frm) { frappe.call({ doc: frm.doc, @@ -341,7 +365,7 @@ frappe.ui.form.on("Stock Entry", { ); } - if (frm.doc.purpose === "Manufacture" && frm.doc.work_order) { + if (frm.doc.purpose === "Manufacture") { frm.add_custom_button( __("Disassemble"), async function () { @@ -350,7 +374,6 @@ frappe.ui.form.on("Stock Entry", { { stock_entry_name: frm.doc.name } ); frappe.prompt( - // fields { fieldtype: "Float", label: __("Qty to Disassemble"), @@ -358,28 +381,33 @@ frappe.ui.form.on("Stock Entry", { default: available_qty, description: __("Max: {0}", [available_qty]), }, - // callback async (data) => { - if (data.qty > available_qty) { - frappe.throw( - __("Cannot disassemble more than available quantity ({0})", [ - available_qty, - ]) + if (frm.doc.work_order) { + let stock_entry = await frappe.xcall( + "erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry", + { + work_order_id: frm.doc.work_order, + purpose: "Disassemble", + qty: data.qty, + source_stock_entry: frm.doc.name, + } ); - } - - let stock_entry = await frappe.xcall( - "erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry", - { - work_order_id: frm.doc.work_order, - purpose: "Disassemble", - qty: data.qty, - source_stock_entry: frm.doc.name, + if (stock_entry) { + frappe.model.sync(stock_entry); + frappe.set_route("Form", stock_entry.doctype, stock_entry.name); } - ); - if (stock_entry) { - frappe.model.sync(stock_entry); - frappe.set_route("Form", stock_entry.doctype, stock_entry.name); + } else { + let se = frappe.model.get_new_doc("Stock Entry"); + se.company = frm.doc.company; + se.stock_entry_type = "Disassemble"; + se.purpose = "Disassemble"; + se.source_stock_entry = frm.doc.name; + se.from_bom = frm.doc.from_bom; + se.bom_no = frm.doc.bom_no; + se.fg_completed_qty = data.qty; + frm._via_source_stock_entry = true; + + frappe.set_route("Form", "Stock Entry", se.name); } }, __("Disassemble"), @@ -1401,8 +1429,11 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle get_items() { var me = this; - if (this.frm.doc.work_order || this.frm.doc.bom_no) { - // if work order / bom is mentioned, get items + if ( + this.frm.doc.work_order || + this.frm.doc.bom_no || + (this.frm.doc.purpose === "Disassemble" && this.frm.doc.source_stock_entry) + ) { return this.frm.call({ doc: me.frm.doc, freeze: true, From 1237f9a0b17881f2f686bb715d1b7415094a4bf0 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 30 Mar 2026 15:46:37 +0530 Subject: [PATCH 116/168] fix: validate qty that can be disassembled from source stock entry. (cherry picked from commit 6394dead724b346deae30a6f2b8088d68cac0176) # Conflicts: # erpnext/manufacturing/doctype/work_order/work_order.py --- .../doctype/work_order/work_order.py | 25 +++++++++++-------- .../stock/doctype/stock_entry/stock_entry.py | 21 ++++++++++++++++ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 9b6f95f25df..268918eca00 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -2433,24 +2433,27 @@ def make_stock_entry( @frappe.whitelist() <<<<<<< HEAD +<<<<<<< HEAD def get_default_warehouse(company): ======= def get_disassembly_available_qty(stock_entry_name: str) -> float: +======= +def get_disassembly_available_qty(stock_entry_name: str, current_se_name: str | None = None) -> float: +>>>>>>> 6394dead72 (fix: validate qty that can be disassembled from source stock entry.) se = frappe.db.get_value("Stock Entry", stock_entry_name, ["fg_completed_qty"], as_dict=True) if not se: return 0.0 - already_disassembled = flt( - frappe.db.get_value( - "Stock Entry", - { - "source_stock_entry": stock_entry_name, - "purpose": "Disassemble", - "docstatus": 1, - }, - [{"SUM": "fg_completed_qty"}], - ) - ) + filters = { + "source_stock_entry": stock_entry_name, + "purpose": "Disassemble", + "docstatus": 1, + } + + if current_se_name: + filters["name"] = ("!=", current_se_name) + + already_disassembled = flt(frappe.db.get_value("Stock Entry", filters, [{"SUM": "fg_completed_qty"}])) return flt(se.fg_completed_qty) - already_disassembled diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8d5c1fe60dc..c1eeec3eaf4 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -247,6 +247,7 @@ class StockEntry(StockController, SubcontractingInwardController): self.validate_warehouse() self.validate_warehouse_of_sabb() self.validate_work_order() + self.validate_source_stock_entry() self.validate_bom() self.set_process_loss_qty() self.validate_purchase_order() @@ -847,6 +848,26 @@ class StockEntry(StockController, SubcontractingInwardController): elif self.purpose != "Material Transfer": self.work_order = None + def validate_source_stock_entry(self): + if not self.get("source_stock_entry"): + return + + from erpnext.manufacturing.doctype.work_order.work_order import get_disassembly_available_qty + + available_qty = get_disassembly_available_qty(self.source_stock_entry, self.name) + + if flt(self.fg_completed_qty) > available_qty: + frappe.throw( + _( + "Cannot disassemble {0} qty against Stock Entry {1}. Only {2} qty available to disassemble." + ).format( + self.fg_completed_qty, + self.source_stock_entry, + available_qty, + ), + title=_("Excess Disassembly"), + ) + def check_if_operations_completed(self): """Check if Time Sheets are completed against before manufacturing to capture operating costs.""" prod_order = frappe.get_doc("Work Order", self.work_order) From 4232640a8b2670d04ac585f6a7eb4dc53b32ddf8 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 30 Mar 2026 18:19:19 +0530 Subject: [PATCH 117/168] fix: add support to fetch items based on manufacture stock entry; fix how it's done from work order (cherry picked from commit 1ed0124ad7668cffe2aa858edaadf9a52faab313) --- .../stock/doctype/stock_entry/stock_entry.py | 178 ++++++++++++------ 1 file changed, 121 insertions(+), 57 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index c1eeec3eaf4..7284b50a3e7 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -29,7 +29,6 @@ from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals from erpnext.manufacturing.doctype.bom.bom import ( add_additional_cost, - get_bom_items_as_dict, get_op_cost_from_sub_assemblies, get_secondary_items_from_sub_assemblies, validate_bom_no, @@ -2269,45 +2268,108 @@ class StockEntry(StockController, SubcontractingInwardController): ) def get_items_for_disassembly(self): - """Get items for Disassembly Order""" + """Get items for Disassembly Order. + + Priority: + 1. From a specific Manufacture Stock Entry (exact reversal) + 2. From Work Order required_items (reflects WO changes) + 3. From BOM (standalone disassembly) + """ + + if self.get("source_stock_entry"): + return self._add_items_for_disassembly_from_stock_entry() if self.work_order: return self._add_items_for_disassembly_from_work_order() return self._add_items_for_disassembly_from_bom() + def _add_items_for_disassembly_from_stock_entry(self): + source_fg_qty = frappe.db.get_value("Stock Entry", self.source_stock_entry, "fg_completed_qty") + if not source_fg_qty: + frappe.throw( + _("Source Stock Entry {0} has no finished goods quantity").format(self.source_stock_entry) + ) + + scale_factor = flt(self.fg_completed_qty) / flt(source_fg_qty) + + for source_row in self.get_items_from_manufacture_stock_entry(self.source_stock_entry): + if source_row.is_finished_item: + qty = flt(self.fg_completed_qty) + s_warehouse = self.from_warehouse or source_row.t_warehouse + t_warehouse = "" + else: + qty = flt(source_row.qty * scale_factor) + s_warehouse = "" + t_warehouse = self.to_warehouse or source_row.s_warehouse + + use_serial_batch_fields = 1 if (source_row.batch_no or source_row.serial_no) else 0 + + self.append( + "items", + { + "item_code": source_row.item_code, + "item_name": source_row.item_name, + "description": source_row.description, + "stock_uom": source_row.stock_uom, + "uom": source_row.uom, + "conversion_factor": source_row.conversion_factor, + "basic_rate": source_row.basic_rate, + "qty": qty, + "s_warehouse": s_warehouse, + "t_warehouse": t_warehouse, + "is_finished_item": source_row.is_finished_item, + "against_stock_entry": self.source_stock_entry, + "ste_detail": source_row.name, + "batch_no": source_row.batch_no, + "serial_no": source_row.serial_no, + "use_serial_batch_fields": use_serial_batch_fields, + }, + ) + def _add_items_for_disassembly_from_work_order(self): - items = self.get_items_from_manufacture_entry() + wo = frappe.get_doc("Work Order", self.work_order) - s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse") + if not wo.required_items: + return self._add_items_for_disassembly_from_bom() - items_dict = get_bom_items_as_dict( - self.bom_no, - self.company, - self.fg_completed_qty, - fetch_exploded=self.use_multi_level_bom, - fetch_qty_in_stock_uom=False, + scale_factor = flt(self.fg_completed_qty) / flt(wo.qty) if flt(wo.qty) else 0 + + # RMs + for ri in wo.required_items: + self.append( + "items", + { + "item_code": ri.item_code, + "item_name": ri.item_name, + "description": ri.description, + "qty": flt(ri.required_qty * scale_factor), + "stock_uom": ri.stock_uom, + "uom": ri.stock_uom, + "conversion_factor": 1, + "t_warehouse": ri.source_warehouse or wo.source_warehouse or self.to_warehouse, + "s_warehouse": "", + "is_finished_item": 0, + }, + ) + + # FG + self.append( + "items", + { + "item_code": wo.production_item, + "item_name": wo.item_name, + "description": wo.description, + "qty": flt(self.fg_completed_qty), + "stock_uom": wo.stock_uom, + "uom": wo.stock_uom, + "conversion_factor": 1, + "s_warehouse": self.from_warehouse or wo.fg_warehouse, + "t_warehouse": "", + "is_finished_item": 1, + }, ) - for row in items: - child_row = self.append("items", {}) - for field, value in row.items(): - if value is not None: - child_row.set(field, value) - - # update qty and amount from BOM items - bom_items = items_dict.get(row.item_code) - if bom_items: - child_row.qty = bom_items.get("qty", child_row.qty) - child_row.amount = bom_items.get("amount", child_row.amount) - - if row.is_finished_item: - child_row.qty = self.fg_completed_qty - - child_row.s_warehouse = (self.from_warehouse or s_warehouse) if row.is_finished_item else "" - child_row.t_warehouse = row.s_warehouse - child_row.is_finished_item = 0 if row.is_finished_item else 1 - def _add_items_for_disassembly_from_bom(self): if not self.bom_no or not self.fg_completed_qty: frappe.throw(_("BOM and Finished Good Quantity is mandatory for Disassembly")) @@ -2325,34 +2387,36 @@ class StockEntry(StockController, SubcontractingInwardController): # Finished goods self.load_items_from_bom() - def get_items_from_manufacture_entry(self): - return frappe.get_all( - "Stock Entry", - fields=[ - "`tabStock Entry Detail`.`item_code`", - "`tabStock Entry Detail`.`item_name`", - "`tabStock Entry Detail`.`description`", - {"SUM": "`tabStock Entry Detail`.`qty`", "as": "qty"}, - {"SUM": "`tabStock Entry Detail`.`transfer_qty`", "as": "transfer_qty"}, - "`tabStock Entry Detail`.`stock_uom`", - "`tabStock Entry Detail`.`uom`", - "`tabStock Entry Detail`.`basic_rate`", - "`tabStock Entry Detail`.`conversion_factor`", - "`tabStock Entry Detail`.`is_finished_item`", - "`tabStock Entry Detail`.`batch_no`", - "`tabStock Entry Detail`.`serial_no`", - "`tabStock Entry Detail`.`s_warehouse`", - "`tabStock Entry Detail`.`t_warehouse`", - "`tabStock Entry Detail`.`use_serial_batch_fields`", - ], - filters=[ - ["Stock Entry", "purpose", "=", "Manufacture"], - ["Stock Entry", "work_order", "=", self.work_order], - ["Stock Entry", "docstatus", "=", 1], - ["Stock Entry Detail", "docstatus", "=", 1], - ], - order_by="`tabStock Entry Detail`.`idx` desc, `tabStock Entry Detail`.`is_finished_item` desc", - group_by="`tabStock Entry Detail`.`item_code`", + def get_items_from_manufacture_stock_entry(self, stock_entry): + SE = frappe.qb.DocType("Stock Entry") + SED = frappe.qb.DocType("Stock Entry Detail") + + return ( + frappe.qb.from_(SED) + .join(SE) + .on(SED.parent == SE.name) + .select( + SED.name, + SED.item_code, + SED.item_name, + SED.description, + SED.qty, + SED.transfer_qty, + SED.stock_uom, + SED.uom, + SED.basic_rate, + SED.conversion_factor, + SED.is_finished_item, + SED.batch_no, + SED.serial_no, + SED.use_serial_batch_fields, + SED.s_warehouse, + SED.t_warehouse, + ) + .where(SE.name == stock_entry) + .where(SE.docstatus == 1) + .orderby(SED.idx) + .run(as_dict=True) ) @frappe.whitelist() From eead8d6d8c219daa21c5742efe9255fb80833a9e Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 30 Mar 2026 18:19:55 +0530 Subject: [PATCH 118/168] fix: auto-set source_stock_entry (cherry picked from commit 2e4e8bcaa7566283fe8d9db3bf9a50cfb1f1b68e) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7284b50a3e7..b878f097e60 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2276,6 +2276,21 @@ class StockEntry(StockController, SubcontractingInwardController): 3. From BOM (standalone disassembly) """ + # Auto-set source_stock_entry if WO has exactly one manufacture entry + if not self.get("source_stock_entry") and self.work_order: + manufacture_entries = frappe.get_all( + "Stock Entry", + filters={ + "work_order": self.work_order, + "purpose": "Manufacture", + "docstatus": 1, + }, + pluck="name", + limit_page_length=2, + ) + if len(manufacture_entries) == 1: + self.source_stock_entry = manufacture_entries[0] + if self.get("source_stock_entry"): return self._add_items_for_disassembly_from_stock_entry() From 919cbd5c0229fdde896afad5dbf58fbe2da65556 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 31 Mar 2026 07:44:25 +0530 Subject: [PATCH 119/168] fix: correct warehouse preference for disassemble (cherry picked from commit d3d6b5c6608b9a21db381160987c2b5fa17f2229) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index b878f097e60..2068b0cf514 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2362,7 +2362,8 @@ class StockEntry(StockController, SubcontractingInwardController): "stock_uom": ri.stock_uom, "uom": ri.stock_uom, "conversion_factor": 1, - "t_warehouse": ri.source_warehouse or wo.source_warehouse or self.to_warehouse, + # manufacture transfers RMs from WIP (not source warehouse) + "t_warehouse": self.to_warehouse or wo.wip_warehouse, "s_warehouse": "", "is_finished_item": 0, }, From ff104edf1289dbf9876db11d8bdc95ecd9fb8d3b Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 31 Mar 2026 09:27:19 +0530 Subject: [PATCH 120/168] fix: set serial and batch from source stock entry - on disassemble (cherry picked from commit 13b019ab8efe75cff787b400cbbcb9b5c9677bfb) --- .../stock/doctype/stock_entry/stock_entry.py | 107 +++++++++++++----- 1 file changed, 81 insertions(+), 26 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 2068b0cf514..3aee2be095a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -331,6 +331,58 @@ class StockEntry(StockController, SubcontractingInwardController): if self.purpose != "Disassemble": return + if self.get("source_stock_entry"): + self._set_serial_batch_for_disassembly_from_stock_entry() + else: + self._set_serial_batch_for_disassembly_from_available_materials() + + def _set_serial_batch_for_disassembly_from_stock_entry(self): + from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( + get_voucher_wise_serial_batch_from_bundle, + ) + + source_fg_qty = flt(frappe.db.get_value("Stock Entry", self.source_stock_entry, "fg_completed_qty")) + scale_factor = flt(self.fg_completed_qty) / source_fg_qty if source_fg_qty else 0 + + bundle_data = get_voucher_wise_serial_batch_from_bundle(voucher_no=[self.source_stock_entry]) + source_rows_by_name = { + r.name: r for r in self.get_items_from_manufacture_stock_entry(self.source_stock_entry) + } + + for row in self.items: + if not row.ste_detail: + continue + + source_row = source_rows_by_name.get(row.ste_detail) + if not source_row: + continue + + source_warehouse = source_row.s_warehouse or source_row.t_warehouse + key = (source_row.item_code, source_warehouse, self.source_stock_entry) + source_bundle = bundle_data.get(key, {}) + + batches = defaultdict(float) + serial_nos = [] + + if source_bundle.get("batch_nos"): + qty_remaining = row.transfer_qty + for batch_no, batch_qty in source_bundle["batch_nos"].items(): + if qty_remaining <= 0: + break + alloc = min(flt(batch_qty) * scale_factor, qty_remaining) + batches[batch_no] = alloc + qty_remaining -= alloc + elif source_row.batch_no: + batches[source_row.batch_no] = row.transfer_qty + + if source_bundle.get("serial_nos"): + serial_nos = get_serial_nos(source_bundle["serial_nos"])[: int(row.transfer_qty)] + elif source_row.serial_no: + serial_nos = get_serial_nos(source_row.serial_no)[: int(row.transfer_qty)] + + self._set_serial_batch_bundle_for_disassembly_row(row, serial_nos, batches) + + def _set_serial_batch_for_disassembly_from_available_materials(self): available_materials = get_available_materials(self.work_order, self) for row in self.items: warehouse = row.s_warehouse or row.t_warehouse @@ -356,33 +408,37 @@ class StockEntry(StockController, SubcontractingInwardController): if materials.serial_nos: serial_nos = materials.serial_nos[: int(row.transfer_qty)] - if not serial_nos and not batches: - continue + self._set_serial_batch_bundle_for_disassembly_row(row, serial_nos, batches) - bundle_doc = SerialBatchCreation( - { - "item_code": row.item_code, - "warehouse": warehouse, - "posting_datetime": get_combine_datetime(self.posting_date, self.posting_time), - "voucher_type": self.doctype, - "voucher_no": self.name, - "voucher_detail_no": row.name, - "qty": row.transfer_qty, - "type_of_transaction": "Inward" if row.t_warehouse else "Outward", - "company": self.company, - "do_not_submit": True, - } - ).make_serial_and_batch_bundle(serial_nos=serial_nos, batch_nos=batches) + def _set_serial_batch_bundle_for_disassembly_row(self, row, serial_nos, batches): + if not serial_nos and not batches: + return - row.serial_and_batch_bundle = bundle_doc.name - row.use_serial_batch_fields = 0 + warehouse = row.s_warehouse or row.t_warehouse + bundle_doc = SerialBatchCreation( + { + "item_code": row.item_code, + "warehouse": warehouse, + "posting_datetime": get_combine_datetime(self.posting_date, self.posting_time), + "voucher_type": self.doctype, + "voucher_no": self.name, + "voucher_detail_no": row.name, + "qty": row.transfer_qty, + "type_of_transaction": "Inward" if row.t_warehouse else "Outward", + "company": self.company, + "do_not_submit": True, + } + ).make_serial_and_batch_bundle(serial_nos=serial_nos, batch_nos=batches) - row.db_set( - { - "serial_and_batch_bundle": bundle_doc.name, - "use_serial_batch_fields": 0, - } - ) + row.serial_and_batch_bundle = bundle_doc.name + row.use_serial_batch_fields = 0 + + row.db_set( + { + "serial_and_batch_bundle": bundle_doc.name, + "use_serial_batch_fields": 0, + } + ) def on_submit(self): self.set_serial_batch_for_disassembly() @@ -2336,8 +2392,7 @@ class StockEntry(StockController, SubcontractingInwardController): "is_finished_item": source_row.is_finished_item, "against_stock_entry": self.source_stock_entry, "ste_detail": source_row.name, - "batch_no": source_row.batch_no, - "serial_no": source_row.serial_no, + # batch and serial bundles built on submit "use_serial_batch_fields": use_serial_batch_fields, }, ) From 195a10efb3577a1de72daa25cd4f72d7f3d4d306 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 31 Mar 2026 15:01:46 +0530 Subject: [PATCH 121/168] test: disassembly from wo (cherry picked from commit 342a14d3403de36a09f5df3e3739489e9a1ab879) --- .../doctype/work_order/test_work_order.py | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 81ee66ecb4f..7fec4314bca 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2419,7 +2419,7 @@ class TestWorkOrder(ERPNextTestSuite): stock_entry.submit() - def test_disassembly_order_with_qty_behavior(self): + def test_disassembly_order_with_qty_from_wo_behavior(self): # Create raw material and FG item raw_item = make_item("Test Raw for Disassembly", {"is_stock_item": 1}).name fg_item = make_item("Test FG for Disassembly", {"is_stock_item": 1}).name @@ -2459,27 +2459,9 @@ class TestWorkOrder(ERPNextTestSuite): se_for_manufacture = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", wo.qty)) se_for_manufacture.submit() - # Simulate a disassembly stock entry + # Disassembly via WO required_items path (no source_stock_entry) disassemble_qty = 4 stock_entry = frappe.get_doc(make_stock_entry(wo.name, "Disassemble", disassemble_qty)) - stock_entry.append( - "items", - { - "item_code": fg_item, - "qty": disassemble_qty, - "s_warehouse": wo.fg_warehouse, - }, - ) - - for bom_item in bom.items: - stock_entry.append( - "items", - { - "item_code": bom_item.item_code, - "qty": (bom_item.qty / bom.quantity) * disassemble_qty, - "t_warehouse": wo.source_warehouse, - }, - ) wo.reload() stock_entry.save() @@ -2494,7 +2476,7 @@ class TestWorkOrder(ERPNextTestSuite): f"Expected FG qty {disassemble_qty}, found {finished_good_entry.qty}", ) - # Assert raw materials + # Assert raw materials - qty scaled from WO required_items for item in stock_entry.items: if item.item_code == fg_item: continue From 4c0ebee15be6bc8d4ac131eb725f097017f5a2dd Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 31 Mar 2026 15:14:56 +0530 Subject: [PATCH 122/168] test: disassemble with source stock entry reference (cherry picked from commit 6988e2cbbc00221c117a07c0a45edac5d97b34ab) --- .../doctype/work_order/test_work_order.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 7fec4314bca..b62b4194f72 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2500,6 +2500,30 @@ class TestWorkOrder(ERPNextTestSuite): f"Work Order disassembled_qty mismatch: expected {disassemble_qty}, got {wo.disassembled_qty}", ) + # Second disassembly: explicitly linked to manufacture SE — verifies SE-linked path + # (first disassembly auto-set source_stock_entry since there's only one manufacture entry) + disassemble_qty_2 = 2 + stock_entry_2 = frappe.get_doc( + make_stock_entry( + wo.name, "Disassemble", disassemble_qty_2, source_stock_entry=se_for_manufacture.name + ) + ) + stock_entry_2.save() + stock_entry_2.submit() + + # All rows must trace back to se_for_manufacture + for item in stock_entry_2.items: + self.assertEqual(item.against_stock_entry, se_for_manufacture.name) + self.assertTrue(item.ste_detail) + + # RM qty scaled from the manufacture SE rows + rm_row = next((i for i in stock_entry_2.items if i.item_code == raw_item), None) + expected_rm_qty = (bom.items[0].qty / bom.quantity) * disassemble_qty_2 + self.assertAlmostEqual(rm_row.qty, expected_rm_qty, places=3) + + wo.reload() + self.assertEqual(wo.disassembled_qty, disassemble_qty + disassemble_qty_2) + def test_disassembly_with_multiple_manufacture_entries(self): """ Test that disassembly does not create duplicate items when manufacturing From 8444778f74e38c10eedb80afb7832b56a57cab99 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 31 Mar 2026 15:18:42 +0530 Subject: [PATCH 123/168] test: additional items in stock entry considered with disassembly (cherry picked from commit d32977e3a9d0eea3813a3a9368146df8c69ba995) --- .../doctype/work_order/test_work_order.py | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index b62b4194f72..7ff5b0fee82 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2639,17 +2639,16 @@ class TestWorkOrder(ERPNextTestSuite): def test_disassembly_with_additional_rm_not_in_bom(self): """ - Test that disassembly correctly handles additional raw materials that were - manually added during manufacturing (not part of the BOM). + Test that SE-linked disassembly includes additional raw materials + that were manually added during manufacturing (not part of the BOM). Scenario: 1. Create Work Order for 10 units with 2 raw materials in BOM 2. Transfer raw materials for manufacture 3. Manufacture in 2 parts (3 units, then 7 units) 4. In each manufacture entry, manually add an extra consumable item - (not in BOM) in proportion to the manufactured qty - 5. Create Disassembly for 4 units - 6. Verify that the additional RM is included in disassembly with proportional qty + 5. Disassemble 3 units linked to first manufacture entry + 6. Verify additional RM is included with correct proportional qty from SE1 """ from erpnext.stock.doctype.stock_entry.test_stock_entry import ( make_stock_entry as make_stock_entry_test_record, @@ -2685,9 +2684,8 @@ class TestWorkOrder(ERPNextTestSuite): se_for_material_transfer.save() se_for_material_transfer.submit() - # First Manufacture Entry - 3 units + # First Manufacture Entry - 3 units with additional RM se_manufacture1 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 3)) - # Additional RM se_manufacture1.append( "items", { @@ -2700,9 +2698,8 @@ class TestWorkOrder(ERPNextTestSuite): se_manufacture1.save() se_manufacture1.submit() - # Second Manufacture Entry - 7 units + # Second Manufacture Entry - 7 units with additional RM se_manufacture2 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 7)) - # AAdditional RM se_manufacture2.append( "items", { @@ -2718,13 +2715,15 @@ class TestWorkOrder(ERPNextTestSuite): wo.reload() self.assertEqual(wo.produced_qty, 10) - # Disassembly for 4 units - disassemble_qty = 4 - stock_entry = frappe.get_doc(make_stock_entry(wo.name, "Disassemble", disassemble_qty)) + # Disassemble 3 units linked to first manufacture entry + disassemble_qty = 3 + stock_entry = frappe.get_doc( + make_stock_entry(wo.name, "Disassemble", disassemble_qty, source_stock_entry=se_manufacture1.name) + ) stock_entry.save() stock_entry.submit() - # No duplicate + # No duplicates item_counts = {} for item in stock_entry.items: item_code = item.item_code @@ -2737,16 +2736,15 @@ class TestWorkOrder(ERPNextTestSuite): f"Found duplicate items in disassembly stock entry: {duplicates}", ) - # Additional RM qty + # Additional RM should be included — qty proportional to SE1 (3 units -> 3 additional RM) additional_rm_row = next((i for i in stock_entry.items if i.item_code == additional_rm), None) self.assertIsNotNone( additional_rm_row, f"Additional raw material {additional_rm} not found in disassembly", ) - # intentional full reversal as not part of BOM - # eg: dies or consumables used during manufacturing - expected_additional_rm_qty = 3 + 7 + # SE1 had 3 additional RM for 3 manufactured units, disassembling all 3 + expected_additional_rm_qty = 3 self.assertAlmostEqual( additional_rm_row.qty, expected_additional_rm_qty, @@ -2754,7 +2752,7 @@ class TestWorkOrder(ERPNextTestSuite): msg=f"Additional RM qty mismatch: expected {expected_additional_rm_qty}, got {additional_rm_row.qty}", ) - # RM qty + # BOM RM qty — scaled from SE1's rows for bom_item in bom.items: expected_qty = (bom_item.qty / bom.quantity) * disassemble_qty rm_row = next((i for i in stock_entry.items if i.item_code == bom_item.item_code), None) @@ -2770,6 +2768,7 @@ class TestWorkOrder(ERPNextTestSuite): fg_item_row = next((i for i in stock_entry.items if i.item_code == fg_item), None) self.assertEqual(fg_item_row.qty, disassemble_qty) + # FG + 2 BOM RM + 1 additional RM = 4 items expected_items = 4 self.assertEqual( len(stock_entry.items), @@ -2777,6 +2776,11 @@ class TestWorkOrder(ERPNextTestSuite): f"Expected {expected_items} items, found {len(stock_entry.items)}", ) + # Verify traceability + for item in stock_entry.items: + self.assertEqual(item.against_stock_entry, se_manufacture1.name) + self.assertTrue(item.ste_detail) + def test_components_alternate_item_for_bom_based_manufacture_entry(self): frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM") frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 1) From e1a4d9fab44b84a2c4a85763b10b36b7adff62a0 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 31 Mar 2026 17:52:58 +0530 Subject: [PATCH 124/168] test: disassembly of items with batch and serial numbers (cherry picked from commit 1693698fed085fdc5a20b9bdd23a0d5ae96af195) --- .../doctype/work_order/test_work_order.py | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 7ff5b0fee82..ac9ce2091a9 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2781,6 +2781,206 @@ class TestWorkOrder(ERPNextTestSuite): self.assertEqual(item.against_stock_entry, se_manufacture1.name) self.assertTrue(item.ste_detail) + def test_disassembly_auto_sets_source_stock_entry(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import ( + make_stock_entry as make_stock_entry_test_record, + ) + + raw_item = make_item("Test Raw Auto Set Disassembly", {"is_stock_item": 1}).name + fg_item = make_item("Test FG Auto Set Disassembly", {"is_stock_item": 1}).name + bom = make_bom(item=fg_item, quantity=1, raw_materials=[raw_item], rm_qty=2) + + wo = make_wo_order_test_record(production_item=fg_item, qty=5, bom_no=bom.name, status="Not Started") + + make_stock_entry_test_record( + item_code=raw_item, purpose="Material Receipt", target=wo.wip_warehouse, qty=50, basic_rate=100 + ) + + se_transfer = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", wo.qty)) + for item in se_transfer.items: + item.s_warehouse = wo.wip_warehouse + se_transfer.save() + se_transfer.submit() + + se_manufacture = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", wo.qty)) + se_manufacture.submit() + + # Disassemble without specifying source_stock_entry + stock_entry = frappe.get_doc(make_stock_entry(wo.name, "Disassemble", 3)) + stock_entry.save() + + # source_stock_entry should be auto-set since only one manufacture entry + self.assertEqual(stock_entry.source_stock_entry, se_manufacture.name) + + # All items should have against_stock_entry linked + for item in stock_entry.items: + self.assertEqual(item.against_stock_entry, se_manufacture.name) + self.assertTrue(item.ste_detail) + + stock_entry.submit() + + def test_disassembly_batch_tracked_items(self): + from erpnext.stock.doctype.batch.batch import make_batch + from erpnext.stock.doctype.stock_entry.test_stock_entry import ( + make_stock_entry as make_stock_entry_test_record, + ) + + wip_wh = "_Test Warehouse - _TC" + + rm_item = make_item( + "Test Batch RM for Disassembly SB", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TBRD-RM-.###", + }, + ).name + fg_item = make_item( + "Test Batch FG for Disassembly SB", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TBRD-FG-.###", + }, + ).name + + bom = make_bom(item=fg_item, quantity=1, raw_materials=[rm_item], rm_qty=2) + wo = make_wo_order_test_record( + production_item=fg_item, + qty=6, + bom_no=bom.name, + skip_transfer=1, + source_warehouse=wip_wh, + status="Not Started", + ) + + # Stock up RM — batch auto-created on receipt + rm_receipt = make_stock_entry_test_record( + item_code=rm_item, purpose="Material Receipt", target=wip_wh, qty=18, basic_rate=100 + ) + rm_bundle = frappe.db.get_value( + "Stock Entry Detail", {"parent": rm_receipt.name, "item_code": rm_item}, "serial_and_batch_bundle" + ) + rm_batch = get_batch_from_bundle(rm_bundle) + + # Pre-create FG batch so we can assign it to the manufacture row + fg_batch = make_batch(frappe._dict(item=fg_item)) + + # Manufacture 3 units: assign batches explicitly on RM and FG rows + se_manufacture = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 3)) + for row in se_manufacture.items: + if row.item_code == rm_item: + row.batch_no = rm_batch + row.use_serial_batch_fields = 1 + elif row.item_code == fg_item: + row.batch_no = fg_batch + row.use_serial_batch_fields = 1 + se_manufacture.save() + se_manufacture.submit() + + # Disassemble 2 of the 3 manufactured units linked to the manufacture SE + disassemble_qty = 2 + stock_entry = frappe.get_doc( + make_stock_entry(wo.name, "Disassemble", disassemble_qty, source_stock_entry=se_manufacture.name) + ) + stock_entry.save() + stock_entry.submit() + + # FG row: consuming batch from FG warehouse — bundle must use FG batch + fg_row = next((i for i in stock_entry.items if i.item_code == fg_item), None) + self.assertIsNotNone(fg_row) + self.assertTrue(fg_row.serial_and_batch_bundle, "FG row must have a serial_and_batch_bundle") + self.assertEqual(get_batch_from_bundle(fg_row.serial_and_batch_bundle), fg_batch) + + # RM row: returning to WIP warehouse — bundle must use RM batch + rm_row = next((i for i in stock_entry.items if i.item_code == rm_item), None) + self.assertIsNotNone(rm_row) + self.assertTrue(rm_row.serial_and_batch_bundle, "RM row must have a serial_and_batch_bundle") + self.assertEqual(get_batch_from_bundle(rm_row.serial_and_batch_bundle), rm_batch) + + # RM qty: 2 FG disassembled x 2 RM per FG = 4 + self.assertAlmostEqual(rm_row.qty, 4.0, places=3) + + def test_disassembly_serial_tracked_items(self): + from frappe.model.naming import make_autoname + + from erpnext.stock.doctype.stock_entry.test_stock_entry import ( + make_stock_entry as make_stock_entry_test_record, + ) + + wip_wh = "_Test Warehouse - _TC" + + rm_item = make_item( + "Test Serial RM for Disassembly SB", + {"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "TSRD-RM-.####"}, + ).name + fg_item = make_item( + "Test Serial FG for Disassembly SB", + {"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "TSRD-FG-.####"}, + ).name + + bom = make_bom(item=fg_item, quantity=1, raw_materials=[rm_item], rm_qty=2) + wo = make_wo_order_test_record( + production_item=fg_item, + qty=6, + bom_no=bom.name, + skip_transfer=1, + source_warehouse=wip_wh, + status="Not Started", + ) + + # Stock up 6 RM serials — series auto-generates them + rm_receipt = make_stock_entry_test_record( + item_code=rm_item, purpose="Material Receipt", target=wip_wh, qty=6, basic_rate=100 + ) + rm_bundle = frappe.db.get_value( + "Stock Entry Detail", {"parent": rm_receipt.name, "item_code": rm_item}, "serial_and_batch_bundle" + ) + all_rm_serials = get_serial_nos_from_bundle(rm_bundle) + self.assertEqual(len(all_rm_serials), 6) + + # Pre-generate 3 FG serial numbers + series = frappe.db.get_value("Item", fg_item, "serial_no_series") + fg_serials = [make_autoname(series) for _ in range(3)] + + # Manufacture 3 units: consume first 6 RM serials, produce 3 FG serials + se_manufacture = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 3)) + for row in se_manufacture.items: + if row.item_code == rm_item: + row.serial_no = "\n".join(all_rm_serials) + row.use_serial_batch_fields = 1 + elif row.item_code == fg_item: + row.serial_no = "\n".join(fg_serials) + row.use_serial_batch_fields = 1 + se_manufacture.save() + se_manufacture.submit() + + # Disassemble 2 of the 3 manufactured units + disassemble_qty = 2 + stock_entry = frappe.get_doc( + make_stock_entry(wo.name, "Disassemble", disassemble_qty, source_stock_entry=se_manufacture.name) + ) + stock_entry.save() + stock_entry.submit() + + # FG row: 2 serials consumed — must be a subset of the manufacture FG serials + fg_row = next((i for i in stock_entry.items if i.item_code == fg_item), None) + self.assertIsNotNone(fg_row) + self.assertTrue(fg_row.serial_and_batch_bundle, "FG row must have a serial_and_batch_bundle") + fg_dasm_serials = get_serial_nos_from_bundle(fg_row.serial_and_batch_bundle) + self.assertEqual(len(fg_dasm_serials), disassemble_qty) + self.assertTrue(set(fg_dasm_serials).issubset(set(fg_serials))) + + # RM row: 4 serials returned (2 FG x 2 RM each) — must be a subset of manufacture RM serials + rm_row = next((i for i in stock_entry.items if i.item_code == rm_item), None) + self.assertIsNotNone(rm_row) + self.assertTrue(rm_row.serial_and_batch_bundle, "RM row must have a serial_and_batch_bundle") + rm_dasm_serials = get_serial_nos_from_bundle(rm_row.serial_and_batch_bundle) + self.assertEqual(len(rm_dasm_serials), disassemble_qty * 2) + self.assertTrue(set(rm_dasm_serials).issubset(set(all_rm_serials))) + def test_components_alternate_item_for_bom_based_manufacture_entry(self): frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM") frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 1) From d50279b718c195ba6f2735d448bc4736f3d4bfcd Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 1 Apr 2026 15:48:25 +0530 Subject: [PATCH 125/168] fix: handle disassembly for secondary / scrap items (cherry picked from commit 2be8313819c7811afc3758a037457d46f3cae244) --- .../stock/doctype/stock_entry/stock_entry.py | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 3aee2be095a..046e325375a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -864,7 +864,7 @@ class StockEntry(StockController, SubcontractingInwardController): if self.purpose == "Disassemble": if has_bom: - if d.is_finished_item: + if d.is_finished_item or d.type or d.is_legacy_scrap_item: d.t_warehouse = None if not d.s_warehouse: frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx)) @@ -2369,10 +2369,16 @@ class StockEntry(StockController, SubcontractingInwardController): qty = flt(self.fg_completed_qty) s_warehouse = self.from_warehouse or source_row.t_warehouse t_warehouse = "" - else: + elif source_row.s_warehouse: + # RM: was consumed FROM s_warehouse → return TO s_warehouse qty = flt(source_row.qty * scale_factor) s_warehouse = "" t_warehouse = self.to_warehouse or source_row.s_warehouse + else: + # Scrap/secondary: was produced TO t_warehouse → take FROM t_warehouse + qty = flt(source_row.qty * scale_factor) + s_warehouse = source_row.t_warehouse + t_warehouse = "" use_serial_batch_fields = 1 if (source_row.batch_no or source_row.serial_no) else 0 @@ -2390,6 +2396,9 @@ class StockEntry(StockController, SubcontractingInwardController): "s_warehouse": s_warehouse, "t_warehouse": t_warehouse, "is_finished_item": source_row.is_finished_item, + "type": source_row.type, + "is_legacy_scrap_item": source_row.is_legacy_scrap_item, + "bom_secondary_item": source_row.bom_secondary_item, "against_stock_entry": self.source_stock_entry, "ste_detail": source_row.name, # batch and serial bundles built on submit @@ -2424,6 +2433,16 @@ class StockEntry(StockController, SubcontractingInwardController): }, ) + # Secondary/Scrap items + secondary_items = self.get_secondary_items(self.fg_completed_qty) + if secondary_items: + scrap_warehouse = wo.scrap_warehouse or self.from_warehouse or wo.fg_warehouse + for item in secondary_items.values(): + item["from_warehouse"] = scrap_warehouse + item["to_warehouse"] = "" + item["is_finished_item"] = 0 + self.add_to_stock_entry_detail(secondary_items, bom_no=self.bom_no) + # FG self.append( "items", @@ -2455,6 +2474,23 @@ class StockEntry(StockController, SubcontractingInwardController): self.add_to_stock_entry_detail(item_dict) + # Secondary/Scrap items (reverse of what set_secondary_items does for Manufacture) + secondary_items = self.get_secondary_items(self.fg_completed_qty) + if secondary_items: + scrap_warehouse = self.from_warehouse + if self.work_order: + wo_values = frappe.db.get_value( + "Work Order", self.work_order, ["scrap_warehouse", "fg_warehouse"], as_dict=True + ) + scrap_warehouse = wo_values.scrap_warehouse or scrap_warehouse or wo_values.fg_warehouse + + for item in secondary_items.values(): + item["from_warehouse"] = scrap_warehouse + item["to_warehouse"] = "" + item["is_finished_item"] = 0 + + self.add_to_stock_entry_detail(secondary_items, bom_no=self.bom_no) + # Finished goods self.load_items_from_bom() @@ -2478,6 +2514,9 @@ class StockEntry(StockController, SubcontractingInwardController): SED.basic_rate, SED.conversion_factor, SED.is_finished_item, + SED.type, + SED.is_legacy_scrap_item, + SED.bom_secondary_item, SED.batch_no, SED.serial_no, SED.use_serial_batch_fields, From 901e6267293d3c02a0c98ea21b1047143d063a19 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 1 Apr 2026 16:17:00 +0530 Subject: [PATCH 126/168] test: disassembly for scrap / secondary item (cherry picked from commit a6d41151ff33e99d8e5c189ee90b4c3707537956) --- .../doctype/work_order/test_work_order.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index ac9ce2091a9..77a9acdf02e 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2527,7 +2527,8 @@ class TestWorkOrder(ERPNextTestSuite): def test_disassembly_with_multiple_manufacture_entries(self): """ Test that disassembly does not create duplicate items when manufacturing - is done in multiple batches (multiple manufacture stock entries). + is done in multiple batches (multiple manufacture stock entries), including + secondary/scrap items. Scenario: 1. Create Work Order for 10 units @@ -2536,11 +2537,19 @@ class TestWorkOrder(ERPNextTestSuite): 4. Create Disassembly for 4 units 5. Verify no duplicate items in the disassembly stock entry """ - # Create RM and FG item + # Create RM, scrap and FG item raw_item1 = make_item("Test Raw for Multi Batch Disassembly 1", {"is_stock_item": 1}).name raw_item2 = make_item("Test Raw for Multi Batch Disassembly 2", {"is_stock_item": 1}).name + scrap_item = make_item("Test Scrap for Multi Batch Disassembly", {"is_stock_item": 1}).name fg_item = make_item("Test FG for Multi Batch Disassembly", {"is_stock_item": 1}).name - bom = make_bom(item=fg_item, quantity=1, raw_materials=[raw_item1, raw_item2], rm_qty=2) + bom = make_bom( + item=fg_item, + quantity=1, + raw_materials=[raw_item1, raw_item2], + rm_qty=2, + scrap_items=[scrap_item], + scrap_qty=10, + ) # Create WO wo = make_wo_order_test_record(production_item=fg_item, qty=10, bom_no=bom.name, status="Not Started") @@ -2615,7 +2624,7 @@ class TestWorkOrder(ERPNextTestSuite): f"Found duplicate items in disassembly stock entry: {duplicates}", ) - expected_items = 3 # FG item + 2 raw materials + expected_items = 4 # FG item + 2 raw materials + 1 scrap item self.assertEqual( len(stock_entry.items), expected_items, @@ -2626,6 +2635,15 @@ class TestWorkOrder(ERPNextTestSuite): fg_item_row = next((i for i in stock_entry.items if i.item_code == fg_item), None) self.assertEqual(fg_item_row.qty, disassemble_qty) + # Secondary/Scrap item: should be taken from scrap warehouse in disassembly + scrap_row = next((i for i in stock_entry.items if i.item_code == scrap_item), None) + self.assertIsNotNone(scrap_row) + self.assertEqual(scrap_row.type, "Scrap") + self.assertTrue(scrap_row.s_warehouse) + self.assertFalse(scrap_row.t_warehouse) + self.assertEqual(scrap_row.s_warehouse, wo.scrap_warehouse) + self.assertEqual(scrap_row.qty, 40) + # RM quantities for bom_item in bom.items: expected_qty = (bom_item.qty / bom.quantity) * disassemble_qty From 31ac46ae4c04ce379def08dc2cadb84f5e5a1261 Mon Sep 17 00:00:00 2001 From: vorasmit Date: Wed, 1 Apr 2026 23:42:36 +0530 Subject: [PATCH 127/168] fix: manufacture entry with group_by support (cherry picked from commit 3cf1ce83608b51a0e98663378670ed810bd6305c) --- .../stock/doctype/stock_entry/stock_entry.py | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 046e325375a..92ff6d7da83 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2494,37 +2494,43 @@ class StockEntry(StockController, SubcontractingInwardController): # Finished goods self.load_items_from_bom() - def get_items_from_manufacture_stock_entry(self, stock_entry): + def get_items_from_manufacture_stock_entry(self, stock_entry=None): SE = frappe.qb.DocType("Stock Entry") SED = frappe.qb.DocType("Stock Entry Detail") + query = frappe.qb.from_(SED).join(SE).on(SED.parent == SE.name).where(SE.docstatus == 1) + + common_fields = [ + SED.item_code, + SED.item_name, + SED.description, + SED.stock_uom, + SED.uom, + SED.basic_rate, + SED.conversion_factor, + SED.is_finished_item, + SED.type, + SED.is_legacy_scrap_item, + SED.bom_secondary_item, + SED.batch_no, + SED.serial_no, + SED.use_serial_batch_fields, + SED.s_warehouse, + SED.t_warehouse, + ] + + if stock_entry: + return ( + query.select(SED.name, SED.qty, SED.transfer_qty, *common_fields) + .where(SE.name == stock_entry) + .orderby(SED.idx) + .run(as_dict=True) + ) return ( - frappe.qb.from_(SED) - .join(SE) - .on(SED.parent == SE.name) - .select( - SED.name, - SED.item_code, - SED.item_name, - SED.description, - SED.qty, - SED.transfer_qty, - SED.stock_uom, - SED.uom, - SED.basic_rate, - SED.conversion_factor, - SED.is_finished_item, - SED.type, - SED.is_legacy_scrap_item, - SED.bom_secondary_item, - SED.batch_no, - SED.serial_no, - SED.use_serial_batch_fields, - SED.s_warehouse, - SED.t_warehouse, - ) - .where(SE.name == stock_entry) - .where(SE.docstatus == 1) + query.select(Sum(SED.qty).as_("qty"), Sum(SED.transfer_qty).as_("transfer_qty"), *common_fields) + .where(SE.purpose == "Manufacture") + .where(SE.work_order == self.work_order) + .groupby(SED.item_code) .orderby(SED.idx) .run(as_dict=True) ) From 0ceb08410475c82157dbaff6431dcd06ae1031e2 Mon Sep 17 00:00:00 2001 From: vorasmit Date: Wed, 1 Apr 2026 23:56:44 +0530 Subject: [PATCH 128/168] fix: avg stock entries for disassembly from WO (cherry picked from commit 71fd18bdf93630410377c840342f1cd36933e3d6) --- .../doctype/work_order/test_work_order.py | 4 +- .../stock/doctype/stock_entry/stock_entry.py | 153 ++++++++---------- 2 files changed, 67 insertions(+), 90 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 77a9acdf02e..e5ae8ec39ec 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2642,7 +2642,9 @@ class TestWorkOrder(ERPNextTestSuite): self.assertTrue(scrap_row.s_warehouse) self.assertFalse(scrap_row.t_warehouse) self.assertEqual(scrap_row.s_warehouse, wo.scrap_warehouse) - self.assertEqual(scrap_row.qty, 40) + # BOM has scrap_qty=10/FG but also process_loss_per=10%, so actual scrap per FG = 9 + # Total produced = 9*3 + 9*7 = 90, disassemble 4/10 → 36 + self.assertEqual(scrap_row.qty, 36) # RM quantities for bom_item in bom.items: diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 92ff6d7da83..9d73a3f117c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2328,7 +2328,7 @@ class StockEntry(StockController, SubcontractingInwardController): Priority: 1. From a specific Manufacture Stock Entry (exact reversal) - 2. From Work Order required_items (reflects WO changes) + 2. From Work Order Manufacture Stock Entries (averaged reversal) 3. From BOM (standalone disassembly) """ @@ -2362,104 +2362,79 @@ class StockEntry(StockController, SubcontractingInwardController): _("Source Stock Entry {0} has no finished goods quantity").format(self.source_stock_entry) ) - scale_factor = flt(self.fg_completed_qty) / flt(source_fg_qty) + disassemble_qty = flt(self.fg_completed_qty) + scale_factor = disassemble_qty / flt(source_fg_qty) - for source_row in self.get_items_from_manufacture_stock_entry(self.source_stock_entry): - if source_row.is_finished_item: - qty = flt(self.fg_completed_qty) - s_warehouse = self.from_warehouse or source_row.t_warehouse - t_warehouse = "" - elif source_row.s_warehouse: - # RM: was consumed FROM s_warehouse → return TO s_warehouse - qty = flt(source_row.qty * scale_factor) - s_warehouse = "" - t_warehouse = self.to_warehouse or source_row.s_warehouse - else: - # Scrap/secondary: was produced TO t_warehouse → take FROM t_warehouse - qty = flt(source_row.qty * scale_factor) - s_warehouse = source_row.t_warehouse - t_warehouse = "" - - use_serial_batch_fields = 1 if (source_row.batch_no or source_row.serial_no) else 0 - - self.append( - "items", - { - "item_code": source_row.item_code, - "item_name": source_row.item_name, - "description": source_row.description, - "stock_uom": source_row.stock_uom, - "uom": source_row.uom, - "conversion_factor": source_row.conversion_factor, - "basic_rate": source_row.basic_rate, - "qty": qty, - "s_warehouse": s_warehouse, - "t_warehouse": t_warehouse, - "is_finished_item": source_row.is_finished_item, - "type": source_row.type, - "is_legacy_scrap_item": source_row.is_legacy_scrap_item, - "bom_secondary_item": source_row.bom_secondary_item, - "against_stock_entry": self.source_stock_entry, - "ste_detail": source_row.name, - # batch and serial bundles built on submit - "use_serial_batch_fields": use_serial_batch_fields, - }, - ) + self._append_disassembly_row_from_source( + disassemble_qty=disassemble_qty, + scale_factor=scale_factor, + source_stock_entry=self.source_stock_entry, + ) def _add_items_for_disassembly_from_work_order(self): wo = frappe.get_doc("Work Order", self.work_order) - if not wo.required_items: - return self._add_items_for_disassembly_from_bom() + wo_produced_qty = flt(wo.produced_qty) + if wo_produced_qty <= 0: + frappe.throw(_("Work Order {0} has no produced qty").format(self.work_order)) - scale_factor = flt(self.fg_completed_qty) / flt(wo.qty) if flt(wo.qty) else 0 + disassemble_qty = flt(self.fg_completed_qty) + if disassemble_qty <= 0: + frappe.throw(_("Disassemble Qty cannot be less than or equal to 0.")) - # RMs - for ri in wo.required_items: - self.append( - "items", - { - "item_code": ri.item_code, - "item_name": ri.item_name, - "description": ri.description, - "qty": flt(ri.required_qty * scale_factor), - "stock_uom": ri.stock_uom, - "uom": ri.stock_uom, - "conversion_factor": 1, - # manufacture transfers RMs from WIP (not source warehouse) - "t_warehouse": self.to_warehouse or wo.wip_warehouse, - "s_warehouse": "", - "is_finished_item": 0, - }, - ) + scale_factor = disassemble_qty / wo_produced_qty - # Secondary/Scrap items - secondary_items = self.get_secondary_items(self.fg_completed_qty) - if secondary_items: - scrap_warehouse = wo.scrap_warehouse or self.from_warehouse or wo.fg_warehouse - for item in secondary_items.values(): - item["from_warehouse"] = scrap_warehouse - item["to_warehouse"] = "" - item["is_finished_item"] = 0 - self.add_to_stock_entry_detail(secondary_items, bom_no=self.bom_no) - - # FG - self.append( - "items", - { - "item_code": wo.production_item, - "item_name": wo.item_name, - "description": wo.description, - "qty": flt(self.fg_completed_qty), - "stock_uom": wo.stock_uom, - "uom": wo.stock_uom, - "conversion_factor": 1, - "s_warehouse": self.from_warehouse or wo.fg_warehouse, - "t_warehouse": "", - "is_finished_item": 1, - }, + self._append_disassembly_row_from_source( + disassemble_qty=disassemble_qty, + scale_factor=scale_factor, ) + def _append_disassembly_row_from_source(self, disassemble_qty, scale_factor, source_stock_entry=None): + for source_row in self.get_items_from_manufacture_stock_entry(self.source_stock_entry): + if source_row.is_finished_item: + qty = disassemble_qty + s_warehouse = self.from_warehouse or source_row.t_warehouse + t_warehouse = "" + elif source_row.s_warehouse: + # RM: was consumed FROM s_warehouse -> return TO s_warehouse + qty = flt(source_row.qty * scale_factor) + s_warehouse = "" + t_warehouse = self.to_warehouse or source_row.s_warehouse + else: + # Scrap/secondary: was produced TO t_warehouse -> take FROM t_warehouse + qty = flt(source_row.qty * scale_factor) + s_warehouse = source_row.t_warehouse + t_warehouse = "" + + item = { + "item_code": source_row.item_code, + "item_name": source_row.item_name, + "description": source_row.description, + "stock_uom": source_row.stock_uom, + "uom": source_row.uom, + "conversion_factor": source_row.conversion_factor, + "basic_rate": source_row.basic_rate, + "qty": qty, + "s_warehouse": s_warehouse, + "t_warehouse": t_warehouse, + "is_finished_item": source_row.is_finished_item, + "type": source_row.type, + "is_legacy_scrap_item": source_row.is_legacy_scrap_item, + "bom_secondary_item": source_row.bom_secondary_item, + # batch and serial bundles built on submit + "use_serial_batch_fields": 1 if (source_row.batch_no or source_row.serial_no) else 0, + } + + if source_stock_entry: + item.update( + { + "against_stock_entry": source_stock_entry, + "ste_detail": source_row.name, + } + ) + + self.append("items", item) + def _add_items_for_disassembly_from_bom(self): if not self.bom_no or not self.fg_completed_qty: frappe.throw(_("BOM and Finished Good Quantity is mandatory for Disassembly")) From e4eb88d80b3cdeb4124aa07ffba2868315904248 Mon Sep 17 00:00:00 2001 From: vorasmit Date: Fri, 3 Apr 2026 16:19:43 +0530 Subject: [PATCH 129/168] fix: use get_value (cherry picked from commit a71e8bb116a5ca8eb72d3e7b1815a38245757159) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 9d73a3f117c..be184de0e20 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2372,9 +2372,9 @@ class StockEntry(StockController, SubcontractingInwardController): ) def _add_items_for_disassembly_from_work_order(self): - wo = frappe.get_doc("Work Order", self.work_order) + wo_produced_qty = frappe.db.get_value("Work Order", self.work_order, "produced_qty") - wo_produced_qty = flt(wo.produced_qty) + wo_produced_qty = flt(wo_produced_qty) if wo_produced_qty <= 0: frappe.throw(_("Work Order {0} has no produced qty").format(self.work_order)) From b030eeafb8d920f32f84f7f4e62e84b607297c94 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 6 Apr 2026 20:08:38 +0530 Subject: [PATCH 130/168] fix: validate work order consistency in stock entry (cherry picked from commit ea392b2009a478eb06307aa3d63ca863488eecef) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index be184de0e20..3cbd6a5b4db 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -907,6 +907,16 @@ class StockEntry(StockController, SubcontractingInwardController): if not self.get("source_stock_entry"): return + if self.work_order: + source_wo = frappe.db.get_value("Stock Entry", self.source_stock_entry, "work_order") + if source_wo and source_wo != self.work_order: + frappe.throw( + _( + "Source Stock Entry {0} belongs to Work Order {1}, not {2}. Please use a manufacture entry from the same Work Order." + ).format(self.source_stock_entry, source_wo, self.work_order), + title=_("Work Order Mismatch"), + ) + from erpnext.manufacturing.doctype.work_order.work_order import get_disassembly_available_qty available_qty = get_disassembly_available_qty(self.source_stock_entry, self.name) @@ -2704,7 +2714,7 @@ class StockEntry(StockController, SubcontractingInwardController): sorted_items = sorted(self.items, key=lambda x: x.item_code) if self.purpose == "Manufacture": # ensure finished item at last - sorted_items = sorted(sorted_items, key=lambda x: (x.t_warehouse)) + sorted_items = sorted(sorted_items, key=lambda x: x.t_warehouse) idx = 0 for row in sorted_items: From 0a257ea63d6820a49b13b713f1ffa6a4185ad479 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 7 Apr 2026 09:59:54 +0530 Subject: [PATCH 131/168] fix: process loss with bom path disassembly (cherry picked from commit 93ad48bc1bf7f21e280c0fc068c8e04ab8c422ec) --- .../doctype/work_order/test_work_order.py | 32 ++++++++++++++++++- .../stock/doctype/stock_entry/stock_entry.py | 6 ++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index e5ae8ec39ec..8c135d297d9 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -6,7 +6,7 @@ from collections import defaultdict import frappe from frappe.tests import timeout -from frappe.utils import add_days, add_months, add_to_date, cint, flt, now, today +from frappe.utils import add_days, add_months, add_to_date, cint, flt, now, nowdate, nowtime, today from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError from erpnext.manufacturing.doctype.job_card.job_card import make_stock_entry as make_stock_entry_from_jc @@ -2657,6 +2657,36 @@ class TestWorkOrder(ERPNextTestSuite): msg=f"Raw material {bom_item.item_code} qty mismatch", ) + # -- BOM-path disassembly (no source_stock_entry, no work_order) -- + bom_disassemble_qty = 2 + bom_se = frappe.get_doc( + { + "doctype": "Stock Entry", + "stock_entry_type": "Disassemble", + "purpose": "Disassemble", + "from_bom": 1, + "bom_no": bom.name, + "fg_completed_qty": bom_disassemble_qty, + "from_warehouse": wo.fg_warehouse, + "to_warehouse": wo.wip_warehouse, + "company": wo.company, + "posting_date": nowdate(), + "posting_time": nowtime(), + } + ) + bom_se.get_items() + bom_se.save() + bom_se.submit() + + bom_scrap_row = next((i for i in bom_se.items if i.item_code == scrap_item), None) + self.assertIsNotNone(bom_scrap_row, "Scrap item must appear in BOM-path disassembly") + # Without fix 3: qty = 10 * 2 = 20; with fix 3 (process_loss_per=10%): qty = 9 * 2 = 18 + self.assertEqual( + bom_scrap_row.qty, + 18, + f"BOM-path disassembly must apply process_loss_per; expected 18, got {bom_scrap_row.qty}", + ) + def test_disassembly_with_additional_rm_not_in_bom(self): """ Test that SE-linked disassembly includes additional raw materials diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 3cbd6a5b4db..0f954c88493 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2474,6 +2474,12 @@ class StockEntry(StockController, SubcontractingInwardController): item["to_warehouse"] = "" item["is_finished_item"] = 0 + if item.get("process_loss_per"): + item["qty"] -= flt( + item["qty"] * (item["process_loss_per"] / 100), + self.precision("fg_completed_qty"), + ) + self.add_to_stock_entry_detail(secondary_items, bom_no=self.bom_no) # Finished goods From fb1d865e9b6b0f47e09ab9521bc7bf6b8c0efd9b Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 7 Apr 2026 10:00:30 +0530 Subject: [PATCH 132/168] fix: set bom details on disassembly; abs batch qty (cherry picked from commit ab1fc2243141d69739bbe4467e2e2584171c199b) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 0f954c88493..5d24abaee98 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -369,7 +369,7 @@ class StockEntry(StockController, SubcontractingInwardController): for batch_no, batch_qty in source_bundle["batch_nos"].items(): if qty_remaining <= 0: break - alloc = min(flt(batch_qty) * scale_factor, qty_remaining) + alloc = min(abs(flt(batch_qty)) * scale_factor, qty_remaining) batches[batch_no] = alloc qty_remaining -= alloc elif source_row.batch_no: @@ -2431,6 +2431,7 @@ class StockEntry(StockController, SubcontractingInwardController): "type": source_row.type, "is_legacy_scrap_item": source_row.is_legacy_scrap_item, "bom_secondary_item": source_row.bom_secondary_item, + "bom_no": source_row.bom_no, # batch and serial bundles built on submit "use_serial_batch_fields": 1 if (source_row.batch_no or source_row.serial_no) else 0, } @@ -2507,6 +2508,7 @@ class StockEntry(StockController, SubcontractingInwardController): SED.use_serial_batch_fields, SED.s_warehouse, SED.t_warehouse, + SED.bom_no, ] if stock_entry: From d4fde552f42d623c293bb1ee5e36e31cc05ece84 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 7 Apr 2026 10:27:41 +0530 Subject: [PATCH 133/168] test: maintain sufficient stock for scrap item (cherry picked from commit b892139342ea8c2aa7a1c1a65b2db80992f7a8aa) --- .../manufacturing/doctype/work_order/test_work_order.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 8c135d297d9..4acf3fe0d23 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2658,6 +2658,15 @@ class TestWorkOrder(ERPNextTestSuite): ) # -- BOM-path disassembly (no source_stock_entry, no work_order) -- + + make_stock_entry_test_record( + item_code=scrap_item, + purpose="Material Receipt", + target=wo.fg_warehouse, + qty=50, + basic_rate=10, + ) + bom_disassemble_qty = 2 bom_se = frappe.get_doc( { From 6cebea314d0b13ff5076d91e79196688e2047d1d Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 7 Apr 2026 10:54:34 +0530 Subject: [PATCH 134/168] test: enhance tests as per review comments (cherry picked from commit f13d37fbf93a7a3f18cf430f3a36515de8bb0a3a) --- .../doctype/work_order/test_work_order.py | 155 +++++++++++++----- 1 file changed, 113 insertions(+), 42 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 4acf3fe0d23..8a13ed11fe2 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2915,49 +2915,81 @@ class TestWorkOrder(ERPNextTestSuite): status="Not Started", ) - # Stock up RM — batch auto-created on receipt - rm_receipt = make_stock_entry_test_record( - item_code=rm_item, purpose="Material Receipt", target=wip_wh, qty=18, basic_rate=100 + # Two separate RM receipts → two distinct batches (batch_1, batch_2) + rm_receipt_1 = make_stock_entry_test_record( + item_code=rm_item, purpose="Material Receipt", target=wip_wh, qty=6, basic_rate=100 ) - rm_bundle = frappe.db.get_value( - "Stock Entry Detail", {"parent": rm_receipt.name, "item_code": rm_item}, "serial_and_batch_bundle" + rm_batch_1 = get_batch_from_bundle( + frappe.db.get_value( + "Stock Entry Detail", + {"parent": rm_receipt_1.name, "item_code": rm_item}, + "serial_and_batch_bundle", + ) ) - rm_batch = get_batch_from_bundle(rm_bundle) - # Pre-create FG batch so we can assign it to the manufacture row - fg_batch = make_batch(frappe._dict(item=fg_item)) + rm_receipt_2 = make_stock_entry_test_record( + item_code=rm_item, purpose="Material Receipt", target=wip_wh, qty=6, basic_rate=100 + ) + rm_batch_2 = get_batch_from_bundle( + frappe.db.get_value( + "Stock Entry Detail", + {"parent": rm_receipt_2.name, "item_code": rm_item}, + "serial_and_batch_bundle", + ) + ) - # Manufacture 3 units: assign batches explicitly on RM and FG rows - se_manufacture = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 3)) - for row in se_manufacture.items: + self.assertNotEqual(rm_batch_1, rm_batch_2, "Two receipts must create two distinct RM batches") + + fg_batch_1 = make_batch(frappe._dict(item=fg_item)) + fg_batch_2 = make_batch(frappe._dict(item=fg_item)) + + # Manufacture entry 1 — 3 FG using batch_1 RM/FG + se_manufacture_1 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 3)) + for row in se_manufacture_1.items: if row.item_code == rm_item: - row.batch_no = rm_batch + row.batch_no = rm_batch_1 row.use_serial_batch_fields = 1 elif row.item_code == fg_item: - row.batch_no = fg_batch + row.batch_no = fg_batch_1 row.use_serial_batch_fields = 1 - se_manufacture.save() - se_manufacture.submit() + se_manufacture_1.save() + se_manufacture_1.submit() - # Disassemble 2 of the 3 manufactured units linked to the manufacture SE + # Manufacture entry 2 — 3 FG using batch_2 RM/FG + se_manufacture_2 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 3)) + for row in se_manufacture_2.items: + if row.item_code == rm_item: + row.batch_no = rm_batch_2 + row.use_serial_batch_fields = 1 + elif row.item_code == fg_item: + row.batch_no = fg_batch_2 + row.use_serial_batch_fields = 1 + se_manufacture_2.save() + se_manufacture_2.submit() + + # Disassemble 2 units from SE_1 only — must use SE_1's batches, not SE_2's disassemble_qty = 2 stock_entry = frappe.get_doc( - make_stock_entry(wo.name, "Disassemble", disassemble_qty, source_stock_entry=se_manufacture.name) + make_stock_entry( + wo.name, "Disassemble", disassemble_qty, source_stock_entry=se_manufacture_1.name + ) ) stock_entry.save() stock_entry.submit() - # FG row: consuming batch from FG warehouse — bundle must use FG batch + # FG row: must use fg_batch_1 exclusively (fg_batch_2 must not appear) fg_row = next((i for i in stock_entry.items if i.item_code == fg_item), None) self.assertIsNotNone(fg_row) self.assertTrue(fg_row.serial_and_batch_bundle, "FG row must have a serial_and_batch_bundle") - self.assertEqual(get_batch_from_bundle(fg_row.serial_and_batch_bundle), fg_batch) + self.assertEqual(get_batch_from_bundle(fg_row.serial_and_batch_bundle), fg_batch_1) + self.assertNotEqual(get_batch_from_bundle(fg_row.serial_and_batch_bundle), fg_batch_2) - # RM row: returning to WIP warehouse — bundle must use RM batch + # RM row: must use rm_batch_1 exclusively (rm_batch_2 must not appear) rm_row = next((i for i in stock_entry.items if i.item_code == rm_item), None) self.assertIsNotNone(rm_row) self.assertTrue(rm_row.serial_and_batch_bundle, "RM row must have a serial_and_batch_bundle") - self.assertEqual(get_batch_from_bundle(rm_row.serial_and_batch_bundle), rm_batch) + self.assertEqual(get_batch_from_bundle(rm_row.serial_and_batch_bundle), rm_batch_1) + self.assertNotEqual(get_batch_from_bundle(rm_row.serial_and_batch_bundle), rm_batch_2) # RM qty: 2 FG disassembled x 2 RM per FG = 4 self.assertAlmostEqual(rm_row.qty, 4.0, places=3) @@ -2990,55 +3022,94 @@ class TestWorkOrder(ERPNextTestSuite): status="Not Started", ) - # Stock up 6 RM serials — series auto-generates them - rm_receipt = make_stock_entry_test_record( + # Two separate RM receipts → two disjoint sets of serial numbers + rm_receipt_1 = make_stock_entry_test_record( item_code=rm_item, purpose="Material Receipt", target=wip_wh, qty=6, basic_rate=100 ) - rm_bundle = frappe.db.get_value( - "Stock Entry Detail", {"parent": rm_receipt.name, "item_code": rm_item}, "serial_and_batch_bundle" + rm_serials_1 = get_serial_nos_from_bundle( + frappe.db.get_value( + "Stock Entry Detail", + {"parent": rm_receipt_1.name, "item_code": rm_item}, + "serial_and_batch_bundle", + ) ) - all_rm_serials = get_serial_nos_from_bundle(rm_bundle) - self.assertEqual(len(all_rm_serials), 6) + self.assertEqual(len(rm_serials_1), 6) - # Pre-generate 3 FG serial numbers + rm_receipt_2 = make_stock_entry_test_record( + item_code=rm_item, purpose="Material Receipt", target=wip_wh, qty=6, basic_rate=100 + ) + rm_serials_2 = get_serial_nos_from_bundle( + frappe.db.get_value( + "Stock Entry Detail", + {"parent": rm_receipt_2.name, "item_code": rm_item}, + "serial_and_batch_bundle", + ) + ) + self.assertEqual(len(rm_serials_2), 6) + self.assertFalse( + set(rm_serials_1) & set(rm_serials_2), "Two receipts must produce disjoint RM serial sets" + ) + + # Pre-generate two sets of FG serial numbers series = frappe.db.get_value("Item", fg_item, "serial_no_series") - fg_serials = [make_autoname(series) for _ in range(3)] + fg_serials_1 = [make_autoname(series) for _ in range(3)] + fg_serials_2 = [make_autoname(series) for _ in range(3)] - # Manufacture 3 units: consume first 6 RM serials, produce 3 FG serials - se_manufacture = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 3)) - for row in se_manufacture.items: + # Manufacture entry 1 — consumes rm_serials_1, produces fg_serials_1 + se_manufacture_1 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 3)) + for row in se_manufacture_1.items: if row.item_code == rm_item: - row.serial_no = "\n".join(all_rm_serials) + row.serial_no = "\n".join(rm_serials_1) row.use_serial_batch_fields = 1 elif row.item_code == fg_item: - row.serial_no = "\n".join(fg_serials) + row.serial_no = "\n".join(fg_serials_1) row.use_serial_batch_fields = 1 - se_manufacture.save() - se_manufacture.submit() + se_manufacture_1.save() + se_manufacture_1.submit() - # Disassemble 2 of the 3 manufactured units + # Manufacture entry 2 — consumes rm_serials_2, produces fg_serials_2 + se_manufacture_2 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 3)) + for row in se_manufacture_2.items: + if row.item_code == rm_item: + row.serial_no = "\n".join(rm_serials_2) + row.use_serial_batch_fields = 1 + elif row.item_code == fg_item: + row.serial_no = "\n".join(fg_serials_2) + row.use_serial_batch_fields = 1 + se_manufacture_2.save() + se_manufacture_2.submit() + + # Disassemble 2 units from SE_1 only — must use SE_1's serials, not SE_2's disassemble_qty = 2 stock_entry = frappe.get_doc( - make_stock_entry(wo.name, "Disassemble", disassemble_qty, source_stock_entry=se_manufacture.name) + make_stock_entry( + wo.name, "Disassemble", disassemble_qty, source_stock_entry=se_manufacture_1.name + ) ) stock_entry.save() stock_entry.submit() - # FG row: 2 serials consumed — must be a subset of the manufacture FG serials + # FG row: 2 serials consumed — must be subset of fg_serials_1, disjoint from fg_serials_2 fg_row = next((i for i in stock_entry.items if i.item_code == fg_item), None) self.assertIsNotNone(fg_row) self.assertTrue(fg_row.serial_and_batch_bundle, "FG row must have a serial_and_batch_bundle") fg_dasm_serials = get_serial_nos_from_bundle(fg_row.serial_and_batch_bundle) self.assertEqual(len(fg_dasm_serials), disassemble_qty) - self.assertTrue(set(fg_dasm_serials).issubset(set(fg_serials))) + self.assertTrue(set(fg_dasm_serials).issubset(set(fg_serials_1))) + self.assertFalse( + set(fg_dasm_serials) & set(fg_serials_2), "Disassembly must not use SE_2's FG serials" + ) - # RM row: 4 serials returned (2 FG x 2 RM each) — must be a subset of manufacture RM serials + # RM row: 4 serials returned (2 FG x 2 RM each) — subset of rm_serials_1, disjoint from rm_serials_2 rm_row = next((i for i in stock_entry.items if i.item_code == rm_item), None) self.assertIsNotNone(rm_row) self.assertTrue(rm_row.serial_and_batch_bundle, "RM row must have a serial_and_batch_bundle") rm_dasm_serials = get_serial_nos_from_bundle(rm_row.serial_and_batch_bundle) self.assertEqual(len(rm_dasm_serials), disassemble_qty * 2) - self.assertTrue(set(rm_dasm_serials).issubset(set(all_rm_serials))) + self.assertTrue(set(rm_dasm_serials).issubset(set(rm_serials_1))) + self.assertFalse( + set(rm_dasm_serials) & set(rm_serials_2), "Disassembly must not use SE_2's RM serials" + ) def test_components_alternate_item_for_bom_based_manufacture_entry(self): frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM") From 7bef9542d420fb89ed1dcf90345cbe409c08563e Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 7 Apr 2026 13:17:22 +0530 Subject: [PATCH 135/168] fix: remove unnecessary param, and use value from self (cherry picked from commit 98dfd64f6326cefc8882756eb0d30b26863a0556) --- .../stock/doctype/stock_entry/stock_entry.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 5d24abaee98..ae5c390fdae 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -345,9 +345,7 @@ class StockEntry(StockController, SubcontractingInwardController): scale_factor = flt(self.fg_completed_qty) / source_fg_qty if source_fg_qty else 0 bundle_data = get_voucher_wise_serial_batch_from_bundle(voucher_no=[self.source_stock_entry]) - source_rows_by_name = { - r.name: r for r in self.get_items_from_manufacture_stock_entry(self.source_stock_entry) - } + source_rows_by_name = {r.name: r for r in self.get_items_from_manufacture_stock_entry()} for row in self.items: if not row.ste_detail: @@ -2378,7 +2376,6 @@ class StockEntry(StockController, SubcontractingInwardController): self._append_disassembly_row_from_source( disassemble_qty=disassemble_qty, scale_factor=scale_factor, - source_stock_entry=self.source_stock_entry, ) def _add_items_for_disassembly_from_work_order(self): @@ -2399,8 +2396,8 @@ class StockEntry(StockController, SubcontractingInwardController): scale_factor=scale_factor, ) - def _append_disassembly_row_from_source(self, disassemble_qty, scale_factor, source_stock_entry=None): - for source_row in self.get_items_from_manufacture_stock_entry(self.source_stock_entry): + def _append_disassembly_row_from_source(self, disassemble_qty, scale_factor): + for source_row in self.get_items_from_manufacture_stock_entry(): if source_row.is_finished_item: qty = disassemble_qty s_warehouse = self.from_warehouse or source_row.t_warehouse @@ -2436,10 +2433,10 @@ class StockEntry(StockController, SubcontractingInwardController): "use_serial_batch_fields": 1 if (source_row.batch_no or source_row.serial_no) else 0, } - if source_stock_entry: + if self.source_stock_entry: item.update( { - "against_stock_entry": source_stock_entry, + "against_stock_entry": self.source_stock_entry, "ste_detail": source_row.name, } ) @@ -2486,7 +2483,7 @@ class StockEntry(StockController, SubcontractingInwardController): # Finished goods self.load_items_from_bom() - def get_items_from_manufacture_stock_entry(self, stock_entry=None): + def get_items_from_manufacture_stock_entry(self): SE = frappe.qb.DocType("Stock Entry") SED = frappe.qb.DocType("Stock Entry Detail") query = frappe.qb.from_(SED).join(SE).on(SED.parent == SE.name).where(SE.docstatus == 1) @@ -2511,10 +2508,10 @@ class StockEntry(StockController, SubcontractingInwardController): SED.bom_no, ] - if stock_entry: + if self.source_stock_entry: return ( query.select(SED.name, SED.qty, SED.transfer_qty, *common_fields) - .where(SE.name == stock_entry) + .where(SE.name == self.source_stock_entry) .orderby(SED.idx) .run(as_dict=True) ) From 9e83badbf5d100a01e94beb47ff71354ea0e1dab Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 7 Apr 2026 14:57:56 +0530 Subject: [PATCH 136/168] chore: resolve conflicts --- erpnext/manufacturing/doctype/work_order/work_order.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 268918eca00..5e18f68e8c0 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -2432,14 +2432,7 @@ def make_stock_entry( @frappe.whitelist() -<<<<<<< HEAD -<<<<<<< HEAD -def get_default_warehouse(company): -======= -def get_disassembly_available_qty(stock_entry_name: str) -> float: -======= def get_disassembly_available_qty(stock_entry_name: str, current_se_name: str | None = None) -> float: ->>>>>>> 6394dead72 (fix: validate qty that can be disassembled from source stock entry.) se = frappe.db.get_value("Stock Entry", stock_entry_name, ["fg_completed_qty"], as_dict=True) if not se: return 0.0 @@ -2459,8 +2452,7 @@ def get_disassembly_available_qty(stock_entry_name: str, current_se_name: str | @frappe.whitelist() -def get_default_warehouse(company: str): ->>>>>>> 68e97808c5 (fix: disassembly prompt with source stock entry field) +def get_default_warehouse(company): wip, fg, scrap = frappe.get_cached_value( "Company", company, ["default_wip_warehouse", "default_fg_warehouse", "default_scrap_warehouse"] ) From 093ca8745d9b9358ed5c163bb3ee675b56276f3f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:20:17 +0530 Subject: [PATCH 137/168] perf: optimize account balance data fetching for Chart Of Accounts (backport #53044) (#53802) Co-authored-by: Shllokkk <140623894+Shllokkk@users.noreply.github.com> --- .../accounts/doctype/account/account_tree.js | 97 +++++++++---------- erpnext/accounts/utils.py | 72 ++++++++++++++ 2 files changed, 118 insertions(+), 51 deletions(-) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 315b41560ce..5ff4e4a47e2 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -52,60 +52,55 @@ frappe.treeview_settings["Account"] = { ], root_label: "Accounts", get_tree_nodes: "erpnext.accounts.utils.get_children", - on_get_node: function (nodes, deep = false) { - if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return; + on_node_render: function (node, deep) { + const render_balances = () => { + for (let account of cur_tree.account_balance_data) { + const node = cur_tree.nodes && cur_tree.nodes[account.value]; + if (!node || node.is_root) continue; - let accounts = []; - if (deep) { - // in case of `get_all_nodes` - accounts = nodes.reduce((acc, node) => [...acc, ...node.data], []); - } else { - accounts = nodes; - } + // show Dr if positive since balance is calculated as debit - credit else show Cr + const balance = account.balance_in_account_currency || account.balance; + const dr_or_cr = balance > 0 ? __("Dr") : __("Cr"); + const format = (value, currency) => format_currency(Math.abs(value), currency); - frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => { - if (value) { - const get_balances = frappe.call({ - method: "erpnext.accounts.utils.get_account_balances", - args: { - accounts: accounts, - company: cur_tree.args.company, - include_default_fb_balances: true, - }, - }); - - get_balances.then((r) => { - if (!r.message || r.message.length == 0) return; - - for (let account of r.message) { - const node = cur_tree.nodes && cur_tree.nodes[account.value]; - if (!node || node.is_root) continue; - - // show Dr if positive since balance is calculated as debit - credit else show Cr - const balance = account.balance_in_account_currency || account.balance; - const dr_or_cr = balance > 0 ? __("Dr") : __("Cr"); - const format = (value, currency) => format_currency(Math.abs(value), currency); - - if (account.balance !== undefined) { - node.parent && node.parent.find(".balance-area").remove(); - $( - '' + - (account.balance_in_account_currency - ? format( - account.balance_in_account_currency, - account.account_currency - ) + " / " - : "") + - format(account.balance, account.company_currency) + - " " + - dr_or_cr + - "" - ).insertBefore(node.$ul); - } - } - }); + if (account.balance !== undefined) { + node.parent && node.parent.find(".balance-area").remove(); + $( + '' + + (account.account_currency != account.company_currency + ? format(account.balance_in_account_currency, account.account_currency) + + " / " + : "") + + format(account.balance, account.company_currency) + + " " + + dr_or_cr + + "" + ).insertBefore(node.$ul); + } } - }); + }; + + if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return; + if (!cur_tree.account_balance_data) { + frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => { + if (value) { + frappe.call({ + method: "erpnext.accounts.utils.get_account_balances_coa", + args: { + company: cur_tree.args.company, + include_default_fb_balances: true, + }, + callback: function (r) { + if (!r.message || r.message.length === 0) return; + cur_tree.account_balance_data = r.message || []; + render_balances(); + }, + }); + } + }); + } else { + render_balances(); + } }, add_tree_node: "erpnext.accounts.utils.add_ac", menu_items: [ diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 7a23a39497b..5b1bce2cb35 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1404,6 +1404,78 @@ def get_account_balances(accounts, company, finance_book=None, include_default_f return accounts +@frappe.whitelist() +def get_account_balances_coa(company: str, include_default_fb_balances: bool = False): + company_currency = frappe.get_cached_value("Company", company, "default_currency") + + Account = DocType("Account") + account_list = ( + frappe.qb.from_(Account) + .select(Account.name, Account.parent_account, Account.account_currency) + .where(Account.company == company) + .orderby(Account.lft) + .run(as_dict=True) + ) + + account_balances_cc = {account.get("name"): 0 for account in account_list} + + account_balances_ac = {account.get("name"): 0 for account in account_list} + + GLEntry = DocType("GL Entry") + precision = get_currency_precision() + get_ledger_balances_query = ( + frappe.qb.from_(GLEntry) + .select( + GLEntry.account, + (Sum(Round(GLEntry.debit, precision)) - Sum(Round(GLEntry.credit, precision))).as_("balance"), + ( + Sum(Round(GLEntry.debit_in_account_currency, precision)) + - Sum(Round(GLEntry.credit_in_account_currency, precision)) + ).as_("balance_in_account_currency"), + ) + .groupby(GLEntry.account) + ) + + condition_list = [GLEntry.company == company, GLEntry.is_cancelled == 0] + + default_finance_book = None + + if include_default_fb_balances: + default_finance_book = frappe.get_cached_value("Company", company, "default_finance_book") + + if default_finance_book: + condition_list.append( + (GLEntry.finance_book == default_finance_book) | (GLEntry.finance_book.isnull()) + ) + + for condition in condition_list: + get_ledger_balances_query = get_ledger_balances_query.where(condition) + + ledger_balances = get_ledger_balances_query.run(as_dict=True) + + for ledger_entry in ledger_balances: + account_balances_cc[ledger_entry.get("account")] = ledger_entry.get("balance") + account_balances_ac[ledger_entry.get("account")] = ledger_entry.get("balance_in_account_currency") + + for account in reversed(account_list): + parent = account.get("parent_account") + if parent: + account_balances_cc[parent] += account_balances_cc.get(account.get("name")) + + accounts_data = [ + { + "value": account.get("name"), + "company_currency": company_currency, + "balance": account_balances_cc.get(account.get("name")), + "account_currency": account.get("account_currency"), + "balance_in_account_currency": account_balances_ac.get(account.get("name")), + } + for account in account_list + ] + + return accounts_data + + def create_payment_gateway_account(gateway, payment_channel="Email", company=None): from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account From 7062b7153e8f4fc27c90533160b5b869816cc6b8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:05:33 +0000 Subject: [PATCH 138/168] fix: skip validate_stock_accounts in Journal Entry when perpetual inventory is disabled (backport #53554) (#53558) Co-authored-by: Saeed Kola Co-authored-by: diptanilsaha --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 23fb4fd0825..01caa360dbe 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -353,8 +353,11 @@ class JournalEntry(AccountsController): frappe.throw(_("Account {0} should be of type Expense").format(d.account)) def validate_stock_accounts(self): - if self.voucher_type == "Periodic Accounting Entry": - # Skip validation for periodic accounting entry + if ( + not erpnext.is_perpetual_inventory_enabled(self.company) + or self.voucher_type == "Periodic Accounting Entry" + ): + # Skip validation for periodic accounting entry and Perpetual Inventory Disabled Company. return stock_accounts = get_stock_accounts(self.company, accounts=self.accounts) From df3f242331053d1412592e70048546f235b5b8f5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:06:39 +0000 Subject: [PATCH 139/168] fix: sync paid and received amount (backport #53039) (#54108) Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com> fix: sync paid and received amount (#53039) --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index f1e816a9cbe..6509b2e3873 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -824,7 +824,7 @@ frappe.ui.form.on("Payment Entry", { paid_amount: function (frm) { frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate)); let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; - if (!frm.doc.received_amount) { + if (frm.doc.paid_amount) { if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) { frm.set_value("received_amount", frm.doc.paid_amount); } else if (company_currency == frm.doc.paid_to_account_currency) { @@ -845,7 +845,7 @@ frappe.ui.form.on("Payment Entry", { flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate) ); - if (!frm.doc.paid_amount) { + if (frm.doc.received_amount) { if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) { frm.set_value("paid_amount", frm.doc.received_amount); if (frm.doc.target_exchange_rate) { From 4a6fe477d4e82efdb01a61f53d6ce15a76bafbc8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:55:18 +0530 Subject: [PATCH 140/168] =?UTF-8?q?fix(promotional=5Fscheme):=20toggle=20e?= =?UTF-8?q?nable=20state=20between=20Buying=20and=20Selli=E2=80=A6=20(back?= =?UTF-8?q?port=20#54110)=20(#54112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ahmed AbuKhatwa <82771130+AhmedAbokhatwa@users.noreply.github.com> Co-authored-by: AhmedAbukhatwa fix(promotional_scheme): toggle enable state between Buying and Selli… (#54110) --- .../accounts/doctype/promotional_scheme/promotional_scheme.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.js b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.js index 920b9a99eac..8926461b01a 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.js +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.js @@ -21,10 +21,12 @@ frappe.ui.form.on("Promotional Scheme", { selling: function (frm) { frm.trigger("set_options_for_applicable_for"); + frm.toggle_enable("buying", !frm.doc.selling); }, buying: function (frm) { frm.trigger("set_options_for_applicable_for"); + frm.toggle_enable("selling", !frm.doc.buying); }, set_options_for_applicable_for: function (frm) { From 7b91566435e9acd150c70def486d4b7bda558f9f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:10:42 +0000 Subject: [PATCH 141/168] refactor: financial report template enhancements (backport #52687) (#54113) Co-authored-by: Abdeali Chharchhodawala <99460106+Abdeali099@users.noreply.github.com> --- .../account_category/account_category.json | 9 +++-- .../financial_report_template.js | 21 ++++++------ .../financial_report_template.json | 6 ++-- .../financial_report_template.py | 13 +++++++ .../financial_report_validation.py | 34 ++++++++++++------- .../test_financial_report_engine.py | 1 + 6 files changed, 56 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/doctype/account_category/account_category.json b/erpnext/accounts/doctype/account_category/account_category.json index d69d37bd78b..cc8f4103f21 100644 --- a/erpnext/accounts/doctype/account_category/account_category.json +++ b/erpnext/accounts/doctype/account_category/account_category.json @@ -26,8 +26,13 @@ ], "grid_page_length": 50, "index_web_pages_for_search": 1, - "links": [], - "modified": "2025-10-15 03:19:47.171349", + "links": [ + { + "link_doctype": "Account", + "link_fieldname": "account_category" + } + ], + "modified": "2026-02-23 01:19:49.589393", "modified_by": "Administrator", "module": "Accounts", "name": "Account Category", diff --git a/erpnext/accounts/doctype/financial_report_template/financial_report_template.js b/erpnext/accounts/doctype/financial_report_template/financial_report_template.js index 739956631fd..304da47577b 100644 --- a/erpnext/accounts/doctype/financial_report_template/financial_report_template.js +++ b/erpnext/accounts/doctype/financial_report_template/financial_report_template.js @@ -3,6 +3,8 @@ frappe.ui.form.on("Financial Report Template", { refresh(frm) { + if (frm.is_new() || frm.doc.rows.length === 0) return; + // add custom button to view missed accounts frm.add_custom_button(__("View Account Coverage"), function () { let selected_rows = frm.get_field("rows").grid.get_selected_children(); @@ -20,7 +22,7 @@ frappe.ui.form.on("Financial Report Template", { }); }, - validate(frm) { + after_save(frm) { if (!frm.doc.rows || frm.doc.rows.length === 0) { frappe.msgprint(__("At least one row is required for a financial report template")); } @@ -34,14 +36,6 @@ frappe.ui.form.on("Financial Report Row", { update_formula_label(frm, row.data_source); update_formula_description(frm, row.data_source); - if (row.data_source !== "Account Data") { - frappe.model.set_value(cdt, cdn, "balance_type", ""); - } - - if (["Blank Line", "Column Break", "Section Break"].includes(row.data_source)) { - frappe.model.set_value(cdt, cdn, "calculation_formula", ""); - } - set_up_filters_editor(frm, cdt, cdn); }, @@ -322,6 +316,8 @@ function update_formula_description(frm, data_source) { const list_style = `style="margin-bottom: var(--margin-sm); color: var(--text-muted); font-size: 0.9em;"`; const note_style = `style="margin-bottom: 0; color: var(--text-muted); font-size: 0.9em;"`; const tip_style = `style="margin-bottom: 0; color: var(--text-color); font-size: 0.85em;"`; + const code_style = `style="background: var(--bg-light-gray); padding: var(--padding-xs); border-radius: var(--border-radius); font-size: 0.85em; width: max-content; margin-bottom: var(--margin-sm);"`; + const pre_style = `style="margin: 0; border-radius: var(--border-radius)"`; let description_html = ""; @@ -382,8 +378,13 @@ function update_formula_description(frm, data_source) {
  • my_app.financial_reports.get_kpi_data
  • +
    Method Signature:
    +
    +
    def get_custom_data(filters, periods, row): 
      # filters: dict — report filters (company, period, etc.)
      # periods: list[dict] — period definitions
      # row: dict — the current report row

      return [1000.0, 1200.0, 1150.0] # one value per period
    +
    +
    Return Format:
    -

    Numbers for each period: [1000.0, 1200.0, 1150.0]

    +

    A list of numbers, one for each period: [1000.0, 1200.0, 1150.0]

    `; } else if (data_source === "Blank Line") { description_html = ` diff --git a/erpnext/accounts/doctype/financial_report_template/financial_report_template.json b/erpnext/accounts/doctype/financial_report_template/financial_report_template.json index 7383306f332..5bfd56810db 100644 --- a/erpnext/accounts/doctype/financial_report_template/financial_report_template.json +++ b/erpnext/accounts/doctype/financial_report_template/financial_report_template.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_rename": 1, "autoname": "field:template_name", "creation": "2025-08-02 04:44:15.184541", "doctype": "DocType", @@ -31,7 +30,8 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Report Type", - "options": "\nProfit and Loss Statement\nBalance Sheet\nCash Flow\nCustom Financial Statement" + "options": "\nProfit and Loss Statement\nBalance Sheet\nCash Flow\nCustom Financial Statement", + "reqd": 1 }, { "depends_on": "eval:frappe.boot.developer_mode", @@ -66,7 +66,7 @@ "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-11-14 00:11:03.508139", + "modified": "2026-02-23 01:04:05.797161", "modified_by": "Administrator", "module": "Accounts", "name": "Financial Report Template", diff --git a/erpnext/accounts/doctype/financial_report_template/financial_report_template.py b/erpnext/accounts/doctype/financial_report_template/financial_report_template.py index 69ee7e4f7dd..f30ca7b1249 100644 --- a/erpnext/accounts/doctype/financial_report_template/financial_report_template.py +++ b/erpnext/accounts/doctype/financial_report_template/financial_report_template.py @@ -32,6 +32,19 @@ class FinancialReportTemplate(Document): template_name: DF.Data # end: auto-generated types + def before_validate(self): + self.clear_hidden_fields() + + def clear_hidden_fields(self): + style_data_sources = {"Blank Line", "Column Break", "Section Break"} + + for row in self.rows: + if row.data_source != "Account Data": + row.balance_type = None + + if row.data_source in style_data_sources: + row.calculation_formula = None + def validate(self): validator = TemplateValidator(self) result = validator.validate() diff --git a/erpnext/accounts/doctype/financial_report_template/financial_report_validation.py b/erpnext/accounts/doctype/financial_report_template/financial_report_validation.py index 306fb562585..170225fa74d 100644 --- a/erpnext/accounts/doctype/financial_report_template/financial_report_validation.py +++ b/erpnext/accounts/doctype/financial_report_template/financial_report_validation.py @@ -70,8 +70,8 @@ class ValidationResult: self.warnings.append(issue) def notify_user(self) -> None: - warnings = "

    ".join(str(w) for w in self.warnings) - errors = "

    ".join(str(e) for e in self.issues) + warnings = "

    ".join(str(w) for w in self.warnings if w) + errors = "

    ".join(str(e) for e in self.issues if e) if warnings: frappe.msgprint(warnings, title=_("Warnings"), indicator="orange") @@ -99,9 +99,8 @@ class TemplateValidator: result.merge(validator.validate(self.template)) # Run row-level validations - account_fields = {field.fieldname for field in frappe.get_meta("Account").fields} for row in self.template.rows: - result.merge(self.formula_validator.validate(row, account_fields)) + result.merge(self.formula_validator.validate(row)) return result @@ -383,7 +382,8 @@ class AccountFilterValidator(Validator): """Validates account filter expressions used in Account Data rows""" def __init__(self, account_fields: set | None = None): - self.account_fields = account_fields or set(frappe.get_meta("Account")._valid_columns) + self.account_meta = frappe.get_meta("Account") + self.account_fields = account_fields or set(self.account_meta._valid_columns) def validate(self, row) -> ValidationResult: result = ValidationResult() @@ -403,7 +403,11 @@ class AccountFilterValidator(Validator): try: filter_config = json.loads(row.calculation_formula) - error = self._validate_filter_structure(filter_config, self.account_fields) + error = self._validate_filter_structure( + filter_config, + self.account_fields, + row.advanced_filtering, + ) if error: result.add_error( @@ -425,7 +429,12 @@ class AccountFilterValidator(Validator): return result - def _validate_filter_structure(self, filter_config, account_fields: set) -> str | None: + def _validate_filter_structure( + self, + filter_config, + account_fields: set, + advanced_filtering: bool = False, + ) -> str | None: # simple condition: [field, operator, value] if isinstance(filter_config, list): if len(filter_config) != 3: @@ -436,8 +445,10 @@ class AccountFilterValidator(Validator): if not isinstance(field, str) or not isinstance(operator, str): return "Field and operator must be strings" + display = (field if advanced_filtering else self.account_meta.get_label(field)) or field + if field not in account_fields: - return f"Field '{field}' is not a valid account field" + return f"Field '{display}' is not a valid Account field" if operator.casefold() not in OPERATOR_MAP: return f"Invalid operator '{operator}'" @@ -460,7 +471,7 @@ class AccountFilterValidator(Validator): # recursive for condition in conditions: - error = self._validate_filter_structure(condition, account_fields) + error = self._validate_filter_structure(condition, account_fields, advanced_filtering) if error: return error else: @@ -476,7 +487,7 @@ class FormulaValidator(Validator): self.calculation_validator = CalculationFormulaValidator(reference_codes) self.account_filter_validator = AccountFilterValidator() - def validate(self, row, account_fields: set) -> ValidationResult: + def validate(self, row) -> ValidationResult: result = ValidationResult() if not row.calculation_formula: @@ -486,9 +497,6 @@ class FormulaValidator(Validator): return self.calculation_validator.validate(row) elif row.data_source == "Account Data": - # Update account fields if provided - if account_fields: - self.account_filter_validator.account_fields = account_fields return self.account_filter_validator.validate(row) elif row.data_source == "Custom API": diff --git a/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py b/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py index a23c6bb6883..ef6f2785184 100644 --- a/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py +++ b/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py @@ -1295,6 +1295,7 @@ class TestFilterExpressionParser(FinancialReportTemplateTestCase): self.data_source = "Account Data" self.idx = 1 self.reverse_sign = 0 + self.advanced_filtering = True return MockReportRow(formula, reference_code) From b7f1677eef6a90e272131c279299e408883cbcbc Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Tue, 7 Apr 2026 16:54:44 +0530 Subject: [PATCH 142/168] fix: preserve asset movement field properties after save (cherry picked from commit 4a004a2a82b8fc7d371abe2bfd95de4173d9861b) --- erpnext/assets/doctype/asset_movement/asset_movement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js index f56c1e31f27..2e73d0d9df8 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.js +++ b/erpnext/assets/doctype/asset_movement/asset_movement.js @@ -41,7 +41,7 @@ frappe.ui.form.on("Asset Movement", { }); }, - onload: (frm) => { + refresh: (frm) => { frm.trigger("set_required_fields"); }, From e361afb6bcf1ea0eeb2e0a46b98f7a6c9b3d2a25 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 7 Apr 2026 15:45:34 +0530 Subject: [PATCH 143/168] fix: hardcoded precision causing decimal issues (cherry picked from commit 90fd6f2e40e0fca8a716ed3ad6f9a68b238ded12) --- .../purchase_invoice_item/purchase_invoice_item.json | 3 +-- .../doctype/sales_invoice/test_sales_invoice.py | 2 +- erpnext/controllers/selling_controller.py | 10 +++++----- erpnext/controllers/taxes_and_totals.py | 5 ++++- .../doctype/delivery_note_item/delivery_note_item.json | 3 +-- .../purchase_receipt_item/purchase_receipt_item.json | 3 +-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 7a55a5eb141..7399e987fa7 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -740,7 +740,6 @@ "label": "Valuation Rate", "no_copy": 1, "options": "Company:company:default_currency", - "precision": "6", "print_hide": 1, "read_only": 1 }, @@ -1008,7 +1007,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2026-03-25 18:03:33.522195", + "modified": "2026-04-07 15:40:45.687554", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 002cdc4a43c..03d8b2e7eb5 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2887,7 +2887,7 @@ class TestSalesInvoice(ERPNextTestSuite): si.submit() # Check if adjustment entry is created - self.assertTrue( + self.assertFalse( frappe.db.exists( "GL Entry", { diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 8138a94693c..5a062e19daf 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -634,11 +634,11 @@ class SellingController(StockController): if allow_at_arms_length_price: continue - rate = flt( - flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor, - d.precision("rate"), - ) - if d.rate != rate: + rate = flt(flt(d.incoming_rate) * flt(d.conversion_factor or 1.0)) + + if flt(d.rate, d.precision("incoming_rate")) != flt( + rate, d.precision("incoming_rate") + ): d.rate = rate frappe.msgprint( _( diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 7f41e8476ea..21743e57f11 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -167,8 +167,11 @@ class calculate_taxes_and_totals: bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value( "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice" ) + + do_not_round_fields = ["valuation_rate", "incoming_rate"] + for item in self.doc.items: - self.doc.round_floats_in(item) + self.doc.round_floats_in(item, do_not_round_fields=do_not_round_fields) if item.discount_percentage == 100: item.rate = 0.0 diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index d88af98bca8..0175b790887 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -759,7 +759,6 @@ "label": "Incoming Rate", "no_copy": 1, "options": "Company:company:default_currency", - "precision": "6", "print_hide": 1, "read_only": 1 }, @@ -953,7 +952,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-05-31 19:51:32.651562", + "modified": "2026-04-07 15:43:20.892151", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index e388199c361..b3f2e2eed5c 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -735,7 +735,6 @@ "oldfieldname": "valuation_rate", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "precision": "6", "print_hide": 1, "print_width": "80px", "read_only": 1, @@ -1150,7 +1149,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2026-02-07 14:42:11.646809", + "modified": "2026-04-07 15:40:47.032889", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 864a7fdab5fd3953929a80776ba80a0587866f44 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:18:35 +0530 Subject: [PATCH 144/168] fix(stock): ignore delivery note on delivery trip on_cancel trigger (backport #54120) (#54123) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> fix(stock): ignore delivery note on delivery trip on_cancel trigger (#54120) --- erpnext/stock/doctype/delivery_trip/delivery_trip.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js index 692b468e6ae..61c6743054f 100755 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js @@ -41,6 +41,8 @@ frappe.ui.form.on("Delivery Trip", { }, refresh: function (frm) { + frm.ignore_doctypes_on_cancel_all = ["Delivery Note"]; + if (frm.doc.docstatus == 1 && frm.doc.delivery_stops.length > 0) { frm.add_custom_button(__("Notify Customers via Email"), function () { frm.trigger("notify_customers"); From 9d317129f4c51ba3ac51c44966b5e4463e754e3d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:14:41 +0000 Subject: [PATCH 145/168] fix: quality inspection item code fetch perm issue (backport #54121) (#54127) Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com> Co-authored-by: Mihir Kandoi fix: quality inspection item code fetch perm issue (#54121) --- erpnext/stock/doctype/quality_inspection/quality_inspection.js | 1 + erpnext/stock/doctype/quality_inspection/quality_inspection.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index 69bc03a8bd4..8d5764d5697 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -58,6 +58,7 @@ frappe.ui.form.on("Quality Inspection", { if (doc.reference_type && doc.reference_name) { let filters = { from: doctype, + parent_doctype: doc.reference_type, inspection_type: doc.inspection_type, }; diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 67fc49acb8a..52ac45ec6fb 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -368,10 +368,11 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): from frappe.desk.reportview import get_match_cond from_doctype = cstr(filters.get("from")) + parent_doctype = cstr(filters.get("parent_doctype")) if not from_doctype or not frappe.db.exists("DocType", from_doctype): return [] - mcond = get_match_cond(from_doctype) + mcond = get_match_cond(parent_doctype or from_doctype) cond, qi_condition = "", "and (quality_inspection is null or quality_inspection = '')" if filters.get("parent"): From 856ba2419462a94e21a89668e9f113865c07c1a8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:15:28 +0000 Subject: [PATCH 146/168] fix(manufacturing): check remaining qty to calculate operating cost (backport #53983) (#54128) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> fix(manufacturing): check remaining qty to calculate operating cost (#53983) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index ae5c390fdae..d62ce4e2cb4 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -3928,9 +3928,12 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None): for d in work_order.get("operations"): if flt(d.completed_qty): - operating_cost_per_unit += flt( - d.actual_operating_cost - get_consumed_operating_cost(work_order.name, bom_no) - ) / flt(d.completed_qty - work_order.produced_qty) + if not (remaining_qty := flt(d.completed_qty - work_order.produced_qty)): + continue + operating_cost_per_unit += ( + flt(d.actual_operating_cost - get_consumed_operating_cost(work_order.name, bom_no)) + / remaining_qty + ) elif work_order.qty: operating_cost_per_unit += flt(d.planned_operating_cost) / flt(work_order.qty) From 7b0d34e97941ef304ebf19f406068fcc6781f6e6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:43:01 +0000 Subject: [PATCH 147/168] fix: inventory dimensions should not be mandatory unnecesarily (backport #54064) (#54134) * fix: inventory dimensions should not be mandatory unnecesarily (#54064) (cherry picked from commit 6e44b8913e95ecb9b95a5c4e71bb2ae542b24352) # Conflicts: # erpnext/patches.txt * chore: resolve conflicts --------- Co-authored-by: Mihir Kandoi --- erpnext/patches.txt | 1 + .../v16_0/depends_on_inv_dimensions.py | 70 +++++++++++++++++++ .../inventory_dimension.json | 12 +--- .../inventory_dimension.py | 16 +++-- .../test_inventory_dimension.py | 6 +- 5 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 erpnext/patches/v16_0/depends_on_inv_dimensions.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8e36eaaed40..d41924edb4a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -473,3 +473,4 @@ 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 erpnext.patches.v16_0.co_by_product_patch +erpnext.patches.v16_0.depends_on_inv_dimensions diff --git a/erpnext/patches/v16_0/depends_on_inv_dimensions.py b/erpnext/patches/v16_0/depends_on_inv_dimensions.py new file mode 100644 index 00000000000..114e6e4b725 --- /dev/null +++ b/erpnext/patches/v16_0/depends_on_inv_dimensions.py @@ -0,0 +1,70 @@ +import frappe + + +def get_inventory_dimensions(): + return frappe.get_all( + "Inventory Dimension", + fields=[ + "target_fieldname as fieldname", + "source_fieldname", + "reference_document as doctype", + "reqd", + "mandatory_depends_on", + ], + order_by="creation", + distinct=True, + ) + + +def get_display_depends_on(doctype): + if doctype not in [ + "Stock Entry Detail", + "Sales Invoice Item", + "Delivery Note Item", + "Purchase Invoice Item", + "Purchase Receipt Item", + ]: + return + + display_depends_on = "" + + if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]: + display_depends_on = "eval:parent.is_internal_supplier == 1" + elif doctype != "Stock Entry Detail": + display_depends_on = "eval:parent.is_internal_customer == 1" + elif doctype == "Stock Entry Detail": + display_depends_on = "eval:doc.t_warehouse" + + return display_depends_on + + +def execute(): + for dimension in get_inventory_dimensions(): + frappe.set_value( + "Custom Field", + {"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail"}, + "depends_on", + "eval:doc.s_warehouse", + ) + frappe.set_value( + "Custom Field", + {"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail", "reqd": 1}, + {"mandatory_depends_on": "eval:doc.s_warehouse", "reqd": 0}, + ) + frappe.set_value( + "Custom Field", + { + "fieldname": f"to_{dimension.fieldname}", + "dt": "Stock Entry Detail", + "depends_on": "eval:parent.purpose != 'Material Issue'", + }, + "depends_on", + "eval:doc.t_warehouse", + ) + if display_depends_on := get_display_depends_on(dimension.doctype): + frappe.set_value( + "Custom Field", + {"fieldname": dimension.fieldname, "dt": dimension.doctype}, + "mandatory_depends_on", + display_depends_on if dimension.reqd else dimension.mandatory_depends_on, + ) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json index 376b09f9370..aae81a29eac 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json @@ -8,9 +8,8 @@ "field_order": [ "dimension_details_tab", "dimension_name", - "reference_document", "column_break_4", - "disabled", + "reference_document", "field_mapping_section", "source_fieldname", "column_break_9", @@ -93,12 +92,6 @@ "fieldtype": "Check", "label": "Apply to All Inventory Documents" }, - { - "default": "0", - "fieldname": "disabled", - "fieldtype": "Check", - "label": "Disabled" - }, { "fieldname": "target_fieldname", "fieldtype": "Data", @@ -159,6 +152,7 @@ "label": "Conditional Rule Examples" }, { + "depends_on": "eval:!doc.apply_to_all_doctypes", "description": "To apply condition on parent field use parent.field_name and to apply condition on child table use doc.field_name. Here field_name could be based on the actual column name of the respective field.", "fieldname": "mandatory_depends_on", "fieldtype": "Small Text", @@ -188,7 +182,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-07 15:51:29.329064", + "modified": "2026-04-08 10:10:16.884388", "modified_by": "Administrator", "module": "Stock", "name": "Inventory Dimension", diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index 53a2e45f1df..fc5038db069 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -32,7 +32,6 @@ class InventoryDimension(Document): apply_to_all_doctypes: DF.Check condition: DF.Code | None dimension_name: DF.Data - disabled: DF.Check document_type: DF.Link | None fetch_from_parent: DF.Literal[None] istable: DF.Check @@ -76,7 +75,6 @@ class InventoryDimension(Document): old_doc = self._doc_before_save allow_to_edit_fields = [ - "disabled", "fetch_from_parent", "type_of_transaction", "condition", @@ -120,6 +118,7 @@ class InventoryDimension(Document): def reset_value(self): if self.apply_to_all_doctypes: self.type_of_transaction = "" + self.mandatory_depends_on = "" self.istable = 0 for field in ["document_type", "condition"]: @@ -184,8 +183,12 @@ class InventoryDimension(Document): label=_(label), depends_on="eval:doc.s_warehouse" if doctype == "Stock Entry Detail" else "", search_index=1, - reqd=self.reqd, - mandatory_depends_on=self.mandatory_depends_on, + reqd=1 + if self.reqd and not self.mandatory_depends_on and doctype != "Stock Entry Detail" + else 0, + mandatory_depends_on="eval:doc.s_warehouse" + if self.reqd and doctype == "Stock Entry Detail" + else self.mandatory_depends_on, ), ] @@ -296,12 +299,13 @@ class InventoryDimension(Document): options=self.reference_document, label=label, depends_on=display_depends_on, + mandatory_depends_on=display_depends_on if self.reqd else self.mandatory_depends_on, ), ] ) -def field_exists(doctype, fieldname) -> str or None: +def field_exists(doctype, fieldname) -> str | None: return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name") @@ -372,7 +376,6 @@ def get_document_wise_inventory_dimensions(doctype) -> dict: "type_of_transaction", "fetch_from_parent", ], - filters={"disabled": 0}, or_filters={"document_type": doctype, "apply_to_all_doctypes": 1}, ) @@ -389,7 +392,6 @@ def get_inventory_dimensions(): "validate_negative_stock", "name as dimension_name", ], - filters={"disabled": 0}, order_by="creation", distinct=True, ) diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index fb62b0eb5c0..bfe6864486b 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -211,9 +211,9 @@ class TestInventoryDimension(ERPNextTestSuite): doc = create_inventory_dimension( reference_document="Pallet", type_of_transaction="Outward", - dimension_name="Pallet", + dimension_name="Pallet 75", apply_to_all_doctypes=0, - document_type="Stock Entry Detail", + document_type="Delivery Note Item", ) doc.reqd = 1 @@ -221,7 +221,7 @@ class TestInventoryDimension(ERPNextTestSuite): self.assertTrue( frappe.db.get_value( - "Custom Field", {"fieldname": "pallet", "dt": "Stock Entry Detail", "reqd": 1}, "name" + "Custom Field", {"fieldname": "pallet_75", "dt": "Delivery Note Item", "reqd": 1}, "name" ) ) From 526c8d041891a8648029ef50c55709dc56cd4ee7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:30:35 +0000 Subject: [PATCH 148/168] refactor(lost_opportunity_report): replaced raw_sql with query builder (backport #54136) (#54140) Co-authored-by: diptanilsaha --- .../lost_opportunity/lost_opportunity.py | 82 +++++++++---------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.py b/erpnext/crm/report/lost_opportunity/lost_opportunity.py index eb09711667a..cfbee3901e2 100644 --- a/erpnext/crm/report/lost_opportunity/lost_opportunity.py +++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.py @@ -4,6 +4,12 @@ import frappe from frappe import _ +from frappe.query_builder import DocType +from frappe.query_builder.custom import GROUP_CONCAT +from frappe.query_builder.functions import Date + +Opportunity = DocType("Opportunity") +OpportunityLostReasonDetail = DocType("Opportunity Lost Reason Detail") def execute(filters=None): @@ -66,58 +72,48 @@ def get_columns(): def get_data(filters): - return frappe.db.sql( - f""" - SELECT - `tabOpportunity`.name, - `tabOpportunity`.opportunity_from, - `tabOpportunity`.party_name, - `tabOpportunity`.customer_name, - `tabOpportunity`.opportunity_type, - GROUP_CONCAT(`tabOpportunity Lost Reason Detail`.lost_reason separator ', ') lost_reason, - `tabOpportunity`.sales_stage, - `tabOpportunity`.territory - FROM - `tabOpportunity` - {get_join(filters)} - WHERE - `tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s - AND DATE(`tabOpportunity`.modified) BETWEEN %(from_date)s AND %(to_date)s - {get_conditions(filters)} - GROUP BY - `tabOpportunity`.name - ORDER BY - `tabOpportunity`.creation asc """, - filters, - as_dict=1, + query = ( + frappe.qb.from_(Opportunity) + .left_join(OpportunityLostReasonDetail) + .on( + (OpportunityLostReasonDetail.parenttype == "Opportunity") + & (OpportunityLostReasonDetail.parent == Opportunity.name) + ) + .select( + Opportunity.name, + Opportunity.opportunity_from, + Opportunity.party_name, + Opportunity.customer_name, + Opportunity.opportunity_type, + GROUP_CONCAT(OpportunityLostReasonDetail.lost_reason, alias="lost_reason").separator(", "), + Opportunity.sales_stage, + Opportunity.territory, + ) + .where( + (Opportunity.status == "Lost") + & (Opportunity.company == filters.get("company")) + & (Date(Opportunity.modified).between(filters.get("from_date"), filters.get("to_date"))) + ) + .groupby(Opportunity.name) + .orderby(Opportunity.creation) ) + query = get_conditions(filters, query) -def get_conditions(filters): - conditions = [] + return query.run(as_dict=1) + +def get_conditions(filters, query): if filters.get("territory"): - conditions.append(" and `tabOpportunity`.territory=%(territory)s") + query = query.where(Opportunity.territory == filters.get("territory")) if filters.get("opportunity_from"): - conditions.append(" and `tabOpportunity`.opportunity_from=%(opportunity_from)s") + query = query.where(Opportunity.opportunity_from == filters.get("opportunity_from")) if filters.get("party_name"): - conditions.append(" and `tabOpportunity`.party_name=%(party_name)s") - - return " ".join(conditions) if conditions else "" - - -def get_join(filters): - join = """LEFT JOIN `tabOpportunity Lost Reason Detail` - ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and - `tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name""" + query = query.where(Opportunity.party_name == filters.get("party_name")) if filters.get("lost_reason"): - join = """JOIN `tabOpportunity Lost Reason Detail` - ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and - `tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and - `tabOpportunity Lost Reason Detail`.lost_reason = '{}' - """.format(filters.get("lost_reason")) + query = query.where(OpportunityLostReasonDetail.lost_reason == filters.get("lost_reason")) - return join + return query From 233dc7c07bf02344dfe27113ec193ea6a7d22ef5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 02:02:35 +0000 Subject: [PATCH 149/168] feat: Allowing operation level quality inspection check in BOM (backport #53859) (#54144) Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com> Co-authored-by: Mihir Kandoi --- .../doctype/bom_operation/bom_operation.json | 10 ++++++++- .../doctype/bom_operation/bom_operation.py | 1 + .../doctype/job_card/job_card.py | 22 +++++++++---------- .../doctype/job_card/test_job_card.py | 11 ++++++++++ .../doctype/work_order/work_order.py | 1 + .../work_order_operation.json | 9 +++++++- .../work_order_operation.py | 1 + 7 files changed, 42 insertions(+), 13 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 11c704649a3..ad47d4024b4 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -20,6 +20,7 @@ "is_subcontracted", "is_final_finished_good", "set_cost_based_on_bom_qty", + "quality_inspection_required", "warehouse_section", "skip_material_transfer", "backflush_from_wip_warehouse", @@ -290,13 +291,20 @@ "fieldname": "backflush_from_wip_warehouse", "fieldtype": "Check", "label": "Backflush Materials From WIP Warehouse" + }, + { + "default": "0", + "depends_on": "eval:parent.inspection_required", + "fieldname": "quality_inspection_required", + "fieldtype": "Check", + "label": "Quality Inspection Required" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2026-03-31 17:09:48.771834", + "modified": "2026-04-01 17:09:48.771834", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.py b/erpnext/manufacturing/doctype/bom_operation/bom_operation.py index fd197e89e62..72d7f194fd8 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.py +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.py @@ -35,6 +35,7 @@ class BOMOperation(Document): parent: DF.Data parentfield: DF.Data parenttype: DF.Data + quality_inspection_required: DF.Check sequence_id: DF.Int set_cost_based_on_bom_qty: DF.Check skip_material_transfer: DF.Check diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index a4eaec8e73f..9716ae49dc0 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -788,27 +788,27 @@ class JobCard(Document): ["action_if_quality_inspection_is_not_submitted", "action_if_quality_inspection_is_rejected"], ) - item = self.finished_good or self.production_item - bom_inspection_required = frappe.db.get_value( - "BOM", self.semi_fg_bom or self.bom_no, "inspection_required" + bom_inspection_required = frappe.get_value("BOM", self.bom_no, "inspection_required") + operation_inspection_required = frappe.get_value( + "Work Order Operation", self.operation_id, "quality_inspection_required" ) - if bom_inspection_required: + if bom_inspection_required and operation_inspection_required: if not self.quality_inspection: frappe.throw( _( "Quality Inspection is required for the item {0} before completing the job card {1}" - ).format(get_link_to_form("Item", item), bold(self.name)) + ).format(get_link_to_form("Item", self.finished_good), bold(self.name)) ) - qa_status, docstatus = frappe.db.get_value( + + qa_status, docstatus = frappe.get_value( "Quality Inspection", self.quality_inspection, ["status", "docstatus"] ) - if docstatus != 1: if action_submit == "Stop": frappe.throw( _("Quality Inspection {0} is not submitted for the item: {1}").format( get_link_to_form("Quality Inspection", self.quality_inspection), - get_link_to_form("Item", item), + get_link_to_form("Item", self.finished_good), ), title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError, @@ -817,7 +817,7 @@ class JobCard(Document): frappe.msgprint( _("Quality Inspection {0} is not submitted for the item: {1}").format( get_link_to_form("Quality Inspection", self.quality_inspection), - get_link_to_form("Item", item), + get_link_to_form("Item", self.finished_good), ), alert=True, indicator="orange", @@ -827,7 +827,7 @@ class JobCard(Document): frappe.throw( _("Quality Inspection {0} is rejected for the item: {1}").format( get_link_to_form("Quality Inspection", self.quality_inspection), - get_link_to_form("Item", item), + get_link_to_form("Item", self.finished_good), ), title=_("Inspection Rejected"), exc=QualityInspectionRejectedError, @@ -836,7 +836,7 @@ class JobCard(Document): frappe.msgprint( _("Quality Inspection {0} is rejected for the item: {1}").format( get_link_to_form("Quality Inspection", self.quality_inspection), - get_link_to_form("Item", item), + get_link_to_form("Item", self.finished_good), ), alert=True, indicator="orange", diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index a25b6e1af3d..9a6733232a1 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -87,6 +87,7 @@ class TestJobCard(ERPNextTestSuite): with_operations=1, track_semi_finished_goods=1, company="_Test Company", + inspection_required=1, ) final_bom.append("items", {"item_code": raw.name, "qty": 1}) final_bom.append( @@ -97,6 +98,7 @@ class TestJobCard(ERPNextTestSuite): "bom_no": cut_bom, "skip_material_transfer": 1, "time_in_mins": 60, + "quality_inspection_required": 1, }, ) final_bom.append( @@ -133,6 +135,15 @@ class TestJobCard(ERPNextTestSuite): work_order.submit() job_card = frappe.get_all("Job Card", filters={"work_order": work_order.name, "operation": "Cutting"}) job_card_doc = frappe.get_doc("Job Card", job_card[0].name) + job_card_doc.append( + "time_logs", + { + "from_time": "2024-01-01 08:00:00", + "to_time": "2024-01-01 09:00:00", + "time_in_mins": 60, + "completed_qty": 1, + }, + ) self.assertRaises(frappe.ValidationError, job_card_doc.submit) def test_job_card_operations(self): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 5e18f68e8c0..19185a6ec58 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1277,6 +1277,7 @@ class WorkOrder(Document): "skip_material_transfer", "backflush_from_wip_warehouse", "set_cost_based_on_bom_qty", + "quality_inspection_required", ], order_by="idx", ) diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index 6cbcc855d01..89ed830116b 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -15,6 +15,7 @@ "workstation_type", "workstation", "sequence_id", + "quality_inspection_required", "section_break_insy", "bom_no", "finished_good", @@ -294,13 +295,19 @@ "fieldtype": "Check", "label": "Backflush Materials From WIP Warehouse", "read_only": 1 + }, + { + "default": "0", + "fieldname": "quality_inspection_required", + "fieldtype": "Check", + "label": "Quality Inspection Required" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-05-15 15:10:06.885440", + "modified": "2026-03-30 17:20:08.874381", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.py b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.py index fb8b3feb4dd..2e45434f94b 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.py +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.py @@ -36,6 +36,7 @@ class WorkOrderOperation(Document): planned_operating_cost: DF.Currency planned_start_time: DF.Datetime | None process_loss_qty: DF.Float + quality_inspection_required: DF.Check sequence_id: DF.Int skip_material_transfer: DF.Check source_warehouse: DF.Link | None From f2b3adec0fbcb3311669a78936ac64613d92db4c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 02:09:46 +0000 Subject: [PATCH 150/168] fix: inventory dimension patch (backport #54141) (#54146) Co-authored-by: Mihir Kandoi fix: inventory dimension patch (#54141) --- .../v16_0/depends_on_inv_dimensions.py | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/erpnext/patches/v16_0/depends_on_inv_dimensions.py b/erpnext/patches/v16_0/depends_on_inv_dimensions.py index 114e6e4b725..3ee805df7ef 100644 --- a/erpnext/patches/v16_0/depends_on_inv_dimensions.py +++ b/erpnext/patches/v16_0/depends_on_inv_dimensions.py @@ -40,28 +40,45 @@ def get_display_depends_on(doctype): def execute(): for dimension in get_inventory_dimensions(): - frappe.set_value( - "Custom Field", - {"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail"}, - "depends_on", - "eval:doc.s_warehouse", - ) - frappe.set_value( - "Custom Field", - {"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail", "reqd": 1}, - {"mandatory_depends_on": "eval:doc.s_warehouse", "reqd": 0}, - ) - frappe.set_value( + if frappe.db.exists( + "Custom Field", {"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail"} + ): + frappe.set_value( + "Custom Field", + {"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail"}, + "depends_on", + "eval:doc.s_warehouse", + ) + if frappe.db.exists( + "Custom Field", {"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail", "reqd": 1} + ): + frappe.set_value( + "Custom Field", + {"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail", "reqd": 1}, + {"mandatory_depends_on": "eval:doc.s_warehouse", "reqd": 0}, + ) + if frappe.db.exists( "Custom Field", { "fieldname": f"to_{dimension.fieldname}", "dt": "Stock Entry Detail", "depends_on": "eval:parent.purpose != 'Material Issue'", }, - "depends_on", - "eval:doc.t_warehouse", - ) - if display_depends_on := get_display_depends_on(dimension.doctype): + ): + frappe.set_value( + "Custom Field", + { + "fieldname": f"to_{dimension.fieldname}", + "dt": "Stock Entry Detail", + "depends_on": "eval:parent.purpose != 'Material Issue'", + }, + "depends_on", + "eval:doc.t_warehouse", + ) + if (display_depends_on := get_display_depends_on(dimension.doctype)) and frappe.db.exists( + "Custom Field", + {"fieldname": dimension.fieldname, "dt": dimension.doctype}, + ): frappe.set_value( "Custom Field", {"fieldname": dimension.fieldname, "dt": dimension.doctype}, From 60a1da0a1b10d751328afa8cfe1ba672c6c8699b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 8 Apr 2026 18:22:08 +0530 Subject: [PATCH 151/168] fix: last SLE not updated in the file (cherry picked from commit 38ed425ee299e70d8c22b759899f250fd5429393) --- .../doctype/work_order/test_work_order.py | 18 +++++- erpnext/stock/stock_ledger.py | 59 ++++++++++--------- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 8a13ed11fe2..b2a1eba0232 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -518,9 +518,23 @@ class TestWorkOrder(ERPNextTestSuite): do_not_save=True, ) + operation_name = "_Test Custom Operation" + workstation_name = "_Test Custom Workstation" + + if not frappe.db.exists("Workstation", workstation_name): + doc = frappe.new_doc("Workstation") + doc.workstation_name = workstation_name + doc.save() + + if not frappe.db.exists("Operation", operation_name): + doc = frappe.new_doc("Operation") + doc.name = operation_name + doc.workstation = workstation_name + doc.save() + operation = { - "operation": "_Test Operation 1", - "workstation": "_Test Workstation 1", + "operation": operation_name, + "workstation": workstation_name, "description": "Test Data", "operating_cost": 100, "time_in_mins": 40, diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index f754cab7650..5bc799acdbe 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -267,17 +267,11 @@ def update_args_in_repost_item_valuation( items_to_be_repost, repost_affected_transaction, item_wh_wise_last_posted_sle=None, - only_affected_transaction=False, ): file_name = "" - has_file = False - if not item_wh_wise_last_posted_sle: item_wh_wise_last_posted_sle = {} - if doc.reposting_data_file: - has_file = True - if doc.reposting_data_file: file_name = get_reposting_file_name(doc.doctype, doc.name) # frappe.delete_doc("File", file_name, ignore_permissions=True, delete_permanently=True) @@ -292,15 +286,14 @@ def update_args_in_repost_item_valuation( file_name, ) - if not only_affected_transaction or not has_file: - doc.db_set( - { - "current_index": index, - "items_to_be_repost": frappe.as_json(items_to_be_repost), - "total_reposting_count": len(items_to_be_repost), - "reposting_data_file": doc.reposting_data_file, - } - ) + doc.db_set( + { + "current_index": index, + "items_to_be_repost": frappe.as_json(items_to_be_repost), + "total_reposting_count": len(items_to_be_repost), + "reposting_data_file": doc.reposting_data_file, + } + ) if not frappe.in_test: frappe.db.commit() @@ -584,13 +577,9 @@ class update_entries_after: self.update_bin() else: self.item_wh_wise_last_posted_sle = self.get_item_wh_wise_last_posted_sle() - _item_wh_sle = self.sort_sles(self.item_wh_wise_last_posted_sle.values()) - - while _item_wh_sle: - self.initialize_reposting() - sle_dict = _item_wh_sle.pop(0) - self.repost_stock_ledgers(sle_dict) - + item_wh_sles = self.sort_sles(self.item_wh_wise_last_posted_sle.values()) + self.initialize_reposting() + self.repost_stock_ledgers(item_wh_sles) self.update_bin() self.reset_vouchers_and_idx() self.update_data_in_repost() @@ -625,8 +614,19 @@ class update_entries_after: ) } - def repost_stock_ledgers(self, sle_dict=None): - self._sles = self.get_future_entries_to_repost(sle_dict) + def _get_future_entries_to_repost(self, item_wh_sles): + sles = [] + + for sle in item_wh_sles: + if (sle.item_code, sle.warehouse) not in self.distinct_dependant_item_wh: + self.distinct_dependant_item_wh.add((sle.item_code, sle.warehouse)) + + sles.extend(self.get_future_entries_to_repost(sle)) + + return self.sort_sles(sles) + + def repost_stock_ledgers(self, item_wh_sles=None): + self._sles = self._get_future_entries_to_repost(item_wh_sles) if not isinstance(self._sles, deque): self._sles = deque(self._sles) @@ -634,10 +634,13 @@ class update_entries_after: i = 0 while self._sles: sle = self._sles.popleft() - i += 1 + if (sle.item_code, sle.warehouse) not in self.distinct_dependant_item_wh: + self.distinct_dependant_item_wh.add((sle.item_code, sle.warehouse)) + if sle.name in self.distinct_sles: continue + i += 1 item_wh_key = (sle.item_code, sle.warehouse) if item_wh_key not in self.prev_sle_dict: self.prev_sle_dict[item_wh_key] = get_previous_sle_of_current_voucher(sle) @@ -651,7 +654,7 @@ class update_entries_after: self.include_dependant_sle_in_reposting(sle) self.update_item_wh_wise_last_posted_sle(sle) - if i % 1000 == 0: + if i % 2000 == 0: self.update_data_in_repost(len(self._sles), i) def sort_sles(self, sles): @@ -733,7 +736,6 @@ class update_entries_after: self.items_to_be_repost, self.repost_affected_transaction, self.item_wh_wise_last_posted_sle, - only_affected_transaction=True, ) if not frappe.in_test: @@ -990,6 +992,9 @@ class update_entries_after: ): return + if not cint(erpnext.is_perpetual_inventory_enabled(sle.company)): + return + if self.args.item_code != sle.item_code or self.args.warehouse != sle.warehouse: self.repost_affected_transaction.add((sle.voucher_type, sle.voucher_no)) From 943ddff6aacff7d0ec6f8893c8738651f6070893 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 09:46:08 +0530 Subject: [PATCH 152/168] fix: inventory dimension patch (backport #54147) (#54149) Co-authored-by: Mihir Kandoi fix: inventory dimension patch (#54147) --- .../patches/v16_0/depends_on_inv_dimensions.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/patches/v16_0/depends_on_inv_dimensions.py b/erpnext/patches/v16_0/depends_on_inv_dimensions.py index 3ee805df7ef..0de46f68f11 100644 --- a/erpnext/patches/v16_0/depends_on_inv_dimensions.py +++ b/erpnext/patches/v16_0/depends_on_inv_dimensions.py @@ -16,7 +16,7 @@ def get_inventory_dimensions(): ) -def get_display_depends_on(doctype): +def get_display_depends_on(doctype, fieldname): if doctype not in [ "Stock Entry Detail", "Sales Invoice Item", @@ -24,18 +24,20 @@ def get_display_depends_on(doctype): "Purchase Invoice Item", "Purchase Receipt Item", ]: - return + return None, None + fieldname_start_with = "to" display_depends_on = "" if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]: display_depends_on = "eval:parent.is_internal_supplier == 1" + fieldname_start_with = "from" elif doctype != "Stock Entry Detail": display_depends_on = "eval:parent.is_internal_customer == 1" elif doctype == "Stock Entry Detail": display_depends_on = "eval:doc.t_warehouse" - return display_depends_on + return f"{fieldname_start_with}_{fieldname}", display_depends_on def execute(): @@ -75,13 +77,13 @@ def execute(): "depends_on", "eval:doc.t_warehouse", ) - if (display_depends_on := get_display_depends_on(dimension.doctype)) and frappe.db.exists( - "Custom Field", - {"fieldname": dimension.fieldname, "dt": dimension.doctype}, + fieldname, display_depends_on = get_display_depends_on(dimension.doctype, dimension.fieldname) + if display_depends_on and frappe.db.exists( + "Custom Field", {"fieldname": fieldname, "dt": dimension.doctype} ): frappe.set_value( "Custom Field", - {"fieldname": dimension.fieldname, "dt": dimension.doctype}, + {"fieldname": fieldname, "dt": dimension.doctype}, "mandatory_depends_on", display_depends_on if dimension.reqd else dimension.mandatory_depends_on, ) From 21cf83b915df76a8c033df6a0617f00d51dd009d Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Wed, 8 Apr 2026 17:19:43 +0530 Subject: [PATCH 153/168] refactor: update reset password method name (cherry picked from commit c4d74483e1895b4d10a7d28b808c2781e32b2428) --- .../doctype/request_for_quotation/request_for_quotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 1fc2cdf3386..09d7d30ab39 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -285,7 +285,7 @@ class RequestforQuotation(BuyingController): } ) user.save(ignore_permissions=True) - update_password_link = user.reset_password() + update_password_link = user._reset_password() return user, update_password_link From fe2161ea0cd5b479dd6aaf0d120efb6cd54b3133 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 11:58:02 +0530 Subject: [PATCH 154/168] fix(sales invoice): toggle Get Items From button based on is_return and POS view (backport #52594) (#54139) Co-authored-by: NaviN <118178330+Navin-S-R@users.noreply.github.com> Co-authored-by: Navin-S-R fix(sales invoice): toggle Get Items From button based on is_return and POS view (#52594) --- .../doctype/sales_invoice/sales_invoice.js | 172 ++++++++++-------- erpnext/controllers/queries.py | 59 +++--- 2 files changed, 131 insertions(+), 100 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 64728cd1e0a..90c0da74f26 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -165,13 +165,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( ); } } - - // Show buttons only when pos view is active - if (cint(doc.docstatus == 0) && this.frm.page.current_view_name !== "pos" && !doc.is_return) { - this.frm.cscript.sales_order_btn(); - this.frm.cscript.delivery_note_btn(); - this.frm.cscript.quotation_btn(); - } + this.toggle_get_items(); this.set_default_print_format(); if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) { @@ -260,6 +254,93 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( } } + toggle_get_items() { + const buttons = ["Sales Order", "Quotation", "Timesheet", "Delivery Note"]; + + buttons.forEach((label) => { + this.frm.remove_custom_button(label, "Get Items From"); + }); + + if (cint(this.frm.doc.docstatus) !== 0 || this.frm.page.current_view_name === "pos") { + return; + } + + if (!this.frm.doc.is_return) { + this.frm.cscript.sales_order_btn(); + this.frm.cscript.quotation_btn(); + this.frm.cscript.timesheet_btn(); + } + + this.frm.cscript.delivery_note_btn(); + } + + timesheet_btn() { + var me = this; + + me.frm.add_custom_button( + __("Timesheet"), + function () { + let d = new frappe.ui.Dialog({ + title: __("Fetch Timesheet"), + fields: [ + { + label: __("From"), + fieldname: "from_time", + fieldtype: "Date", + reqd: 1, + }, + { + label: __("Item Code"), + fieldname: "item_code", + fieldtype: "Link", + options: "Item", + get_query: () => { + return { + query: "erpnext.controllers.queries.item_query", + filters: { + is_sales_item: 1, + customer: me.frm.doc.customer, + has_variants: 0, + }, + }; + }, + }, + { + fieldtype: "Column Break", + fieldname: "col_break_1", + }, + { + label: __("To"), + fieldname: "to_time", + fieldtype: "Date", + reqd: 1, + }, + { + label: __("Project"), + fieldname: "project", + fieldtype: "Link", + options: "Project", + default: me.frm.doc.project, + }, + ], + primary_action: function () { + const data = d.get_values(); + me.frm.events.add_timesheet_data(me.frm, { + from_time: data.from_time, + to_time: data.to_time, + project: data.project, + item_code: data.item_code, + }); + d.hide(); + }, + primary_action_label: __("Get Timesheets"), + }); + d.show(); + }, + __("Get Items From") + ); + } + sales_order_btn() { var me = this; @@ -331,6 +412,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( this.$delivery_note_btn = this.frm.add_custom_button( __("Delivery Note"), function () { + if (!me.frm.doc.customer) { + frappe.throw({ + title: __("Mandatory"), + message: __("Please Select a Customer"), + }); + } erpnext.utils.map_current_doc({ method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice", source_doctype: "Delivery Note", @@ -343,7 +430,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( var filters = { docstatus: 1, company: me.frm.doc.company, - is_return: 0, + is_return: me.frm.doc.is_return, }; if (me.frm.doc.customer) filters["customer"] = me.frm.doc.customer; return { @@ -610,6 +697,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( apply_tds(frm) { this.frm.clear_table("tax_withholding_entries"); } + + is_return() { + this.toggle_get_items(); + } }; // for backward compatibility: combine new and previous states @@ -1061,71 +1152,6 @@ frappe.ui.form.on("Sales Invoice", { }, refresh: function (frm) { - if (frm.doc.docstatus === 0 && !frm.doc.is_return) { - frm.add_custom_button( - __("Timesheet"), - function () { - let d = new frappe.ui.Dialog({ - title: __("Fetch Timesheet"), - fields: [ - { - label: __("From"), - fieldname: "from_time", - fieldtype: "Date", - reqd: 1, - }, - { - label: __("Item Code"), - fieldname: "item_code", - fieldtype: "Link", - options: "Item", - get_query: () => { - return { - query: "erpnext.controllers.queries.item_query", - filters: { - is_sales_item: 1, - customer: frm.doc.customer, - has_variants: 0, - }, - }; - }, - }, - { - fieldtype: "Column Break", - fieldname: "col_break_1", - }, - { - label: __("To"), - fieldname: "to_time", - fieldtype: "Date", - reqd: 1, - }, - { - label: __("Project"), - fieldname: "project", - fieldtype: "Link", - options: "Project", - default: frm.doc.project, - }, - ], - primary_action: function () { - const data = d.get_values(); - frm.events.add_timesheet_data(frm, { - from_time: data.from_time, - to_time: data.to_time, - project: data.project, - item_code: data.item_code, - }); - d.hide(); - }, - primary_action_label: __("Get Timesheets"), - }); - d.show(); - }, - __("Get Items From") - ); - } - if (frm.doc.is_debit_note) { frm.set_df_property("return_against", "label", __("Adjustment Against")); } diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 9b663c0d2b9..b2e7337a4ec 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -364,38 +364,43 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs -def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict): - doctype = "Delivery Note" +def get_delivery_notes_to_be_billed( + doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict, as_dict: bool = False +): + DeliveryNote = frappe.qb.DocType("Delivery Note") + fields = get_fields(doctype, ["name", "customer", "posting_date"]) - return frappe.db.sql( - """ - select {fields} - from `tabDelivery Note` - where `tabDelivery Note`.`{key}` like {txt} and - `tabDelivery Note`.docstatus = 1 - and status not in ('Stopped', 'Closed') {fcond} - and ( - (`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100) - or (`tabDelivery Note`.grand_total = 0 and `tabDelivery Note`.per_billed < 100) - or ( - `tabDelivery Note`.is_return = 1 - and return_against in (select name from `tabDelivery Note` where per_billed < 100) + original_dn = ( + frappe.qb.from_(DeliveryNote) + .select(DeliveryNote.name) + .where((DeliveryNote.docstatus == 1) & (DeliveryNote.is_return == 0) & (DeliveryNote.per_billed > 0)) + ) + + query = ( + frappe.qb.from_(DeliveryNote) + .select(*[DeliveryNote[f] for f in fields]) + .where( + (DeliveryNote.docstatus == 1) + & (DeliveryNote.status.notin(["Stopped", "Closed"])) + & (DeliveryNote[searchfield].like(f"%{txt}%")) + & ( + ((DeliveryNote.is_return == 0) & (DeliveryNote.per_billed < 100)) + | ((DeliveryNote.grand_total == 0) & (DeliveryNote.per_billed < 100)) + | ( + (DeliveryNote.is_return == 1) + & (DeliveryNote.per_billed < 100) + & (DeliveryNote.return_against.isin(original_dn)) ) ) - {mcond} order by `tabDelivery Note`.`{key}` asc limit {page_len} offset {start} - """.format( - fields=", ".join([f"`tabDelivery Note`.{f}" for f in fields]), - key=searchfield, - fcond=get_filters_cond(doctype, filters, []), - mcond=get_match_cond(doctype), - start=start, - page_len=page_len, - txt="%(txt)s", - ), - {"txt": ("%%%s%%" % txt)}, - as_dict=as_dict, + ) ) + if filters and isinstance(filters, dict): + for key, value in filters.items(): + query = query.where(DeliveryNote[key] == value) + + query = query.orderby(DeliveryNote[searchfield], order=Order.asc).limit(page_len).offset(start) + return query.run(as_dict=as_dict) @frappe.whitelist() From 1086a7237317e9d2622d52d207949f7a7f66343b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 9 Apr 2026 12:47:36 +0530 Subject: [PATCH 155/168] fix: set default posting time in RIV (cherry picked from commit a7ece65536d54c03a615ba43b5f23e4643492d6b) --- .../repost_item_valuation/repost_item_valuation.py | 8 ++++++++ erpnext/stock/stock_ledger.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 84cf6234dcf..c63c2d34916 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -80,6 +80,7 @@ class RepostItemValuation(Document): repost(self) def validate(self): + self.set_default_posting_time() self.reset_repost_only_accounting_ledgers() self.set_company() self.validate_update_stock() @@ -90,6 +91,13 @@ class RepostItemValuation(Document): self.reset_recreate_stock_ledgers() self.validate_recreate_stock_ledgers() + def set_default_posting_time(self): + if not self.posting_time: + self.posting_time = nowtime() + + if not self.posting_date: + frappe.throw(_("Posting date is required")) + def reset_repost_only_accounting_ledgers(self): if self.repost_only_accounting_ledgers and self.based_on != "Transaction": self.repost_only_accounting_ledgers = 0 diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 5bc799acdbe..c1a2cdb57b1 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -237,6 +237,8 @@ def repost_future_sle( index = get_current_index(doc) or 0 while index < len(items_to_be_repost): + validate_item_warehouse(items_to_be_repost[index]) + obj = update_entries_after( { "item_code": items_to_be_repost[index].get("item_code"), From a71814a483d1056fb50c4be7412210d2cada7374 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Thu, 9 Apr 2026 01:54:35 +0530 Subject: [PATCH 156/168] fix: Set remarks blank instead of No remarks in Sales/Purchase Invoices (cherry picked from commit 2515bf3aff9ae14b4ad34586e3788d7e60c7be62) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 3 --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 8b561730de0..c790f86633a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -332,9 +332,6 @@ class PurchaseInvoice(BuyingController): if self.bill_date: self.remarks += " " + _("dated {0}").format(formatdate(self.bill_date)) - else: - self.remarks = _("No Remarks") - def set_missing_values(self, for_validate=False): if not self.credit_to: self.credit_to = get_party_account("Supplier", self.supplier, self.company) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ba5aa9dfd93..0477c172587 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1101,9 +1101,6 @@ class SalesInvoice(SellingController): if self.po_date: self.remarks += " " + _("dated {0}").format(formatdate(self.po_date)) - else: - self.remarks = _("No Remarks") - def validate_auto_set_posting_time(self): # Don't auto set the posting date and time if invoice is amended if self.is_new() and self.amended_from: From 699325506f76d74d60a0ea9d63ed10015666c3aa Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Thu, 9 Apr 2026 11:14:35 +0530 Subject: [PATCH 157/168] fix(test): Remove usage of No remark as remark in tests (cherry picked from commit 56416d18d3cc155d87f0f4a4fc1781408ac972d1) --- .../test_accounts_receivable.py | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index a739502074e..4b852e0583d 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -120,12 +120,12 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): report = execute(filters) - expected_data = [[100, 30, "No Remarks"], [100, 50, "No Remarks"], [100, 20, "No Remarks"]] + expected_data = [[100, 30], [100, 50], [100, 20]] for i in range(3): row = report[1][i - 1] - self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks]) - + self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced]) + self.assertFalse(row.get("remarks")) # check invoice grand total, invoiced, paid and outstanding column's value after payment self.create_payment_entry(si.name) report = execute(filters) @@ -178,11 +178,11 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): report = execute(filters) - expected_data = [[100, 30, "No Remarks"], [100, 50, "No Remarks"], [100, 20, "No Remarks"]] + expected_data = [[100, 30], [100, 50], [100, 20]] for i in range(3): row = report[1][i - 1] - self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks]) + self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced]) # check invoice grand total, invoiced, paid and outstanding column's value after credit note cr_note = self.create_credit_note(si.name, do_not_submit=True) @@ -225,9 +225,10 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): report = execute(filters) row = report[1][0] - expected_data = [8000, 8000, "No Remarks"] # Data in company currency + expected_data = [8000, 8000] # Data in company currency - self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks]) + self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced]) + self.assertFalse(row.get("remarks")) # CASE 2: Transaction currency and party account currency are the same self.create_customer( @@ -258,18 +259,20 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): report = execute(filters) row = report[1][0] - expected_data = [100, 100, "No Remarks"] # Data in Part Account Currency + expected_data = [100, 100] # Data in Part Account Currency - self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks]) + self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced]) + self.assertFalse(row.get("remarks")) # View in Company currency filters.pop("in_party_currency") report = execute(filters) row = report[1][0] - expected_data = [8000, 8000, "No Remarks"] # Data in Company Currency + expected_data = [8000, 8000] # Data in Company Currency - self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks]) + self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced]) + self.assertFalse(row.get("remarks")) def test_accounts_receivable_with_partial_payment(self): filters = { @@ -285,11 +288,12 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): report = execute(filters) - expected_data = [[200, 60, "No Remarks"], [200, 100, "No Remarks"], [200, 40, "No Remarks"]] + expected_data = [[200, 60], [200, 100], [200, 40]] for i in range(3): row = report[1][i - 1] - self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks]) + self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced]) + self.assertFalse(row.get("remarks")) # check invoice grand total, invoiced, paid and outstanding column's value after payment self.create_payment_entry(si.name) @@ -348,11 +352,12 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): report = execute(filters) - expected_data = [100, 100, "No Remarks"] + expected_data = [100, 100] self.assertEqual(len(report[1]), 1) row = report[1][0] - self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks]) + self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced]) + self.assertFalse(row.get("remarks")) # check invoice grand total, invoiced, paid and outstanding column's value after payment self.create_payment_entry(si.name) From abb896ecf1e7336ddda7b9a993134dc6cefb440c Mon Sep 17 00:00:00 2001 From: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:57:22 +0530 Subject: [PATCH 158/168] fix: update_nsm only in warehouse creation (#54165) (cherry picked from commit b0e3fa3979e6efe6c9d230ac7e0812832b4563d2) --- erpnext/stock/doctype/warehouse/warehouse.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index c2c553457b0..b2b898eec0d 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -73,7 +73,8 @@ class Warehouse(NestedSet): self.warn_about_multiple_warehouse_account() def on_update(self): - self.update_nsm_model() + if self.is_new() or self.has_value_changed("parent_warehouse"): + self.update_nsm_model() def update_nsm_model(self): frappe.utils.nestedset.update_nsm(self) From 720a79588ded6917d42e4f950acdf320e38022a5 Mon Sep 17 00:00:00 2001 From: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com> Date: Thu, 9 Apr 2026 18:13:39 +0530 Subject: [PATCH 159/168] Revert "fix: update_nsm only in warehouse creation (backport #54165)" --- erpnext/stock/doctype/warehouse/warehouse.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index b2b898eec0d..c2c553457b0 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -73,8 +73,7 @@ class Warehouse(NestedSet): self.warn_about_multiple_warehouse_account() def on_update(self): - if self.is_new() or self.has_value_changed("parent_warehouse"): - self.update_nsm_model() + self.update_nsm_model() def update_nsm_model(self): frappe.utils.nestedset.update_nsm(self) From 4e828fd897557145237eb697475e312889e47ff5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:58:57 +0000 Subject: [PATCH 160/168] fix: remove unneccessary function for serial no status updation (backport #54191) (#54197) Co-authored-by: Mihir Kandoi fix: remove unneccessary function for serial no status updation (#54191) --- .../test_stock_reconciliation.py | 41 +++++++++++++++++++ erpnext/stock/stock_ledger.py | 28 ------------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index e0fc0ba6d11..4c8b666ef5d 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -1801,6 +1801,47 @@ class TestStockReconciliation(ERPNextTestSuite, StockTestMixin): elif s.id_plant == plant_b.name: self.assertEqual(s.actual_qty, 3) + def test_serial_no_status_with_backdated_stock_reco(self): + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + item_code = self.make_item( + "Test Item", + { + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "SERIAL.###", + }, + ).name + + warehouse = "_Test Warehouse - _TC" + + reco = create_stock_reconciliation( + item_code=item_code, + posting_date=add_days(nowdate(), -2), + warehouse=warehouse, + qty=1, + rate=80, + purpose="Opening Stock", + ) + + serial_no = get_serial_nos_from_bundle(reco.items[0].serial_and_batch_bundle)[0] + + create_delivery_note( + item_code=item_code, warehouse=warehouse, qty=1, rate=100, posting_date=nowdate() + ) + + self.assertEqual(frappe.get_value("Serial No", serial_no, "status"), "Delivered") + + reco = create_stock_reconciliation( + item_code=item_code, + posting_date=add_days(nowdate(), -1), + warehouse=warehouse, + qty=1, + rate=90, + ) + + self.assertEqual(frappe.get_value("Serial No", serial_no, "status"), "Delivered") + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c1a2cdb57b1..7a4787123b6 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1071,34 +1071,6 @@ class update_entries_after: sabb_doc.voucher_no = None sabb_doc.cancel() - if sle.serial_and_batch_bundle and frappe.get_cached_value("Item", sle.item_code, "has_serial_no"): - self.update_serial_no_status(sle) - - def update_serial_no_status(self, sle): - from erpnext.stock.serial_batch_bundle import get_serial_nos - - serial_nos = get_serial_nos(sle.serial_and_batch_bundle) - if not serial_nos: - return - - warehouse = None - status = "Inactive" - - if sle.actual_qty > 0: - warehouse = sle.warehouse - status = "Active" - - sn_table = frappe.qb.DocType("Serial No") - - query = ( - frappe.qb.update(sn_table) - .set(sn_table.warehouse, warehouse) - .set(sn_table.status, status) - .where(sn_table.name.isin(serial_nos)) - ) - - query.run() - def calculate_valuation_for_serial_batch_bundle(self, sle): if not frappe.db.exists("Serial and Batch Bundle", sle.serial_and_batch_bundle): return From 22774fd81071ae8ad31ef15b97868f336b91850e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:20:49 +0000 Subject: [PATCH 161/168] fix: update return value in workstation list view indicator (backport #54198) (#54201) Co-authored-by: Praveenkumar Dhanasekar <164200710+Praveenku-mar@users.noreply.github.com> fix: update return value in workstation list view indicator (#54198) --- erpnext/manufacturing/doctype/workstation/workstation_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/workstation/workstation_list.js b/erpnext/manufacturing/doctype/workstation/workstation_list.js index 33722634b96..4c81ab082bf 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation_list.js +++ b/erpnext/manufacturing/doctype/workstation/workstation_list.js @@ -10,6 +10,6 @@ frappe.listview_settings["Workstation"] = { Setup: "blue", }; - return [__(doc.status), color_map[doc.status], true]; + return [__(doc.status), color_map[doc.status], "status,=," + doc.status]; }, }; From b42e23993d6fed5f56b6a9847bd2f5f170f17515 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 20:32:45 +0530 Subject: [PATCH 162/168] fix: account change in warehouse (backport #54182) (#54205) Co-authored-by: nishkagosalia --- erpnext/stock/doctype/warehouse/warehouse.py | 52 +++++--------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index c2c553457b0..f4079cc54cb 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -109,49 +109,23 @@ class Warehouse(NestedSet): def warn_about_multiple_warehouse_account(self): "If Warehouse value is split across multiple accounts, warn." - def get_accounts_where_value_is_booked(name): - sle = frappe.qb.DocType("Stock Ledger Entry") - gle = frappe.qb.DocType("GL Entry") - ac = frappe.qb.DocType("Account") - - return ( - frappe.qb.from_(sle) - .join(gle) - .on(sle.voucher_no == gle.voucher_no) - .join(ac) - .on(ac.name == gle.account) - .select(gle.account) - .distinct() - .where((sle.warehouse == name) & (ac.account_type == "Stock")) - .orderby(sle.creation) - .run(as_dict=True) - ) - - if self.is_new(): + if not frappe.db.count("Stock Ledger Entry", {"warehouse": self.name}): return - old_wh_account = frappe.db.get_value("Warehouse", self.name, "account") + doc_before_save = self.get_doc_before_save() + old_wh_account = doc_before_save.account if doc_before_save else None - # WH account is being changed or set get all accounts against which wh value is booked - if self.account != old_wh_account: - accounts = get_accounts_where_value_is_booked(self.name) - accounts = [d.account for d in accounts] + if self.is_new() or (self.account and old_wh_account == self.account): + return - if not accounts or (len(accounts) == 1 and self.account in accounts): - # if same singular account has stock value booked ignore - return - - warning = _("Warehouse's Stock Value has already been booked in the following accounts:") - account_str = "
    " + ", ".join(frappe.bold(ac) for ac in accounts) - reason = "

    " + _( - "Booking stock value across multiple accounts will make it harder to track stock and account value." - ) - - frappe.msgprint( - warning + account_str + reason, - title=_("Multiple Warehouse Accounts"), - indicator="orange", - ) + frappe.msgprint( + title=_("Warning: Account changed for warehouse"), + indicator="orange", + msg=_( + "Stock entries exist with the old account. Changing the account may lead to a mismatch between the warehouse closing balance and the account closing balance. The overall closing balance will still match, but not for the specific account." + ), + alert=True, + ) def check_if_sle_exists(self, non_cancelled_only=False): filters = {"warehouse": self.name} From 66fdd061e719e9566aab41a5f96bc6a14e6af804 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 23:44:42 +0530 Subject: [PATCH 163/168] Fix(bom): refetch the rate of item when 'source_from_supplier' is updated (backport #54187) (#54208) Co-authored-by: Sambhav Saxena <76242518+sambhavsaxena@users.noreply.github.com> Fix(bom): refetch the rate of item when 'source_from_supplier' is updated (#54187) --- erpnext/manufacturing/doctype/bom/bom.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 32c543703bc..f0d9576d469 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -955,6 +955,8 @@ frappe.ui.form.on("BOM Item", "sourced_by_supplier", function (frm, cdt, cdn) { if (d.sourced_by_supplier) { d.rate = 0; refresh_field("rate", d.name, d.parentfield); + } else { + get_bom_material_detail(frm.doc, cdt, cdn, false); } }); From 7d8f59eb0a70a5d91b4d0987d562f02d3fa4f7a9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 11 Apr 2026 10:32:32 +0530 Subject: [PATCH 164/168] fix: timer not showing in job card (backport #53839) (#54212) Co-authored-by: Rohit Waghchaure --- erpnext/manufacturing/doctype/job_card/job_card.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 68d1e3e6214..19132ecf9fd 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -278,6 +278,8 @@ frappe.ui.form.on("Job Card", { frm.trigger("complete_job_card"); }); } + + frm.trigger("make_dashboard"); } } From 3bdac5c30a563bb6b565d603192972aeccaed5cf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 11 Apr 2026 05:06:40 +0000 Subject: [PATCH 165/168] fix: batch/serial should use parent's posting datetime for naming (backport #54206) (#54209) --- erpnext/accounts/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 5b1bce2cb35..0c23353d1eb 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1603,6 +1603,10 @@ def parse_naming_series_variable(doc, variable): else: data = {"YY": "%y", "YYYY": "%Y", "MM": "%m", "DD": "%d", "JJJ": "%j"} + + if doc and doc.doctype in ["Batch", "Serial No"] and doc.reference_doctype and doc.reference_name: + doc = frappe.get_doc(doc.reference_doctype, doc.reference_name) + date = ( ( getdate(doc.get("posting_date") or doc.get("transaction_date") or doc.get("posting_datetime")) From d2745f3ec907b7b7b95fc6a3d634389c0ca4fb16 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 11 Apr 2026 05:32:11 +0000 Subject: [PATCH 166/168] fix(selling): enable selling_settings creation through fixtures (backport #54177) (#54215) Co-authored-by: mgicking-bmi Fix(selling): enable selling_settings creation through fixtures (#54177) --- erpnext/selling/doctype/selling_settings/selling_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index c13d4ce0a6c..bf8750cc1b8 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -93,10 +93,10 @@ class SellingSettings(Document): self.validate_fallback_to_default_price_list() - if old_doc.enable_tracking_sales_commissions != self.enable_tracking_sales_commissions: + if old_doc and old_doc.enable_tracking_sales_commissions != self.enable_tracking_sales_commissions: toggle_tracking_sales_commissions_section(not self.enable_tracking_sales_commissions) - if old_doc.enable_utm != self.enable_utm: + if old_doc and old_doc.enable_utm != self.enable_utm: toggle_utm_analytics_section(not self.enable_utm) def validate_fallback_to_default_price_list(self): From 39848ffb1ebcd7b021f5614cc4e7228a4bdf2382 Mon Sep 17 00:00:00 2001 From: MochaMind Date: Sun, 12 Apr 2026 15:40:15 +0530 Subject: [PATCH 167/168] chore: update POT file (#54229) --- erpnext/locale/main.pot | 1428 ++++++++++++++++++++------------------- 1 file changed, 744 insertions(+), 684 deletions(-) diff --git a/erpnext/locale/main.pot b/erpnext/locale/main.pot index 2ce76e47917..06178e5e76e 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-04-05 09:48+0000\n" -"PO-Revision-Date: 2026-04-05 09:48+0000\n" +"POT-Creation-Date: 2026-04-12 09:47+0000\n" +"PO-Revision-Date: 2026-04-12 09:47+0000\n" "Last-Translator: hello@frappe.io\n" "Language-Team: hello@frappe.io\n" "MIME-Version: 1.0\n" @@ -16,7 +16,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1558 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1591 msgid "" "\n" "\t\t\tThe Batch {0} of an item {1} has negative stock in the warehouse {2}{3}.\n" @@ -162,7 +162,7 @@ msgstr "" msgid "% Delivered" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:1017 +#: erpnext/manufacturing/doctype/bom/bom.js:1019 #, python-format msgid "% Finished Item Quantity" msgstr "" @@ -287,7 +287,7 @@ msgstr "" msgid "'Default {0} Account' in Company {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1226 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1229 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:1760 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1763 msgid "A Reverse Journal Entry {0} already exists for this Journal Entry." msgstr "" @@ -1216,7 +1216,7 @@ msgstr "" msgid "According to CEFACT/ICG/2010/IC013 or CEFACT/ICG/2010/IC010" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:988 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1080 msgid "According to the BOM {0}, the Item '{1}' is missing in the stock entry." msgstr "" @@ -1229,7 +1229,7 @@ msgstr "" #. Name of a DocType #. Label of a Workspace Sidebar Item #: erpnext/accounts/doctype/account/account.json -#: erpnext/accounts/doctype/account/account_tree.js:167 +#: erpnext/accounts/doctype/account/account_tree.js:162 #: erpnext/accounts/doctype/account_category/account_category.json #: erpnext/workspace_sidebar/accounts_setup.json msgid "Account Category" @@ -1359,7 +1359,7 @@ msgstr "" #. Label of the account_number (Data) field in DocType 'Account' #: erpnext/accounts/doctype/account/account.json -#: erpnext/accounts/doctype/account/account_tree.js:133 +#: erpnext/accounts/doctype/account/account_tree.js:128 #: erpnext/accounts/report/consolidated_trial_balance/consolidated_trial_balance.py:396 #: erpnext/accounts/report/financial_statements.py:685 #: erpnext/accounts/report/trial_balance/trial_balance.py:495 @@ -1406,7 +1406,7 @@ msgstr "" #. Label of the account_type (Select) field in DocType 'Party Type' #: erpnext/accounts/doctype/account/account.json #: erpnext/accounts/doctype/account/account.py:206 -#: erpnext/accounts/doctype/account/account_tree.js:159 +#: erpnext/accounts/doctype/account/account_tree.js:154 #: erpnext/accounts/doctype/bank_account/bank_account.json #: erpnext/accounts/doctype/bank_account_type/bank_account_type.json #: erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -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:368 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:371 msgid "Account: {0} can only be updated via Stock Transactions" msgstr "" @@ -1853,8 +1853,8 @@ msgstr "" msgid "Accounting Entry for Asset" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1964 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1984 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2056 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2076 msgid "Accounting Entry for LCV in Stock Entry {0}" msgstr "" @@ -1862,29 +1862,29 @@ msgstr "" msgid "Accounting Entry for Landed Cost Voucher for SCR {0}" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:844 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:843 msgid "Accounting Entry for Service" msgstr "" -#: 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/accounts/doctype/purchase_invoice/purchase_invoice.py:1017 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1038 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1056 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1077 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1098 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1126 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1238 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1474 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1496 #: erpnext/controllers/stock_controller.py:727 #: erpnext/controllers/stock_controller.py:744 -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:937 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1909 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1923 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:936 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2001 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2015 #: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:745 msgid "Accounting Entry for Stock" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:741 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:740 msgid "Accounting Entry for {0}" msgstr "" @@ -1978,12 +1978,12 @@ msgstr "" msgid "Accounts Frozen Till Date" msgstr "" -#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:192 +#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:186 msgid "Accounts Included in Report" msgstr "" -#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:166 -#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:191 +#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:160 +#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:185 msgid "Accounts Missing from Report" msgstr "" @@ -2083,7 +2083,7 @@ msgstr "" msgid "Accounts Setup" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1325 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1328 msgid "Accounts table cannot be blank." msgstr "" @@ -2547,7 +2547,7 @@ msgid "Add Employees" msgstr "" #: erpnext/public/js/bom_configurator/bom_configurator.bundle.js:256 -#: erpnext/selling/doctype/sales_order/sales_order.js:277 +#: erpnext/selling/doctype/sales_order/sales_order.js:284 #: erpnext/stock/dashboard/item_dashboard.js:216 msgid "Add Item" 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:1045 +#: erpnext/manufacturing/doctype/bom/bom.js:1047 #: erpnext/manufacturing/doctype/bom_operation/bom_operation.json msgid "Add Raw Materials" msgstr "" @@ -2623,7 +2623,7 @@ msgid "Add Sales Partners" msgstr "" #. Label of the add_schedule (Button) field in DocType 'Sales Order Item' -#: erpnext/selling/doctype/sales_order/sales_order.js:649 +#: erpnext/selling/doctype/sales_order/sales_order.js:656 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json msgid "Add Schedule" msgstr "" @@ -2885,7 +2885,7 @@ msgstr "" msgid "Additional Discount Amount (Company Currency)" msgstr "" -#: erpnext/controllers/taxes_and_totals.py:833 +#: erpnext/controllers/taxes_and_totals.py:837 msgid "Additional Discount Amount ({discount_amount}) cannot exceed the total before such discount ({total_before_discount})" msgstr "" @@ -2980,7 +2980,7 @@ msgstr "" msgid "Additional Information updated successfully." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:812 +#: erpnext/manufacturing/doctype/work_order/work_order.js:818 msgid "Additional Material Transfer" msgstr "" @@ -3167,11 +3167,11 @@ msgstr "" msgid "Adjust Qty" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1130 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1156 msgid "Adjustment Against" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:669 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:668 msgid "Adjustment based on Purchase Invoice rate" msgstr "" @@ -3284,11 +3284,11 @@ msgstr "" msgid "Advance amount" msgstr "" -#: erpnext/controllers/taxes_and_totals.py:970 +#: erpnext/controllers/taxes_and_totals.py:974 msgid "Advance amount cannot be greater than {0} {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:873 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:876 msgid "Advance paid against {0} {1} cannot be greater than Grand Total {2}" msgstr "" @@ -3410,7 +3410,7 @@ msgstr "" msgid "Against Income Account" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:735 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:738 #: erpnext/accounts/doctype/payment_entry/payment_entry.py:777 msgid "Against Journal Entry {0} does not have any unmatched {1} entry" msgstr "" @@ -3596,7 +3596,7 @@ msgstr "" #: erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js:165 #: erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js:185 #: erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py:166 -#: erpnext/accounts/utils.py:1555 erpnext/public/js/setup_wizard.js:184 +#: erpnext/accounts/utils.py:1631 erpnext/public/js/setup_wizard.js:184 msgid "All Accounts" msgstr "" @@ -3757,7 +3757,7 @@ msgstr "" msgid "All items are already requested" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1430 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1487 msgid "All items have already been Invoiced/Returned" msgstr "" @@ -3765,7 +3765,7 @@ msgstr "" msgid "All items have already been received" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3136 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3335 msgid "All items have already been transferred for this Work Order." msgstr "" @@ -3773,11 +3773,11 @@ msgstr "" msgid "All items in this document already have a linked Quality Inspection." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1239 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1236 msgid "All items must be linked to a Sales Order or Subcontracting Inward Order for this Sales Invoice." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1250 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1247 msgid "All linked Sales Orders must be subcontracted." msgstr "" @@ -3791,7 +3791,7 @@ msgstr "" msgid "All the items have been already returned." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1196 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1256 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 "" @@ -4337,8 +4337,8 @@ msgstr "" #: erpnext/manufacturing/doctype/bom/bom.js:288 #: erpnext/manufacturing/doctype/work_order/work_order.js:165 #: erpnext/manufacturing/doctype/work_order/work_order.js:180 -#: erpnext/public/js/utils.js:571 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:288 +#: erpnext/public/js/utils.js:587 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:322 msgid "Alternate Item" msgstr "" @@ -4738,7 +4738,7 @@ msgstr "" msgid "An Item Group is a way to classify items based on types." msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:537 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:558 msgid "An error has been appeared while reposting item valuation via {0}" msgstr "" @@ -4765,7 +4765,7 @@ msgstr "" msgid "Analytical Accounting" msgstr "" -#: erpnext/public/js/utils.js:168 +#: erpnext/public/js/utils.js:184 msgid "Annual Billing: {0}" msgstr "" @@ -5705,7 +5705,7 @@ msgstr "" msgid "Asset restored after Asset Capitalization {0} was cancelled" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1522 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1519 msgid "Asset returned" msgstr "" @@ -5717,8 +5717,8 @@ msgstr "" msgid "Asset scrapped via Journal Entry {0}" msgstr "" +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1519 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1522 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1525 msgid "Asset sold" msgstr "" @@ -5857,7 +5857,7 @@ msgstr "" msgid "At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} in the warehouse {4}." msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1403 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1436 msgid "At Row {0}: In Serial and Batch Bundle {1} must have docstatus as 1 and not 0" msgstr "" @@ -5890,19 +5890,19 @@ msgstr "" msgid "At least one of the Selling or Buying must be selected" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:316 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:324 msgid "At least one raw material item must be present in the stock entry for the type {0}" msgstr "" -#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:25 +#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:27 msgid "At least one row is required for a financial report template" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:819 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:881 msgid "At least one warehouse is mandatory" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:721 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:783 msgid "At row #{0}: the Difference Account must not be a Stock type account, please change the Account Type for the account {1} or select a different account" msgstr "" @@ -5910,11 +5910,11 @@ msgstr "" msgid "At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:732 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:794 msgid "At row #{0}: you have selected the Difference Account {1}, which is a Cost of Goods Sold type account. Please select a different account" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1151 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1184 msgid "At row {0}: Batch No is mandatory for Item {1}" msgstr "" @@ -5922,11 +5922,11 @@ msgstr "" msgid "At row {0}: Parent Row No cannot be set for item {1}" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1136 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1169 msgid "At row {0}: Qty is mandatory for the batch {1}" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1143 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1176 msgid "At row {0}: Serial No is mandatory for Item {1}" msgstr "" @@ -6319,7 +6319,7 @@ msgstr "" #: erpnext/manufacturing/doctype/workstation/workstation.js:505 #: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:118 #: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.py:175 -#: erpnext/public/js/utils.js:631 +#: erpnext/public/js/utils.js:647 #: 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:170 @@ -6411,7 +6411,7 @@ msgstr "" msgid "Available for use date is required" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:952 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1044 msgid "Available quantity is {0}, you need {1}" msgstr "" @@ -6539,10 +6539,10 @@ msgstr "" #: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js:8 #: erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py:109 #: erpnext/manufacturing/workspace/manufacturing/manufacturing.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1415 +#: erpnext/selling/doctype/sales_order/sales_order.js:1422 #: erpnext/stock/doctype/material_request/material_request.js:351 #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json -#: erpnext/stock/doctype/stock_entry/stock_entry.js:709 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:796 #: erpnext/stock/report/bom_search/bom_search.py:38 #: erpnext/subcontracting/doctype/subcontracting_inward_order_item/subcontracting_inward_order_item.json #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:525 @@ -6791,7 +6791,7 @@ msgstr "" msgid "BOM Website Operation" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2284 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2448 msgid "BOM and Finished Good Quantity is mandatory for Disassembly" msgstr "" @@ -6802,7 +6802,7 @@ msgid "BOM and Production" msgstr "" #: erpnext/stock/doctype/material_request/material_request.js:386 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:761 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:848 msgid "BOM does not contain any stock item" msgstr "" @@ -6835,15 +6835,15 @@ msgstr "" msgid "BOMs Updated" msgstr "" -#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:297 +#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:299 msgid "BOMs created successfully" msgstr "" -#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:307 +#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:309 msgid "BOMs creation failed" msgstr "" -#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:247 +#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:249 msgid "BOMs creation has been enqueued, kindly check the status after some time" msgstr "" @@ -6859,7 +6859,7 @@ msgstr "" #. Order Operation' #: erpnext/manufacturing/doctype/bom_operation/bom_operation.json #: erpnext/manufacturing/doctype/job_card/job_card.json -#: erpnext/manufacturing/doctype/work_order/work_order.js:368 +#: erpnext/manufacturing/doctype/work_order/work_order.js:371 #: erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json msgid "Backflush Materials From WIP Warehouse" msgstr "" @@ -7585,11 +7585,11 @@ msgstr "" msgid "Batch No" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1154 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1187 msgid "Batch No is mandatory" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3435 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3468 msgid "Batch No {0} does not exists" msgstr "" @@ -7597,7 +7597,7 @@ msgstr "" msgid "Batch No {0} is linked with Item {1} which has serial no. Please scan serial no instead." msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:458 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:491 msgid "Batch No {0} is not present in the original {1} {2}, hence you can't return it against the {1} {2}" msgstr "" @@ -7612,7 +7612,7 @@ msgstr "" msgid "Batch Nos" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1976 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2009 msgid "Batch Nos are created successfully" msgstr "" @@ -7649,7 +7649,7 @@ msgstr "" #. Label of the batch_size (Float) field in DocType 'Work Order Operation' #: erpnext/manufacturing/doctype/bom_operation/bom_operation.json #: erpnext/manufacturing/doctype/operation/operation.json -#: erpnext/manufacturing/doctype/work_order/work_order.js:350 +#: erpnext/manufacturing/doctype/work_order/work_order.js:353 #: erpnext/manufacturing/doctype/work_order/work_order.json #: erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json msgid "Batch Size" @@ -7678,12 +7678,12 @@ msgstr "" msgid "Batch {0} is not available in warehouse {1}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3320 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3519 #: erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py:290 msgid "Batch {0} of Item {1} has expired." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3326 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3525 msgid "Batch {0} of Item {1} is disabled." msgstr "" @@ -7753,7 +7753,7 @@ msgstr "" #: erpnext/manufacturing/doctype/bom/bom.py:1372 #: erpnext/manufacturing/workspace/manufacturing/manufacturing.json #: erpnext/stock/doctype/material_request/material_request.js:139 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:695 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:782 #: erpnext/workspace_sidebar/subcontracting.json msgid "Bill of Materials" msgstr "" @@ -8189,10 +8189,6 @@ msgstr "" msgid "Booked Fixed Asset" msgstr "" -#: erpnext/stock/doctype/warehouse/warehouse.py:146 -msgid "Booking stock value across multiple accounts will make it harder to track stock and account value." -msgstr "" - #: erpnext/accounts/general_ledger.py:828 msgid "Books have been closed till the period ending on {0}" msgstr "" @@ -8911,7 +8907,7 @@ msgstr "" msgid "Can be approved by {0}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2530 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2554 msgid "Can not close Work Order. Since {0} Job Cards are in Work In Progress state." msgstr "" @@ -8939,7 +8935,7 @@ msgstr "" msgid "Can not filter based on Voucher No, if grouped by Voucher" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1384 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1387 #: erpnext/accounts/doctype/payment_entry/payment_entry.py:2874 msgid "Can only make payment against unbilled {0}" msgstr "" @@ -8987,7 +8983,7 @@ msgstr "" msgid "Cannot Assign Cashier" msgstr "" -#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:90 +#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:92 #: erpnext/stock/doctype/delivery_trip/delivery_trip.py:219 msgid "Cannot Calculate Arrival Time as Driver Address is Missing." msgstr "" @@ -9006,7 +9002,7 @@ msgstr "" msgid "Cannot Merge" msgstr "" -#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:123 +#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:125 msgid "Cannot Optimize Route as Driver Address is Missing." msgstr "" @@ -9026,7 +9022,7 @@ msgstr "" msgid "Cannot amend {0} {1}, please create a new one instead." msgstr "" -#: erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py:1293 +#: erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py:1297 msgid "Cannot apply TDS against multiple parties in one entry" msgstr "" @@ -9046,7 +9042,7 @@ msgstr "" msgid "Cannot cancel Stock Reservation Entry {0}, as it has used in the work order {1}. Please cancel the work order first or unreserved the stock" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:252 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:273 msgid "Cannot cancel as processing of cancelled documents is pending." msgstr "" @@ -9062,7 +9058,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:575 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:578 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 "" @@ -9070,7 +9066,7 @@ msgstr "" msgid "Cannot cancel this document as it is linked with the submitted asset {asset_link}. Please cancel the asset to continue." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:496 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:558 msgid "Cannot cancel transaction for Completed Work Order." msgstr "" @@ -9114,7 +9110,7 @@ msgstr "" msgid "Cannot covert to Group because Account Type is selected." msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1018 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1017 msgid "Cannot create Stock Reservation Entries for future dated Purchase Receipts." msgstr "" @@ -9181,6 +9177,10 @@ msgstr "" msgid "Cannot disassemble more than produced quantity." msgstr "" +#: erpnext/stock/doctype/stock_entry/stock_entry.py:924 +msgid "Cannot disassemble {0} qty against Stock Entry {1}. Only {2} qty available to disassemble." +msgstr "" + #: erpnext/setup/doctype/company/company.py:223 msgid "Cannot enable Item-wise Inventory Account, as there are existing Stock Ledger Entries for the company {0} with Warehouse-wise Inventory Account. Please cancel the stock transactions first and try again." msgstr "" @@ -9214,11 +9214,11 @@ msgstr "" msgid "Cannot produce more Item {0} than Sales Order quantity {1} {2}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:1450 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1451 msgid "Cannot produce more item for {0}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:1454 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1455 msgid "Cannot produce more than {0} items for {1}" msgstr "" @@ -9565,7 +9565,7 @@ msgstr "" msgid "Caution" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:187 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:208 msgid "Caution: This might alter frozen accounts." msgstr "" @@ -9771,7 +9771,7 @@ msgstr "" #. Label of a Link in the Invoicing Workspace #. Label of a Workspace Sidebar Item -#: erpnext/accounts/doctype/account/account_tree.js:196 +#: erpnext/accounts/doctype/account/account_tree.js:191 #: erpnext/accounts/doctype/cost_center/cost_center.js:41 #: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/workspace_sidebar/accounts_setup.json @@ -10110,7 +10110,7 @@ msgstr "" msgid "Closed Documents" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2453 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2477 msgid "Closed Work Order can not be stopped or Re-opened" msgstr "" @@ -10986,12 +10986,12 @@ msgstr "" msgid "Company and Posting Date is mandatory" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2581 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2578 msgid "Company currencies of both the companies should match for Inter Company Transactions." msgstr "" #: erpnext/stock/doctype/material_request/material_request.js:380 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:755 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:842 msgid "Company field is required" msgstr "" @@ -11138,12 +11138,12 @@ msgstr "" msgid "Completed Qty" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:1368 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1369 msgid "Completed Qty cannot be greater than 'Qty to Manufacture'" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:323 -#: erpnext/manufacturing/doctype/job_card/job_card.js:444 +#: erpnext/manufacturing/doctype/job_card/job_card.js:325 +#: erpnext/manufacturing/doctype/job_card/job_card.js:446 #: erpnext/manufacturing/doctype/workstation/workstation.js:296 msgid "Completed Quantity" msgstr "" @@ -11289,7 +11289,7 @@ msgstr "" msgid "Consider Minimum Order Qty" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1018 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1078 msgid "Consider Process Loss" msgstr "" @@ -11480,7 +11480,7 @@ msgstr "" msgid "Consumed Qty" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:1744 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1745 msgid "Consumed Qty cannot be greater than Reserved Qty for item {0}" msgstr "" @@ -11792,7 +11792,7 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_item/bom_item.json #: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json #: erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json -#: erpnext/public/js/utils.js:886 +#: erpnext/public/js/utils.js:902 #: 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 @@ -11900,13 +11900,13 @@ msgstr "" msgid "Corrective Action" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:501 +#: erpnext/manufacturing/doctype/job_card/job_card.js:503 msgid "Corrective Job Card" msgstr "" #. Label of the corrective_operation_section (Tab Break) field in DocType 'Job #. Card' -#: erpnext/manufacturing/doctype/job_card/job_card.js:508 +#: erpnext/manufacturing/doctype/job_card/job_card.js:510 #: erpnext/manufacturing/doctype/job_card/job_card.json msgid "Corrective Operation" msgstr "" @@ -12158,8 +12158,8 @@ 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:1433 -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:903 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1439 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:902 msgid "Cost Center is required in row {0} in Taxes table for type {1}" msgstr "" @@ -12229,7 +12229,7 @@ msgstr "" msgid "Cost of Goods Sold" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:735 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:797 msgid "Cost of Goods Sold Account in Items Table" msgstr "" @@ -12394,7 +12394,7 @@ msgstr "" msgid "Coupon Type" msgstr "" -#: erpnext/accounts/doctype/account/account_tree.js:86 +#: erpnext/accounts/doctype/account/account_tree.js:63 #: erpnext/accounts/doctype/bank_clearance/bank_clearance.py:84 #: erpnext/templates/form_grid/bank_reconciliation_grid.html:16 msgid "Cr" @@ -12592,7 +12592,7 @@ msgstr "" msgid "Create Payment Request" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:794 +#: erpnext/manufacturing/doctype/work_order/work_order.js:800 msgid "Create Pick List" msgstr "" @@ -12619,7 +12619,7 @@ msgstr "" #. Title of an Onboarding Step #. Label of an action in the Onboarding Step 'Create Purchase Order' #: erpnext/buying/onboarding_step/create_purchase_order/create_purchase_order.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1668 +#: erpnext/selling/doctype/sales_order/sales_order.js:1675 #: erpnext/utilities/activation.py:106 msgid "Create Purchase Order" msgstr "" @@ -12804,7 +12804,7 @@ msgstr "" msgid "Create a variant with the template image." msgstr "" -#: erpnext/stock/stock_ledger.py:2072 +#: erpnext/stock/stock_ledger.py:2058 msgid "Create an incoming stock transaction for the Item." msgstr "" @@ -12852,11 +12852,11 @@ msgstr "" msgid "Creating Accounts..." msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1543 +#: erpnext/selling/doctype/sales_order/sales_order.js:1550 msgid "Creating Delivery Note ..." msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:677 +#: erpnext/selling/doctype/sales_order/sales_order.js:684 msgid "Creating Delivery Schedule..." msgstr "" @@ -12876,7 +12876,7 @@ msgstr "" msgid "Creating Purchase Invoices ..." msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1692 +#: erpnext/selling/doctype/sales_order/sales_order.js:1699 msgid "Creating Purchase Order ..." msgstr "" @@ -12895,7 +12895,7 @@ msgstr "" msgid "Creating Stock Entry" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1813 +#: erpnext/selling/doctype/sales_order/sales_order.js:1820 msgid "Creating Subcontracting Inward Order ..." msgstr "" @@ -13128,8 +13128,8 @@ msgstr "" #. Label of the credit_to (Link) field in DocType 'Purchase Invoice' #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:378 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:386 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:375 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:383 #: erpnext/controllers/accounts_controller.py:2370 msgid "Credit To" msgstr "" @@ -13332,7 +13332,7 @@ msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.py:1604 #: erpnext/accounts/doctype/payment_entry/payment_entry.py:1672 -#: erpnext/accounts/utils.py:2456 +#: erpnext/accounts/utils.py:2532 msgid "Currency for {0} must be {1}" msgstr "" @@ -13597,7 +13597,7 @@ msgstr "" #: erpnext/accounts/doctype/pricing_rule/pricing_rule.json #: erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json #: erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:309 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:390 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json #: erpnext/accounts/doctype/sales_invoice_reference/sales_invoice_reference.json #: erpnext/accounts/doctype/tax_rule/tax_rule.json @@ -13637,7 +13637,7 @@ msgstr "" #: erpnext/selling/doctype/customer/customer.json #: erpnext/selling/doctype/installation_note/installation_note.json #: erpnext/selling/doctype/party_specific_item/party_specific_item.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1183 +#: erpnext/selling/doctype/sales_order/sales_order.js:1190 #: erpnext/selling/doctype/sales_order/sales_order.json #: erpnext/selling/doctype/sales_order/sales_order_calendar.js:19 #: erpnext/selling/doctype/sms_center/sms_center.json @@ -13675,7 +13675,7 @@ msgstr "" #: erpnext/stock/doctype/pick_list/pick_list.json #: erpnext/stock/doctype/serial_no/serial_no.json #: erpnext/stock/doctype/shipment/shipment.json -#: erpnext/stock/doctype/stock_entry/stock_entry.js:385 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:472 #: erpnext/stock/doctype/warehouse/warehouse.json #: erpnext/stock/report/delayed_item_report/delayed_item_report.js:36 #: erpnext/stock/report/delayed_item_report/delayed_item_report.py:121 @@ -14154,7 +14154,7 @@ msgstr "" msgid "Customer required for 'Customerwise Discount'" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1143 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1140 #: erpnext/selling/doctype/sales_order/sales_order.py:434 #: erpnext/stock/doctype/delivery_note/delivery_note.py:436 msgid "Customer {0} does not belong to project {1}" @@ -14207,7 +14207,7 @@ msgstr "" msgid "Customer-wise Item Price" msgstr "" -#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:38 +#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:44 msgid "Customer/Lead Name" msgstr "" @@ -14776,7 +14776,7 @@ msgstr "" msgid "Default BOM ({0}) must be active for this item or its template" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2244 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2245 msgid "Default BOM for {0} not found" msgstr "" @@ -14784,7 +14784,7 @@ msgstr "" msgid "Default BOM not found for FG Item {0}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2241 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2242 msgid "Default BOM not found for Item {0} and Project {1}" msgstr "" @@ -15509,10 +15509,10 @@ 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:879 +#: erpnext/public/js/utils.js:895 #: 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:1490 +#: erpnext/selling/doctype/sales_order/sales_order.js:631 +#: erpnext/selling/doctype/sales_order/sales_order.js:1497 #: erpnext/selling/doctype/sales_order/sales_order.json #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/selling/report/sales_order_analysis/sales_order_analysis.py:321 @@ -15552,19 +15552,19 @@ msgstr "" #. Label of a Workspace Sidebar Item #: erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json #: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:129 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:332 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:413 #: erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js:36 #: erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json #: erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.js:22 #: erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py:21 #: erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py:291 #: erpnext/accounts/report/sales_register/sales_register.py:245 -#: erpnext/selling/doctype/sales_order/sales_order.js:1042 +#: erpnext/selling/doctype/sales_order/sales_order.js:1049 #: erpnext/selling/doctype/sales_order/sales_order_list.js:81 #: erpnext/setup/doctype/authorization_rule/authorization_rule.json #: erpnext/stock/doctype/delivery_note/delivery_note.json #: erpnext/stock/doctype/delivery_stop/delivery_stop.json -#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:52 +#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:54 #: erpnext/stock/doctype/packing_slip/packing_slip.json #: erpnext/stock/doctype/pick_list/pick_list.js:134 #: erpnext/stock/doctype/purchase_receipt/purchase_receipt.js:59 @@ -15612,12 +15612,12 @@ msgstr "" msgid "Delivery Note Trends" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1404 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1401 msgid "Delivery Note {0} is not submitted" msgstr "" #: erpnext/accounts/report/accounts_receivable/accounts_receivable.py:1285 -#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:73 +#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:75 msgid "Delivery Notes" msgstr "" @@ -15629,8 +15629,8 @@ msgstr "" msgid "Delivery Notes {0} updated" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:619 -#: erpnext/selling/doctype/sales_order/sales_order.js:646 +#: erpnext/selling/doctype/sales_order/sales_order.js:626 +#: erpnext/selling/doctype/sales_order/sales_order.js:653 msgid "Delivery Schedule" msgstr "" @@ -16025,11 +16025,11 @@ msgstr "" msgid "Difference Account" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:724 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:786 msgid "Difference Account in Items Table" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:713 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:775 msgid "Difference Account must be a Asset/Liability type account (Temporary Opening), since this Stock Entry is an Opening Entry" msgstr "" @@ -16276,6 +16276,9 @@ msgstr "" #. Option for the 'Purpose' (Select) field in DocType 'Stock Entry' #. Option for the 'Purpose' (Select) field in DocType 'Stock Entry Type' +#: erpnext/manufacturing/doctype/work_order/work_order.js:1056 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:370 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:413 #: erpnext/stock/doctype/stock_entry/stock_entry.json #: erpnext/stock/doctype/stock_entry_type/stock_entry_type.json msgid "Disassemble" @@ -16285,7 +16288,11 @@ msgstr "" msgid "Disassemble Order" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:444 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2390 +msgid "Disassemble Qty cannot be less than or equal to 0." +msgstr "" + +#: erpnext/manufacturing/doctype/work_order/work_order.js:447 msgid "Disassemble Qty cannot be less than or equal to 0." msgstr "" @@ -16834,7 +16841,7 @@ msgstr "" msgid "Do you want to change valuation method?" msgstr "" -#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:156 +#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:158 msgid "Do you want to notify all the customers by email?" msgstr "" @@ -17085,7 +17092,7 @@ msgstr "" msgid "Due Date cannot be before {0}" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:144 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:165 msgid "Due to stock closing entry {0}, you cannot repost item valuation before {1}" msgstr "" @@ -18022,8 +18029,8 @@ msgstr "" #. Label of the end_time (Time) field in DocType 'Stock Reposting Settings' #. Label of the end_time (Time) field in DocType 'Service Day' #. Label of the end_time (Datetime) field in DocType 'Call Log' -#: erpnext/manufacturing/doctype/job_card/job_card.js:381 -#: erpnext/manufacturing/doctype/job_card/job_card.js:451 +#: erpnext/manufacturing/doctype/job_card/job_card.js:383 +#: erpnext/manufacturing/doctype/job_card/job_card.js:453 #: erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json #: erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json #: erpnext/support/doctype/service_day/service_day.json @@ -18031,7 +18038,7 @@ msgstr "" msgid "End Time" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.js:311 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:345 msgid "End Transit" msgstr "" @@ -18108,8 +18115,8 @@ msgstr "" msgid "Enter Serial Nos" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:408 -#: erpnext/manufacturing/doctype/job_card/job_card.js:477 +#: erpnext/manufacturing/doctype/job_card/job_card.js:410 +#: erpnext/manufacturing/doctype/job_card/job_card.js:479 #: erpnext/manufacturing/doctype/workstation/workstation.js:312 msgid "Enter Value" msgstr "" @@ -18185,11 +18192,11 @@ msgstr "" msgid "Enter the opening stock units." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:990 +#: erpnext/manufacturing/doctype/bom/bom.js:992 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:1158 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1218 msgid "Enter the quantity to manufacture. Raw material Items will be fetched only when this is set." msgstr "" @@ -18278,7 +18285,7 @@ msgstr "" msgid "Error while processing deferred accounting for {0}" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:533 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:554 msgid "Error while reposting item valuation" msgstr "" @@ -18351,7 +18358,7 @@ msgstr "" msgid "Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions, then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item, leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings." msgstr "" -#: erpnext/stock/stock_ledger.py:2335 +#: erpnext/stock/stock_ledger.py:2321 msgid "Example: Serial No {0} reserved in {1}." msgstr "" @@ -18361,6 +18368,10 @@ msgstr "" msgid "Exception Budget Approver Role" msgstr "" +#: erpnext/stock/doctype/stock_entry/stock_entry.py:931 +msgid "Excess Disassembly" +msgstr "" + #: erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js:55 msgid "Excess Materials Consumed" msgstr "" @@ -18506,7 +18517,7 @@ msgstr "" msgid "Excise Entry" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.js:1411 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:1501 msgid "Excise Invoice" msgstr "" @@ -18699,7 +18710,7 @@ msgstr "" #: erpnext/accounts/doctype/cashier_closing/cashier_closing.json #: erpnext/accounts/doctype/ledger_merge/ledger_merge.json #: erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:604 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:601 #: erpnext/accounts/report/account_balance/account_balance.js:28 #: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js:89 #: erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py:183 @@ -18770,13 +18781,13 @@ msgstr "" msgid "Expense Head" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:498 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:522 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:542 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:495 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:519 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:539 msgid "Expense Head Changed" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:600 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:597 msgid "Expense account is mandatory for item {0}" msgstr "" @@ -18802,7 +18813,7 @@ msgid "Expenses Included In Valuation" msgstr "" #: erpnext/stock/doctype/pick_list/pick_list.py:307 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:409 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:496 msgid "Expired Batches" msgstr "" @@ -19079,7 +19090,7 @@ msgstr "" msgid "Fetch Subscription Updates" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1069 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:284 msgid "Fetch Timesheet" msgstr "" @@ -19102,7 +19113,7 @@ msgid "Fetch Value From" msgstr "" #: erpnext/stock/doctype/material_request/material_request.js:372 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:732 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:819 msgid "Fetch exploded BOM (including sub-assemblies)" msgstr "" @@ -19363,9 +19374,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:878 -#: 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:884 +#: erpnext/manufacturing/doctype/work_order/work_order.js:899 +#: erpnext/manufacturing/doctype/work_order/work_order.js:908 msgid "Finish" msgstr "" @@ -19381,7 +19392,7 @@ msgstr "" #: erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json #: erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py:43 #: erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py:147 -#: erpnext/selling/doctype/sales_order/sales_order.js:824 +#: erpnext/selling/doctype/sales_order/sales_order.js:831 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.json msgid "Finished Good" @@ -19396,7 +19407,7 @@ msgstr "" #. Service Item' #. Label of the fg_item (Link) field in DocType 'Subcontracting Order Service #. Item' -#: erpnext/public/js/utils.js:905 +#: erpnext/public/js/utils.js:921 #: 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" @@ -19409,7 +19420,7 @@ msgstr "" msgid "Finished Good Item Code" msgstr "" -#: erpnext/public/js/utils.js:923 +#: erpnext/public/js/utils.js:939 msgid "Finished Good Item Qty" msgstr "" @@ -19516,11 +19527,11 @@ msgstr "" msgid "Finished Goods based Operating Cost" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1675 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1767 msgid "Finished Item {0} does not match with Work Order {1}" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:577 +#: erpnext/selling/doctype/sales_order/sales_order.js:584 msgid "First Delivery Date" msgstr "" @@ -19831,7 +19842,7 @@ msgid "For Job Card" msgstr "" #. Label of the for_operation (Link) field in DocType 'Job Card' -#: erpnext/manufacturing/doctype/job_card/job_card.js:521 +#: erpnext/manufacturing/doctype/job_card/job_card.js:523 #: erpnext/manufacturing/doctype/job_card/job_card.json msgid "For Operation" msgstr "" @@ -19852,7 +19863,7 @@ msgstr "" msgid "For Production" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:836 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:898 msgid "For Quantity (Manufactured Qty) is mandatory" msgstr "" @@ -19880,7 +19891,7 @@ msgstr "" #: erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json #: erpnext/manufacturing/doctype/production_plan/production_plan.js:471 #: erpnext/manufacturing/doctype/production_plan/production_plan.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1407 +#: erpnext/selling/doctype/sales_order/sales_order.js:1414 #: erpnext/stock/doctype/material_request/material_request.js:361 #: erpnext/templates/form_grid/material_request_grid.html:36 msgid "For Warehouse" @@ -19932,7 +19943,7 @@ msgstr "" msgid "For operation {0} at row {1}, please add raw materials or set a BOM against it." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2600 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2624 msgid "For operation {0}: Quantity ({1}) can not be greater than pending quantity({2})" msgstr "" @@ -19949,7 +19960,7 @@ msgstr "" msgid "For projected and forecast quantities, the system will consider all child warehouses under the selected parent warehouse." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1707 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1799 msgid "For quantity {0} should not be greater than allowed quantity {1}" msgstr "" @@ -19982,7 +19993,7 @@ msgstr "" msgid "For the convenience of customers, these codes can be used in print formats like Invoices and Delivery Notes" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:977 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1069 msgid "For the item {0}, the consumed quantity should be {1} according to the BOM {2}." msgstr "" @@ -20544,7 +20555,7 @@ msgstr "" msgid "Furniture and Fixtures" msgstr "" -#: erpnext/accounts/doctype/account/account_tree.js:140 +#: erpnext/accounts/doctype/account/account_tree.js:135 msgid "Further accounts can be made under Groups, but entries can be made against non-Groups" msgstr "" @@ -20833,7 +20844,7 @@ msgstr "" msgid "Get Customer Group Details" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:608 +#: erpnext/selling/doctype/sales_order/sales_order.js:615 msgid "Get Delivery Schedule" msgstr "" @@ -20871,10 +20882,10 @@ msgstr "" #. Label of the get_items_from (Select) field in DocType 'Production Plan' #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js:166 #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js:191 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:293 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:325 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:359 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1125 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:340 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:374 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:406 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:446 #: 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:380 @@ -20889,19 +20900,19 @@ 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:203 -#: erpnext/selling/doctype/sales_order/sales_order.js:1200 +#: erpnext/selling/doctype/sales_order/sales_order.js:210 +#: erpnext/selling/doctype/sales_order/sales_order.js:1207 #: erpnext/stock/doctype/delivery_note/delivery_note.js:187 #: erpnext/stock/doctype/delivery_note/delivery_note.js:239 #: erpnext/stock/doctype/material_request/material_request.js:141 #: erpnext/stock/doctype/material_request/material_request.js:238 #: erpnext/stock/doctype/purchase_receipt/purchase_receipt.js:144 #: erpnext/stock/doctype/purchase_receipt/purchase_receipt.js:244 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:352 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:399 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:432 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:523 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:699 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:439 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:486 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:519 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:610 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:786 #: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js:165 msgid "Get Items From" msgstr "" @@ -20917,8 +20928,8 @@ msgid "Get Items for Purchase Only" msgstr "" #: erpnext/stock/doctype/material_request/material_request.js:346 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:735 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:748 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:822 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:835 msgid "Get Items from BOM" msgstr "" @@ -21021,7 +21032,7 @@ msgstr "" msgid "Get Suppliers By" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1121 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:336 msgid "Get Timesheets" msgstr "" @@ -21034,7 +21045,7 @@ msgstr "" msgid "Get Unreconciled Entries" msgstr "" -#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:69 +#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:71 msgid "Get stops from" msgstr "" @@ -21093,7 +21104,7 @@ msgstr "" msgid "Goods Transferred" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2225 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2317 msgid "Goods are already received against the outward entry {0}" msgstr "" @@ -21695,7 +21706,7 @@ msgstr "" msgid "Here are the error logs for the aforementioned failed depreciation entries: {0}" msgstr "" -#: erpnext/stock/stock_ledger.py:2057 +#: erpnext/stock/stock_ledger.py:2043 msgid "Here are the options to proceed:" msgstr "" @@ -21723,7 +21734,7 @@ msgstr "" msgid "Hertz" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:535 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:556 msgid "Hi," msgstr "" @@ -21790,7 +21801,7 @@ msgid "History In Company" msgstr "" #: erpnext/buying/doctype/purchase_order/purchase_order.js:338 -#: erpnext/selling/doctype/sales_order/sales_order.js:989 +#: erpnext/selling/doctype/sales_order/sales_order.js:996 msgid "Hold" msgstr "" @@ -22280,7 +22291,7 @@ msgstr "" msgid "If no taxes are set, and Taxes and Charges Template is selected, the system will automatically apply the taxes from the chosen template." msgstr "" -#: erpnext/stock/stock_ledger.py:2067 +#: erpnext/stock/stock_ledger.py:2053 msgid "If not, you can Cancel / Submit this entry" msgstr "" @@ -22308,7 +22319,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:1191 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1251 msgid "If the BOM results in Scrap material, the Scrap Warehouse needs to be selected." msgstr "" @@ -22317,7 +22328,7 @@ msgstr "" msgid "If the account is frozen, entries are allowed to restricted users." msgstr "" -#: erpnext/stock/stock_ledger.py:2060 +#: erpnext/stock/stock_ledger.py:2046 msgid "If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table." msgstr "" @@ -22327,7 +22338,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:1210 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1270 msgid "If the selected BOM has Operations mentioned in it, the system will fetch all Operations from BOM, these values can be changed." msgstr "" @@ -22494,7 +22505,7 @@ msgstr "" msgid "Ignore Exchange Rate Revaluation and Gain / Loss Journals" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1390 +#: erpnext/selling/doctype/sales_order/sales_order.js:1397 msgid "Ignore Existing Ordered Qty" msgstr "" @@ -22949,7 +22960,7 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json #: erpnext/buying/doctype/purchase_order_item/purchase_order_item.json #: erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1386 +#: erpnext/selling/doctype/sales_order/sales_order.js:1393 #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json #: erpnext/subcontracting/doctype/subcontracting_inward_order_item/subcontracting_inward_order_item.json #: erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json @@ -23170,7 +23181,7 @@ msgstr "" msgid "Incorrect Company" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:984 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1076 msgid "Incorrect Component Quantity" msgstr "" @@ -23475,13 +23486,13 @@ msgstr "" #: erpnext/stock/doctype/pick_list/pick_list.py:145 #: erpnext/stock/doctype/pick_list/pick_list.py:163 #: erpnext/stock/doctype/pick_list/pick_list.py:1088 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:956 -#: erpnext/stock/serial_batch_bundle.py:1205 erpnext/stock/stock_ledger.py:1748 -#: erpnext/stock/stock_ledger.py:2226 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1048 +#: erpnext/stock/serial_batch_bundle.py:1205 erpnext/stock/stock_ledger.py:1734 +#: erpnext/stock/stock_ledger.py:2212 msgid "Insufficient Stock" msgstr "" -#: erpnext/stock/stock_ledger.py:2241 +#: erpnext/stock/stock_ledger.py:2227 msgid "Insufficient Stock for Batch" msgstr "" @@ -23574,7 +23585,7 @@ msgstr "" msgid "Inter Company Order Reference" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1137 +#: erpnext/selling/doctype/sales_order/sales_order.js:1144 msgid "Inter Company Purchase Order" msgstr "" @@ -23640,7 +23651,7 @@ msgstr "" msgid "Internal Customer for company {0} already exists" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1136 +#: erpnext/selling/doctype/sales_order/sales_order.js:1143 msgid "Internal Purchase Order" msgstr "" @@ -23710,8 +23721,8 @@ msgstr "" msgid "Interval should be between 1 to 59 MInutes" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:379 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:387 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:376 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:384 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1017 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1027 #: erpnext/assets/doctype/asset_category/asset_category.py:69 @@ -23762,7 +23773,7 @@ msgstr "" msgid "Invalid Company Field" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2356 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2353 msgid "Invalid Company for Inter Company Transaction." msgstr "" @@ -23784,7 +23795,7 @@ msgstr "" msgid "Invalid Discount" msgstr "" -#: erpnext/controllers/taxes_and_totals.py:840 +#: erpnext/controllers/taxes_and_totals.py:844 msgid "Invalid Discount Amount" msgstr "" @@ -23896,12 +23907,12 @@ msgstr "" msgid "Invalid Selling Price" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1750 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1842 msgid "Invalid Serial and Batch Bundle" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1018 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1040 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1110 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1132 msgid "Invalid Source and Target Warehouse" msgstr "" @@ -23926,7 +23937,7 @@ msgstr "" msgid "Invalid file URL" msgstr "" -#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:93 +#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:87 msgid "Invalid filter formula. Please check the syntax." msgstr "" @@ -23965,7 +23976,7 @@ msgstr "" msgid "Invalid {0}" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2354 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2351 msgid "Invalid {0} for Inter Company Transaction." msgstr "" @@ -23989,7 +24000,7 @@ msgstr "" #. Label of a Workspace Sidebar Item #: erpnext/patches/v15_0/refactor_closing_stock_balance.py:43 #: erpnext/stock/doctype/inventory_dimension/inventory_dimension.json -#: erpnext/stock/doctype/inventory_dimension/inventory_dimension.py:176 +#: erpnext/stock/doctype/inventory_dimension/inventory_dimension.py:175 #: erpnext/workspace_sidebar/stock.json msgid "Inventory Dimension" msgstr "" @@ -24199,7 +24210,7 @@ msgstr "" #: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json #: erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json #: erpnext/accounts/doctype/pos_profile/pos_profile.json -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2405 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2402 #: erpnext/buying/doctype/supplier/supplier.json #: erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py:62 msgid "Invoices" @@ -24934,9 +24945,9 @@ msgstr "" #: erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js:33 #: erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py:204 #: erpnext/buying/workspace/buying/buying.json -#: erpnext/controllers/taxes_and_totals.py:1249 +#: erpnext/controllers/taxes_and_totals.py:1253 #: erpnext/manufacturing/doctype/blanket_order/blanket_order.json -#: erpnext/manufacturing/doctype/bom/bom.js:1083 +#: erpnext/manufacturing/doctype/bom/bom.js:1085 #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/plant_floor/plant_floor.js:109 #: erpnext/manufacturing/doctype/workstation/workstation_job_card.html:25 @@ -24955,7 +24966,7 @@ msgstr "" #: erpnext/public/js/stock_analytics.js:92 #: erpnext/selling/doctype/party_specific_item/party_specific_item.json #: erpnext/selling/doctype/product_bundle_item/product_bundle_item.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1631 +#: erpnext/selling/doctype/sales_order/sales_order.js:1638 #: erpnext/selling/page/point_of_sale/pos_item_cart.js:50 #: erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.js:14 #: erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js:36 @@ -25182,7 +25193,7 @@ msgstr "" #: erpnext/accounts/doctype/pricing_rule_item_code/pricing_rule_item_code.json #: erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json #: erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1078 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:293 #: erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py:68 #: erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py:37 #: erpnext/accounts/report/gross_profit/gross_profit.py:312 @@ -25239,17 +25250,17 @@ msgstr "" #: erpnext/projects/doctype/timesheet/timesheet.js:214 #: erpnext/public/js/controllers/transaction.js:2911 #: erpnext/public/js/stock_reservation.js:112 -#: erpnext/public/js/stock_reservation.js:318 erpnext/public/js/utils.js:563 -#: erpnext/public/js/utils.js:720 +#: erpnext/public/js/stock_reservation.js:318 erpnext/public/js/utils.js:579 +#: erpnext/public/js/utils.js:736 #: 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 #: erpnext/selling/doctype/quotation/quotation.js:298 #: erpnext/selling/doctype/quotation_item/quotation_item.json -#: erpnext/selling/doctype/sales_order/sales_order.js:368 -#: erpnext/selling/doctype/sales_order/sales_order.js:476 -#: erpnext/selling/doctype/sales_order/sales_order.js:1245 -#: erpnext/selling/doctype/sales_order/sales_order.js:1400 +#: erpnext/selling/doctype/sales_order/sales_order.js:375 +#: erpnext/selling/doctype/sales_order/sales_order.js:483 +#: erpnext/selling/doctype/sales_order/sales_order.js:1252 +#: erpnext/selling/doctype/sales_order/sales_order.js:1407 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py:29 #: erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py:27 @@ -25331,7 +25342,7 @@ msgstr "" msgid "Item Code cannot be changed for Serial No." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:454 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:451 msgid "Item Code required at Row No {0}" msgstr "" @@ -25719,9 +25730,9 @@ 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:2917 -#: erpnext/public/js/utils.js:815 +#: erpnext/public/js/utils.js:831 #: erpnext/selling/doctype/quotation_item/quotation_item.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1252 +#: erpnext/selling/doctype/sales_order/sales_order.js:1259 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py:35 #: erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py:33 @@ -26096,7 +26107,7 @@ msgstr "" msgid "Item Wise Tax Details" msgstr "" -#: erpnext/controllers/taxes_and_totals.py:559 +#: erpnext/controllers/taxes_and_totals.py:562 msgid "Item Wise Tax Details do not match with Taxes and Charges at the following rows:" msgstr "" @@ -26116,7 +26127,7 @@ msgstr "" msgid "Item and Warranty Details" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3299 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3498 msgid "Item for row {0} does not match Material Request" msgstr "" @@ -26137,7 +26148,7 @@ msgid "Item must be added using 'Get Items from Purchase Receipts' button" msgstr "" #: erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py:42 -#: erpnext/selling/doctype/sales_order/sales_order.js:1638 +#: erpnext/selling/doctype/sales_order/sales_order.js:1645 msgid "Item name" msgstr "" @@ -26150,7 +26161,7 @@ msgstr "" msgid "Item qty can not be updated as raw materials are already processed." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1155 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1247 msgid "Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {0}" msgstr "" @@ -26249,7 +26260,7 @@ msgstr "" msgid "Item {0} is not a subcontracted item" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2137 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2229 msgid "Item {0} is not active or end of life has been reached" msgstr "" @@ -26269,7 +26280,7 @@ msgstr "" msgid "Item {0} must be a non-stock item" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1488 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1580 msgid "Item {0} not found in 'Raw Materials Supplied' table in {1} {2}" msgstr "" @@ -26356,7 +26367,7 @@ msgid "Items Filter" msgstr "" #: erpnext/manufacturing/doctype/production_plan/production_plan.py:1683 -#: erpnext/selling/doctype/sales_order/sales_order.js:1676 +#: erpnext/selling/doctype/sales_order/sales_order.js:1683 msgid "Items Required" msgstr "" @@ -26387,7 +26398,7 @@ msgstr "" msgid "Items cannot be updated as Subcontracting Order is created against the Purchase Order {0}." msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1436 +#: erpnext/selling/doctype/sales_order/sales_order.js:1443 msgid "Items for Raw Material Request" msgstr "" @@ -26395,7 +26406,7 @@ msgstr "" msgid "Items not found." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1151 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1243 msgid "Items rate has been updated to zero as Allow Zero Valuation Rate is checked for the following items: {0}" msgstr "" @@ -26415,7 +26426,7 @@ msgid "Items to Order and Receive" msgstr "" #: erpnext/public/js/stock_reservation.js:72 -#: erpnext/selling/doctype/sales_order/sales_order.js:327 +#: erpnext/selling/doctype/sales_order/sales_order.js:334 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:226 msgid "Items to Reserve" msgstr "" @@ -26472,7 +26483,7 @@ msgstr "" #: erpnext/manufacturing/doctype/job_card/job_card.json #: erpnext/manufacturing/doctype/job_card/job_card.py:997 #: erpnext/manufacturing/doctype/operation/operation.json -#: erpnext/manufacturing/doctype/work_order/work_order.js:397 +#: erpnext/manufacturing/doctype/work_order/work_order.js:400 #: erpnext/manufacturing/doctype/work_order/work_order.json #: erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js:29 #: erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py:86 @@ -26610,7 +26621,7 @@ msgstr "" msgid "Job Worker Warehouse" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2654 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2678 msgid "Job card {0} created" msgstr "" @@ -26695,7 +26706,7 @@ msgstr "" msgid "Journal Entry Type" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:553 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:556 msgid "Journal Entry for Asset scrapping cannot be cancelled. Please restore the Asset." msgstr "" @@ -26708,7 +26719,7 @@ msgstr "" msgid "Journal Entry type should be set as Depreciation Entry for asset depreciation" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:723 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:726 msgid "Journal Entry {0} does not have account {1} or already matched against other voucher" msgstr "" @@ -27614,7 +27625,7 @@ msgstr "" #. Label of the lost_reasons_section (Section Break) field in DocType #. 'Quotation' #: erpnext/crm/doctype/opportunity/opportunity.json -#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:49 +#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:55 #: erpnext/public/js/utils/sales_common.js:596 #: erpnext/selling/doctype/quotation/quotation.json msgid "Lost Reasons" @@ -27701,7 +27712,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:184 +#: erpnext/public/js/utils.js:200 msgid "Loyalty Points: {0}" msgstr "" @@ -27715,7 +27726,7 @@ msgstr "" #: erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.json #: erpnext/accounts/doctype/loyalty_program/loyalty_program.json #: erpnext/accounts/doctype/pos_invoice/pos_invoice.json -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1176 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1202 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json #: erpnext/selling/doctype/customer/customer.json #: erpnext/selling/page/point_of_sale/pos_item_cart.js:952 @@ -27912,7 +27923,7 @@ msgstr "" #: erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json #: erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js:81 #: erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1114 +#: erpnext/selling/doctype/sales_order/sales_order.js:1121 #: erpnext/support/workspace/support/support.json #: erpnext/workspace_sidebar/crm.json erpnext/workspace_sidebar/support.json msgid "Maintenance Schedule" @@ -28019,7 +28030,7 @@ msgstr "" #: erpnext/crm/workspace/crm/crm.json #: erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js:87 #: erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1107 +#: erpnext/selling/doctype/sales_order/sales_order.js:1114 #: erpnext/support/doctype/warranty_claim/warranty_claim.js:47 #: erpnext/support/workspace/support/support.json #: erpnext/workspace_sidebar/crm.json erpnext/workspace_sidebar/support.json @@ -28042,9 +28053,9 @@ 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:542 -#: erpnext/manufacturing/doctype/work_order/work_order.js:833 -#: erpnext/manufacturing/doctype/work_order/work_order.js:867 +#: erpnext/manufacturing/doctype/job_card/job_card.js:544 +#: erpnext/manufacturing/doctype/work_order/work_order.js:839 +#: erpnext/manufacturing/doctype/work_order/work_order.js:873 #: erpnext/setup/doctype/vehicle/vehicle.json msgid "Make" msgstr "" @@ -28107,7 +28118,7 @@ msgstr "" msgid "Make Stock Entry" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:416 +#: erpnext/manufacturing/doctype/job_card/job_card.js:418 msgid "Make Subcontracting PO" msgstr "" @@ -28171,7 +28182,7 @@ msgstr "" msgid "Mandatory Accounting Dimension" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1882 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1879 msgid "Mandatory Field" msgstr "" @@ -28191,11 +28202,11 @@ msgstr "" msgid "Mandatory Missing" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:637 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:634 msgid "Mandatory Purchase Order" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:658 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:655 msgid "Mandatory Purchase Receipt" msgstr "" @@ -28270,8 +28281,8 @@ msgstr "" #: erpnext/stock/doctype/material_request_item/material_request_item.json #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json #: erpnext/stock/doctype/stock_entry/stock_entry.json -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1232 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1248 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1324 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1340 #: erpnext/stock/doctype/stock_entry_type/stock_entry_type.json #: erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json #: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -28420,7 +28431,7 @@ msgstr "" msgid "Manufacturing Manager" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2390 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2589 msgid "Manufacturing Quantity is mandatory" msgstr "" @@ -28497,7 +28508,7 @@ msgstr "" msgid "Mapping Subcontracting Order ..." msgstr "" -#: erpnext/public/js/utils.js:1050 +#: erpnext/public/js/utils.js:1066 msgid "Mapping {0} ..." msgstr "" @@ -28643,7 +28654,7 @@ msgstr "" msgid "Material" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:858 +#: erpnext/manufacturing/doctype/work_order/work_order.js:864 msgid "Material Consumption" msgstr "" @@ -28651,12 +28662,12 @@ msgstr "" #. Option for the 'Purpose' (Select) field in DocType 'Stock Entry Type' #: erpnext/setup/setup_wizard/operations/install_fixtures.py:114 #: erpnext/stock/doctype/stock_entry/stock_entry.json -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1233 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1325 #: erpnext/stock/doctype/stock_entry_type/stock_entry_type.json msgid "Material Consumption for Manufacture" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.js:579 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:666 msgid "Material Consumption is not set in Manufacturing Settings." msgstr "" @@ -28738,7 +28749,7 @@ msgstr "" #: erpnext/manufacturing/doctype/production_plan_material_request/production_plan_material_request.json #: erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json #: erpnext/manufacturing/doctype/work_order/work_order.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1078 +#: erpnext/selling/doctype/sales_order/sales_order.js:1085 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py:36 #: erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -28748,8 +28759,8 @@ msgstr "" #: erpnext/stock/doctype/pick_list/pick_list.json #: erpnext/stock/doctype/pick_list_item/pick_list_item.json #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json -#: erpnext/stock/doctype/stock_entry/stock_entry.js:253 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:356 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:287 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:443 #: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json #: erpnext/stock/workspace/stock/stock.json #: erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json @@ -28850,7 +28861,7 @@ msgstr "" msgid "Material Request {0} is cancelled or stopped" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1452 +#: erpnext/selling/doctype/sales_order/sales_order.js:1459 msgid "Material Request {0} submitted." msgstr "" @@ -29039,8 +29050,11 @@ msgstr "" msgid "Max discount allowed for item: {0} is {1}%" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1010 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1040 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1047 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1070 #: erpnext/stock/doctype/pick_list/pick_list.js:200 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:382 msgid "Max: {0}" msgstr "" @@ -29066,11 +29080,11 @@ msgstr "" msgid "Maximum Producible Items" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3902 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:4104 msgid "Maximum Samples - {0} can be retained for Batch {1} and Item {2}." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3893 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:4095 msgid "Maximum Samples - {0} have already been retained for Batch {1} and Item {2} in Batch {3}." msgstr "" @@ -29125,7 +29139,7 @@ msgstr "" msgid "Megawatt" msgstr "" -#: erpnext/stock/stock_ledger.py:2073 +#: erpnext/stock/stock_ledger.py:2059 msgid "Mention Valuation Rate in the Item master." msgstr "" @@ -29170,7 +29184,7 @@ msgstr "" msgid "Merge Similar Account Heads" msgstr "" -#: erpnext/public/js/utils.js:1082 +#: erpnext/public/js/utils.js:1098 msgid "Merge taxes from multiple documents" msgstr "" @@ -29498,9 +29512,9 @@ msgstr "" #: erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py:97 #: erpnext/accounts/doctype/pos_profile/pos_profile.py:200 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:596 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2422 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3030 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:593 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2419 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3027 #: erpnext/assets/doctype/asset_category/asset_category.py:116 msgid "Missing Account" msgstr "" @@ -29526,7 +29540,7 @@ msgstr "" msgid "Missing Finance Book" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1685 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1777 msgid "Missing Finished Good" msgstr "" @@ -29534,7 +29548,7 @@ msgstr "" msgid "Missing Formula" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:991 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1083 msgid "Missing Item" msgstr "" @@ -29554,7 +29568,7 @@ msgstr "" msgid "Missing Warehouse" msgstr "" -#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:154 +#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:156 msgid "Missing email template for dispatch. Please set one in Delivery Settings." msgstr "" @@ -29563,7 +29577,7 @@ msgid "Missing required filter: {0}" msgstr "" #: erpnext/manufacturing/doctype/bom/bom.py:1218 -#: erpnext/manufacturing/doctype/work_order/work_order.py:1476 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1477 msgid "Missing value" msgstr "" @@ -29802,7 +29816,7 @@ msgstr "" msgid "Multiple Loyalty Programs found for Customer {}. Please select manually." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1194 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1191 msgid "Multiple POS Opening Entry" msgstr "" @@ -29820,10 +29834,6 @@ msgstr "" msgid "Multiple Variants" msgstr "" -#: erpnext/stock/doctype/warehouse/warehouse.py:152 -msgid "Multiple Warehouse Accounts" -msgstr "" - #: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js:244 msgid "Multiple company fields available: {0}. Please select manually." msgstr "" @@ -29832,7 +29842,7 @@ msgstr "" msgid "Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1692 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1784 msgid "Multiple items cannot be marked as finished item" msgstr "" @@ -29841,7 +29851,7 @@ msgid "Music" msgstr "" #. Label of the must_be_whole_number (Check) field in DocType 'UOM' -#: erpnext/manufacturing/doctype/work_order/work_order.py:1423 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1424 #: erpnext/setup/doctype/uom/uom.json #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:267 #: erpnext/utilities/transaction_base.py:568 @@ -29875,7 +29885,7 @@ msgstr "" msgid "Name of Beneficiary" msgstr "" -#: erpnext/accounts/doctype/account/account_tree.js:126 +#: erpnext/accounts/doctype/account/account_tree.js:121 msgid "Name of new Account. Note: Please don't create accounts for Customers and Suppliers" msgstr "" @@ -29969,7 +29979,7 @@ msgstr "" msgid "Negative Quantity is not allowed" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1575 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1608 #: erpnext/stock/serial_batch_bundle.py:1528 msgid "Negative Stock Error" msgstr "" @@ -30284,7 +30294,7 @@ msgstr "" msgid "Net total calculation precision loss" msgstr "" -#: erpnext/accounts/doctype/account/account_tree.js:124 +#: erpnext/accounts/doctype/account/account_tree.js:119 msgid "New Account Name" msgstr "" @@ -30326,7 +30336,7 @@ msgstr "" msgid "New Batch Qty" msgstr "" -#: erpnext/accounts/doctype/account/account_tree.js:113 +#: erpnext/accounts/doctype/account/account_tree.js:108 #: erpnext/accounts/doctype/cost_center/cost_center_tree.js:18 #: erpnext/setup/doctype/company/company_tree.js:23 msgid "New Company" @@ -30503,7 +30513,7 @@ msgstr "" msgid "Next email will be sent on:" msgstr "" -#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:161 +#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:155 msgid "No Account Data row found" msgstr "" @@ -30520,7 +30530,7 @@ msgstr "" msgid "No Answer" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2527 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2524 msgid "No Customer found for Inter Company Transactions which represents company {0}" msgstr "" @@ -30529,7 +30539,7 @@ msgstr "" msgid "No Customers found with selected options." msgstr "" -#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:144 +#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:146 msgid "No Delivery Note selected for Customer {}" msgstr "" @@ -30553,11 +30563,11 @@ msgstr "" msgid "No Items selected for transfer." msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1226 +#: erpnext/selling/doctype/sales_order/sales_order.js:1233 msgid "No Items with Bill of Materials to Manufacture or all items already manufactured" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1372 +#: erpnext/selling/doctype/sales_order/sales_order.js:1379 msgid "No Items with Bill of Materials." msgstr "" @@ -30577,9 +30587,9 @@ msgstr "" msgid "No POS Profile found. Please create a New POS Profile first" msgstr "" -#: 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/accounts/doctype/journal_entry/journal_entry.py:1573 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1633 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1647 #: erpnext/stock/doctype/item/item.py:1388 msgid "No Permission" msgstr "" @@ -30593,11 +30603,6 @@ msgstr "" msgid "No Records for these settings." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:336 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1105 -msgid "No Remarks" -msgstr "" - #: erpnext/public/js/utils/unreconcile.js:147 msgid "No Selection" msgstr "" @@ -30614,7 +30619,7 @@ msgstr "" msgid "No Summary" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2511 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2508 msgid "No Supplier found for Inter Company Transactions which represents company {0}" msgstr "" @@ -30643,7 +30648,7 @@ msgstr "" msgid "No Work Orders were created" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:833 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:832 #: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:897 msgid "No accounting entries for the following warehouses" msgstr "" @@ -30729,7 +30734,7 @@ msgstr "" msgid "No more children on Right" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:600 +#: erpnext/selling/doctype/sales_order/sales_order.js:607 msgid "No of Deliveries" msgstr "" @@ -30805,7 +30810,7 @@ msgstr "" msgid "No open Material Requests found for the given criteria." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1188 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1185 msgid "No open POS Opening Entry found for POS Profile {0}." msgstr "" @@ -30894,7 +30899,7 @@ msgstr "" msgid "No values" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2575 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2572 msgid "No {0} found for Inter Company Transactions." msgstr "" @@ -31084,7 +31089,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:1027 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1030 msgid "Note: {0}" msgstr "" @@ -31141,7 +31146,7 @@ msgstr "" msgid "Notice (days)" msgstr "" -#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:45 +#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:47 msgid "Notify Customers via Email" msgstr "" @@ -31240,7 +31245,7 @@ msgstr "" msgid "Number of intervals for the interval field e.g if Interval is 'Days' and Billing Interval Count is 3, invoices will be generated every 3 days" msgstr "" -#: erpnext/accounts/doctype/account/account_tree.js:134 +#: erpnext/accounts/doctype/account/account_tree.js:129 msgid "Number of new Account, it will be included in the account name as a prefix" msgstr "" @@ -31446,7 +31451,7 @@ msgstr "" msgid "Once set, this invoice will be on hold till the set date" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:745 +#: erpnext/manufacturing/doctype/work_order/work_order.js:751 msgid "Once the Work Order is Closed. It can't be resumed." msgstr "" @@ -31542,7 +31547,7 @@ msgstr "" msgid "Only one operation can have 'Is Final Finished Good' checked when 'Track Semi Finished Goods' is enabled." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1247 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1339 msgid "Only one {0} entry can be created against the Work Order {1}" msgstr "" @@ -31786,7 +31791,7 @@ msgstr "" #. Name of a DocType #. Label of a Link in the Invoicing Workspace #. Label of a Link in the Home Workspace -#: erpnext/accounts/doctype/account/account_tree.js:206 +#: erpnext/accounts/doctype/account/account_tree.js:201 #: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json #: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/setup/workspace/home/home.json @@ -31807,8 +31812,8 @@ msgstr "" msgid "Opening Invoice Tool" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1647 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1991 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1653 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1988 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 "" @@ -31944,7 +31949,7 @@ msgstr "" msgid "Operation ID" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:333 +#: erpnext/manufacturing/doctype/work_order/work_order.js:336 msgid "Operation Id" msgstr "" @@ -31972,7 +31977,7 @@ msgstr "" msgid "Operation Time" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:1482 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1483 msgid "Operation Time must be greater than 0 for Operation {0}" msgstr "" @@ -31987,7 +31992,7 @@ msgstr "" msgid "Operation time does not depend on quantity to produce" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:584 +#: erpnext/manufacturing/doctype/job_card/job_card.js:586 msgid "Operation {0} added multiple times in the work order {1}" msgstr "" @@ -32007,7 +32012,7 @@ msgstr "" #. Label of the operations (Table) field in DocType 'Work Order' #. Label of the operation (Section Break) field in DocType 'Email Digest' #: erpnext/manufacturing/doctype/bom/bom.json -#: erpnext/manufacturing/doctype/work_order/work_order.js:314 +#: erpnext/manufacturing/doctype/work_order/work_order.js:317 #: erpnext/manufacturing/doctype/work_order/work_order.json #: erpnext/setup/doctype/company/company.py:469 #: erpnext/setup/doctype/email_digest/email_digest.json @@ -32079,7 +32084,7 @@ msgstr "" #: erpnext/crm/doctype/prospect/prospect.js:20 #: erpnext/crm/doctype/prospect_opportunity/prospect_opportunity.json #: erpnext/crm/report/lead_details/lead_details.js:36 -#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:17 +#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:23 #: erpnext/public/js/communication.js:35 #: erpnext/selling/doctype/customer/customer.json #: erpnext/selling/doctype/quotation/quotation.js:155 @@ -32108,7 +32113,7 @@ msgstr "" #. Label of the opportunity_from (Link) field in DocType 'Opportunity' #: erpnext/crm/doctype/opportunity/opportunity.json #: erpnext/crm/report/lost_opportunity/lost_opportunity.js:42 -#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:24 +#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:30 msgid "Opportunity From" msgstr "" @@ -32161,7 +32166,7 @@ msgstr "" #. Name of a DocType #: erpnext/crm/doctype/opportunity/opportunity.json #: erpnext/crm/doctype/opportunity_type/opportunity_type.json -#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:44 +#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:50 #: erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js:52 #: erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py:48 #: erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js:64 @@ -32182,15 +32187,19 @@ msgstr "" msgid "Optimize Route" msgstr "" -#: erpnext/accounts/doctype/account/account_tree.js:183 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1017 +msgid "Optional. Select a specific manufacture entry to reverse." +msgstr "" + +#: erpnext/accounts/doctype/account/account_tree.js:178 msgid "Optional. Sets company's default currency, if not specified." msgstr "" -#: erpnext/accounts/doctype/account/account_tree.js:162 +#: erpnext/accounts/doctype/account/account_tree.js:157 msgid "Optional. This setting will be used to filter in various transactions." msgstr "" -#: erpnext/accounts/doctype/account/account_tree.js:170 +#: erpnext/accounts/doctype/account/account_tree.js:165 msgid "Optional. Used with Financial Report Template" msgstr "" @@ -32480,7 +32489,7 @@ msgstr "" msgid "Out of stock" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1201 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1198 #: erpnext/selling/page/point_of_sale/pos_controller.js:208 msgid "Outdated POS Opening Entry" msgstr "" @@ -32586,7 +32595,7 @@ msgstr "" msgid "Over Billing Allowance (%)" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1321 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:1344 msgid "Over Billing Allowance exceeded for Purchase Receipt Item {0} ({1}) by {2}%" msgstr "" @@ -32914,7 +32923,7 @@ msgstr "" msgid "POS Opening Entry" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1202 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1199 msgid "POS Opening Entry - {0} is outdated. Please close the POS and create a new POS Opening Entry." msgstr "" @@ -32935,7 +32944,7 @@ msgstr "" msgid "POS Opening Entry Exists" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1187 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1184 msgid "POS Opening Entry Missing" msgstr "" @@ -32971,7 +32980,7 @@ msgstr "" msgid "POS Profile" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1195 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1192 msgid "POS Profile - {0} has multiple open POS Opening Entries. Please close or cancel the existing entries before proceeding." msgstr "" @@ -32989,11 +32998,11 @@ msgstr "" msgid "POS Profile doesn't match {}" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1155 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1152 msgid "POS Profile is mandatory to mark this invoice as POS Transaction." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1384 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1381 msgid "POS Profile required to make POS Entry" msgstr "" @@ -33228,7 +33237,7 @@ msgid "Paid To Account Type" msgstr "" #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:326 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1151 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1148 msgid "Paid amount + Write Off Amount can not be greater than Grand Total" msgstr "" @@ -33384,7 +33393,7 @@ msgstr "" msgid "Parent Row No" msgstr "" -#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:533 +#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:535 msgid "Parent Row No not found for {0}" msgstr "" @@ -33440,7 +33449,7 @@ msgstr "" msgid "Partial Material Transferred" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1174 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1171 msgid "Partial Payment in POS Transactions are not allowed." msgstr "" @@ -33656,7 +33665,7 @@ msgstr "" #: erpnext/crm/doctype/appointment/appointment.json #: erpnext/crm/doctype/opportunity/opportunity.json #: erpnext/crm/report/lost_opportunity/lost_opportunity.js:55 -#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:31 +#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:37 #: erpnext/public/js/bank_reconciliation_tool/data_table_manager.js:50 #: erpnext/public/js/bank_reconciliation_tool/dialog_manager.js:135 #: erpnext/selling/doctype/quotation/quotation.json @@ -34001,7 +34010,7 @@ msgstr "" #: erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js:42 #: erpnext/buying/doctype/purchase_order/purchase_order.js:421 #: erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py:24 -#: erpnext/selling/doctype/sales_order/sales_order.js:1159 +#: erpnext/selling/doctype/sales_order/sales_order.js:1166 #: erpnext/selling/doctype/sales_order/sales_order_dashboard.py:31 msgid "Payment" msgstr "" @@ -34163,7 +34172,7 @@ msgstr "" msgid "Payment Gateway Account" msgstr "" -#: erpnext/accounts/utils.py:1437 +#: erpnext/accounts/utils.py:1509 msgid "Payment Gateway Account not created, please create one manually." msgstr "" @@ -34353,7 +34362,7 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js:135 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:139 #: erpnext/buying/doctype/purchase_order/purchase_order.js:429 -#: erpnext/selling/doctype/sales_order/sales_order.js:1152 +#: erpnext/selling/doctype/sales_order/sales_order.js:1159 #: erpnext/workspace_sidebar/invoicing.json #: erpnext/workspace_sidebar/payments.json msgid "Payment Request" @@ -34554,7 +34563,7 @@ msgstr "" msgid "Payment Unlink Error" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:895 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:898 msgid "Payment against {0} {1} cannot be greater than Outstanding Amount {2}" msgstr "" @@ -34566,7 +34575,7 @@ msgstr "" msgid "Payment methods are mandatory. Please add at least one payment method." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3034 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3031 msgid "Payment methods refreshed. Please review before proceeding." msgstr "" @@ -34692,9 +34701,9 @@ msgstr "" #. Label of the pending_qty (Float) field in DocType 'Production Plan Item' #: erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py:254 #: erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json -#: erpnext/manufacturing/doctype/work_order/work_order.js:338 +#: erpnext/manufacturing/doctype/work_order/work_order.js:341 #: erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py:182 -#: erpnext/selling/doctype/sales_order/sales_order.js:1645 +#: erpnext/selling/doctype/sales_order/sales_order.js:1652 #: erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py:45 msgid "Pending Qty" msgstr "" @@ -34729,7 +34738,7 @@ msgstr "" msgid "Pending activities for today" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:254 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:275 msgid "Pending processing" msgstr "" @@ -35056,7 +35065,7 @@ msgstr "" #. Reservation Entry' #. Label of a Link in the Stock Workspace #. Label of a Workspace Sidebar Item -#: erpnext/selling/doctype/sales_order/sales_order.js:1022 +#: erpnext/selling/doctype/sales_order/sales_order.js:1029 #: erpnext/stock/doctype/delivery_note/delivery_note.js:199 #: erpnext/stock/doctype/material_request/material_request.js:156 #: erpnext/stock/doctype/pick_list/pick_list.json @@ -35398,6 +35407,7 @@ msgstr "" msgid "Please Select a Company." msgstr "" +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:418 #: erpnext/stock/doctype/delivery_note/delivery_note.js:162 #: erpnext/stock/doctype/delivery_note/delivery_note.js:204 msgid "Please Select a Customer" @@ -35453,7 +35463,7 @@ msgstr "" msgid "Please add the Bank Account column" msgstr "" -#: erpnext/accounts/doctype/account/account_tree.js:244 +#: erpnext/accounts/doctype/account/account_tree.js:239 msgid "Please add the account to root level Company - {0}" msgstr "" @@ -35473,7 +35483,7 @@ msgstr "" msgid "Please attach CSV file" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3173 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3170 msgid "Please cancel and amend the Payment Entry" msgstr "" @@ -35491,7 +35501,7 @@ msgstr "" msgid "Please capitalize this asset before submitting." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:969 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:972 msgid "Please check Multi Currency option to allow accounts with other currency" msgstr "" @@ -35507,7 +35517,7 @@ msgstr "" msgid "Please check the 'Enable Serial and Batch No for Item' checkbox in the {0} to make Serial and Batch Bundle for the item." msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:541 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:562 msgid "Please check the error message and take necessary actions to fix the error and then restart the reposting again." msgstr "" @@ -35608,11 +35618,11 @@ msgstr "" msgid "Please enable {} in {} to allow same item in multiple rows" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:376 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:373 msgid "Please ensure that the {0} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:384 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:381 msgid "Please ensure that the {0} account {1} is a Payable account. You can change the account type to Payable or select a different account." msgstr "" @@ -35624,12 +35634,12 @@ msgstr "" msgid "Please ensure {} account {} is a Receivable account." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:699 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:761 msgid "Please enter Difference Account or set default Stock Adjustment Account for company {0}" msgstr "" #: erpnext/accounts/doctype/pos_invoice/pos_invoice.py:554 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1286 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1283 msgid "Please enter Account for Change Amount" msgstr "" @@ -35658,7 +35668,7 @@ msgid "Please enter Expense Account" msgstr "" #: erpnext/assets/doctype/asset_capitalization/asset_capitalization.js:84 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:87 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:97 msgid "Please enter Item Code to get Batch Number" msgstr "" @@ -35690,7 +35700,7 @@ msgstr "" msgid "Please enter Receipt Document" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1033 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1036 msgid "Please enter Reference date" msgstr "" @@ -35714,20 +35724,20 @@ msgstr "" msgid "Please enter Warehouse and Date" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:662 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1282 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:659 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1279 msgid "Please enter Write Off Account" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:717 +#: erpnext/selling/doctype/sales_order/sales_order.js:724 msgid "Please enter a valid number of deliveries" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:658 +#: erpnext/selling/doctype/sales_order/sales_order.js:665 msgid "Please enter a valid quantity" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:652 +#: erpnext/selling/doctype/sales_order/sales_order.js:659 msgid "Please enter at least one delivery date and quantity" msgstr "" @@ -35767,7 +35777,7 @@ msgstr "" msgid "Please enter the company name to confirm" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:713 +#: erpnext/selling/doctype/sales_order/sales_order.js:720 msgid "Please enter the first delivery date" msgstr "" @@ -35877,7 +35887,7 @@ msgstr "" msgid "Please save first" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:859 +#: erpnext/selling/doctype/sales_order/sales_order.js:866 msgid "Please save the Sales Order before adding a delivery schedule." msgstr "" @@ -35885,7 +35895,7 @@ msgstr "" msgid "Please select Template Type to download template" msgstr "" -#: erpnext/controllers/taxes_and_totals.py:846 +#: erpnext/controllers/taxes_and_totals.py:850 #: erpnext/public/js/controllers/taxes_and_totals.js:796 msgid "Please select Apply Discount On" msgstr "" @@ -36002,7 +36012,7 @@ msgstr "" msgid "Please select Stock Asset Account" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1611 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1703 msgid "Please select Subcontracting Order instead of Purchase Order {0}" msgstr "" @@ -36075,7 +36085,7 @@ msgstr "" msgid "Please select a field to edit from numpad" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:709 +#: erpnext/selling/doctype/sales_order/sales_order.js:716 msgid "Please select a frequency for delivery schedule" msgstr "" @@ -36120,15 +36130,15 @@ msgstr "" msgid "Please select at least one schedule." msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1296 +#: erpnext/selling/doctype/sales_order/sales_order.js:1303 msgid "Please select atleast one item to continue" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:381 +#: erpnext/manufacturing/doctype/work_order/work_order.js:384 msgid "Please select atleast one operation to create Job Card" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1709 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1712 msgid "Please select correct account" msgstr "" @@ -36146,13 +36156,13 @@ msgid "Please select item code" msgstr "" #: erpnext/public/js/stock_reservation.js:212 -#: erpnext/selling/doctype/sales_order/sales_order.js:419 +#: erpnext/selling/doctype/sales_order/sales_order.js:426 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:301 msgid "Please select items to reserve." msgstr "" #: erpnext/public/js/stock_reservation.js:290 -#: erpnext/selling/doctype/sales_order/sales_order.js:523 +#: erpnext/selling/doctype/sales_order/sales_order.js:530 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:399 msgid "Please select items to unreserve." msgstr "" @@ -36226,7 +36236,7 @@ msgstr "" msgid "Please set Account" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1882 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1879 msgid "Please set Account for Change Amount" msgstr "" @@ -36248,7 +36258,7 @@ msgstr "" #: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js:58 #: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js:68 #: erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js:78 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:789 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:880 msgid "Please set Company" msgstr "" @@ -36278,11 +36288,11 @@ msgstr "" msgid "Please set Fixed Asset Account in Asset Category {0}" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:593 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:590 msgid "Please set Fixed Asset Account in {} against {}." msgstr "" -#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:279 +#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:281 msgid "Please set Parent Row No for item {0}" msgstr "" @@ -36328,7 +36338,7 @@ msgstr "" msgid "Please set a default Holiday List for Employee {0} or Company {1}" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1116 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1117 msgid "Please set account in Warehouse {0}" msgstr "" @@ -36357,23 +36367,23 @@ msgstr "" msgid "Please set both the Tax ID and Fiscal Code on Company {0}" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2419 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2416 msgid "Please set default Cash or Bank account in Mode of Payment {0}" msgstr "" #: erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py:94 #: erpnext/accounts/doctype/pos_profile/pos_profile.py:197 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3027 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3024 msgid "Please set default Cash or Bank account in Mode of Payment {}" msgstr "" #: erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py:96 #: erpnext/accounts/doctype/pos_profile/pos_profile.py:199 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3029 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3026 msgid "Please set default Cash or Bank account in Mode of Payments {}" msgstr "" -#: erpnext/accounts/utils.py:2451 +#: erpnext/accounts/utils.py:2527 msgid "Please set default Exchange Gain/Loss Account in Company {}" msgstr "" @@ -36422,7 +36432,7 @@ msgstr "" msgid "Please set the Default Cost Center in {0} company." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:662 +#: erpnext/manufacturing/doctype/work_order/work_order.js:668 msgid "Please set the Item Code first" msgstr "" @@ -36461,7 +36471,7 @@ msgstr "" msgid "Please set {0} for address {1}" msgstr "" -#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:232 +#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:234 msgid "Please set {0} in BOM Creator {1}" msgstr "" @@ -36487,7 +36497,7 @@ msgstr "" #: erpnext/accounts/doctype/pos_invoice/pos_invoice.js:120 #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js:419 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:526 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:613 msgid "Please specify Company to proceed" msgstr "" @@ -36512,7 +36522,7 @@ msgstr "" msgid "Please specify from/to range" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:253 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:274 msgid "Please try again in an hour." msgstr "" @@ -36736,7 +36746,7 @@ msgstr "" msgid "Posting Date Inheritance for Exchange Gain / Loss" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:270 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:269 #: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:142 msgid "Posting Date cannot be future date" msgstr "" @@ -36804,10 +36814,14 @@ msgstr "" msgid "Posting Time" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2338 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2537 msgid "Posting date and posting time is mandatory" msgstr "" +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:99 +msgid "Posting date is required" +msgstr "" + #: erpnext/controllers/sales_and_purchase_return.py:66 msgid "Posting timestamp must be after {0}" msgstr "" @@ -37613,7 +37627,7 @@ msgstr "" msgid "Process Loss Qty" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:338 +#: erpnext/manufacturing/doctype/job_card/job_card.js:340 msgid "Process Loss Quantity" msgstr "" @@ -38586,7 +38600,7 @@ msgstr "" #: erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js:30 #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json #: erpnext/stock/doctype/quality_inspection/quality_inspection.json -#: erpnext/stock/doctype/stock_entry/stock_entry.js:337 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:424 #: erpnext/workspace_sidebar/buying.json #: erpnext/workspace_sidebar/invoicing.json msgid "Purchase Invoice" @@ -38625,12 +38639,12 @@ msgstr "" msgid "Purchase Invoice cannot be made against an existing asset {0}" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:453 -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:467 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:452 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:466 msgid "Purchase Invoice {0} is already submitted" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1932 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1938 msgid "Purchase Invoices" msgstr "" @@ -38673,8 +38687,8 @@ 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:181 -#: erpnext/selling/doctype/sales_order/sales_order.js:1097 +#: erpnext/selling/doctype/sales_order/sales_order.js:188 +#: erpnext/selling/doctype/sales_order/sales_order.js:1104 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/setup/doctype/authorization_rule/authorization_rule.json #: erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -38757,11 +38771,11 @@ msgstr "" msgid "Purchase Order Pricing Rule" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:633 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:630 msgid "Purchase Order Required" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:628 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:625 msgid "Purchase Order Required for item {}" msgstr "" @@ -38775,11 +38789,11 @@ msgstr "" msgid "Purchase Order Trends" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1589 +#: erpnext/selling/doctype/sales_order/sales_order.js:1596 msgid "Purchase Order already created for all Sales Order items" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:339 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:338 msgid "Purchase Order number required for Item {0}" msgstr "" @@ -38787,7 +38801,7 @@ msgstr "" msgid "Purchase Order {0} created" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:671 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:668 msgid "Purchase Order {0} is not submitted" msgstr "" @@ -38904,11 +38918,11 @@ msgstr "" msgid "Purchase Receipt No" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:654 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:651 msgid "Purchase Receipt Required" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:649 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:646 msgid "Purchase Receipt Required for item {}" msgstr "" @@ -38936,7 +38950,7 @@ msgstr "" msgid "Purchase Receipt {0} created." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:678 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:675 msgid "Purchase Receipt {0} is not submitted" msgstr "" @@ -39054,14 +39068,14 @@ msgstr "" #: erpnext/manufacturing/doctype/master_production_schedule/master_production_schedule.js:163 #: erpnext/stock/doctype/material_request/material_request.json #: erpnext/stock/doctype/pick_list/pick_list.json -#: erpnext/stock/doctype/stock_entry/stock_entry.js:372 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:459 #: erpnext/stock/doctype/stock_entry/stock_entry.json #: erpnext/stock/doctype/stock_entry_type/stock_entry_type.json #: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json msgid "Purpose" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:516 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:578 msgid "Purpose must be one of {0}" msgstr "" @@ -39126,7 +39140,7 @@ msgstr "" #: erpnext/controllers/trends.py:276 erpnext/controllers/trends.py:288 #: erpnext/controllers/trends.py:293 #: erpnext/crm/doctype/opportunity_item/opportunity_item.json -#: erpnext/manufacturing/doctype/bom/bom.js:1103 +#: erpnext/manufacturing/doctype/bom/bom.js:1105 #: erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json #: erpnext/manufacturing/doctype/bom_item/bom_item.json #: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json @@ -39142,15 +39156,15 @@ 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:853 +#: erpnext/public/js/stock_reservation.js:336 erpnext/public/js/utils.js:869 #: 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 -#: erpnext/selling/doctype/sales_order/sales_order.js:494 -#: erpnext/selling/doctype/sales_order/sales_order.js:584 -#: erpnext/selling/doctype/sales_order/sales_order.js:631 -#: erpnext/selling/doctype/sales_order/sales_order.js:1272 -#: erpnext/selling/doctype/sales_order/sales_order.js:1425 +#: erpnext/selling/doctype/sales_order/sales_order.js:397 +#: erpnext/selling/doctype/sales_order/sales_order.js:501 +#: erpnext/selling/doctype/sales_order/sales_order.js:591 +#: erpnext/selling/doctype/sales_order/sales_order.js:638 +#: erpnext/selling/doctype/sales_order/sales_order.js:1279 +#: erpnext/selling/doctype/sales_order/sales_order.js:1432 #: erpnext/selling/report/sales_order_analysis/sales_order_analysis.py:255 #: erpnext/stock/doctype/landed_cost_item/landed_cost_item.json #: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json @@ -39260,7 +39274,7 @@ msgstr "" msgid "Qty To Manufacture" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:1419 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1420 msgid "Qty To Manufacture ({0}) cannot be a fraction for the UOM {2}. To allow this, disable '{1}' in the UOM {2}." msgstr "" @@ -39315,7 +39329,8 @@ msgstr "" msgid "Qty for which recursion isn't applicable." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1008 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1045 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1068 msgid "Qty for {0}" msgstr "" @@ -39362,11 +39377,15 @@ msgstr "" msgid "Qty to Deliver" msgstr "" +#: erpnext/stock/doctype/stock_entry/stock_entry.js:379 +msgid "Qty to Disassemble" +msgstr "" + #: erpnext/public/js/utils/serial_no_batch_selector.js:373 msgid "Qty to Fetch" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:310 +#: erpnext/manufacturing/doctype/job_card/job_card.js:312 #: erpnext/manufacturing/doctype/job_card/job_card.py:889 msgid "Qty to Manufacture" msgstr "" @@ -39553,7 +39572,13 @@ msgid "Quality Inspection Reading" msgstr "" #. Label of the inspection_required (Check) field in DocType 'BOM' +#. Label of the quality_inspection_required (Check) field in DocType 'BOM +#. Operation' +#. Label of the quality_inspection_required (Check) field in DocType 'Work +#. Order Operation' #: erpnext/manufacturing/doctype/bom/bom.json +#: erpnext/manufacturing/doctype/bom_operation/bom_operation.json +#: erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json msgid "Quality Inspection Required" msgstr "" @@ -39613,7 +39638,7 @@ msgid "Quality Inspection {0} is rejected for the item: {1}" msgstr "" #: erpnext/public/js/controllers/transaction.js:384 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:196 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:206 msgid "Quality Inspection(s)" msgstr "" @@ -39766,7 +39791,7 @@ msgstr "" #: erpnext/stock/doctype/material_request_item/material_request_item.json #: erpnext/stock/doctype/packing_slip_item/packing_slip_item.json #: erpnext/stock/doctype/pick_list_item/pick_list_item.json -#: erpnext/stock/doctype/stock_entry/stock_entry.js:728 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:815 #: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json #: erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json #: erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py:36 @@ -39881,7 +39906,7 @@ msgstr "" msgid "Quantity must be less than or equal to {0}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1038 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1098 #: erpnext/stock/doctype/pick_list/pick_list.js:206 msgid "Quantity must not be more than {0}" msgstr "" @@ -39896,21 +39921,21 @@ msgid "Quantity required for Item {0} in row {1}" msgstr "" #: erpnext/manufacturing/doctype/bom/bom.py:716 -#: erpnext/manufacturing/doctype/job_card/job_card.js:391 -#: erpnext/manufacturing/doctype/job_card/job_card.js:461 +#: erpnext/manufacturing/doctype/job_card/job_card.js:393 +#: erpnext/manufacturing/doctype/job_card/job_card.js:463 #: erpnext/manufacturing/doctype/workstation/workstation.js:303 msgid "Quantity should be greater than 0" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:343 +#: erpnext/manufacturing/doctype/work_order/work_order.js:346 msgid "Quantity to Manufacture" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2593 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2617 msgid "Quantity to Manufacture can not be zero for the operation {0}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:1411 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1412 msgid "Quantity to Manufacture must be greater than 0." msgstr "" @@ -39989,7 +40014,7 @@ msgstr "" #. Label of a Link in the Selling Workspace #. Option for the 'Transaction' (Select) field in DocType 'Authorization Rule' #. Label of a Workspace Sidebar Item -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:300 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:381 #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.js:51 #: erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html:20 #: erpnext/crm/doctype/contract/contract.json @@ -40000,7 +40025,7 @@ msgstr "" #: erpnext/crm/report/lead_details/lead_details.js:37 #: erpnext/manufacturing/doctype/blanket_order/blanket_order.js:38 #: erpnext/selling/doctype/quotation/quotation.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1175 +#: erpnext/selling/doctype/sales_order/sales_order.js:1182 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/selling/workspace/selling/selling.json #: erpnext/setup/doctype/authorization_rule/authorization_rule.json @@ -40183,7 +40208,7 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_item/bom_item.json #: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json #: erpnext/manufacturing/doctype/work_order_item/work_order_item.json -#: erpnext/public/js/utils.js:863 +#: erpnext/public/js/utils.js:879 #: 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 @@ -40506,7 +40531,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:446 -#: erpnext/manufacturing/doctype/bom/bom.js:1076 +#: erpnext/manufacturing/doctype/bom/bom.js:1078 #: erpnext/manufacturing/doctype/bom/bom.json #: erpnext/manufacturing/doctype/job_card/job_card.json #: erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -40536,7 +40561,7 @@ msgstr "" msgid "Raw Materials Consumption" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:319 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:327 msgid "Raw Materials Missing" msgstr "" @@ -40592,8 +40617,8 @@ 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:761 -#: erpnext/selling/doctype/sales_order/sales_order.js:968 +#: erpnext/manufacturing/doctype/work_order/work_order.js:767 +#: erpnext/selling/doctype/sales_order/sales_order.js:975 #: erpnext/selling/doctype/sales_order/sales_order_list.js:70 #: erpnext/stock/doctype/material_request/material_request.js:243 #: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.js:116 @@ -40692,7 +40717,7 @@ msgid "Reason for Failure" msgstr "" #: erpnext/buying/doctype/purchase_order/purchase_order.js:679 -#: erpnext/selling/doctype/sales_order/sales_order.js:1760 +#: erpnext/selling/doctype/sales_order/sales_order.js:1767 msgid "Reason for Hold" msgstr "" @@ -40701,7 +40726,7 @@ msgstr "" msgid "Reason for Leaving" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1775 +#: erpnext/selling/doctype/sales_order/sales_order.js:1782 msgid "Reason for hold:" msgstr "" @@ -40895,7 +40920,7 @@ msgstr "" msgid "Received Quantity" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.js:321 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:355 msgid "Received Stock Entries" msgstr "" @@ -41135,7 +41160,7 @@ msgstr "" msgid "Ref Date" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1031 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1034 msgid "Reference #{0} dated {1}" msgstr "" @@ -41173,7 +41198,7 @@ msgstr "" msgid "Reference No" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:645 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:648 msgid "Reference No & Reference Date is required for {0}" msgstr "" @@ -41181,7 +41206,7 @@ msgstr "" msgid "Reference No and Reference Date is mandatory for Bank transaction" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:650 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:653 msgid "Reference No is mandatory if you entered Reference Date" msgstr "" @@ -41330,7 +41355,7 @@ msgstr "" msgid "Regular" msgstr "" -#: erpnext/stock/doctype/inventory_dimension/inventory_dimension.py:199 +#: erpnext/stock/doctype/inventory_dimension/inventory_dimension.py:202 msgid "Rejected " msgstr "" @@ -41758,7 +41783,7 @@ msgstr "" msgid "Repost Item Valuation" msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:346 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:367 msgid "Repost Item Valuation restarted for selected failed records." msgstr "" @@ -41888,7 +41913,7 @@ msgstr "" msgid "Reqd Qty (BOM)" msgstr "" -#: erpnext/public/js/utils.js:879 +#: erpnext/public/js/utils.js:895 msgid "Reqd by date" msgstr "" @@ -41955,7 +41980,7 @@ msgstr "" msgid "Request for Quotation Supplier" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1084 +#: erpnext/selling/doctype/sales_order/sales_order.js:1091 msgid "Request for Raw Materials" msgstr "" @@ -42160,8 +42185,8 @@ msgstr "" msgid "Reservation Based On" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:919 -#: erpnext/selling/doctype/sales_order/sales_order.js:99 +#: erpnext/manufacturing/doctype/work_order/work_order.js:925 +#: erpnext/selling/doctype/sales_order/sales_order.js:106 #: erpnext/stock/doctype/pick_list/pick_list.js:150 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:180 msgid "Reserve" @@ -42175,7 +42200,7 @@ msgstr "" #: erpnext/manufacturing/doctype/production_plan/production_plan.json #: erpnext/manufacturing/doctype/work_order/work_order.json #: erpnext/public/js/stock_reservation.js:15 -#: erpnext/selling/doctype/sales_order/sales_order.js:397 +#: erpnext/selling/doctype/sales_order/sales_order.js:404 #: erpnext/selling/doctype/sales_order/sales_order.json #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:278 @@ -42275,29 +42300,29 @@ msgstr "" msgid "Reserved Quantity for Production" msgstr "" -#: erpnext/stock/stock_ledger.py:2341 +#: erpnext/stock/stock_ledger.py:2327 msgid "Reserved Serial No." 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:935 +#: erpnext/manufacturing/doctype/work_order/work_order.js:941 #: erpnext/public/js/stock_reservation.js:236 -#: erpnext/selling/doctype/sales_order/sales_order.js:127 -#: erpnext/selling/doctype/sales_order/sales_order.js:457 +#: erpnext/selling/doctype/sales_order/sales_order.js:134 +#: erpnext/selling/doctype/sales_order/sales_order.js:464 #: erpnext/stock/dashboard/item_dashboard_list.html:15 #: erpnext/stock/doctype/bin/bin.json #: erpnext/stock/doctype/pick_list/pick_list.js:170 #: erpnext/stock/report/reserved_stock/reserved_stock.json #: erpnext/stock/report/stock_balance/stock_balance.py:572 -#: erpnext/stock/stock_ledger.py:2325 +#: erpnext/stock/stock_ledger.py:2311 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:205 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:333 msgid "Reserved Stock" msgstr "" -#: erpnext/stock/stock_ledger.py:2370 +#: erpnext/stock/stock_ledger.py:2356 msgid "Reserved Stock for Batch" msgstr "" @@ -42342,7 +42367,7 @@ msgid "Reserved for sub contracting" msgstr "" #: erpnext/public/js/stock_reservation.js:203 -#: erpnext/selling/doctype/sales_order/sales_order.js:410 +#: erpnext/selling/doctype/sales_order/sales_order.js:417 #: erpnext/stock/doctype/pick_list/pick_list.js:295 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:293 msgid "Reserving Stock..." @@ -42559,7 +42584,7 @@ msgstr "" #: erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js:43 #: erpnext/buying/doctype/purchase_order/purchase_order.js:344 #: erpnext/manufacturing/doctype/workstation/workstation_job_card.html:63 -#: erpnext/selling/doctype/sales_order/sales_order.js:954 +#: erpnext/selling/doctype/sales_order/sales_order.js:961 msgid "Resume" msgstr "" @@ -42664,7 +42689,7 @@ msgstr "" msgid "Return Against Subcontracting Receipt" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:284 +#: erpnext/manufacturing/doctype/work_order/work_order.js:287 msgid "Return Components" msgstr "" @@ -42700,7 +42725,7 @@ msgstr "" msgid "Return Raw Material to Customer" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1525 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1522 msgid "Return invoice of asset cancelled" msgstr "" @@ -43009,7 +43034,7 @@ msgstr "" #. Label of the root_type (Select) field in DocType 'Account' #. Label of the root_type (Select) field in DocType 'Ledger Merge' #: erpnext/accounts/doctype/account/account.json -#: erpnext/accounts/doctype/account/account_tree.js:152 +#: erpnext/accounts/doctype/account/account_tree.js:147 #: erpnext/accounts/doctype/ledger_merge/ledger_merge.json #: erpnext/accounts/report/account_balance/account_balance.js:22 msgid "Root Type" @@ -43230,12 +43255,12 @@ msgid "Row #1: Sequence ID must be 1 for Operation {0}." msgstr "" #: erpnext/accounts/doctype/pos_invoice/pos_invoice.py:563 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2074 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2071 msgid "Row #{0} (Payment Table): Amount must be negative" msgstr "" #: erpnext/accounts/doctype/pos_invoice/pos_invoice.py:561 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2069 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2066 msgid "Row #{0} (Payment Table): Amount must be positive" msgstr "" @@ -43467,7 +43492,7 @@ msgstr "" msgid "Row #{0}: Finished Good Item {1} must be a sub-contracted item" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:472 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:534 msgid "Row #{0}: Finished Good must be {1}" msgstr "" @@ -43480,11 +43505,11 @@ msgstr "" msgid "Row #{0}: For Customer Provided Item {1}, Source Warehouse must be {2}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:693 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:696 msgid "Row #{0}: For {1}, you can select reference document only if account gets credited" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:703 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:706 msgid "Row #{0}: For {1}, you can select reference document only if account gets debited" msgstr "" @@ -43504,7 +43529,7 @@ msgstr "" msgid "Row #{0}: Item added" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1542 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1634 msgid "Row #{0}: Item {1} cannot be transferred more than {2} against {3} {4}" msgstr "" @@ -43581,7 +43606,7 @@ msgstr "" msgid "Row #{0}: Opening Accumulated Depreciation must be less than or equal to {1}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:868 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:960 msgid "Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Work Order {3}. Please update operation status via Job Card {4}." msgstr "" @@ -43767,11 +43792,11 @@ msgstr "" msgid "Row #{0}: Source Warehouse {1} for item {2} must be same as Source Warehouse {3} in the Work Order." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1015 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1107 msgid "Row #{0}: Source and Target Warehouse cannot be the same for Material Transfer" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1037 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1129 msgid "Row #{0}: Source, Target Warehouse and Inventory Dimensions cannot be the exact same for Material Transfer" msgstr "" @@ -43783,7 +43808,7 @@ msgstr "" msgid "Row #{0}: Status is mandatory" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:455 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:458 msgid "Row #{0}: Status must be {1} for Invoice Discounting {2}" msgstr "" @@ -43816,7 +43841,7 @@ msgstr "" msgid "Row #{0}: Stock not available to reserve for the Item {1} in Warehouse {2}." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1268 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1265 msgid "Row #{0}: Stock quantity {1} ({2}) for item {3} cannot exceed {4}" msgstr "" @@ -43985,7 +44010,7 @@ msgstr "" msgid "Row #{}: {} {} doesn't belong to Company {}. Please select valid {}." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:443 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:440 msgid "Row No {0}: Warehouse is required. Please set a Default Warehouse for Item {1} and Company {2}" msgstr "" @@ -43997,7 +44022,7 @@ msgstr "" msgid "Row {0} picked quantity is less than the required quantity, additional {1} {2} required." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1566 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1658 msgid "Row {0}# Item {1} not found in 'Raw Materials Supplied' table in {2} {3}" msgstr "" @@ -44005,7 +44030,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:608 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:611 msgid "Row {0}: Account {1} and Party Type {2} have different account types" msgstr "" @@ -44013,11 +44038,11 @@ msgstr "" msgid "Row {0}: Activity Type is mandatory." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:674 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:677 msgid "Row {0}: Advance against Customer must be credit" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:676 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:679 msgid "Row {0}: Advance against Supplier must be debit" msgstr "" @@ -44029,7 +44054,7 @@ msgstr "" msgid "Row {0}: Allocated amount {1} must be less than or equal to remaining payment amount {2}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1227 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1319 msgid "Row {0}: As {1} is enabled, raw materials cannot be added to {2} entry. Use {3} entry to consume raw materials." msgstr "" @@ -44037,7 +44062,7 @@ msgstr "" msgid "Row {0}: Bill of Materials not found for the Item {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:927 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:930 msgid "Row {0}: Both Debit and Credit values cannot be zero" msgstr "" @@ -44059,7 +44084,7 @@ msgstr "" msgid "Row {0}: Cost center is required for an item {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:773 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:776 msgid "Row {0}: Credit entry can not be linked with a {1}" msgstr "" @@ -44067,7 +44092,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:768 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:771 msgid "Row {0}: Debit entry can not be linked with a {1}" msgstr "" @@ -44087,8 +44112,8 @@ msgstr "" msgid "Row {0}: Either Delivery Note Item or Packed Item reference is mandatory." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1018 -#: erpnext/controllers/taxes_and_totals.py:1377 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1021 +#: erpnext/controllers/taxes_and_totals.py:1381 msgid "Row {0}: Exchange Rate is mandatory" msgstr "" @@ -44104,15 +44129,15 @@ msgstr "" msgid "Row {0}: Expense Account {1} is linked to company {2}. Please select an account belonging to company {3}." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:533 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:530 msgid "Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:490 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:487 msgid "Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:515 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:512 msgid "Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}" msgstr "" @@ -44141,7 +44166,7 @@ msgstr "" msgid "Row {0}: Hours value must be greater than zero." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:793 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:796 msgid "Row {0}: Invalid reference {1}" msgstr "" @@ -44181,11 +44206,11 @@ msgstr "" msgid "Row {0}: Packing Slip is already created for Item {1}." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:819 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:822 msgid "Row {0}: Party / Account does not match with {1} / {2} in {3} {4}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:597 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:600 msgid "Row {0}: Party Type and Party is required for Receivable / Payable account {1}" msgstr "" @@ -44193,11 +44218,11 @@ msgstr "" msgid "Row {0}: Payment Term is mandatory" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:667 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:670 msgid "Row {0}: Payment against Sales/Purchase Order should always be marked as advance" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:660 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:663 msgid "Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry." msgstr "" @@ -44241,7 +44266,7 @@ msgstr "" msgid "Row {0}: Qty cannot be greater than {1} for the Item {2}." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:563 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:625 msgid "Row {0}: Qty in Stock UOM can not be zero." msgstr "" @@ -44253,7 +44278,7 @@ msgstr "" msgid "Row {0}: Quantity cannot be negative." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:942 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1034 msgid "Row {0}: Quantity not available for {4} in warehouse {1} at posting time of the entry ({2} {3})" msgstr "" @@ -44265,7 +44290,7 @@ msgstr "" msgid "Row {0}: Shift cannot be changed since the depreciation has already been processed" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1579 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1671 msgid "Row {0}: Subcontracted Item is mandatory for the raw material {1}" msgstr "" @@ -44281,7 +44306,7 @@ msgstr "" msgid "Row {0}: The entire expense amount for account {1} in {2} has already been allocated." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:609 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:671 msgid "Row {0}: The item {1}, quantity must be positive number" msgstr "" @@ -44293,11 +44318,11 @@ msgstr "" msgid "Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3394 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3593 msgid "Row {0}: Transferred quantity cannot be greater than the requested quantity." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:557 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:619 msgid "Row {0}: UOM Conversion Factor is mandatory" msgstr "" @@ -44330,7 +44355,7 @@ msgstr "" msgid "Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:833 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:836 msgid "Row {0}: {1} {2} does not match with {3}" msgstr "" @@ -44455,7 +44480,7 @@ msgstr "" msgid "SLA Paused On" msgstr "" -#: erpnext/public/js/utils.js:1243 +#: erpnext/public/js/utils.js:1259 msgid "SLA is on hold since {0}" msgstr "" @@ -44662,7 +44687,7 @@ msgstr "" #: erpnext/projects/doctype/timesheet/timesheet.json #: erpnext/projects/doctype/timesheet_detail/timesheet_detail.json #: erpnext/selling/doctype/quotation/quotation_list.js:22 -#: erpnext/selling/doctype/sales_order/sales_order.js:1063 +#: erpnext/selling/doctype/sales_order/sales_order.js:1070 #: erpnext/selling/doctype/sales_order/sales_order_list.js:75 #: erpnext/selling/workspace/selling/selling.json #: erpnext/setup/doctype/authorization_rule/authorization_rule.json @@ -44810,7 +44835,7 @@ msgstr "" #. Label of a Workspace Sidebar Item #: erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json #: erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:278 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:359 #: erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json #: erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py:284 #: erpnext/accounts/report/sales_register/sales_register.py:238 @@ -44908,8 +44933,8 @@ msgstr "" #: erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json #: erpnext/manufacturing/doctype/work_order/work_order.json #: erpnext/selling/doctype/delivery_schedule_item/delivery_schedule_item.json -#: erpnext/selling/doctype/sales_order/sales_order.js:336 -#: erpnext/selling/doctype/sales_order/sales_order.js:1279 +#: erpnext/selling/doctype/sales_order/sales_order.js:343 +#: erpnext/selling/doctype/sales_order/sales_order.js:1286 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/stock/doctype/material_request_item/material_request_item.json #: erpnext/stock/doctype/pick_list_item/pick_list_item.json @@ -44960,7 +44985,7 @@ msgstr "" msgid "Sales Order {0} already exists against Customer's Purchase Order {1}. To allow multiple Sales Orders, Enable {2} in {3}" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1398 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1395 msgid "Sales Order {0} is not submitted" msgstr "" @@ -45236,7 +45261,7 @@ msgstr "" #. Label of a Workspace Sidebar Item #: erpnext/crm/doctype/opportunity/opportunity.json #: erpnext/crm/doctype/sales_stage/sales_stage.json -#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:51 +#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:57 #: erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py:70 #: erpnext/crm/workspace/crm/crm.json erpnext/workspace_sidebar/crm.json msgid "Sales Stage" @@ -45390,7 +45415,7 @@ msgid "Sample Quantity" msgstr "" #: erpnext/stock/doctype/purchase_receipt/purchase_receipt.js:269 -#: erpnext/stock/doctype/stock_entry/stock_entry.js:448 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:535 msgid "Sample Retention Stock Entry" msgstr "" @@ -45407,7 +45432,7 @@ msgstr "" msgid "Sample Size" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3884 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:4086 msgid "Sample quantity {0} cannot be more than received quantity {1}" msgstr "" @@ -45776,7 +45801,7 @@ msgstr "" msgid "Select Accounting Dimension." msgstr "" -#: erpnext/public/js/utils.js:539 +#: erpnext/public/js/utils.js:555 msgid "Select Alternate Item" msgstr "" @@ -45788,15 +45813,15 @@ msgstr "" msgid "Select Attribute Values" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1262 +#: erpnext/selling/doctype/sales_order/sales_order.js:1269 msgid "Select BOM" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1239 +#: erpnext/selling/doctype/sales_order/sales_order.js:1246 msgid "Select BOM and Qty for Production" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1395 +#: erpnext/selling/doctype/sales_order/sales_order.js:1402 msgid "Select BOM, Qty and For Warehouse" msgstr "" @@ -45830,7 +45855,7 @@ msgstr "" msgid "Select Company Address" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:539 +#: erpnext/manufacturing/doctype/job_card/job_card.js:541 msgid "Select Corrective Operation" msgstr "" @@ -45871,7 +45896,7 @@ msgid "Select Employees" msgstr "" #: erpnext/buying/doctype/purchase_order/purchase_order.js:198 -#: erpnext/selling/doctype/sales_order/sales_order.js:818 +#: erpnext/selling/doctype/sales_order/sales_order.js:825 msgid "Select Finished Good" msgstr "" @@ -45881,13 +45906,13 @@ msgstr "" #. Forecast' #: erpnext/manufacturing/doctype/master_production_schedule/master_production_schedule.json #: erpnext/manufacturing/doctype/sales_forecast/sales_forecast.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1596 -#: erpnext/selling/doctype/sales_order/sales_order.js:1624 +#: erpnext/selling/doctype/sales_order/sales_order.js:1603 +#: erpnext/selling/doctype/sales_order/sales_order.js:1631 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:493 msgid "Select Items" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1482 +#: erpnext/selling/doctype/sales_order/sales_order.js:1489 msgid "Select Items based on Delivery Date" msgstr "" @@ -45898,7 +45923,7 @@ msgstr "" #. Label of the select_items_to_manufacture_section (Section Break) field in #. DocType 'Production Plan' #: erpnext/manufacturing/doctype/production_plan/production_plan.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1291 +#: erpnext/selling/doctype/sales_order/sales_order.js:1298 msgid "Select Items to Manufacture" msgstr "" @@ -45916,7 +45941,7 @@ msgstr "" msgid "Select Job Worker Address" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1173 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1199 #: erpnext/selling/page/point_of_sale/pos_item_cart.js:955 msgid "Select Loyalty Program" msgstr "" @@ -45929,7 +45954,7 @@ msgstr "" msgid "Select Possible Supplier" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1044 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1104 #: erpnext/stock/doctype/pick_list/pick_list.js:216 msgid "Select Quantity" msgstr "" @@ -46063,11 +46088,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:1146 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1206 msgid "Select the Item to be manufactured." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:983 +#: erpnext/manufacturing/doctype/bom/bom.js:985 msgid "Select the Item to be manufactured. The Item name, UoM, Company, and Currency will be fetched automatically." msgstr "" @@ -46088,7 +46113,7 @@ msgstr "" msgid "Select the date and your timezone" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:1002 +#: erpnext/manufacturing/doctype/bom/bom.js:1004 msgid "Select the raw materials (Items) required to manufacture the Item" msgstr "" @@ -46116,7 +46141,7 @@ msgstr "" msgid "Selected POS Opening Entry should be open." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2570 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2567 msgid "Selected Price List should have buying and selling fields checked." msgstr "" @@ -46166,7 +46191,7 @@ msgstr "" msgid "Sell quantity cannot exceed the asset quantity" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1411 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1408 msgid "Sell quantity cannot exceed the asset quantity. Asset {0} has only {1} item(s)." msgstr "" @@ -46365,7 +46390,7 @@ msgstr "" msgid "Serial / Batch No" msgstr "" -#: erpnext/public/js/utils.js:201 +#: erpnext/public/js/utils.js:217 msgid "Serial / Batch Nos" msgstr "" @@ -46478,7 +46503,7 @@ msgstr "" msgid "Serial No Range" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2639 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2672 msgid "Serial No Reserved" msgstr "" @@ -46535,7 +46560,7 @@ msgstr "" msgid "Serial No and Batch Traceability" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1146 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1179 msgid "Serial No is mandatory" msgstr "" @@ -46564,7 +46589,7 @@ msgstr "" msgid "Serial No {0} does not exist" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3429 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:3462 msgid "Serial No {0} does not exists" msgstr "" @@ -46580,7 +46605,7 @@ msgstr "" msgid "Serial No {0} is already assigned to customer {1}. Can only be returned against the customer {1}" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:451 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:484 msgid "Serial No {0} is not present in the {1} {2}, hence you can't return it against the {1} {2}" msgstr "" @@ -46618,11 +46643,11 @@ msgstr "" msgid "Serial Nos and Batches" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1925 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1958 msgid "Serial Nos are created successfully" msgstr "" -#: erpnext/stock/stock_ledger.py:2331 +#: erpnext/stock/stock_ledger.py:2317 msgid "Serial Nos are reserved in Stock Reservation Entries, you need to unreserve them before proceeding." msgstr "" @@ -46699,11 +46724,11 @@ msgstr "" msgid "Serial and Batch Bundle" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2147 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2180 msgid "Serial and Batch Bundle created" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2219 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2252 msgid "Serial and Batch Bundle updated" msgstr "" @@ -47105,7 +47130,7 @@ msgstr "" #. Label of the set_basic_rate_manually (Check) field in DocType 'Stock Entry #. Detail' -#: erpnext/stock/doctype/stock_entry/stock_entry.py:299 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:307 #: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json msgid "Set Basic Rate Manually" msgstr "" @@ -47120,8 +47145,8 @@ msgstr "" msgid "Set Delivery Warehouse" msgstr "" -#: erpnext/manufacturing/doctype/job_card/job_card.js:410 -#: erpnext/manufacturing/doctype/job_card/job_card.js:479 +#: erpnext/manufacturing/doctype/job_card/job_card.js:412 +#: erpnext/manufacturing/doctype/job_card/job_card.js:481 msgid "Set Finished Good Quantity" msgstr "" @@ -47158,7 +47183,7 @@ msgstr "" msgid "Set Landed Cost Based on Purchase Invoice Rate" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1185 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1211 msgid "Set Loyalty Program" msgstr "" @@ -47187,7 +47212,7 @@ msgstr "" msgid "Set Posting Date" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:1029 +#: erpnext/manufacturing/doctype/bom/bom.js:1031 msgid "Set Process Loss Item Quantity" msgstr "" @@ -47230,7 +47255,7 @@ msgstr "" msgid "Set Source Warehouse" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1602 +#: erpnext/selling/doctype/sales_order/sales_order.js:1609 msgid "Set Supplier" msgstr "" @@ -47260,7 +47285,7 @@ msgstr "" msgid "Set Valuation Rate for Rejected Materials" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:256 +#: erpnext/selling/doctype/sales_order/sales_order.js:263 msgid "Set Warehouse" msgstr "" @@ -47310,7 +47335,7 @@ msgstr "" msgid "Set fieldname from which you want to fetch the data from the parent form." msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:1019 +#: erpnext/manufacturing/doctype/bom/bom.js:1021 msgid "Set quantity of process loss item:" msgstr "" @@ -47326,7 +47351,7 @@ msgstr "" msgid "Set targets Item Group-wise for this Sales Person." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1203 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1263 msgid "Set the Planned Start Date (an Estimated Date at which you want the Production to begin)" msgstr "" @@ -47422,7 +47447,7 @@ msgid "Setting up company" msgstr "" #: erpnext/manufacturing/doctype/bom/bom.py:1217 -#: erpnext/manufacturing/doctype/work_order/work_order.py:1475 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1476 msgid "Setting {0} is required" msgstr "" @@ -48137,7 +48162,7 @@ msgstr "" msgid "Simultaneous" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:687 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:749 msgid "Since there is a process loss of {0} units for the finished good {1}, you should reduce the quantity by {0} units for the finished good {1} in the Items Table." msgstr "" @@ -48145,10 +48170,14 @@ msgstr "" msgid "Since you have enabled 'Track Semi Finished Goods', at least one operation must have 'Is Final Finished Good' checked. For that set the FG / Semi FG Item as {0} against an operation." msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:111 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:132 msgid "Since {0} are Serial No/Batch No items, you cannot enable 'Recreate Stock Ledgers' in Repost Item Valuation." msgstr "" +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:112 +msgid "Since {0} has 'Update Stock' disabled, you cannot create repost item valuation against it" +msgstr "" + #. Option for the 'Marital Status' (Select) field in DocType 'Employee' #: erpnext/setup/doctype/employee/employee.json msgid "Single" @@ -48171,7 +48200,7 @@ msgstr "" #. Label of the skip_material_transfer (Check) field in DocType 'Work Order #. Operation' -#: erpnext/manufacturing/doctype/work_order/work_order.js:362 +#: erpnext/manufacturing/doctype/work_order/work_order.js:365 #: erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json #: erpnext/manufacturing/doctype/workstation/workstation.js:454 msgid "Skip Material Transfer" @@ -48302,6 +48331,23 @@ msgstr "" msgid "Source Location" msgstr "" +#: erpnext/manufacturing/doctype/work_order/work_order.js:1014 +msgid "Source Manufacture Entry" +msgstr "" + +#. Label of the source_stock_entry (Link) field in DocType 'Stock Entry' +#: erpnext/stock/doctype/stock_entry/stock_entry.json +msgid "Source Stock Entry (Manufacture)" +msgstr "" + +#: erpnext/stock/doctype/stock_entry/stock_entry.py:912 +msgid "Source Stock Entry {0} belongs to Work Order {1}, not {2}. Please use a manufacture entry from the same Work Order." +msgstr "" + +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2370 +msgid "Source Stock Entry {0} has no finished goods quantity" +msgstr "" + #. Label of the source_type (Select) field in DocType 'Support Search Source' #: erpnext/support/doctype/support_search_source/support_search_source.json msgid "Source Type" @@ -48336,7 +48382,7 @@ msgstr "" #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/stock/dashboard/item_dashboard.js:227 #: erpnext/stock/doctype/material_request_item/material_request_item.json -#: erpnext/stock/doctype/stock_entry/stock_entry.js:719 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:806 #: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json msgid "Source Warehouse" msgstr "" @@ -48364,7 +48410,7 @@ msgstr "" msgid "Source and Target Location cannot be same" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:816 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:878 msgid "Source and target warehouse cannot be same for row {0}" msgstr "" @@ -48377,9 +48423,9 @@ msgstr "" msgid "Source of Funds (Liabilities)" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:782 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:799 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:806 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:844 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:861 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:868 msgid "Source warehouse is mandatory for row {0}" msgstr "" @@ -48721,8 +48767,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:1358 -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1384 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1364 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1390 #: erpnext/accounts/report/account_balance/account_balance.js:58 msgid "Stock Adjustment" msgstr "" @@ -48829,7 +48875,7 @@ msgstr "" msgid "Stock Details" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:910 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1002 msgid "Stock Entries already created for Work Order {0}: {1}" msgstr "" @@ -48900,7 +48946,7 @@ msgstr "" msgid "Stock Entry {0} has created" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1312 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1315 msgid "Stock Entry {0} is not submitted" msgstr "" @@ -49131,15 +49177,15 @@ 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:921 -#: erpnext/manufacturing/doctype/work_order/work_order.js:930 -#: erpnext/manufacturing/doctype/work_order/work_order.js:937 +#: erpnext/manufacturing/doctype/work_order/work_order.js:927 +#: erpnext/manufacturing/doctype/work_order/work_order.js:936 +#: erpnext/manufacturing/doctype/work_order/work_order.js:943 #: 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: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/selling/doctype/sales_order/sales_order.js:108 +#: erpnext/selling/doctype/sales_order/sales_order.js:123 +#: erpnext/selling/doctype/sales_order/sales_order.js:136 +#: erpnext/selling/doctype/sales_order/sales_order.js:257 #: erpnext/stock/doctype/pick_list/pick_list.js:152 #: erpnext/stock/doctype/pick_list/pick_list.js:167 #: erpnext/stock/doctype/pick_list/pick_list.js:172 @@ -49171,7 +49217,7 @@ msgstr "" #: erpnext/controllers/subcontracting_inward_controller.py:1018 #: erpnext/manufacturing/doctype/production_plan/production_plan.py:2245 -#: erpnext/manufacturing/doctype/work_order/work_order.py:2124 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2125 #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:1777 msgid "Stock Reservation Entries Created" msgstr "" @@ -49182,7 +49228,7 @@ msgstr "" #. Name of a DocType #: erpnext/public/js/stock_reservation.js:309 -#: erpnext/selling/doctype/sales_order/sales_order.js:467 +#: erpnext/selling/doctype/sales_order/sales_order.js:474 #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:388 #: erpnext/stock/report/reserved_stock/reserved_stock.js:53 @@ -49371,7 +49417,7 @@ msgid "Stock UOM Quantity" msgstr "" #: erpnext/public/js/stock_reservation.js:230 -#: erpnext/selling/doctype/sales_order/sales_order.js:451 +#: erpnext/selling/doctype/sales_order/sales_order.js:458 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:327 msgid "Stock Unreservation" msgstr "" @@ -49385,7 +49431,7 @@ msgstr "" msgid "Stock Uom" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:739 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:736 msgid "Stock Update Not Allowed" msgstr "" @@ -49482,18 +49528,22 @@ msgstr "" msgid "Stock cannot be reserved in the group warehouse {0}." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1226 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1223 msgid "Stock cannot be updated against the following Delivery Notes: {0}" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1295 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1292 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 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:733 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/warehouse/warehouse.py:124 +msgid "Stock entries exist with the old account. Changing the account may lead to a mismatch between the warehouse closing balance and the account closing balance. The overall closing balance will still match, but not for the specific account." +msgstr "" + #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:1131 msgid "Stock has been unreserved for work order {0}." msgstr "" @@ -49602,7 +49652,7 @@ msgstr "" #. Label of the operation (Link) field in DocType 'Job Card Time Log' #. Name of a DocType -#: erpnext/manufacturing/doctype/job_card/job_card.js:355 +#: erpnext/manufacturing/doctype/job_card/job_card.js:357 #: erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json #: erpnext/manufacturing/doctype/sub_operation/sub_operation.json msgid "Sub Operation" @@ -49786,12 +49836,14 @@ msgstr "" #. Name of a DocType #. Label of a Card Break in the Subcontracting Workspace #. Label of a Link in the Subcontracting Workspace +#. Label of a Workspace Sidebar Item #: erpnext/manufacturing/doctype/work_order/work_order.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1005 +#: erpnext/selling/doctype/sales_order/sales_order.js:1012 #: erpnext/stock/doctype/stock_entry/stock_entry.json #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json #: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.json #: erpnext/subcontracting/workspace/subcontracting/subcontracting.json +#: erpnext/workspace_sidebar/subcontracting.json msgid "Subcontracting Inward Order" msgstr "" @@ -50329,8 +50381,8 @@ 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:189 -#: erpnext/selling/doctype/sales_order/sales_order.js:1660 +#: erpnext/selling/doctype/sales_order/sales_order.js:196 +#: erpnext/selling/doctype/sales_order/sales_order.js:1667 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/selling/doctype/sms_center/sms_center.json #: erpnext/setup/workspace/home/home.json @@ -50489,7 +50541,7 @@ msgstr "" msgid "Supplier Invoice No" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1774 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1780 msgid "Supplier Invoice No exists in Purchase Invoice {0}" msgstr "" @@ -50657,7 +50709,7 @@ msgstr "" msgid "Supplier Reference" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1684 +#: erpnext/selling/doctype/sales_order/sales_order.js:1691 msgid "Supplier Required" msgstr "" @@ -50751,7 +50803,7 @@ msgstr "" msgid "Supplier delivers to Customer" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1683 +#: erpnext/selling/doctype/sales_order/sales_order.js:1690 msgid "Supplier is required for all selected Items" msgstr "" @@ -50919,7 +50971,7 @@ msgstr "" msgid "TDS Computation Summary" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1535 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1541 msgid "TDS Deducted" msgstr "" @@ -51058,7 +51110,7 @@ msgstr "" #: erpnext/stock/dashboard/item_dashboard.js:234 #: erpnext/stock/doctype/delivery_note_item/delivery_note_item.json #: erpnext/stock/doctype/material_request_item/material_request_item.json -#: erpnext/stock/doctype/stock_entry/stock_entry.js:725 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:812 #: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json msgid "Target Warehouse" msgstr "" @@ -51094,9 +51146,9 @@ msgstr "" msgid "Target Warehouse {0} must be same as Delivery Warehouse {1} in the Subcontracting Inward Order Item." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:788 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:795 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:810 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:850 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:857 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:872 msgid "Target warehouse is mandatory for row {0}" msgstr "" @@ -51356,7 +51408,7 @@ msgstr "" #. Label of the rate (Float) field in DocType 'Sales Taxes and Charges' #. Label of the tax_rate (Percent) field in DocType 'Tax Withholding Entry' #: erpnext/accounts/doctype/account/account.json -#: erpnext/accounts/doctype/account/account_tree.js:175 +#: erpnext/accounts/doctype/account/account_tree.js:170 #: erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json #: erpnext/accounts/doctype/item_tax_template_detail/item_tax_template_detail.json #: erpnext/accounts/doctype/item_wise_tax_detail/item_wise_tax_detail.json @@ -51567,7 +51619,7 @@ msgstr "" #. Detail' #: erpnext/accounts/doctype/item_wise_tax_detail/item_wise_tax_detail.json #: erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py:157 -#: erpnext/controllers/taxes_and_totals.py:1249 +#: erpnext/controllers/taxes_and_totals.py:1253 msgid "Taxable Amount" msgstr "" @@ -52016,7 +52068,7 @@ msgstr "" #: erpnext/crm/report/lead_details/lead_details.js:46 #: erpnext/crm/report/lead_details/lead_details.py:34 #: erpnext/crm/report/lost_opportunity/lost_opportunity.js:36 -#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:58 +#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:64 #: erpnext/crm/workspace/crm/crm.json #: erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json #: erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json @@ -52152,7 +52204,7 @@ msgstr "" msgid "The Pick List having Stock Reservation Entries cannot be updated. If you need to make changes, we recommend canceling the existing Stock Reservation Entries before updating the Pick List." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2621 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2820 msgid "The Process Loss Qty has reset as per job cards Process Loss Qty" msgstr "" @@ -52164,11 +52216,11 @@ 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:2636 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:2669 msgid "The Serial No {0} is reserved against the {1} {2} and cannot be used for any other transaction." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1747 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1839 msgid "The Serial and Batch Bundle {0} is not valid for this transaction. The 'Type of Transaction' should be 'Outward' instead of 'Inward' in Serial and Batch Bundle {0}" msgstr "" @@ -52206,7 +52258,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:1151 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1211 msgid "The default BOM for that item will be fetched by the system. You can also change the BOM." msgstr "" @@ -52392,7 +52444,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:951 +#: erpnext/public/js/utils.js:967 msgid "The reserved stock will be released when you update items. Are you certain you wish to proceed?" msgstr "" @@ -52445,7 +52497,7 @@ msgstr "" msgid "The shares don't exist with the {0}" msgstr "" -#: erpnext/stock/stock_ledger.py:813 +#: erpnext/stock/stock_ledger.py:814 msgid "The stock for the item {0} in the {1} warehouse was negative on the {2}. You should create a positive entry {3} before the date {4} and time {5} to post the correct valuation rate. For more details, please read the documentation." msgstr "" @@ -52511,15 +52563,15 @@ msgstr "" msgid "The value {0} is already assigned to an existing Item {1}." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1179 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1239 msgid "The warehouse where you store finished Items before they are shipped." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1172 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1232 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:1184 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1244 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 "" @@ -52607,7 +52659,7 @@ msgstr "" msgid "There is no batch found against the {0}: {1}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1684 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1776 msgid "There must be atleast 1 Finished Good in this Stock Entry" msgstr "" @@ -52769,11 +52821,11 @@ msgstr "" msgid "This is considered dangerous from accounting point of view." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:539 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:536 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:1165 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1225 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 "" @@ -52811,7 +52863,7 @@ msgstr "" msgid "This schedule was created when Asset {0} was repaired through Asset Repair {1}." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1502 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1499 msgid "This schedule was created when Asset {0} was restored due to Sales Invoice {1} cancellation." msgstr "" @@ -52823,7 +52875,7 @@ msgstr "" msgid "This schedule was created when Asset {0} was restored." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1498 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1495 msgid "This schedule was created when Asset {0} was returned through Sales Invoice {1}." msgstr "" @@ -52835,7 +52887,7 @@ msgstr "" msgid "This schedule was created when Asset {0} was {1} into new Asset {2}." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1474 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1471 msgid "This schedule was created when Asset {0} was {1} through Sales Invoice {2}." msgstr "" @@ -53021,7 +53073,7 @@ msgstr "" #. Name of a DocType #. Label of a Link in the Projects Workspace #. Label of a Workspace Sidebar Item -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1066 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:281 #: erpnext/projects/doctype/timesheet/timesheet.json #: erpnext/projects/report/daily_timesheet_summary/daily_timesheet_summary.py:26 #: erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py:59 @@ -53318,7 +53370,7 @@ msgstr "" msgid "To Warehouse (Optional)" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:997 +#: erpnext/manufacturing/doctype/bom/bom.js:999 msgid "To add Operations tick the 'With Operations' checkbox." msgstr "" @@ -53397,11 +53449,11 @@ msgstr "" msgid "To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings." msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:630 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:627 msgid "To submit the invoice without purchase order please set {0} as {1} in {2}" msgstr "" -#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:651 +#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:648 msgid "To submit the invoice without purchase receipt please set {0} as {1} in {2}" msgstr "" @@ -53723,7 +53775,7 @@ msgstr "" msgid "Total Debit" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:933 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:936 msgid "Total Debit must be equal to Total Credit. The difference is {0}" msgstr "" @@ -54156,7 +54208,7 @@ msgstr "" msgid "Total Time in Mins" msgstr "" -#: erpnext/public/js/utils.js:177 +#: erpnext/public/js/utils.js:193 msgid "Total Unpaid: {0}" msgstr "" @@ -54260,7 +54312,7 @@ msgstr "" msgid "Total percentage against cost centers should be 100" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:665 +#: erpnext/selling/doctype/sales_order/sales_order.js:672 msgid "Total quantity in delivery schedule cannot be greater than the item quantity" msgstr "" @@ -54532,7 +54584,7 @@ msgstr "" msgid "Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1159 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1156 msgid "Transactions using Sales Invoice in POS are disabled." msgstr "" @@ -54631,7 +54683,7 @@ msgstr "" msgid "Transit" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.js:502 +#: erpnext/stock/doctype/stock_entry/stock_entry.js:589 msgid "Transit Entry" msgstr "" @@ -54918,13 +54970,13 @@ 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:824 +#: erpnext/public/js/stock_analytics.js:94 erpnext/public/js/utils.js:840 #: 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 #: erpnext/selling/doctype/product_bundle_item/product_bundle_item.json #: erpnext/selling/doctype/quotation_item/quotation_item.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1653 +#: erpnext/selling/doctype/sales_order/sales_order.js:1660 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py:43 #: erpnext/selling/report/sales_analytics/sales_analytics.py:138 @@ -55014,7 +55066,7 @@ msgstr "" msgid "UOM Name" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3806 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:4008 msgid "UOM conversion factor required for UOM: {0} in Item: {1}" msgstr "" @@ -55328,15 +55380,15 @@ msgstr "" msgid "Unreconciled Entries" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:928 -#: erpnext/selling/doctype/sales_order/sales_order.js:114 +#: erpnext/manufacturing/doctype/work_order/work_order.js:934 +#: erpnext/selling/doctype/sales_order/sales_order.js:121 #: erpnext/stock/doctype/pick_list/pick_list.js:158 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:193 msgid "Unreserve" msgstr "" #: erpnext/public/js/stock_reservation.js:245 -#: erpnext/selling/doctype/sales_order/sales_order.js:502 +#: erpnext/selling/doctype/sales_order/sales_order.js:509 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:378 msgid "Unreserve Stock" msgstr "" @@ -55350,7 +55402,7 @@ msgid "Unreserve for Sub-assembly" msgstr "" #: erpnext/public/js/stock_reservation.js:281 -#: erpnext/selling/doctype/sales_order/sales_order.js:514 +#: erpnext/selling/doctype/sales_order/sales_order.js:521 #: erpnext/stock/doctype/pick_list/pick_list.js:310 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:390 msgid "Unreserving Stock..." @@ -55536,10 +55588,10 @@ msgstr "" #: erpnext/buying/doctype/purchase_order/purchase_order.js:324 #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.js:43 -#: erpnext/public/js/utils.js:930 +#: erpnext/public/js/utils.js:946 #: erpnext/selling/doctype/quotation/quotation.js:135 -#: erpnext/selling/doctype/sales_order/sales_order.js:82 -#: erpnext/selling/doctype/sales_order/sales_order.js:940 +#: erpnext/selling/doctype/sales_order/sales_order.js:89 +#: erpnext/selling/doctype/sales_order/sales_order.js:947 msgid "Update Items" msgstr "" @@ -55638,7 +55690,7 @@ msgstr "" msgid "Updating Variants..." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1127 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1187 msgid "Updating Work Order status" msgstr "" @@ -56190,11 +56242,11 @@ msgstr "" msgid "Valuation Rate (In / Out)" msgstr "" -#: erpnext/stock/stock_ledger.py:2076 +#: erpnext/stock/stock_ledger.py:2062 msgid "Valuation Rate Missing" msgstr "" -#: erpnext/stock/stock_ledger.py:2054 +#: erpnext/stock/stock_ledger.py:2040 msgid "Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}." msgstr "" @@ -56511,7 +56563,7 @@ msgstr "" msgid "Video Settings" msgstr "" -#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:7 +#: erpnext/accounts/doctype/financial_report_template/financial_report_template.js:9 msgid "View Account Coverage" msgstr "" @@ -56543,7 +56595,7 @@ msgstr "" msgid "View Leads" msgstr "" -#: erpnext/accounts/doctype/account/account_tree.js:279 +#: erpnext/accounts/doctype/account/account_tree.js:274 #: erpnext/stock/doctype/batch/batch.js:18 msgid "View Ledger" msgstr "" @@ -56760,7 +56812,7 @@ msgstr "" msgid "Voucher No" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1386 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1419 msgid "Voucher No is mandatory" msgstr "" @@ -57004,11 +57056,11 @@ msgstr "" msgid "Warehouse is required to get producible FG Items" msgstr "" -#: erpnext/stock/doctype/warehouse/warehouse.py:259 +#: erpnext/stock/doctype/warehouse/warehouse.py:233 msgid "Warehouse not found against the account {0}" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1216 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1213 #: erpnext/stock/doctype/delivery_note/delivery_note.py:444 msgid "Warehouse required for stock Item {0}" msgstr "" @@ -57030,7 +57082,7 @@ msgstr "" msgid "Warehouse {0} does not belong to company {1}" msgstr "" -#: erpnext/stock/doctype/warehouse/warehouse.py:306 +#: erpnext/stock/doctype/warehouse/warehouse.py:280 msgid "Warehouse {0} does not exist" msgstr "" @@ -57042,10 +57094,6 @@ msgstr "" msgid "Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}." msgstr "" -#: erpnext/stock/doctype/warehouse/warehouse.py:144 -msgid "Warehouse's Stock Value has already been booked in the following accounts:" -msgstr "" - #: erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py:20 msgid "Warehouse: {0} does not belong to {1}" msgstr "" @@ -57059,15 +57107,15 @@ msgstr "" msgid "Warehouses" msgstr "" -#: erpnext/stock/doctype/warehouse/warehouse.py:173 +#: erpnext/stock/doctype/warehouse/warehouse.py:147 msgid "Warehouses with child nodes cannot be converted to ledger" msgstr "" -#: erpnext/stock/doctype/warehouse/warehouse.py:183 +#: erpnext/stock/doctype/warehouse/warehouse.py:157 msgid "Warehouses with existing transaction can not be converted to group." msgstr "" -#: erpnext/stock/doctype/warehouse/warehouse.py:175 +#: erpnext/stock/doctype/warehouse/warehouse.py:149 msgid "Warehouses with existing transaction can not be converted to ledger." msgstr "" @@ -57143,7 +57191,7 @@ msgstr "" msgid "Warning - Row {0}: Billing Hours are more than Actual Hours" msgstr "" -#: erpnext/stock/stock_ledger.py:823 +#: erpnext/stock/stock_ledger.py:824 msgid "Warning on Negative Stock" msgstr "" @@ -57151,7 +57199,11 @@ msgstr "" msgid "Warning!" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1318 +#: erpnext/stock/doctype/warehouse/warehouse.py:122 +msgid "Warning: Account changed for warehouse" +msgstr "" + +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1321 msgid "Warning: Another {0} # {1} exists against stock entry {2}" msgstr "" @@ -57159,7 +57211,7 @@ msgstr "" msgid "Warning: Material Requested Qty is less than Minimum Order Qty" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:1460 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1461 msgid "Warning: Quantity exceeds maximum producible quantity based on quantity of raw materials received through the Subcontracting Inward Order {0}." msgstr "" @@ -57430,7 +57482,7 @@ msgstr "" msgid "When creating an Item, entering a value for this field will automatically create an Item Price at the backend." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:296 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:304 msgid "When there are multiple finished goods ({0}) in a Repack stock entry, the basic rate for all finished goods must be set manually. To set rate manually, enable the checkbox 'Set Basic Rate Manually' in the respective finished good row." msgstr "" @@ -57582,7 +57634,7 @@ msgstr "" #: erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js:29 #: erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py:104 #: erpnext/manufacturing/workspace/manufacturing/manufacturing.json -#: erpnext/selling/doctype/sales_order/sales_order.js:1050 +#: erpnext/selling/doctype/sales_order/sales_order.js:1057 #: erpnext/stock/doctype/material_request/material_request.js:216 #: erpnext/stock/doctype/material_request/material_request.json #: erpnext/stock/doctype/material_request/material_request.py:871 @@ -57619,6 +57671,10 @@ msgstr "" msgid "Work Order Item" msgstr "" +#: erpnext/stock/doctype/stock_entry/stock_entry.py:915 +msgid "Work Order Mismatch" +msgstr "" + #. Name of a DocType #: erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json msgid "Work Order Operation" @@ -57660,16 +57716,16 @@ msgstr "" msgid "Work Order cannot be created for following reason:
    {0}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:1404 +#: erpnext/manufacturing/doctype/work_order/work_order.py:1405 msgid "Work Order cannot be raised against a Item Template" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.py:2457 -#: erpnext/manufacturing/doctype/work_order/work_order.py:2537 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2481 +#: erpnext/manufacturing/doctype/work_order/work_order.py:2561 msgid "Work Order has been {0}" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1225 +#: erpnext/selling/doctype/sales_order/sales_order.js:1232 msgid "Work Order not created" msgstr "" @@ -57677,7 +57733,11 @@ msgstr "" msgid "Work Order {0} created" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:860 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2386 +msgid "Work Order {0} has no produced qty" +msgstr "" + +#: erpnext/stock/doctype/stock_entry/stock_entry.py:952 msgid "Work Order {0}: Job Card not found for the operation {1}" msgstr "" @@ -57686,7 +57746,7 @@ msgstr "" msgid "Work Orders" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:1318 +#: erpnext/selling/doctype/sales_order/sales_order.js:1325 msgid "Work Orders Created: {0}" msgstr "" @@ -57755,7 +57815,7 @@ msgstr "" #: erpnext/manufacturing/doctype/bom_operation/bom_operation.json #: erpnext/manufacturing/doctype/bom_website_operation/bom_website_operation.json #: erpnext/manufacturing/doctype/job_card/job_card.json -#: erpnext/manufacturing/doctype/work_order/work_order.js:326 +#: erpnext/manufacturing/doctype/work_order/work_order.js:329 #: erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json #: erpnext/manufacturing/doctype/workstation/workstation.json #: erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js:35 @@ -58039,7 +58099,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:709 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:712 msgid "You can not enter current voucher in 'Against Journal Entry' column" msgstr "" @@ -58048,7 +58108,7 @@ msgid "You can only have Plans with the same billing cycle in a Subscription" msgstr "" #: erpnext/accounts/doctype/pos_invoice/pos_invoice.js:423 -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:928 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:1019 msgid "You can only redeem max {0} points in this order." msgstr "" @@ -58096,7 +58156,7 @@ msgstr "" msgid "You cannot create/amend any accounting entries till this date." msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:942 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:945 msgid "You cannot credit and debit same account at the same time" msgstr "" @@ -58120,7 +58180,7 @@ msgstr "" msgid "You cannot redeem more than {0}." msgstr "" -#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:189 +#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py:210 msgid "You cannot repost item valuation before {}" msgstr "" @@ -58140,7 +58200,7 @@ 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:565 +#: erpnext/manufacturing/doctype/bom_creator/bom_creator.py:567 msgid "You do not have permission to edit this document" msgstr "" @@ -58160,7 +58220,7 @@ msgstr "" msgid "You had {} errors while creating opening invoices. Check {} for more details" msgstr "" -#: erpnext/public/js/utils.js:1030 +#: erpnext/public/js/utils.js:1046 msgid "You have already selected items from {0} {1}" msgstr "" @@ -58247,7 +58307,7 @@ msgstr "" msgid "Zero Rated" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:563 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:625 msgid "Zero quantity" msgstr "" @@ -58264,7 +58324,7 @@ msgstr "" msgid "`Allow Negative rates for Items`" msgstr "" -#: erpnext/stock/stock_ledger.py:2068 +#: erpnext/stock/stock_ledger.py:2054 msgid "after" msgstr "" @@ -58280,11 +58340,11 @@ msgstr "" msgid "as Title" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.js:1021 +#: erpnext/manufacturing/doctype/bom/bom.js:1023 msgid "as a percentage of finished item quantity" msgstr "" -#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1556 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1589 msgid "as of {0}" msgstr "" @@ -58412,7 +58472,7 @@ msgstr "" msgid "must be between 0 and 100" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.js:638 +#: erpnext/selling/doctype/sales_order/sales_order.js:645 msgid "name" msgstr "" @@ -58433,7 +58493,7 @@ msgstr "" msgid "paid to" msgstr "" -#: erpnext/public/js/utils.js:447 +#: erpnext/public/js/utils.js:463 msgid "payments app is not installed. Please install it from {0} or {1}" msgstr "" @@ -58454,7 +58514,7 @@ msgstr "" msgid "per hour" msgstr "" -#: erpnext/stock/stock_ledger.py:2069 +#: erpnext/stock/stock_ledger.py:2055 msgid "performing either one below:" msgstr "" @@ -58483,7 +58543,7 @@ msgstr "" msgid "received from" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1476 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1473 msgid "returned" msgstr "" @@ -58518,7 +58578,7 @@ msgstr "" msgid "sandbox" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1476 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1473 msgid "sold" msgstr "" @@ -58545,7 +58605,7 @@ msgstr "" msgid "to" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3175 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:3172 msgid "to unallocate the amount of this Return Invoice before cancelling it." msgstr "" @@ -58612,7 +58672,7 @@ msgstr "" msgid "{0} Digest" msgstr "" -#: erpnext/accounts/utils.py:1497 +#: erpnext/accounts/utils.py:1569 msgid "{0} Number {1} is already used in {2} {3}" msgstr "" @@ -58620,7 +58680,7 @@ msgstr "" msgid "{0} Operating Cost for operation {1}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:556 +#: erpnext/manufacturing/doctype/work_order/work_order.js:562 msgid "{0} Operations: {1}" msgstr "" @@ -58644,23 +58704,23 @@ msgstr "" msgid "{0} account is not of type {1}" msgstr "" -#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:519 +#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:518 msgid "{0} account not found while submitting purchase receipt" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1062 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1065 msgid "{0} against Bill {1} dated {2}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1071 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1074 msgid "{0} against Purchase Order {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1038 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1041 msgid "{0} against Sales Invoice {1}" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1045 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:1048 msgid "{0} against Sales Order {1}" msgstr "" @@ -58788,7 +58848,7 @@ msgstr "" msgid "{0} is in Draft. Submit it before creating the Asset." msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1131 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1128 msgid "{0} is mandatory for Item {1}" msgstr "" @@ -58805,7 +58865,7 @@ 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:1780 +#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:1813 msgid "{0} is not a CSV file." msgstr "" @@ -58817,7 +58877,7 @@ msgstr "" msgid "{0} is not a group node. Please select a group node as parent cost center" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:615 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:677 msgid "{0} is not a stock Item" msgstr "" @@ -58853,27 +58913,27 @@ 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:521 +#: erpnext/manufacturing/doctype/work_order/work_order.js:527 msgid "{0} items disassembled" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:485 +#: erpnext/manufacturing/doctype/work_order/work_order.js:491 msgid "{0} items in progress" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:509 +#: erpnext/manufacturing/doctype/work_order/work_order.js:515 msgid "{0} items lost during process." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:466 +#: erpnext/manufacturing/doctype/work_order/work_order.js:472 msgid "{0} items produced" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:489 +#: erpnext/manufacturing/doctype/work_order/work_order.js:495 msgid "{0} items returned" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:492 +#: erpnext/manufacturing/doctype/work_order/work_order.js:498 msgid "{0} items to return" msgstr "" @@ -58881,7 +58941,7 @@ msgstr "" msgid "{0} must be negative in return document" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2367 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2364 msgid "{0} not allowed to transact with {1}. Please change the Company or add the Company in the 'Allowed To Transact With'-Section in the Customer record." msgstr "" @@ -58917,16 +58977,16 @@ msgstr "" msgid "{0} units of {1} are required in {2} with the inventory dimension: {3} on {4} {5} for {6} to complete the transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:1721 erpnext/stock/stock_ledger.py:2217 -#: erpnext/stock/stock_ledger.py:2231 +#: erpnext/stock/stock_ledger.py:1707 erpnext/stock/stock_ledger.py:2203 +#: erpnext/stock/stock_ledger.py:2217 msgid "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:2318 erpnext/stock/stock_ledger.py:2363 +#: erpnext/stock/stock_ledger.py:2304 erpnext/stock/stock_ledger.py:2349 msgid "{0} units of {1} needed in {2} on {3} {4} to complete this transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:1715 +#: erpnext/stock/stock_ledger.py:1701 msgid "{0} units of {1} needed in {2} to complete this transaction." msgstr "" @@ -59027,7 +59087,7 @@ msgstr "" msgid "{0} {1} is cancelled so the action cannot be completed" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:857 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:860 msgid "{0} {1} is closed" msgstr "" @@ -59039,7 +59099,7 @@ msgstr "" msgid "{0} {1} is frozen" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:854 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:857 msgid "{0} {1} is fully billed" msgstr "" @@ -59055,8 +59115,8 @@ msgstr "" msgid "{0} {1} is not in any active Fiscal Year" msgstr "" -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:851 -#: erpnext/accounts/doctype/journal_entry/journal_entry.py:890 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:854 +#: erpnext/accounts/doctype/journal_entry/journal_entry.py:893 msgid "{0} {1} is not submitted" msgstr "" @@ -59217,7 +59277,7 @@ msgstr "" msgid "{}" msgstr "" -#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2133 +#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:2130 msgid "{} can't be cancelled since the Loyalty Points earned has been redeemed. First cancel the {} No {}" msgstr "" From bfe58b2d680e4d31bf87142570346f535c75b013 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 13 Apr 2026 11:09:12 +0530 Subject: [PATCH 168/168] refactor: boldface for group accounts in financial statements (cherry picked from commit 545e9e069a99e093fd9cc8ae9f86d3cd2d798bda) --- .../dimension_wise_accounts_balance_report.py | 1 + erpnext/public/js/financial_statements.js | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py index 2d0015a461e..394a9636b78 100644 --- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py @@ -142,6 +142,7 @@ def prepare_data(accounts, filters, company_currency, dimension_list): total = 0 row = { "account": d.name, + "is_group": d.is_group, "parent_account": d.parent_account, "indent": d.indent, "from_date": filters.from_date, diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 25918a179b8..0c8366d4e95 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -230,7 +230,10 @@ erpnext.financial_statements = { value = default_formatter(value, row, column, data); - if (data && !data.parent_account && !data.parent_section) { + if ( + data && + ((!data.parent_account && !data.parent_section) || data.is_group_account || data.is_group) + ) { value = $(`${value}`); var $value = $(value).css("font-weight", "bold");