From 288aad6f5dbd6865da6d58cc6c66032e6347edc6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 31 Mar 2025 15:53:55 +0530 Subject: [PATCH 01/62] feat: allow UOMs to select for which converstion rate defined in item master (cherry picked from commit b1dfbbe85e05db3e6e093aa8fb62880cc749d94b) # Conflicts: # erpnext/stock/doctype/stock_settings/stock_settings.json --- erpnext/controllers/queries.py | 29 +++++++++++++++++++ erpnext/public/js/controllers/transaction.js | 13 +++++++++ .../stock_settings/stock_settings.json | 22 ++++++++++++++ .../doctype/stock_settings/stock_settings.py | 1 + 4 files changed, 65 insertions(+) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index e9177f84fcc..670d46453f9 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -898,3 +898,32 @@ def get_filtered_child_rows(doctype, txt, searchfield, start, page_len, filters) ) return query.run(as_dict=False) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_item_uom_query(doctype, txt, searchfield, start, page_len, filters): + if frappe.db.get_single_value("Stock Settings", "allow_uom_with_conversion_rate_defined_in_item"): + query_filters = {"parent": filters.get("item_code")} + + if txt: + query_filters["uom"] = ["like", f"%{txt}%"] + + return frappe.get_all( + "UOM Conversion Detail", + filters=query_filters, + fields=["uom", "conversion_factor"], + limit_start=start, + limit_page_length=page_len, + order_by="idx", + as_list=1, + ) + + return frappe.get_all( + "UOM", + filters={"name": ["like", f"%{txt}%"]}, + fields=["name"], + limit_start=start, + limit_page_length=page_len, + as_list=1, + ) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 6423fd78fe9..8b7487c612e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -150,6 +150,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe }); } + if (this.frm.fields_dict["items"].grid.get_field("uom")) { + this.frm.set_query("uom", "items", function(doc, cdt, cdn) { + let row = locals[cdt][cdn]; + + return { + query: "erpnext.controllers.queries.get_item_uom_query", + filters: { + "item_code": row.item_code + } + }; + }); + } + if( this.frm.docstatus < 2 && this.frm.fields_dict["payment_terms_template"] diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 1987bc8642d..b416bb83aed 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -22,6 +22,8 @@ "allow_to_edit_stock_uom_qty_for_sales", "column_break_lznj", "allow_to_edit_stock_uom_qty_for_purchase", + "section_break_ylhd", + "allow_uom_with_conversion_rate_defined_in_item", "stock_validations_tab", "section_break_9", "over_delivery_receipt_allowance", @@ -490,6 +492,17 @@ { "fieldname": "column_break_wslv", "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_ylhd", + "fieldtype": "Section Break" + }, + { + "default": "0", + "description": "If enabled, the system will allow selecting UOMs in sales and purchase transactions only if the conversion rate is set in the item master.", + "fieldname": "allow_uom_with_conversion_rate_defined_in_item", + "fieldtype": "Check", + "label": "Allow UOM with Conversion Rate Defined in Item" } ], "icon": "icon-cog", @@ -497,7 +510,11 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2025-02-28 16:08:35.938840", +======= + "modified": "2025-03-31 15:34:20.752065", +>>>>>>> b1dfbbe85e (feat: allow UOMs to select for which converstion rate defined in item master) "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", @@ -518,7 +535,12 @@ } ], "quick_entry": 1, +<<<<<<< HEAD "sort_field": "modified", +======= + "row_format": "Dynamic", + "sort_field": "creation", +>>>>>>> b1dfbbe85e (feat: allow UOMs to select for which converstion rate defined in item master) "sort_order": "ASC", "states": [], "track_changes": 1 diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index 8589004c8bf..4de8057a006 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -33,6 +33,7 @@ class StockSettings(Document): allow_partial_reservation: DF.Check allow_to_edit_stock_uom_qty_for_purchase: DF.Check allow_to_edit_stock_uom_qty_for_sales: DF.Check + allow_uom_with_conversion_rate_defined_in_item: DF.Check auto_create_serial_and_batch_bundle_for_outward: DF.Check auto_indent: DF.Check auto_insert_price_list_rate_if_missing: DF.Check From 2b854377b15a2a701480baf5ec85010cd5ef0061 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 31 Mar 2025 20:43:34 +0530 Subject: [PATCH 02/62] chore: fix conflicts --- erpnext/stock/doctype/stock_settings/stock_settings.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index b416bb83aed..551104688a2 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -510,11 +510,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], -<<<<<<< HEAD - "modified": "2025-02-28 16:08:35.938840", -======= "modified": "2025-03-31 15:34:20.752065", ->>>>>>> b1dfbbe85e (feat: allow UOMs to select for which converstion rate defined in item master) "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", @@ -535,12 +531,7 @@ } ], "quick_entry": 1, -<<<<<<< HEAD - "sort_field": "modified", -======= - "row_format": "Dynamic", "sort_field": "creation", ->>>>>>> b1dfbbe85e (feat: allow UOMs to select for which converstion rate defined in item master) "sort_order": "ASC", "states": [], "track_changes": 1 From 353fa0cbc3d90ac5da683b24412f074a81fc5aea Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 31 Mar 2025 14:23:47 +0530 Subject: [PATCH 03/62] fix: condition to update the last puurchase rate (cherry picked from commit bad901e7daf36f3c51d6093f8b94edeb339da86a) --- erpnext/stock/doctype/item/item.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index e40b3822af6..003e0d4d3a0 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1196,7 +1196,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): return out -def get_purchase_voucher_details(doctype, item_code, document_name): +def get_purchase_voucher_details(doctype, item_code, document_name=None): parent_doc = frappe.qb.DocType(doctype) child_doc = frappe.qb.DocType(doctype + " Item") @@ -1215,9 +1215,11 @@ def get_purchase_voucher_details(doctype, item_code, document_name): ) .where(parent_doc.docstatus == 1) .where(child_doc.item_code == item_code) - .where(parent_doc.name != document_name) ) + if document_name: + query = query.where(parent_doc.name != document_name) + if doctype in ("Purchase Receipt", "Purchase Invoice"): query = query.select(parent_doc.posting_date, parent_doc.posting_time) query = query.orderby( From 55b17b918f9df9bea12a801013455c761121f03d Mon Sep 17 00:00:00 2001 From: vishakhdesai Date: Tue, 1 Apr 2025 12:43:34 +0530 Subject: [PATCH 04/62] fix: use `grand_total_diff` instead of `rounding_adjustment` in `taxes_and_totals` (cherry picked from commit fd252da6b1293824b0a4da6d440b4e1afa142cd8) --- erpnext/controllers/taxes_and_totals.py | 27 ++++++++----------- .../public/js/controllers/taxes_and_totals.js | 22 ++++++++------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index d2559241781..091b08cad0f 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -377,9 +377,7 @@ class calculate_taxes_and_totals: self._calculate() def calculate_taxes(self): - rounding_adjustment_computed = self.doc.get("is_consolidated") and self.doc.get("rounding_adjustment") - if not rounding_adjustment_computed: - self.doc.rounding_adjustment = 0 + self.grand_total_diff = 0 # maintain actual tax rate based on idx actual_tax_dict = dict( @@ -446,9 +444,8 @@ class calculate_taxes_and_totals: and self.discount_amount_applied and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total" - and not rounding_adjustment_computed ): - self.doc.rounding_adjustment = flt( + self.grand_total_diff = flt( self.doc.grand_total - flt(self.doc.discount_amount) - tax.total, self.doc.precision("rounding_adjustment"), ) @@ -552,11 +549,11 @@ class calculate_taxes_and_totals: return self.adjust_grand_total_for_inclusive_tax() def adjust_grand_total_for_inclusive_tax(self): - # if fully inclusive taxes and diff + # if any inclusive taxes and diff if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")): last_tax = self.doc.get("taxes")[-1] non_inclusive_tax_amount = sum( - flt(d.tax_amount_after_discount_amount) + self.get_tax_amount_if_for_valuation_or_deduction(d.tax_amount_after_discount_amount, d) for d in self.doc.get("taxes") if not d.included_in_print_rate ) @@ -573,27 +570,23 @@ class calculate_taxes_and_totals: diff = flt(diff, self.doc.precision("rounding_adjustment")) if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")): - self.doc.grand_total_diff = diff - else: - self.doc.grand_total_diff = 0 + self.grand_total_diff = diff def calculate_totals(self): if self.doc.get("taxes"): - self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt( - self.doc.get("grand_total_diff") - ) + self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + self.grand_total_diff else: self.doc.grand_total = flt(self.doc.net_total) if self.doc.get("taxes"): self.doc.total_taxes_and_charges = flt( - self.doc.grand_total - self.doc.net_total - flt(self.doc.get("grand_total_diff")), + self.doc.grand_total - self.doc.net_total - self.grand_total_diff, self.doc.precision("total_taxes_and_charges"), ) else: self.doc.total_taxes_and_charges = 0.0 - self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"]) + self._set_in_company_currency(self.doc, ["total_taxes_and_charges"]) if self.doc.doctype in [ "Quotation", @@ -643,7 +636,9 @@ class calculate_taxes_and_totals: if self.doc.meta.get_field("rounded_total"): if self.doc.is_rounded_total_disabled(): - self.doc.rounded_total = self.doc.base_rounded_total = 0 + self.doc.rounded_total = 0 + self.doc.base_rounded_total = 0 + self.doc.rounding_adjustment = 0 return self.doc.rounded_total = round_based_on_smallest_currency_fraction( diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 233c3d4c3b6..4bc787f85ff 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -343,7 +343,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { calculate_taxes() { var me = this; - this.frm.doc.rounding_adjustment = 0; + this.grand_total_diff = 0; var actual_tax_dict = {}; // maintain actual tax rate based on idx @@ -417,7 +417,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { // adjust Discount Amount loss in last tax iteration if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied && me.frm.doc.apply_discount_on == "Grand Total" && me.frm.doc.discount_amount) { - me.frm.doc.rounding_adjustment = flt(me.frm.doc.grand_total - + me.grand_total_diff = flt(me.frm.doc.grand_total - flt(me.frm.doc.discount_amount) - tax.total, precision("rounding_adjustment")); } } @@ -535,7 +535,8 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { adjust_grand_total_for_inclusive_tax() { var me = this; - // if fully inclusive taxes and diff + + // if any inclusive taxes and diff if (this.frm.doc["taxes"] && this.frm.doc["taxes"].length) { var any_inclusive_tax = false; $.each(this.frm.doc.taxes || [], function(i, d) { @@ -546,7 +547,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { var non_inclusive_tax_amount = frappe.utils.sum($.map(this.frm.doc.taxes || [], function(d) { if(!d.included_in_print_rate) { - return flt(d.tax_amount_after_discount_amount); + let tax_amount = d.category === "Valuation" ? 0 : d.tax_amount_after_discount_amount; + if (d.add_deduct_tax === "Deduct") tax_amount *= -1; + return tax_amount; } } )); @@ -560,9 +563,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { diff = flt(diff, precision("rounding_adjustment")); if ( diff && Math.abs(diff) <= (5.0 / Math.pow(10, precision("tax_amount", last_tax))) ) { - me.frm.doc.grand_total_diff = diff; - } else { - me.frm.doc.grand_total_diff = 0; + me.grand_total_diff = diff; } } } @@ -573,7 +574,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { var me = this; var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0; this.frm.doc.grand_total = flt(tax_count - ? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.grand_total_diff) + ? this.frm.doc["taxes"][tax_count - 1].total + this.grand_total_diff : this.frm.doc.net_total); if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) { @@ -605,9 +606,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } this.frm.doc.total_taxes_and_charges = flt(this.frm.doc.grand_total - this.frm.doc.net_total - - flt(this.frm.doc.grand_total_diff), precision("total_taxes_and_charges")); + - this.grand_total_diff, precision("total_taxes_and_charges")); - this.set_in_company_currency(this.frm.doc, ["total_taxes_and_charges", "rounding_adjustment"]); + this.set_in_company_currency(this.frm.doc, ["total_taxes_and_charges"]); // Round grand total as per precision frappe.model.round_floats_in(this.frm.doc, ["grand_total", "base_grand_total"]); @@ -627,6 +628,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if (cint(disable_rounded_total)) { this.frm.doc.rounded_total = 0; this.frm.doc.base_rounded_total = 0; + this.frm.doc.rounding_adjustment = 0; return; } From 80f144ac2236fc6f935a5f9897c716129e3d7a4c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 13:54:29 +0530 Subject: [PATCH 05/62] fix: pos checking opened entry closed or not (backport #46726) (#46830) fix: pos checking opened entry closed or not (#46726) * fix: pos checking opened entry closed or not * fix: linter issue * fix: linter issue (cherry picked from commit 5d5b6acc79a2a5639c468d276d71d20daa2fb99f) Co-authored-by: Diptanil Saha --- .../pos_closing_entry/pos_closing_entry.py | 5 +++++ .../page/point_of_sale/pos_controller.js | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index fda868cfe51..4fa8317ff76 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -124,6 +124,11 @@ class POSClosingEntry(StatusUpdater): def on_submit(self): consolidate_pos_invoices(closing_entry=self) + frappe.publish_realtime( + f"poe_{self.pos_opening_entry}_closed", + self, + docname=f"POS Opening Entry/{self.pos_opening_entry}", + ) def on_cancel(self): unconsolidate_pos_invoices(closing_entry=self) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index b7062abecba..d77381bf309 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -149,6 +149,25 @@ erpnext.PointOfSale.Controller = class { this.make_app(); }, }); + + frappe.realtime.on(`poe_${this.pos_opening}_closed`, (data) => { + if (data) { + frappe.dom.freeze(); + frappe.msgprint({ + title: __("POS Closed"), + indicator: "orange", + message: __("POS has been closed at {0}. Please refresh the page.", [ + frappe.datetime.str_to_user(data.creation).bold(), + ]), + primary_action_label: __("Refresh"), + primary_action: { + action() { + window.location.reload(); + }, + }, + }); + } + }); } set_opening_entry_status() { From c6979ab260dcb6ce97bbdff6b28b8efd4ec2da41 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 1 Apr 2025 14:45:24 +0530 Subject: [PATCH 06/62] fix: use work_order bom_no if no bom present in operation --- erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 885978deece..0bf383de285 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1616,7 +1616,7 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create "posting_date": nowdate(), "for_quantity": row.job_card_qty or work_order.get("qty", 0), "operation_id": row.get("name"), - "bom_no": row.get("bom"), + "bom_no": row.get("bom") or work_order.bom_no, "project": work_order.project, "company": work_order.company, "sequence_id": row.get("sequence_id"), From 54159b9e5e54893572e64e1e7cf54933d620b92a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 1 Apr 2025 13:26:28 +0530 Subject: [PATCH 07/62] fix: set draft QC in purchase document on creation of qc (cherry picked from commit 2553dea78ef35d7bebda98df1bf3db1f97c70027) --- .../quality_inspection/quality_inspection.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 7c6b892416b..bf1c918668f 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -197,8 +197,19 @@ class QualityInspection(Document): self.quality_inspection_template = template self.get_item_specification_details() + def on_update(self): + if ( + frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_not_submitted") + == "Warn" + ): + self.update_qc_reference() + def on_submit(self): - self.update_qc_reference() + if ( + frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_not_submitted") + == "Stop" + ): + self.update_qc_reference() def on_cancel(self): self.ignore_linked_doctypes = "Serial and Batch Bundle" @@ -206,15 +217,15 @@ class QualityInspection(Document): self.update_qc_reference() def on_trash(self): - self.update_qc_reference() + self.update_qc_reference(remove_reference=True) def validate_readings_status_mandatory(self): for reading in self.readings: if not reading.status: frappe.throw(_("Row #{0}: Status is mandatory").format(reading.idx)) - def update_qc_reference(self): - quality_inspection = self.name if self.docstatus == 1 else "" + def update_qc_reference(self, remove_reference=False): + quality_inspection = self.name if self.docstatus < 2 and not remove_reference else "" if self.reference_type == "Job Card": if self.reference_name: @@ -244,7 +255,7 @@ class QualityInspection(Document): ) ) - if self.batch_no and self.docstatus == 1: + if self.batch_no and self.docstatus < 2: query = query.where(child_doc.batch_no == self.batch_no) if self.docstatus == 2: # if cancel, then remove qi link wherever same name From 2dfe13e183f5358e9ad2ce85ddf5f4bea31d7902 Mon Sep 17 00:00:00 2001 From: Md Hussain Nagaria <34810212+NagariaHussain@users.noreply.github.com> Date: Wed, 2 Apr 2025 12:15:10 +0530 Subject: [PATCH 08/62] chore: update links to Frappe School (#46823) (cherry picked from commit ef4f662c319d8d7d1943c3bc62abc9652a15a7e4) # Conflicts: # erpnext/accounts/workspace/accounting/accounting.json # erpnext/buying/workspace/buying/buying.json # erpnext/manufacturing/workspace/manufacturing/manufacturing.json # erpnext/projects/workspace/projects/projects.json # erpnext/selling/workspace/selling/selling.json # erpnext/stock/workspace/stock/stock.json --- README.md | 2 +- .../accounts/workspace/accounting/accounting.json | 6 +++++- erpnext/buying/workspace/buying/buying.json | 6 +++++- .../workspace/manufacturing/manufacturing.json | 6 +++++- erpnext/projects/workspace/projects/projects.json | 6 +++++- erpnext/selling/workspace/selling/selling.json | 12 +++++++++++- erpnext/setup/install.py | 2 +- erpnext/stock/workspace/stock/stock.json | 6 +++++- 8 files changed, 38 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 44bd7296881..3b703d97831 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ New passwords will be created for the ERPNext "Administrator" user, the MariaDB ## Learning and community -1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community. +1. [Frappe School](https://school.frappe.io) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community. 2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext. 3. [Discussion Forum](https://discuss.erpnext.com/) - Engage with community of ERPNext users and service providers. 4. [Telegram Group](https://erpnext_public.t.me) - Get instant help from huge community of users. diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 45ab92e2c56..ed081102aa3 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -621,7 +621,11 @@ "doc_view": "List", "label": "Learn Accounting", "type": "URL", +<<<<<<< HEAD "url": "https://frappe.school/courses/erpnext-accounting?utm_source=in_app" +======= + "url": "https://school.frappe.io/lms/courses/erpnext-accounting?utm_source=in_app" +>>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "label": "Chart of Accounts", @@ -670,4 +674,4 @@ } ], "title": "Accounting" -} \ No newline at end of file +} diff --git a/erpnext/buying/workspace/buying/buying.json b/erpnext/buying/workspace/buying/buying.json index 1394fc48d5b..54f44ea13da 100644 --- a/erpnext/buying/workspace/buying/buying.json +++ b/erpnext/buying/workspace/buying/buying.json @@ -537,7 +537,11 @@ "doc_view": "List", "label": "Learn Procurement", "type": "URL", +<<<<<<< HEAD "url": "https://frappe.school/courses/procurement?utm_source=in_app" +======= + "url": "https://school.frappe.io/lms/courses/procurement?utm_source=in_app" +>>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "color": "Yellow", @@ -572,4 +576,4 @@ } ], "title": "Buying" -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json index 06b9f1b0759..08befc61ef0 100644 --- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json @@ -336,7 +336,11 @@ "doc_view": "List", "label": "Learn Manufacturing", "type": "URL", +<<<<<<< HEAD "url": "https://frappe.school/courses/manufacturing?utm_source=in_app" +======= + "url": "https://school.frappe.io/lms/courses/manufacturing?utm_source=in_app" +>>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "color": "Grey", @@ -402,4 +406,4 @@ ], "title": "Manufacturing", "type": "Workspace" -} \ No newline at end of file +} diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index 94ae9c04a40..9effb1cbda6 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -210,7 +210,11 @@ "doc_view": "List", "label": "Learn Project Management", "type": "URL", +<<<<<<< HEAD "url": "https://frappe.school/courses/project-management?utm_source=in_app" +======= + "url": "https://school.frappe.io/lms/courses/project-management?utm_source=in_app" +>>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "color": "Blue", @@ -245,4 +249,4 @@ } ], "title": "Projects" -} \ No newline at end of file +} diff --git a/erpnext/selling/workspace/selling/selling.json b/erpnext/selling/workspace/selling/selling.json index e13bdec11fb..a392574e15c 100644 --- a/erpnext/selling/workspace/selling/selling.json +++ b/erpnext/selling/workspace/selling/selling.json @@ -639,7 +639,11 @@ "doc_view": "List", "label": "Learn Sales Management", "type": "URL", +<<<<<<< HEAD "url": "https://frappe.school/courses/sales-management-course?utm_source=in_app" +======= + "url": "https://school.frappe.io/lms/courses/sales-management-course?utm_source=in_app" +>>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "label": "Point of Sale", @@ -676,5 +680,11 @@ "type": "Dashboard" } ], +<<<<<<< HEAD "title": "Selling" -} \ No newline at end of file +} +======= + "title": "Selling", + "type": "Workspace" +} +>>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 23ffd49de5d..81e1ac6a458 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -182,7 +182,7 @@ def add_standard_navbar_items(): { "item_label": "Frappe School", "item_type": "Route", - "route": "https://frappe.school?utm_source=in_app", + "route": "https://frappe.io/school?utm_source=in_app", "is_standard": 1, }, { diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json index 1a683e2b3d8..9a9e3d9e5c3 100644 --- a/erpnext/stock/workspace/stock/stock.json +++ b/erpnext/stock/workspace/stock/stock.json @@ -802,7 +802,11 @@ "doc_view": "List", "label": "Learn Inventory Management", "type": "URL", +<<<<<<< HEAD "url": "https://frappe.school/courses/inventory-management?utm_source=in_app" +======= + "url": "https://school.frappe.io/lms/courses/inventory-management?utm_source=in_app" +>>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "color": "Yellow", @@ -850,4 +854,4 @@ } ], "title": "Stock" -} \ No newline at end of file +} From a19eece8818fef0f3a7c45ffa2a41fd9e12fca69 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Thu, 20 Mar 2025 15:12:06 +0530 Subject: [PATCH 09/62] refactor: move `payment_document` query to `setup` (cherry picked from commit 257802aeda0eeb4b8ad6264ab477ad8f1e38a55a) # Conflicts: # erpnext/accounts/doctype/bank_transaction/bank_transaction.js --- .../doctype/bank_transaction/bank_transaction.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index 5c5d9ff3469..3ce5da72f9a 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -2,7 +2,15 @@ // For license information, please see license.txt frappe.ui.form.on("Bank Transaction", { - onload(frm) { + setup: function (frm) { + frm.set_query("party_type", function () { + return { + filters: { + name: ["in", Object.keys(frappe.boot.party_account_types)], + }, + }; + }); + frm.set_query("payment_document", "payment_entries", function () { const payment_doctypes = frm.events.get_payment_doctypes(frm); return { @@ -12,6 +20,7 @@ frappe.ui.form.on("Bank Transaction", { }; }); }, + refresh(frm) { if (!frm.is_dirty() && frm.doc.payment_entries.length > 0) { frm.add_custom_button(__("Unreconcile Transaction"), () => { @@ -19,10 +28,12 @@ frappe.ui.form.on("Bank Transaction", { }); } }, + bank_account: function (frm) { set_bank_statement_filter(frm); }, +<<<<<<< HEAD setup: function (frm) { frm.set_query("party_type", function () { return { @@ -39,6 +50,8 @@ frappe.ui.form.on("Bank Transaction", { }); }, +======= +>>>>>>> 257802aeda (refactor: move `payment_document` query to `setup`) get_payment_doctypes: function () { // get payment doctypes from all the apps return ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Bank Transaction"]; From 5d47db78e635354cc91a4bc5b2d92e4776c818f0 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Thu, 20 Mar 2025 15:19:01 +0530 Subject: [PATCH 10/62] fix: add `Not Cancelled` filter for `payment_entry` in Bank Transaction (cherry picked from commit 85dd1dd4c7b1ff616401823470b9c011a4eb2383) --- .../accounts/doctype/bank_transaction/bank_transaction.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index 3ce5da72f9a..cb7bed698a2 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -19,6 +19,14 @@ frappe.ui.form.on("Bank Transaction", { }, }; }); + + frm.set_query("payment_entry", "payment_entries", function () { + return { + filters: { + docstatus: ["!=", 2], + }, + }; + }); }, refresh(frm) { From 3dc29cbec8f057665f878d362cef69ad3d86a09a Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Tue, 1 Apr 2025 12:12:37 +0530 Subject: [PATCH 11/62] chore: formatting (cherry picked from commit 4ae11d4384e98dcdce03dea772675bb856470670) # Conflicts: # erpnext/accounts/doctype/bank_transaction/bank_transaction.js --- .../doctype/bank_transaction/bank_transaction.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index cb7bed698a2..7a89f9e75b4 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -10,6 +10,15 @@ frappe.ui.form.on("Bank Transaction", { }, }; }); +<<<<<<< HEAD +======= + + frm.set_query("bank_account", function () { + return { + filters: { is_company_account: 1 }, + }; + }); +>>>>>>> 4ae11d4384 (chore: formatting) frm.set_query("payment_document", "payment_entries", function () { const payment_doctypes = frm.events.get_payment_doctypes(frm); From a6834f3875fdd93990b7e95cadc2487e6a120b5c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 5 Apr 2025 07:32:15 +0530 Subject: [PATCH 12/62] chore: resolve conflict --- .../bank_transaction/bank_transaction.js | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index 7a89f9e75b4..1ca76a68fde 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -10,15 +10,12 @@ frappe.ui.form.on("Bank Transaction", { }, }; }); -<<<<<<< HEAD -======= frm.set_query("bank_account", function () { return { filters: { is_company_account: 1 }, }; }); ->>>>>>> 4ae11d4384 (chore: formatting) frm.set_query("payment_document", "payment_entries", function () { const payment_doctypes = frm.events.get_payment_doctypes(frm); @@ -50,25 +47,6 @@ frappe.ui.form.on("Bank Transaction", { set_bank_statement_filter(frm); }, -<<<<<<< HEAD - setup: function (frm) { - frm.set_query("party_type", function () { - return { - filters: { - name: ["in", Object.keys(frappe.boot.party_account_types)], - }, - }; - }); - - frm.set_query("bank_account", function () { - return { - filters: { is_company_account: 1 }, - }; - }); - }, - -======= ->>>>>>> 257802aeda (refactor: move `payment_document` query to `setup`) get_payment_doctypes: function () { // get payment doctypes from all the apps return ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Bank Transaction"]; From f63595cf0cd0d18a6a7aafc2519c0dd86c6e4315 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 5 Apr 2025 17:36:43 +0200 Subject: [PATCH 13/62] fix(Dunning): undefined variable (backport #46868) (#46869) fix(Dunning): undefined variable (#46868) (cherry picked from commit 04df09cfcab118c94e7a20d8a1ed5caa3f5310d1) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/accounts/doctype/dunning/dunning.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index d63db3a09a1..00ed85a4e0b 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -220,6 +220,7 @@ def get_dunning_letter_text(dunning_type: str, doc: str | dict, language: str | if not language: language = doc.get("language") + letter_text = None if language: letter_text = frappe.db.get_value( DOCTYPE, {"parent": dunning_type, "language": language}, FIELDS, as_dict=1 From fcade5d8cd87fd51d002aee190af1271b6c6c672 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 5 Apr 2025 17:37:08 +0200 Subject: [PATCH 14/62] fix: Translate UnReconcile dialog title (backport #46818) (#46861) fix: Translate UnReconcile dialog title (cherry picked from commit f2cfb03c2c53453ea9fe85d0c1389f09eb954aa3) Co-authored-by: Corentin Forler --- erpnext/public/js/utils/unreconcile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/unreconcile.js b/erpnext/public/js/utils/unreconcile.js index 7dba4705e40..d085db05fa5 100644 --- a/erpnext/public/js/utils/unreconcile.js +++ b/erpnext/public/js/utils/unreconcile.js @@ -121,10 +121,10 @@ erpnext.accounts.unreconcile_payment = { }; let d = new frappe.ui.Dialog({ - title: "UnReconcile Allocations", + title: __("UnReconcile Allocations"), fields: unreconcile_dialog_fields, size: "large", - primary_action_label: "UnReconcile", + primary_action_label: __("UnReconcile"), primary_action(values) { let selected_allocations = values.allocations.filter((x) => x.__checked); if (selected_allocations.length > 0) { From d7bb4a288cb810882115f836ced846d883287600 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 5 Apr 2025 17:37:36 +0200 Subject: [PATCH 15/62] fix: make message translatable (backport #46863) (#46866) fix: make message translatable (#46863) (cherry picked from commit 7d12e9afd441812a07e032d9d38b70833d52a112) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/public/js/utils/unreconcile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/unreconcile.js b/erpnext/public/js/utils/unreconcile.js index d085db05fa5..b8c1db66cc8 100644 --- a/erpnext/public/js/utils/unreconcile.js +++ b/erpnext/public/js/utils/unreconcile.js @@ -138,7 +138,7 @@ erpnext.accounts.unreconcile_payment = { ); d.hide(); } else { - frappe.msgprint("No Selection"); + frappe.msgprint(__("No Selection")); } }, }); From 86853224c3df3089c78b09916c1d26a63bd9751e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 5 Apr 2025 17:39:42 +0200 Subject: [PATCH 16/62] refactor(Payment Entry): reduce indentation (backport #46864) (#46867) refactor(Payment Entry): reduce indentation (#46864) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .../doctype/payment_entry/payment_entry.py | 467 +++++++++--------- 1 file changed, 233 insertions(+), 234 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 6c7b1ad5f5a..4e9e3c4c042 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -249,16 +249,18 @@ class PaymentEntry(AccountsController): reference_names.add(key) def set_bank_account_data(self): - if self.bank_account: - bank_data = get_bank_account_details(self.bank_account) + if not self.bank_account: + return - field = "paid_from" if self.payment_type == "Pay" else "paid_to" + bank_data = get_bank_account_details(self.bank_account) - self.bank = bank_data.bank - self.bank_account_no = bank_data.bank_account_no + field = "paid_from" if self.payment_type == "Pay" else "paid_to" - if not self.get(field): - self.set(field, bank_data.account) + self.bank = bank_data.bank + self.bank_account_no = bank_data.bank_account_no + + if not self.get(field): + self.set(field, bank_data.account) def validate_payment_type_with_outstanding(self): total_outstanding = sum(d.allocated_amount for d in self.get("references")) @@ -276,15 +278,16 @@ class PaymentEntry(AccountsController): if self.party_type in ("Customer", "Supplier"): self.validate_allocated_amount_with_latest_data() - else: - fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") - for d in self.get("references"): - if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount): - frappe.throw(fail_message.format(d.idx)) + return - # Check for negative outstanding invoices as well - if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount): - frappe.throw(fail_message.format(d.idx)) + fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") + for d in self.get("references"): + if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) + + # Check for negative outstanding invoices as well + if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) def validate_allocated_amount_as_per_payment_request(self): """ @@ -322,91 +325,89 @@ class PaymentEntry(AccountsController): return False def validate_allocated_amount_with_latest_data(self): - if self.references: - uniq_vouchers = set([(x.reference_doctype, x.reference_name) for x in self.references]) - vouchers = [frappe._dict({"voucher_type": x[0], "voucher_no": x[1]}) for x in uniq_vouchers] - latest_references = get_outstanding_reference_documents( - { - "posting_date": self.posting_date, - "company": self.company, - "party_type": self.party_type, - "payment_type": self.payment_type, - "party": self.party, - "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, - "get_outstanding_invoices": True, - "get_orders_to_be_billed": True, - "vouchers": vouchers, - "book_advance_payments_in_separate_party_account": self.book_advance_payments_in_separate_party_account, - }, - validate=True, - ) + if not self.references: + return - # Group latest_references by (voucher_type, voucher_no) - latest_lookup = {} - for d in latest_references: - d = frappe._dict(d) - latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d + uniq_vouchers = {(x.reference_doctype, x.reference_name) for x in self.references} + vouchers = [frappe._dict({"voucher_type": x[0], "voucher_no": x[1]}) for x in uniq_vouchers] + latest_references = get_outstanding_reference_documents( + { + "posting_date": self.posting_date, + "company": self.company, + "party_type": self.party_type, + "payment_type": self.payment_type, + "party": self.party, + "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, + "get_outstanding_invoices": True, + "get_orders_to_be_billed": True, + "vouchers": vouchers, + "book_advance_payments_in_separate_party_account": self.book_advance_payments_in_separate_party_account, + }, + validate=True, + ) - for idx, d in enumerate(self.get("references"), start=1): - latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict() + # Group latest_references by (voucher_type, voucher_no) + latest_lookup = {} + for d in latest_references: + d = frappe._dict(d) + latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d - # If term based allocation is enabled, throw - if ( - d.payment_term is None or d.payment_term == "" - ) and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name): - frappe.throw( - _( - "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section" - ).format(frappe.bold(d.reference_name), frappe.bold(idx)) - ) + for idx, d in enumerate(self.get("references"), start=1): + latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict() - # if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key - latest = latest.get(d.payment_term) or latest.get(None) - # The reference has already been fully paid - if not latest: - frappe.throw( - _("{0} {1} has already been fully paid.").format( - _(d.reference_doctype), d.reference_name - ) - ) - # The reference has already been partly paid - elif ( - latest.outstanding_amount < latest.invoice_amount - and flt(d.outstanding_amount, d.precision("outstanding_amount")) - != flt(latest.outstanding_amount, d.precision("outstanding_amount")) - and d.payment_term == "" - ): - frappe.throw( - _( - "{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." - ).format(_(d.reference_doctype), d.reference_name) - ) + # If term based allocation is enabled, throw + if ( + d.payment_term is None or d.payment_term == "" + ) and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name): + frappe.throw( + _( + "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section" + ).format(frappe.bold(d.reference_name), frappe.bold(idx)) + ) - fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") + # if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key + latest = latest.get(d.payment_term) or latest.get(None) + # The reference has already been fully paid + if not latest: + frappe.throw( + _("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name) + ) + # The reference has already been partly paid + elif ( + latest.outstanding_amount < latest.invoice_amount + and flt(d.outstanding_amount, d.precision("outstanding_amount")) + != flt(latest.outstanding_amount, d.precision("outstanding_amount")) + and d.payment_term == "" + ): + frappe.throw( + _( + "{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." + ).format(_(d.reference_doctype), d.reference_name) + ) - if ( - d.payment_term - and ( - (flt(d.allocated_amount)) > 0 - and latest.payment_term_outstanding - and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding)) - ) - and self.term_based_allocation_enabled_for_reference( - d.reference_doctype, d.reference_name - ) - ): - frappe.throw( - _( - "Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}" - ).format(d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term) - ) + fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") - if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount): - frappe.throw(fail_message.format(d.idx)) + if ( + d.payment_term + and ( + (flt(d.allocated_amount)) > 0 + and latest.payment_term_outstanding + and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding)) + ) + and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name) + ): + frappe.throw( + _( + "Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}" + ).format(d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term) + ) - # Check for negative outstanding invoices as well - if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount): - frappe.throw(fail_message.format(d.idx)) + if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) + + # Check for negative outstanding invoices as well + if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) def delink_advance_entry_references(self): for reference in self.references: @@ -479,47 +480,48 @@ class PaymentEntry(AccountsController): reference_exchange_details: dict | None = None, ) -> None: for d in self.get("references"): - if d.allocated_amount: - if update_ref_details_only_for and ( - (d.reference_doctype, d.reference_name) not in update_ref_details_only_for - ): + if not d.allocated_amount: + continue + + if update_ref_details_only_for and ( + (d.reference_doctype, d.reference_name) not in update_ref_details_only_for + ): + continue + + ref_details = get_reference_details( + d.reference_doctype, + d.reference_name, + self.party_account_currency, + self.party_type, + self.party, + ) + + # Only update exchange rate when the reference is Journal Entry + if ( + reference_exchange_details + and d.reference_doctype == reference_exchange_details.reference_doctype + and d.reference_name == reference_exchange_details.reference_name + ): + ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate}) + + for field, value in ref_details.items(): + if d.exchange_gain_loss: + # for cases where gain/loss is booked into invoice + # exchange_gain_loss is calculated from invoice & populated + # and row.exchange_rate is already set to payment entry's exchange rate + # refer -> `update_reference_in_payment_entry()` in utils.py continue - ref_details = get_reference_details( - d.reference_doctype, - d.reference_name, - self.party_account_currency, - self.party_type, - self.party, - ) - - # Only update exchange rate when the reference is Journal Entry - if ( - reference_exchange_details - and d.reference_doctype == reference_exchange_details.reference_doctype - and d.reference_name == reference_exchange_details.reference_name - ): - ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate}) - - for field, value in ref_details.items(): - if d.exchange_gain_loss: - # for cases where gain/loss is booked into invoice - # exchange_gain_loss is calculated from invoice & populated - # and row.exchange_rate is already set to payment entry's exchange rate - # refer -> `update_reference_in_payment_entry()` in utils.py - continue - - if field == "exchange_rate" or not d.get(field) or force: - d.db_set(field, value) + if field == "exchange_rate" or not d.get(field) or force: + d.db_set(field, value) def validate_payment_type(self): if self.payment_type not in ("Receive", "Pay", "Internal Transfer"): frappe.throw(_("Payment Type must be one of Receive, Pay and Internal Transfer")) def validate_party_details(self): - if self.party: - if not frappe.db.exists(self.party_type, self.party): - frappe.throw(_("{0} {1} does not exist").format(_(self.party_type), self.party)) + if self.party and not frappe.db.exists(self.party_type, self.party): + frappe.throw(_("{0} {1} does not exist").format(_(self.party_type), self.party)) def set_exchange_rate(self, ref_doc=None): self.set_source_exchange_rate(ref_doc) @@ -529,12 +531,8 @@ class PaymentEntry(AccountsController): if self.paid_from: if self.paid_from_account_currency == self.company_currency: self.source_exchange_rate = 1 - else: - if ref_doc: - if self.paid_from_account_currency == ref_doc.currency: - self.source_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get( - "conversion_rate" - ) + elif ref_doc and self.paid_from_account_currency == ref_doc.currency: + self.source_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get("conversion_rate") if not self.source_exchange_rate: self.source_exchange_rate = get_exchange_rate( @@ -545,9 +543,8 @@ class PaymentEntry(AccountsController): if self.paid_from_account_currency == self.paid_to_account_currency: self.target_exchange_rate = self.source_exchange_rate elif self.paid_to and not self.target_exchange_rate: - if ref_doc: - if self.paid_to_account_currency == ref_doc.currency: - self.target_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get("conversion_rate") + if ref_doc and self.paid_to_account_currency == ref_doc.currency: + self.target_exchange_rate = ref_doc.get("exchange_rate") or ref_doc.get("conversion_rate") if not self.target_exchange_rate: self.target_exchange_rate = get_exchange_rate( @@ -578,63 +575,61 @@ class PaymentEntry(AccountsController): elif d.reference_name: if not frappe.db.exists(d.reference_doctype, d.reference_name): frappe.throw(_("{0} {1} does not exist").format(d.reference_doctype, d.reference_name)) - else: - ref_doc = frappe.get_doc(d.reference_doctype, d.reference_name) - if d.reference_doctype != "Journal Entry": - if self.party != ref_doc.get(scrub(self.party_type)): - frappe.throw( - _("{0} {1} is not associated with {2} {3}").format( - _(d.reference_doctype), d.reference_name, _(self.party_type), self.party - ) - ) - else: - self.validate_journal_entry() + ref_doc = frappe.get_doc(d.reference_doctype, d.reference_name) - if d.reference_doctype in frappe.get_hooks("invoice_doctypes"): - if self.party_type == "Customer": - ref_party_account = ( - get_party_account_based_on_invoice_discounting(d.reference_name) - or ref_doc.debit_to - ) - elif self.party_type == "Supplier": - ref_party_account = ref_doc.credit_to - elif self.party_type == "Employee": - ref_party_account = ref_doc.payable_account - - if ( - ref_party_account != self.party_account - and not self.book_advance_payments_in_separate_party_account - ): - frappe.throw( - _("{0} {1} is associated with {2}, but Party Account is {3}").format( - _(d.reference_doctype), - d.reference_name, - ref_party_account, - self.party_account, - ) - ) - - if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"): - frappe.throw( - _("{0} {1} is on hold").format(_(d.reference_doctype), d.reference_name), - title=_("Invalid Purchase Invoice"), - ) - - if ref_doc.docstatus != 1: + if d.reference_doctype != "Journal Entry": + if self.party != ref_doc.get(scrub(self.party_type)): frappe.throw( - _("{0} {1} must be submitted").format(_(d.reference_doctype), d.reference_name) + _("{0} {1} is not associated with {2} {3}").format( + _(d.reference_doctype), d.reference_name, _(self.party_type), self.party + ) ) + else: + self.validate_journal_entry() + + if d.reference_doctype in frappe.get_hooks("invoice_doctypes"): + if self.party_type == "Customer": + ref_party_account = ( + get_party_account_based_on_invoice_discounting(d.reference_name) + or ref_doc.debit_to + ) + elif self.party_type == "Supplier": + ref_party_account = ref_doc.credit_to + elif self.party_type == "Employee": + ref_party_account = ref_doc.payable_account + + if ( + ref_party_account != self.party_account + and not self.book_advance_payments_in_separate_party_account + ): + frappe.throw( + _("{0} {1} is associated with {2}, but Party Account is {3}").format( + _(d.reference_doctype), + d.reference_name, + ref_party_account, + self.party_account, + ) + ) + + if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"): + frappe.throw( + _("{0} {1} is on hold").format(_(d.reference_doctype), d.reference_name), + title=_("Invalid Purchase Invoice"), + ) + + if ref_doc.docstatus != 1: + frappe.throw( + _("{0} {1} must be submitted").format(_(d.reference_doctype), d.reference_name) + ) def get_valid_reference_doctypes(self): if self.party_type == "Customer": return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning", "Payment Entry") + elif self.party_type in ["Shareholder", "Employee"]: + return ("Journal Entry",) elif self.party_type == "Supplier": return ("Purchase Order", "Purchase Invoice", "Journal Entry", "Payment Entry") - elif self.party_type == "Shareholder": - return ("Journal Entry",) - elif self.party_type == "Employee": - return ("Journal Entry",) def validate_paid_invoices(self): no_oustanding_refs = {} @@ -700,37 +695,39 @@ class PaymentEntry(AccountsController): invoice_paid_amount_map = {} for ref in self.get("references"): - if ref.payment_term and ref.reference_name: - key = (ref.payment_term, ref.reference_name, ref.reference_doctype) - invoice_payment_amount_map.setdefault(key, 0.0) - invoice_payment_amount_map[key] += ref.allocated_amount + if not ref.payment_term or not ref.reference_name: + continue - if not invoice_paid_amount_map.get(key): - payment_schedule = frappe.get_all( - "Payment Schedule", - filters={"parent": ref.reference_name}, - fields=[ - "paid_amount", - "payment_amount", - "payment_term", - "discount", - "outstanding", - "discount_type", - ], - ) - for term in payment_schedule: - invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype) - invoice_paid_amount_map.setdefault(invoice_key, {}) - invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding - if not (term.discount_type and term.discount): - continue + key = (ref.payment_term, ref.reference_name, ref.reference_doctype) + invoice_payment_amount_map.setdefault(key, 0.0) + invoice_payment_amount_map[key] += ref.allocated_amount - if term.discount_type == "Percentage": - invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * ( - term.discount / 100 - ) - else: - invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount + if not invoice_paid_amount_map.get(key): + payment_schedule = frappe.get_all( + "Payment Schedule", + filters={"parent": ref.reference_name}, + fields=[ + "paid_amount", + "payment_amount", + "payment_term", + "discount", + "outstanding", + "discount_type", + ], + ) + for term in payment_schedule: + invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype) + invoice_paid_amount_map.setdefault(invoice_key, {}) + invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding + if not (term.discount_type and term.discount): + continue + + if term.discount_type == "Percentage": + invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * ( + term.discount / 100 + ) + else: + invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount for idx, (key, allocated_amount) in enumerate(invoice_payment_amount_map.items(), 1): if not invoice_paid_amount_map.get(key): @@ -977,14 +974,14 @@ class PaymentEntry(AccountsController): applicable_tax = 0 base_applicable_tax = 0 for tax in self.get("taxes"): - if not tax.included_in_paid_amount: - amount = -1 * tax.tax_amount if tax.add_deduct_tax == "Deduct" else tax.tax_amount - base_amount = ( - -1 * tax.base_tax_amount if tax.add_deduct_tax == "Deduct" else tax.base_tax_amount - ) + if tax.included_in_paid_amount: + continue - applicable_tax += amount - base_applicable_tax += base_amount + amount = -1 * tax.tax_amount if tax.add_deduct_tax == "Deduct" else tax.tax_amount + base_amount = -1 * tax.base_tax_amount if tax.add_deduct_tax == "Deduct" else tax.base_tax_amount + + applicable_tax += amount + base_applicable_tax += base_amount self.paid_amount_after_tax = flt( flt(self.paid_amount) + flt(applicable_tax), self.precision("paid_amount_after_tax") @@ -1648,25 +1645,27 @@ class PaymentEntry(AccountsController): def add_deductions_gl_entries(self, gl_entries): for d in self.get("deductions"): - if d.amount: - account_currency = get_account_currency(d.account) - if account_currency != self.company_currency: - frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency)) + if not d.amount: + continue - gl_entries.append( - self.get_gl_dict( - { - "account": d.account, - "account_currency": account_currency, - "against": self.party or self.paid_from, - "debit_in_account_currency": d.amount, - "debit_in_transaction_currency": d.amount / self.transaction_exchange_rate, - "debit": d.amount, - "cost_center": d.cost_center, - }, - item=d, - ) + account_currency = get_account_currency(d.account) + if account_currency != self.company_currency: + frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency)) + + gl_entries.append( + self.get_gl_dict( + { + "account": d.account, + "account_currency": account_currency, + "against": self.party or self.paid_from, + "debit_in_account_currency": d.amount, + "debit_in_transaction_currency": d.amount / self.transaction_exchange_rate, + "debit": d.amount, + "cost_center": d.cost_center, + }, + item=d, ) + ) def get_party_account_for_taxes(self): if self.payment_type == "Receive": From 890abf6b90a25b57a35a76afe6a12ee76f418d49 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 5 Apr 2025 23:04:42 +0530 Subject: [PATCH 17/62] perf: reduce query when validating any doc (cherry picked from commit b863296e5331739004ffb8eaffdc77a36515f50b) --- .../transaction_deletion_record.py | 56 +++++++++++-------- .../service_level_agreement.py | 18 +++--- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index edb55f5dc46..227df38e116 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -10,6 +10,14 @@ from frappe.model.document import Document from frappe.utils import cint, comma_and, create_batch, get_link_to_form from frappe.utils.background_jobs import get_job, is_job_enqueued +LEDGER_ENTRY_DOCTYPES = frozenset( + ( + "GL Entry", + "Payment Ledger Entry", + "Stock Ledger Entry", + ) +) + class TransactionDeletionRecord(Document): # begin: auto-generated types @@ -475,31 +483,31 @@ def get_doctypes_to_be_ignored(): @frappe.whitelist() def is_deletion_doc_running(company: str | None = None, err_msg: str | None = None): - if company: - if running_deletion_jobs := frappe.db.get_all( - "Transaction Deletion Record", - filters={"docstatus": 1, "company": company, "status": "Running"}, - ): - if not err_msg: - err_msg = "" - frappe.throw( - title=_("Deletion in Progress!"), - msg=_("Transaction Deletion Document: {0} is running for this Company. {1}").format( - get_link_to_form("Transaction Deletion Record", running_deletion_jobs[0].name), err_msg - ), - ) + if not company: + return + + running_deletion_job = frappe.db.get_value( + "Transaction Deletion Record", + {"docstatus": 1, "company": company, "status": "Running"}, + "name", + ) + + if not running_deletion_job: + return + + frappe.throw( + title=_("Deletion in Progress!"), + msg=_("Transaction Deletion Document: {0} is running for this Company. {1}").format( + get_link_to_form("Transaction Deletion Record", running_deletion_job), err_msg or "" + ), + ) def check_for_running_deletion_job(doc, method=None): # Check if DocType has 'company' field - if doc.doctype not in ("GL Entry", "Payment Ledger Entry", "Stock Ledger Entry"): - df = qb.DocType("DocField") - if ( - qb.from_(df) - .select(df.parent) - .where((df.fieldname == "company") & (df.parent == doc.doctype)) - .run() - ): - is_deletion_doc_running( - doc.company, _("Cannot make any transactions until the deletion job is completed") - ) + if doc.doctype in LEDGER_ENTRY_DOCTYPES or not doc.meta.has_field("company"): + return + + is_deletion_doc_running( + doc.company, _("Cannot make any transactions until the deletion job is completed") + ) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index 3433a842ea8..26c017bddaa 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -475,7 +475,7 @@ def get_repeated(values): def get_documents_with_active_service_level_agreement(): - sla_doctypes = frappe.cache().hget("service_level_agreement", "active") + sla_doctypes = frappe.cache.get_value("doctypes_with_active_sla") if sla_doctypes is None: return set_documents_with_active_service_level_agreement() @@ -484,20 +484,22 @@ def get_documents_with_active_service_level_agreement(): def set_documents_with_active_service_level_agreement(): - active = [ + active = frozenset( sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"]) - ] - frappe.cache().hset("service_level_agreement", "active", active) + ) + frappe.cache.set_value("doctypes_with_active_sla", active) return active def apply(doc, method=None): # Applies SLA to document on validate + flags = frappe.local.flags + if ( - frappe.flags.in_patch - or frappe.flags.in_migrate - or frappe.flags.in_install - or frappe.flags.in_setup_wizard + flags.in_patch + or flags.in_migrate + or flags.in_install + or flags.in_setup_wizard or doc.doctype not in get_documents_with_active_service_level_agreement() ): return From 8c61639062e9cf88b750cc237836c25919607b1a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 4 Apr 2025 13:38:45 +0530 Subject: [PATCH 18/62] fix: stock entry repack amount calculation (cherry picked from commit 544ceb93cd23045499246d2f21b1ab6419aeff44) --- .../doctype/stock_entry/test_stock_entry.py | 53 +++++++++++++++++++ erpnext/stock/stock_ledger.py | 6 ++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index fea807264f8..0791877f522 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -1907,6 +1907,59 @@ class TestStockEntry(FrappeTestCase): self.assertEqual(sle.stock_value_difference, 100) self.assertEqual(sle.stock_value, 100 * i) + def test_stock_entry_amount(self): + warehouse = "_Test Warehouse - _TC" + rm_item_code = "Test Stock Entry Amount 1" + make_item(rm_item_code, {"is_stock_item": 1}) + + fg_item_code = "Test Repack Stock Entry Amount 1" + make_item(fg_item_code, {"is_stock_item": 1}) + + make_stock_entry( + item_code=rm_item_code, + qty=1, + to_warehouse=warehouse, + basic_rate=200, + posting_date=nowdate(), + ) + + se = make_stock_entry( + item_code=rm_item_code, + qty=1, + purpose="Repack", + basic_rate=100, + do_not_save=True, + ) + + se.items[0].s_warehouse = warehouse + se.append( + "items", + { + "item_code": fg_item_code, + "qty": 1, + "t_warehouse": warehouse, + "uom": "Nos", + "conversion_factor": 1.0, + }, + ) + se.set_stock_entry_type() + se.submit() + + self.assertEqual(se.items[0].amount, 200) + self.assertEqual(se.items[0].basic_amount, 200) + + make_stock_entry( + item_code=rm_item_code, + qty=1, + to_warehouse=warehouse, + basic_rate=300, + posting_date=add_days(nowdate(), -1), + ) + + se.reload() + self.assertEqual(se.items[0].amount, 300) + self.assertEqual(se.items[0].basic_amount, 300) + def make_serialized_item(**args): args = frappe._dict(args) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9776486eabc..52f481f1374 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1238,7 +1238,11 @@ class update_entries_after: stock_entry.db_update() for d in stock_entry.items: # Update only the row that matches the voucher_detail_no or the row containing the FG/Scrap Item. - if d.name == voucher_detail_no or (not d.s_warehouse and d.t_warehouse): + if ( + d.name == voucher_detail_no + or (not d.s_warehouse and d.t_warehouse) + or stock_entry.purpose in ["Manufacture", "Repack"] + ): d.db_update() def update_rate_on_delivery_and_sales_return(self, sle, outgoing_rate): From 6b3e64c0cc7ac0bfb32eeb20021b0c68d43689a3 Mon Sep 17 00:00:00 2001 From: Sruthy Date: Sun, 23 Mar 2025 05:50:03 +0000 Subject: [PATCH 19/62] chore: adjusted dimension placement in Accounts Payable (cherry picked from commit 361a55a703074fe052125d27815f7f6f2d6a8029) --- erpnext/accounts/report/accounts_payable/accounts_payable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index c13197613d2..56550ac1fd4 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -164,7 +164,7 @@ frappe.query_reports["Accounts Payable"] = { }, }; -erpnext.utils.add_dimensions("Accounts Payable", 9); +erpnext.utils.add_dimensions("Accounts Payable", 10); function get_party_type_options() { let options = []; From 1128b5f09c89929700b9f5b4a8bdcdf0348bc73f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 25 Mar 2025 11:12:35 +0100 Subject: [PATCH 20/62] feat(Customer): add Dunning to dashboard (cherry picked from commit 638d825d8c67c23d327ad325a92b0904b8574e2c) --- erpnext/selling/doctype/customer/customer_dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer_dashboard.py b/erpnext/selling/doctype/customer/customer_dashboard.py index 161a3ba0c50..fc3c5cf1ab2 100644 --- a/erpnext/selling/doctype/customer/customer_dashboard.py +++ b/erpnext/selling/doctype/customer/customer_dashboard.py @@ -15,7 +15,7 @@ def get_data(): "transactions": [ {"label": _("Pre Sales"), "items": ["Opportunity", "Quotation"]}, {"label": _("Orders"), "items": ["Sales Order", "Delivery Note", "Sales Invoice"]}, - {"label": _("Payments"), "items": ["Payment Entry", "Bank Account"]}, + {"label": _("Payments"), "items": ["Payment Entry", "Bank Account", "Dunning"]}, { "label": _("Support"), "items": ["Issue", "Maintenance Visit", "Installation Note", "Warranty Claim"], From 1bdfd338166b357bbd48456fb9d9527c68846021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=BCrker=20Tunal=C4=B1?= Date: Sun, 6 Apr 2025 12:25:42 +0300 Subject: [PATCH 21/62] perf: Stock entry cancel is slow Some queries still use "timestamp" function instead of "posting_datetime". In my instance single stock entry cancel ends with request timeout. Using "posting_datetime" field directly improves the situation. cont: https://github.com/frappe/erpnext/pull/46293 (cherry picked from commit ddbb44c6a27b3d8bdcb95be978f98dc4bfe68a63) --- erpnext/controllers/stock_controller.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index e892c5d27e2..90959479fd8 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1631,6 +1631,8 @@ def is_reposting_pending(): def future_sle_exists(args, sl_entries=None, allow_force_reposting=True): + from erpnext.stock.utils import get_combine_datetime + if allow_force_reposting and frappe.db.get_single_value( "Stock Reposting Settings", "do_reposting_for_each_stock_transaction" ): @@ -1652,14 +1654,15 @@ def future_sle_exists(args, sl_entries=None, allow_force_reposting=True): or_conditions = get_conditions_to_validate_future_sle(sl_entries) + args["posting_datetime"] = get_combine_datetime(args["posting_date"], args["posting_time"]) + data = frappe.db.sql( """ select item_code, warehouse, count(name) as total_row - from `tabStock Ledger Entry` force index (item_warehouse) + from `tabStock Ledger Entry` where ({}) - and timestamp(posting_date, posting_time) - >= timestamp(%(posting_date)s, %(posting_time)s) + and posting_datetime >= %(posting_datetime)s and voucher_no != %(voucher_no)s and is_cancelled = 0 GROUP BY From 23dc9d58720330d5de17b737883d6d3d5ae064dd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 6 Apr 2025 09:52:41 +0530 Subject: [PATCH 22/62] fix: slow query (cherry picked from commit f82c8ea5eb3551a92ce0f2d54c57404a3cb5139d) # Conflicts: # erpnext/stock/deprecated_serial_batch.py --- erpnext/stock/deprecated_serial_batch.py | 18 ++++++++++-------- .../serial_and_batch_bundle.py | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index f084c324745..d6ce85ef7cc 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -195,6 +195,7 @@ class DeprecatedBatchNoValuation: @deprecated def set_balance_value_for_non_batchwise_valuation_batches(self): + self.last_sle = self.get_last_sle_for_non_batch() self.set_balance_value_from_sl_entries() self.set_balance_value_from_bundle() @@ -242,11 +243,10 @@ class DeprecatedBatchNoValuation: for d in batch_data: self.available_qty[d.batch_no] += flt(d.batch_qty) - last_sle = self.get_last_sle_for_non_batch() for d in batch_data: if self.available_qty.get(d.batch_no): - self.non_batchwise_balance_value[d.batch_no] += flt(last_sle.stock_value) - self.non_batchwise_balance_qty[d.batch_no] += flt(last_sle.qty_after_transaction) + self.non_batchwise_balance_value[d.batch_no] += flt(self.last_sle.stock_value) + self.non_batchwise_balance_qty[d.batch_no] += flt(self.last_sle.qty_after_transaction) def get_last_sle_for_non_batch(self): from erpnext.stock.utils import get_combine_datetime @@ -285,8 +285,8 @@ class DeprecatedBatchNoValuation: query = query.where(sle.name != self.sle.name) data = query.run(as_dict=True) - return data[0] if data else {} +<<<<<<< HEAD @deprecated def get_last_sle_for_sabb_no_batchwise_valuation(self): sabb = frappe.qb.DocType("Serial and Batch Bundle") @@ -339,6 +339,9 @@ class DeprecatedBatchNoValuation: ) return sle if sle else {} +======= + return data[0] if data else frappe._dict() +>>>>>>> f82c8ea5eb (fix: slow query) @deprecated def set_balance_value_from_bundle(self) -> None: @@ -389,10 +392,9 @@ class DeprecatedBatchNoValuation: for d in batch_data: self.available_qty[d.batch_no] += flt(d.batch_qty) - last_sle = self.get_last_sle_for_sabb_no_batchwise_valuation() - if not last_sle: + if not self.last_sle: return for batch_no in self.available_qty: - self.non_batchwise_balance_value[batch_no] = flt(last_sle.stock_value) - self.non_batchwise_balance_qty[batch_no] = flt(last_sle.qty_after_transaction) + self.non_batchwise_balance_value[batch_no] = flt(self.last_sle.stock_value) + self.non_batchwise_balance_qty[batch_no] = flt(self.last_sle.qty_after_transaction) 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 8502c0cc3cc..8abe5b7e082 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 @@ -2103,7 +2103,8 @@ def get_auto_batch_nos(kwargs): filter_zero_near_batches(available_batches, kwargs) if not kwargs.consider_negative_batches: - available_batches = list(filter(lambda x: x.qty > 0, available_batches)) + precision = frappe.get_precision("Stock Ledger Entry", "actual_qty") + available_batches = [d for d in available_batches if flt(d.qty, precision) > 0] if not qty: return available_batches From 4bcf05222014857e0ff5b2002623e290e88e1e8d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 6 Apr 2025 16:25:20 +0530 Subject: [PATCH 23/62] chore: fix conflicts --- erpnext/stock/deprecated_serial_batch.py | 55 ------------------------ 1 file changed, 55 deletions(-) diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index d6ce85ef7cc..a8e17993c30 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -286,62 +286,7 @@ class DeprecatedBatchNoValuation: data = query.run(as_dict=True) -<<<<<<< HEAD - @deprecated - def get_last_sle_for_sabb_no_batchwise_valuation(self): - sabb = frappe.qb.DocType("Serial and Batch Bundle") - sabb_entry = frappe.qb.DocType("Serial and Batch Entry") - batch = frappe.qb.DocType("Batch") - - posting_datetime = CombineDatetime(self.sle.posting_date, self.sle.posting_time) - timestamp_condition = CombineDatetime(sabb.posting_date, sabb.posting_time) < posting_datetime - - if self.sle.creation: - timestamp_condition |= ( - CombineDatetime(sabb.posting_date, sabb.posting_time) == posting_datetime - ) & (sabb.creation < self.sle.creation) - - query = ( - frappe.qb.from_(sabb) - .inner_join(sabb_entry) - .on(sabb.name == sabb_entry.parent) - .inner_join(batch) - .on(sabb_entry.batch_no == batch.name) - .select(sabb.name) - .where( - (sabb.item_code == self.sle.item_code) - & (sabb.warehouse == self.sle.warehouse) - & (sabb_entry.batch_no.isnotnull()) - & (sabb.is_cancelled == 0) - & (sabb.docstatus == 1) - ) - .where(timestamp_condition) - .orderby(sabb.posting_date, order=Order.desc) - .orderby(sabb.posting_time, order=Order.desc) - .orderby(sabb.creation, order=Order.desc) - .limit(1) - ) - - if self.sle.voucher_detail_no: - query = query.where(sabb.voucher_detail_no != self.sle.voucher_detail_no) - - query = query.where(sabb.voucher_type != "Pick List") - - data = query.run(as_dict=True) - if not data: - return {} - - sle = frappe.db.get_value( - "Stock Ledger Entry", - {"serial_and_batch_bundle": data[0].name}, - ["stock_value", "qty_after_transaction"], - as_dict=1, - ) - - return sle if sle else {} -======= return data[0] if data else frappe._dict() ->>>>>>> f82c8ea5eb (fix: slow query) @deprecated def set_balance_value_from_bundle(self) -> None: From c1fe8f600070e1db74eed4d7c9fda3d44c1c7eac Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 6 Apr 2025 21:32:17 +0530 Subject: [PATCH 24/62] fix: pos closed dialog on pos closing entry (backport #46881) (#46882) fix: pos closed dialog on pos closing entry (#46881) (cherry picked from commit 21954b9f9cafc966ed0cd13fe6d7a6c4501d0d5b) Co-authored-by: Diptanil Saha --- erpnext/selling/page/point_of_sale/pos_controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index d77381bf309..d86bf92177f 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -151,7 +151,8 @@ erpnext.PointOfSale.Controller = class { }); frappe.realtime.on(`poe_${this.pos_opening}_closed`, (data) => { - if (data) { + const route = frappe.get_route_str(); + if (data && route == "point-of-sale") { frappe.dom.freeze(); frappe.msgprint({ title: __("POS Closed"), From ab52524f12a2e937e3e8d8ec33851d363bf3a390 Mon Sep 17 00:00:00 2001 From: rethik Date: Wed, 26 Mar 2025 11:10:08 +0530 Subject: [PATCH 25/62] fix: use docstatus for status filter (cherry picked from commit 31e59354c9b4425612cd9bb1a4e260f6eab13b01) --- .../purchase_order/purchase_order_list.js | 20 +++++++++++++++---- .../doctype/sales_order/sales_order_list.js | 12 +++++++++-- .../delivery_note/delivery_note_list.js | 4 ++-- .../material_request/material_request_list.js | 2 +- .../purchase_receipt/purchase_receipt_list.js | 8 ++++---- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_list.js b/erpnext/buying/doctype/purchase_order/purchase_order_list.js index 3c357c0a933..8dc84e23816 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order_list.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order_list.js @@ -22,15 +22,27 @@ frappe.listview_settings["Purchase Order"] = { return [ __("To Receive and Bill"), "orange", - "per_received,<,100|per_billed,<,100|status,!=,Closed", + "per_received,<,100|per_billed,<,100|status,!=,Closed|docstatus,=,1", ]; } else { - return [__("To Receive"), "orange", "per_received,<,100|per_billed,=,100|status,!=,Closed"]; + return [ + __("To Receive"), + "orange", + "per_received,<,100|per_billed,=,100|status,!=,Closed|docstatus,=,1", + ]; } } else if (flt(doc.per_received) >= 100 && flt(doc.per_billed) < 100 && doc.status !== "Closed") { - return [__("To Bill"), "orange", "per_received,=,100|per_billed,<,100|status,!=,Closed"]; + return [ + __("To Bill"), + "orange", + "per_received,=,100|per_billed,<,100|status,!=,Closed|docstatus,=,1", + ]; } else if (flt(doc.per_received) >= 100 && flt(doc.per_billed) == 100 && doc.status !== "Closed") { - return [__("Completed"), "green", "per_received,=,100|per_billed,=,100|status,!=,Closed"]; + return [ + __("Completed"), + "green", + "per_received,=,100|per_billed,=,100|status,!=,Closed|docstatus,=,1", + ]; } }, onload: function (listview) { diff --git a/erpnext/selling/doctype/sales_order/sales_order_list.js b/erpnext/selling/doctype/sales_order/sales_order_list.js index c9bd4fc0f9d..6bad47c2693 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_list.js +++ b/erpnext/selling/doctype/sales_order/sales_order_list.js @@ -23,10 +23,18 @@ frappe.listview_settings["Sales Order"] = { } else if (!doc.skip_delivery_note && flt(doc.per_delivered) < 100) { if (frappe.datetime.get_diff(doc.delivery_date) < 0) { // not delivered & overdue - return [__("Overdue"), "red", "per_delivered,<,100|delivery_date,<,Today|status,!=,Closed"]; + return [ + __("Overdue"), + "red", + "per_delivered,<,100|delivery_date,<,Today|status,!=,Closed|docstatus,=,1", + ]; } else if (flt(doc.grand_total) === 0) { // not delivered (zeroount order) - return [__("To Deliver"), "orange", "per_delivered,<,100|grand_total,=,0|status,!=,Closed"]; + return [ + __("To Deliver"), + "orange", + "per_delivered,<,100|grand_total,=,0|status,!=,Closed|docstatus,=,1", + ]; } else if (flt(doc.per_billed) < 100) { // not delivered & not billed return [ diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js index dd09f6cfcf5..af40fd6a8a2 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js @@ -19,9 +19,9 @@ frappe.listview_settings["Delivery Note"] = { } else if (doc.status === "Return Issued") { return [__("Return Issued"), "grey", "status,=,Return Issued"]; } else if (flt(doc.per_billed, 2) < 100) { - return [__("To Bill"), "orange", "per_billed,<,100"]; + return [__("To Bill"), "orange", "per_billed,<,100|docstatus,=,1"]; } else if (flt(doc.per_billed, 2) === 100) { - return [__("Completed"), "green", "per_billed,=,100"]; + return [__("Completed"), "green", "per_billed,=,100|docstatus,=,1"]; } }, onload: function (doclist) { diff --git a/erpnext/stock/doctype/material_request/material_request_list.js b/erpnext/stock/doctype/material_request/material_request_list.js index 57332aa7730..b20ac15f136 100644 --- a/erpnext/stock/doctype/material_request/material_request_list.js +++ b/erpnext/stock/doctype/material_request/material_request_list.js @@ -13,7 +13,7 @@ frappe.listview_settings["Material Request"] = { return [__("Completed"), "green"]; } } else if (doc.docstatus == 1 && flt(doc.per_ordered, precision) == 0) { - return [__("Pending"), "orange", "per_ordered,=,0"]; + return [__("Pending"), "orange", "per_ordered,=,0|docstatus,=,1"]; } else if ( doc.docstatus == 1 && flt(doc.per_ordered, precision) < 100 && diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js index fc4aabdaa18..e295127e6fc 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js @@ -16,13 +16,13 @@ frappe.listview_settings["Purchase Receipt"] = { } else if (doc.status === "Closed") { return [__("Closed"), "green", "status,=,Closed"]; } else if (flt(doc.per_returned, 2) === 100) { - return [__("Return Issued"), "grey", "per_returned,=,100"]; + return [__("Return Issued"), "grey", "per_returned,=,100|docstatus,=,1"]; } else if (flt(doc.grand_total) !== 0 && flt(doc.per_billed, 2) == 0) { - return [__("To Bill"), "orange", "per_billed,<,100"]; + return [__("To Bill"), "orange", "per_billed,<,100|docstatus,=,1"]; } else if (flt(doc.per_billed, 2) > 0 && flt(doc.per_billed, 2) < 100) { - return [__("Partly Billed"), "yellow", "per_billed,<,100"]; + return [__("Partly Billed"), "yellow", "per_billed,<,100|docstatus,=,1"]; } else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) === 100) { - return [__("Completed"), "green", "per_billed,=,100"]; + return [__("Completed"), "green", "per_billed,=,100|docstatus,=,1"]; } }, From 13f1afa14127cb3fb2e3772857ffb24787591b26 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 1 Apr 2025 14:15:47 +0530 Subject: [PATCH 26/62] fix: remove all serial/batch fields when use button is unselected (cherry picked from commit 22ffdb9e7704d022b8347fff38d4f34140045835) --- erpnext/public/js/controllers/transaction.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8b7487c612e..da3c1ec6d33 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -251,6 +251,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } } + use_serial_batch_fields(frm, cdt, cdn) { + const item = locals[cdt][cdn]; + if (!item.use_serial_batch_fields) { + frappe.model.set_value(cdt, cdn, "serial_no", ""); + frappe.model.set_value(cdt, cdn, "batch_no", ""); + frappe.model.set_value(cdt, cdn, "rejected_serial_no", ""); + } + } + set_fields_onload_for_line_item() { if (this.frm.is_new() && this.frm.doc?.items) { this.frm.doc.items.forEach(item => { From 26f93f57b8e488fb8d340631021f7663948734e7 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Thu, 27 Mar 2025 01:04:38 +0530 Subject: [PATCH 27/62] fix: include auto_reconcile_vouchers flag in background job (cherry picked from commit 35fbbc20576fa9533cb7862b11b1ff7c949e61e4) --- .../bank_reconciliation_tool/bank_reconciliation_tool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index d47cc7b5968..7f97c3677bd 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -374,8 +374,6 @@ def auto_reconcile_vouchers( from_reference_date=None, to_reference_date=None, ): - frappe.flags.auto_reconcile_vouchers = True - bank_transactions = get_bank_transactions(bank_account) if len(bank_transactions) > 10: @@ -404,6 +402,8 @@ def auto_reconcile_vouchers( def start_auto_reconcile( bank_transactions, from_date, to_date, filter_by_reference_date, from_reference_date, to_reference_date ): + frappe.flags.auto_reconcile_vouchers = True + reconciled, partially_reconciled = set(), set() for transaction in bank_transactions: linked_payments = get_linked_payments( From e2c8ed2afdc2cb169003cce03665ba72528709eb Mon Sep 17 00:00:00 2001 From: MohsinAli Date: Sat, 29 Mar 2025 11:03:24 +0530 Subject: [PATCH 28/62] fix: correct mapping(schedule_date) sales order to material request (cherry picked from commit 732e9502651256999e33cb2e1accb69cbc5f25ac) --- erpnext/selling/doctype/sales_order/sales_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 4a026395425..220dafb2e45 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -875,7 +875,7 @@ def make_material_request(source_name, target_doc=None): "field_map": { "name": "sales_order_item", "parent": "sales_order", - "delivery_date": "required_by", + "delivery_date": "schedule_date", "bom_no": "bom_no", }, "condition": lambda item: not frappe.db.exists( From 1b9980bb86225213949e3db2681ece877c23b7fd Mon Sep 17 00:00:00 2001 From: venkat102 Date: Fri, 21 Mar 2025 13:17:32 +0530 Subject: [PATCH 29/62] fix(payment term): allocate payment amount when payment term is fetched from order (cherry picked from commit 5618859bd8e7a11f6bdf3a9be123c2d742aaa343) --- erpnext/controllers/accounts_controller.py | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2e90c446ade..184c0792dda 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2328,7 +2328,7 @@ class AccountsController(TransactionBase): and automatically_fetch_payment_terms and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype) ): - self.fetch_payment_terms_from_order(po_or_so, doctype) + self.fetch_payment_terms_from_order(po_or_so, doctype, grand_total, base_grand_total) if self.get("payment_terms_template"): self.ignore_default_payment_terms_template = 1 elif self.get("payment_terms_template"): @@ -2372,7 +2372,7 @@ class AccountsController(TransactionBase): d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount") ) else: - self.fetch_payment_terms_from_order(po_or_so, doctype) + self.fetch_payment_terms_from_order(po_or_so, doctype, grand_total, base_grand_total) self.ignore_default_payment_terms_template = 1 def get_order_details(self): @@ -2410,7 +2410,7 @@ class AccountsController(TransactionBase): def linked_order_has_payment_schedule(self, po_or_so): return frappe.get_all("Payment Schedule", filters={"parent": po_or_so}) - def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype): + def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype, grand_total, base_grand_total): """ Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice. """ @@ -2426,12 +2426,25 @@ class AccountsController(TransactionBase): "invoice_portion": schedule.invoice_portion, "mode_of_payment": schedule.mode_of_payment, "description": schedule.description, - "payment_amount": schedule.payment_amount, - "base_payment_amount": schedule.base_payment_amount, - "outstanding": schedule.outstanding, "paid_amount": schedule.paid_amount, } + if payment_schedule["invoice_portion"]: + payment_schedule["payment_amount"] = flt( + grand_total * flt(payment_schedule["invoice_portion"]) / 100, + schedule.precision("payment_amount"), + ) + payment_schedule["base_payment_amount"] = flt( + base_grand_total * flt(payment_schedule["invoice_portion"]) / 100, + schedule.precision("base_payment_amount"), + ) + payment_schedule["outstanding"] = payment_schedule["payment_amount"] + else: + payment_schedule["base_payment_amount"] = flt( + schedule.base_payment_amount * self.get("conversion_rate"), + schedule.precision("base_payment_amount"), + ) + if schedule.discount_type == "Percentage": payment_schedule["discount_type"] = schedule.discount_type payment_schedule["discount"] = schedule.discount From 3b66c4847964879313b22660a2e0f3071c1c2dd1 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Fri, 21 Mar 2025 13:18:19 +0530 Subject: [PATCH 30/62] test: validate payment schedule based on invoice amount (cherry picked from commit 77852965736add87613d2c65a4fee27de8d681f8) --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index b8930963144..084a262a890 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2094,7 +2094,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): 1, ) pi = make_pi_from_pr(pr.name) - self.assertEqual(pi.payment_schedule[0].payment_amount, 2500) + self.assertEqual(pi.payment_schedule[0].payment_amount, 1000) automatically_fetch_payment_terms(enable=0) frappe.db.set_value( From ea289a40fb4656bbac9ba7cc49e62a1caca4bd38 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Thu, 27 Mar 2025 22:28:20 +0530 Subject: [PATCH 31/62] fix: update payment amount if automatically_fetch_payment_terms is enabled (cherry picked from commit 7bf1a39861e2841c04ea7818de6d03028a095fee) --- erpnext/controllers/accounts_controller.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 184c0792dda..bcaf8f897f9 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2328,7 +2328,9 @@ class AccountsController(TransactionBase): and automatically_fetch_payment_terms and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype) ): - self.fetch_payment_terms_from_order(po_or_so, doctype, grand_total, base_grand_total) + self.fetch_payment_terms_from_order( + po_or_so, doctype, grand_total, base_grand_total, automatically_fetch_payment_terms + ) if self.get("payment_terms_template"): self.ignore_default_payment_terms_template = 1 elif self.get("payment_terms_template"): @@ -2372,7 +2374,9 @@ class AccountsController(TransactionBase): d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount") ) else: - self.fetch_payment_terms_from_order(po_or_so, doctype, grand_total, base_grand_total) + self.fetch_payment_terms_from_order( + po_or_so, doctype, grand_total, base_grand_total, automatically_fetch_payment_terms + ) self.ignore_default_payment_terms_template = 1 def get_order_details(self): @@ -2410,7 +2414,9 @@ class AccountsController(TransactionBase): def linked_order_has_payment_schedule(self, po_or_so): return frappe.get_all("Payment Schedule", filters={"parent": po_or_so}) - def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype, grand_total, base_grand_total): + def fetch_payment_terms_from_order( + self, po_or_so, po_or_so_doctype, grand_total, base_grand_total, automatically_fetch_payment_terms + ): """ Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice. """ @@ -2429,7 +2435,7 @@ class AccountsController(TransactionBase): "paid_amount": schedule.paid_amount, } - if payment_schedule["invoice_portion"]: + if automatically_fetch_payment_terms: payment_schedule["payment_amount"] = flt( grand_total * flt(payment_schedule["invoice_portion"]) / 100, schedule.precision("payment_amount"), From 3d57916832cd3bfbe3e6dd7d35f7a184d2d8921c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 7 Apr 2025 11:28:39 +0530 Subject: [PATCH 32/62] chore: resolve conflict --- erpnext/accounts/workspace/accounting/accounting.json | 4 ---- erpnext/buying/workspace/buying/buying.json | 4 ---- .../workspace/manufacturing/manufacturing.json | 4 ---- erpnext/projects/workspace/projects/projects.json | 4 ---- erpnext/selling/workspace/selling/selling.json | 9 --------- erpnext/stock/workspace/stock/stock.json | 4 ---- 6 files changed, 29 deletions(-) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index ed081102aa3..c6a7fc6d84c 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -621,11 +621,7 @@ "doc_view": "List", "label": "Learn Accounting", "type": "URL", -<<<<<<< HEAD - "url": "https://frappe.school/courses/erpnext-accounting?utm_source=in_app" -======= "url": "https://school.frappe.io/lms/courses/erpnext-accounting?utm_source=in_app" ->>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "label": "Chart of Accounts", diff --git a/erpnext/buying/workspace/buying/buying.json b/erpnext/buying/workspace/buying/buying.json index 54f44ea13da..db5c7d046db 100644 --- a/erpnext/buying/workspace/buying/buying.json +++ b/erpnext/buying/workspace/buying/buying.json @@ -537,11 +537,7 @@ "doc_view": "List", "label": "Learn Procurement", "type": "URL", -<<<<<<< HEAD - "url": "https://frappe.school/courses/procurement?utm_source=in_app" -======= "url": "https://school.frappe.io/lms/courses/procurement?utm_source=in_app" ->>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "color": "Yellow", diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json index 08befc61ef0..320db68e037 100644 --- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json @@ -336,11 +336,7 @@ "doc_view": "List", "label": "Learn Manufacturing", "type": "URL", -<<<<<<< HEAD - "url": "https://frappe.school/courses/manufacturing?utm_source=in_app" -======= "url": "https://school.frappe.io/lms/courses/manufacturing?utm_source=in_app" ->>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "color": "Grey", diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index 9effb1cbda6..19c94bc8f90 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -210,11 +210,7 @@ "doc_view": "List", "label": "Learn Project Management", "type": "URL", -<<<<<<< HEAD - "url": "https://frappe.school/courses/project-management?utm_source=in_app" -======= "url": "https://school.frappe.io/lms/courses/project-management?utm_source=in_app" ->>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "color": "Blue", diff --git a/erpnext/selling/workspace/selling/selling.json b/erpnext/selling/workspace/selling/selling.json index a392574e15c..d17b8438eae 100644 --- a/erpnext/selling/workspace/selling/selling.json +++ b/erpnext/selling/workspace/selling/selling.json @@ -639,11 +639,7 @@ "doc_view": "List", "label": "Learn Sales Management", "type": "URL", -<<<<<<< HEAD - "url": "https://frappe.school/courses/sales-management-course?utm_source=in_app" -======= "url": "https://school.frappe.io/lms/courses/sales-management-course?utm_source=in_app" ->>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "label": "Point of Sale", @@ -680,11 +676,6 @@ "type": "Dashboard" } ], -<<<<<<< HEAD - "title": "Selling" -} -======= "title": "Selling", "type": "Workspace" } ->>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json index 9a9e3d9e5c3..2f8435ac702 100644 --- a/erpnext/stock/workspace/stock/stock.json +++ b/erpnext/stock/workspace/stock/stock.json @@ -802,11 +802,7 @@ "doc_view": "List", "label": "Learn Inventory Management", "type": "URL", -<<<<<<< HEAD - "url": "https://frappe.school/courses/inventory-management?utm_source=in_app" -======= "url": "https://school.frappe.io/lms/courses/inventory-management?utm_source=in_app" ->>>>>>> ef4f662c31 (chore: update links to Frappe School (#46823)) }, { "color": "Yellow", From 2bf44dc326ea285bd9a65daa7ea6a7269ac8629d Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 26 Mar 2025 11:37:35 +0530 Subject: [PATCH 33/62] fix: update posting date before running validations (cherry picked from commit d04dbd8ed92f2c4be34657dc03476d310ecab604) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 673a65dae55..a57e313e2e9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -267,8 +267,8 @@ class SalesInvoice(SellingController): self.indicator_title = _("Paid") def validate(self): - super().validate() self.validate_auto_set_posting_time() + super().validate() if not (self.is_pos or self.is_debit_note): self.so_dn_required() From ba1e7e17fbcbdb4aeff41cca8ee54bae7871b364 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 7 Apr 2025 16:28:52 +0530 Subject: [PATCH 34/62] fix: remove against_voucher from General Ledger Report (cherry picked from commit 6d1f119a0faebdd0945aa565ef0c71b7a8484aa7) --- .../report/general_ledger/general_ledger.js | 5 ----- .../report/general_ledger/general_ledger.py | 14 -------------- 2 files changed, 19 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 54d6fb2e2f6..2f142fc76b2 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -52,11 +52,6 @@ frappe.query_reports["General Ledger"] = { frappe.query_report.set_filter_value("group_by", "Group by Voucher (Consolidated)"); }, }, - { - fieldname: "against_voucher_no", - label: __("Against Voucher No"), - fieldtype: "Data", - }, { fieldtype: "Break", }, diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index a62ba2e3732..73a16e730ac 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -224,9 +224,6 @@ def get_conditions(filters): if filters.get("voucher_no"): conditions.append("voucher_no=%(voucher_no)s") - if filters.get("against_voucher_no"): - conditions.append("against_voucher=%(against_voucher_no)s") - if filters.get("ignore_err"): err_journals = frappe.db.get_all( "Journal Entry", @@ -490,9 +487,6 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, tot data[key][rev_dr_or_cr] = 0 data[key][rev_dr_or_cr + "_in_account_currency"] = 0 - if data[key].against_voucher and gle.against_voucher: - data[key].against_voucher += ", " + gle.against_voucher - from_date, to_date = getdate(filters.from_date), getdate(filters.to_date) show_opening_entries = filters.get("show_opening_entries") @@ -695,14 +689,6 @@ def get_columns(filters): columns.extend( [ - {"label": _("Against Voucher Type"), "fieldname": "against_voucher_type", "width": 100}, - { - "label": _("Against Voucher"), - "fieldname": "against_voucher", - "fieldtype": "Dynamic Link", - "options": "against_voucher_type", - "width": 100, - }, {"label": _("Supplier Invoice No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 100}, ] ) From 95cc2827c6fa76bb84e859e75b974c1317ca7c69 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 7 Apr 2025 14:09:52 +0530 Subject: [PATCH 35/62] fix: empty party filter on change of party type in General Ledger Report. (cherry picked from commit 9c68bc22fafa8fa988c1ff5c13e1532d63ab87e4) --- erpnext/accounts/report/general_ledger/general_ledger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 2f142fc76b2..bcd850c0896 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -61,7 +61,7 @@ frappe.query_reports["General Ledger"] = { fieldtype: "Autocomplete", options: Object.keys(frappe.boot.party_account_types), on_change: function () { - frappe.query_report.set_filter_value("party", ""); + frappe.query_report.set_filter_value("party", []); }, }, { From 999ab28bf089aac0a0558c6cf149b14564b96bc6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:54:23 +0530 Subject: [PATCH 36/62] fix: validate if pos is opened before pos invoice creation (backport #46907) (#46910) fix: validate if pos is opened before pos invoice creation (#46907) * fix: validate if pos is opened before pos invoice creation * fix: added title on throw dialog * test: fixed failing test (cherry picked from commit 3de1b22480170bcb483dd18206e7f1688363de20) Co-authored-by: Diptanil Saha --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 13 +++++++++++++ .../doctype/pos_invoice/test_pos_invoice.py | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index a8a733ac42c..6933b04d2e1 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -196,6 +196,7 @@ class POSInvoice(SalesInvoice): # run on validate method of selling controller super(SalesInvoice, self).validate() + self.validate_pos_opening_entry() self.validate_auto_set_posting_time() self.validate_mode_of_payment() self.validate_uom_is_integer("stock_uom", "stock_qty") @@ -320,6 +321,18 @@ class POSInvoice(SalesInvoice): _("Payment related to {0} is not completed").format(pay.mode_of_payment) ) + def validate_pos_opening_entry(self): + opening_entries = frappe.get_list( + "POS Opening Entry", filters={"pos_profile": self.pos_profile, "status": "Open", "docstatus": 1} + ) + if len(opening_entries) == 0: + frappe.throw( + title=_("POS Opening Entry Missing"), + msg=_("No open POS Opening Entry found for POS Profile {0}.").format( + frappe.bold(self.pos_profile) + ), + ) + def validate_stock_availablility(self): if self.is_return: return diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 09c9443bdd9..cfe805be4ce 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -26,6 +26,12 @@ class TestPOSInvoice(unittest.TestCase): make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100) frappe.db.sql("delete from `tabTax Rule`") + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry + + cls.test_user, cls.pos_profile = init_user_and_profile() + create_opening_entry(cls.pos_profile, cls.test_user) + def tearDown(self): if frappe.session.user != "Administrator": frappe.set_user("Administrator") From 8b11d13cd4c3b155ebfebe7ad45dbb3e80500685 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:03:20 +0530 Subject: [PATCH 37/62] fix: pos opening entry's status not getting updated on cancel (backport #46909) (#46911) fix: pos opening entry's status not getting updated on cancel (#46909) (cherry picked from commit 6fae98afda0a428924a1347ff16675bc35562076) Co-authored-by: Diptanil Saha --- .../accounts/doctype/pos_opening_entry/pos_opening_entry.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py index 10b07c2c800..7f1890ceabf 100644 --- a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py +++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py @@ -70,3 +70,6 @@ class POSOpeningEntry(StatusUpdater): def on_submit(self): self.set_status(update=True) + + def on_cancel(self): + self.set_status(update=True) From 8276e8e8b37cc964ae8ba5b5f6448ec01a57c121 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Tue, 8 Apr 2025 02:39:04 +0200 Subject: [PATCH 38/62] chore: fix german translations (#46912) --- erpnext/translations/de.csv | 601 ++++++++++++++++++------------------ 1 file changed, 300 insertions(+), 301 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index a093970cbdb..f2d4e5443ed 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -508,9 +508,9 @@ Close Loan,Darlehen schließen, Close the POS,Schließen Sie die Kasse, Closed,Geschlossen, Closed order cannot be cancelled. Unclose to cancel.,Geschlosser Auftrag kann nicht abgebrochen werden. Bitte wiedereröffnen um abzubrechen., -Closing (Cr),Schlußstand (Haben), -Closing (Dr),Schlußstand (Soll), -Closing (Opening + Total),Schließen (Eröffnung + Gesamt), +Closing (Cr),Schlußstand (H), +Closing (Dr),Schlußstand (S), +Closing (Opening + Total),Schlußstand (Anfangssstand + Summe), Closing Account {0} must be of type Liability / Equity,Abschlußkonto {0} muss vom Typ Verbindlichkeiten/Eigenkapital sein, Closing Balance,Schlussbilanz, Code,Code, @@ -598,7 +598,7 @@ Course Code: ,Kurscode:, Course Enrollment {0} does not exists,Die Kursanmeldung {0} existiert nicht, Course Schedule,Kurstermine, Course: ,Kurs:, -Cr,Haben, +Cr,H, Create,Erstellen, Create BOM,Stückliste anlegen, Create Delivery Trip,Erstelle Auslieferungsfahrt, @@ -647,8 +647,8 @@ Creating Fees,Gebühren anlegen, Creating student groups,Erstelle Studentengruppen, Creating {0} Invoice,{0} Rechnung erstellen, Credit,Haben, -Credit ({0}),Guthaben ({0}), -Credit Account,Guthabenkonto, +Credit ({0}),Haben ({0}), +Credit Account,Haben-Konto, Credit Balance,Verfügbarer Kredit, Credit Card,Kreditkarte, Credit Days cannot be a negative number,Kredit-Tage können keine negative Zahl sein, @@ -712,14 +712,14 @@ Date of Transaction,Datum der Transaktion, Day,Tag, Debit,Soll, Debit ({0}),Soll ({0}), -Debit Account,Sollkonto, +Debit Account,Soll-Konto, Debit Note,Lastschrift, Debit Note Amount,Lastschriftbetrag, Debit Note Issued,Lastschrift ausgestellt am, -Debit To is required,Debit Um erforderlich, +Debit To is required,Forderungskonto ist erforderlich, Debit and Credit not equal for {0} #{1}. Difference is {2}.,Soll und Haben nicht gleich für {0} #{1}. Unterschied ist {2}., -Debtors,Schuldner, -Debtors ({0}),Schuldnern ({0}), +Debtors,Debitoren, +Debtors ({0}),Debitoren ({0}), Declare Lost,Für verloren erklären, Default Activity Cost exists for Activity Type - {0},Es gibt Standard-Aktivitätskosten für Aktivitätsart - {0}, Default BOM ({0}) must be active for this item or its template,Standardstückliste ({0}) muss für diesen Artikel oder dessen Vorlage aktiv sein, @@ -1628,7 +1628,7 @@ Open Item {0},Offene-Posten {0}, Open Notifications,Offene Benachrichtigungen, Open Orders,Offene Bestellungen, Open a new ticket,Öffnen Sie ein neues Ticket, -Opening,Eröffnung, +Opening,Anfangssstand, Opening (Cr),Anfangssstand (Haben), Opening (Dr),Anfangsstand (Soll), Opening Accounting Balance,Eröffnungsbilanz, @@ -2215,8 +2215,8 @@ Retained Earnings,Gewinnrücklagen, Retention Stock Entry,Vorratsbestandseintrag, Retention Stock Entry already created or Sample Quantity not provided,Aufbewahrungsbestandseintrag bereits angelegt oder Musterbestand nicht bereitgestellt, Return,Zurück, -Return / Credit Note,Return / Gutschrift, -Return / Debit Note,Return / Lastschrift, +Return / Credit Note,Rückgabe / Gutschrift, +Return / Debit Note,Rückgabe / Lastschrift, Returns,Retouren, Reverse Journal Entry,Buchungssatz umkehren, Review Invitation Sent,Einladung überprüfen gesendet, @@ -2826,7 +2826,7 @@ To {0} | {1} {2},An {0} | {1} {2}, Toggle Filters,Filter umschalten, Too many columns. Export the report and print it using a spreadsheet application.,Zu viele Spalten. Exportieren Sie den Bericht und drucken Sie ihn mit einem Tabellenkalkulationsprogramm aus., Tools,Werkzeuge, -Total (Credit),Insgesamt (Credit), +Total (Credit),Insgesamt (Haben), Total (Without Tax),Summe (ohne Steuern), Total Achieved,Gesamtsumme erreicht, Total Actual,Summe Tatsächlich, @@ -2837,7 +2837,7 @@ Total Budget,Gesamtbudget; Gesamtetat, Total Collected: {0},Gesammelt gesammelt: {0}, Total Commission,Gesamtprovision, Total Contribution Amount: {0},Gesamtbeitragsbetrag: {0}, -Total Credit/ Debit Amount should be same as linked Journal Entry,Der Gesamtkreditbetrag sollte identisch mit dem verknüpften Buchungssatz sein, +Total Credit/ Debit Amount should be same as linked Journal Entry,Der gesamte Soll-/ Habenbetrag sollte identisch mit dem verknüpften Buchungssatz sein, Total Debit must be equal to Total Credit. The difference is {0},Gesamt-Soll muss gleich Gesamt-Haben sein. Die Differenz ist {0}, Total Invoiced Amount,Gesamtrechnungsbetrag, Total Order Considered,Geschätzte Summe der Bestellungen, @@ -2915,7 +2915,7 @@ Unable to find score starting at {0}. You need to have standing scores covering Unable to find variable: ,Variable kann nicht gefunden werden:, Unblock Invoice,Rechnung entsperren, Uncheck all,Alle abwählen, -Unclosed Fiscal Years Profit / Loss (Credit),Offener Gewinn / Verlust (Kredit) des Geschäftsjahres, +Unclosed Fiscal Years Profit / Loss (Credit),Offener Gewinn / Verlust (Haben) des Geschäftsjahres, Unit,Einheit, Unit of Measure,Maßeinheit, Unit of Measure {0} has been entered more than once in Conversion Factor Table,Die Mengeneinheit {0} wurde mehr als einmal in die Umrechnungsfaktortabelle eingetragen., @@ -3185,7 +3185,7 @@ on,Am, {0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.,"{0} {1}: Kostenstelle ist erforderlich für ""Gewinn- und Verlust"" Konto {2}. Bitte erstellen Sie eine Standard-Kostenstelle für das Unternehmen.", {0} {1}: Cost Center {2} does not belong to Company {3},{0} {1}: Kostenstelle {2} gehört nicht zu Unternehmen {3}, {0} {1}: Customer is required against Receivable account {2},{0} {1}: Für das Eingangskonto {2} ist ein Kunde erforderlich, -{0} {1}: Either debit or credit amount is required for {2},{0} {1}: Debit- oder Kreditbetrag ist für {2} erforderlich, +{0} {1}: Either debit or credit amount is required for {2},{0} {1}: Soll- oder Habenbetrag ist für {2} erforderlich, {0} {1}: Supplier is required against Payable account {2},{0} {1}: Für das Kreditorenkonto ist ein Lieferant erforderlich {2}, {0}% Billed,{0}% berechnet, {0}% Delivered,{0}% geliefert, @@ -3408,7 +3408,7 @@ Do you want to submit the material request,Möchten Sie die Materialanfrage einr Doctype,DocType, Document {0} successfully uncleared,Dokument {0} wurde nicht erfolgreich gelöscht, Download Template,Vorlage herunterladen, -Dr,Soll, +Dr,S, Due Date,Fälligkeitsdatum, Duplicate,Duplizieren, Duplicate Project with Tasks,Projekt mit Aufgaben duplizieren, @@ -3889,7 +3889,7 @@ Operation Id,Arbeitsgang-ID, Partially ordered,teilweise geordnete, Please select company first,Bitte wählen Sie zuerst die Firma aus, Please select patient,Bitte wählen Sie Patient, -Printed On ,Gedruckt auf, +Printed On ,Gedruckt am, Projected qty,Geplante Menge, Sales person,Vertriebsmitarbeiter, Serial No {0} Created,Seriennummer {0} Erstellt, @@ -4338,9 +4338,9 @@ Auto Created,Automatisch erstellt, Stock User,Lager-Benutzer, Fiscal Year Company,Geschäftsjahr Unternehmen, Debit Amount,Soll-Betrag, -Credit Amount,Guthaben-Summe, +Credit Amount,Haben-Betrag, Debit Amount in Account Currency,Soll-Betrag in Kontowährung, -Credit Amount in Account Currency,(Gut)Haben-Betrag in Kontowährung, +Credit Amount in Account Currency,Haben-Betrag in Kontowährung, Voucher Detail No,Belegdetail-Nr., Is Opening,Ist Eröffnungsbuchung, Is Advance,Ist Anzahlung, @@ -5699,13 +5699,13 @@ Is Master Data Processed,Werden Stammdaten verarbeitet?, Is Master Data Imported,Werden Stammdaten importiert?, Tally Creditors Account,Tally Gläubigerkonto, Creditors Account set in Tally,Gläubigerkonto in Tally eingestellt, -Tally Debtors Account,Tally Debtors Account, +Tally Debtors Account,Tally Debitorenkonto, Debtors Account set in Tally,Debitorenkonto in Tally eingestellt, -Tally Company,Tally Company, -Company Name as per Imported Tally Data,Firmenname gemäß Imported Tally Data, +Tally Company,Tally Unternehmen, +Company Name as per Imported Tally Data,Firmenname gemäß Importierten Tally-Daten, Default UOM,Standard-UOM, UOM in case unspecified in imported data,"UOM für den Fall, dass in importierten Daten nicht angegeben", -ERPNext Company,ERPNext Company, +ERPNext Company,ERPNext Unternehmen, Your Company set in ERPNext,Ihr Unternehmen in ERPNext eingestellt, Processed Files,Verarbeitete Dateien, Parties,Parteien, @@ -8845,7 +8845,6 @@ Column {0},Spalte {0}, Field Mapping,Feldzuordnung, Not Specified,Keine Angabe, Update Type,Aktualisierungsart, -Dr,Soll, End Time,Endzeit, Fetching...,Abrufen ..., "It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.","Es scheint, dass ein Problem mit der Stripe-Konfiguration des Servers vorliegt. Im Falle eines Fehlers wird der Betrag Ihrem Konto gutgeschrieben.", @@ -11829,113 +11828,113 @@ will be,wird sein, {} is a child company.,{} ist ein untergeordnetes Unternehmen., {} {} is already linked with another {},{} {} ist bereits mit einem anderen {} verknüpft, {} {} is already linked with {} {},{} {} ist bereits mit {} {} verknüpft, -A Transaction Deletion Document: {0} is triggered for {0},Eine Transaktion Löschungsdokument: {0} wird für {0} ausgelöst, -About Us Settings,"Einstellungen zu ""Über uns""", -Allow Internal Transfers at Arm's Length Price,Interne Übertragungen zum Fremdvergleichspreis zulassen, -Asset decapitalized after Asset Capitalization {0} was submitted,"Vermögenswert freigegeben, nachdem Anlagenaktivierung {0} gebucht wurde", -Auto Email Report,Auto Email-Bericht, -Auto close Opportunity Replied after the no. of days mentioned above,Automatische Schließungschaltung antwortete nach der oben genannten Anzahl von Tagen, -Avg Rate (Balance Stock),Durchschnittliche Rate (Lagerbestand), -Billing Interval in Subscription Plan must be Month to follow calendar months,"Abrechnungsintervall im Abonnementplan muss ""Monat"" sein, um Kalendermonate zu folgen", -Bulk Update,Massen-Update, -Can't disable batch wise valuation for active batches.,Sie können die chargenweise Bewertung für aktive Chargen nicht deaktivieren., -Can't disable batch wise valuation for items with FIFO valuation method.,Sie können die chargenweise Bewertung für Artikel mit FIFO-Bewertungsmethode nicht deaktivieren., -Cannot disable batch wise valuation for FIFO valuation method.,Sie können die chargenweise Bewertung für die FIFO-Bewertungsmethode nicht deaktivieren., -Cannot enqueue multi docs for one company. {0} is already queued/running for company: {1},Mehrere Dokumente für ein Unternehmen können nicht in die Warteschlange gestellt werden. {0} ist bereits in die Warteschlange gestellt/wird für das Unternehmen ausgeführt: {1}, -Contact Us Settings,Einstellungen zu „Kontaktieren Sie uns“, -Create Journal Entries,Buchungssätze erstellen, -Create a variant with the template image.,Eine Variante mit dem Vorlagenbild erstellen., -Create in Draft Status,In Entwurfsstatus erstellen, -Custom delimiters,Benutzerdefinierte Trennzeichen, -Deleted Documents,Gelöschte Dokumente, -Delimiter options,Trennzeichenoptionen, -Dependent Task {0} is not a Template Task,Abhängige Aufgabe {0} ist keine Vorlage einer Aufgabe, -Depreciation Entry Posting Status,Buchungsstatus des Abschreibungseintrags, -Depreciation Schedule View,Ansicht Abschreibungsplan, -Depreciation cannot be calculated for fully depreciated assets,Für vollständig abgeschriebene Vermögensgegenstände kann keine Abschreibung berechnet werden, -Do Not Use Batch-wise Valuation,Keine chargenweise Bewertung verwenden, -Domain Settings,Domäneneinstellungen, -Email Domain,E-Mail-Domain, -Enable Immutable Ledger,Unveränderliches Hauptbuch aktivieren, -Enable it if users want to consider rejected materials to dispatch.,"Aktivieren Sie diese Option, wenn Benutzer zurückgewiesenes Material für den Versand berücksichtigen möchten.", -Excess Materials Consumed,Überschüssige Materialien verbraucht, -Excess Transfer,Überschuss-Übertragung, -FIFO Queue vs Qty After Transaction Comparison,Vergleich zwischen FIFO-Warteschlange und Menge nach Transaktion, -"For the {0}, the quantity is required to make the return entry","Für die {0} ist die Menge erforderlich, um die Retoure zu erstellen", -"If enabled, the item rate won't adjust to the valuation rate during internal transfers, but accounting will still use the valuation rate.","Falls aktiviert, wird der Artikelkurs bei internen Transfers nicht an den Bewertungskurs angepasst, aber die Buchhaltung verwendet weiterhin den Wertansatz.", -"If enabled, the system will use the moving average valuation method to calculate the valuation rate for the batched items and will not consider the individual batch-wise incoming rate.","Falls aktiviert, verwendet das System die Bewertungsmethode des gleitenden Durchschnitts zur Berechnung des Wertansatzes für die chargenweisen Artikel und berücksichtigt nicht den individuellen chargenweisen Eingangskurs.", -Job Worker,Unterauftragnehmer, -Job Worker Address,Unterauftragnehmer Adresse, -Job Worker Address Details,Vorschau Adresse Unterauftragnehmer, -Job Worker Contact,Vertrag des Unterauftragnehmers, -Job Worker Delivery Note,Lieferschein des Unterauftragnehmers, -Job Worker Name,Name des Unterauftragnehmer, -Job Worker Warehouse,Lagerhaus des Unterauftragnehmers, -"Learn about Common Party","Erfahren Sie mehr über die Verknüpfung von Kunden und Lieferanten", -Notification,Benachrichtigung, -Notification Settings,Benachrichtigungseinstellungen, -Offsetting for Accounting Dimension,Verrechnung für Buchhaltungsdimension, -Only Include Allocated Payments,Nur zugeordnete Zahlungen einbeziehen, -Only one {0} entry can be created against the Work Order {1},Nur ein {0} Eintrag kann gegen den Arbeitsauftrag {1} erstellt werden, -Over Picking Allowance,Überkommissionierzugabe, -Over Transfer Allowance,Überschlusstransferzugabe, -Overbilling of {} ignored because you have {} role.,"Überhöhte Abrechnung von {} wurde ignoriert, weil Sie die Rolle {} haben.", -Payment Reconciliation Job: {0} is running for this party. Can't reconcile now.,Job für Zahlungsabgleich: {0} läuft für diese Partei. Kann jetzt nicht abgleichen., -Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.,"Eine Zahlungsanforderung, die aus einem Auftrag oder einer Bestellung erstellt wurde, wird im Entwurfsstatus sein. Wenn deaktiviert, wird das Dokument in ungespeichertem Zustand sein.", -Payment Request took too long to respond. Please try requesting for payment again.,"Zahlungsaufforderung hat zu lange gedauert, um zu antworten. Bitte versuchen Sie die Zahlung erneut anzufragen.", -Payment Terms Status for Sales Order,Status für Zahlungsbedingungen für Aufträge, -Pipeline By,Pipeline von, -Please enable Use Old Serial / Batch Fields to make_bundle,"Bitte aktivieren Sie ""Alte Serien-/Batchfelder verwenden"" für make_bundle", -Print Style,Druckstil, -Reconcile All Serial Nos / Batches,Alle Seriennummern/Chargen abgleichen, -Reset Company Default Values,Standardwerte des Unternehmens zurücksetzen, -Reset Raw Materials Table,Tabelle Rohstoffe zurücksetzen, -Return Against Subcontracting Receipt,Retoure gegen Unterauftragsbeleg, -Return Components,Komponenten zurückgeben, -Returned Against,Zurückgegeben gegen, -Returned exchange rate is neither integer not float.,Der zurückgegebene Wechselkurs ist weder Integer noch Float., -Round Off Tax Amount,Steuerbetrag abrunden, -Rounding Loss Allowance,Rundungsverlusttoleranz, -Rounding Loss Allowance should be between 0 and 1,Rundungsverlusttoleranz muss zwischen 0 und 1 sein, -Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1},Zeile #{0}: Ausschusslager ist für den abgelehnten Artikel {1} obligatorisch, -Row #{0}: You cannot use the inventory dimension '{1}' in Stock Reconciliation to modify the quantity or valuation rate. Stock reconciliation with inventory dimensions is intended solely for performing opening entries.,"Zeile #{0}: Sie können die Bestandsdimension '{1}' in der Bestandsabgleich nicht verwenden, um die Menge oder den Wertansatz zu ändern. Die Bestandsabgleich mit Bestandsdimensionen ist ausschließlich für die Durchführung von Eröffnungsbuchungen vorgesehen.", -Row {0}: Packed Qty must be equal to {1} Qty.,Zeile {0}: Verpackte Menge muss gleich der {1} Menge sein., -Row {0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations,Zeile {0}: Die Gesamtzahl der Abschreibungen kann nicht kleiner oder gleich der Anzahl der gebuchten Abschreibungen zu Beginn sein, -SCO Supplied Item,Artikel beigestellt für Unterauftrag, -SLA Fulfilled On Status,SLA erfüllt am Status, -SLA will be applied if {1} is set as {2}{3},"SLA wird angewendet, wenn {1} als {2}{3} eingestellt ist", -SMS Settings,SMS-Einstellungen, -SO Total Qty,Kd.-Auftr.-Gesamtmenge, -"Sales Order {0} already exists against Customer's Purchase Order {1}. To allow multiple Sales Orders, Enable {2} in {3}","Auftrag {0} existiert bereits für die Kundenbestellung {1}. Um mehrere Verkaufsaufträge zuzulassen, aktivieren Sie {2} in {3}", +A Transaction Deletion Document: {0} is triggered for {0},Eine Transaktion Löschungsdokument: {0} wird für {0} ausgelöst, +About Us Settings,"Einstellungen zu ""Über uns""", +Allow Internal Transfers at Arm's Length Price,Interne Übertragungen zum Fremdvergleichspreis zulassen, +Asset decapitalized after Asset Capitalization {0} was submitted,"Vermögenswert freigegeben, nachdem Anlagenaktivierung {0} gebucht wurde", +Auto Email Report,Auto Email-Bericht, +Auto close Opportunity Replied after the no. of days mentioned above,Automatische Schließungschaltung antwortete nach der oben genannten Anzahl von Tagen, +Avg Rate (Balance Stock),Durchschnittliche Rate (Lagerbestand), +Billing Interval in Subscription Plan must be Month to follow calendar months,"Abrechnungsintervall im Abonnementplan muss ""Monat"" sein, um Kalendermonate zu folgen", +Bulk Update,Massen-Update, +Can't disable batch wise valuation for active batches.,Sie können die chargenweise Bewertung für aktive Chargen nicht deaktivieren., +Can't disable batch wise valuation for items with FIFO valuation method.,Sie können die chargenweise Bewertung für Artikel mit FIFO-Bewertungsmethode nicht deaktivieren., +Cannot disable batch wise valuation for FIFO valuation method.,Sie können die chargenweise Bewertung für die FIFO-Bewertungsmethode nicht deaktivieren., +Cannot enqueue multi docs for one company. {0} is already queued/running for company: {1},Mehrere Dokumente für ein Unternehmen können nicht in die Warteschlange gestellt werden. {0} ist bereits in die Warteschlange gestellt/wird für das Unternehmen ausgeführt: {1}, +Contact Us Settings,Einstellungen zu „Kontaktieren Sie uns“, +Create Journal Entries,Buchungssätze erstellen, +Create a variant with the template image.,Eine Variante mit dem Vorlagenbild erstellen., +Create in Draft Status,In Entwurfsstatus erstellen, +Custom delimiters,Benutzerdefinierte Trennzeichen, +Deleted Documents,Gelöschte Dokumente, +Delimiter options,Trennzeichenoptionen, +Dependent Task {0} is not a Template Task,Abhängige Aufgabe {0} ist keine Vorlage einer Aufgabe, +Depreciation Entry Posting Status,Buchungsstatus des Abschreibungseintrags, +Depreciation Schedule View,Ansicht Abschreibungsplan, +Depreciation cannot be calculated for fully depreciated assets,Für vollständig abgeschriebene Vermögensgegenstände kann keine Abschreibung berechnet werden, +Do Not Use Batch-wise Valuation,Keine chargenweise Bewertung verwenden, +Domain Settings,Domäneneinstellungen, +Email Domain,E-Mail-Domain, +Enable Immutable Ledger,Unveränderliches Hauptbuch aktivieren, +Enable it if users want to consider rejected materials to dispatch.,"Aktivieren Sie diese Option, wenn Benutzer zurückgewiesenes Material für den Versand berücksichtigen möchten.", +Excess Materials Consumed,Überschüssige Materialien verbraucht, +Excess Transfer,Überschuss-Übertragung, +FIFO Queue vs Qty After Transaction Comparison,Vergleich zwischen FIFO-Warteschlange und Menge nach Transaktion, +"For the {0}, the quantity is required to make the return entry","Für die {0} ist die Menge erforderlich, um die Retoure zu erstellen", +"If enabled, the item rate won't adjust to the valuation rate during internal transfers, but accounting will still use the valuation rate.","Falls aktiviert, wird der Artikelkurs bei internen Transfers nicht an den Bewertungskurs angepasst, aber die Buchhaltung verwendet weiterhin den Wertansatz.", +"If enabled, the system will use the moving average valuation method to calculate the valuation rate for the batched items and will not consider the individual batch-wise incoming rate.","Falls aktiviert, verwendet das System die Bewertungsmethode des gleitenden Durchschnitts zur Berechnung des Wertansatzes für die chargenweisen Artikel und berücksichtigt nicht den individuellen chargenweisen Eingangskurs.", +Job Worker,Unterauftragnehmer, +Job Worker Address,Unterauftragnehmer Adresse, +Job Worker Address Details,Vorschau Adresse Unterauftragnehmer, +Job Worker Contact,Vertrag des Unterauftragnehmers, +Job Worker Delivery Note,Lieferschein des Unterauftragnehmers, +Job Worker Name,Name des Unterauftragnehmer, +Job Worker Warehouse,Lagerhaus des Unterauftragnehmers, +"Learn about Common Party","Erfahren Sie mehr über die Verknüpfung von Kunden und Lieferanten", +Notification,Benachrichtigung, +Notification Settings,Benachrichtigungseinstellungen, +Offsetting for Accounting Dimension,Verrechnung für Buchhaltungsdimension, +Only Include Allocated Payments,Nur zugeordnete Zahlungen einbeziehen, +Only one {0} entry can be created against the Work Order {1},Nur ein {0} Eintrag kann gegen den Arbeitsauftrag {1} erstellt werden, +Over Picking Allowance,Überkommissionierzugabe, +Over Transfer Allowance,Überschlusstransferzugabe, +Overbilling of {} ignored because you have {} role.,"Überhöhte Abrechnung von {} wurde ignoriert, weil Sie die Rolle {} haben.", +Payment Reconciliation Job: {0} is running for this party. Can't reconcile now.,Job für Zahlungsabgleich: {0} läuft für diese Partei. Kann jetzt nicht abgleichen., +Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.,"Eine Zahlungsanforderung, die aus einem Auftrag oder einer Bestellung erstellt wurde, wird im Entwurfsstatus sein. Wenn deaktiviert, wird das Dokument in ungespeichertem Zustand sein.", +Payment Request took too long to respond. Please try requesting for payment again.,"Zahlungsaufforderung hat zu lange gedauert, um zu antworten. Bitte versuchen Sie die Zahlung erneut anzufragen.", +Payment Terms Status for Sales Order,Status für Zahlungsbedingungen für Aufträge, +Pipeline By,Pipeline von, +Please enable Use Old Serial / Batch Fields to make_bundle,"Bitte aktivieren Sie ""Alte Serien-/Batchfelder verwenden"" für make_bundle", +Print Style,Druckstil, +Reconcile All Serial Nos / Batches,Alle Seriennummern/Chargen abgleichen, +Reset Company Default Values,Standardwerte des Unternehmens zurücksetzen, +Reset Raw Materials Table,Tabelle Rohstoffe zurücksetzen, +Return Against Subcontracting Receipt,Retoure gegen Unterauftragsbeleg, +Return Components,Komponenten zurückgeben, +Returned Against,Zurückgegeben gegen, +Returned exchange rate is neither integer not float.,Der zurückgegebene Wechselkurs ist weder Integer noch Float., +Round Off Tax Amount,Steuerbetrag abrunden, +Rounding Loss Allowance,Rundungsverlusttoleranz, +Rounding Loss Allowance should be between 0 and 1,Rundungsverlusttoleranz muss zwischen 0 und 1 sein, +Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1},Zeile #{0}: Ausschusslager ist für den abgelehnten Artikel {1} obligatorisch, +Row #{0}: You cannot use the inventory dimension '{1}' in Stock Reconciliation to modify the quantity or valuation rate. Stock reconciliation with inventory dimensions is intended solely for performing opening entries.,"Zeile #{0}: Sie können die Bestandsdimension '{1}' in der Bestandsabgleich nicht verwenden, um die Menge oder den Wertansatz zu ändern. Die Bestandsabgleich mit Bestandsdimensionen ist ausschließlich für die Durchführung von Eröffnungsbuchungen vorgesehen.", +Row {0}: Packed Qty must be equal to {1} Qty.,Zeile {0}: Verpackte Menge muss gleich der {1} Menge sein., +Row {0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations,Zeile {0}: Die Gesamtzahl der Abschreibungen kann nicht kleiner oder gleich der Anzahl der gebuchten Abschreibungen zu Beginn sein, +SCO Supplied Item,Artikel beigestellt für Unterauftrag, +SLA Fulfilled On Status,SLA erfüllt am Status, +SLA will be applied if {1} is set as {2}{3},"SLA wird angewendet, wenn {1} als {2}{3} eingestellt ist", +SMS Settings,SMS-Einstellungen, +SO Total Qty,Kd.-Auftr.-Gesamtmenge, +"Sales Order {0} already exists against Customer's Purchase Order {1}. To allow multiple Sales Orders, Enable {2} in {3}","Auftrag {0} existiert bereits für die Kundenbestellung {1}. Um mehrere Verkaufsaufträge zuzulassen, aktivieren Sie {2} in {3}", "Scorecard variables can be used, as well as: {total_score} (the total score from that period), {period_number} (the number of periods to present day) ","Variablen der Bewertung können verwendet werden, sowie: {total_score} (die Gesamtpunktzahl aus diesem Zeitraum), {period_number} (die Anzahl der Zeiträume bis zum heutigen Tag) -", -Select Accounting Dimension.,Buchhaltungsdimension auswählen, -Select Corrective Operation,Nacharbeit auswählen, -Select Date of Birth. This will validate Employees age and prevent hiring of under-age staff.,Wählen Sie Geburtsdatum. Damit wird das Alter der Mitarbeiter überprüft und die Einstellung von minderjährigen Mitarbeitern verhindert., -"Select Date of joining. It will have impact on the first salary calculation, Leave allocation on pro-rata bases.",Wählen Sie Eintrittsdatum. Es wirkt sich auf die erste Gehaltsberechnung und die Zuteilung von Abwesenheiten auf Pro-rata-Basis aus., -Select Dimension,Dimension auswählen, -Select Items for Quality Inspection,Artikel für die Qualitätsprüfung auswählen, -Select Job Worker Address,Unterauftragnehmer Adresse auswählen, -Service Expenses,Wartungsaufwand, -Service Level Agreement for {0} {1} already exists.,Service Level Agreement für {0} {1} existiert bereits., -System Settings,Systemverwaltung, -Website Script,Webseiten-Skript, -Website Theme,Webseiten-Thema, -Workflow Action,Workflow-Aktion, -Workflow State,Workflow-Status, -{0} is not running. Cannot trigger events for this Document,{0} läuft nicht. Ereignisse für dieses Dokument können nicht ausgelöst werden, -"""SN-01::10"" for ""SN-01"" to ""SN-10""","""SN-01::10"" für ""SN-01"" bis ""SN-10""", -"Masters & Reports","Stammdaten & Berichte", -"Quick Access","Schnellzugriff", -"Reports & Masters","Berichte & Stammdaten", -"Reports & Masters","Berichte & Stammdaten", -"Settings","Einstellungen", -"Shortcuts","Verknüpfungen", +", +Select Accounting Dimension.,Buchhaltungsdimension auswählen, +Select Corrective Operation,Nacharbeit auswählen, +Select Date of Birth. This will validate Employees age and prevent hiring of under-age staff.,Wählen Sie Geburtsdatum. Damit wird das Alter der Mitarbeiter überprüft und die Einstellung von minderjährigen Mitarbeitern verhindert., +"Select Date of joining. It will have impact on the first salary calculation, Leave allocation on pro-rata bases.",Wählen Sie Eintrittsdatum. Es wirkt sich auf die erste Gehaltsberechnung und die Zuteilung von Abwesenheiten auf Pro-rata-Basis aus., +Select Dimension,Dimension auswählen, +Select Items for Quality Inspection,Artikel für die Qualitätsprüfung auswählen, +Select Job Worker Address,Unterauftragnehmer Adresse auswählen, +Service Expenses,Wartungsaufwand, +Service Level Agreement for {0} {1} already exists.,Service Level Agreement für {0} {1} existiert bereits., +System Settings,Systemverwaltung, +Website Script,Webseiten-Skript, +Website Theme,Webseiten-Thema, +Workflow Action,Workflow-Aktion, +Workflow State,Workflow-Status, +{0} is not running. Cannot trigger events for this Document,{0} läuft nicht. Ereignisse für dieses Dokument können nicht ausgelöst werden, +"""SN-01::10"" for ""SN-01"" to ""SN-10""","""SN-01::10"" für ""SN-01"" bis ""SN-10""", +"Masters & Reports","Stammdaten & Berichte", +"Quick Access","Schnellzugriff", +"Reports & Masters","Berichte & Stammdaten", +"Reports & Masters","Berichte & Stammdaten", +"Settings","Einstellungen", +"Shortcuts","Verknüpfungen", "Your Shortcuts @@ -11948,183 +11947,183 @@ Workflow State,Workflow-Status, - ", -"Your Shortcuts","Ihre Verknüpfungen", -Grand Total: {0},Gesamtsumme:{0}, -Outstanding Amount: {0},Ausstehender Betrag: {0}, -Against Customer Order {0},Gegen Kundenauftrag {0}, -Against Supplier Invoice {0},Gegen Lieferantenrechnung {0}, -Ageing Range,Alterungsbereich, -Allocate Payment Request,Zahlungsanfrage zuweisen, -Amount in party's bank account currency,Betrag in der Währung des Bankkontos des Beteiligten, -Amount in transaction currency,Betrag in Transaktionswährung, -BIN Qty,BIN Menge, -BOM and Production,Stückliste und Produktion, -Balance Stock Value,Bestandswert, -Base Cost Per Unit,Grundkosten pro Einheit, -Base Rate,Basispreis, -Base Tax Withholding Net Total,Basis-Steuereinbehalt-Nettosumme, -Base Total,Basis-Summe, -Base Total Billable Amount,Basis Gesamter abrechenbarer Betrag, -Batch Expiry Date,Ablaufdatum der Charge, -Bill for Rejected Quantity in Purchase Invoice,Rechnung für abgelehnte Menge in der Eingangsrechnung, -Calculate daily depreciation using total days in depreciation period,Tägliche Abschreibung anhand der Gesamttage im Abschreibungszeitraum berechnen, -Call Schedule Row {0}: To time slot should always be ahead of From time slot.,Anrufplanzeile {0}: Das Zeitfenster Bis sollte immer vor dem Zeitfenster Von liegen., -Cannot find a default warehouse for item {0}. Please set one in the Item Master or in Stock Settings.,Es wurde kein Standardlager für den Artikel {0} gefunden. Bitte legen Sie eines im Artikelstamm oder in den Lagereinstellungen fest., -Cannot {0} from {2} without any negative outstanding invoice,Kann nicht {0} von {2} ohne negative ausstehende Rechnung, -Cheques and Deposits Incorrectly cleared,Falsch verrechnete Schecks und Einzahlungen, -Columns are not according to template. Please compare the uploaded file with standard template,Die Spalten stimmen nicht mit der Vorlage überein. Bitte vergleichen Sie die hochgeladene Datei mit der Standardvorlage, -Company is mandatory,Unternehmen ist obligatorisch, -Completion Date can not be before Failure Date. Please adjust the dates accordingly.,Das Fertigstellungsdatum kann nicht vor dem Ausfalldatum liegen. Bitte passen Sie die Daten entsprechend an., -Consumed Stock Items or Consumed Asset Items are mandatory for creating new composite asset,Verbrauchte Lagerartikel oder verbrauchte Vermögensgegenstand-Artikel sind für die Erstellung obligatorisch, -Convert to Item Based Reposting,Umstellung auf artikelbasiertes Umbuchen, -Create Workstation,Arbeitsplatz erstellen, -Creating Journal Entries...,Journaleinträge erstellen..., -Creating Purchase Invoices ...,Eingangsrechnungen erstellen ..., -Creating Sales Invoices ...,Ausgangsrechnungen erstellen ..., -Currency Exchange Settings Result,Währungsumtauscheinstellungen Ergebnis, -Deal Owner,Besitzer des Deals, -Decapitalized,Dekapitalisiert, -Dependant SLE Voucher Detail No,Unterhaltsberechtigter SLE Beleg Detail Nr., -Disassemble,Demontage, -Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.,Dokumente: {0} hat vertagte Einnahmen/Ausgaben aktiviert. Kann nicht erneut posten., -Don't Reserve Sales Order Qty on Sales Return,Menge des Auftrags nicht bei der Rücksendung reservieren, -Enter Manually,Manuell eingeben, -Failed to post depreciation entries,Abschreibungsbuchungen fehlgeschlagen, -Filters missing,Filter fehlen, -"For Return Invoices with Stock effect, '0' qty Items are not allowed. Following rows are affected: {0}",Bei Retourenrechnungen mit Lagereffekt sind Artikel mit einer Menge von '0' nicht zulässig. Folgende Zeilen sind betroffen: {0}, -"For the item {0}, the quantity should be {1} according to the BOM {2}.",Für den Artikel {0} sollte die Menge gemäß Stückliste {2} {1} betragen., -"For the {0}, no stock is available for the return in the warehouse {1}.",Für {0} ist im Lager {1} kein Bestand für die Retoure verfügbar., -Force-Fetch Subscription Updates,Abonnement-Updates erzwingen, -From Date is mandatory,Von-Datum ist obligatorisch, -From Prospect,Von Interessenten, -Gross Purchase Amount Too Low: {0} cannot be depreciated over {1} cycles with a frequency of {2} depreciations.,Bruttokaufbetrag zu niedrig: {0} kann nicht über {1} Zyklen mit einer Häufigkeit von {2} Abschreibungen abgeschrieben werden., -If enabled then system won't apply the pricing rule on the delivery note which will be create from the pick list,"Falls aktiviert, wird das System die Preisregel nicht auf den Lieferschein anwenden, der aus der Pickliste erstellt wird", -Impairment,Wertminderung, -Include Closed Orders,Geschlossene Aufträge/Bestellungen einbeziehen, -Invalid Allocated Amount,Ungültiger zugewiesener Betrag, -Invalid Amount,Ungültiger Betrag, -Is Standard,Ist Standard, -Item {0} does not exist.,Artikel {0} existiert nicht., -Items {0} do not exist in the Item master.,Artikel {0} sind nicht im Artikelstamm vorhanden., -Journal entries have been created,Journaleinträge wurden erstellt, -Net total calculation precision loss,Präzisionsverlust bei Berechnung der Nettosumme, -Only Deduct Tax On Excess Amount ,Nur den überschüssigen Betrag versteuern , -Payment Ledger Entry,Zahlungsbucheintrag, -Payment Requests cannot be created against: {0},Zahlungsanforderungen können nicht erstellt werden für: {0}, -Period Closing Entry For Current Period,Periodenabschlussbuchung für aktuelle Periode, -Provisional Account,Vorläufiges Konto, -Rate of Stock UOM,Einzelpreis der Lager-ME, -Raw Materials Consumption ,Rohstoffverbrauch , -Recalculating Purchase Cost against this Project...,Neuberechnung der Anschaffungskosten für dieses Projekt..., -Reference DocType,Referenz DocType, -Round Tax Amount Row-wise,Steuerbetrag zeilenweise runden, -Rounding gain/loss Entry for Stock Transfer,Rundungsgewinn/-verlustbuchung für Umlagerung, -Row #{0}: Only {1} available to reserve for the Item {2},Zeile #{0}: Nur {1} zur Reservierung für den Artikel {2} verfügbar, -Row #{}: The original Invoice {} of return invoice {} is not consolidated.,Zeile #{}: Die ursprüngliche Rechnung {} der Rechnungskorrektur {} ist nicht konsolidiert., -Row {0}: Item {1} must be a subcontracted item.,Zeile {0}: Artikel {1} muss ein an Dritte vergebener Artikel sein., -Row {0}: Please provide a valid Delivery Note Item or Packed Item reference.,Zeile {0}: Bitte geben Sie einen gültigen Lieferschein Artikel oder verpackten Artikel an., -Row {0}: Target Warehouse is mandatory for internal transfers,Zeile {0}: Ziellager ist für interne Transfers obligatorisch, -Row({0}): Outstanding Amount cannot be greater than actual Outstanding Amount {1} in {2},Zeile({0}): Ausstehender Betrag kann nicht größer sein als der tatsächliche ausstehende Betrag {1} in {2}, -Rows with Same Account heads will be merged on Ledger,Zeilen mit denselben Konten werden im Hauptbuch zusammengefasst, -Select Warehouses to get Stock for Materials Planning,"Wählen Sie Lager aus, um Bestände für die Materialplanung zu erhalten", -Select an invoice to load summary data,"Wählen Sie eine Rechnung aus, um die Zusammenfassung zu laden", -Serial / Batch Bundle,Serien- / Chargenbündel, -Serial / Batch Bundle Missing,Serien- / Chargenbündel fehlt, -Serial No Range,Seriennummernbereich, -Serial and Batch Details,Serien- und Chargendetails, -Sets 'Accepted Warehouse' in each row of the Items table.,Legt in jeder Zeile der Artikeltabelle das Annahmelager fest., -Sets 'Rejected Warehouse' in each row of the Items table.,Legt in jeder Zeile der Artikeltabelle das „Ausschusslager“ fest., -Shelf Life in Days,Haltbarkeitsdauer in Tagen, -Show Disabled Warehouses,Deaktivierte Lager anzeigen, -Show GL Balance,Hauptbuchsaldo anzeigen, -Show Pay Button in Purchase Order Portal,Schaltfläche „Bezahlen“ im Bestellportal anzeigen, -Show Taxes as Table in Print,Steuern als Tabelle im Druck anzeigen, -Show net values in opening and closing columns,Nettowerte in Eröffnungs- und Abschlussspalten anzeigen, -Show with upcoming revenue/expense,Mit kommenden Einnahmen/Ausgaben anzeigen, -Something went wrong please try again,"Etwas ist schief gelaufen, bitte versuchen Sie es erneut", -South Africa VAT Account,Südafrika Mehrwertsteuer-Konto, -South Africa VAT Settings,Südafrika Mehrwertsteuer-Einstellungen, -Start Date should be lower than End Date,Das Startdatum muss vor dem Enddatum liegen, -Start Deletion,Löschen starten, -Start Time can't be greater than or equal to End Time for {0}.,Die Startzeit kann nicht größer oder gleich der Endzeit für {0} sein., -Started a background job to create {1} {0},Hintergrundjob zum Erstellen von {1} {0} gestartet, -Status set to rejected as there are one or more rejected readings.,"Der Status wurde auf abgelehnt gesetzt, da es einen oder mehrere abgelehnte Messwerte gibt.", -Stock Consumed During Repair,Während der Reparatur verbrauchter Bestand, -Stock Consumption Details,Details zum Lagerverbrauch, -Stock Planning,Bestandsplanung, -Stock Reservation,Bestandsreservierung, -Stock Reservation Entries Cancelled,Bestandsreservierungen storniert, -Stock Reservation Entries Created,Bestandsreservierungen erstellt, -Stock Reservation Entry,Bestandsreservierungseintrag, -Stock Reservation Entry cannot be updated as it has been delivered.,"Der Bestandsreservierungseintrag kann nicht aktualisiert werden, da er bereits geliefert wurde.", -"Stock Reservation Entry created against a Pick List cannot be updated. If you need to make changes, we recommend canceling the existing entry and creating a new one.","Ein anhand einer Kommissionierliste erstellter Bestandsreservierungseintrag kann nicht aktualisiert werden. Wenn Sie Änderungen vornehmen müssen, empfehlen wir, den vorhandenen Eintrag zu stornieren und einen neuen zu erstellen.", -Stock Reservation can only be created against {0}.,Bestandsreservierungen können nur gegen {0} erstellt werden., -Stock Reserved Qty (in Stock UOM),Reservierter Bestand (in Lager-ME), -Stock Unreservation,Aufhebung der Bestandsreservierung, -Stock/Accounts can not be frozen as processing of backdated entries is going on. Please try again later.,"Lagerbestände/Konten können nicht eingefroren werden, da die Verarbeitung rückwirkender Einträge noch läuft. Bitte versuchen Sie es später erneut.", -Supplied Item,Gelieferter Artikel, -Supplies subject to the reverse charge provision,"Lieferungen, die der Reverse-Charge-Regelung unterliegen", -Task {0} depends on Task {1}. Please add Task {1} to the Tasks list.,Aufgabe {0} hängt von Aufgabe {1} ab. Bitte fügen Sie Aufgabe {1} zur Aufgabenliste hinzu., -Tax Amount will be rounded on a row(items) level,Der Steuerbetrag wird auf (Artikel-)Zeilenebene gerundet, -Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme,Steuererstattungen für Touristen im Rahmen der Steuererstattungsregelung für Touristen, + ", +"Your Shortcuts","Ihre Verknüpfungen", +Grand Total: {0},Gesamtsumme:{0}, +Outstanding Amount: {0},Ausstehender Betrag: {0}, +Against Customer Order {0},Gegen Kundenauftrag {0}, +Against Supplier Invoice {0},Gegen Lieferantenrechnung {0}, +Ageing Range,Alterungsbereich, +Allocate Payment Request,Zahlungsanfrage zuweisen, +Amount in party's bank account currency,Betrag in der Währung des Bankkontos des Beteiligten, +Amount in transaction currency,Betrag in Transaktionswährung, +BIN Qty,BIN Menge, +BOM and Production,Stückliste und Produktion, +Balance Stock Value,Bestandswert, +Base Cost Per Unit,Grundkosten pro Einheit, +Base Rate,Basispreis, +Base Tax Withholding Net Total,Basis-Steuereinbehalt-Nettosumme, +Base Total,Basis-Summe, +Base Total Billable Amount,Basis Gesamter abrechenbarer Betrag, +Batch Expiry Date,Ablaufdatum der Charge, +Bill for Rejected Quantity in Purchase Invoice,Rechnung für abgelehnte Menge in der Eingangsrechnung, +Calculate daily depreciation using total days in depreciation period,Tägliche Abschreibung anhand der Gesamttage im Abschreibungszeitraum berechnen, +Call Schedule Row {0}: To time slot should always be ahead of From time slot.,Anrufplanzeile {0}: Das Zeitfenster Bis sollte immer vor dem Zeitfenster Von liegen., +Cannot find a default warehouse for item {0}. Please set one in the Item Master or in Stock Settings.,Es wurde kein Standardlager für den Artikel {0} gefunden. Bitte legen Sie eines im Artikelstamm oder in den Lagereinstellungen fest., +Cannot {0} from {2} without any negative outstanding invoice,Kann nicht {0} von {2} ohne negative ausstehende Rechnung, +Cheques and Deposits Incorrectly cleared,Falsch verrechnete Schecks und Einzahlungen, +Columns are not according to template. Please compare the uploaded file with standard template,Die Spalten stimmen nicht mit der Vorlage überein. Bitte vergleichen Sie die hochgeladene Datei mit der Standardvorlage, +Company is mandatory,Unternehmen ist obligatorisch, +Completion Date can not be before Failure Date. Please adjust the dates accordingly.,Das Fertigstellungsdatum kann nicht vor dem Ausfalldatum liegen. Bitte passen Sie die Daten entsprechend an., +Consumed Stock Items or Consumed Asset Items are mandatory for creating new composite asset,Verbrauchte Lagerartikel oder verbrauchte Vermögensgegenstand-Artikel sind für die Erstellung obligatorisch, +Convert to Item Based Reposting,Umstellung auf artikelbasiertes Umbuchen, +Create Workstation,Arbeitsplatz erstellen, +Creating Journal Entries...,Journaleinträge erstellen..., +Creating Purchase Invoices ...,Eingangsrechnungen erstellen ..., +Creating Sales Invoices ...,Ausgangsrechnungen erstellen ..., +Currency Exchange Settings Result,Währungsumtauscheinstellungen Ergebnis, +Deal Owner,Besitzer des Deals, +Decapitalized,Dekapitalisiert, +Dependant SLE Voucher Detail No,Unterhaltsberechtigter SLE Beleg Detail Nr., +Disassemble,Demontage, +Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.,Dokumente: {0} hat vertagte Einnahmen/Ausgaben aktiviert. Kann nicht erneut posten., +Don't Reserve Sales Order Qty on Sales Return,Menge des Auftrags nicht bei der Rücksendung reservieren, +Enter Manually,Manuell eingeben, +Failed to post depreciation entries,Abschreibungsbuchungen fehlgeschlagen, +Filters missing,Filter fehlen, +"For Return Invoices with Stock effect, '0' qty Items are not allowed. Following rows are affected: {0}",Bei Retourenrechnungen mit Lagereffekt sind Artikel mit einer Menge von '0' nicht zulässig. Folgende Zeilen sind betroffen: {0}, +"For the item {0}, the quantity should be {1} according to the BOM {2}.",Für den Artikel {0} sollte die Menge gemäß Stückliste {2} {1} betragen., +"For the {0}, no stock is available for the return in the warehouse {1}.",Für {0} ist im Lager {1} kein Bestand für die Retoure verfügbar., +Force-Fetch Subscription Updates,Abonnement-Updates erzwingen, +From Date is mandatory,Von-Datum ist obligatorisch, +From Prospect,Von Interessenten, +Gross Purchase Amount Too Low: {0} cannot be depreciated over {1} cycles with a frequency of {2} depreciations.,Bruttokaufbetrag zu niedrig: {0} kann nicht über {1} Zyklen mit einer Häufigkeit von {2} Abschreibungen abgeschrieben werden., +If enabled then system won't apply the pricing rule on the delivery note which will be create from the pick list,"Falls aktiviert, wird das System die Preisregel nicht auf den Lieferschein anwenden, der aus der Pickliste erstellt wird", +Impairment,Wertminderung, +Include Closed Orders,Geschlossene Aufträge/Bestellungen einbeziehen, +Invalid Allocated Amount,Ungültiger zugewiesener Betrag, +Invalid Amount,Ungültiger Betrag, +Is Standard,Ist Standard, +Item {0} does not exist.,Artikel {0} existiert nicht., +Items {0} do not exist in the Item master.,Artikel {0} sind nicht im Artikelstamm vorhanden., +Journal entries have been created,Journaleinträge wurden erstellt, +Net total calculation precision loss,Präzisionsverlust bei Berechnung der Nettosumme, +Only Deduct Tax On Excess Amount ,Nur den überschüssigen Betrag versteuern , +Payment Ledger Entry,Zahlungsbucheintrag, +Payment Requests cannot be created against: {0},Zahlungsanforderungen können nicht erstellt werden für: {0}, +Period Closing Entry For Current Period,Periodenabschlussbuchung für aktuelle Periode, +Provisional Account,Vorläufiges Konto, +Rate of Stock UOM,Einzelpreis der Lager-ME, +Raw Materials Consumption ,Rohstoffverbrauch , +Recalculating Purchase Cost against this Project...,Neuberechnung der Anschaffungskosten für dieses Projekt..., +Reference DocType,Referenz DocType, +Round Tax Amount Row-wise,Steuerbetrag zeilenweise runden, +Rounding gain/loss Entry for Stock Transfer,Rundungsgewinn/-verlustbuchung für Umlagerung, +Row #{0}: Only {1} available to reserve for the Item {2},Zeile #{0}: Nur {1} zur Reservierung für den Artikel {2} verfügbar, +Row #{}: The original Invoice {} of return invoice {} is not consolidated.,Zeile #{}: Die ursprüngliche Rechnung {} der Rechnungskorrektur {} ist nicht konsolidiert., +Row {0}: Item {1} must be a subcontracted item.,Zeile {0}: Artikel {1} muss ein an Dritte vergebener Artikel sein., +Row {0}: Please provide a valid Delivery Note Item or Packed Item reference.,Zeile {0}: Bitte geben Sie einen gültigen Lieferschein Artikel oder verpackten Artikel an., +Row {0}: Target Warehouse is mandatory for internal transfers,Zeile {0}: Ziellager ist für interne Transfers obligatorisch, +Row({0}): Outstanding Amount cannot be greater than actual Outstanding Amount {1} in {2},Zeile({0}): Ausstehender Betrag kann nicht größer sein als der tatsächliche ausstehende Betrag {1} in {2}, +Rows with Same Account heads will be merged on Ledger,Zeilen mit denselben Konten werden im Hauptbuch zusammengefasst, +Select Warehouses to get Stock for Materials Planning,"Wählen Sie Lager aus, um Bestände für die Materialplanung zu erhalten", +Select an invoice to load summary data,"Wählen Sie eine Rechnung aus, um die Zusammenfassung zu laden", +Serial / Batch Bundle,Serien- / Chargenbündel, +Serial / Batch Bundle Missing,Serien- / Chargenbündel fehlt, +Serial No Range,Seriennummernbereich, +Serial and Batch Details,Serien- und Chargendetails, +Sets 'Accepted Warehouse' in each row of the Items table.,Legt in jeder Zeile der Artikeltabelle das Annahmelager fest., +Sets 'Rejected Warehouse' in each row of the Items table.,Legt in jeder Zeile der Artikeltabelle das „Ausschusslager“ fest., +Shelf Life in Days,Haltbarkeitsdauer in Tagen, +Show Disabled Warehouses,Deaktivierte Lager anzeigen, +Show GL Balance,Hauptbuchsaldo anzeigen, +Show Pay Button in Purchase Order Portal,Schaltfläche „Bezahlen“ im Bestellportal anzeigen, +Show Taxes as Table in Print,Steuern als Tabelle im Druck anzeigen, +Show net values in opening and closing columns,Nettowerte in Eröffnungs- und Abschlussspalten anzeigen, +Show with upcoming revenue/expense,Mit kommenden Einnahmen/Ausgaben anzeigen, +Something went wrong please try again,"Etwas ist schief gelaufen, bitte versuchen Sie es erneut", +South Africa VAT Account,Südafrika Mehrwertsteuer-Konto, +South Africa VAT Settings,Südafrika Mehrwertsteuer-Einstellungen, +Start Date should be lower than End Date,Das Startdatum muss vor dem Enddatum liegen, +Start Deletion,Löschen starten, +Start Time can't be greater than or equal to End Time for {0}.,Die Startzeit kann nicht größer oder gleich der Endzeit für {0} sein., +Started a background job to create {1} {0},Hintergrundjob zum Erstellen von {1} {0} gestartet, +Status set to rejected as there are one or more rejected readings.,"Der Status wurde auf abgelehnt gesetzt, da es einen oder mehrere abgelehnte Messwerte gibt.", +Stock Consumed During Repair,Während der Reparatur verbrauchter Bestand, +Stock Consumption Details,Details zum Lagerverbrauch, +Stock Planning,Bestandsplanung, +Stock Reservation,Bestandsreservierung, +Stock Reservation Entries Cancelled,Bestandsreservierungen storniert, +Stock Reservation Entries Created,Bestandsreservierungen erstellt, +Stock Reservation Entry,Bestandsreservierungseintrag, +Stock Reservation Entry cannot be updated as it has been delivered.,"Der Bestandsreservierungseintrag kann nicht aktualisiert werden, da er bereits geliefert wurde.", +"Stock Reservation Entry created against a Pick List cannot be updated. If you need to make changes, we recommend canceling the existing entry and creating a new one.","Ein anhand einer Kommissionierliste erstellter Bestandsreservierungseintrag kann nicht aktualisiert werden. Wenn Sie Änderungen vornehmen müssen, empfehlen wir, den vorhandenen Eintrag zu stornieren und einen neuen zu erstellen.", +Stock Reservation can only be created against {0}.,Bestandsreservierungen können nur gegen {0} erstellt werden., +Stock Reserved Qty (in Stock UOM),Reservierter Bestand (in Lager-ME), +Stock Unreservation,Aufhebung der Bestandsreservierung, +Stock/Accounts can not be frozen as processing of backdated entries is going on. Please try again later.,"Lagerbestände/Konten können nicht eingefroren werden, da die Verarbeitung rückwirkender Einträge noch läuft. Bitte versuchen Sie es später erneut.", +Supplied Item,Gelieferter Artikel, +Supplies subject to the reverse charge provision,"Lieferungen, die der Reverse-Charge-Regelung unterliegen", +Task {0} depends on Task {1}. Please add Task {1} to the Tasks list.,Aufgabe {0} hängt von Aufgabe {1} ab. Bitte fügen Sie Aufgabe {1} zur Aufgabenliste hinzu., +Tax Amount will be rounded on a row(items) level,Der Steuerbetrag wird auf (Artikel-)Zeilenebene gerundet, +Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme,Steuererstattungen für Touristen im Rahmen der Steuererstattungsregelung für Touristen, "Tax detail table fetched from item master as a string and stored in this field. Used for Taxes and Charges","Steuerdetailtabelle, die aus dem Artikelstamm als Zeichenfolge abgerufen und in diesem Feld gespeichert wird. -Wird für Steuern und Gebühren verwendet", -"The Payment Request {0} is already paid, cannot process payment twice","Die Auszahlungsanforderung {0} ist bereits bezahlt, die Zahlung kann nicht zweimal verarbeitet werden", -The Serial No at Row #{0}: {1} is not available in warehouse {2}.,Die Seriennummer in Zeile #{0}: {1} ist im Lager {2} nicht verfügbar., -The Work Order is mandatory for Disassembly Order,Der Arbeitsauftrag ist obligatorisch für den Demontageauftrag, -The allocated amount is greater than the outstanding amount of Payment Request {0},Der zugewiesene Betrag ist größer als der ausstehende Betrag der Zahlungsanforderung {0}, -The field {0} in row {1} is not set,Das Feld {0} in der Zeile {1} ist nicht gesetzt, -The following invalid Pricing Rules are deleted:,Die folgenden ungültigen Preisregeln werden gelöscht:, -The original invoice should be consolidated before or along with the return invoice.,Die Originalrechnung sollte vor oder zusammen mit der Erstattungsrechnung konsolidiert werden., -"The sync has started in the background, please check the {0} list for new records.",Die Synchronisierung wurde im Hintergrund gestartet. Bitte überprüfen Sie die Liste {0} auf neue Datensätze., -"The users with this Role are allowed to create/modify a stock transaction, even though the transaction is frozen.","Die Benutzer mit dieser Rolle dürfen eine Lagerbewegungen erstellen/ändern, auch wenn die Transaktion eingefroren ist.", -There are no active Fiscal Years for which Demo Data can be generated.,"Es gibt keine aktiven Geschäftsjahre, für die Demodaten erstellt werden können.", -There were issues unlinking payment entry {0}.,Es gab Probleme bei der Aufhebung der Verknüpfung der Zahlung {0}., -"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.","Diese Option ist standardmäßig aktiviert. Wenn Sie Materialien für Unterbaugruppen des Artikels, den Sie herstellen, planen möchten, lassen Sie diese Option aktiviert. Wenn Sie die Unterbaugruppen separat planen und herstellen, können Sie dieses Kontrollkästchen deaktivieren.", -To Date is mandatory,Bis Datum ist obligatorisch, -To Delivery Date,Bis Liefertermin, -To Due Date,Bis Fälligkeitsdatum, -To Reference Date,Bis Stichtag, -To cancel a {} you need to cancel the POS Closing Entry {}.,"Um einen {} zu stornieren, müssen Sie die POS-Abschlussbuchung {} stornieren.", -Total Incoming Value (Receipt),Gesamter eingehender Wert (Empfang), -Total Number of Booked Depreciations ,Gesamtzahl der gebuchten Abschreibungen , -Total Operation Time,Gesamtbetriebszeit, -Total Other Charges,Sonstige Kosten insgesamt, -Total Purchase Amount,Gesamtkaufbetrag, -Total Purchase Cost has been updated,Die Gesamteinkaufskosten wurden aktualisiert, -UnReconcile,Zuordnung aufheben, -Unrealized Profit / Loss account for intra-company transfers,Konto für nicht realisierte Gewinne/Verluste aus konzerninternen Transfers, -Unrealized Profit/Loss account for intra-company transfers,Konto für nicht realisierte Gewinne/Verluste aus konzerninternen Transfers, -Validate Components Quantities Per BOM,Anzahl der Komponenten pro Stückliste überprüfen, -Validate Pricing Rule,Preisregel validieren, -Validate Stock on Save,Lagerbestand beim Speichern validieren, -Warning on Negative Stock,Warnung vor negativem Bestand, -You cannot create a {0} within the closed Accounting Period {1},Sie können innerhalb der abgeschlossenen Abrechnungsperiode {1} kein(e) {0} erstellen, -dated {0},von {0}, -must be between 0 and 100,muss zwischen 0 und 100 liegen, -or its descendants,oder seine Nachkommen, -subscription is already cancelled.,abonnement ist bereits storniert., -{0} Account not found against Customer {1}.,{0} Konto für Kunde {1} nicht gefunden., -{0} Transaction(s) Reconciled,{0} Transaktion(en) Abgestimmt, -{0} cannot be zero,{0} kann nicht Null sein, -{0} is already running for {1},{0} läuft bereits für {1}, -{0} units of Item {1} is not available in any of the warehouses.,{0} Einheiten des Artikels {1} sind in keinem der Lager verfügbar., -Cannot {0} from {1} without any negative outstanding invoice,Kann nicht {0} von {1} ohne negative ausstehende Rechnung, -Common Code,Gemeinsamer Code, -Event,Ereignis, -Forecast,Prognose, +Wird für Steuern und Gebühren verwendet", +"The Payment Request {0} is already paid, cannot process payment twice","Die Auszahlungsanforderung {0} ist bereits bezahlt, die Zahlung kann nicht zweimal verarbeitet werden", +The Serial No at Row #{0}: {1} is not available in warehouse {2}.,Die Seriennummer in Zeile #{0}: {1} ist im Lager {2} nicht verfügbar., +The Work Order is mandatory for Disassembly Order,Der Arbeitsauftrag ist obligatorisch für den Demontageauftrag, +The allocated amount is greater than the outstanding amount of Payment Request {0},Der zugewiesene Betrag ist größer als der ausstehende Betrag der Zahlungsanforderung {0}, +The field {0} in row {1} is not set,Das Feld {0} in der Zeile {1} ist nicht gesetzt, +The following invalid Pricing Rules are deleted:,Die folgenden ungültigen Preisregeln werden gelöscht:, +The original invoice should be consolidated before or along with the return invoice.,Die Originalrechnung sollte vor oder zusammen mit der Erstattungsrechnung konsolidiert werden., +"The sync has started in the background, please check the {0} list for new records.",Die Synchronisierung wurde im Hintergrund gestartet. Bitte überprüfen Sie die Liste {0} auf neue Datensätze., +"The users with this Role are allowed to create/modify a stock transaction, even though the transaction is frozen.","Die Benutzer mit dieser Rolle dürfen eine Lagerbewegungen erstellen/ändern, auch wenn die Transaktion eingefroren ist.", +There are no active Fiscal Years for which Demo Data can be generated.,"Es gibt keine aktiven Geschäftsjahre, für die Demodaten erstellt werden können.", +There were issues unlinking payment entry {0}.,Es gab Probleme bei der Aufhebung der Verknüpfung der Zahlung {0}., +"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.","Diese Option ist standardmäßig aktiviert. Wenn Sie Materialien für Unterbaugruppen des Artikels, den Sie herstellen, planen möchten, lassen Sie diese Option aktiviert. Wenn Sie die Unterbaugruppen separat planen und herstellen, können Sie dieses Kontrollkästchen deaktivieren.", +To Date is mandatory,Bis Datum ist obligatorisch, +To Delivery Date,Bis Liefertermin, +To Due Date,Bis Fälligkeitsdatum, +To Reference Date,Bis Stichtag, +To cancel a {} you need to cancel the POS Closing Entry {}.,"Um einen {} zu stornieren, müssen Sie die POS-Abschlussbuchung {} stornieren.", +Total Incoming Value (Receipt),Gesamter eingehender Wert (Empfang), +Total Number of Booked Depreciations ,Gesamtzahl der gebuchten Abschreibungen , +Total Operation Time,Gesamtbetriebszeit, +Total Other Charges,Sonstige Kosten insgesamt, +Total Purchase Amount,Gesamtkaufbetrag, +Total Purchase Cost has been updated,Die Gesamteinkaufskosten wurden aktualisiert, +UnReconcile,Zuordnung aufheben, +Unrealized Profit / Loss account for intra-company transfers,Konto für nicht realisierte Gewinne/Verluste aus konzerninternen Transfers, +Unrealized Profit/Loss account for intra-company transfers,Konto für nicht realisierte Gewinne/Verluste aus konzerninternen Transfers, +Validate Components Quantities Per BOM,Anzahl der Komponenten pro Stückliste überprüfen, +Validate Pricing Rule,Preisregel validieren, +Validate Stock on Save,Lagerbestand beim Speichern validieren, +Warning on Negative Stock,Warnung vor negativem Bestand, +You cannot create a {0} within the closed Accounting Period {1},Sie können innerhalb der abgeschlossenen Abrechnungsperiode {1} kein(e) {0} erstellen, +dated {0},von {0}, +must be between 0 and 100,muss zwischen 0 und 100 liegen, +or its descendants,oder seine Nachkommen, +subscription is already cancelled.,abonnement ist bereits storniert., +{0} Account not found against Customer {1}.,{0} Konto für Kunde {1} nicht gefunden., +{0} Transaction(s) Reconciled,{0} Transaktion(en) Abgestimmt, +{0} cannot be zero,{0} kann nicht Null sein, +{0} is already running for {1},{0} läuft bereits für {1}, +{0} units of Item {1} is not available in any of the warehouses.,{0} Einheiten des Artikels {1} sind in keinem der Lager verfügbar., +Cannot {0} from {1} without any negative outstanding invoice,Kann nicht {0} von {1} ohne negative ausstehende Rechnung, +Common Code,Gemeinsamer Code, +Event,Ereignis, +Forecast,Prognose, "If Enabled - Reconciliation happens on the Advance Payment posting date
If Disabled - Reconciliation happens on oldest of 2 Dates: Invoice Date or the Advance Payment posting date
","Falls aktiviert - erfolgt der Abgleich am Buchungsdatum der Vorauszahlung
Falls deaktiviert - erfolgt der Abgleich am ältesten von 2 Daten: Rechnungsdatum oder Buchungsdatum der Vorauszahlung
-", -Pay,Zahlen,Amount -Please set '{0}' in Company: {1},Bitte stellen Sie '{0}' in Unternehmen ein: {1}, -Price is not set for the item.,Für den Artikel ist kein Preis festgelegt., -Rate of Depreciation (%),Abschreibungssatz (%), -Rejected ,Abgelehnt , -Row #{0}: Allocated Amount cannot be greater than Outstanding Amount of Payment Request {1},Zeile #{0}: Der zugewiesene Betrag kann nicht größer sein als der ausstehende Betrag der Zahlungsanforderung {1}, -Section,Sektion, -Sending...,Senden..., +", +Pay,Zahlen,Amount +Please set '{0}' in Company: {1},Bitte stellen Sie '{0}' in Unternehmen ein: {1}, +Price is not set for the item.,Für den Artikel ist kein Preis festgelegt., +Rate of Depreciation (%),Abschreibungssatz (%), +Rejected ,Abgelehnt , +Row #{0}: Allocated Amount cannot be greater than Outstanding Amount of Payment Request {1},Zeile #{0}: Der zugewiesene Betrag kann nicht größer sein als der ausstehende Betrag der Zahlungsanforderung {1}, +Section,Sektion, +Sending...,Senden..., From 48822f6feefe0dbbcae9efcb746e8375de3b10b9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 7 Apr 2025 14:24:13 +0530 Subject: [PATCH 39/62] fix: removed hardcoded search fields to fix performance issue (cherry picked from commit 216bf2456e696ad4079ef268b3e0e788f97ecb02) # Conflicts: # erpnext/manufacturing/doctype/bom/bom.py --- erpnext/manufacturing/doctype/bom/bom.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 7f2d2acc00d..5aacda01c1b 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1496,11 +1496,16 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): fields = ["name", "item_name", "item_group", "description"] fields.extend([field for field in searchfields if field not in ["name", "item_group", "description"]]) +<<<<<<< HEAD searchfields = searchfields + [ field for field in [searchfield or "name", "item_code", "item_group", "item_name"] if field not in searchfields ] +======= + if not searchfields: + searchfields = ["name"] +>>>>>>> 216bf2456e (fix: removed hardcoded search fields to fix performance issue) query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())} From fe0e5c2d48e529835bbd07cd334f14ddc2dec81f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 7 Apr 2025 11:47:27 +0530 Subject: [PATCH 40/62] fix: inventory dimensions columns visibility depends on filter (cherry picked from commit 2b411fb7f5bda812e2e1c8882dd8ee6fbb44586c) --- erpnext/public/js/utils.js | 4 ++++ .../report/stock_balance/stock_balance.py | 21 ++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index c0126f2c6ee..39a341814db 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -263,6 +263,10 @@ $.extend(erpnext.utils, { fieldname: dimension["fieldname"], label: __(dimension["doctype"]), fieldtype: "MultiSelectList", + depends_on: + report_name === "Stock Balance" + ? "eval:doc.show_dimension_wise_stock === 1" + : "", get_data: function (txt) { return frappe.db.get_link_options(dimension["doctype"], txt); }, diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 2153b1d73b3..b6587cd7b37 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -406,16 +406,17 @@ class StockBalanceReport: }, ] - for dimension in get_inventory_dimensions(): - columns.append( - { - "label": _(dimension.doctype), - "fieldname": dimension.fieldname, - "fieldtype": "Link", - "options": dimension.doctype, - "width": 110, - } - ) + if self.filters.get("show_dimension_wise_stock"): + for dimension in get_inventory_dimensions(): + columns.append( + { + "label": _(dimension.doctype), + "fieldname": dimension.fieldname, + "fieldtype": "Link", + "options": dimension.doctype, + "width": 110, + } + ) columns.extend( [ From e1154090f66e3d204136b24a47f89ff97223e5fe Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 31 Mar 2025 12:37:28 +0530 Subject: [PATCH 41/62] fix: update outstanding with precision (cherry picked from commit aadda9f606748bb90cc24a4679e079d75cf29828) --- erpnext/accounts/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index ac37775f45f..b46e427382f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1854,14 +1854,17 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa ): outstanding = voucher_outstanding[0] ref_doc = frappe.get_doc(voucher_type, voucher_no) + outstanding_amount = flt( + outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount") + ) # Didn't use db_set for optimisation purpose - ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"] or 0.0 + ref_doc.outstanding_amount = outstanding_amount frappe.db.set_value( voucher_type, voucher_no, "outstanding_amount", - outstanding["outstanding_in_account_currency"] or 0.0, + outstanding_amount, ) ref_doc.set_status(update=True) From c705623fdc1a002169344c4165279f18fbe4fdb0 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 25 Mar 2025 14:15:24 +0530 Subject: [PATCH 42/62] fix: user permissions in sales and purchase report (cherry picked from commit f4bc1dfd005d9d52c75c919703ba47f8399439fc) --- .../item_wise_purchase_register.py | 20 ++++++----- .../item_wise_sales_register.py | 33 +++++++++++-------- .../purchase_register/purchase_register.py | 14 ++++++-- .../report/sales_register/sales_register.py | 14 ++++++-- 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 83f664c984a..b313ed8b173 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -11,7 +11,7 @@ import erpnext from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import ( add_sub_total_row, add_total_row, - apply_group_by_conditions, + apply_order_by_conditions, get_grand_total, get_group_by_and_display_fields, get_tax_accounts, @@ -305,12 +305,6 @@ def apply_conditions(query, pi, pii, filters): if filters.get("item_group"): query = query.where(pii.item_group == filters.get("item_group")) - if not filters.get("group_by"): - query = query.orderby(pi.posting_date, order=Order.desc) - query = query.orderby(pii.item_group, order=Order.desc) - else: - query = apply_group_by_conditions(query, pi, pii, filters) - return query @@ -372,7 +366,17 @@ def get_items(filters, additional_table_columns): query = apply_conditions(query, pi, pii, filters) - return query.run(as_dict=True) + from frappe.desk.reportview import build_match_conditions + + query, params = query.walk() + match_conditions = build_match_conditions("Sales Invoice") + + if match_conditions: + query += " and " + match_conditions + + query = apply_order_by_conditions(query, pi, pii, filters) + + return frappe.db.sql(query, params, as_dict=True) def get_aii_accounts(): diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 604c0a6569d..af2c4e7e38b 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -384,27 +384,24 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): | (si.unrealized_profit_loss_account == filters.get("income_account")) ) - if not filters.get("group_by"): - query = query.orderby(si.posting_date, order=Order.desc) - query = query.orderby(sii.item_group, order=Order.desc) - else: - query = apply_group_by_conditions(query, si, sii, filters) - for key, value in (additional_conditions or {}).items(): query = query.where(si[key] == value) return query -def apply_group_by_conditions(query, si, ii, filters): - if filters.get("group_by") == "Invoice": - query = query.orderby(ii.parent, order=Order.desc) +def apply_order_by_conditions(query, si, ii, filters): + if not filters.get("group_by"): + query += f" order by {si.posting_date} desc, {ii.item_group} desc" + elif filters.get("group_by") == "Invoice": + query += f" order by {ii.parent} desc" elif filters.get("group_by") == "Item": - query = query.orderby(ii.item_code) + query += f" order by {ii.item_code}" elif filters.get("group_by") == "Item Group": - query = query.orderby(ii.item_group) + query += f" order by {ii.item_group}" elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"): - query = query.orderby(si[frappe.scrub(filters.get("group_by"))]) + filter_field = frappe.scrub(filters.get("group_by")) + query += f" order by {filter_field} desc" return query @@ -479,7 +476,17 @@ def get_items(filters, additional_query_columns, additional_conditions=None): query = apply_conditions(query, si, sii, filters, additional_conditions) - return query.run(as_dict=True) + from frappe.desk.reportview import build_match_conditions + + query, params = query.walk() + match_conditions = build_match_conditions("Sales Invoice") + + if match_conditions: + query += " and " + match_conditions + + query = apply_order_by_conditions(query, si, sii, filters) + + return frappe.db.sql(query, params, as_dict=True) def get_delivery_notes_against_sales_order(item_list): diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 48364cc2c91..026aecce036 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -397,7 +397,6 @@ def get_invoices(filters, additional_query_columns): pi.mode_of_payment, ) .where(pi.docstatus == 1) - .orderby(pi.posting_date, pi.name, order=Order.desc) ) if additional_query_columns: @@ -421,8 +420,17 @@ def get_invoices(filters, additional_query_columns): ) query = query.where(pi.credit_to.isin(party_account)) - invoices = query.run(as_dict=True) - return invoices + from frappe.desk.reportview import build_match_conditions + + query, params = query.walk() + match_conditions = build_match_conditions("Purchase Invoice") + + if match_conditions: + query += " and " + match_conditions + + query += " order by posting_date desc, name desc" + + return frappe.db.sql(query, params, as_dict=True) def get_conditions(filters, query, doctype): diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index 34d53238f50..e55f217682d 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -439,7 +439,6 @@ def get_invoices(filters, additional_query_columns): si.company, ) .where(si.docstatus == 1) - .orderby(si.posting_date, si.name, order=Order.desc) ) if additional_query_columns: @@ -457,8 +456,17 @@ def get_invoices(filters, additional_query_columns): filters, query, doctype="Sales Invoice", child_doctype="Sales Invoice Item" ) - invoices = query.run(as_dict=True) - return invoices + from frappe.desk.reportview import build_match_conditions + + query, params = query.walk() + match_conditions = build_match_conditions("Sales Invoice") + + if match_conditions: + query += " and " + match_conditions + + query += " order by posting_date desc, name desc" + + return frappe.db.sql(query, params, as_dict=True) def get_conditions(filters, query, doctype): From 7dfff8d3a2f3374873b0460b43673a871c01d8bf Mon Sep 17 00:00:00 2001 From: Corentin Forler Date: Mon, 31 Mar 2025 13:53:17 +0200 Subject: [PATCH 43/62] fix: Fix fieldtype in UnReconcile dialog (cherry picked from commit 665645721b3cfcdd62a82c9c080799a59071c3e8) --- erpnext/public/js/utils/unreconcile.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/unreconcile.js b/erpnext/public/js/utils/unreconcile.js index b8c1db66cc8..072b541753d 100644 --- a/erpnext/public/js/utils/unreconcile.js +++ b/erpnext/public/js/utils/unreconcile.js @@ -91,7 +91,13 @@ erpnext.accounts.unreconcile_payment = { read_only: 1, options: "account_currency", }, - { label: __("Currency"), fieldname: "account_currency", fieldtype: "Currency", read_only: 1 }, + { + label: __("Currency"), + fieldname: "account_currency", + fieldtype: "Link", + options: "Currency", + read_only: 1, + }, ]; let unreconcile_dialog_fields = [ { From 26611475f6fd63eda6bbe3fea4a3b1c754efe68b Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 7 Apr 2025 17:17:42 +0530 Subject: [PATCH 44/62] fix: restrict customer change if creating from opportunity (cherry picked from commit dc4819e89773c7e74e09928d0d91c45a83a87117) --- erpnext/selling/doctype/quotation/quotation.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 03665b48a85..b9d46e0b84b 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -69,6 +69,8 @@ frappe.ui.form.on("Quotation", { erpnext.selling.QuotationController = class QuotationController extends erpnext.selling.SellingController { onload(doc, dt, dn) { super.onload(doc, dt, dn); + + this.frm.trigger("disable_customer_if_creating_from_opportunity"); } party_name() { var me = this; @@ -373,6 +375,12 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext. ); } } + + disable_customer_if_creating_from_opportunity(doc) { + if (doc.opportunity) { + this.frm.set_df_property("party_name", "read_only", 1); + } + } }; cur_frm.script_manager.make(erpnext.selling.QuotationController); From 1aac8d31f467eb68b0d1fb6f2d3f6584770146db Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 31 Mar 2025 18:06:33 +0530 Subject: [PATCH 45/62] fix: removed customer_group query in customer.js (cherry picked from commit f49adfdd98141ffa555257549eddd4cf240bdc2a) --- erpnext/selling/doctype/customer/customer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 98301fb3ddd..11a3eecb967 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -18,7 +18,6 @@ frappe.ui.form.on("Customer", { frm.add_fetch("lead_name", "company_name", "customer_name"); frm.add_fetch("default_sales_partner", "commission_rate", "default_commission_rate"); - frm.set_query("customer_group", { is_group: 0 }); frm.set_query("default_price_list", { selling: 1 }); frm.set_query("account", "accounts", function (doc, cdt, cdn) { let d = locals[cdt][cdn]; From b824038d3704a114c3021887e4e572671c451877 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 8 Apr 2025 13:32:49 +0530 Subject: [PATCH 46/62] chore: fix conflicts --- erpnext/manufacturing/doctype/bom/bom.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 5aacda01c1b..46bf75780d4 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1496,16 +1496,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): fields = ["name", "item_name", "item_group", "description"] fields.extend([field for field in searchfields if field not in ["name", "item_group", "description"]]) -<<<<<<< HEAD - searchfields = searchfields + [ - field - for field in [searchfield or "name", "item_code", "item_group", "item_name"] - if field not in searchfields - ] -======= if not searchfields: searchfields = ["name"] ->>>>>>> 216bf2456e (fix: removed hardcoded search fields to fix performance issue) query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())} From fb06f886d261c2b674ff3851a5b0bcdde1bdfb0f Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 20 Mar 2025 19:19:47 +0530 Subject: [PATCH 47/62] fix(accounting): update outstanding amount based on update_outstanding_for_self fix(accounting): against voucher has been already paid show proper message and update update_outstanding_for_self as 1 (cherry picked from commit 222f1834f1c4264bdea2a64ecc69a87ef7124106) # Conflicts: # erpnext/controllers/accounts_controller.py --- erpnext/controllers/accounts_controller.py | 46 ++++++++++++++++++++++ erpnext/controllers/taxes_and_totals.py | 3 ++ 2 files changed, 49 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index bcaf8f897f9..d8581f58a4f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -165,6 +165,48 @@ class AccountsController(TransactionBase): raise_exception=1, ) + def validate_against_voucher_outstanding(self): + from frappe.model.meta import get_meta + + if not get_meta(self.doctype).has_field("outstanding_amount"): + return + + if self.get("is_return") and self.return_against and not self.get("is_pos"): + against_voucher_outstanding = frappe.get_value( + self.doctype, self.return_against, "outstanding_amount" + ) + document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note" + + msg = "" + if self.get("update_outstanding_for_self"): + msg = ( + "We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, " + "uncheck '{2}' checkbox.

