From 467046436b6deb115ef3da895672d2e14cadd42f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 23 Mar 2023 11:41:20 +0530 Subject: [PATCH] refactor: serial no ledger and batchwise balance history report --- .../sales_invoice/test_sales_invoice.py | 2 +- .../controllers/sales_and_purchase_return.py | 54 +++++++++++---- .../controllers/subcontracting_controller.py | 7 +- erpnext/stock/deprecated_serial_batch.py | 2 +- .../serial_and_batch_bundle.py | 11 ++-- .../stock/doctype/stock_entry/stock_entry.js | 3 +- .../stock_ledger_entry/stock_ledger_entry.py | 10 +++ .../test_stock_reconciliation.py | 15 +++-- .../batch_wise_balance_history.py | 48 +++++++++++++- .../serial_no_ledger/serial_no_ledger.js | 32 +++++++-- .../serial_no_ledger/serial_no_ledger.py | 65 ++++++++++++++++++- erpnext/stock/serial_batch_bundle.py | 36 ++++++++-- erpnext/stock/stock_ledger.py | 6 +- erpnext/stock/utils.py | 6 +- 14 files changed, 242 insertions(+), 55 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6051c9915d..48fef1892d 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2573,7 +2573,7 @@ class TestSalesInvoice(unittest.TestCase): "posting_date": si.posting_date, "posting_time": si.posting_time, "qty": -1 * flt(d.get("stock_qty")), - "serial_no": d.serial_no, + "serial_and_batch_bundle": d.serial_and_batch_bundle, "company": si.company, "voucher_type": "Sales Invoice", "voucher_no": si.name, diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 80275de8e6..71fee9f049 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -323,8 +323,6 @@ def get_returned_qty_map_for_row(return_against, party, row_name, doctype): def make_return_doc(doctype: str, source_name: str, target_doc=None): from frappe.model.mapper import get_mapped_doc - from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos - company = frappe.db.get_value("Delivery Note", source_name, "company") default_warehouse_for_sales_return = frappe.get_cached_value( "Company", company, "default_warehouse_for_sales_return" @@ -392,23 +390,51 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None): doc.run_method("calculate_taxes_and_totals") def update_item(source_doc, target_doc, source_parent): + from erpnext.stock.serial_batch_bundle import SerialBatchCreation + target_doc.qty = -1 * source_doc.qty - if source_doc.serial_no: - returned_serial_nos = get_returned_serial_nos(source_doc, source_parent) - serial_nos = list(set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos)) - if serial_nos: - target_doc.serial_no = "\n".join(serial_nos) + if source_doc.get("serial_and_batch_bundle"): + type_of_transaction = "Inward" + if ( + frappe.db.get_value( + "Serial and Batch Bundle", source_doc.serial_and_batch_bundle, "type_of_transaction" + ) + == "Inward" + ): + type_of_transaction = "Outward" - if source_doc.get("rejected_serial_no"): - returned_serial_nos = get_returned_serial_nos( - source_doc, source_parent, serial_no_field="rejected_serial_no" + cls_obj = SerialBatchCreation( + { + "type_of_transaction": type_of_transaction, + "serial_and_batch_bundle": source_doc.serial_and_batch_bundle, + } ) - rejected_serial_nos = list( - set(get_serial_nos(source_doc.rejected_serial_no)) - set(returned_serial_nos) + + cls_obj.duplicate_package() + if cls_obj.serial_and_batch_bundle: + target_doc.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle + + if source_doc.get("rejected_serial_and_batch_bundle"): + type_of_transaction = "Inward" + if ( + frappe.db.get_value( + "Serial and Batch Bundle", source_doc.rejected_serial_and_batch_bundle, "type_of_transaction" + ) + == "Inward" + ): + type_of_transaction = "Outward" + + cls_obj = SerialBatchCreation( + { + "type_of_transaction": type_of_transaction, + "serial_and_batch_bundle": source_doc.rejected_serial_and_batch_bundle, + } ) - if rejected_serial_nos: - target_doc.rejected_serial_no = "\n".join(rejected_serial_nos) + + cls_obj.duplicate_package() + if cls_obj.serial_and_batch_bundle: + target_doc.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle if doctype in ["Purchase Receipt", "Subcontracting Receipt"]: returned_qty_map = get_returned_qty_map_for_row( diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index b92988342a..814657d5f5 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -294,13 +294,13 @@ class SubcontractingController(StockController): for batch_no, qty in consumed_bundles.batch_nos.items(): self.available_materials[key]["batch_no"][batch_no] -= abs(qty) - # Will be deperecated in v16 + # Will be deprecated in v16 if row.serial_no: self.available_materials[key]["serial_no"] = list( set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no)) ) - # Will be deperecated in v16 + # Will be deprecated in v16 if row.batch_no: self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty @@ -814,8 +814,7 @@ class SubcontractingController(StockController): "posting_date": self.posting_date, "posting_time": self.posting_time, "qty": -1 * item.consumed_qty, - "serial_no": item.serial_no, - "batch_no": item.batch_no, + "serial_and_batch_bundle": item.serial_and_batch_bundle, } ) diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index 14717c6902..ac30f8200a 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -4,7 +4,7 @@ from frappe.utils import flt class DeprecatedSerialNoValuation: - # Will be deperecated in v16 + # Will be deprecated in v16 def calculate_stock_value_from_deprecarated_ledgers(self): serial_nos = list( diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index c06f63f203..311b35fa5c 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -11,7 +11,7 @@ from frappe.query_builder.functions import CombineDatetime, Sum from frappe.utils import add_days, cint, flt, get_link_to_form, today from pypika import Case -from erpnext.stock.serial_batch_bundle import BatchNoBundleValuation, SerialNoBundleValuation +from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation class SerialNoExistsInFutureTransactionError(frappe.ValidationError): @@ -81,14 +81,14 @@ class SerialandBatchBundle(Document): def set_incoming_rate_for_outward_transaction(self, row=None, save=False): sle = self.get_sle_for_outward_transaction(row) if self.has_serial_no: - sn_obj = SerialNoBundleValuation( + sn_obj = SerialNoValuation( sle=sle, warehouse=self.item_code, item_code=self.warehouse, ) else: - sn_obj = BatchNoBundleValuation( + sn_obj = BatchNoValuation( sle=sle, warehouse=self.item_code, item_code=self.warehouse, @@ -187,9 +187,12 @@ class SerialandBatchBundle(Document): self.set_incoming_rate(save=True, row=row) self.calculate_qty_and_amount(save=True) self.validate_quantity(row) - self.set_warranty_expiry_date(row) + self.set_warranty_expiry_date() def set_warranty_expiry_date(self): + if self.type_of_transaction != "Outward": + return + if not (self.docstatus == 1 and self.voucher_type == "Delivery Note" and self.has_serial_no): return diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index e0c32e42b5..6ffe5b35b2 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -493,8 +493,7 @@ frappe.ui.form.on('Stock Entry', { 'item_code': child.item_code, 'warehouse': cstr(child.s_warehouse) || cstr(child.t_warehouse), 'transfer_qty': child.transfer_qty, - 'serial_no': child.serial_no, - 'batch_no': child.batch_no, + 'serial_and_batch_bundle': child.serial_and_batch_bundle, 'qty': child.s_warehouse ? -1* child.transfer_qty : child.transfer_qty, 'posting_date': frm.doc.posting_date, 'posting_time': frm.doc.posting_time, diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index a902655952..7b3d7f4efb 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -92,6 +92,16 @@ class StockLedgerEntry(Document): as_dict=1, ) + values_to_be_change = {} + if self.has_batch_no != item_detail.has_batch_no: + values_to_be_change["has_batch_no"] = item_detail.has_batch_no + + if self.has_serial_no != item_detail.has_serial_no: + values_to_be_change["has_serial_no"] = item_detail.has_serial_no + + if values_to_be_change: + self.db_set(values_to_be_change) + if not item_detail: frappe.throw(_("Item {0} not found").format(self.item_code)) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 621b9df124..66bef503e5 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -157,7 +157,9 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): item_code=serial_item_code, warehouse=serial_warehouse, qty=5, rate=200 ) - serial_nos = get_serial_nos(sr.items[0].serial_no) + serial_nos = frappe.get_doc( + "Serial and Batch Bundle", sr.items[0].serial_and_batch_bundle + ).get_serial_nos() self.assertEqual(len(serial_nos), 5) args = { @@ -165,7 +167,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): "warehouse": serial_warehouse, "posting_date": nowdate(), "posting_time": nowtime(), - "serial_no": sr.items[0].serial_no, + "serial_and_batch_bundle": sr.items[0].serial_and_batch_bundle, } valuation_rate = get_incoming_rate(args) @@ -177,7 +179,10 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): item_code=serial_item_code, warehouse=serial_warehouse, qty=5, rate=300 ) - serial_nos1 = get_serial_nos(sr.items[0].serial_no) + serial_nos1 = frappe.get_doc( + "Serial and Batch Bundle", sr.items[0].serial_and_batch_bundle + ).get_serial_nos() + self.assertEqual(len(serial_nos1), 5) args = { @@ -185,7 +190,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): "warehouse": serial_warehouse, "posting_date": nowdate(), "posting_time": nowtime(), - "serial_no": sr.items[0].serial_no, + "serial_and_batch_bundle": sr.items[0].serial_and_batch_bundle, } valuation_rate = get_incoming_rate(args) @@ -257,7 +262,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): sr.save() sr.submit() - batch_no = sr.items[0].batch_no + batch_no = sr.items[0].serial_and_batch_bundle self.assertTrue(batch_no) to_delete_records.append(sr.name) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 0d57938e31..2c460821d3 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -67,8 +67,16 @@ def get_columns(filters): return columns -# get all details def get_stock_ledger_entries(filters): + # Will be deprecated in v16 + entries = get_stock_ledger_entries_for_batch_no(filters) + + entries += get_stock_ledger_entries_for_batch_bundle(filters) + return entries + + +# get all details +def get_stock_ledger_entries_for_batch_no(filters): if not filters.get("from_date"): frappe.throw(_("'From Date' is required")) if not filters.get("to_date"): @@ -99,7 +107,43 @@ def get_stock_ledger_entries(filters): if filters.get(field): query = query.where(sle[field] == filters.get(field)) - return query.run(as_dict=True) + return query.run(as_dict=True) or [] + + +def get_stock_ledger_entries_for_batch_bundle(filters): + sle = frappe.qb.DocType("Stock Ledger Entry") + batch_package = frappe.qb.DocType("Serial and Batch Entry") + + query = ( + frappe.qb.from_(sle) + .inner_join(batch_package) + .on(batch_package.parent == sle.serial_and_batch_bundle) + .select( + sle.item_code, + sle.warehouse, + batch_package.batch_no, + sle.posting_date, + fn.Sum(batch_package.qty).as_("actual_qty"), + ) + .where( + (sle.docstatus < 2) + & (sle.is_cancelled == 0) + & (sle.has_batch_no == 1) + & (sle.posting_date <= filters["to_date"]) + ) + .groupby(sle.voucher_no, sle.batch_no, sle.item_code, sle.warehouse) + .orderby(sle.item_code, sle.warehouse) + ) + + query = apply_warehouse_filter(query, sle, filters) + for field in ["item_code", "batch_no", "company"]: + if filters.get(field): + if field == "batch_no": + query = query.where(batch_package[field] == filters.get(field)) + else: + query = query.where(sle[field] == filters.get(field)) + + return query.run(as_dict=True) or [] def get_item_warehouse_batch_map(filters, float_precision): diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js index 616312e311..976e5156ad 100644 --- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js @@ -18,13 +18,6 @@ frappe.query_reports["Serial No Ledger"] = { } } }, - { - 'label': __('Serial No'), - 'fieldtype': 'Link', - 'fieldname': 'serial_no', - 'options': 'Serial No', - 'reqd': 1 - }, { 'label': __('Warehouse'), 'fieldtype': 'Link', @@ -42,11 +35,36 @@ frappe.query_reports["Serial No Ledger"] = { } } }, + { + 'label': __('Serial No'), + 'fieldtype': 'Link', + 'fieldname': 'serial_no', + 'options': 'Serial No', + get_query: function() { + let item_code = frappe.query_report.get_filter_value('item_code'); + let warehouse = frappe.query_report.get_filter_value('warehouse'); + + let query_filters = {'item_code': item_code}; + if (warehouse) { + query_filters['warehouse'] = warehouse; + } + + return { + filters: query_filters + } + } + }, { 'label': __('As On Date'), 'fieldtype': 'Date', 'fieldname': 'posting_date', 'default': frappe.datetime.get_today() }, + { + 'label': __('Posting Time'), + 'fieldtype': 'Time', + 'fieldname': 'posting_time', + 'default': frappe.datetime.get_time() + }, ] }; diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py index e439f51dd6..99f1a9403b 100644 --- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - +import frappe from frappe import _ from erpnext.stock.stock_ledger import get_stock_ledger_entries @@ -45,10 +45,71 @@ def get_columns(filters): "options": "Warehouse", "width": 220, }, + { + "label": _("Serial No"), + "fieldtype": "Link", + "fieldname": "serial_no", + "options": "Serial No", + "width": 220, + }, ] return columns def get_data(filters): - return get_stock_ledger_entries(filters, "<=", order="asc") or [] + stock_ledgers = get_stock_ledger_entries(filters, "<=", order="asc", check_serial_no=False) + + if not stock_ledgers: + return [] + + data = [] + serial_bundle_ids = [ + d.serial_and_batch_bundle for d in stock_ledgers if d.serial_and_batch_bundle + ] + + bundle_wise_serial_nos = get_serial_nos(filters, serial_bundle_ids) + + for row in stock_ledgers: + args = frappe._dict( + { + "posting_date": row.posting_date, + "posting_time": row.posting_time, + "voucher_type": row.voucher_type, + "voucher_no": row.voucher_no, + "company": row.company, + "warehouse": row.warehouse, + } + ) + + serial_nos = bundle_wise_serial_nos.get(row.serial_and_batch_bundle, []) + + for index, serial_no in enumerate(serial_nos): + if index == 0: + args.serial_no = serial_no + data.append(args) + else: + data.append( + { + "serial_no": serial_no, + } + ) + + return data + + +def get_serial_nos(filters, serial_bundle_ids): + bundle_wise_serial_nos = {} + bundle_filters = {"parent": ["in", serial_bundle_ids]} + if filters.get("serial_no"): + bundle_filters["serial_no"] = filters.get("serial_no") + + for d in frappe.get_all( + "Serial and Batch Entry", + fields=["serial_no", "parent"], + filters=bundle_filters, + order_by="idx asc", + ): + bundle_wise_serial_nos.setdefault(d.parent, []).append(d.serial_no) + + return bundle_wise_serial_nos diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index f2de819a50..1266133e68 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -341,7 +341,7 @@ def get_serial_nos(serial_and_batch_bundle, check_outward=True): return [d.serial_no for d in entries] -class SerialNoBundleValuation(DeprecatedSerialNoValuation): +class SerialNoValuation(DeprecatedSerialNoValuation): def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) @@ -470,7 +470,7 @@ def is_rejected(voucher_type, voucher_detail_no, warehouse): return False -class BatchNoBundleValuation(DeprecatedBatchNoValuation): +class BatchNoValuation(DeprecatedBatchNoValuation): def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) @@ -567,11 +567,11 @@ class BatchNoBundleValuation(DeprecatedBatchNoValuation): def get_empty_batches_based_work_order(work_order, item_code): - batches = get_batches_from_work_order(work_order) + batches = get_batches_from_work_order(work_order, item_code) if not batches: return batches - entries = get_batches_from_stock_entries(work_order) + entries = get_batches_from_stock_entries(work_order, item_code) if not entries: return batches @@ -589,15 +589,18 @@ def get_empty_batches_based_work_order(work_order, item_code): return batches -def get_batches_from_work_order(work_order): +def get_batches_from_work_order(work_order, item_code): return frappe._dict( frappe.get_all( - "Batch", fields=["name", "qty_to_produce"], filters={"reference_name": work_order}, as_list=1 + "Batch", + fields=["name", "qty_to_produce"], + filters={"reference_name": work_order, "item": item_code}, + as_list=1, ) ) -def get_batches_from_stock_entries(work_order): +def get_batches_from_stock_entries(work_order, item_code): entries = frappe.get_all( "Stock Entry", filters={"work_order": work_order, "docstatus": 1, "purpose": "Manufacture"}, @@ -610,6 +613,7 @@ def get_batches_from_stock_entries(work_order): filters={ "parent": ("in", [d.name for d in entries]), "is_finished_item": 1, + "item_code": item_code, }, ) @@ -623,3 +627,21 @@ def set_batch_details_from_package(ids, batches): for d in entries: batches[d.batch_no] -= d.qty + + +class SerialBatchCreation: + def __init__(self, args): + for key, value in args.items(): + setattr(self, key, value) + + def duplicate_package(self): + if not self.serial_and_batch_bundle: + return + + id = self.serial_and_batch_bundle + package = frappe.get_doc("Serial and Batch Bundle", id) + new_package = frappe.copy_doc(package) + new_package.type_of_transaction = self.type_of_transaction + new_package.save() + + self.serial_and_batch_bundle = new_package.name diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index dfb77864cd..e616ed030f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -27,7 +27,7 @@ from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( get_sre_reserved_qty_for_item_and_warehouse as get_reserved_stock, ) -from erpnext.stock.serial_batch_bundle import BatchNoBundleValuation, SerialNoBundleValuation +from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation from erpnext.stock.utils import ( get_incoming_outgoing_rate_for_cancel, get_or_make_bin, @@ -693,7 +693,7 @@ class update_entries_after(object): if sle.serial_and_batch_bundle: if frappe.get_cached_value("Item", sle.item_code, "has_serial_no"): - SerialNoBundleValuation( + SerialNoValuation( sle=sle, sle_self=self, wh_data=self.wh_data, @@ -701,7 +701,7 @@ class update_entries_after(object): item_code=sle.item_code, ) else: - BatchNoBundleValuation( + BatchNoValuation( sle=sle, sle_self=self, wh_data=self.wh_data, diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 18e0b90efc..8d1ec54e53 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -12,7 +12,7 @@ from frappe.utils import cstr, flt, get_link_to_form, nowdate, nowtime import erpnext from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses -from erpnext.stock.serial_batch_bundle import BatchNoBundleValuation, SerialNoBundleValuation +from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation from erpnext.stock.valuation import FIFOValuation, LIFOValuation BarcodeScanResult = Dict[str, Optional[str]] @@ -264,7 +264,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True): if item_details.has_serial_no and args.get("serial_and_batch_bundle"): args.actual_qty = args.qty - sn_obj = SerialNoBundleValuation( + sn_obj = SerialNoValuation( sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code"), @@ -274,7 +274,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True): elif item_details.has_batch_no and args.get("serial_and_batch_bundle"): args.actual_qty = args.qty - batch_obj = BatchNoBundleValuation( + batch_obj = BatchNoValuation( sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code"),