From b93c18bd4a0320a22fb03b9f41b92675ee246f19 Mon Sep 17 00:00:00 2001 From: Prateek <40106895+prateekkaramchandani@users.noreply.github.com> Date: Fri, 17 Feb 2023 20:25:13 +0530 Subject: [PATCH 01/39] fix: use max function to get default company address --- erpnext/setup/doctype/company/company.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 07ee2890c4..fcdf245659 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -808,7 +808,7 @@ def get_default_company_address(name, sort_key="is_primary_address", existing_ad return existing_address if out: - return min(out, key=lambda x: x[1])[0] # find min by sort_key + return max(out, key=lambda x: x[1])[0] # find max by sort_key else: return None From abf9a28d6af8b3c9bfab1e892e56bf3adb18ee8e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 2 Mar 2023 16:40:37 +0530 Subject: [PATCH 02/39] fix: hide `+` button based on `Blanket Order Type` --- .../manufacturing/doctype/blanket_order/blanket_order.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js index d3bb33e86e..7b26a14a57 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js @@ -7,6 +7,12 @@ frappe.ui.form.on('Blanket Order', { }, setup: function(frm) { + frm.custom_make_buttons = { + 'Purchase Order': 'Purchase Order', + 'Sales Order': 'Sales Order', + 'Quotation': 'Quotation', + }; + frm.add_fetch("customer", "customer_name", "customer_name"); frm.add_fetch("supplier", "supplier_name", "supplier_name"); }, From f5937f46cb60f3521463f7a4c80c765f8a65e52b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 2 Mar 2023 17:04:09 +0530 Subject: [PATCH 03/39] feat: add field `Over Order Allowance (%)` in `Buying Settings` --- .../doctype/buying_settings/buying_settings.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 95857e4604..8c73e56a99 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -16,6 +16,7 @@ "transaction_settings_section", "po_required", "pr_required", + "over_order_allowance", "column_break_12", "maintain_same_rate", "set_landed_cost_based_on_purchase_invoice_rate", @@ -156,6 +157,13 @@ "fieldname": "set_landed_cost_based_on_purchase_invoice_rate", "fieldtype": "Check", "label": "Set Landed Cost Based on Purchase Invoice Rate" + }, + { + "default": "0", + "description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.", + "fieldname": "over_order_allowance", + "fieldtype": "Float", + "label": "Over Order Allowance (%)" } ], "icon": "fa fa-cog", @@ -163,7 +171,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-02-28 15:41:32.686805", + "modified": "2023-03-02 17:02:14.404622", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", From f3993783a3fc431a2909b445e9d09d9f584ff73e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 2 Mar 2023 17:18:46 +0530 Subject: [PATCH 04/39] refactor: rewrite `blanket_order.py` queries in `QB` --- .../doctype/blanket_order/blanket_order.py | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index ff2140199d..3298f43ac3 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -6,6 +6,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc +from frappe.query_builder.functions import Sum from frappe.utils import flt, getdate from erpnext.stock.doctype.item.item import get_item_defaults @@ -29,21 +30,23 @@ class BlanketOrder(Document): def update_ordered_qty(self): ref_doctype = "Sales Order" if self.blanket_order_type == "Selling" else "Purchase Order" + + trans = frappe.qb.DocType(ref_doctype) + trans_item = frappe.qb.DocType(f"{ref_doctype} Item") + item_ordered_qty = frappe._dict( - frappe.db.sql( - """ - select trans_item.item_code, sum(trans_item.stock_qty) as qty - from `tab{0} Item` trans_item, `tab{0}` trans - where trans.name = trans_item.parent - and trans_item.blanket_order=%s - and trans.docstatus=1 - and trans.status not in ('Closed', 'Stopped') - group by trans_item.item_code - """.format( - ref_doctype - ), - self.name, - ) + ( + frappe.qb.from_(trans_item) + .from_(trans) + .select(trans_item.item_code, Sum(trans_item.stock_qty).as_("qty")) + .where( + (trans.name == trans_item.parent) + & (trans_item.blanket_order == self.name) + & (trans.docstatus == 1) + & (trans.status.notin(["Stopped", "Closed"])) + ) + .groupby(trans_item.item_code) + ).run() ) for d in self.items: From fc1088d9c4787b12bd9734597604492044eff4a0 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 3 Mar 2023 10:49:25 +0530 Subject: [PATCH 05/39] fix: don't map item row having `0` qty --- erpnext/manufacturing/doctype/blanket_order/blanket_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index 3298f43ac3..d03f019b08 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -82,6 +82,7 @@ def make_order(source_name): "doctype": doctype + " Item", "field_map": {"rate": "blanket_order_rate", "parent": "blanket_order"}, "postprocess": update_item, + "condition": lambda item: (flt(item.qty) - flt(item.ordered_qty)) > 0, }, }, ) From 8bcbc45add7767ac947fa7c9b3aaca99fc9dda9b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 3 Mar 2023 11:11:33 +0530 Subject: [PATCH 06/39] feat: consider `over_order_allowance` while validating order qty --- .../doctype/purchase_order/purchase_order.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 2415aec8cb..d9ff981322 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -69,6 +69,7 @@ class PurchaseOrder(BuyingController): self.validate_with_previous_doc() self.validate_for_subcontracting() self.validate_minimum_order_qty() + self.validate_against_blanket_order() if self.is_old_subcontracting_flow: self.validate_bom_for_subcontracting_items() @@ -197,6 +198,33 @@ class PurchaseOrder(BuyingController): ).format(item_code, qty, itemwise_min_order_qty.get(item_code)) ) + def validate_against_blanket_order(self): + po_data = {} + for item in self.get("items"): + if item.against_blanket_order and item.blanket_order: + if item.blanket_order in po_data: + if item.item_code in po_data[item.blanket_order]: + po_data[item.blanket_order][item.item_code] += item.qty + else: + po_data[item.blanket_order][item.item_code] = item.qty + else: + po_data[item.blanket_order] = {item.item_code: item.qty} + + if po_data: + allowance = flt(frappe.db.get_single_value("Buying Settings", "over_order_allowance")) + for bo_name, item_data in po_data.items(): + bo_doc = frappe.get_doc("Blanket Order", bo_name) + for item in bo_doc.get("items"): + if item.item_code in item_data: + remaining_qty = item.qty - item.ordered_qty + allowed_qty = remaining_qty + (remaining_qty * (allowance / 100)) + if allowed_qty < item_data[item.item_code]: + frappe.throw( + _( + f"Item {item.item_code} cannot be ordered more than {allowed_qty} against Blanket Order {bo_name}." + ) + ) + def validate_bom_for_subcontracting_items(self): for item in self.items: if not item.bom: From d7da8928ac44df3a84f6099fc7bfbc9a9161be20 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 3 Mar 2023 11:19:28 +0530 Subject: [PATCH 07/39] feat: add field `Over Order Allowance (%)` in `Selling Settings` --- .../doctype/selling_settings/selling_settings.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 6ea66a0237..45ad7d95a1 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -24,6 +24,7 @@ "so_required", "dn_required", "sales_update_frequency", + "over_order_allowance", "column_break_5", "allow_multiple_items", "allow_against_multiple_purchase_orders", @@ -179,6 +180,12 @@ "fieldname": "allow_sales_order_creation_for_expired_quotation", "fieldtype": "Check", "label": "Allow Sales Order Creation For Expired Quotation" + }, + { + "description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.", + "fieldname": "over_order_allowance", + "fieldtype": "Float", + "label": "Over Order Allowance (%)" } ], "icon": "fa fa-cog", @@ -186,7 +193,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-02-04 12:37:53.380857", + "modified": "2023-03-03 11:16:54.333615", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", From 53701c37b18c7aecfaa00efabf4d3be768e59cb3 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 3 Mar 2023 22:03:54 +0530 Subject: [PATCH 08/39] feat: consider `over_order_allowance` while validating sales order qty --- .../doctype/purchase_order/purchase_order.py | 32 +++-------------- .../doctype/blanket_order/blanket_order.py | 35 +++++++++++++++++++ .../doctype/sales_order/sales_order.py | 4 +++ 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index d9ff981322..06b9d29e69 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -21,6 +21,9 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category from erpnext.accounts.party import get_party_account, get_party_account_currency from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items from erpnext.controllers.buying_controller import BuyingController +from erpnext.manufacturing.doctype.blanket_order.blanket_order import ( + validate_against_blanket_order, +) from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.stock.doctype.item.item import get_item_defaults, get_last_purchase_details from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty @@ -69,7 +72,7 @@ class PurchaseOrder(BuyingController): self.validate_with_previous_doc() self.validate_for_subcontracting() self.validate_minimum_order_qty() - self.validate_against_blanket_order() + validate_against_blanket_order(self) if self.is_old_subcontracting_flow: self.validate_bom_for_subcontracting_items() @@ -198,33 +201,6 @@ class PurchaseOrder(BuyingController): ).format(item_code, qty, itemwise_min_order_qty.get(item_code)) ) - def validate_against_blanket_order(self): - po_data = {} - for item in self.get("items"): - if item.against_blanket_order and item.blanket_order: - if item.blanket_order in po_data: - if item.item_code in po_data[item.blanket_order]: - po_data[item.blanket_order][item.item_code] += item.qty - else: - po_data[item.blanket_order][item.item_code] = item.qty - else: - po_data[item.blanket_order] = {item.item_code: item.qty} - - if po_data: - allowance = flt(frappe.db.get_single_value("Buying Settings", "over_order_allowance")) - for bo_name, item_data in po_data.items(): - bo_doc = frappe.get_doc("Blanket Order", bo_name) - for item in bo_doc.get("items"): - if item.item_code in item_data: - remaining_qty = item.qty - item.ordered_qty - allowed_qty = remaining_qty + (remaining_qty * (allowance / 100)) - if allowed_qty < item_data[item.item_code]: - frappe.throw( - _( - f"Item {item.item_code} cannot be ordered more than {allowed_qty} against Blanket Order {bo_name}." - ) - ) - def validate_bom_for_subcontracting_items(self): for item in self.items: if not item.bom: diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index d03f019b08..32f1c365ad 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -87,3 +87,38 @@ def make_order(source_name): }, ) return target_doc + + +def validate_against_blanket_order(order_doc): + if order_doc.doctype in ("Sales Order", "Purchase Order"): + order_data = {} + + for item in order_doc.get("items"): + if item.against_blanket_order and item.blanket_order: + if item.blanket_order in order_data: + if item.item_code in order_data[item.blanket_order]: + order_data[item.blanket_order][item.item_code] += item.qty + else: + order_data[item.blanket_order][item.item_code] = item.qty + else: + order_data[item.blanket_order] = {item.item_code: item.qty} + + if order_data: + allowance = flt( + frappe.db.get_single_value( + "Selling Settings" if order_doc.doctype == "Sales Order" else "Buying Settings", + "over_order_allowance", + ) + ) + for bo_name, item_data in order_data.items(): + bo_doc = frappe.get_doc("Blanket Order", bo_name) + for item in bo_doc.get("items"): + if item.item_code in item_data: + remaining_qty = item.qty - item.ordered_qty + allowed_qty = remaining_qty + (remaining_qty * (allowance / 100)) + if allowed_qty < item_data[item.item_code]: + frappe.throw( + _("Item {0} cannot be ordered more than {1} against Blanket Order {2}.").format( + item.item_code, allowed_qty, bo_name + ) + ) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 385d0f3a58..ee9161bee4 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -21,6 +21,9 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( ) from erpnext.accounts.party import get_party_account from erpnext.controllers.selling_controller import SellingController +from erpnext.manufacturing.doctype.blanket_order.blanket_order import ( + validate_against_blanket_order, +) from erpnext.manufacturing.doctype.production_plan.production_plan import ( get_items_for_material_requests, ) @@ -52,6 +55,7 @@ class SalesOrder(SellingController): self.validate_warehouse() self.validate_drop_ship() self.validate_serial_no_based_delivery() + validate_against_blanket_order(self) validate_inter_company_party( self.doctype, self.customer, self.company, self.inter_company_order_reference ) From ebb69532740bff99ce8886b4e04ed18545b20897 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Thu, 9 Mar 2023 17:55:27 +0530 Subject: [PATCH 09/39] fix: add missing patch for new fields added in #34214 --- erpnext/patches.txt | 1 + .../update_gpa_and_ndb_for_assdeprsch.py | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2abd65b1b5..b1cb0408cb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -323,6 +323,7 @@ erpnext.patches.v14_0.update_entry_type_for_journal_entry erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers erpnext.patches.v14_0.set_pick_list_status erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries +erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch # below 2 migration patches should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger diff --git a/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py new file mode 100644 index 0000000000..afb59e0f6f --- /dev/null +++ b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py @@ -0,0 +1,20 @@ +import frappe + + +def execute(): + # not using frappe.qb because https://github.com/frappe/frappe/issues/20292 + frappe.db.sql( + """UPDATE `tabAsset Depreciation Schedule` + JOIN `tabAsset` + ON `tabAsset Depreciation Schedule`.`asset`=`tabAsset`.`name` + SET + `tabAsset Depreciation Schedule`.`gross_purchase_amount`=`tabAsset`.`gross_purchase_amount`, + `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`=`tabAsset`.`number_of_depreciations_booked` + WHERE + ( + `tabAsset Depreciation Schedule`.`gross_purchase_amount`<>`tabAsset`.`gross_purchase_amount` + OR + `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`<>`tabAsset`.`number_of_depreciations_booked` + ) + AND `tabAsset Depreciation Schedule`.`docstatus`<2""" + ) From 442ee3adbaab6527ced5a3db4126a395452edc2f Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 12 Mar 2023 14:31:37 +0530 Subject: [PATCH 10/39] fix: operation time for multi-level BOM in WO --- erpnext/manufacturing/doctype/bom/bom.py | 5 +++-- erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 8ab79e68be..619a415c8b 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -31,7 +31,7 @@ class BOMTree: # specifying the attributes to save resources # ref: https://docs.python.org/3/reference/datamodel.html#slots - __slots__ = ["name", "child_items", "is_bom", "item_code", "exploded_qty", "qty"] + __slots__ = ["name", "child_items", "is_bom", "item_code", "qty", "exploded_qty", "bom_qty"] def __init__( self, name: str, is_bom: bool = True, exploded_qty: float = 1.0, qty: float = 1 @@ -50,9 +50,10 @@ class BOMTree: def __create_tree(self): bom = frappe.get_cached_doc("BOM", self.name) self.item_code = bom.item + self.bom_qty = bom.quantity for item in bom.get("items", []): - qty = item.qty / bom.quantity # quantity per unit + qty = item.stock_qty / bom.quantity # quantity per unit exploded_qty = self.exploded_qty * qty if item.bom_no: child = BOMTree(item.bom_no, exploded_qty=exploded_qty, qty=qty) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index ae9e9c6962..66b871c746 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -682,7 +682,7 @@ class WorkOrder(Document): for node in bom_traversal: if node.is_bom: - operations.extend(_get_operations(node.name, qty=node.exploded_qty)) + operations.extend(_get_operations(node.name, qty=node.exploded_qty / node.bom_qty)) bom_qty = frappe.get_cached_value("BOM", self.bom_no, "quantity") operations.extend(_get_operations(self.bom_no, qty=1.0 / bom_qty)) From 66f650061dbae8c1093878f5b808e2a62f3a144a Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 13 Mar 2023 17:21:07 +0530 Subject: [PATCH 11/39] test: add test cases for `Over Order Allowance` against `Blanket Order` --- .../blanket_order/test_blanket_order.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py index 2f1f3ae0f5..58f3c95059 100644 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py @@ -63,6 +63,33 @@ class TestBlanketOrder(FrappeTestCase): po1.currency = get_company_currency(po1.company) self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty)) + def test_over_order_allowance(self): + # Sales Order + bo = make_blanket_order(blanket_order_type="Selling", quantity=100) + + frappe.flags.args.doctype = "Sales Order" + so = make_order(bo.name) + so.currency = get_company_currency(so.company) + so.delivery_date = today() + so.items[0].qty = 110 + self.assertRaises(frappe.ValidationError, so.submit) + + frappe.db.set_single_value("Selling Settings", "over_order_allowance", 10) + so.submit() + + # Purchase Order + bo = make_blanket_order(blanket_order_type="Purchasing", quantity=100) + + frappe.flags.args.doctype = "Purchase Order" + po = make_order(bo.name) + po.currency = get_company_currency(po.company) + po.schedule_date = today() + po.items[0].qty = 110 + self.assertRaises(frappe.ValidationError, po.submit) + + frappe.db.set_single_value("Buying Settings", "over_order_allowance", 10) + po.submit() + def make_blanket_order(**args): args = frappe._dict(args) From 4416ddc4afab0ba0ae61421f4c708fbdf4e490fb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 13 Mar 2023 18:36:43 +0530 Subject: [PATCH 12/39] fix: Linked invoice cancellation issue via timesheet (#34337) --- erpnext/projects/doctype/timesheet/timesheet.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index a376bf46a5..d1d07a79d6 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -5,6 +5,8 @@ frappe.ui.form.on("Timesheet", { setup: function(frm) { frappe.require("/assets/erpnext/js/projects/timer.js"); + frm.ignore_doctypes_on_cancel_all = ['Sales Invoice']; + frm.fields_dict.employee.get_query = function() { return { filters:{ From c8cc3fc65fb51a21350f445b36739547484bd6a6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 13 Mar 2023 18:37:19 +0530 Subject: [PATCH 13/39] chore: Move source and campaign to additional info section (#34414) --- .../doctype/sales_invoice/sales_invoice.json | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 2f4e45e618..2a8ff40413 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -32,9 +32,6 @@ "cost_center", "dimension_col_break", "project", - "column_break_27", - "campaign", - "source", "currency_and_price_list", "currency", "conversion_rate", @@ -203,7 +200,9 @@ "more_information", "status", "inter_company_invoice_reference", + "campaign", "represents_company", + "source", "customer_group", "col_break23", "is_internal_customer", @@ -2083,10 +2082,6 @@ "fieldname": "company_addr_col_break", "fieldtype": "Column Break" }, - { - "fieldname": "column_break_27", - "fieldtype": "Column Break" - }, { "fieldname": "column_break_52", "fieldtype": "Column Break" @@ -2143,11 +2138,10 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2023-01-28 19:45:47.538163", + "modified": "2023-03-13 11:43:15.883055", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", - "name_case": "Title Case", "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ From 796ad01b874bb943f2015364a8c84df3eba9c885 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 13 Mar 2023 07:32:34 -0600 Subject: [PATCH 14/39] fix: Add translate function to Delayed Tasks Summary Report (#34411) fix: Add translate function to Delayed Tasks Summary Report --- .../report/delayed_tasks_summary/delayed_tasks_summary.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py index 17e3155e28..766e40e319 100644 --- a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py +++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py @@ -46,6 +46,9 @@ def get_data(filters): # task has no end date, hence no delay task.delay = 0 + task.status = _(task.status) + task.priority = _(task.priority) + # Sort by descending order of delay tasks.sort(key=lambda x: x["delay"], reverse=True) return tasks @@ -73,7 +76,7 @@ def get_chart_data(data): on_track = on_track + 1 charts = { "data": { - "labels": ["On Track", "Delayed"], + "labels": [_("On Track"), _("Delayed")], "datasets": [{"name": "Delayed", "values": [on_track, delay]}], }, "type": "percentage", From d267111e1349737fa546b42ec5da84b68c9b5e26 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Mon, 13 Mar 2023 14:33:16 +0100 Subject: [PATCH 15/39] chore: fix french translation (#34381) chore: update french translation --- erpnext/translations/fr.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 8367afd331..bace129213 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -2801,7 +2801,7 @@ Stock Ledger Entries and GL Entries are reposted for the selected Purchase Recei Stock Levels,Niveaux du Stocks, Stock Liabilities,Passif du Stock, Stock Options,Options du Stock, -Stock Qty,Qté en Stock, +Stock Qty,Qté en unité de stock, Stock Received But Not Billed,Stock Reçus Mais Non Facturés, Stock Reports,Rapports de stock, Stock Summary,Résumé du Stock, From 5c06620f97a7ccf82eb9d4b5bcbdbc9358deecd5 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 13 Mar 2023 14:02:50 +0000 Subject: [PATCH 16/39] fix: set tax category from address before executing `get_regional_address_details` (#34372) --- erpnext/accounts/party.py | 46 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 01cfb58dec..b217f00065 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -32,6 +32,16 @@ from erpnext import get_company_currency from erpnext.accounts.utils import get_fiscal_year from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen +PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"} +SALES_TRANSACTION_TYPES = { + "Quotation", + "Sales Order", + "Delivery Note", + "Sales Invoice", + "POS Invoice", +} +TRANSACTION_TYPES = PURCHASE_TRANSACTION_TYPES | SALES_TRANSACTION_TYPES + class DuplicatePartyAccountError(frappe.ValidationError): pass @@ -124,12 +134,6 @@ def _get_party_details( set_other_values(party_details, party, party_type) set_price_list(party_details, party, party_type, price_list, pos_profile) - party_details["tax_category"] = get_address_tax_category( - party.get("tax_category"), - party_address, - shipping_address if party_type != "Supplier" else party_address, - ) - tax_template = set_taxes( party.name, party_type, @@ -211,20 +215,10 @@ def set_address_details( else: party_details.update(get_company_address(company)) - if doctype and doctype in [ - "Delivery Note", - "Sales Invoice", - "Sales Order", - "Quotation", - "POS Invoice", - ]: - if party_details.company_address: - party_details.update( - get_fetch_values(doctype, "company_address", party_details.company_address) - ) - get_regional_address_details(party_details, doctype, company) + if doctype in SALES_TRANSACTION_TYPES and party_details.company_address: + party_details.update(get_fetch_values(doctype, "company_address", party_details.company_address)) - elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]: + if doctype in PURCHASE_TRANSACTION_TYPES: if shipping_address: party_details.update( shipping_address=shipping_address, @@ -250,9 +244,21 @@ def set_address_details( **get_fetch_values(doctype, "shipping_address", party_details.billing_address) ) + party_address, shipping_address = ( + party_details.get(billing_address_field), + party_details.shipping_address_name, + ) + + party_details["tax_category"] = get_address_tax_category( + party.get("tax_category"), + party_address, + shipping_address if party_type != "Supplier" else party_address, + ) + + if doctype in TRANSACTION_TYPES: get_regional_address_details(party_details, doctype, company) - return party_details.get(billing_address_field), party_details.shipping_address_name + return party_address, shipping_address @erpnext.allow_regional From d8e54a21fb0f8c4c2323c61bcd6397f54873a6c7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 13 Mar 2023 19:34:15 +0530 Subject: [PATCH 17/39] test: Update values in Sales Invoice tests (#34419) --- .../sales_invoice/test_sales_invoice.py | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 0ffd9463e6..6051c9915d 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -266,16 +266,16 @@ class TestSalesInvoice(unittest.TestCase): "_Test Account Education Cess - _TC": [3, 1618, 0.06, 32.36], "_Test Account S&H Education Cess - _TC": [1.5, 1619.5, 0.03, 32.39], "_Test Account CST - _TC": [32.5, 1652, 0.65, 33.04], - "_Test Account VAT - _TC": [156.5, 1808.5, 3.13, 36.17], - "_Test Account Discount - _TC": [-181.0, 1627.5, -3.62, 32.55], + "_Test Account VAT - _TC": [156.0, 1808.0, 3.12, 36.16], + "_Test Account Discount - _TC": [-181.0, 1627.0, -3.62, 32.54], } for d in si.get("taxes"): for i, k in enumerate(expected_values["keys"]): self.assertEqual(d.get(k), expected_values[d.account_head][i]) - self.assertEqual(si.base_grand_total, 1627.5) - self.assertEqual(si.grand_total, 32.55) + self.assertEqual(si.base_grand_total, 1627.0) + self.assertEqual(si.grand_total, 32.54) def test_sales_invoice_with_discount_and_inclusive_tax(self): si = create_sales_invoice(qty=100, rate=50, do_not_save=True) @@ -401,10 +401,10 @@ class TestSalesInvoice(unittest.TestCase): "_Test Account S&H Education Cess - _TC": [1.4, 1.30, 1297.67], "_Test Account CST - _TC": [27.88, 25.95, 1323.62], "_Test Account VAT - _TC": [156.25, 145.43, 1469.05], - "_Test Account Customs Duty - _TC": [125, 116.35, 1585.40], - "_Test Account Shipping Charges - _TC": [100, 100, 1685.40], - "_Test Account Discount - _TC": [-180.33, -168.54, 1516.86], - "_Test Account Service Tax - _TC": [-18.03, -16.85, 1500.01], + "_Test Account Customs Duty - _TC": [125, 116.34, 1585.39], + "_Test Account Shipping Charges - _TC": [100, 100, 1685.39], + "_Test Account Discount - _TC": [-180.33, -168.54, 1516.85], + "_Test Account Service Tax - _TC": [-18.03, -16.85, 1500.00], } for d in si.get("taxes"): @@ -413,7 +413,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(si.base_grand_total, 1500) self.assertEqual(si.grand_total, 1500) - self.assertEqual(si.rounding_adjustment, -0.01) + self.assertEqual(si.rounding_adjustment, 0.0) def test_discount_amount_gl_entry(self): frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC") @@ -454,7 +454,7 @@ class TestSalesInvoice(unittest.TestCase): [test_records[3]["taxes"][2]["account_head"], 0.0, 1.30], [test_records[3]["taxes"][3]["account_head"], 0.0, 25.95], [test_records[3]["taxes"][4]["account_head"], 0.0, 145.43], - [test_records[3]["taxes"][5]["account_head"], 0.0, 116.35], + [test_records[3]["taxes"][5]["account_head"], 0.0, 116.34], [test_records[3]["taxes"][6]["account_head"], 0.0, 100], [test_records[3]["taxes"][7]["account_head"], 168.54, 0.0], ["_Test Account Service Tax - _TC", 16.85, 0.0], @@ -1614,7 +1614,7 @@ class TestSalesInvoice(unittest.TestCase): "_Test Account Education Cess - _TC": [1.4, 1.4, 1.4], "_Test Account S&H Education Cess - _TC": [0.7, 0.7, 0.7], "_Test Account CST - _TC": [17.19, 17.19, 17.19], - "_Test Account VAT - _TC": [78.13, 78.13, 78.13], + "_Test Account VAT - _TC": [78.12, 78.12, 78.12], "_Test Account Discount - _TC": [-95.49, -95.49, -95.49], } @@ -1623,9 +1623,9 @@ class TestSalesInvoice(unittest.TestCase): if expected_values.get(d.account_head): self.assertEqual(d.get(k), expected_values[d.account_head][i]) - self.assertEqual(si.total_taxes_and_charges, 234.43) - self.assertEqual(si.base_grand_total, 859.43) - self.assertEqual(si.grand_total, 859.43) + self.assertEqual(si.total_taxes_and_charges, 234.42) + self.assertEqual(si.base_grand_total, 859.42) + self.assertEqual(si.grand_total, 859.42) def test_multi_currency_gle(self): si = create_sales_invoice( @@ -1985,17 +1985,17 @@ class TestSalesInvoice(unittest.TestCase): ) si.save() si.submit() - self.assertEqual(si.net_total, 19453.13) + self.assertEqual(si.net_total, 19453.12) self.assertEqual(si.grand_total, 24900) self.assertEqual(si.total_taxes_and_charges, 5446.88) - self.assertEqual(si.rounding_adjustment, -0.01) + self.assertEqual(si.rounding_adjustment, 0.0) expected_values = dict( (d[0], d) for d in [ [si.debit_to, 24900, 0.0], ["_Test Account Service Tax - _TC", 0.0, 5446.88], - ["Sales - _TC", 0.0, 19453.13], + ["Sales - _TC", 0.0, 19453.12], ["Round Off - _TC", 0.01, 0.0], ] ) From fa776d2987005f51edf10e112182b8c66c6ac7ec Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 13 Mar 2023 19:37:09 +0530 Subject: [PATCH 18/39] fix: Use customer name instead of name(id) in PSOA (#34412) --- .../process_statement_of_accounts.html | 2 +- .../process_statement_of_accounts.py | 16 +++++++++++----- .../process_statement_of_accounts_customer.json | 12 ++++++++++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 3920d4cf09..b9680dfb3b 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -15,7 +15,7 @@

{{ _("STATEMENTS OF ACCOUNTS") }}

-
{{ _("Customer: ") }} {{filters.party[0] }}
+
{{ _("Customer: ") }} {{filters.party_name[0] }}
{{ _("Date: ") }} {{ frappe.format(filters.from_date, 'Date')}} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index a48c0272ff..a482931a8e 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -24,7 +24,7 @@ from erpnext.accounts.report.general_ledger.general_ledger import execute as get class ProcessStatementOfAccounts(Document): def validate(self): if not self.subject: - self.subject = "Statement Of Accounts for {{ customer.name }}" + self.subject = "Statement Of Accounts for {{ customer.customer_name }}" if not self.body: self.body = "Hello {{ customer.name }},
PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}." @@ -87,6 +87,7 @@ def get_report_pdf(doc, consolidated=True): "account": [doc.account] if doc.account else None, "party_type": "Customer", "party": [entry.customer], + "party_name": [entry.customer_name] if entry.customer_name else None, "presentation_currency": presentation_currency, "group_by": doc.group_by, "currency": doc.currency, @@ -156,7 +157,7 @@ def get_customers_based_on_territory_or_customer_group(customer_collection, coll ] return frappe.get_list( "Customer", - fields=["name", "email_id"], + fields=["name", "customer_name", "email_id"], filters=[[fields_dict[customer_collection], "IN", selected]], ) @@ -179,7 +180,7 @@ def get_customers_based_on_sales_person(sales_person): if sales_person_records.get("Customer"): return frappe.get_list( "Customer", - fields=["name", "email_id"], + fields=["name", "customer_name", "email_id"], filters=[["name", "in", list(sales_person_records["Customer"])]], ) else: @@ -228,7 +229,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): if customer_collection == "Sales Partner": customers = frappe.get_list( "Customer", - fields=["name", "email_id"], + fields=["name", "customer_name", "email_id"], filters=[["default_sales_partner", "=", collection_name]], ) else: @@ -245,7 +246,12 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): continue customer_list.append( - {"name": customer.name, "primary_email": primary_email, "billing_email": billing_email} + { + "name": customer.name, + "customer_name": customer.customer_name, + "primary_email": primary_email, + "billing_email": billing_email, + } ) return customer_list diff --git a/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json index dd04dc1b3c..8bffd6a93b 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json @@ -1,12 +1,12 @@ { "actions": [], - "allow_workflow": 1, "creation": "2020-08-03 16:35:21.852178", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "customer", + "customer_name", "billing_email", "primary_email" ], @@ -30,11 +30,18 @@ "fieldtype": "Read Only", "in_list_view": 1, "label": "Billing Email" + }, + { + "fetch_from": "customer.customer_name", + "fieldname": "customer_name", + "fieldtype": "Data", + "label": "Customer Name", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2020-08-03 22:55:38.875601", + "modified": "2023-03-13 00:12:34.508086", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts Customer", @@ -43,5 +50,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From c6999fc6879df2049f2bff6ee68ee17ac5911606 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 13 Mar 2023 21:18:30 +0530 Subject: [PATCH 19/39] fix: Total row in trail balance report (#34395) * fix: Total row in trail balance report * fix: Calculate total after preparing opening and closing --- .../report/trial_balance/trial_balance.py | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 3af01fde7d..bc334c7060 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -78,7 +78,6 @@ def validate_filters(filters): def get_data(filters): - accounts = frappe.db.sql( """select name, account_number, parent_account, account_name, root_type, report_type, lft, rgt @@ -118,12 +117,10 @@ def get_data(filters): ignore_closing_entries=not flt(filters.with_period_closing_entry), ) - total_row = calculate_values( - accounts, gl_entries_by_account, opening_balances, filters, company_currency - ) + calculate_values(accounts, gl_entries_by_account, opening_balances) accumulate_values_into_parents(accounts, accounts_by_name) - data = prepare_data(accounts, filters, total_row, parent_children_map, company_currency) + data = prepare_data(accounts, filters, parent_children_map, company_currency) data = filter_out_zero_value_rows( data, parent_children_map, show_zero_values=filters.get("show_zero_values") ) @@ -218,7 +215,7 @@ def get_rootwise_opening_balances(filters, report_type): return opening -def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, company_currency): +def calculate_values(accounts, gl_entries_by_account, opening_balances): init = { "opening_debit": 0.0, "opening_credit": 0.0, @@ -228,22 +225,6 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, "closing_credit": 0.0, } - total_row = { - "account": "'" + _("Total") + "'", - "account_name": "'" + _("Total") + "'", - "warn_if_negative": True, - "opening_debit": 0.0, - "opening_credit": 0.0, - "debit": 0.0, - "credit": 0.0, - "closing_debit": 0.0, - "closing_credit": 0.0, - "parent_account": None, - "indent": 0, - "has_value": True, - "currency": company_currency, - } - for d in accounts: d.update(init.copy()) @@ -261,8 +242,28 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, prepare_opening_closing(d) - for field in value_fields: - total_row[field] += d[field] + +def calculate_total_row(accounts, company_currency): + total_row = { + "account": "'" + _("Total") + "'", + "account_name": "'" + _("Total") + "'", + "warn_if_negative": True, + "opening_debit": 0.0, + "opening_credit": 0.0, + "debit": 0.0, + "credit": 0.0, + "closing_debit": 0.0, + "closing_credit": 0.0, + "parent_account": None, + "indent": 0, + "has_value": True, + "currency": company_currency, + } + + for d in accounts: + if not d.parent_account: + for field in value_fields: + total_row[field] += d[field] return total_row @@ -274,7 +275,7 @@ def accumulate_values_into_parents(accounts, accounts_by_name): accounts_by_name[d.parent_account][key] += d[key] -def prepare_data(accounts, filters, total_row, parent_children_map, company_currency): +def prepare_data(accounts, filters, parent_children_map, company_currency): data = [] for d in accounts: @@ -305,6 +306,7 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr row["has_value"] = has_value data.append(row) + total_row = calculate_total_row(accounts, company_currency) data.extend([{}, total_row]) return data From 22ad9a1903b8cd51829b68df02d05708054871cd Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 14 Mar 2023 14:12:39 +0530 Subject: [PATCH 20/39] chore: `Allow Zero Valuation Rate` msg in SE --- .../stock/doctype/stock_entry/stock_entry.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7f69397fce..36c875f308 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -658,6 +658,7 @@ class StockEntry(StockController): ) finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item) + items = [] # Set basic rate for incoming items for d in self.get("items"): if d.s_warehouse or d.set_basic_rate_manually: @@ -665,12 +666,7 @@ class StockEntry(StockController): if d.allow_zero_valuation_rate: d.basic_rate = 0.0 - frappe.msgprint( - _( - "Row {0}: Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {1}" - ).format(d.idx, d.item_code), - alert=1, - ) + items.append(d.item_code) elif d.is_finished_item: if self.purpose == "Manufacture": @@ -697,6 +693,20 @@ class StockEntry(StockController): d.basic_rate = flt(d.basic_rate) d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount")) + if items: + message = "" + + if len(items) > 1: + message = _( + "Items rate has been updated to zero as Allow Zero Valuation Rate is checked for the following items: {0}" + ).format(", ".join(frappe.bold(item) for item in items)) + else: + message = _( + "Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {0}" + ).format(frappe.bold(items[0])) + + frappe.msgprint(message, alert=True) + def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True): outgoing_items_cost = 0.0 for d in self.get("items"): From 70c78d0d6739d531c7897e702a2bfeac72784d3d Mon Sep 17 00:00:00 2001 From: Johannes Obermeier Date: Tue, 14 Mar 2023 10:57:05 +0100 Subject: [PATCH 21/39] fix: incorrect EAN validation, EAN can be an EAN8, EAN12 or EAN13 code (#34250) Co-authored-by: Deepesh Garg --- erpnext/stock/doctype/item/item.py | 25 ++++++++++++++++++------- erpnext/stock/doctype/item/test_item.py | 5 +++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index c06700a99a..05a37ee4c4 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -377,7 +377,9 @@ class Item(Document): "" if item_barcode.barcode_type not in options else item_barcode.barcode_type ) if item_barcode.barcode_type: - barcode_type = convert_erpnext_to_barcodenumber(item_barcode.barcode_type.upper()) + barcode_type = convert_erpnext_to_barcodenumber( + item_barcode.barcode_type.upper(), item_barcode.barcode + ) if barcode_type in barcodenumber.barcodes(): if not barcodenumber.check_code(barcode_type, item_barcode.barcode): frappe.throw( @@ -982,20 +984,29 @@ class Item(Document): ) -def convert_erpnext_to_barcodenumber(erpnext_number): +def convert_erpnext_to_barcodenumber(erpnext_number, barcode): + if erpnext_number == "EAN": + ean_type = { + 8: "EAN8", + 13: "EAN13", + } + barcode_length = len(barcode) + if barcode_length in ean_type: + return ean_type[barcode_length] + + return erpnext_number + convert = { "UPC-A": "UPCA", "CODE-39": "CODE39", - "EAN": "EAN13", - "EAN-12": "EAN", - "EAN-8": "EAN8", "ISBN-10": "ISBN10", "ISBN-13": "ISBN13", } + if erpnext_number in convert: return convert[erpnext_number] - else: - return erpnext_number + + return erpnext_number def make_item_price(item, price_list_name, item_price): diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 67ed90d4e7..0c6dc77635 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -581,8 +581,9 @@ class TestItem(FrappeTestCase): }, {"barcode": "72527273070", "barcode_type": "UPC-A"}, {"barcode": "123456", "barcode_type": "CODE-39"}, - {"barcode": "401268452363", "barcode_type": "EAN-12"}, - {"barcode": "90311017", "barcode_type": "EAN-8"}, + {"barcode": "401268452363", "barcode_type": "EAN"}, + {"barcode": "90311017", "barcode_type": "EAN"}, + {"barcode": "73513537", "barcode_type": "EAN"}, {"barcode": "0123456789012", "barcode_type": "GS1"}, {"barcode": "2211564566668", "barcode_type": "GTIN"}, {"barcode": "0256480249", "barcode_type": "ISBN"}, From b89ecd482d4c8db69765f633bc22c95619f2f3f9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 14 Mar 2023 15:53:03 +0530 Subject: [PATCH 22/39] chore: remove failing test (#34444) --- erpnext/manufacturing/doctype/bom/test_bom.py | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index d60feb2b39..75229991b1 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -71,34 +71,6 @@ class TestBOM(FrappeTestCase): self.assertTrue(_get_default_bom_in_item(), bom.name) - def test_update_bom_cost_in_all_boms(self): - # get current rate for '_Test Item 2' - bom_rates = frappe.db.get_values( - "BOM Item", - { - "parent": "BOM-_Test Item Home Desktop Manufactured-001", - "item_code": "_Test Item 2", - "docstatus": 1, - }, - fieldname=["rate", "base_rate"], - as_dict=True, - ) - rm_base_rate = bom_rates[0].get("base_rate") if bom_rates else 0 - - # Reset item valuation rate - reset_item_valuation_rate(item_code="_Test Item 2", qty=200, rate=rm_base_rate + 10) - - # update cost of all BOMs based on latest valuation rate - update_cost_in_all_boms_in_test() - - # check if new valuation rate updated in all BOMs - for d in frappe.db.sql( - """select base_rate from `tabBOM Item` - where item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""", - as_dict=1, - ): - self.assertEqual(d.base_rate, rm_base_rate + 10) - def test_bom_cost(self): bom = frappe.copy_doc(test_records[2]) bom.insert() From f95ad039e4564d9516cd654816c0180d2c71a7a9 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Mar 2023 18:10:17 +0530 Subject: [PATCH 23/39] test: add timeout to all BOM related tests (#34446) * Revert "chore: remove failing test (#34444)" This reverts commit b89ecd482d4c8db69765f633bc22c95619f2f3f9. * test: add timeout to bom tests --- erpnext/manufacturing/doctype/bom/test_bom.py | 57 ++++++++++++++++++- .../bom_update_tool/test_bom_update_tool.py | 4 +- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 75229991b1..01bf2e4315 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -6,7 +6,7 @@ from collections import deque from functools import partial import frappe -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, timeout from frappe.utils import cstr, flt from erpnext.controllers.tests.test_subcontracting_controller import ( @@ -27,6 +27,7 @@ test_dependencies = ["Item", "Quality Inspection Template"] class TestBOM(FrappeTestCase): + @timeout def test_get_items(self): from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict @@ -37,6 +38,7 @@ class TestBOM(FrappeTestCase): self.assertTrue(test_records[2]["items"][1]["item_code"] in items_dict) self.assertEqual(len(items_dict.values()), 2) + @timeout def test_get_items_exploded(self): from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict @@ -49,11 +51,13 @@ class TestBOM(FrappeTestCase): self.assertTrue(test_records[0]["items"][1]["item_code"] in items_dict) self.assertEqual(len(items_dict.values()), 3) + @timeout def test_get_items_list(self): from erpnext.manufacturing.doctype.bom.bom import get_bom_items self.assertEqual(len(get_bom_items(bom=get_default_bom(), company="_Test Company")), 3) + @timeout def test_default_bom(self): def _get_default_bom_in_item(): return cstr(frappe.db.get_value("Item", "_Test FG Item 2", "default_bom")) @@ -71,6 +75,36 @@ class TestBOM(FrappeTestCase): self.assertTrue(_get_default_bom_in_item(), bom.name) + @timeout + def test_update_bom_cost_in_all_boms(self): + # get current rate for '_Test Item 2' + bom_rates = frappe.db.get_values( + "BOM Item", + { + "parent": "BOM-_Test Item Home Desktop Manufactured-001", + "item_code": "_Test Item 2", + "docstatus": 1, + }, + fieldname=["rate", "base_rate"], + as_dict=True, + ) + rm_base_rate = bom_rates[0].get("base_rate") if bom_rates else 0 + + # Reset item valuation rate + reset_item_valuation_rate(item_code="_Test Item 2", qty=200, rate=rm_base_rate + 10) + + # update cost of all BOMs based on latest valuation rate + update_cost_in_all_boms_in_test() + + # check if new valuation rate updated in all BOMs + for d in frappe.db.sql( + """select base_rate from `tabBOM Item` + where item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""", + as_dict=1, + ): + self.assertEqual(d.base_rate, rm_base_rate + 10) + + @timeout def test_bom_cost(self): bom = frappe.copy_doc(test_records[2]) bom.insert() @@ -99,6 +133,7 @@ class TestBOM(FrappeTestCase): self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost) self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) + @timeout def test_bom_cost_with_batch_size(self): bom = frappe.copy_doc(test_records[2]) bom.docstatus = 0 @@ -117,6 +152,7 @@ class TestBOM(FrappeTestCase): self.assertAlmostEqual(bom.operating_cost, op_cost / 2) bom.delete() + @timeout def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1) for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)): @@ -153,6 +189,7 @@ class TestBOM(FrappeTestCase): self.assertEqual(bom.base_raw_material_cost, 27000) self.assertEqual(bom.base_total_cost, 33000) + @timeout def test_bom_cost_multi_uom_based_on_valuation_rate(self): bom = frappe.copy_doc(test_records[2]) bom.set_rate_of_sub_assembly_item_based_on_bom = 0 @@ -174,6 +211,7 @@ class TestBOM(FrappeTestCase): self.assertEqual(bom.items[0].rate, 20) + @timeout def test_bom_cost_with_fg_based_operating_cost(self): bom = frappe.copy_doc(test_records[4]) bom.insert() @@ -201,6 +239,7 @@ class TestBOM(FrappeTestCase): self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost) self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) + @timeout def test_subcontractor_sourced_item(self): item_code = "_Test Subcontracted FG Item 1" set_backflush_based_on("Material Transferred for Subcontract") @@ -282,6 +321,7 @@ class TestBOM(FrappeTestCase): supplied_items = sorted([d.rm_item_code for d in sco.supplied_items]) self.assertEqual(bom_items, supplied_items) + @timeout def test_bom_tree_representation(self): bom_tree = { "Assembly": { @@ -307,6 +347,7 @@ class TestBOM(FrappeTestCase): for reqd_item, created_item in zip(reqd_order, created_order): self.assertEqual(reqd_item, created_item.item_code) + @timeout def test_generated_variant_bom(self): from erpnext.controllers.item_variant import create_variant @@ -347,6 +388,7 @@ class TestBOM(FrappeTestCase): self.assertEqual(reqd_item.qty, created_item.qty) self.assertEqual(reqd_item.exploded_qty, created_item.exploded_qty) + @timeout def test_bom_recursion_1st_level(self): """BOM should not allow BOM item again in child""" item_code = make_item(properties={"is_stock_item": 1}).name @@ -359,6 +401,7 @@ class TestBOM(FrappeTestCase): bom.items[0].bom_no = bom.name bom.save() + @timeout def test_bom_recursion_transitive(self): item1 = make_item(properties={"is_stock_item": 1}).name item2 = make_item(properties={"is_stock_item": 1}).name @@ -380,6 +423,7 @@ class TestBOM(FrappeTestCase): bom1.save() bom2.save() + @timeout def test_bom_with_process_loss_item(self): fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items() @@ -393,6 +437,7 @@ class TestBOM(FrappeTestCase): # Items with whole UOMs can't be PL Items self.assertRaises(frappe.ValidationError, bom_doc.submit) + @timeout def test_bom_item_query(self): query = partial( item_query, @@ -412,6 +457,7 @@ class TestBOM(FrappeTestCase): ) self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results") + @timeout def test_exclude_exploded_items_from_bom(self): bom_no = get_default_bom() new_bom = frappe.copy_doc(frappe.get_doc("BOM", bom_no)) @@ -430,6 +476,7 @@ class TestBOM(FrappeTestCase): new_bom.delete() + @timeout def test_valid_transfer_defaults(self): bom_with_op = frappe.db.get_value( "BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1} @@ -461,11 +508,13 @@ class TestBOM(FrappeTestCase): self.assertEqual(bom.transfer_material_against, "Work Order") bom.delete() + @timeout def test_bom_name_length(self): """test >140 char names""" bom_tree = {"x" * 140: {" ".join(["abc"] * 35): {}}} create_nested_bom(bom_tree, prefix="") + @timeout def test_version_index(self): bom = frappe.new_doc("BOM") @@ -487,6 +536,7 @@ class TestBOM(FrappeTestCase): msg=f"Incorrect index for {existing_boms}", ) + @timeout def test_bom_versioning(self): bom_tree = {frappe.generate_hash(length=10): {frappe.generate_hash(length=10): {}}} bom = create_nested_bom(bom_tree, prefix="") @@ -519,6 +569,7 @@ class TestBOM(FrappeTestCase): self.assertNotEqual(amendment.name, version.name) self.assertEqual(int(version.name.split("-")[-1]), 2) + @timeout def test_clear_inpection_quality(self): bom = frappe.copy_doc(test_records[2], ignore_no_copy=True) @@ -537,6 +588,7 @@ class TestBOM(FrappeTestCase): self.assertEqual(bom.quality_inspection_template, None) + @timeout def test_bom_pricing_based_on_lpp(self): from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt @@ -557,6 +609,7 @@ class TestBOM(FrappeTestCase): bom.submit() self.assertEqual(bom.items[0].rate, 42) + @timeout def test_set_default_bom_for_item_having_single_bom(self): from erpnext.stock.doctype.item.test_item import make_item @@ -593,6 +646,7 @@ class TestBOM(FrappeTestCase): bom.reload() self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name) + @timeout def test_exploded_items_rate(self): rm_item = make_item( properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89} @@ -621,6 +675,7 @@ class TestBOM(FrappeTestCase): bom.submit() self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) + @timeout def test_bom_cost_update_flag(self): rm_item = make_item( properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89} diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py index 5dd557f8ab..2026f62914 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py @@ -2,7 +2,7 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, timeout from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import ( update_cost_in_all_boms_in_test, @@ -20,6 +20,7 @@ class TestBOMUpdateTool(FrappeTestCase): def tearDown(self): frappe.db.rollback() + @timeout def test_replace_bom(self): current_bom = "BOM-_Test Item Home Desktop Manufactured-001" @@ -33,6 +34,7 @@ class TestBOMUpdateTool(FrappeTestCase): self.assertFalse(frappe.db.exists("BOM Item", {"bom_no": current_bom, "docstatus": 1})) self.assertTrue(frappe.db.exists("BOM Item", {"bom_no": bom_doc.name, "docstatus": 1})) + @timeout def test_bom_cost(self): for item in ["BOM Cost Test Item 1", "BOM Cost Test Item 2", "BOM Cost Test Item 3"]: item_doc = create_item(item, valuation_rate=100) From e0042972c85e916e3081818570d79d2378f222a6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Mar 2023 18:49:04 +0530 Subject: [PATCH 24/39] test: add test for primary address sorting --- erpnext/setup/doctype/company/test_company.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index 29e056e34f..fd2fe300fa 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -11,6 +11,7 @@ from frappe.utils import random_string from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import ( get_charts_for_country, ) +from erpnext.setup.doctype.company.company import get_default_company_address test_ignore = ["Account", "Cost Center", "Payment Terms Template", "Salary Component", "Warehouse"] test_dependencies = ["Fiscal Year"] @@ -132,6 +133,38 @@ class TestCompany(unittest.TestCase): self.assertTrue(lft >= min_lft) self.assertTrue(rgt <= max_rgt) + def test_primary_address(self): + company = "_Test Company" + + secondary = frappe.get_doc( + { + "address_title": "Non Primary", + "doctype": "Address", + "address_type": "Billing", + "address_line1": "Something", + "city": "Mumbai", + "state": "Maharashtra", + "country": "India", + "is_primary_address": 1, + "pincode": "400098", + "links": [ + { + "link_doctype": "Company", + "link_name": company, + } + ], + } + ) + secondary.insert() + self.addCleanup(secondary.delete) + + primary = frappe.copy_doc(secondary) + primary.is_primary_address = 1 + primary.insert() + self.addCleanup(primary.delete) + + self.assertEqual(get_default_company_address(company), primary.name) + def get_no_of_children(self, company): def get_no_of_children(companies, no_of_children): children = [] From bd2dd7dfac38d91d47a417d2f9a18a0a6f9809b9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 16:20:21 +0530 Subject: [PATCH 25/39] ci: use version specific payments repo (backport #34468) (#34470) ci: use version specific payments repo (#34468) ci: use version-14 branch of payments repo for v14 erpnext (cherry picked from commit befd1a0f918d8dc35ef071b8d00dd4ae95e167ab) Co-authored-by: Ritwik Puri --- .github/helper/install.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 0c71b41a7c..48337cee64 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -8,8 +8,9 @@ sudo apt update && sudo apt install redis-server libcups2-dev pip install frappe-bench +githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}} frappeuser=${FRAPPE_USER:-"frappe"} -frappebranch=${FRAPPE_BRANCH:-${GITHUB_BASE_REF:-${GITHUB_REF##*/}}} +frappebranch=${FRAPPE_BRANCH:-$githubbranch} git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench @@ -60,7 +61,7 @@ sed -i 's/schedule:/# schedule:/g' Procfile sed -i 's/socketio:/# socketio:/g' Procfile sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile -bench get-app payments +bench get-app payments --branch ${githubbranch%"-hotfix"} bench get-app erpnext "${GITHUB_WORKSPACE}" if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi From b6b83924137bfbf3c2769f8207c72c51f0c64acd Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Thu, 16 Mar 2023 18:18:47 +0530 Subject: [PATCH 26/39] fix: refactor asset depr schedule and remove unnecessary depr method (#34434) * fix: remove depr method from depr schedule and refactor assetdeprsch * chore: use assetdeprsch's depr method, not deprschedule's * fix: use default 0 value for NDB and OAD * chore: fix rounded numbers * chore: correct rounding in test_website_item_price_for_logged_in_user --- erpnext/assets/doctype/asset/test_asset.py | 4 +-- .../asset_depreciation_schedule.py | 35 ++++++++----------- .../depreciation_schedule.json | 14 ++------ .../doctype/website_item/test_website_item.py | 4 +-- 4 files changed, 21 insertions(+), 36 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 9a152638f9..2c9772d12a 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -828,8 +828,8 @@ class TestDepreciationMethods(AssetSetup): expected_schedules = [ ["2030-12-31", 28630.14, 28630.14], ["2031-12-31", 35684.93, 64315.07], - ["2032-12-31", 17842.47, 82157.54], - ["2033-06-06", 5342.46, 87500.0], + ["2032-12-31", 17842.46, 82157.53], + ["2033-06-06", 5342.47, 87500.0], ] schedules = [ diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index b75fbcbeb3..5912329846 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -140,8 +140,8 @@ class AssetDepreciationSchedule(Document): self.asset = asset_doc.name self.finance_book = row.finance_book self.finance_book_id = row.idx - self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation - self.number_of_depreciations_booked = asset_doc.number_of_depreciations_booked + self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation or 0 + self.number_of_depreciations_booked = asset_doc.number_of_depreciations_booked or 0 self.gross_purchase_amount = asset_doc.gross_purchase_amount self.depreciation_method = row.depreciation_method self.total_number_of_depreciations = row.total_number_of_depreciations @@ -185,14 +185,14 @@ class AssetDepreciationSchedule(Document): ): asset_doc.validate_asset_finance_books(row) - value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row) + value_after_depreciation = self._get_value_after_depreciation_for_making_schedule(asset_doc, row) row.value_after_depreciation = value_after_depreciation if update_asset_finance_book_row: row.db_update() number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint( - asset_doc.number_of_depreciations_booked + self.number_of_depreciations_booked ) has_pro_rata = asset_doc.check_is_pro_rata(row) @@ -235,13 +235,12 @@ class AssetDepreciationSchedule(Document): self.add_depr_schedule_row( date_of_disposal, depreciation_amount, - row.depreciation_method, ) break # For first row - if has_pro_rata and not asset_doc.opening_accumulated_depreciation and n == 0: + if has_pro_rata and not self.opening_accumulated_depreciation and n == 0: from_date = add_days( asset_doc.available_for_use_date, -1 ) # needed to calc depr amount for available_for_use_date too @@ -260,7 +259,7 @@ class AssetDepreciationSchedule(Document): # In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission asset_doc.to_date = add_months( asset_doc.available_for_use_date, - (n + asset_doc.number_of_depreciations_booked) * cint(row.frequency_of_depreciation), + (n + self.number_of_depreciations_booked) * cint(row.frequency_of_depreciation), ) depreciation_amount_without_pro_rata = depreciation_amount @@ -298,7 +297,6 @@ class AssetDepreciationSchedule(Document): self.add_depr_schedule_row( schedule_date, depreciation_amount, - row.depreciation_method, ) # to ensure that final accumulated depreciation amount is accurate @@ -325,14 +323,12 @@ class AssetDepreciationSchedule(Document): self, schedule_date, depreciation_amount, - depreciation_method, ): self.append( "depreciation_schedule", { "schedule_date": schedule_date, "depreciation_amount": depreciation_amount, - "depreciation_method": depreciation_method, }, ) @@ -346,7 +342,7 @@ class AssetDepreciationSchedule(Document): straight_line_idx = [ d.idx for d in self.get("depreciation_schedule") - if d.depreciation_method == "Straight Line" or d.depreciation_method == "Manual" + if self.depreciation_method == "Straight Line" or self.depreciation_method == "Manual" ] accumulated_depreciation = flt(self.opening_accumulated_depreciation) @@ -377,16 +373,15 @@ class AssetDepreciationSchedule(Document): accumulated_depreciation, d.precision("accumulated_depreciation_amount") ) + def _get_value_after_depreciation_for_making_schedule(self, asset_doc, fb_row): + if asset_doc.docstatus == 1 and fb_row.value_after_depreciation: + value_after_depreciation = flt(fb_row.value_after_depreciation) + else: + value_after_depreciation = flt(self.gross_purchase_amount) - flt( + self.opening_accumulated_depreciation + ) -def _get_value_after_depreciation_for_making_schedule(asset_doc, fb_row): - if asset_doc.docstatus == 1 and fb_row.value_after_depreciation: - value_after_depreciation = flt(fb_row.value_after_depreciation) - else: - value_after_depreciation = flt(asset_doc.gross_purchase_amount) - flt( - asset_doc.opening_accumulated_depreciation - ) - - return value_after_depreciation + return value_after_depreciation def make_draft_asset_depr_schedules_if_not_present(asset_doc): diff --git a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json index 882c4bf00b..abe295c680 100644 --- a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json +++ b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json @@ -12,8 +12,7 @@ "column_break_3", "accumulated_depreciation_amount", "journal_entry", - "make_depreciation_entry", - "depreciation_method" + "make_depreciation_entry" ], "fields": [ { @@ -58,20 +57,11 @@ "fieldname": "make_depreciation_entry", "fieldtype": "Button", "label": "Make Depreciation Entry" - }, - { - "fieldname": "depreciation_method", - "fieldtype": "Select", - "hidden": 1, - "label": "Depreciation Method", - "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual", - "print_hide": 1, - "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2022-12-06 20:35:50.264281", + "modified": "2023-03-13 23:17:15.849950", "modified_by": "Administrator", "module": "Assets", "name": "Depreciation Schedule", diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py index bbe04d5514..e41c9da594 100644 --- a/erpnext/e_commerce/doctype/website_item/test_website_item.py +++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py @@ -226,11 +226,11 @@ class TestWebsiteItem(unittest.TestCase): self.assertTrue(bool(data.product_info["price"])) price_object = data.product_info["price"] - self.assertEqual(price_object.get("discount_percent"), 25) + self.assertEqual(price_object.get("discount_percent"), 25.0) self.assertEqual(price_object.get("price_list_rate"), 750) self.assertEqual(price_object.get("formatted_mrp"), "₹ 1,000.00") self.assertEqual(price_object.get("formatted_price"), "₹ 750.00") - self.assertEqual(price_object.get("formatted_discount_percent"), "25%") + self.assertEqual(price_object.get("formatted_discount_percent"), "25.0%") # switch to admin and disable show price frappe.set_user("Administrator") From 48fae0c1ce7b95bac8ba6e46516ac58a070e6003 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Mar 2023 09:54:06 +0530 Subject: [PATCH 27/39] fix: difference amount calculation for company currency accounts --- .../payment_reconciliation.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index e3d9c26b2d..c9e3998ac8 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -221,12 +221,15 @@ class PaymentReconciliation(Document): def get_difference_amount(self, payment_entry, invoice, allocated_amount): difference_amount = 0 - if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get( - "exchange_rate", 1 - ): - allocated_amount_in_ref_rate = payment_entry.get("exchange_rate", 1) * allocated_amount - allocated_amount_in_inv_rate = invoice.get("exchange_rate", 1) * allocated_amount - difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate + if frappe.get_cached_value( + "Account", self.receivable_payable_account, "account_currency" + ) != frappe.get_cached_value("Company", self.company, "default_currency"): + if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get( + "exchange_rate", 1 + ): + allocated_amount_in_ref_rate = payment_entry.get("exchange_rate", 1) * allocated_amount + allocated_amount_in_inv_rate = invoice.get("exchange_rate", 1) * allocated_amount + difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate return difference_amount From 861387f16447fc635e959a706090724a55840d4b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Mar 2023 10:35:06 +0530 Subject: [PATCH 28/39] test: difference amount should not be calculated for base currency --- .../test_payment_reconciliation.py | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index f9dda0593b..fca6caedd5 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -5,7 +5,7 @@ import unittest import frappe from frappe import qb -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, nowdate from erpnext import get_default_cost_center @@ -824,6 +824,52 @@ class TestPaymentReconciliation(FrappeTestCase): payment_vouchers = [x.get("reference_name") for x in pr.get("payments")] self.assertCountEqual(payment_vouchers, [je2.name, pe2.name]) + @change_settings( + "Accounts Settings", + { + "allow_multi_currency_invoices_against_single_party_account": 1, + }, + ) + def test_no_difference_amount_for_base_currency_accounts(self): + # Make Sale Invoice + si = self.create_sales_invoice( + qty=1, rate=1, posting_date=nowdate(), do_not_save=True, do_not_submit=True + ) + si.customer = self.customer + si.currency = "EUR" + si.conversion_rate = 85 + si.debit_to = self.debit_to + si.save().submit() + + # Make payment using Payment Entry + pe1 = create_payment_entry( + company=self.company, + payment_type="Receive", + party_type="Customer", + party=self.customer, + paid_from=self.debit_to, + paid_to=self.bank, + paid_amount=100, + ) + + pe1.save() + pe1.submit() + + pr = self.create_payment_reconciliation() + pr.party = self.customer + pr.receivable_payable_account = self.debit_to + pr.get_unreconciled_entries() + + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + + invoices = [x.as_dict() for x in pr.invoices] + payments = [pr.payments[0].as_dict()] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + self.assertEqual(pr.allocation[0].allocated_amount, 85) + self.assertEqual(pr.allocation[0].difference_amount, 0) + def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): From ec075122b661b36763de0ecc6d5cb57895278871 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Mar 2023 11:12:44 +0530 Subject: [PATCH 29/39] refactor: difference amt validation for same currency accounts --- .../test_payment_reconciliation.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index fca6caedd5..3be11ae31a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -349,6 +349,11 @@ class TestPaymentReconciliation(FrappeTestCase): invoices = [x.as_dict() for x in pr.get("invoices")] payments = [x.as_dict() for x in pr.get("payments")] pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + # Difference amount should not be calculated for base currency accounts + for row in pr.allocation: + self.assertEqual(flt(row.get("difference_amount")), 0.0) + pr.reconcile() si.reload() @@ -390,6 +395,11 @@ class TestPaymentReconciliation(FrappeTestCase): invoices = [x.as_dict() for x in pr.get("invoices")] payments = [x.as_dict() for x in pr.get("payments")] pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + # Difference amount should not be calculated for base currency accounts + for row in pr.allocation: + self.assertEqual(flt(row.get("difference_amount")), 0.0) + pr.reconcile() # check PR tool output @@ -414,6 +424,11 @@ class TestPaymentReconciliation(FrappeTestCase): invoices = [x.as_dict() for x in pr.get("invoices")] payments = [x.as_dict() for x in pr.get("payments")] pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + # Difference amount should not be calculated for base currency accounts + for row in pr.allocation: + self.assertEqual(flt(row.get("difference_amount")), 0.0) + pr.reconcile() # assert outstanding @@ -450,6 +465,11 @@ class TestPaymentReconciliation(FrappeTestCase): invoices = [x.as_dict() for x in pr.get("invoices")] payments = [x.as_dict() for x in pr.get("payments")] pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + # Difference amount should not be calculated for base currency accounts + for row in pr.allocation: + self.assertEqual(flt(row.get("difference_amount")), 0.0) + pr.reconcile() self.assertEqual(pr.get("invoices"), []) From e2f19c6a14f95d4f26acd8dfa91f50335f58b290 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 5 Feb 2023 13:09:34 +0530 Subject: [PATCH 30/39] fix: Overallocation of 'qty' from Cr Notes to Parent Invoice Cr Notes 'qty' are overallocated to parent invoice, when there are mulitple instances of same item in Invoice. --- erpnext/accounts/report/gross_profit/gross_profit.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index fde4de8402..67599fd815 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -501,7 +501,14 @@ class GrossProfitGenerator(object): ): returned_item_rows = self.returned_invoices[row.parent][row.item_code] for returned_item_row in returned_item_rows: - row.qty += flt(returned_item_row.qty) + # returned_items 'qty' should be stateful + if returned_item_row.qty != 0: + if row.qty >= abs(returned_item_row.qty): + row.qty += returned_item_row.qty + returned_item_row.qty = 0 + else: + row.qty = 0 + returned_item_row.qty += row.qty row.base_amount += flt(returned_item_row.base_amount, self.currency_precision) row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision) if flt(row.qty) or row.base_amount: From d0715a82ebfbd691c70c9e01cdf2357f40f19d04 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 14 Mar 2023 16:22:49 +0530 Subject: [PATCH 31/39] refactor: Ignore linked Cr Notes in Report output --- erpnext/accounts/report/gross_profit/gross_profit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 67599fd815..01fee281b0 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -741,6 +741,8 @@ class GrossProfitGenerator(object): if self.filters.to_date: conditions += " and posting_date <= %(to_date)s" + conditions += " and (is_return = 0 or (is_return=1 and return_against is null))" + if self.filters.item_group: conditions += " and {0}".format(get_item_group_condition(self.filters.item_group)) From cc61daeec4fc78f0c50af44db62998d12b2d5ea5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 5 Feb 2023 14:48:29 +0530 Subject: [PATCH 32/39] test: Gross Profit report output for Cr notes 2 New test cases added. 1. Standalone Cr notes will be reported as normal Invoices 2. Cr notes against an Invoice will not overallocate qty if there are multiple instances of same item --- .../report/gross_profit/test_gross_profit.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py index 21681bef5b..82fe1a0ba1 100644 --- a/erpnext/accounts/report/gross_profit/test_gross_profit.py +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -381,3 +381,82 @@ class TestGrossProfit(FrappeTestCase): } gp_entry = [x for x in data if x.parent_invoice == sinv.name] self.assertDictContainsSubset(expected_entry, gp_entry[0]) + + def test_crnote_against_invoice_with_multiple_instances_of_same_item(self): + """ + Item Qty for Sales Invoices with multiple instances of same item go in the -ve. Ideally, the credit noteshould cancel out the invoice items. + """ + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return + + # Invoice with an item added twice + sinv = self.create_sales_invoice(qty=1, rate=100, posting_date=nowdate(), do_not_submit=True) + sinv.append("items", frappe.copy_doc(sinv.items[0], ignore_no_copy=False)) + sinv = sinv.save().submit() + + # Create Credit Note for Invoice + cr_note = make_sales_return(sinv.name) + cr_note = cr_note.save().submit() + + filters = frappe._dict( + company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice" + ) + + columns, data = execute(filters=filters) + expected_entry = { + "parent_invoice": sinv.name, + "currency": "INR", + "sales_invoice": self.item, + "customer": self.customer, + "posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()), + "item_code": self.item, + "item_name": self.item, + "warehouse": "Stores - _GP", + "qty": 0.0, + "avg._selling_rate": 0.0, + "valuation_rate": 0.0, + "selling_amount": -100.0, + "buying_amount": 0.0, + "gross_profit": -100.0, + "gross_profit_%": 100.0, + } + gp_entry = [x for x in data if x.parent_invoice == sinv.name] + # Both items of Invoice should have '0' qty + self.assertEqual(len(gp_entry), 2) + self.assertDictContainsSubset(expected_entry, gp_entry[0]) + self.assertDictContainsSubset(expected_entry, gp_entry[1]) + + def test_standalone_cr_notes(self): + """ + Standalone cr notes will be reported as usual + """ + # Make Cr Note + sinv = self.create_sales_invoice( + qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True + ) + sinv.is_return = 1 + sinv = sinv.save().submit() + + filters = frappe._dict( + company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice" + ) + + columns, data = execute(filters=filters) + expected_entry = { + "parent_invoice": sinv.name, + "currency": "INR", + "sales_invoice": self.item, + "customer": self.customer, + "posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()), + "item_code": self.item, + "item_name": self.item, + "warehouse": "Stores - _GP", + "qty": -1.0, + "avg._selling_rate": 100.0, + "valuation_rate": 0.0, + "selling_amount": -100.0, + "buying_amount": 0.0, + "gross_profit": -100.0, + "gross_profit_%": 100.0, + } + gp_entry = [x for x in data if x.parent_invoice == sinv.name] + self.assertDictContainsSubset(expected_entry, gp_entry[0]) From be723bb9d483c615fa0b14b0115338e39e32a698 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 17 Mar 2023 15:39:33 +0530 Subject: [PATCH 33/39] chore: Update user manual link (#34478) --- erpnext/patches.txt | 1 + erpnext/patches/v13_0/update_docs_link.py | 14 ++++++++++++++ erpnext/setup/install.py | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v13_0/update_docs_link.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c1d4b82baf..d42b012466 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -322,6 +322,7 @@ erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request erpnext.patches.v14_0.update_entry_type_for_journal_entry erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers erpnext.patches.v14_0.set_pick_list_status +erpnext.patches.v13_0.update_docs_link erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch # below migration patches should always run last diff --git a/erpnext/patches/v13_0/update_docs_link.py b/erpnext/patches/v13_0/update_docs_link.py new file mode 100644 index 0000000000..4bc5c053d2 --- /dev/null +++ b/erpnext/patches/v13_0/update_docs_link.py @@ -0,0 +1,14 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# License: MIT. See LICENSE + + +import frappe + + +def execute(): + navbar_settings = frappe.get_single("Navbar Settings") + for item in navbar_settings.help_dropdown: + if item.is_standard and item.route == "https://erpnext.com/docs/user/manual": + item.route = "https://docs.erpnext.com/docs/v14/user/manual/en/introduction" + + navbar_settings.save() diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 1f7dddfb95..088958d1b2 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -155,7 +155,7 @@ def add_standard_navbar_items(): { "item_label": "Documentation", "item_type": "Route", - "route": "https://erpnext.com/docs/user/manual", + "route": "https://docs.erpnext.com/docs/v14/user/manual/en/introduction", "is_standard": 1, }, { From 7b630217bde9e7a54f4a26f44e1b549218aaf275 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 17 Mar 2023 15:51:33 +0530 Subject: [PATCH 34/39] fix: Multiple accounting dimension filtering in AR/AP reports (#34464) Co-authored-by: Anand Baburajan --- .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 94a1510f09..11de9a098d 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -859,7 +859,7 @@ class ReceivablePayableReport(object): ) else: self.qb_selection_filter.append( - self.ple[dimension.fieldname] == self.filters[dimension.fieldname] + self.ple[dimension.fieldname].isin(self.filters[dimension.fieldname]) ) def is_invoice(self, ple): From d8ece86463084a750c1395297a9d1b48d70ee774 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 17 Mar 2023 15:55:11 +0530 Subject: [PATCH 35/39] fix: Update account number from parent company (#34474) --- erpnext/accounts/doctype/account/account.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index ec0ba081c8..0404d1c677 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -394,7 +394,13 @@ def update_account_number(name, account_name, account_number=None, from_descenda if ancestors and not allow_independent_account_creation: for ancestor in ancestors: - if frappe.db.get_value("Account", {"account_name": old_acc_name, "company": ancestor}, "name"): + old_name = frappe.db.get_value( + "Account", + {"account_number": old_acc_number, "account_name": old_acc_name, "company": ancestor}, + "name", + ) + + if old_name: # same account in parent company exists allow_child_account_creation = _("Allow Account Creation Against Child Company") From ca10e2bb9ff61bc1c9ffcd1e7a2a14d6ec4f4d51 Mon Sep 17 00:00:00 2001 From: Danny <66078599+mmdanny89@users.noreply.github.com> Date: Fri, 17 Mar 2023 06:43:32 -0400 Subject: [PATCH 36/39] fix: bad strings format for command get-untraslated (#34361) * fix: bad string foramt * fix: bad string format * fix: pre-commit format --------- Co-authored-by: Anand Baburajan --- erpnext/accounts/utils.py | 4 +++- erpnext/stock/doctype/pick_list/pick_list.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 2608c03ffe..005a2f176c 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -455,7 +455,9 @@ def reconcile_against_document(args): # nosemgrep try: doc.validate_total_debit_and_credit() except Exception as validation_exception: - raise frappe.ValidationError(_(f"Validation Error for {doc.name}")) from validation_exception + raise frappe.ValidationError( + _("Validation Error for {0}").format(doc.name) + ) from validation_exception doc.save(ignore_permissions=True) # re-submit advance entry diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index bf3b5ddc54..46d6e9e757 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -172,8 +172,8 @@ class PickList(Document): if (row.picked_qty / row.stock_qty) * 100 > over_delivery_receipt_allowance: frappe.throw( _( - f"You are picking more than required quantity for the item {row.item_code}. Check if there is any other pick list created for the sales order {row.sales_order}." - ) + "You are picking more than required quantity for the item {0}. Check if there is any other pick list created for the sales order {1}." + ).format(row.item_code, row.sales_order) ) @frappe.whitelist() From d791dc11a3eea4445e2e772125260923c43c3f9e Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sun, 19 Mar 2023 13:35:12 +0100 Subject: [PATCH 37/39] fix: patch depends on Currency Exchange Settings (#34494) --- erpnext/patches/v14_0/update_opportunity_currency_fields.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/patches/v14_0/update_opportunity_currency_fields.py b/erpnext/patches/v14_0/update_opportunity_currency_fields.py index b803e9fa2d..af736919d8 100644 --- a/erpnext/patches/v14_0/update_opportunity_currency_fields.py +++ b/erpnext/patches/v14_0/update_opportunity_currency_fields.py @@ -7,6 +7,9 @@ from erpnext.setup.utils import get_exchange_rate def execute(): + frappe.reload_doc( + "accounts", "doctype", "currency_exchange_settings" + ) # get_exchange_rate depends on Currency Exchange Settings frappe.reload_doctype("Opportunity") opportunities = frappe.db.get_list( "Opportunity", From fc86a8568f41b95f9463d6e29c8628b2b158e67a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 19 Mar 2023 18:09:18 +0530 Subject: [PATCH 38/39] fix: Supplier RFQ email link (#34338) --- .../doctype/request_for_quotation/request_for_quotation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 7927beb823..4590f8c3d9 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -113,7 +113,10 @@ class RequestforQuotation(BuyingController): def get_link(self): # RFQ link for supplier portal - return get_url("/app/request-for-quotation/" + self.name) + route = frappe.db.get_value( + "Portal Menu Item", {"reference_doctype": "Request for Quotation"}, ["route"] + ) + return get_url("/app/{0}/".format(route) + self.name) def update_supplier_part_no(self, supplier): self.vendor = supplier From 109a9f1390306740fa91f05ca458f5f1a968d6b8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Mar 2023 14:46:26 +0530 Subject: [PATCH 39/39] perf: index against_sales_invoice field on DN items (#34509) This is used on Sales invoice dashboard and takes a lot of time to load as db size increases. Results: Before: ~10-20 seconds to load dashboard After: few milliseconds because of index [skip ci] --- .../stock/doctype/delivery_note_item/delivery_note_item.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 916ab2a05b..1763269193 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -636,7 +636,8 @@ "no_copy": 1, "options": "Sales Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "so_detail", @@ -837,7 +838,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-11-09 12:17:50.850142", + "modified": "2023-03-20 14:24:10.406746", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item",