Or" + ).format( + frappe.bold(document_type), + get_link_to_form(self.doctype, self.get("return_against")), + frappe.bold(_("Update Outstanding for Self")), + ) + + elif not self.update_outstanding_for_self and ( + abs(flt(self.rounded_total) or flt(self.grand_total)) > flt(against_voucher_outstanding) + ): + self.update_outstanding_for_self = 1 + msg = ( + "The outstanding amount {} in {} is lesser than {}. Updating the outstanding to this invoice.

And" + ).format( + against_voucher_outstanding, + get_link_to_form(self.doctype, self.get("return_against")), + flt(abs(self.outstanding_amount)), + ) + + if msg: + msg += " you can use {} tool to reconcile against {} later.".format( + get_link_to_form("Payment Reconciliation"), + get_link_to_form(self.doctype, self.get("return_against")), + ) + frappe.msgprint(_(msg)) + def validate(self): if not self.get("is_return") and not self.get("is_debit_note"): self.validate_qty_is_not_zero() @@ -193,6 +235,7 @@ class AccountsController(TransactionBase): self.disable_tax_included_prices_for_internal_transfer() self.set_incoming_rate() self.init_internal_values() + self.validate_against_voucher_outstanding() # Need to set taxes based on taxes_and_charges template # before calculating taxes and totals @@ -228,6 +271,7 @@ class AccountsController(TransactionBase): ) ) +<<<<<<< HEAD if self.get("is_return") and self.get("return_against") and not self.get("is_pos"): if self.get("update_outstanding_for_self"): document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note" @@ -242,6 +286,8 @@ class AccountsController(TransactionBase): ) ) +======= +>>>>>>> 222f1834f1 (fix(accounting): update outstanding amount based on update_outstanding_for_self) pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid" if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): self.set_advances() diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 091b08cad0f..955c9261031 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -819,9 +819,12 @@ class calculate_taxes_and_totals: if ( self.doc.is_return and self.doc.return_against + and not self.doc.update_outstanding_for_self and not self.doc.get("is_pos") or self.is_internal_invoice() ): + # Do not calculate the outstanding amount for a return invoice if 'update_outstanding_for_self' is not enabled. + self.doc.outstanding_amount = 0 return self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"]) From 88e11d6fd94f6aa0e224b29f50b63d176d689c97 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 20 Mar 2025 19:20:41 +0530 Subject: [PATCH 48/62] test: add unit test to validate outstanding amount for update_outstanding_for_self checkbox enabled (cherry picked from commit 7b0882600a29ddd641115c062e961cedc84477dd) --- .../sales_invoice/test_sales_invoice.py | 29 +++++ .../test_accounts_receivable.py | 106 +++++++++++++++++- 2 files changed, 131 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index fe8342d78bd..d2398d21f4e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -4284,6 +4284,35 @@ class TestSalesInvoice(FrappeTestCase): pos_return = make_sales_return(pos.name) self.assertEqual(abs(pos_return.payments[0].amount), pos.payments[0].amount) + def test_create_return_invoice_for_self_update(self): + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + invoice = create_sales_invoice() + + payment_entry = get_payment_entry(dt=invoice.doctype, dn=invoice.name) + payment_entry.reference_no = "test001" + payment_entry.reference_date = getdate() + + payment_entry.save() + payment_entry.submit() + + r_invoice = make_return_doc(invoice.doctype, invoice.name) + + r_invoice.update_outstanding_for_self = 0 + r_invoice.save() + + self.assertEqual(r_invoice.update_outstanding_for_self, 1) + + r_invoice.submit() + + self.assertNotEqual(r_invoice.outstanding_amount, 0) + + invoice.reload() + + self.assertEqual(invoice.outstanding_amount, 0) + def test_prevents_fully_returned_invoice_with_zero_quantity(self): from erpnext.controllers.sales_and_purchase_return import StockOverReturnError, make_return_doc diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 39ca78153c3..44fee120d8b 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -21,7 +21,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): def tearDown(self): frappe.db.rollback() - def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False): + def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False, **args): frappe.set_user("Administrator") si = create_sales_invoice( item=self.item, @@ -34,6 +34,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): rate=100, price_list_rate=100, do_not_save=1, + **args, ) if not no_payment_schedule: si.append( @@ -108,7 +109,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): self.assertEqual(expected_data[0], [row.invoiced, row.paid, row.credit_note]) pos_inv.cancel() - def test_accounts_receivable(self): + def test_accounts_receivable_with_payment(self): filters = { "company": self.company, "based_on_payment_terms": 1, @@ -145,11 +146,15 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): cr_note = self.create_credit_note(si.name, do_not_submit=True) cr_note.update_outstanding_for_self = False cr_note.save().submit() + + # as the invoice partially paid and returning the full amount so the outstanding amount should be True + self.assertEqual(cr_note.update_outstanding_for_self, True) + report = execute(filters) - expected_data_after_credit_note = [100, 0, 0, 40, -40, self.debit_to] + expected_data_after_credit_note = [0, 0, 100, 0, -100, self.debit_to] - row = report[1][0] + row = report[1][-1] self.assertEqual( expected_data_after_credit_note, [ @@ -162,6 +167,99 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): ], ) + def test_accounts_receivable_without_payment(self): + filters = { + "company": self.company, + "based_on_payment_terms": 1, + "report_date": today(), + "range": "30, 60, 90, 120", + "show_remarks": True, + } + + # check invoice grand total and invoiced column's value for 3 payment terms + si = self.create_sales_invoice() + + report = execute(filters) + + expected_data = [[100, 30, "No Remarks"], [100, 50, "No Remarks"], [100, 20, "No Remarks"]] + + for i in range(3): + row = report[1][i - 1] + self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks]) + + # 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) + cr_note.update_outstanding_for_self = False + cr_note.save().submit() + + self.assertEqual(cr_note.update_outstanding_for_self, False) + + report = execute(filters) + + row = report[1] + self.assertTrue(len(row) == 0) + + def test_accounts_receivable_with_partial_payment(self): + filters = { + "company": self.company, + "based_on_payment_terms": 1, + "report_date": today(), + "range": "30, 60, 90, 120", + "show_remarks": True, + } + + # check invoice grand total and invoiced column's value for 3 payment terms + si = self.create_sales_invoice(qty=2) + + report = execute(filters) + + expected_data = [[200, 60, "No Remarks"], [200, 100, "No Remarks"], [200, 40, "No Remarks"]] + + for i in range(3): + row = report[1][i - 1] + self.assertEqual(expected_data[i - 1], [row.invoice_grand_total, row.invoiced, row.remarks]) + + # check invoice grand total, invoiced, paid and outstanding column's value after payment + self.create_payment_entry(si.name) + report = execute(filters) + + expected_data_after_payment = [[200, 60, 40, 20], [200, 100, 0, 100], [200, 40, 0, 40]] + + for i in range(3): + row = report[1][i - 1] + self.assertEqual( + expected_data_after_payment[i - 1], + [row.invoice_grand_total, row.invoiced, row.paid, row.outstanding], + ) + + # 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) + cr_note.update_outstanding_for_self = False + cr_note.save().submit() + + self.assertFalse(cr_note.update_outstanding_for_self) + + report = execute(filters) + + expected_data_after_credit_note = [ + [200, 100, 0, 80, 20, self.debit_to], + [200, 40, 0, 0, 40, self.debit_to], + ] + + for i in range(2): + row = report[1][i - 1] + self.assertEqual( + expected_data_after_credit_note[i - 1], + [ + row.invoice_grand_total, + row.invoiced, + row.paid, + row.credit_note, + row.outstanding, + row.party_account, + ], + ) + def test_cr_note_flag_to_update_self(self): filters = { "company": self.company, From d6b488f52962a30f0374d9997c242c41b0b8d721 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 8 Apr 2025 14:36:15 +0530 Subject: [PATCH 49/62] chore: resolve conflict --- erpnext/controllers/accounts_controller.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d8581f58a4f..31d157bed08 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -271,23 +271,6 @@ class AccountsController(TransactionBase): ) ) -<<<<<<< HEAD - if self.get("is_return") and self.get("return_against") and not self.get("is_pos"): - if self.get("update_outstanding_for_self"): - document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note" - frappe.msgprint( - _( - "We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, uncheck '{2}' checkbox.

