From 944dc966bc2c26bf11986d52d5186f4e89fd11cc Mon Sep 17 00:00:00 2001 From: vishakhdesai Date: Thu, 19 Dec 2024 12:27:06 +0530 Subject: [PATCH 01/49] fix: refactor query in get_total_allocated_amount in bank_transaction (cherry picked from commit 6b847cdb623322a64096337dcbe86cdbdc60bb6b) --- .../bank_reconciliation_tool.py | 6 ++- .../bank_transaction/bank_transaction.py | 37 +++++++++++++------ 2 files changed, 30 insertions(+), 13 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 42b1a54dea6..8fb3f36f28e 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -480,8 +480,12 @@ def get_linked_payments( def subtract_allocations(gl_account, vouchers): "Look up & subtract any existing Bank Transaction allocations" copied = [] + + voucher_docs = [(voucher.get("doctype"), voucher.get("name")) for voucher in vouchers] + voucher_allocated_amounts = get_total_allocated_amount(voucher_docs) + for voucher in vouchers: - rows = get_total_allocated_amount(voucher.get("doctype"), voucher.get("name")) + rows = voucher_allocated_amounts.get((voucher.get("doctype"), voucher.get("name"))) filtered_row = list(filter(lambda row: row.get("gl_account") == gl_account, rows)) if amount := None if not filtered_row else filtered_row[0]["total"]: diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index c13dbe445f1..7e509896fec 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -154,10 +154,16 @@ class BankTransaction(Document): """ remaining_amount = self.unallocated_amount to_remove = [] + payment_entry_docs = [(pe.payment_document, pe.payment_entry) for pe in self.payment_entries] + pe_bt_allocations = get_total_allocated_amount(payment_entry_docs) + for payment_entry in self.payment_entries: if payment_entry.allocated_amount == 0.0: unallocated_amount, should_clear, latest_transaction = get_clearance_details( - self, payment_entry + self, + payment_entry, + pe_bt_allocations.get((payment_entry.payment_document, payment_entry.payment_entry)) + or [], ) if 0.0 == unallocated_amount: @@ -232,7 +238,7 @@ def get_doctypes_for_bank_reconciliation(): return frappe.get_hooks("bank_reconciliation_doctypes") -def get_clearance_details(transaction, payment_entry): +def get_clearance_details(transaction, payment_entry, bt_allocations): """ There should only be one bank gle for a voucher. Could be none for a Bank Transaction. @@ -241,7 +247,6 @@ def get_clearance_details(transaction, payment_entry): """ gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account") gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry) - bt_allocations = get_total_allocated_amount(payment_entry.payment_document, payment_entry.payment_entry) unallocated_amount = min( transaction.unallocated_amount, @@ -294,44 +299,52 @@ def get_related_bank_gl_entries(doctype, docname): ) -def get_total_allocated_amount(doctype, docname): +def get_total_allocated_amount(docs): """ Gets the sum of allocations for a voucher on each bank GL account along with the latest bank transaction name & date NOTE: query may also include just saved vouchers/payments but with zero allocated_amount """ + if not docs: + return {} + # nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql result = frappe.db.sql( """ - SELECT total, latest_name, latest_date, gl_account FROM ( + SELECT total, latest_name, latest_date, gl_account, payment_document, payment_entry FROM ( SELECT ROW_NUMBER() OVER w AS rownum, - SUM(btp.allocated_amount) OVER(PARTITION BY ba.account) AS total, + SUM(btp.allocated_amount) OVER(PARTITION BY ba.account, btp.payment_document, btp.payment_entry) AS total, FIRST_VALUE(bt.name) OVER w AS latest_name, FIRST_VALUE(bt.date) OVER w AS latest_date, - ba.account AS gl_account + ba.account AS gl_account, + btp.payment_document, + btp.payment_entry FROM `tabBank Transaction Payments` btp LEFT JOIN `tabBank Transaction` bt ON bt.name=btp.parent LEFT JOIN `tabBank Account` ba ON ba.name=bt.bank_account WHERE - btp.payment_document = %(doctype)s - AND btp.payment_entry = %(docname)s + (btp.payment_document, btp.payment_entry) IN %(docs)s AND bt.docstatus = 1 - WINDOW w AS (PARTITION BY ba.account ORDER BY bt.date desc) + WINDOW w AS (PARTITION BY ba.account, btp.payment_document, btp.payment_entry ORDER BY bt.date DESC) ) temp WHERE rownum = 1 """, - dict(doctype=doctype, docname=docname), + dict(docs=docs), as_dict=True, ) + + payment_allocation_details = {} for row in result: # Why is this *sometimes* a byte string? if isinstance(row["latest_name"], bytes): row["latest_name"] = row["latest_name"].decode() row["latest_date"] = frappe.utils.getdate(row["latest_date"]) - return result + payment_allocation_details.setdefault((row["payment_document"], row["payment_entry"]), []).append(row) + + return payment_allocation_details def get_paid_amount(payment_entry, currency, gl_bank_account): From 61367ee1edb5ebff648750fd1f2886c58acf4198 Mon Sep 17 00:00:00 2001 From: vishakhdesai Date: Thu, 19 Dec 2024 12:55:11 +0530 Subject: [PATCH 02/49] fix: failing tests fixed (cherry picked from commit 2ce07865d36d74ef058d613dfe4714ddfd6b58e6) --- .../bank_reconciliation_tool/bank_reconciliation_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8fb3f36f28e..28038e9212a 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -485,7 +485,7 @@ def subtract_allocations(gl_account, vouchers): voucher_allocated_amounts = get_total_allocated_amount(voucher_docs) for voucher in vouchers: - rows = voucher_allocated_amounts.get((voucher.get("doctype"), voucher.get("name"))) + rows = voucher_allocated_amounts.get((voucher.get("doctype"), voucher.get("name"))) or [] filtered_row = list(filter(lambda row: row.get("gl_account") == gl_account, rows)) if amount := None if not filtered_row else filtered_row[0]["total"]: From 9060e4ce57499b5aed335e3fc4b27cd27f452cef Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:03:13 +0530 Subject: [PATCH 03/49] fix: allow zero valuation rate (backport #44902) (#44910) fix: allow zero valuation rate (#44902) (cherry picked from commit 614a8f106d0cc84f04cd44261d0e2d16a0cca536) Co-authored-by: rohitwaghchaure --- erpnext/stock/stock_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index d11c19446bb..5aaf30c9889 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -937,7 +937,7 @@ class update_entries_after: # else it remains the same as that of previous entry self.wh_data.valuation_rate = new_stock_value / new_stock_qty - if not self.wh_data.valuation_rate and sle.voucher_detail_no: + if self.wh_data.valuation_rate is None and sle.voucher_detail_no: allow_zero_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no) if not allow_zero_rate: self.wh_data.valuation_rate = self.get_fallback_rate(sle) From c24126226660889427bb169d5460e04fa5be7a45 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:58:11 +0530 Subject: [PATCH 04/49] fix: not able to make purchase receipt from SCR (backport #44919) (#44925) * fix: not able to make purchase receipt from SCR (#44919) (cherry picked from commit ab1cca0c406445adf5c4d147e97f4e3a8011bbba) * chore: fix test case --------- Co-authored-by: rohitwaghchaure --- .../subcontracting_receipt.py | 29 ++++++++ .../test_subcontracting_receipt.py | 74 +++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 37cd43ac1f6..300f7a774eb 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -775,6 +775,9 @@ def make_purchase_receipt(source_name, target_doc=None, save=False, submit=False postprocess=post_process, ) + if not target_doc.get("items"): + add_po_items_to_pr(source_doc, target_doc) + if (save or submit) and frappe.has_permission(target_doc.doctype, "create"): target_doc.save() @@ -794,3 +797,29 @@ def make_purchase_receipt(source_name, target_doc=None, save=False, submit=False ) return target_doc + + +def add_po_items_to_pr(scr_doc, target_doc): + fg_items = {(item.item_code, item.purchase_order): item.qty for item in scr_doc.items} + + for (item_code, po_name), fg_qty in fg_items.items(): + po_doc = frappe.get_doc("Purchase Order", po_name) + for item in po_doc.items: + if item.fg_item != item_code: + continue + + qty = (item.stock_qty - item.received_qty) * fg_qty / item.fg_item_qty + if qty: + target_doc.append( + "items", + { + "item_code": item.item_code, + "item_name": item.item_name, + "description": item.description, + "qty": qty, + "rate": item.rate, + "warehouse": item.warehouse, + "purchase_order": item.parent, + "purchase_order_item": item.name, + }, + ) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index e0fa7923ef9..b5d190f7736 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -1137,6 +1137,80 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertEqual(pr_details[0]["total_taxes_and_charges"], 60) + @change_settings("Buying Settings", {"auto_create_purchase_receipt": 1}) + def test_auto_create_purchase_receipt_with_no_reference_of_po_item(self): + from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order + + fg_item = "Subcontracted Item SA1" + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 10, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 5, + }, + ] + + po = create_purchase_order( + rm_items=service_items, + is_subcontracted=1, + supplier_warehouse="_Test Warehouse 1 - _TC", + do_not_submit=True, + ) + po.append( + "taxes", + { + "account_head": "_Test Account Excise Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Excise Duty", + "doctype": "Purchase Taxes and Charges", + "rate": 10, + }, + ) + po.save() + po.submit() + + sco = get_subcontracting_order(po_name=po.name) + for row in sco.items: + row.db_set("purchase_order_item", None) + + sco.reload() + + for row in sco.items: + self.assertFalse(row.purchase_order_item) + + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr = make_subcontracting_receipt(sco.name) + for row in scr.items: + self.assertFalse(row.purchase_order_item) + + scr.items[0].qty = 3 + scr.save() + scr.submit() + + pr_details = frappe.get_all( + "Purchase Receipt", + filters={"subcontracting_receipt": scr.name}, + fields=["name", "total_taxes_and_charges"], + ) + + self.assertTrue(pr_details) + + pr_qty = frappe.db.get_value("Purchase Receipt Item", {"parent": pr_details[0]["name"]}, "qty") + self.assertEqual(pr_qty, 6) + + self.assertEqual(pr_details[0]["total_taxes_and_charges"], 60) + def test_use_serial_batch_fields_for_subcontracting_receipt(self): fg_item = make_item( "Test Subcontracted Item With Batch No", From 042d12b2c14ab689d8676803bc6136f897f57932 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:58:29 +0530 Subject: [PATCH 05/49] fix: material request status (backport #44917) (#44918) * fix: material request status (#44917) (cherry picked from commit 1319ce4bc1e8e77c9580b7b7dd68b5320f5186ff) * chore: fix test case --------- Co-authored-by: rohitwaghchaure --- erpnext/controllers/status_updater.py | 6 ++- .../material_request/material_request_list.js | 6 +++ .../material_request/test_material_request.py | 38 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index e8aa6254d34..e1cd0a1c340 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -126,9 +126,13 @@ status_map = { "Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'", ], + [ + "Partially Received", + "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'", + ], [ "Partially Ordered", - "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1", + "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1 and self.material_request_type != 'Material Transfer'", ], [ "Manufactured", diff --git a/erpnext/stock/doctype/material_request/material_request_list.js b/erpnext/stock/doctype/material_request/material_request_list.js index ee5c9e7b86c..57332aa7730 100644 --- a/erpnext/stock/doctype/material_request/material_request_list.js +++ b/erpnext/stock/doctype/material_request/material_request_list.js @@ -14,6 +14,12 @@ frappe.listview_settings["Material Request"] = { } } else if (doc.docstatus == 1 && flt(doc.per_ordered, precision) == 0) { return [__("Pending"), "orange", "per_ordered,=,0"]; + } else if ( + doc.docstatus == 1 && + flt(doc.per_ordered, precision) < 100 && + doc.material_request_type == "Material Transfer" + ) { + return [__("Partially Received"), "yellow", "per_ordered,<,100"]; } else if (doc.docstatus == 1 && flt(doc.per_ordered, precision) < 100) { return [__("Partially ordered"), "yellow", "per_ordered,<,100"]; } else if (doc.docstatus == 1 && flt(doc.per_ordered, precision) == 100) { diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 4d48d50c9f0..b0ea7964117 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -17,6 +17,7 @@ from erpnext.stock.doctype.material_request.material_request import ( make_supplier_quotation, raise_work_orders, ) +from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse class TestMaterialRequest(FrappeTestCase): @@ -59,6 +60,43 @@ class TestMaterialRequest(FrappeTestCase): self.assertEqual(se.doctype, "Stock Entry") self.assertEqual(len(se.get("items")), len(mr.get("items"))) + def test_partial_make_stock_entry(self): + from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry as _make_stock_entry + + mr = frappe.copy_doc(test_records[0]).insert() + + source_wh = create_warehouse( + warehouse_name="_Test Source Warehouse", + properties={"parent_warehouse": "All Warehouses - _TC"}, + company="_Test Company", + ) + + mr = frappe.get_doc("Material Request", mr.name) + mr.material_request_type = "Material Transfer" + + for row in mr.items: + _make_stock_entry( + item_code=row.item_code, + qty=10, + to_warehouse=source_wh, + company="_Test Company", + rate=100, + ) + + row.from_warehouse = source_wh + row.qty = 10 + + mr.save() + mr.submit() + + se = make_stock_entry(mr.name) + se.get("items")[0].qty = 5 + se.insert() + se.submit() + + mr.reload() + self.assertEqual(mr.status, "Partially Received") + def test_in_transit_make_stock_entry(self): mr = frappe.copy_doc(test_records[0]).insert() From 991a3366a8e19cd9ba4f52fe4e911167842b4786 Mon Sep 17 00:00:00 2001 From: mahsem <137205921+mahsem@users.noreply.github.com> Date: Sat, 21 Dec 2024 13:49:54 +0100 Subject: [PATCH 06/49] fix: relabel rate to tax rate (cherry picked from commit 1eb8b0ceef5b6b4bbbcfa599aa58d7bb4819b1a2) --- erpnext/accounts/doctype/account/account.json | 2 +- .../advance_taxes_and_charges/advance_taxes_and_charges.json | 2 +- .../pos_closing_entry_taxes/pos_closing_entry_taxes.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index 7b56444e635..5f906b54aaa 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -128,7 +128,7 @@ "description": "Rate at which this tax is applied", "fieldname": "tax_rate", "fieldtype": "Float", - "label": "Rate", + "label": "Tax Rate", "oldfieldname": "tax_rate", "oldfieldtype": "Currency" }, diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json index 05b284ae16f..544f4fd6640 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json @@ -101,7 +101,7 @@ "fieldname": "rate", "fieldtype": "Float", "in_list_view": 1, - "label": "Rate", + "label": "Tax Rate", "oldfieldname": "rate", "oldfieldtype": "Currency" }, diff --git a/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.json b/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.json index 42e7d0ef965..7e3e9c259f1 100644 --- a/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.json +++ b/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.json @@ -14,7 +14,7 @@ "fieldname": "rate", "fieldtype": "Percent", "in_list_view": 1, - "label": "Rate", + "label": "Tax Rate", "read_only": 1 }, { From 5cc9e10923a1687bf5ee41e7036acd88de397f26 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:24:57 +0530 Subject: [PATCH 07/49] fix: Show order tax amount in customer currency on the portal (backport #44915) (#44923) fix: Show order tax amount in customer currency on the portal (#44915) (cherry picked from commit b998933ef032f1fb2be78c0e9e4a3ba374db6b57) Co-authored-by: Nabin Hait --- erpnext/templates/includes/order/order_taxes.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html index d7b9620fa0a..42f46ac8d12 100644 --- a/erpnext/templates/includes/order/order_taxes.html +++ b/erpnext/templates/includes/order/order_taxes.html @@ -12,14 +12,14 @@ {% endif %} {% for d in doc.taxes %} - {% if d.base_tax_amount %} + {% if d.tax_amount %}
{{ d.description }}
- {{ d.get_formatted("base_tax_amount") }} + {{ d.get_formatted("tax_amount") }}
From 488d8080c8cd416adcc3249c5f88e505436c805d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:33:09 +0530 Subject: [PATCH 08/49] fix: strings for translation (backport #44816) (#44927) * fix: strings for translation (cherry picked from commit 3be633f6f1446af09631b3ce2253696b146866c2) # Conflicts: # erpnext/accounts/utils.py * fix: resolved conflict --------- Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com> Co-authored-by: Nabin Hait --- .../item_wise_sales_register/item_wise_sales_register.py | 2 +- erpnext/accounts/utils.py | 2 +- .../exponential_smoothing_forecasting.py | 4 ++-- .../report/production_analytics/production_analytics.py | 2 +- .../public/js/bom_configurator/bom_configurator.bundle.js | 8 ++++---- .../stock/doctype/material_request/material_request.js | 2 +- erpnext/templates/includes/footer/footer_extension.html | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 604c0a6569d..f7a2e40b4ba 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 @@ -318,7 +318,7 @@ def get_columns(additional_table_columns, filters): "width": 100, }, { - "label": _("Rate"), + "label": _("Tax Rate"), "fieldname": "rate", "fieldtype": "Float", "options": "currency", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 144039b794f..b3408a073af 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -130,7 +130,7 @@ def get_fiscal_years( else: return ((fy.name, fy.year_start_date, fy.year_end_date),) - error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date)) + error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(_(label), formatdate(transaction_date)) if company: error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company)) diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index 0f5fa959dc5..6e66c9e539d 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -230,8 +230,8 @@ class ForecastingReport(ExponentialSmoothingForecast): "data": { "labels": labels, "datasets": [ - {"name": "Demand", "values": self.total_demand}, - {"name": "Forecast", "values": self.total_forecast}, + {"name": _("Demand"), "values": self.total_demand}, + {"name": _("Forecast"), "values": self.total_forecast}, ], }, "type": "line", diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py index dc2b9ad62f3..e511612d3a3 100644 --- a/erpnext/manufacturing/report/production_analytics/production_analytics.py +++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py @@ -106,7 +106,7 @@ def get_data(filters, columns): for label in labels: work = {} - work["Status"] = label + work["Status"] = _(label) for _dummy, end_date in ranges: period = get_period(end_date, filters) if periodic_data.get(label).get(period): diff --git a/erpnext/public/js/bom_configurator/bom_configurator.bundle.js b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js index 21aa70fe7ae..b1019f67ca9 100644 --- a/erpnext/public/js/bom_configurator/bom_configurator.bundle.js +++ b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js @@ -147,10 +147,10 @@ class BOMConfigurator { if (!node.expanded) { view.tree.load_children(node, true); $(node.parent[0]).find(".tree-children").show(); - node.$toolbar.find(".expand-all-btn").html("Collapse All"); + node.$toolbar.find(".expand-all-btn").html(__("Collapse All")); } else { node.$tree_link.trigger("click"); - node.$toolbar.find(".expand-all-btn").html("Expand All"); + node.$toolbar.find(".expand-all-btn").html(__("Expand All")); } }, condition: function (node) { @@ -190,10 +190,10 @@ class BOMConfigurator { if (!node.expanded) { view.tree.load_children(node, true); $(node.parent[0]).find(".tree-children").show(); - node.$toolbar.find(".expand-all-btn").html("Collapse All"); + node.$toolbar.find(".expand-all-btn").html(__("Collapse All")); } else { node.$tree_link.trigger("click"); - node.$toolbar.find(".expand-all-btn").html("Expand All"); + node.$toolbar.find(".expand-all-btn").html(__("Expand All")); } }, condition: function (node) { diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 09c01a0ee88..83b63e225b3 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -322,7 +322,7 @@ frappe.ui.form.on("Material Request", { default: 1, }, ], - primary_action_label: "Get Items", + primary_action_label: __("Get Items"), primary_action(values) { if (!values) return; values["company"] = frm.doc.company; diff --git a/erpnext/templates/includes/footer/footer_extension.html b/erpnext/templates/includes/footer/footer_extension.html index 0072dc280c7..11e0adaa2ee 100644 --- a/erpnext/templates/includes/footer/footer_extension.html +++ b/erpnext/templates/includes/footer/footer_extension.html @@ -17,7 +17,7 @@ frappe.ready(function() { if($("#footer-subscribe-email").val() && validate_email($("#footer-subscribe-email").val())) { $("#footer-subscribe-email").attr('disabled', true); - $("#footer-subscribe-button").html("Sending...") + $("#footer-subscribe-button").html(__("Sending...")) .attr("disabled", true); erpnext.subscribe_to_newsletter({ email: $("#footer-subscribe-email").val(), From 6cc70605fa4d4b61b329a323aec49b6005ad7af4 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Tue, 24 Dec 2024 16:38:51 +0530 Subject: [PATCH 09/49] fix: clear payment schedule in purchase invoice for is_paid (cherry picked from commit e1fc239f3d6e737d1acf022db274fedf4a396af0) --- .../doctype/purchase_invoice/purchase_invoice.js | 2 ++ erpnext/controllers/accounts_controller.py | 14 ++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index be429689b5a..dd8758e68a3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -399,6 +399,8 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. hide_fields(this.frm.doc); if (cint(this.frm.doc.is_paid)) { this.frm.set_value("allocate_advances_automatically", 0); + this.frm.set_value("payment_terms_template", ""); + this.frm.set_value("payment_schedule", []); if (!this.frm.doc.company) { this.frm.set_value("is_paid", 0); frappe.msgprint(__("Please specify Company to proceed")); diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 20b5885bfdc..c09475db9bd 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -464,10 +464,14 @@ class AccountsController(TransactionBase): ) def validate_invoice_documents_schedule(self): - if self.is_return: + if ( + self.is_return + or (self.doctype == "Purchase Invoice" and self.is_paid) + or (self.doctype == "Sales Invoice" and self.is_pos) + or self.get("is_opening") == "Yes" + ): self.payment_terms_template = "" self.payment_schedule = [] - return self.validate_payment_schedule_dates() self.set_due_date() @@ -2342,9 +2346,6 @@ class AccountsController(TransactionBase): dates = [] li = [] - if self.doctype == "Sales Invoice" and self.is_pos: - return - for d in self.get("payment_schedule"): if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date): frappe.throw( @@ -2361,9 +2362,6 @@ class AccountsController(TransactionBase): frappe.throw(_("Rows with duplicate due dates in other rows were found: {0}").format(duplicates)) def validate_payment_schedule_amount(self): - if (self.doctype == "Sales Invoice" and self.is_pos) or self.get("is_opening") == "Yes": - return - party_account_currency = self.get("party_account_currency") if not party_account_currency: party_type, party = self.get_party() From 508435ac9feb2ca429430766bfea5cafa916378e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 27 Dec 2024 12:48:25 +0530 Subject: [PATCH 10/49] refactor: early return is always better validate_advance_entries() has a heavy IO bound operation. Early return on unwanted cases is always better. (cherry picked from commit 0589fa7f3efcc826d856be71c20c7ad684f1c822) --- erpnext/controllers/accounts_controller.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c09475db9bd..d87a11f79f4 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -473,6 +473,9 @@ class AccountsController(TransactionBase): self.payment_terms_template = "" self.payment_schedule = [] + if self.is_return: + return + self.validate_payment_schedule_dates() self.set_due_date() self.set_payment_schedule() @@ -2346,6 +2349,9 @@ class AccountsController(TransactionBase): dates = [] li = [] + if self.doctype == "Sales Invoice" and self.is_pos: + return + for d in self.get("payment_schedule"): if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date): frappe.throw( @@ -2362,6 +2368,9 @@ class AccountsController(TransactionBase): frappe.throw(_("Rows with duplicate due dates in other rows were found: {0}").format(duplicates)) def validate_payment_schedule_amount(self): + if (self.doctype == "Sales Invoice" and self.is_pos) or self.get("is_opening") == "Yes": + return + party_account_currency = self.get("party_account_currency") if not party_account_currency: party_type, party = self.get_party() From 6c206c1cb324133195913b5b00cf1a88858dd162 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 25 Dec 2024 15:22:53 +0530 Subject: [PATCH 11/49] fix: ignore duplicate while creating default templates (cherry picked from commit 9368485594f3f42f201be768aa2c79a21ad72a5d) --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 6561f386c55..f8cd61d50ea 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -292,7 +292,7 @@ def get_or_create_tax_group(company_name, root_type): tax_group_account.flags.ignore_links = True tax_group_account.flags.ignore_validate = True - tax_group_account.insert(ignore_permissions=True) + tax_group_account.insert(ignore_permissions=True, ignore_if_duplicate=True) tax_group_name = tax_group_account.name From 2f279a6eb417af7d43608fc699fe64ae1c9aaae3 Mon Sep 17 00:00:00 2001 From: Navin R C <40256832+navinrc@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:49:38 +0530 Subject: [PATCH 12/49] fix: SQL syntax error in Purchase Receipt query for empty filters (#44636) fix(po-analysis): handle SQL error due to empty data in IN() clause Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> (cherry picked from commit 48b49cdea43684b56307e7a353e5bf77e8c32013) --- .../purchase_order_analysis/purchase_order_analysis.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index 6d2034d1878..75658e28780 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -103,6 +103,11 @@ def get_received_amount_data(data): pr = frappe.qb.DocType("Purchase Receipt") pr_item = frappe.qb.DocType("Purchase Receipt Item") + po_items = [row.name for row in data] + + if not po_items: + return frappe._dict() + query = ( frappe.qb.from_(pr) .inner_join(pr_item) @@ -111,12 +116,10 @@ def get_received_amount_data(data): pr_item.purchase_order_item, Sum(pr_item.base_amount).as_("received_qty_amount"), ) - .where((pr_item.parent == pr.name) & (pr.docstatus == 1)) + .where((pr.docstatus == 1) & (pr_item.purchase_order_item.isin(po_items))) .groupby(pr_item.purchase_order_item) ) - query = query.where(pr_item.purchase_order_item.isin([row.name for row in data])) - data = query.run() if not data: From f83112520d4cfd9d8ddcd6d102765b5187659e2f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 30 Dec 2024 11:01:55 +0530 Subject: [PATCH 13/49] refactor(test): make manufacturing test idempotent (cherry picked from commit f3be246df36e3e5295db3e3794c87c8dafe8c0fc) --- erpnext/manufacturing/report/test_reports.py | 102 ++++++++++--------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/erpnext/manufacturing/report/test_reports.py b/erpnext/manufacturing/report/test_reports.py index 3e20f310ff9..f9c3154a63b 100644 --- a/erpnext/manufacturing/report/test_reports.py +++ b/erpnext/manufacturing/report/test_reports.py @@ -4,61 +4,67 @@ import frappe from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report -DEFAULT_FILTERS = { - "company": "_Test Company", - "from_date": "2010-01-01", - "to_date": "2030-01-01", - "warehouse": "_Test Warehouse - _TC", -} - - -REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [ - ("BOM Explorer", {"bom": frappe.get_last_doc("BOM").name}), - ("BOM Operations Time", {}), - ("BOM Stock Calculated", {"bom": frappe.get_last_doc("BOM").name, "qty_to_make": 2}), - ("BOM Stock Report", {"bom": frappe.get_last_doc("BOM").name, "qty_to_produce": 2}), - ("Cost of Poor Quality Report", {"item": "_Test Item", "serial_no": "00"}), - ("Downtime Analysis", {}), - ( - "Exponential Smoothing Forecasting", - { - "based_on_document": "Sales Order", - "based_on_field": "Qty", - "no_of_years": 3, - "periodicity": "Yearly", - "smoothing_constant": 0.3, - }, - ), - ("Job Card Summary", {"fiscal_year": "2021-2022"}), - ("Production Analytics", {"range": "Monthly"}), - ("Quality Inspection Summary", {}), - ("Process Loss Report", {}), - ("Work Order Stock Report", {}), - ("Work Order Summary", {"fiscal_year": "2021-2022", "age": 0}), -] - - -if frappe.db.a_row_exists("Production Plan"): - REPORT_FILTER_TEST_CASES.append( - ("Production Plan Summary", {"production_plan": frappe.get_last_doc("Production Plan").name}) - ) - -OPTIONAL_FILTERS = { - "warehouse": "_Test Warehouse - _TC", - "item": "_Test Item", - "item_group": "_Test Item Group", -} - class TestManufacturingReports(unittest.TestCase): + def setUp(self): + self.setup_default_filters() + + def tearDown(self): + frappe.db.rollback() + + def setup_default_filters(self): + self.last_bom = frappe.get_last_doc("BOM").name + self.DEFAULT_FILTERS = { + "company": "_Test Company", + "from_date": "2010-01-01", + "to_date": "2030-01-01", + "warehouse": "_Test Warehouse - _TC", + } + + self.REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [ + ("BOM Explorer", {"bom": self.last_bom}), + ("BOM Operations Time", {}), + ("BOM Stock Calculated", {"bom": self.last_bom, "qty_to_make": 2}), + ("BOM Stock Report", {"bom": self.last_bom, "qty_to_produce": 2}), + ("Cost of Poor Quality Report", {"item": "_Test Item", "serial_no": "00"}), + ("Downtime Analysis", {}), + ( + "Exponential Smoothing Forecasting", + { + "based_on_document": "Sales Order", + "based_on_field": "Qty", + "no_of_years": 3, + "periodicity": "Yearly", + "smoothing_constant": 0.3, + }, + ), + ("Job Card Summary", {"fiscal_year": "2021-2022"}), + ("Production Analytics", {"range": "Monthly"}), + ("Quality Inspection Summary", {}), + ("Process Loss Report", {}), + ("Work Order Stock Report", {}), + ("Work Order Summary", {"fiscal_year": "2021-2022", "age": 0}), + ] + + if frappe.db.a_row_exists("Production Plan"): + self.REPORT_FILTER_TEST_CASES.append( + ("Production Plan Summary", {"production_plan": frappe.get_last_doc("Production Plan").name}) + ) + + self.OPTIONAL_FILTERS = { + "warehouse": "_Test Warehouse - _TC", + "item": "_Test Item", + "item_group": "_Test Item Group", + } + def test_execute_all_manufacturing_reports(self): """Test that all script report in manufacturing modules are executable with supported filters""" - for report, filter in REPORT_FILTER_TEST_CASES: + for report, filter in self.REPORT_FILTER_TEST_CASES: with self.subTest(report=report): execute_script_report( report_name=report, module="Manufacturing", filters=filter, - default_filters=DEFAULT_FILTERS, - optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None, + default_filters=self.DEFAULT_FILTERS, + optional_filters=self.OPTIONAL_FILTERS if filter.get("_optional") else None, ) From a11f7d5a8267d7cdace91eae159e74f4391eb9c4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 30 Dec 2024 12:18:09 +0530 Subject: [PATCH 14/49] chore: fix linter --- erpnext/accounts/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index b3408a073af..51b4ed248ce 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -130,7 +130,9 @@ def get_fiscal_years( else: return ((fy.name, fy.year_start_date, fy.year_end_date),) - error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(_(label), formatdate(transaction_date)) + error_msg = _("""{0} {1} is not in any active Fiscal Year""").format( + _(label), formatdate(transaction_date) + ) if company: error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company)) From cfa432dbcae7c5bfec6ec8be83229211de9c639c Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 25 Dec 2024 18:14:10 +0530 Subject: [PATCH 15/49] fix: set/update billing address on change of company (cherry picked from commit 0adfebee85c58bf0585b259f28f803ad852af182) --- .../accounts/doctype/purchase_invoice/purchase_invoice.js | 8 ++++++++ erpnext/public/js/controllers/transaction.js | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index dd8758e68a3..4a96c3a84e1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -719,6 +719,14 @@ frappe.ui.form.on("Purchase Invoice", { if (response) frm.set_value("credit_to", response.message); }, }); + + frappe.call({ + method: "erpnext.setup.doctype.company.company.get_default_company_address", + args: { name: frm.doc.company }, + callback: (r) => { + frm.set_value("billing_address", r.message || ""); + }, + }); } }, }); diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 18cddd7f7a1..4e573234bb1 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -959,7 +959,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let is_drop_ship = me.frm.doc.items.some(item => item.delivered_by_supplier); if (!is_drop_ship) { - console.log('get_shipping_address'); erpnext.utils.get_shipping_address(this.frm, function() { set_party_account(set_pricing); }); From 98631eb266889a309961747ee3a2c583aa7c8a63 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Fri, 27 Dec 2024 12:35:36 +0530 Subject: [PATCH 16/49] fix: move code from purchase invoice to buying controller (cherry picked from commit cb197fd01f9bd7535dc1dd0a5f8e259e03f13fd5) --- .../doctype/purchase_invoice/purchase_invoice.js | 8 -------- erpnext/public/js/controllers/buying.js | 12 ++++++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 4a96c3a84e1..dd8758e68a3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -719,14 +719,6 @@ frappe.ui.form.on("Purchase Invoice", { if (response) frm.set_value("credit_to", response.message); }, }); - - frappe.call({ - method: "erpnext.setup.doctype.company.company.get_default_company_address", - args: { name: frm.doc.company }, - callback: (r) => { - frm.set_value("billing_address", r.message || ""); - }, - }); } }, }); diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 202efe157f0..c2c86fca960 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -145,6 +145,18 @@ erpnext.buying = { }); } + company(){ + if(this.frm.doc.doctype == "Material Request") return; + + frappe.call({ + method: "erpnext.setup.doctype.company.company.get_default_company_address", + args: { name: this.frm.doc.company, existing_address:this.frm.doc.billing_address }, + callback: (r) => { + this.frm.set_value("billing_address", r.message || ""); + }, + }); + } + supplier_address() { erpnext.utils.get_address_display(this.frm); erpnext.utils.set_taxes_from_address(this.frm, "supplier_address", "supplier_address", "supplier_address"); From e582ff862e09fdcf024f1df64fbd48945a90002f Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Fri, 27 Dec 2024 12:53:41 +0530 Subject: [PATCH 17/49] fix: use meta to check field instead of doctype (cherry picked from commit 187c74ae0928de27d91cd453fc11a97d4797fbba) --- erpnext/public/js/controllers/buying.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index c2c86fca960..5a8b63f601f 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -146,7 +146,7 @@ erpnext.buying = { } company(){ - if(this.frm.doc.doctype == "Material Request") return; + if(!frappe.meta.has_field(this.frm.doc.doctype, "billing_address")) return; frappe.call({ method: "erpnext.setup.doctype.company.company.get_default_company_address", From 2feeebb5fbe7972b8b638d65b54f441e5cc16b27 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 26 Dec 2024 17:18:39 +0530 Subject: [PATCH 18/49] fix: get item tax template based on posting date (cherry picked from commit 976e35d5472c1c8cae2b3f3a28dd4487e8ac577e) --- erpnext/stock/get_item_details.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index d5d492a2c93..17a8fe2cb6a 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -592,7 +592,10 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): if tax.valid_from or tax.maximum_net_rate: # In purchase Invoice first preference will be given to supplier invoice date # if supplier date is not present then posting date - validation_date = args.get("bill_date") or args.get("transaction_date") + + validation_date = ( + args.get("bill_date") or args.get("posting_date") or args.get("transaction_date") + ) if getdate(tax.valid_from) <= getdate(validation_date) and is_within_valid_range(args, tax): taxes_with_validity.append(tax) From 7dd2b0c189f2fd0d0c3ab62dc4554dc53521df73 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 13:56:55 +0530 Subject: [PATCH 19/49] fix: pos payment using non-default mode of payment (backport #44920) (#44971) fix: pos payment using non-default mode of payment (#44920) * fix: pos payment using non-default mode of payment (#41108) * fix: included css syntax * refactor: created a function to sanitize the class name * refactor: reusing method to sanitize class name * refactor: function rename (cherry picked from commit 98cbb7e900237d6985d97e266e7486a55dea9cc8) Co-authored-by: Diptanil Saha --- .../selling/page/point_of_sale/pos_payment.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index bea1918fa20..92349d27aca 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -235,7 +235,7 @@ erpnext.PointOfSale.Payment = class { frappe.ui.form.on("Sales Invoice Payment", "amount", (frm, cdt, cdn) => { // for setting correct amount after loyalty points are redeemed const default_mop = locals[cdt][cdn]; - const mode = default_mop.mode_of_payment.replace(/ +/g, "_").toLowerCase(); + const mode = this.sanitize_mode_of_payment(default_mop.mode_of_payment); if (this[`${mode}_control`] && this[`${mode}_control`].get_value() != default_mop.amount) { this[`${mode}_control`].set_value(default_mop.amount); } @@ -388,7 +388,7 @@ erpnext.PointOfSale.Payment = class { this.$payment_modes.html( `${payments .map((p, i) => { - const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase(); + const mode = this.sanitize_mode_of_payment(p.mode_of_payment); const payment_type = p.type; const margin = i % 2 === 0 ? "pr-2" : "pl-2"; const amount = p.amount > 0 ? format_currency(p.amount, currency) : ""; @@ -407,7 +407,7 @@ erpnext.PointOfSale.Payment = class { ); payments.forEach((p) => { - const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase(); + const mode = this.sanitize_mode_of_payment(p.mode_of_payment); const me = this; this[`${mode}_control`] = frappe.ui.form.make_control({ df: { @@ -442,7 +442,7 @@ erpnext.PointOfSale.Payment = class { const doc = this.events.get_frm().doc; const payments = doc.payments; payments.forEach((p) => { - const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase(); + const mode = this.sanitize_mode_of_payment(p.mode_of_payment); if (p.default) { setTimeout(() => { this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click(); @@ -612,4 +612,12 @@ erpnext.PointOfSale.Payment = class { toggle_component(show) { show ? this.$component.css("display", "flex") : this.$component.css("display", "none"); } + + sanitize_mode_of_payment(mode_of_payment) { + return mode_of_payment + .replace(/ +/g, "_") + .replace(/[^\p{L}\p{N}_-]/gu, "") + .replace(/^[^_a-zA-Z\p{L}]+/u, "") + .toLowerCase(); + } }; From 40f46b76fa748417bb4fe03a88e54b01eb0ab739 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 13:58:58 +0530 Subject: [PATCH 20/49] fix (pos closing entry): validation for 100 pc discount on pos invoice (backport #44899) (#44930) fix (pos closing entry): validation for 100 pc discount on pos invoice (#44899) (cherry picked from commit cfcc24a3415f6a88b0c1988ad12a5cf2158ab8d9) Co-authored-by: Diptanil Saha <50792171+diptanilsaha@users.noreply.github.com> --- 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 dc2b44e7527..cb501c1ffbc 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -507,7 +507,7 @@ class SalesInvoice(SellingController): frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total)) def validate_pos_paid_amount(self): - if len(self.payments) == 0 and self.is_pos: + if len(self.payments) == 0 and self.is_pos and flt(self.grand_total) > 0: frappe.throw(_("At least one mode of payment is required for POS invoice.")) def check_if_consolidated_invoice(self): From cc827c8077bd0f358ee23c01e92c2ef168c4b4d8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 13:59:42 +0530 Subject: [PATCH 21/49] fix: fetch advance payment entries on pos invoice (backport #44856) (#44931) fix: fetch advance payment entries on pos invoice (cherry picked from commit a7078e5702aa620e577b51d6062c6908153f2b11) Co-authored-by: Diptanil Saha --- 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 d87a11f79f4..f9c875477b9 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1226,7 +1226,7 @@ class AccountsController(TransactionBase): party_account = [] default_advance_account = None - if self.doctype == "Sales Invoice": + if self.doctype in ["Sales Invoice", "POS Invoice"]: party_type = "Customer" party = self.customer amount_field = "credit_in_account_currency" From 28442f34148adcc2cf0b9a301c490a071ec791e5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:00:08 +0530 Subject: [PATCH 22/49] fix: limit discount value to 100 in pos cart (backport #44916) (#44932) fix: limit discount value to 100 in pos cart (#44916) * fix: limit discount value to 100 in pos cart * fix: error message on invalid discount (cherry picked from commit ac26622d6e403b956a11f449488b444ef3e65554) Co-authored-by: Diptanil Saha <50792171+diptanilsaha@users.noreply.github.com> --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 6342b237f6e..325f7b258a9 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -390,6 +390,14 @@ erpnext.PointOfSale.ItemCart = class { input_class: "input-xs", onchange: function () { this.value = flt(this.value); + if (this.value > 100) { + frappe.msgprint({ + title: __("Invalid Discount"), + indicator: "red", + message: __("Discount cannot be greater than 100%."), + }); + this.value = 0; + } frappe.model.set_value( frm.doc.doctype, frm.doc.name, From 583182180a25c94ae4d3ee2eca4db8cad726af6a Mon Sep 17 00:00:00 2001 From: Himanshu Shivhare Date: Thu, 5 Dec 2024 15:46:46 +0530 Subject: [PATCH 23/49] refactor: Order Number and Order Date fields to Blanket Order (cherry picked from commit 5a284df51dd171e7c6e62a7257dc456ee754c573) # Conflicts: # erpnext/manufacturing/doctype/blanket_order/blanket_order.json --- .../doctype/blanket_order/blanket_order.json | 18 ++++++++++++++++++ .../doctype/blanket_order/blanket_order.py | 2 ++ 2 files changed, 20 insertions(+) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json index a63fc4da69a..38f5a5e8276 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json @@ -13,6 +13,8 @@ "supplier", "supplier_name", "column_break_8", + "order_no", + "order_date", "from_date", "to_date", "company", @@ -129,15 +131,31 @@ "fieldname": "terms", "fieldtype": "Text Editor", "label": "Terms and Conditions Details" + }, + { + "fieldname": "order_no", + "fieldtype": "Data", + "label": "Order No" + }, + { + "depends_on": "eval:doc.order_no", + "fieldname": "order_date", + "fieldtype": "Date", + "label": "Order Date" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2021-06-29 00:30:30.621636", +======= + "modified": "2024-12-05 15:44:21.520093", +>>>>>>> 5a284df51d (refactor: Order Number and Order Date fields to Blanket Order) "modified_by": "Administrator", "module": "Manufacturing", "name": "Blanket Order", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index 5ecf9d3a9d6..26fce935abb 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -31,6 +31,8 @@ class BlanketOrder(Document): from_date: DF.Date items: DF.Table[BlanketOrderItem] naming_series: DF.Literal["MFG-BLR-.YYYY.-"] + order_date: DF.Date | None + order_no: DF.Data | None supplier: DF.Link | None supplier_name: DF.Data | None tc_name: DF.Link | None From 7b907424095b38fab9db9902b9d8424d08ee36d6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 30 Dec 2024 14:50:23 +0530 Subject: [PATCH 24/49] chore: resolve conflict --- .../manufacturing/doctype/blanket_order/blanket_order.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json index 38f5a5e8276..0e2e7273f10 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json @@ -147,11 +147,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2021-06-29 00:30:30.621636", -======= "modified": "2024-12-05 15:44:21.520093", ->>>>>>> 5a284df51d (refactor: Order Number and Order Date fields to Blanket Order) "modified_by": "Administrator", "module": "Manufacturing", "name": "Blanket Order", From c19725ca74c5a891c08f4f4861794cb2ebea8aa0 Mon Sep 17 00:00:00 2001 From: DHINESH00 <18csa09@karpagamtech.ac.in> Date: Wed, 18 Dec 2024 10:51:04 +0530 Subject: [PATCH 25/49] fix: apply discount on qty change (cherry picked from commit 352b82bc0b51dbef98a686348bc346252a8e8b19) --- erpnext/selling/page/point_of_sale/pos_item_details.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index ad4b4cd15be..2c93a0d546b 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -315,8 +315,12 @@ erpnext.PointOfSale.ItemDetails = class { frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => { const field_control = this[`${fieldname}_control`]; const item_row_is_being_edited = this.compare_with_current_item(item_row); - - if (item_row_is_being_edited && field_control && field_control.get_value() !== value) { + if ( + item_row_is_being_edited && + field_control && + field_control.get_value() !== value && + value == item_row[fieldname] + ) { field_control.set_value(value); cur_pos.update_cart_html(item_row); } From d550b433c1e3f32c0c8efee0be026b174511c8f6 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Mon, 30 Dec 2024 14:18:13 +0530 Subject: [PATCH 26/49] fix: add company filter to project (cherry picked from commit 1a7b09e576ae47a654606b286d632b26f80fda59) --- .../accounts/doctype/payment_entry/payment_entry.js | 12 ++++++++++++ erpnext/public/js/controllers/buying.js | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 8fd5f00b583..4a45007d50a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -27,6 +27,18 @@ frappe.ui.form.on("Payment Entry", { erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); + // project excluded in setup_dimension_filters + frm.set_query("project", function (doc) { + let filters = { + company: doc.company, + }; + if (doc.party_type == "Customer") filters.customer = doc.party; + return { + query: "erpnext.controllers.queries.get_project_name", + filters, + }; + }); + if (frm.is_new()) { set_default_party_type(frm); } diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 5a8b63f601f..af61d5f0258 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -25,6 +25,14 @@ erpnext.buying = { }; }); + this.frm.set_query("project", function (doc) { + return { + filters: { + company: doc.company, + }, + }; + }); + if (this.frm.doc.__islocal && frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")) { From 74220430e537c40ca6b0af1dffe0412f23ef8c99 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Mon, 30 Dec 2024 14:18:55 +0530 Subject: [PATCH 27/49] fix: include company in filter condition (cherry picked from commit b92f8bc5149a8db7d6d095687576325b013df1d9) --- erpnext/controllers/queries.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 463cb859970..03852d3739a 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -271,10 +271,14 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): qb_filter_or_conditions = [] ifelse = CustomFunction("IF", ["condition", "then", "else"]) - if filters and filters.get("customer"): - qb_filter_and_conditions.append( - (proj.customer == filters.get("customer")) | proj.customer.isnull() | proj.customer == "" - ) + if filters: + if filters.get("customer"): + qb_filter_and_conditions.append( + (proj.customer == filters.get("customer")) | proj.customer.isnull() | proj.customer == "" + ) + + if filters.get("company"): + qb_filter_and_conditions.append(proj.company == filters.get("company")) qb_filter_and_conditions.append(proj.status.notin(["Completed", "Cancelled"])) From 1353a14a6b4c58b96916b3bf4f7a27e85716ab79 Mon Sep 17 00:00:00 2001 From: mahsem <137205921+mahsem@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:30:00 +0100 Subject: [PATCH 28/49] fix: in_contex_translation_fixes (cherry picked from commit a87e7fde031393607b384138b0927d0b4c36322b) # Conflicts: # erpnext/accounts/utils.py # erpnext/templates/pages/order.html --- .../closing_voucher_details.html | 10 ++--- erpnext/accounts/utils.py | 42 +++++++++++++++++++ erpnext/templates/pages/order.html | 4 ++ 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html b/erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html index 983f49563cd..5bc4e7a1587 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html +++ b/erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html @@ -12,15 +12,15 @@ - {{ _('Grand Total') }} + {{ _("Grand Total") }} {{ frappe.utils.fmt_money(data.grand_total or '', currency=currency) }} - {{ _('Net Total') }} + {{ _("Net Total") }} {{ frappe.utils.fmt_money(data.net_total or '', currency=currency) }} - {{ _('Total Quantity') }} + {{ _("Total Quantity") }} {{ data.total_quantity or '' }} @@ -44,7 +44,7 @@ {% for d in data.payment_reconciliation %} - {{ d.mode_of_payment }} + {{ _(d.mode_of_payment) }} {{ frappe.utils.fmt_money(d.expected_amount - d.opening_amount, currency=currency) }} {% endfor %} @@ -63,7 +63,7 @@ {{ _("Account") }} - {{ _("Rate") }} + {{ _(" Tax Rate") }} {{ _("Amount") }} diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 51b4ed248ce..96f67613d3f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -76,6 +76,48 @@ def get_fiscal_years( as_dict=False, boolean=False, ): +<<<<<<< HEAD +======= + if transaction_date: + transaction_date = getdate(transaction_date) + # backwards compat + if boolean is not None: + raise_on_missing = not boolean + + all_fiscal_years = _get_fiscal_years(company=company) + + # No restricting selectors + if not transaction_date and not fiscal_year: + return all_fiscal_years + + for fy in all_fiscal_years: + if (fiscal_year and fy.name == fiscal_year) or ( + transaction_date + and getdate(fy.year_start_date) <= transaction_date + and getdate(fy.year_end_date) >= transaction_date + ): + if as_dict: + return (fy,) + else: + return ((fy.name, fy.year_start_date, fy.year_end_date),) + + # No match for restricting selectors + if raise_on_missing: + error_msg = _("""{0} {1} is not in any active Fiscal Year""").format( + _(label), formatdate(transaction_date) + ) + if company: + error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company)) + + if verbose == 1: + frappe.msgprint(error_msg) + + raise FiscalYearError(error_msg) + return [] + + +def _get_fiscal_years(company=None): +>>>>>>> a87e7fde03 (fix: in_contex_translation_fixes) fiscal_years = frappe.cache().hget("fiscal_years", company) or [] if not fiscal_years: diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 388feb9eba9..ec565060191 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -40,7 +40,11 @@

+<<<<<<< HEAD {{ _("Pay") }} {{doc.get_formatted("grand_total") }} +======= + {{ _("Pay", null, "Amount") }} {{ pay_amount }} +>>>>>>> a87e7fde03 (fix: in_contex_translation_fixes)

From 2efc701e4e37127aff69035742b0f415ae4b3505 Mon Sep 17 00:00:00 2001 From: mahsem <137205921+mahsem@users.noreply.github.com> Date: Mon, 23 Dec 2024 17:42:33 +0100 Subject: [PATCH 29/49] fix: whitespace (cherry picked from commit 1f4e1811de51b75c765fa87118aafa0b16dedd6f) --- .../doctype/pos_closing_entry/closing_voucher_details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html b/erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html index 5bc4e7a1587..63e88cf44c2 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html +++ b/erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html @@ -63,7 +63,7 @@ {{ _("Account") }} - {{ _(" Tax Rate") }} + {{ _("Tax Rate") }} {{ _("Amount") }} From 2e9c507dfa70eda65c98558aa6de8078f01681fa Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 30 Dec 2024 16:18:12 +0530 Subject: [PATCH 30/49] chore: resolve conflicts --- erpnext/accounts/utils.py | 42 ------------------------------ erpnext/templates/pages/order.html | 4 --- 2 files changed, 46 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 96f67613d3f..51b4ed248ce 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -76,48 +76,6 @@ def get_fiscal_years( as_dict=False, boolean=False, ): -<<<<<<< HEAD -======= - if transaction_date: - transaction_date = getdate(transaction_date) - # backwards compat - if boolean is not None: - raise_on_missing = not boolean - - all_fiscal_years = _get_fiscal_years(company=company) - - # No restricting selectors - if not transaction_date and not fiscal_year: - return all_fiscal_years - - for fy in all_fiscal_years: - if (fiscal_year and fy.name == fiscal_year) or ( - transaction_date - and getdate(fy.year_start_date) <= transaction_date - and getdate(fy.year_end_date) >= transaction_date - ): - if as_dict: - return (fy,) - else: - return ((fy.name, fy.year_start_date, fy.year_end_date),) - - # No match for restricting selectors - if raise_on_missing: - error_msg = _("""{0} {1} is not in any active Fiscal Year""").format( - _(label), formatdate(transaction_date) - ) - if company: - error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company)) - - if verbose == 1: - frappe.msgprint(error_msg) - - raise FiscalYearError(error_msg) - return [] - - -def _get_fiscal_years(company=None): ->>>>>>> a87e7fde03 (fix: in_contex_translation_fixes) fiscal_years = frappe.cache().hget("fiscal_years", company) or [] if not fiscal_years: diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index ec565060191..ade66dd481f 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -40,11 +40,7 @@

-<<<<<<< HEAD - {{ _("Pay") }} {{doc.get_formatted("grand_total") }} -======= {{ _("Pay", null, "Amount") }} {{ pay_amount }} ->>>>>>> a87e7fde03 (fix: in_contex_translation_fixes)

From 772b7b95ac5e7445020414cd2775fb729a75775d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:37:52 +0530 Subject: [PATCH 31/49] fix: ignore inventory dimension for SABB and Pick List (backport #44933) (#44941) fix: ignore inventory dimension for SABB and Pick List (#44933) (cherry picked from commit 303c52f1344c196e656c69103f5c8801d9faa545) Co-authored-by: rohitwaghchaure --- .../stock/doctype/inventory_dimension/inventory_dimension.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index 661605bdf5f..524c7331bc7 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -237,8 +237,13 @@ class InventoryDimension(Document): custom_fields["Stock Ledger Entry"] = dimension_field filter_custom_fields = {} + ignore_doctypes = ["Serial and Batch Bundle", "Serial and Batch Entry", "Pick List Item"] + if custom_fields: for doctype, fields in custom_fields.items(): + if doctype in ignore_doctypes: + continue + if isinstance(fields, dict): fields = [fields] From c33e07550cbfdbd5adc113cec35109950500a599 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:38:14 +0530 Subject: [PATCH 32/49] fix: ignore validate while making WO from MR (backport #44939) (#44942) fix: ignore validate while making WO from MR (#44939) (cherry picked from commit 9661c1d0814e8e4b175139e48fa5dd6eb291a287) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/material_request/material_request.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 23d289170db..f59b60a3f51 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -766,6 +766,7 @@ def raise_work_orders(material_request): ) wo_order.set_work_order_operations() + wo_order.flags.ignore_validate = True wo_order.flags.ignore_mandatory = True wo_order.save() From 9853bd9ba128d448999690221cc854ec19da8f29 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:38:49 +0530 Subject: [PATCH 33/49] fix: incorrect filter for BOM (backport #44954) (#44956) fix: incorrect filter for BOM (#44954) (cherry picked from commit 9fdeb5f8269cfb6ebe8f63bf426250079c95dca5) Co-authored-by: rohitwaghchaure --- erpnext/manufacturing/doctype/bom/bom_list.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom_list.js b/erpnext/manufacturing/doctype/bom/bom_list.js index a26df545f85..1b6cba90dbc 100644 --- a/erpnext/manufacturing/doctype/bom/bom_list.js +++ b/erpnext/manufacturing/doctype/bom/bom_list.js @@ -2,13 +2,13 @@ frappe.listview_settings["BOM"] = { add_fields: ["is_active", "is_default", "total_cost", "has_variants"], get_indicator: function (doc) { if (doc.is_active && doc.has_variants) { - return [__("Template"), "orange", "has_variants,=,Yes"]; + return [__("Template"), "orange", "has_variants,=,1"]; } else if (doc.is_default) { - return [__("Default"), "green", "is_default,=,Yes"]; + return [__("Default"), "green", "is_default,=,1"]; } else if (doc.is_active) { - return [__("Active"), "blue", "is_active,=,Yes"]; + return [__("Active"), "blue", "is_active,=,1"]; } else if (!doc.is_active) { - return [__("Not active"), "gray", "is_active,=,No"]; + return [__("Not active"), "gray", "is_active,=,0"]; } }, }; From d6903fbc8d7f011062d56504d5c20b7d2ebfa859 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 12:09:33 +0530 Subject: [PATCH 34/49] fix: Validate party on non receivable / payable account (backport #44883) (#44973) * fix: validate party on non receivable / payable account (cherry picked from commit c6a2d86ba6068e9e259f51a510a9c5a2e81bd18f) * test: add unit test to validate on non receivable / payable account (cherry picked from commit a10a15b2c3ffdf0bde9873286da7c054b9af723f) * fix: Set account type payable for advance account (cherry picked from commit 8abbece7c4556399ddc101d8cd903ce7dd74c8db) --------- Co-authored-by: Karuppasamy923 --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 7 ++- .../doctype/gl_entry/test_gl_entry.py | 45 +++++++++++++++++++ .../payment_entry/test_payment_entry.py | 2 +- erpnext/accounts/party.py | 11 +++++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index a7e7edb098d..d9d7807a561 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -13,7 +13,11 @@ import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_checks_for_pl_and_bs_accounts, ) -from erpnext.accounts.party import validate_party_frozen_disabled, validate_party_gle_currency +from erpnext.accounts.party import ( + validate_account_party_type, + validate_party_frozen_disabled, + validate_party_gle_currency, +) from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.exceptions import InvalidAccountCurrency @@ -268,6 +272,7 @@ class GLEntry(Document): def validate_party(self): validate_party_frozen_disabled(self.party_type, self.party) + validate_account_party_type(self) def validate_currency(self): company_currency = erpnext.get_company_currency(self.company) diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py index 3edfd67b005..f6ed163bff5 100644 --- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py @@ -79,3 +79,48 @@ class TestGLEntry(unittest.TestCase): "SELECT current from tabSeries where name = %s", naming_series )[0][0] self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value) + + def test_validate_account_party_type(self): + jv = make_journal_entry( + "_Test Account Cost for Goods Sold - _TC", + "_Test Bank - _TC", + 100, + "_Test Cost Center - _TC", + save=False, + submit=False, + ) + + for row in jv.accounts: + row.party_type = "Supplier" + break + + jv.save() + try: + jv.submit() + except Exception as e: + self.assertEqual( + str(e), + "Party Type and Party can only be set for Receivable / Payable account_Test Account Cost for Goods Sold - _TC", + ) + + jv1 = make_journal_entry( + "_Test Account Cost for Goods Sold - _TC", + "_Test Bank - _TC", + 100, + "_Test Cost Center - _TC", + save=False, + submit=False, + ) + + for row in jv.accounts: + row.party_type = "Customer" + break + + jv1.save() + try: + jv1.submit() + except Exception as e: + self.assertEqual( + str(e), + "Party Type and Party can only be set for Receivable / Payable account_Test Account Cost for Goods Sold - _TC", + ) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 312628d9f97..5883d4e2f1f 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1479,7 +1479,7 @@ class TestPaymentEntry(FrappeTestCase): parent_account="Current Liabilities - _TC", account_name="Advances Paid", company=company, - account_type="Liability", + account_type="Payable", ) frappe.db.set_value( diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 5be80872db8..3033b8ad087 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -759,6 +759,17 @@ def validate_party_frozen_disabled(party_type, party_name): frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), alert=True) +def validate_account_party_type(self): + if self.party_type and self.party: + account_type = frappe.get_cached_value("Account", self.account, "account_type") + if account_type and (account_type not in ["Receivable", "Payable"]): + frappe.throw( + _( + "Party Type and Party can only be set for Receivable / Payable account

" "{0}" + ).format(self.account) + ) + + def get_dashboard_info(party_type, party, loyalty_program=None): current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True) From 839ffb3f2a88e6e3c9901a943c52ce5e7ce03669 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 31 Dec 2024 12:35:51 +0530 Subject: [PATCH 35/49] fix: copy accounting dimensions to asset and sales invoice (#44964) * fix: copy accounting dimensions to asset and sales invoice * fix: replace sql query with query builder * refactor: reuse function for accounting dimensions * fix: loop handling * fix: use explicit param (cherry picked from commit 079ec864de00b55e1b0378b94883d9bba090f808) # Conflicts: # erpnext/controllers/buying_controller.py --- erpnext/assets/doctype/asset/asset.py | 12 ++++++++++++ erpnext/controllers/buying_controller.py | 19 ++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index e499b5e0faa..2e0ba2c8218 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -19,6 +19,7 @@ from frappe.utils import ( ) import erpnext +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions from erpnext.accounts.general_ledger import make_reverse_gl_entries from erpnext.assets.doctype.asset.depreciation import ( get_comma_separated_links, @@ -887,6 +888,7 @@ def get_asset_naming_series(): @frappe.whitelist() def make_sales_invoice(asset, item_code, company, serial_no=None): + asset_doc = frappe.get_doc("Asset", asset) si = frappe.new_doc("Sales Invoice") si.company = company si.currency = frappe.get_cached_value("Company", company, "default_currency") @@ -903,6 +905,16 @@ def make_sales_invoice(asset, item_code, company, serial_no=None): "qty": 1, }, ) + + accounting_dimensions = get_dimensions(with_cost_center_and_project=True) + for dimension in accounting_dimensions[0]: + si.update( + { + dimension["fieldname"]: asset_doc.get(dimension["fieldname"]) + or dimension.get("default_dimension") + } + ) + si.set_missing_values() return si diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 6020dce0761..a03c3692b40 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -9,6 +9,7 @@ from frappe.utils import cint, flt, getdate from frappe.utils.data import nowtime import erpnext +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.party import get_party_details from erpnext.buying.utils import update_last_purchase_rate, validate_for_items @@ -744,6 +745,11 @@ class BuyingController(SubcontractingController): def auto_make_assets(self, asset_items): items_data = get_asset_item_details(asset_items) messages = [] +<<<<<<< HEAD +======= + alert = False + accounting_dimensions = get_dimensions(with_cost_center_and_project=True) +>>>>>>> 079ec864de (fix: copy accounting dimensions to asset and sales invoice (#44964)) for d in self.items: if d.is_fixed_asset: @@ -755,11 +761,11 @@ class BuyingController(SubcontractingController): if item_data.get("asset_naming_series"): created_assets = [] if item_data.get("is_grouped_asset"): - asset = self.make_asset(d, is_grouped_asset=True) + asset = self.make_asset(d, accounting_dimensions, is_grouped_asset=True) created_assets.append(asset) else: for _qty in range(cint(d.qty)): - asset = self.make_asset(d) + asset = self.make_asset(d, accounting_dimensions) created_assets.append(asset) if len(created_assets) > 5: @@ -797,7 +803,7 @@ class BuyingController(SubcontractingController): for message in messages: frappe.msgprint(message, title="Success", indicator="green") - def make_asset(self, row, is_grouped_asset=False): + def make_asset(self, row, accounting_dimensions, is_grouped_asset=False): if not row.asset_location: frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code)) @@ -828,6 +834,13 @@ class BuyingController(SubcontractingController): "purchase_invoice_item": row.name if self.doctype == "Purchase Invoice" else None, } ) + for dimension in accounting_dimensions[0]: + asset.update( + { + dimension["fieldname"]: self.get(dimension["fieldname"]) + or dimension.get("default_dimension") + } + ) asset.flags.ignore_validate = True asset.flags.ignore_mandatory = True From 00ae829d890011df66cf66460e8119d649571c0e Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 31 Dec 2024 12:43:38 +0530 Subject: [PATCH 36/49] fix: resolved conflicts --- erpnext/controllers/buying_controller.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index a03c3692b40..7a6ff6c0889 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -745,11 +745,8 @@ class BuyingController(SubcontractingController): def auto_make_assets(self, asset_items): items_data = get_asset_item_details(asset_items) messages = [] -<<<<<<< HEAD -======= alert = False accounting_dimensions = get_dimensions(with_cost_center_and_project=True) ->>>>>>> 079ec864de (fix: copy accounting dimensions to asset and sales invoice (#44964)) for d in self.items: if d.is_fixed_asset: From ea4b6e8dd726e2c8a8a94f7e70bd5cab03ea6876 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 12:52:13 +0530 Subject: [PATCH 37/49] fix(report): Purchase Order Analysis pymysql.err (backport #44957) (#44994) fix(report): Purchase Order Analysis pymysql.err (#44957) (cherry picked from commit d6980a9493fb18855918de804f4922c2f70fb5d1) Co-authored-by: Vishnu VS --- .../report/purchase_order_analysis/purchase_order_analysis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index 75658e28780..f583ce3e6c8 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -18,11 +18,12 @@ def execute(filters=None): columns = get_columns(filters) data = get_data(filters) - update_received_amount(data) if not data: return [], [], None, [] + update_received_amount(data) + data, chart_data = prepare_data(data, filters) return columns, data, None, chart_data From 20efe7bb801be48f5d0df75050d76a715375c8c7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 12:52:23 +0530 Subject: [PATCH 38/49] fix: load customer default price list in pos during item selection (backport #44991) (#44993) fix: load customer default price list in pos during item selection (#44991) fix: load customer default price list in pos (cherry picked from commit d1ae0d784e8998d9146e4377e340301be615197f) Co-authored-by: Diptanil Saha --- erpnext/selling/page/point_of_sale/pos_controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 32cb2bc0525..dc7e992f654 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -285,6 +285,7 @@ erpnext.PointOfSale.Controller = class { edit_cart: () => this.payment.edit_cart(), customer_details_updated: (details) => { + this.item_selector.load_items_data(); this.customer_details = details; // will add/remove LP payment method this.payment.render_loyalty_points_payment_mode(); From b8922823a3e3989827f3009ad17621dbda704543 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 31 Dec 2024 12:55:41 +0530 Subject: [PATCH 39/49] fix: remove unused variable --- erpnext/controllers/buying_controller.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 7a6ff6c0889..8da22785b94 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -745,7 +745,6 @@ class BuyingController(SubcontractingController): def auto_make_assets(self, asset_items): items_data = get_asset_item_details(asset_items) messages = [] - alert = False accounting_dimensions = get_dimensions(with_cost_center_and_project=True) for d in self.items: From 6f7138996a0252f919e0ff47f5b19a1dab1ff9d6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:17:18 +0530 Subject: [PATCH 40/49] fix: negative stock balance (backport #44990) (#44996) * fix: negative stock balance (#44990) (cherry picked from commit 7c4aecf83464c6f8790041246a086e8f18b533d1) # Conflicts: # erpnext/stock/deprecated_serial_batch.py * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- erpnext/stock/deprecated_serial_batch.py | 58 +++++++++++++++++-- .../test_serial_and_batch_bundle.py | 3 +- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index b3f4c2dead4..7611d751fdd 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -181,6 +181,9 @@ class DeprecatedBatchNoValuation: stock_value_change = self.batch_avg_rate[batch_no] * ledger.qty self.stock_value_change += stock_value_change + self.non_batchwise_balance_value[batch_no] -= stock_value_change + self.non_batchwise_balance_qty[batch_no] -= ledger.qty + frappe.db.set_value( "Serial and Batch Entry", ledger.name, @@ -220,7 +223,6 @@ class DeprecatedBatchNoValuation: .select( sle.batch_no, Sum(sle.actual_qty).as_("batch_qty"), - Sum(sle.stock_value_difference).as_("batch_value"), ) .where( (sle.item_code == self.sle.item_code) @@ -237,11 +239,59 @@ class DeprecatedBatchNoValuation: if self.sle.name: query = query.where(sle.name != self.sle.name) - for d in query.run(as_dict=True): - self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value) - self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty) + batch_data = query.run(as_dict=True) + 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: + 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) + + def get_last_sle_for_non_batch(self): + from erpnext.stock.utils import get_combine_datetime + + sle = frappe.qb.DocType("Stock Ledger Entry") + batch = frappe.qb.DocType("Batch") + + posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + if not self.sle.creation: + posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1) + + timestamp_condition = sle.posting_datetime < posting_datetime + + if self.sle.creation: + timestamp_condition |= (sle.posting_datetime == posting_datetime) & ( + sle.creation < self.sle.creation + ) + + query = ( + frappe.qb.from_(sle) + .inner_join(batch) + .on(sle.batch_no == batch.name) + .select( + sle.stock_value, + sle.qty_after_transaction, + ) + .where( + (sle.item_code == self.sle.item_code) + & (sle.warehouse == self.sle.warehouse) + & (sle.batch_no.isnotnull()) + & (batch.use_batchwise_valuation == 0) + & (sle.is_cancelled == 0) + ) + .where(timestamp_condition) + .orderby(sle.posting_datetime, order=Order.desc) + .orderby(sle.creation, order=Order.desc) + .limit(1) + ) + + if self.sle.name: + query = query.where(sle.name != self.sle.name) + + data = query.run(as_dict=True) + return data[0] if data else {} + @deprecated def set_balance_value_from_bundle(self) -> None: bundle = frappe.qb.DocType("Serial and Batch Bundle") diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index 647082baa68..45a474df2b7 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -166,7 +166,7 @@ class TestSerialandBatchBundle(FrappeTestCase): for qty, valuation in {10: 100, 20: 200}.items(): stock_queue.append([qty, valuation]) qty_after_transaction += qty - balance_value += qty_after_transaction * valuation + balance_value += qty * valuation doc = frappe.get_doc( { @@ -177,6 +177,7 @@ class TestSerialandBatchBundle(FrappeTestCase): "incoming_rate": valuation, "qty_after_transaction": qty_after_transaction, "stock_value_difference": valuation * qty, + "stock_value": balance_value, "balance_value": balance_value, "valuation_rate": balance_value / qty_after_transaction, "actual_qty": qty, From 8885b071143a64912cd00f2632fe4de0ccd8f8d1 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 24 Dec 2024 15:15:30 +0530 Subject: [PATCH 41/49] fix: update item_tax_rate in backend (cherry picked from commit de54c0b41f6c3367f39e74a5726d757bcedbc79f) # Conflicts: # erpnext/controllers/taxes_and_totals.py --- .../doctype/sales_invoice/test_sales_invoice.py | 14 ++++++++++++++ erpnext/controllers/taxes_and_totals.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 57eb84caaa4..763fea7a260 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -43,6 +43,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import ( from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( create_stock_reconciliation, ) +from erpnext.stock.get_item_details import get_item_tax_map from erpnext.stock.utils import get_incoming_rate, get_stock_balance @@ -2873,13 +2874,26 @@ class TestSalesInvoice(FrappeTestCase): item.save() sales_invoice = create_sales_invoice(item="T Shirt", rate=700, do_not_submit=True) + item_tax_map = get_item_tax_map( + doc=sales_invoice, + tax_template=sales_invoice.items[0].item_tax_template, + ) + self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC") + self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map) # Apply discount sales_invoice.apply_discount_on = "Net Total" sales_invoice.discount_amount = 300 sales_invoice.save() + + item_tax_map = get_item_tax_map( + doc=sales_invoice, + tax_template=sales_invoice.items[0].item_tax_template, + ) + self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") + self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map) @change_settings("Selling Settings", {"enable_discount_accounting": 1}) def test_sales_invoice_with_discount_accounting_enabled(self): diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index bf5beab1a82..2ebe8270cad 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -18,7 +18,12 @@ from erpnext.controllers.accounts_controller import ( validate_inclusive_tax, validate_taxes_and_charges, ) +<<<<<<< HEAD from erpnext.stock.get_item_details import _get_item_tax_template +======= +from erpnext.deprecation_dumpster import deprecated +from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template, get_item_tax_map +>>>>>>> de54c0b41f (fix: update item_tax_rate in backend) from erpnext.utilities.regional import temporary_flag @@ -70,6 +75,7 @@ class calculate_taxes_and_totals: self.validate_conversion_rate() self.calculate_item_values() self.validate_item_tax_template() + self.update_item_tax_map() self.initialize_taxes() self.determine_exclusive_rate() self.calculate_net_total() @@ -134,6 +140,14 @@ class calculate_taxes_and_totals: ) ) + def update_item_tax_map(self): + for item in self.doc.items: + item.item_tax_rate = get_item_tax_map( + doc=self.doc, + tax_template=item.item_tax_template, + as_json=True, + ) + def validate_conversion_rate(self): # validate conversion rate company_currency = erpnext.get_company_currency(self.doc.company) From 6703a457fe51024f557c898668eefedc2d3b60f7 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 27 Dec 2024 14:21:00 +0530 Subject: [PATCH 42/49] fix: set paid amount in party currency in bank reco payment entry (cherry picked from commit 70b10772869d104d92af4cd2f9ef618e3a9e6c6e) --- .../bank_reconciliation_tool.py | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 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 28038e9212a..f798d68c3c7 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -12,6 +12,7 @@ from frappe.utils import cint, flt from erpnext import get_default_cost_center from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount +from erpnext.accounts.party import get_party_account from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import ( get_amounts_not_reflected_in_system, get_entries, @@ -304,54 +305,56 @@ def create_payment_entry_bts( bank_transaction = frappe.db.get_values( "Bank Transaction", bank_transaction_name, - fieldname=["name", "unallocated_amount", "deposit", "bank_account"], + fieldname=["name", "unallocated_amount", "deposit", "bank_account", "currency"], as_dict=True, )[0] - paid_amount = bank_transaction.unallocated_amount + payment_type = "Receive" if bank_transaction.deposit > 0.0 else "Pay" - company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account") - company = frappe.get_value("Account", company_account, "company") - payment_entry_dict = { - "company": company, - "payment_type": payment_type, - "reference_no": reference_number, - "reference_date": reference_date, - "party_type": party_type, - "party": party, - "posting_date": posting_date, - "paid_amount": paid_amount, - "received_amount": paid_amount, - } - payment_entry = frappe.new_doc("Payment Entry") + bank_account = frappe.get_cached_value("Bank Account", bank_transaction.bank_account, "account") + company = frappe.get_cached_value("Account", bank_account, "company") + party_account = get_party_account(party_type, party, company) - payment_entry.update(payment_entry_dict) + bank_currency = bank_transaction.currency + party_currency = frappe.get_cached_value("Account", party_account, "account_currency") - if mode_of_payment: - payment_entry.mode_of_payment = mode_of_payment - if project: - payment_entry.project = project - if cost_center: - payment_entry.cost_center = cost_center - if payment_type == "Receive": - payment_entry.paid_to = company_account - else: - payment_entry.paid_from = company_account + exc_rate = get_exchange_rate(bank_currency, party_currency, posting_date) - payment_entry.validate() + amt_in_bank_acc_currency = bank_transaction.unallocated_amount + amount_in_party_currency = bank_transaction.unallocated_amount * exc_rate + + pe = frappe.new_doc("Payment Entry") + pe.payment_type = payment_type + pe.company = company + pe.reference_no = reference_number + pe.reference_date = reference_date + pe.party_type = party_type + pe.party = party + pe.posting_date = posting_date + pe.paid_from = party_account if payment_type == "Receive" else bank_account + pe.paid_to = party_account if payment_type == "Pay" else bank_account + pe.paid_from_account_currency = party_currency if payment_type == "Receive" else bank_currency + pe.paid_to_account_currency = party_currency if payment_type == "Pay" else bank_currency + pe.paid_amount = amount_in_party_currency if payment_type == "Receive" else amt_in_bank_acc_currency + pe.received_amount = amount_in_party_currency if payment_type == "Pay" else amt_in_bank_acc_currency + pe.mode_of_payment = mode_of_payment + pe.project = project + pe.cost_center = cost_center + + pe.validate() if allow_edit: - return payment_entry + return pe - payment_entry.insert() + pe.insert() + pe.submit() - payment_entry.submit() vouchers = json.dumps( [ { "payment_doctype": "Payment Entry", - "payment_name": payment_entry.name, - "amount": paid_amount, + "payment_name": pe.name, + "amount": amt_in_bank_acc_currency, } ] ) From 885dd31c5c2f1d70812c16ac52c1d18ae32009db Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 31 Dec 2024 15:54:06 +0530 Subject: [PATCH 43/49] chore: resolve conflict --- erpnext/controllers/taxes_and_totals.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 2ebe8270cad..53d0ce0ee2f 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -18,12 +18,7 @@ from erpnext.controllers.accounts_controller import ( validate_inclusive_tax, validate_taxes_and_charges, ) -<<<<<<< HEAD -from erpnext.stock.get_item_details import _get_item_tax_template -======= -from erpnext.deprecation_dumpster import deprecated -from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template, get_item_tax_map ->>>>>>> de54c0b41f (fix: update item_tax_rate in backend) +from erpnext.stock.get_item_details import _get_item_tax_template, get_item_tax_map from erpnext.utilities.regional import temporary_flag From b135a684a5a86b6dcfc0896705f482cebace55df Mon Sep 17 00:00:00 2001 From: DHINESH00 <18csa09@karpagamtech.ac.in> Date: Fri, 27 Dec 2024 16:07:18 +0530 Subject: [PATCH 44/49] fix: fetch amount in company currency (cherry picked from commit a984aaae362f2055b6073e75748e73fda247a00b) --- .../bank_reconciliation_tool/bank_reconciliation_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 28038e9212a..162b595078a 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -723,7 +723,7 @@ def get_pe_matching_query( (ref_rank + amount_rank + party_rank + 1).as_("rank"), ConstantColumn("Payment Entry").as_("doctype"), pe.name, - pe.paid_amount_after_tax.as_("paid_amount"), + pe.base_paid_amount_after_tax.as_("paid_amount"), pe.reference_no, pe.reference_date, pe.party, From e09fb8759751942e2eb2b1c927f2a18cb8bea566 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 31 Dec 2024 16:57:16 +0530 Subject: [PATCH 45/49] refactor: use existing method parameters --- .../accounts/doctype/sales_invoice/test_sales_invoice.py | 8 ++++---- erpnext/controllers/taxes_and_totals.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 763fea7a260..439fc5639e5 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2875,8 +2875,8 @@ class TestSalesInvoice(FrappeTestCase): sales_invoice = create_sales_invoice(item="T Shirt", rate=700, do_not_submit=True) item_tax_map = get_item_tax_map( - doc=sales_invoice, - tax_template=sales_invoice.items[0].item_tax_template, + company=sales_invoice.company, + item_tax_template=sales_invoice.items[0].item_tax_template, ) self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC") @@ -2888,8 +2888,8 @@ class TestSalesInvoice(FrappeTestCase): sales_invoice.save() item_tax_map = get_item_tax_map( - doc=sales_invoice, - tax_template=sales_invoice.items[0].item_tax_template, + company=sales_invoice.company, + item_tax_template=sales_invoice.items[0].item_tax_template, ) self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 53d0ce0ee2f..8fecb177295 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -138,8 +138,8 @@ class calculate_taxes_and_totals: def update_item_tax_map(self): for item in self.doc.items: item.item_tax_rate = get_item_tax_map( - doc=self.doc, - tax_template=item.item_tax_template, + company=self.doc.get("company"), + item_tax_template=item.item_tax_template, as_json=True, ) From 8d650e56ba9777c2f0df324a15215cac51641719 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 1 Jan 2025 08:29:02 +0530 Subject: [PATCH 46/49] fix: duplicate validate for closing stock balance (#45015) --- .../doctype/closing_stock_balance/closing_stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py index 8aa49f7cfd8..f6510c04fe9 100644 --- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py +++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py @@ -65,7 +65,7 @@ class ClosingStockBalance(Document): & ( (table.from_date.between(self.from_date, self.to_date)) | (table.to_date.between(self.from_date, self.to_date)) - | ((table.from_date >= self.from_date) & (table.to_date >= self.to_date)) + | ((self.from_date >= table.from_date) & (table.from_date >= self.to_date)) ) ) ) From 52bdf5b17041edda8a021f04f9d8a03f8c011bf3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 1 Jan 2025 09:08:16 +0530 Subject: [PATCH 47/49] fix: precision issue (backport #45013) (#45019) fix: precision issue (#45013) (cherry picked from commit 7db9bcaeacd51bc2cd3dc1ef87e2cad81c7fd269) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index febc814b978..78f69b1ce62 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -3289,8 +3289,10 @@ def create_serial_and_batch_bundle(parent_doc, row, child, type_of_transaction=N doc.append("entries", {"serial_no": serial_no, "warehouse": row.warehouse, "qty": -1}) elif row.batches_to_be_consume: + precision = frappe.get_precision("Serial and Batch Entry", "qty") doc.has_batch_no = 1 for batch_no, qty in row.batches_to_be_consume.items(): + qty = flt(qty, precision) doc.append("entries", {"batch_no": batch_no, "warehouse": row.warehouse, "qty": qty * -1}) if not doc.entries: From 0a2cc6bcd73ab0c98040cb8b4d247a8176bf9aaf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 1 Jan 2025 09:24:48 +0530 Subject: [PATCH 48/49] fix: incorrect quality inspection linked in purchase receipt (backport #44985) (#45020) * fix: incorrect quality inspection linked in purchase receipt (#44985) (cherry picked from commit b84c8ff960784723458a0fcc50bcf8ffb01b7c23) # Conflicts: # erpnext/stock/doctype/quality_inspection/quality_inspection.json * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- erpnext/public/js/controllers/transaction.js | 10 +- .../quality_inspection.json | 14 ++- .../quality_inspection/quality_inspection.py | 102 ++++++++++++++---- 3 files changed, 102 insertions(+), 24 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 4e573234bb1..b3389b27e84 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2309,6 +2309,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe fieldname: "batch_no", label: __("Batch No"), hidden: true + }, + { + fieldtype: "Data", + fieldname: "child_row_reference", + label: __("Child Row Reference"), + hidden: true } ] } @@ -2352,14 +2358,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (this.has_inspection_required(item)) { let dialog_items = dialog.fields_dict.items; dialog_items.df.data.push({ - "docname": item.name, "item_code": item.item_code, "item_name": item.item_name, "qty": item.qty, "description": item.description, "serial_no": item.serial_no, "batch_no": item.batch_no, - "sample_size": item.sample_quantity + "sample_size": item.sample_quantity, + "child_row_reference": item.name, }); dialog_items.grid.refresh(); } diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json index 914a9f3c21f..56017c4fee2 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json @@ -15,6 +15,7 @@ "inspection_type", "reference_type", "reference_name", + "child_row_reference", "section_break_7", "item_code", "item_serial_no", @@ -238,6 +239,15 @@ "fieldname": "manual_inspection", "fieldtype": "Check", "label": "Manual Inspection" + }, + { + "fieldname": "child_row_reference", + "fieldtype": "Data", + "hidden": 1, + "label": "Child Row Reference", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-search", @@ -245,7 +255,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-08-23 11:56:50.282878", + "modified": "2024-12-30 19:08:16.611192", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection", @@ -272,4 +282,4 @@ "sort_field": "modified", "sort_order": "ASC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 6890256dc04..d3b5e65ea9f 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -29,6 +29,7 @@ class QualityInspection(Document): amended_from: DF.Link | None batch_no: DF.Link | None bom_no: DF.Link | None + child_row_reference: DF.Data | None description: DF.SmallText | None inspected_by: DF.Link inspection_type: DF.Literal["", "Incoming", "Outgoing", "In Process"] @@ -74,6 +75,64 @@ class QualityInspection(Document): self.inspect_and_set_status() self.validate_inspection_required() + self.set_child_row_reference() + + def set_child_row_reference(self): + if self.child_row_reference: + return + + if not (self.reference_type and self.reference_name): + return + + doctype = self.reference_type + " Item" + if self.reference_type == "Stock Entry": + doctype = "Stock Entry Detail" + + child_row_references = frappe.get_all( + doctype, + filters={"parent": self.reference_name, "item_code": self.item_code}, + pluck="name", + ) + + if not child_row_references: + return + + if len(child_row_references) == 1: + self.child_row_reference = child_row_references[0] + else: + self.distribute_child_row_reference(child_row_references) + + def distribute_child_row_reference(self, child_row_references): + quality_inspections = frappe.get_all( + "Quality Inspection", + filters={ + "reference_name": self.reference_name, + "item_code": self.item_code, + "docstatus": ("<", 2), + }, + fields=["name", "child_row_reference", "docstatus"], + order_by="child_row_reference desc", + ) + + for row in quality_inspections: + if not child_row_references: + break + + if row.child_row_reference and row.child_row_reference in child_row_references: + child_row_references.remove(row.child_row_reference) + continue + + if row.docstatus == 1: + continue + + if row.name == self.name: + self.child_row_reference = child_row_references[0] + else: + frappe.db.set_value( + "Quality Inspection", row.name, "child_row_reference", child_row_references[0] + ) + + child_row_references.remove(child_row_references[0]) def validate_inspection_required(self): if self.reference_type in ["Purchase Receipt", "Purchase Invoice"] and not frappe.get_cached_value( @@ -157,35 +216,38 @@ class QualityInspection(Document): ) else: - args = [quality_inspection, self.modified, self.reference_name, self.item_code] doctype = self.reference_type + " Item" if self.reference_type == "Stock Entry": doctype = "Stock Entry Detail" - if self.reference_type and self.reference_name: - conditions = "" + if doctype and self.reference_name: + child_doc = frappe.qb.DocType(doctype) + + query = ( + frappe.qb.update(child_doc) + .set(child_doc.quality_inspection, quality_inspection) + .where( + (child_doc.parent == self.reference_name) & (child_doc.item_code == self.item_code) + ) + ) + if self.batch_no and self.docstatus == 1: - conditions += " and t1.batch_no = %s" - args.append(self.batch_no) + query = query.where(child_doc.batch_no == self.batch_no) if self.docstatus == 2: # if cancel, then remove qi link wherever same name - conditions += " and t1.quality_inspection = %s" - args.append(self.name) + query = query.where(child_doc.quality_inspection == self.name) - frappe.db.sql( - f""" - UPDATE - `tab{doctype}` t1, `tab{self.reference_type}` t2 - SET - t1.quality_inspection = %s, t2.modified = %s - WHERE - t1.parent = %s - and t1.item_code = %s - and t1.parent = t2.name - {conditions} - """, - args, + if self.child_row_reference: + query = query.where(child_doc.name == self.child_row_reference) + + query.run() + + frappe.db.set_value( + self.reference_type, + self.reference_name, + "modified", + self.modified, ) def inspect_and_set_status(self): From f09acc784f76c82151eca18e7d40437baf262368 Mon Sep 17 00:00:00 2001 From: DHINESH00 <18csa09@karpagamtech.ac.in> Date: Mon, 30 Dec 2024 22:47:37 +0530 Subject: [PATCH 49/49] fix: apply apply_pricing_rule date change (cherry picked from commit 2cbab9b87590f9d49cbb5bdacde4113d87609389) --- erpnext/public/js/controllers/transaction.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b3389b27e84..db866bd3b76 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -813,6 +813,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } validate() { + this.apply_pricing_rule() this.calculate_taxes_and_totals(false); } @@ -974,6 +975,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } transaction_date() { + this.apply_pricing_rule() if (this.frm.doc.transaction_date) { this.frm.transaction_date = this.frm.doc.transaction_date; frappe.ui.form.trigger(this.frm.doc.doctype, "currency"); @@ -982,6 +984,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe posting_date() { var me = this; + me.apply_pricing_rule() if (this.frm.doc.posting_date) { this.frm.posting_date = this.frm.doc.posting_date;