diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 2a62e2a821d4..5d2a8a60bc16 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -41,3 +41,4 @@ a308792ee7fda18a681e9181f4fd00b36385bc23 # noisy typing refactoring of get_item_details 7b7211ac79c248a79ba8a999ff34e734d874c0ae +d827ed21adc7b36047e247cbb0dc6388d048a7f9 diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 03aabb7f3b14..6e499f647c19 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -533,7 +533,11 @@ def set_status(self, update=False, status=None, update_modified=True): def set_pos_fields(self, for_validate=False): """Set retail related fields from POS Profiles""" - from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details_ + from erpnext.stock.get_item_details import ( + ItemDetailsCtx, + get_pos_profile, + get_pos_profile_item_details_, + ) if not self.pos_profile: pos_profile = get_pos_profile(self.company) or {} @@ -603,7 +607,7 @@ def set_pos_fields(self, for_validate=False): for item in self.get("items"): if item.get("item_code"): profile_details = get_pos_profile_item_details_( - frappe._dict(item.as_dict()), profile.get("company"), profile + ItemDetailsCtx(item.as_dict()), profile.get("company"), profile ) for fname, val in profile_details.items(): if (not for_validate) or (for_validate and not item.get(fname)): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index a69ffb218083..de5ed6525f3e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -766,7 +766,11 @@ def set_pos_fields(self, for_validate=False): "Company", self.company, "default_cash_account" ) - from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details_ + from erpnext.stock.get_item_details import ( + ItemDetailsCtx, + get_pos_profile, + get_pos_profile_item_details_, + ) if not self.pos_profile and not self.flags.ignore_pos_profile: pos_profile = get_pos_profile(self.company) or {} @@ -835,7 +839,7 @@ def set_pos_fields(self, for_validate=False): for item in self.get("items"): if item.get("item_code"): profile_details = get_pos_profile_item_details_( - frappe._dict(item.as_dict()), pos, pos, update_data=True + ItemDetailsCtx(item.as_dict()), pos, pos, update_data=True ) for fname, val in profile_details.items(): if (not for_validate) or (for_validate and not item.get(fname)): diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index d7d289b618e2..2cdd94b15c8b 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -26,6 +26,7 @@ from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.stock.get_item_details import ( + ItemDetailsCtx, get_default_cost_center, get_default_expense_account, get_item_warehouse_, @@ -748,7 +749,7 @@ def get_target_item_details(item_code=None, company=None): item_group_defaults = get_item_group_defaults(item.name, company) brand_defaults = get_brand_defaults(item.name, company) out.cost_center = get_default_cost_center( - frappe._dict({"item_code": item.name, "company": company}), + ItemDetailsCtx({"item_code": item.name, "company": company}), item_defaults, item_group_defaults, brand_defaults, @@ -785,45 +786,42 @@ def get_target_asset_details(asset=None, company=None): @frappe.whitelist() -def get_consumed_stock_item_details(args_): - if isinstance(args_, str): - args_ = json.loads(args_) - - args = frappe._dict(args_) +@erpnext.normalize_ctx_input(ItemDetailsCtx) +def get_consumed_stock_item_details(ctx: ItemDetailsCtx): out = frappe._dict() item = frappe._dict() - if args.item_code: - item = frappe.get_cached_doc("Item", args.item_code) + if ctx.item_code: + item = frappe.get_cached_doc("Item", ctx.item_code) out.item_name = item.item_name out.batch_no = None out.serial_no = "" - out.stock_qty = flt(args.stock_qty) or 1 + out.stock_qty = flt(ctx.stock_qty) or 1 out.stock_uom = item.stock_uom - out.warehouse = get_item_warehouse_(args, item, overwrite_warehouse=True) if item else None + out.warehouse = get_item_warehouse_(ctx, item, overwrite_warehouse=True) if item else None # Cost Center - item_defaults = get_item_defaults(item.name, args.company) - item_group_defaults = get_item_group_defaults(item.name, args.company) - brand_defaults = get_brand_defaults(item.name, args.company) - out.cost_center = get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults) + item_defaults = get_item_defaults(item.name, ctx.company) + item_group_defaults = get_item_group_defaults(item.name, ctx.company) + brand_defaults = get_brand_defaults(item.name, ctx.company) + out.cost_center = get_default_cost_center(ctx, item_defaults, item_group_defaults, brand_defaults) - if args.item_code and out.warehouse: + if ctx.item_code and out.warehouse: incoming_rate_args = frappe._dict( { - "item_code": args.item_code, + "item_code": ctx.item_code, "warehouse": out.warehouse, - "posting_date": args.posting_date, - "posting_time": args.posting_time, + "posting_date": ctx.posting_date, + "posting_time": ctx.posting_time, "qty": -1 * flt(out.stock_qty), - "voucher_type": args.doctype, - "voucher_no": args.name, - "company": args.company, - "serial_no": args.serial_no, - "batch_no": args.batch_no, + "voucher_type": ctx.doctype, + "voucher_no": ctx.name, + "company": ctx.company, + "serial_no": ctx.serial_no, + "batch_no": ctx.batch_no, } ) out.update(get_warehouse_details(incoming_rate_args)) @@ -851,31 +849,28 @@ def get_warehouse_details(args): @frappe.whitelist() -def get_consumed_asset_details(args): - if isinstance(args, str): - args = json.loads(args) - - args = frappe._dict(args) +@erpnext.normalize_ctx_input(ItemDetailsCtx) +def get_consumed_asset_details(ctx): out = frappe._dict() asset_details = frappe._dict() - if args.asset: + if ctx.asset: asset_details = frappe.db.get_value( - "Asset", args.asset, ["asset_name", "item_code", "item_name"], as_dict=1 + "Asset", ctx.asset, ["asset_name", "item_code", "item_name"], as_dict=1 ) if not asset_details: - frappe.throw(_("Asset {0} does not exist").format(args.asset)) + frappe.throw(_("Asset {0} does not exist").format(ctx.asset)) out.item_code = asset_details.item_code out.asset_name = asset_details.asset_name out.item_name = asset_details.item_name - if args.asset: + if ctx.asset: out.current_asset_value = flt( - get_asset_value_after_depreciation(args.asset, finance_book=args.finance_book) + get_asset_value_after_depreciation(ctx.asset, finance_book=ctx.finance_book) ) out.asset_value = get_value_after_depreciation_on_disposal_date( - args.asset, args.posting_date, finance_book=args.finance_book + ctx.asset, ctx.posting_date, finance_book=ctx.finance_book ) else: out.current_asset_value = 0 @@ -884,7 +879,7 @@ def get_consumed_asset_details(args): # Account if asset_details.item_code: out.fixed_asset_account = get_asset_category_account( - "fixed_asset_account", item=asset_details.item_code, company=args.company + "fixed_asset_account", item=asset_details.item_code, company=ctx.company ) else: out.fixed_asset_account = None @@ -892,37 +887,32 @@ def get_consumed_asset_details(args): # Cost Center if asset_details.item_code: item = frappe.get_cached_doc("Item", asset_details.item_code) - item_defaults = get_item_defaults(item.name, args.company) - item_group_defaults = get_item_group_defaults(item.name, args.company) - brand_defaults = get_brand_defaults(item.name, args.company) - out.cost_center = get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults) + item_defaults = get_item_defaults(item.name, ctx.company) + item_group_defaults = get_item_group_defaults(item.name, ctx.company) + brand_defaults = get_brand_defaults(item.name, ctx.company) + out.cost_center = get_default_cost_center(ctx, item_defaults, item_group_defaults, brand_defaults) return out @frappe.whitelist() -def get_service_item_details(args): - if isinstance(args, str): - args = json.loads(args) - - args = frappe._dict(args) +@erpnext.normalize_ctx_input(ItemDetailsCtx) +def get_service_item_details(ctx): out = frappe._dict() item = frappe._dict() - if args.item_code: - item = frappe.get_cached_doc("Item", args.item_code) + if ctx.item_code: + item = frappe.get_cached_doc("Item", ctx.item_code) out.item_name = item.item_name - out.qty = flt(args.qty) or 1 + out.qty = flt(ctx.qty) or 1 out.uom = item.purchase_uom or item.stock_uom - item_defaults = get_item_defaults(item.name, args.company) - item_group_defaults = get_item_group_defaults(item.name, args.company) - brand_defaults = get_brand_defaults(item.name, args.company) + item_defaults = get_item_defaults(item.name, ctx.company) + item_group_defaults = get_item_group_defaults(item.name, ctx.company) + brand_defaults = get_brand_defaults(item.name, ctx.company) - out.expense_account = get_default_expense_account( - args, item_defaults, item_group_defaults, brand_defaults - ) - out.cost_center = get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults) + out.expense_account = get_default_expense_account(ctx, item_defaults, item_group_defaults, brand_defaults) + out.cost_center = get_default_cost_center(ctx, item_defaults, item_group_defaults, brand_defaults) return out diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e4d6746702b8..7db099f5da11 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -62,6 +62,7 @@ from erpnext.stock.doctype.item.item import get_uom_conv_factor from erpnext.stock.doctype.packed_item.packed_item import make_packing_list from erpnext.stock.get_item_details import ( + ItemDetailsCtx, _get_item_tax_template, get_conversion_factor, get_item_details, @@ -752,24 +753,28 @@ def set_missing_item_details(self, for_validate=False): for item in self.get("items"): if item.get("item_code"): - args = parent_dict.copy() - args.update(item.as_dict()) - - args["doctype"] = self.doctype - args["name"] = self.name - args["child_doctype"] = item.doctype - args["child_docname"] = item.name - args["ignore_pricing_rule"] = ( - self.ignore_pricing_rule if hasattr(self, "ignore_pricing_rule") else 0 + ctx: ItemDetailsCtx = ItemDetailsCtx(parent_dict.copy()) + ctx.update(item.as_dict()) + + ctx.update( + { + "doctype": self.doctype, + "name": self.name, + "child_doctype": item.doctype, + "child_docname": item.name, + "ignore_pricing_rule": ( + self.ignore_pricing_rule if hasattr(self, "ignore_pricing_rule") else 0 + ), + } ) - if not args.get("transaction_date"): - args["transaction_date"] = args.get("posting_date") + if not ctx.transaction_date: + ctx.transaction_date = ctx.posting_date if self.get("is_subcontracted"): - args["is_subcontracted"] = self.is_subcontracted + ctx.is_subcontracted = self.is_subcontracted - ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False) + ret = get_item_details(ctx, self, for_validate=for_validate, overwrite_warehouse=False) for fieldname, value in ret.items(): if item.meta.get_field(fieldname) and value is not None: if item.get(fieldname) is None or fieldname in force_item_fields: @@ -3161,14 +3166,16 @@ def get_supplier_block_status(party_name): def set_child_tax_template_and_map(item, child_item, parent_doc): - args = { - "item_code": item.item_code, - "posting_date": parent_doc.transaction_date, - "tax_category": parent_doc.get("tax_category"), - "company": parent_doc.get("company"), - } + ctx = ItemDetailsCtx( + { + "item_code": item.item_code, + "posting_date": parent_doc.transaction_date, + "tax_category": parent_doc.get("tax_category"), + "company": parent_doc.get("company"), + } + ) - child_item.item_tax_template = _get_item_tax_template(args, item.taxes) + child_item.item_tax_template = _get_item_tax_template(ctx, item.taxes) if child_item.get("item_tax_template"): child_item.item_tax_rate = get_item_tax_map( parent_doc.get("company"), child_item.item_tax_template, as_json=True diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index ae240cf7e44d..e0de8e4c6d1d 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -14,7 +14,7 @@ from pypika import Order import erpnext -from erpnext.stock.get_item_details import _get_item_tax_template +from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template # searches for active employees @@ -813,14 +813,16 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters): valid_from = filters.get("valid_from") valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from - args = { - "item_code": filters.get("item_code"), - "posting_date": valid_from, - "tax_category": filters.get("tax_category"), - "company": company, - } + ctx = ItemDetailsCtx( + { + "item_code": filters.get("item_code"), + "posting_date": valid_from, + "tax_category": filters.get("tax_category"), + "company": company, + } + ) - taxes = _get_item_tax_template(args, taxes, for_validate=True) + taxes = _get_item_tax_template(ctx, taxes, for_validate=True) return [(d,) for d in set(taxes)] diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index f32126151617..aa417d0ef157 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -18,7 +18,7 @@ validate_inclusive_tax, validate_taxes_and_charges, ) -from erpnext.stock.get_item_details import _get_item_tax_template +from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template from erpnext.utilities.regional import temporary_flag logger = frappe.logger(__name__) @@ -103,15 +103,17 @@ def validate_item_tax_template(self): for item in self.doc.items: if item.item_code and item.get("item_tax_template"): item_doc = frappe.get_cached_doc("Item", item.item_code) - args = { - "net_rate": item.net_rate or item.rate, - "base_net_rate": item.base_net_rate or item.base_rate, - "tax_category": self.doc.get("tax_category"), - "posting_date": self.doc.get("posting_date"), - "bill_date": self.doc.get("bill_date"), - "transaction_date": self.doc.get("transaction_date"), - "company": self.doc.get("company"), - } + ctx = ItemDetailsCtx( + { + "net_rate": item.net_rate or item.rate, + "base_net_rate": item.base_net_rate or item.base_rate, + "tax_category": self.doc.get("tax_category"), + "posting_date": self.doc.get("posting_date"), + "bill_date": self.doc.get("bill_date"), + "transaction_date": self.doc.get("transaction_date"), + "company": self.doc.get("company"), + } + ) item_group = item_doc.item_group item_group_taxes = [] @@ -127,7 +129,7 @@ def validate_item_tax_template(self): # No validation if no taxes in item or item group continue - taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True) + taxes = _get_item_tax_template(ctx, item_taxes + item_group_taxes, for_validate=True) if taxes: if item.item_tax_template not in taxes: diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 3f88a705da92..0103428de862 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -16,7 +16,7 @@ import erpnext from erpnext.setup.utils import get_exchange_rate from erpnext.stock.doctype.item.item import get_item_details -from erpnext.stock.get_item_details import get_conversion_factor, get_price_list_rate +from erpnext.stock.get_item_details import ItemDetailsCtx, get_conversion_factor, get_price_list_rate form_grid_templates = {"items": "templates/form_grid/item_grid.html"} @@ -1052,7 +1052,7 @@ def get_bom_item_rate(args, bom_doc): elif bom_doc.rm_cost_as_per == "Price List": if not bom_doc.buying_price_list: frappe.throw(_("Please select Price List")) - bom_args = frappe._dict( + ctx = ItemDetailsCtx( { "doctype": "BOM", "price_list": bom_doc.buying_price_list, @@ -1070,7 +1070,7 @@ def get_bom_item_rate(args, bom_doc): } ) item_doc = frappe.get_cached_doc("Item", args.get("item_code")) - price_list_data = get_price_list_rate(bom_args, item_doc) + price_list_data = get_price_list_rate(ctx, item_doc) rate = price_list_data.price_list_rate return flt(rate) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index da6c02a2ec0c..467ac0115921 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -54,7 +54,7 @@ def test_make_sales_order_terms_copied(self): def test_gross_profit(self): from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry - from erpnext.stock.get_item_details import insert_item_price + from erpnext.stock.get_item_details import ItemDetailsCtx, insert_item_price item_doc = make_item("_Test Item for Gross Profit", {"is_stock_item": 1}) item_code = item_doc.name @@ -63,7 +63,7 @@ def test_gross_profit(self): selling_price_list = frappe.get_all("Price List", filters={"selling": 1}, limit=1)[0].name frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1) insert_item_price( - frappe._dict( + ItemDetailsCtx( { "item_code": item_code, "price_list": selling_price_list, diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 450909dd5eac..5b8512df3fe9 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -35,7 +35,12 @@ get_sre_reserved_qty_details_for_voucher, has_reserved_stock, ) -from erpnext.stock.get_item_details import get_bin_details, get_default_bom, get_price_list_rate +from erpnext.stock.get_item_details import ( + ItemDetailsCtx, + get_bin_details, + get_default_bom, + get_price_list_rate, +) from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty form_grid_templates = {"items": "templates/form_grid/item_grid.html"} @@ -849,8 +854,8 @@ def update_item(source, target, source_parent): target.item_code, target.warehouse, source_parent.company, True ).get("actual_qty", 0) - args = target.as_dict().copy() - args.update( + ctx = ItemDetailsCtx(target.as_dict().copy()) + ctx.update( { "company": source_parent.get("company"), "price_list": frappe.db.get_single_value("Buying Settings", "buying_price_list"), @@ -860,7 +865,7 @@ def update_item(source, target, source_parent): ) target.rate = flt( - get_price_list_rate(args, item_doc=frappe.get_cached_doc("Item", target.item_code)).get( + get_price_list_rate(ctx, item_doc=frappe.get_cached_doc("Item", target.item_code)).get( "price_list_rate" ) ) diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index 4c761943f122..36e484871fa6 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -20,7 +20,7 @@ get_batch_from_bundle, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry -from erpnext.stock.get_item_details import get_item_details +from erpnext.stock.get_item_details import ItemDetailsCtx, get_item_details from erpnext.stock.serial_batch_bundle import SerialBatchCreation @@ -436,7 +436,7 @@ def test_batch_wise_item_price(self): company = "_Test Company with perpetual inventory" currency = frappe.get_cached_value("Company", company, "default_currency") - args = frappe._dict( + ctx = ItemDetailsCtx( { "item_code": "_Test Batch Price Item", "company": company, @@ -452,18 +452,18 @@ def test_batch_wise_item_price(self): ) # test price for batch1 - args.update({"batch_no": batch1}) - details = get_item_details(args) + ctx.update({"batch_no": batch1}) + details = get_item_details(ctx) self.assertEqual(details.get("price_list_rate"), 200) # test price for batch2 - args.update({"batch_no": batch2}) - details = get_item_details(args) + ctx.update({"batch_no": batch2}) + details = get_item_details(ctx) self.assertEqual(details.get("price_list_rate"), 300) # test price for batch3 - args.update({"batch_no": batch3}) - details = get_item_details(args) + ctx.update({"batch_no": batch3}) + details = get_item_details(ctx) self.assertEqual(details.get("price_list_rate"), 400) def test_basic_batch_wise_valuation(self, batch_qty=100): diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 45aa55688e7f..e17a8c8eada5 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -26,7 +26,7 @@ validate_is_stock_item, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry -from erpnext.stock.get_item_details import get_item_details +from erpnext.stock.get_item_details import ItemDetailsCtx, get_item_details IGNORE_TEST_RECORD_DEPENDENCIES = ["BOM"] EXTRA_TEST_RECORD_DEPENDENCIES = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"] @@ -145,21 +145,23 @@ def test_get_item_details(self): currency = frappe.get_cached_value("Company", company, "default_currency") details = get_item_details( - { - "item_code": "_Test Item", - "company": company, - "price_list": "_Test Price List", - "currency": currency, - "doctype": "Sales Order", - "conversion_rate": 1, - "price_list_currency": currency, - "plc_conversion_rate": 1, - "order_type": "Sales", - "customer": "_Test Customer", - "conversion_factor": 1, - "price_list_uom_dependant": 1, - "ignore_pricing_rule": 1, - } + ItemDetailsCtx( + { + "item_code": "_Test Item", + "company": company, + "price_list": "_Test Price List", + "currency": currency, + "doctype": "Sales Order", + "conversion_rate": 1, + "price_list_currency": currency, + "plc_conversion_rate": 1, + "order_type": "Sales", + "customer": "_Test Customer", + "conversion_factor": 1, + "price_list_uom_dependant": 1, + "ignore_pricing_rule": 1, + } + ) ) for key, value in to_check.items(): @@ -172,23 +174,27 @@ def test_get_asset_item_details(self): create_fixed_asset_item() details = get_item_details( - { - "item_code": "Macbook Pro", - "company": "_Test Company", - "currency": "INR", - "doctype": "Purchase Receipt", - } + ItemDetailsCtx( + { + "item_code": "Macbook Pro", + "company": "_Test Company", + "currency": "INR", + "doctype": "Purchase Receipt", + } + ) ) self.assertEqual(details.get("expense_account"), "_Test Fixed Asset - _TC") frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", "1") details = get_item_details( - { - "item_code": "Macbook Pro", - "company": "_Test Company", - "currency": "INR", - "doctype": "Purchase Receipt", - } + ItemDetailsCtx( + { + "item_code": "Macbook Pro", + "company": "_Test Company", + "currency": "INR", + "doctype": "Purchase Receipt", + } + ) ) self.assertEqual(details.get("expense_account"), "CWIP Account - _TC") @@ -271,22 +277,24 @@ def test_item_tax_template(self): for data in expected_item_tax_template: details = get_item_details( - { - "item_code": data["item_code"], - "tax_category": data["tax_category"], - "company": "_Test Company", - "price_list": "_Test Price List", - "currency": "_Test Currency", - "doctype": "Sales Order", - "conversion_rate": 1, - "price_list_currency": "_Test Currency", - "plc_conversion_rate": 1, - "order_type": "Sales", - "customer": "_Test Customer", - "conversion_factor": 1, - "price_list_uom_dependant": 1, - "ignore_pricing_rule": 1, - } + ItemDetailsCtx( + { + "item_code": data["item_code"], + "tax_category": data["tax_category"], + "company": "_Test Company", + "price_list": "_Test Price List", + "currency": "_Test Currency", + "doctype": "Sales Order", + "conversion_rate": 1, + "price_list_currency": "_Test Currency", + "plc_conversion_rate": 1, + "order_type": "Sales", + "customer": "_Test Customer", + "conversion_factor": 1, + "price_list_uom_dependant": 1, + "ignore_pricing_rule": 1, + } + ) ) self.assertEqual(details.item_tax_template, data["item_tax_template"]) @@ -320,17 +328,19 @@ def test_item_defaults(self): "cost_center": "_Test Cost Center 2 - _TC", # from item group } sales_item_details = get_item_details( - { - "item_code": "Test Item With Defaults", - "company": "_Test Company", - "price_list": "_Test Price List", - "currency": "_Test Currency", - "doctype": "Sales Invoice", - "conversion_rate": 1, - "price_list_currency": "_Test Currency", - "plc_conversion_rate": 1, - "customer": "_Test Customer", - } + ItemDetailsCtx( + { + "item_code": "Test Item With Defaults", + "company": "_Test Company", + "price_list": "_Test Price List", + "currency": "_Test Currency", + "doctype": "Sales Invoice", + "conversion_rate": 1, + "price_list_currency": "_Test Currency", + "plc_conversion_rate": 1, + "customer": "_Test Customer", + } + ) ) for key, value in sales_item_check.items(): self.assertEqual(value, sales_item_details.get(key)) @@ -343,17 +353,19 @@ def test_item_defaults(self): "cost_center": "_Test Write Off Cost Center - _TC", # from item } purchase_item_details = get_item_details( - { - "item_code": "Test Item With Defaults", - "company": "_Test Company", - "price_list": "_Test Price List", - "currency": "_Test Currency", - "doctype": "Purchase Invoice", - "conversion_rate": 1, - "price_list_currency": "_Test Currency", - "plc_conversion_rate": 1, - "supplier": "_Test Supplier", - } + ItemDetailsCtx( + { + "item_code": "Test Item With Defaults", + "company": "_Test Company", + "price_list": "_Test Price List", + "currency": "_Test Currency", + "doctype": "Purchase Invoice", + "conversion_rate": 1, + "price_list_currency": "_Test Currency", + "plc_conversion_rate": 1, + "supplier": "_Test Supplier", + } + ) ) for key, value in purchase_item_check.items(): self.assertEqual(value, purchase_item_details.get(key)) diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py index eb2efaa9396d..ffe335bead98 100644 --- a/erpnext/stock/doctype/item_price/test_item_price.py +++ b/erpnext/stock/doctype/item_price/test_item_price.py @@ -7,7 +7,7 @@ from frappe.tests.utils import make_test_records_for_doctype from erpnext.stock.doctype.item_price.item_price import ItemPriceDuplicateItem -from erpnext.stock.get_item_details import get_price_list_rate_for +from erpnext.stock.get_item_details import ItemDetailsCtx, get_price_list_rate_for class UnitTestItemPrice(UnitTestCase): @@ -80,85 +80,97 @@ def test_price_in_a_qty(self): # Check correct price at this quantity doc = frappe.copy_doc(self.globalTestRecords["Item Price"][2]) - args = { - "price_list": doc.price_list, - "customer": doc.customer, - "uom": "_Test UOM", - "transaction_date": "2017-04-18", - "qty": 10, - } + ctx = ItemDetailsCtx( + { + "price_list": doc.price_list, + "customer": doc.customer, + "uom": "_Test UOM", + "transaction_date": "2017-04-18", + "qty": 10, + } + ) - price = get_price_list_rate_for(args, doc.item_code) + price = get_price_list_rate_for(ctx, doc.item_code) self.assertEqual(price, 20.0) def test_price_with_no_qty(self): # Check correct price when no quantity doc = frappe.copy_doc(self.globalTestRecords["Item Price"][2]) - args = { - "price_list": doc.price_list, - "customer": doc.customer, - "uom": "_Test UOM", - "transaction_date": "2017-04-18", - } - - price = get_price_list_rate_for(args, doc.item_code) + ctx = ItemDetailsCtx( + { + "price_list": doc.price_list, + "customer": doc.customer, + "uom": "_Test UOM", + "transaction_date": "2017-04-18", + } + ) + + price = get_price_list_rate_for(ctx, doc.item_code) self.assertEqual(price, None) def test_prices_at_date(self): # Check correct price at first date doc = frappe.copy_doc(self.globalTestRecords["Item Price"][2]) - args = { - "price_list": doc.price_list, - "customer": "_Test Customer", - "uom": "_Test UOM", - "transaction_date": "2017-04-18", - "qty": 7, - } + ctx = ItemDetailsCtx( + { + "price_list": doc.price_list, + "customer": "_Test Customer", + "uom": "_Test UOM", + "transaction_date": "2017-04-18", + "qty": 7, + } + ) - price = get_price_list_rate_for(args, doc.item_code) + price = get_price_list_rate_for(ctx, doc.item_code) self.assertEqual(price, 20) def test_prices_at_invalid_date(self): # Check correct price at invalid date doc = frappe.copy_doc(self.globalTestRecords["Item Price"][3]) - args = { - "price_list": doc.price_list, - "qty": 7, - "uom": "_Test UOM", - "transaction_date": "01-15-2019", - } + ctx = ItemDetailsCtx( + { + "price_list": doc.price_list, + "qty": 7, + "uom": "_Test UOM", + "transaction_date": "01-15-2019", + } + ) - price = get_price_list_rate_for(args, doc.item_code) + price = get_price_list_rate_for(ctx, doc.item_code) self.assertEqual(price, None) def test_prices_outside_of_date(self): # Check correct price when outside of the date doc = frappe.copy_doc(self.globalTestRecords["Item Price"][4]) - args = { - "price_list": doc.price_list, - "customer": "_Test Customer", - "uom": "_Test UOM", - "transaction_date": "2017-04-25", - "qty": 7, - } + ctx = ItemDetailsCtx( + { + "price_list": doc.price_list, + "customer": "_Test Customer", + "uom": "_Test UOM", + "transaction_date": "2017-04-25", + "qty": 7, + } + ) - price = get_price_list_rate_for(args, doc.item_code) + price = get_price_list_rate_for(ctx, doc.item_code) self.assertEqual(price, None) def test_lowest_price_when_no_date_provided(self): # Check lowest price when no date provided doc = frappe.copy_doc(self.globalTestRecords["Item Price"][1]) - args = { - "price_list": doc.price_list, - "uom": "_Test UOM", - "qty": 7, - } + ctx = ItemDetailsCtx( + { + "price_list": doc.price_list, + "uom": "_Test UOM", + "qty": 7, + } + ) - price = get_price_list_rate_for(args, doc.item_code) + price = get_price_list_rate_for(ctx, doc.item_code) self.assertEqual(price, 10) def test_invalid_item(self): @@ -182,14 +194,16 @@ def test_empty_duplicate_validation(self): doc.price_list_rate = 21 doc.insert() - args = { - "price_list": doc.price_list, - "uom": "_Test UOM", - "transaction_date": "2017-04-18", - "qty": 7, - } + ctx = ItemDetailsCtx( + { + "price_list": doc.price_list, + "uom": "_Test UOM", + "transaction_date": "2017-04-18", + "qty": 7, + } + ) - price = get_price_list_rate_for(args, doc.item_code) + price = get_price_list_rate_for(ctx, doc.item_code) frappe.db.rollback() self.assertEqual(price, 21) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 081d9c59ddfc..ee5d330a2218 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -10,7 +10,7 @@ from frappe.model.document import Document from frappe.utils import flt -from erpnext.stock.get_item_details import get_item_details, get_price_list_rate +from erpnext.stock.get_item_details import ItemDetailsCtx, get_item_details, get_price_list_rate class PackedItem(Document): @@ -243,8 +243,8 @@ def update_packed_item_price_data(pi_row, item_data, doc): return item_doc = frappe.get_cached_doc("Item", pi_row.item_code) - row_data = pi_row.as_dict().copy() - row_data.update( + ctx = ItemDetailsCtx(pi_row.as_dict().copy()) + ctx.update( { "company": doc.get("company"), "price_list": doc.get("selling_price_list"), @@ -252,10 +252,10 @@ def update_packed_item_price_data(pi_row, item_data, doc): "conversion_rate": doc.get("conversion_rate"), } ) - if not row_data.get("transaction_date"): - row_data.update({"transaction_date": doc.get("transaction_date")}) + if not ctx.transaction_date: + ctx.update({"transaction_date": doc.get("transaction_date")}) - rate = get_price_list_rate(row_data, item_doc).get("price_list_rate") + rate = get_price_list_rate(ctx, item_doc).get("price_list_rate") pi_row.rate = rate or item_data.get("valuation_rate") or 0.0 @@ -323,7 +323,7 @@ def on_doctype_update(): @frappe.whitelist() def get_items_from_product_bundle(row): - row, items = json.loads(row), [] + row, items = ItemDetailsCtx(json.loads(row)), [] bundled_items = get_product_bundle_items(row["item_code"]) for item in bundled_items: diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 41b27427177f..29ed9f55bf38 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1618,7 +1618,7 @@ def test_batch_expiry_for_purchase_receipt(self): self.assertTrue(return_pi.docstatus == 1) def test_disable_last_purchase_rate(self): - from erpnext.stock.get_item_details import get_item_details + from erpnext.stock.get_item_details import ItemDetailsCtx, get_item_details item = make_item( "_Test Disable Last Purchase Rate", @@ -1633,8 +1633,8 @@ def test_disable_last_purchase_rate(self): item_code=item.name, ) - args = pr.items[0].as_dict() - args.update( + ctx = ItemDetailsCtx(pr.items[0].as_dict()) + ctx.update( { "supplier": pr.supplier, "doctype": pr.doctype, @@ -1646,7 +1646,7 @@ def test_disable_last_purchase_rate(self): } ) - res = get_item_details(args) + res = get_item_details(ctx) self.assertEqual(res.get("last_purchase_rate"), 0) frappe.db.set_single_value("Buying Settings", "disable_last_purchase_rate", 0) @@ -1657,7 +1657,7 @@ def test_disable_last_purchase_rate(self): item_code=item.name, ) - res = get_item_details(args) + res = get_item_details(ctx) self.assertEqual(res.get("last_purchase_rate"), 100) def test_validate_received_qty_for_internal_pr(self): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index fde469356081..36b431f8a779 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -40,6 +40,7 @@ OpeningEntryAccountError, ) from erpnext.stock.get_item_details import ( + ItemDetailsCtx, get_barcode_data, get_bin_details, get_conversion_factor, @@ -1636,7 +1637,7 @@ def _validate_work_order(pro_doc): pro_doc.set_actual_dates() @frappe.whitelist() - def get_item_details(self, args=None, for_update=False): + def get_item_details(self, args: ItemDetailsCtx = None, for_update=False): item = frappe.db.sql( """select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group, i.has_batch_no, i.sample_quantity, i.has_serial_no, i.allow_alternative_item,