Or you can use {3} tool to reconcile against {1} later." - ).format( - frappe.bold(document_type), - get_link_to_form(self.doctype, self.get("return_against")), - frappe.bold(_("Update Outstanding for Self")), - get_link_to_form("Payment Reconciliation", "Payment Reconciliation"), - ) - ) - -======= ->>>>>>> 222f1834f1 (fix(accounting): update outstanding amount based on update_outstanding_for_self) pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid" if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): self.set_advances() From cf7252d3e732759d8d160335e501fdb0972856ad Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 2 Feb 2024 21:57:45 +0530 Subject: [PATCH 50/62] fix: check payments against orders for getting request amount (cherry picked from commit f7face43cdcc9ef4e9d7a8cd90b0de8fc567d4e3) # Conflicts: # erpnext/accounts/doctype/payment_request/payment_request.py --- .../payment_request/payment_request.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 4f6205a2445..317090e7547 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -3,7 +3,11 @@ import json import frappe from frappe import _, qb from frappe.model.document import Document +<<<<<<< HEAD from frappe.query_builder.functions import Abs, Sum +======= +from frappe.query_builder.functions import Sum +>>>>>>> f7face43cd (fix: check payments against orders for getting request amount) from frappe.utils import flt, nowdate from frappe.utils.background_jobs import enqueue @@ -127,6 +131,8 @@ class PaymentRequest(Document): ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) if not hasattr(ref_doc, "order_type") or ref_doc.order_type != "Shopping Cart": ref_amount = get_amount(ref_doc, self.payment_account) + if not ref_amount: + frappe.throw(_("Payment Entry is already created")) if existing_payment_request_amount + flt(self.grand_total) > ref_amount: frappe.throw( @@ -544,6 +550,8 @@ def make_payment_request(**args): gateway_account = get_gateway_details(args) or frappe._dict() grand_total = get_amount(ref_doc, gateway_account.get("payment_account")) + if not grand_total: + frappe.throw(_("Payment Entry is already created")) if args.loyalty_points and args.dt == "Sales Order": from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points @@ -678,6 +686,7 @@ def get_amount(ref_doc, payment_account=None): dt = ref_doc.doctype if dt in ["Sales Order", "Purchase Order"]: grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) + grand_total -= get_paid_amount_against_order(dt, ref_doc.name) elif dt in ["Sales Invoice", "Purchase Invoice"]: if not ref_doc.get("is_pos"): if ref_doc.party_account_currency == ref_doc.currency: @@ -699,10 +708,14 @@ def get_amount(ref_doc, payment_account=None): elif dt == "Fees": grand_total = ref_doc.outstanding_amount +<<<<<<< HEAD if grand_total > 0: return flt(grand_total, get_currency_precision()) else: frappe.throw(_("Payment Entry is already created")) +======= + return grand_total +>>>>>>> f7face43cd (fix: check payments against orders for getting request amount) def get_irequest_status(payment_requests: None | list = None) -> list: @@ -992,6 +1005,7 @@ def validate_payment(doc, method=None): ) +<<<<<<< HEAD @frappe.whitelist() def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len, filters): # permission checks in `get_list()` @@ -1032,3 +1046,27 @@ def get_irequests_of_payment_request(doc: str | None = None) -> list: }, ) return res +======= +def get_paid_amount_against_order(dt, dn): + pe_ref = frappe.qb.DocType("Payment Entry Reference") + if dt == "Sales Order": + inv_dt, inv_field = "Sales Invoice Item", "sales_order" + else: + inv_dt, inv_field = "Purchase Invoice Item", "purchase_order" + inv_item = frappe.qb.DocType(inv_dt) + return ( + frappe.qb.from_(pe_ref) + .select( + Sum(pe_ref.allocated_amount), + ) + .where( + (pe_ref.docstatus == 1) + & ( + (pe_ref.reference_name == dn) + | pe_ref.reference_name.isin( + frappe.qb.from_(inv_item).select(inv_item.parent).where(inv_item[inv_field] == dn).distinct() + ) + ) + ) + ).run()[0][0] or 0 +>>>>>>> f7face43cd (fix: check payments against orders for getting request amount) From b0302d71b7a001e1298f3cc5c43c793c6525da32 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 2 Feb 2024 21:59:53 +0530 Subject: [PATCH 51/62] fix(portal): payment amount for orders (cherry picked from commit c18ff5bd253878da2f71690f194e8fedd07b2a87) # Conflicts: # erpnext/templates/pages/order.py --- erpnext/templates/pages/order.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py index dca5a0c7497..dff982ed838 100644 --- a/erpnext/templates/pages/order.py +++ b/erpnext/templates/pages/order.py @@ -4,9 +4,13 @@ import frappe from frappe import _ +<<<<<<< HEAD from erpnext.accounts.doctype.payment_request.payment_request import ( ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST, ) +======= +from erpnext.accounts.doctype.payment_request.payment_request import get_amount +>>>>>>> c18ff5bd25 (fix(portal): payment amount for orders) def get_context(context): @@ -50,11 +54,15 @@ def get_context(context): ) context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points")) +<<<<<<< HEAD context.show_pay_button = ( "payments" in frappe.get_installed_apps() and frappe.db.get_single_value("Buying Settings", "show_pay_button") and context.doc.doctype in ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST ) +======= + context.show_pay_button, context.pay_amount = get_payment_details(context.doc) +>>>>>>> c18ff5bd25 (fix(portal): payment amount for orders) context.show_make_pi_button = False if context.doc.get("supplier"): # show Make Purchase Invoice button based on permission @@ -67,3 +75,14 @@ def get_attachments(dt, dn): fields=["name", "file_name", "file_url", "is_private"], filters={"attached_to_name": dn, "attached_to_doctype": dt, "is_private": 0}, ) + + +def get_payment_details(doc): + show_pay_button, amount = ( + "payments" in frappe.get_installed_apps() + and frappe.db.get_single_value("Buying Settings", "show_pay_button") + ), 0 + if not show_pay_button: + return show_pay_button, amount + amount = get_amount(doc) + return bool(amount), amount From b7ae17aaaf18be38be8ac53c826ff4f9a73a9058 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 2 Feb 2024 22:01:42 +0530 Subject: [PATCH 52/62] fix(portal): context pay_amount for button (cherry picked from commit 7efb5a8cb5d581d2ddfcc728f600bf5ba9649ad5) # Conflicts: # erpnext/templates/pages/order.html --- erpnext/templates/pages/order.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 315478fc649..f5e7dadde7f 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -40,7 +40,11 @@

+<<<<<<< HEAD {{ _("Pay", null, "Amount") }} {{doc.get_formatted("grand_total") }} +======= + {{ _("Pay") }} {{ pay_amount }} +>>>>>>> 7efb5a8cb5 (fix(portal): context pay_amount for button)

From 4eb0f39af720b845959b551d70f2129e5ac7a40d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 8 Apr 2025 15:15:16 +0530 Subject: [PATCH 53/62] chore: pass doctype and name --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 31d157bed08..46418ca8a63 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -202,7 +202,7 @@ class AccountsController(TransactionBase): if msg: msg += " you can use {} tool to reconcile against {} later.".format( - get_link_to_form("Payment Reconciliation"), + get_link_to_form("Payment Reconciliation", "Payment Reconciliation"), get_link_to_form(self.doctype, self.get("return_against")), ) frappe.msgprint(_(msg)) From 4d8984e4e962562c768227d72f36326eff8f8fe0 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 8 Apr 2025 15:54:44 +0530 Subject: [PATCH 54/62] fix: resolve conflicts --- .../payment_request/payment_request.py | 34 ------------------- erpnext/templates/pages/order.html | 6 +--- erpnext/templates/pages/order.py | 24 +++++-------- 3 files changed, 10 insertions(+), 54 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 317090e7547..8cb9eb6b5d9 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -3,11 +3,7 @@ import json import frappe from frappe import _, qb from frappe.model.document import Document -<<<<<<< HEAD from frappe.query_builder.functions import Abs, Sum -======= -from frappe.query_builder.functions import Sum ->>>>>>> f7face43cd (fix: check payments against orders for getting request amount) from frappe.utils import flt, nowdate from frappe.utils.background_jobs import enqueue @@ -686,7 +682,6 @@ def get_amount(ref_doc, payment_account=None): dt = ref_doc.doctype if dt in ["Sales Order", "Purchase Order"]: grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) - grand_total -= get_paid_amount_against_order(dt, ref_doc.name) elif dt in ["Sales Invoice", "Purchase Invoice"]: if not ref_doc.get("is_pos"): if ref_doc.party_account_currency == ref_doc.currency: @@ -708,14 +703,10 @@ def get_amount(ref_doc, payment_account=None): elif dt == "Fees": grand_total = ref_doc.outstanding_amount -<<<<<<< HEAD if grand_total > 0: return flt(grand_total, get_currency_precision()) else: frappe.throw(_("Payment Entry is already created")) -======= - return grand_total ->>>>>>> f7face43cd (fix: check payments against orders for getting request amount) def get_irequest_status(payment_requests: None | list = None) -> list: @@ -1005,7 +996,6 @@ def validate_payment(doc, method=None): ) -<<<<<<< HEAD @frappe.whitelist() def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len, filters): # permission checks in `get_list()` @@ -1046,27 +1036,3 @@ def get_irequests_of_payment_request(doc: str | None = None) -> list: }, ) return res -======= -def get_paid_amount_against_order(dt, dn): - pe_ref = frappe.qb.DocType("Payment Entry Reference") - if dt == "Sales Order": - inv_dt, inv_field = "Sales Invoice Item", "sales_order" - else: - inv_dt, inv_field = "Purchase Invoice Item", "purchase_order" - inv_item = frappe.qb.DocType(inv_dt) - return ( - frappe.qb.from_(pe_ref) - .select( - Sum(pe_ref.allocated_amount), - ) - .where( - (pe_ref.docstatus == 1) - & ( - (pe_ref.reference_name == dn) - | pe_ref.reference_name.isin( - frappe.qb.from_(inv_item).select(inv_item.parent).where(inv_item[inv_field] == dn).distinct() - ) - ) - ) - ).run()[0][0] or 0 ->>>>>>> f7face43cd (fix: check payments against orders for getting request amount) diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index f5e7dadde7f..0805a32ae33 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -40,11 +40,7 @@

-<<<<<<< HEAD - {{ _("Pay", null, "Amount") }} {{doc.get_formatted("grand_total") }} -======= - {{ _("Pay") }} {{ pay_amount }} ->>>>>>> 7efb5a8cb5 (fix(portal): context pay_amount for button) + {{ _("Pay", null, "Amount") }} {{ pay_amount }}

diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py index dff982ed838..dcf3b046722 100644 --- a/erpnext/templates/pages/order.py +++ b/erpnext/templates/pages/order.py @@ -4,13 +4,10 @@ import frappe from frappe import _ -<<<<<<< HEAD from erpnext.accounts.doctype.payment_request.payment_request import ( ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST, + get_amount, ) -======= -from erpnext.accounts.doctype.payment_request.payment_request import get_amount ->>>>>>> c18ff5bd25 (fix(portal): payment amount for orders) def get_context(context): @@ -54,15 +51,7 @@ def get_context(context): ) context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points")) -<<<<<<< HEAD - context.show_pay_button = ( - "payments" in frappe.get_installed_apps() - and frappe.db.get_single_value("Buying Settings", "show_pay_button") - and context.doc.doctype in ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST - ) -======= context.show_pay_button, context.pay_amount = get_payment_details(context.doc) ->>>>>>> c18ff5bd25 (fix(portal): payment amount for orders) context.show_make_pi_button = False if context.doc.get("supplier"): # show Make Purchase Invoice button based on permission @@ -79,10 +68,15 @@ def get_attachments(dt, dn): def get_payment_details(doc): show_pay_button, amount = ( - "payments" in frappe.get_installed_apps() - and frappe.db.get_single_value("Buying Settings", "show_pay_button") - ), 0 + ( + "payments" in frappe.get_installed_apps() + and frappe.db.get_single_value("Buying Settings", "show_pay_button") + and doc.doctype in ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST + ), + 0, + ) if not show_pay_button: return show_pay_button, amount + amount = get_amount(doc) return bool(amount), amount From 23c76aa530cd07c428cc23daf17b3bdcfabb025b Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 20 Mar 2025 19:10:36 +0530 Subject: [PATCH 55/62] fix: correct payment request amount (cherry picked from commit 913c60d77b889c3341ec4751307b83ed396e1ff5) # Conflicts: # erpnext/accounts/doctype/payment_request/payment_request.py --- .../payment_request/payment_request.py | 124 +++++++----------- .../payment_request/test_payment_request.py | 25 +++- 2 files changed, 74 insertions(+), 75 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 8cb9eb6b5d9..3f20f0f6849 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -1,9 +1,9 @@ import json import frappe -from frappe import _, qb +from frappe import _ from frappe.model.document import Document -from frappe.query_builder.functions import Abs, Sum +from frappe.query_builder.functions import Sum from frappe.utils import flt, nowdate from frappe.utils.background_jobs import enqueue @@ -12,7 +12,6 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) from erpnext.accounts.doctype.payment_entry.payment_entry import ( - get_company_defaults, get_payment_entry, ) from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate @@ -120,16 +119,14 @@ class PaymentRequest(Document): title=_("Invalid Amount"), ) - existing_payment_request_amount = flt( - get_existing_payment_request_amount(self.reference_doctype, self.reference_name) - ) - ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) if not hasattr(ref_doc, "order_type") or ref_doc.order_type != "Shopping Cart": ref_amount = get_amount(ref_doc, self.payment_account) if not ref_amount: frappe.throw(_("Payment Entry is already created")) + existing_payment_request_amount = flt(get_existing_payment_request_amount(ref_doc)) + if existing_payment_request_amount + flt(self.grand_total) > ref_amount: frappe.throw( _("Total Payment Request amount cannot be greater than {0} amount").format( @@ -558,6 +555,7 @@ def make_payment_request(**args): frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False) grand_total = grand_total - loyalty_amount +<<<<<<< HEAD bank_account = ( get_party_bank_account(args.get("party_type"), args.get("party")) if args.get("party_type") else "" ) @@ -567,10 +565,10 @@ def make_payment_request(**args): {"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0}, ) +======= +>>>>>>> 913c60d77b (fix: correct payment request amount) # fetches existing payment request `grand_total` amount - existing_payment_request_amount = get_existing_payment_request_amount(ref_doc.doctype, ref_doc.name) - - existing_paid_amount = get_existing_paid_amount(ref_doc.doctype, ref_doc.name) + existing_payment_request_amount = get_existing_payment_request_amount(ref_doc) def validate_and_calculate_grand_total(grand_total, existing_payment_request_amount): grand_total -= existing_payment_request_amount @@ -582,7 +580,7 @@ def make_payment_request(**args): if args.order_type == "Shopping Cart": # If Payment Request is in an advanced stage, then create for remaining amount. if get_existing_payment_request_amount( - ref_doc.doctype, ref_doc.name, ["Initiated", "Partially Paid", "Payment Ordered", "Paid"] + ref_doc, ["Initiated", "Partially Paid", "Payment Ordered", "Paid"] ): grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount) else: @@ -591,14 +589,10 @@ def make_payment_request(**args): else: grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount) - if existing_paid_amount: - if ref_doc.party_account_currency == ref_doc.currency: - if ref_doc.conversion_rate: - grand_total -= flt(existing_paid_amount / ref_doc.conversion_rate) - else: - grand_total -= flt(existing_paid_amount) - else: - grand_total -= flt(existing_paid_amount / ref_doc.conversion_rate) + draft_payment_request = frappe.db.get_value( + "Payment Request", + {"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0}, + ) if draft_payment_request: frappe.db.set_value( @@ -606,6 +600,11 @@ def make_payment_request(**args): ) pr = frappe.get_doc("Payment Request", draft_payment_request) else: + bank_account = ( + get_party_bank_account(args.get("party_type"), args.get("party")) + if args.get("party_type") + else "" + ) pr = frappe.new_doc("Payment Request") if not args.get("payment_request_type"): @@ -679,22 +678,35 @@ def make_payment_request(**args): def get_amount(ref_doc, payment_account=None): """get amount based on doctype""" + grand_total = 0 + dt = ref_doc.doctype if dt in ["Sales Order", "Purchase Order"]: - grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) + grand_total = (flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)) - ref_doc.advance_paid elif dt in ["Sales Invoice", "Purchase Invoice"]: - if not ref_doc.get("is_pos"): + if ( + dt == "Sales Invoice" + and ref_doc.is_pos + and ref_doc.payments + and any( + [ + payment.type == "Phone" and payment.account == payment_account + for payment in ref_doc.payments + ] + ) + ): + grand_total = sum( + [ + payment.amount + for payment in ref_doc.payments + if payment.type == "Phone" and payment.account == payment_account + ] + ) + else: if ref_doc.party_account_currency == ref_doc.currency: - grand_total = flt(ref_doc.rounded_total or ref_doc.grand_total) + grand_total = flt(ref_doc.outstanding_amount) else: - grand_total = flt( - flt(ref_doc.base_rounded_total or ref_doc.base_grand_total) / ref_doc.conversion_rate - ) - elif dt == "Sales Invoice": - for pay in ref_doc.payments: - if pay.type == "Phone" and pay.account == payment_account: - grand_total = pay.amount - break + grand_total = flt(flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate) elif dt == "POS Invoice": for pay in ref_doc.payments: if pay.type == "Phone" and pay.account == payment_account: @@ -703,10 +715,7 @@ def get_amount(ref_doc, payment_account=None): elif dt == "Fees": grand_total = ref_doc.outstanding_amount - if grand_total > 0: - return flt(grand_total, get_currency_precision()) - else: - frappe.throw(_("Payment Entry is already created")) + return flt(grand_total, get_currency_precision()) if grand_total > 0 else 0 def get_irequest_status(payment_requests: None | list = None) -> list: @@ -749,7 +758,7 @@ def cancel_old_payment_requests(ref_dt, ref_dn): frappe.db.set_value("Integration Request", ireq.name, "status", "Cancelled") -def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None = None) -> list: +def get_existing_payment_request_amount(ref_doc, statuses: list | None = None) -> list: """ Return the total amount of Payment Requests against a reference document. """ @@ -757,9 +766,9 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None = query = ( frappe.qb.from_(PR) - .select(Sum(PR.grand_total)) - .where(PR.reference_doctype == ref_dt) - .where(PR.reference_name == ref_dn) + .select(Sum(PR.outstanding_amount)) + .where(PR.reference_doctype == ref_doc.doctype) + .where(PR.reference_name == ref_doc.name) .where(PR.docstatus == 1) ) @@ -768,43 +777,12 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None = response = query.run() - return response[0][0] if response[0] else 0 + os_amount_in_transaction_currency = flt(response[0][0] if response[0] else 0) + if ref_doc.currency != ref_doc.party_account_currency: + os_amount_in_transaction_currency = flt(os_amount_in_transaction_currency / ref_doc.conversion_rate) -def get_existing_paid_amount(doctype, name): - PLE = frappe.qb.DocType("Payment Ledger Entry") - PER = frappe.qb.DocType("Payment Entry Reference") - - query = ( - frappe.qb.from_(PLE) - .left_join(PER) - .on( - (PLE.against_voucher_type == PER.reference_doctype) - & (PLE.against_voucher_no == PER.reference_name) - & (PLE.voucher_type == PER.parenttype) - & (PLE.voucher_no == PER.parent) - ) - .select( - Abs(Sum(PLE.amount)).as_("total_amount"), - Abs(Sum(frappe.qb.terms.Case().when(PER.payment_request.isnotnull(), PLE.amount).else_(0))).as_( - "request_paid_amount" - ), - ) - .where( - (PLE.voucher_type.isin([doctype, "Journal Entry", "Payment Entry"])) - & (PLE.against_voucher_type == doctype) - & (PLE.against_voucher_no == name) - & (PLE.delinked == 0) - & (PLE.docstatus == 1) - & (PLE.amount < 0) - ) - ) - - result = query.run() - ledger_amount = flt(result[0][0]) if result else 0 - request_paid_amount = flt(result[0][1]) if result else 0 - - return ledger_amount - request_paid_amount + return os_amount_in_transaction_currency def get_gateway_details(args): # nosemgrep diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index 02ecb85ac4d..c9628bda433 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -313,6 +313,16 @@ class TestPaymentRequest(FrappeTestCase): self.assertEqual(pr.outstanding_amount, 800) self.assertEqual(pr.grand_total, 1000) + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile(r"Payment Request is already created"), + make_payment_request, + dt="Sales Order", + dn=so.name, + mute_email=1, + submit_doc=1, + return_doc=1, + ) # complete payment pe = pr.create_payment_entry() @@ -331,7 +341,7 @@ class TestPaymentRequest(FrappeTestCase): # creating a more payment Request must not allowed self.assertRaisesRegex( frappe.exceptions.ValidationError, - re.compile(r"Payment Request is already created"), + re.compile(r"Payment Entry is already created"), make_payment_request, dt="Sales Order", dn=so.name, @@ -361,6 +371,17 @@ class TestPaymentRequest(FrappeTestCase): self.assertEqual(pr.party_account_currency, "INR") self.assertEqual(pr.status, "Initiated") + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile(r"Payment Request is already created"), + make_payment_request, + dt="Purchase Invoice", + dn=pi.name, + mute_email=1, + submit_doc=1, + return_doc=1, + ) + # to make partial payment pe = pr.create_payment_entry(submit=False) pe.paid_amount = 2000 @@ -389,7 +410,7 @@ class TestPaymentRequest(FrappeTestCase): # creating a more payment Request must not allowed self.assertRaisesRegex( frappe.exceptions.ValidationError, - re.compile(r"Payment Request is already created"), + re.compile(r"Payment Entry is already created"), make_payment_request, dt="Purchase Invoice", dn=pi.name, From e271933e43a9d2b7112004c1f204c48d0280e3b4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 8 Apr 2025 13:22:33 +0530 Subject: [PATCH 56/62] chore: resolve conflict --- .../doctype/payment_request/payment_request.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 3f20f0f6849..0027f0aa8be 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -555,18 +555,6 @@ def make_payment_request(**args): frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False) grand_total = grand_total - loyalty_amount -<<<<<<< HEAD - bank_account = ( - get_party_bank_account(args.get("party_type"), args.get("party")) if args.get("party_type") else "" - ) - - draft_payment_request = frappe.db.get_value( - "Payment Request", - {"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0}, - ) - -======= ->>>>>>> 913c60d77b (fix: correct payment request amount) # fetches existing payment request `grand_total` amount existing_payment_request_amount = get_existing_payment_request_amount(ref_doc) From f2df8e531d345d26c42057f17a84dc4de790bb3b Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 7 Apr 2025 11:06:19 +0530 Subject: [PATCH 57/62] fix: use get instead of dot operator to access dict value (cherry picked from commit 7fb75f04824320a81a601bc25ebe80886d94115c) --- erpnext/controllers/stock_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 90959479fd8..10799744d41 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -774,7 +774,7 @@ class StockController(AccountsController): if row.get("batch_no"): update_values["batch_no"] = None - if row.serial_and_batch_bundle: + if row.get("serial_and_batch_bundle"): update_values["serial_and_batch_bundle"] = None frappe.db.set_value( "Serial and Batch Bundle", row.serial_and_batch_bundle, {"is_cancelled": 1} From ca56150918423c197d3ffcad92d866965ebfd34c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 8 Apr 2025 15:10:42 +0530 Subject: [PATCH 58/62] fix: ignore backflush setting on subcontracting return (cherry picked from commit 7479e1ec32a9572d74fd8cbc0ad91bb35586b7f4) --- erpnext/controllers/subcontracting_controller.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 739e64fa2eb..ba6f604960c 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -743,7 +743,9 @@ class SubcontractingController(StockController): ): continue - if self.doctype == self.subcontract_data.order_doctype or self.backflush_based_on == "BOM": + if self.doctype == self.subcontract_data.order_doctype or ( + self.backflush_based_on == "BOM" or self.is_return + ): for bom_item in self.__get_materials_from_bom( row.item_code, row.bom, row.get("include_exploded_items") ): From 18e9a9881ce963f3013767a238680ec3f5158297 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 8 Apr 2025 02:28:46 +0200 Subject: [PATCH 59/62] fix: make report's "printed on" translatable --- .../report/accounts_receivable/accounts_receivable.html | 2 +- .../bank_reconciliation_statement.html | 2 +- erpnext/accounts/report/financial_statements.html | 2 +- erpnext/accounts/report/general_ledger/general_ledger.html | 2 +- .../supplier_quotation_comparison.html | 6 ++---- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html index 7d8d33c46b4..6ae42853ca3 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html @@ -282,4 +282,4 @@ {% } %} -

{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

\ No newline at end of file +

{%= __("Printed On {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html index eaabc90205f..fb809f2c456 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html @@ -46,4 +46,4 @@ {% } %} -

Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

+

{%= __("Printed On {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

diff --git a/erpnext/accounts/report/financial_statements.html b/erpnext/accounts/report/financial_statements.html index 2bb09cf0dc5..3633b26c052 100644 --- a/erpnext/accounts/report/financial_statements.html +++ b/erpnext/accounts/report/financial_statements.html @@ -67,5 +67,5 @@

- Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %} + {%= __("Printed On {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html index bdea568bdf4..c40c607fbed 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.html +++ b/erpnext/accounts/report/general_ledger/general_ledger.html @@ -79,4 +79,4 @@ {% } %} -

Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

+

{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html index 015b31c2064..869dadb2fc1 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html @@ -124,9 +124,7 @@ -

Analysis Chart

+

{%= __("Analysis Chart") %}

- - -

Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

+

{%= __("Printed On {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

From 7cf83ffce72867fdf5a999c6e148516eed1db8ae Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:01:21 +0200 Subject: [PATCH 60/62] fix: go for lower case "on" because we already have translations for that --- .../report/accounts_receivable/accounts_receivable.html | 2 +- .../bank_reconciliation_statement.html | 2 +- erpnext/accounts/report/financial_statements.html | 2 +- .../supplier_quotation_comparison.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html index 6ae42853ca3..9cae94ff8b4 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html @@ -282,4 +282,4 @@ {% } %} -

{%= __("Printed On {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

+

{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html index fb809f2c456..422b25965f0 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html @@ -46,4 +46,4 @@ {% } %} -

{%= __("Printed On {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

+

{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

diff --git a/erpnext/accounts/report/financial_statements.html b/erpnext/accounts/report/financial_statements.html index 3633b26c052..dc1d0093bce 100644 --- a/erpnext/accounts/report/financial_statements.html +++ b/erpnext/accounts/report/financial_statements.html @@ -67,5 +67,5 @@

- {%= __("Printed On {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %} + {%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html index 869dadb2fc1..f98dff66853 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html @@ -127,4 +127,4 @@

{%= __("Analysis Chart") %}

-

{%= __("Printed On {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

+

{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}

From 7896f8a855a3cd6217e0b36a2517922c562c8080 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:08:40 +0200 Subject: [PATCH 61/62] fix: remove redundant letter head --- .../bank_reconciliation_statement.html | 3 --- .../supplier_quotation_comparison.html | 3 --- 2 files changed, 6 deletions(-) diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html index 422b25965f0..6957ab41681 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html @@ -1,6 +1,3 @@ -
- {%= frappe.boot.letter_heads[frappe.defaults.get_default("letter_head")] %} -

{%= __("Bank Reconciliation Statement") %}

{%= filters.account && (filters.account + ", "+filters.report_date) || "" %} {%= filters.company %}


diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html index f98dff66853..2fb0986c7bb 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html @@ -94,9 +94,6 @@ -
- {%= frappe.boot.letter_heads[frappe.defaults.get_default("letter_head")] %} -

{%= __(report.report_name) %}

{%= filters.item %}

From d94ebd0c781d72c6c4e9f23499635fc0595b0c4f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:56:48 +0200 Subject: [PATCH 62/62] chore: add missing german translation --- erpnext/translations/de.csv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index f2d4e5443ed..5197b33b47f 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -309,7 +309,7 @@ BOM {0} does not belong to Item {1},Stückliste {0} gehört nicht zum Artikel {1 BOM {0} must be active,Stückliste {0} muss aktiv sein, BOM {0} must be submitted,Stückliste {0} muss übertragen werden, Balance,Saldo, -Balance (Dr - Cr),Balance (Dr - Cr), +Balance (Dr - Cr),Saldo (S - H), Balance ({0}),Saldo ({0}), Balance Qty,Bilanzmenge, Balance Sheet,Bilanz, @@ -3890,6 +3890,7 @@ Partially ordered,teilweise geordnete, Please select company first,Bitte wählen Sie zuerst die Firma aus, Please select patient,Bitte wählen Sie Patient, Printed On ,Gedruckt am, +Printed on {0},Gedruckt am {0}, Projected qty,Geplante Menge, Sales person,Vertriebsmitarbeiter, Serial No {0} Created,Seriennummer {0} Erstellt,