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 3f778696ff6..c75d57f69e0 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -70,6 +70,7 @@ "target_warehouse", "quality_inspection", "col_break4", + "allow_zero_valuation_rate", "against_sales_order", "so_detail", "against_sales_invoice", @@ -79,6 +80,10 @@ "section_break_40", "pick_serial_and_batch", "serial_and_batch_bundle", + "column_break_eaoe", + "serial_no", + "batch_no", + "available_qty_section", "actual_batch_qty", "actual_qty", "installed_qty", @@ -88,7 +93,6 @@ "received_qty", "accounting_details_section", "expense_account", - "allow_zero_valuation_rate", "column_break_71", "internal_transfer_section", "material_request", @@ -505,7 +509,8 @@ }, { "fieldname": "section_break_40", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Serial and Batch No" }, { "allow_on_submit": 1, @@ -847,19 +852,44 @@ "fieldname": "serial_and_batch_bundle", "fieldtype": "Link", "label": "Serial and Batch Bundle", - "options": "Serial and Batch Bundle" + "no_copy": 1, + "options": "Serial and Batch Bundle", + "print_hide": 1 }, { "fieldname": "pick_serial_and_batch", "fieldtype": "Button", "label": "Pick Serial / Batch No" + }, + { + "collapsible": 1, + "fieldname": "available_qty_section", + "fieldtype": "Section Break", + "label": "Available Qty" + }, + { + "fieldname": "column_break_eaoe", + "fieldtype": "Column Break" + }, + { + "fieldname": "serial_no", + "fieldtype": "Text", + "label": "Serial No", + "read_only": 1 + }, + { + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch No", + "options": "Batch", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-05-01 21:05:14.175640", + "modified": "2023-05-02 21:05:14.175640", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 900fb75a5fa..f7798936ab7 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -79,6 +79,7 @@ "purchase_order", "purchase_invoice", "column_break_40", + "allow_zero_valuation_rate", "is_fixed_asset", "asset_location", "asset_category", @@ -93,8 +94,12 @@ "section_break_45", "update_serial_batch_bundle", "serial_and_batch_bundle", + "rejected_serial_and_batch_bundle", "col_break5", - "allow_zero_valuation_rate", + "serial_no", + "rejected_serial_no", + "batch_no", + "subcontract_bom_section", "include_exploded_items", "bom", "item_weight_details", @@ -998,12 +1003,43 @@ "fieldname": "update_serial_batch_bundle", "fieldtype": "Button", "label": "Add Serial / Batch No" + }, + { + "depends_on": "eval:parent.is_old_subcontracting_flow", + "fieldname": "subcontract_bom_section", + "fieldtype": "Section Break", + "label": "Subcontract BOM" + }, + { + "fieldname": "serial_no", + "fieldtype": "Text", + "label": "Serial No", + "read_only": 1 + }, + { + "fieldname": "rejected_serial_no", + "fieldtype": "Text", + "label": "Rejected Serial No", + "read_only": 1 + }, + { + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch No", + "options": "Batch", + "read_only": 1 + }, + { + "fieldname": "rejected_serial_and_batch_bundle", + "fieldtype": "Link", + "label": "Rejected Serial and Batch Bundle", + "options": "Serial and Batch Bundle" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-02-28 16:43:04.470104", + "modified": "2023-03-03 12:45:03.087766", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json index cfe35d7755a..4148946e349 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json @@ -7,8 +7,8 @@ "field_order": [ "item_details_tab", "company", - "item_group", "warehouse", + "type_of_transaction", "column_break_4", "item_code", "item_name", @@ -18,6 +18,7 @@ "ledgers", "quantity_and_rate_section", "total_qty", + "item_group", "column_break_13", "avg_rate", "total_amount", @@ -46,6 +47,7 @@ "fetch_from": "item_code.item_group", "fieldname": "item_group", "fieldtype": "Link", + "hidden": 1, "label": "Item Group", "options": "Item Group" }, @@ -171,12 +173,19 @@ "label": "Warehouse", "options": "Warehouse", "reqd": 1 + }, + { + "fieldname": "type_of_transaction", + "fieldtype": "Select", + "label": "Type of Transaction", + "options": "\nInward\nOutward", + "reqd": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-01-10 11:32:09.018760", + "modified": "2023-03-03 16:18:53.709069", "modified_by": "Administrator", "module": "Stock", "name": "Serial and Batch Bundle", 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 1c9dc15088b..0f8f6d25866 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 @@ -267,7 +267,11 @@ def get_serial_and_batch_ledger(**kwargs): serial_batch_table.qty, serial_batch_table.incoming_rate, ) - .where((sle_table.item_code == kwargs.item_code) & (sle_table.warehouse == kwargs.warehouse)) + .where( + (sle_table.item_code == kwargs.item_code) + & (sle_table.warehouse == kwargs.warehouse) + & (serial_batch_table.is_outward == 0) + ) ) if kwargs.serial_nos: diff --git a/erpnext/stock/doctype/serial_and_batch_ledger/serial_and_batch_ledger.json b/erpnext/stock/doctype/serial_and_batch_ledger/serial_and_batch_ledger.json index 65eaa0357e5..d99322504fe 100644 --- a/erpnext/stock/doctype/serial_and_batch_ledger/serial_and_batch_ledger.json +++ b/erpnext/stock/doctype/serial_and_batch_ledger/serial_and_batch_ledger.json @@ -15,7 +15,8 @@ "incoming_rate", "column_break_8", "outgoing_rate", - "stock_value_difference" + "stock_value_difference", + "is_outward" ], "fields": [ { @@ -93,12 +94,19 @@ "label": "Change in Stock Value", "no_copy": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_outward", + "fieldtype": "Check", + "label": "Is Outward", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-01-10 12:55:57.368650", + "modified": "2023-03-03 16:52:26.039613", "modified_by": "Administrator", "module": "Stock", "name": "Serial and Batch Ledger", diff --git a/erpnext/stock/doctype/serial_and_batch_no_bundle/__init__.py b/erpnext/stock/doctype/serial_and_batch_no_bundle/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.js b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.js new file mode 100644 index 00000000000..c36abd652ee --- /dev/null +++ b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Serial and Batch No Bundle", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.json b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.json new file mode 100644 index 00000000000..ec3315678c3 --- /dev/null +++ b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.json @@ -0,0 +1,176 @@ +{ + "actions": [], + "creation": "2022-09-29 14:56:38.338267", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_details_tab", + "company", + "item_group", + "has_serial_no", + "column_break_4", + "item_code", + "item_name", + "has_batch_no", + "serial_no_and_batch_no_tab", + "ledgers", + "qty", + "reference_tab", + "voucher_type", + "voucher_no", + "posting_date", + "posting_time", + "is_cancelled", + "amended_from" + ], + "fields": [ + { + "fieldname": "item_details_tab", + "fieldtype": "Tab Break", + "label": "Item Details" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fetch_from": "item_code.item_group", + "fieldname": "item_group", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group" + }, + { + "default": "0", + "fetch_from": "item_code.has_serial_no", + "fieldname": "has_serial_no", + "fieldtype": "Check", + "label": "Has Serial No", + "read_only": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1 + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name" + }, + { + "default": "0", + "fetch_from": "item_code.has_batch_no", + "fieldname": "has_batch_no", + "fieldtype": "Check", + "label": "Has Batch No", + "read_only": 1 + }, + { + "fieldname": "serial_no_and_batch_no_tab", + "fieldtype": "Section Break" + }, + { + "allow_bulk_edit": 1, + "fieldname": "ledgers", + "fieldtype": "Table", + "label": "Serial No and Batch No Transaction", + "options": "Serial and Batch No Ledger", + "reqd": 1 + }, + { + "fieldname": "qty", + "fieldtype": "Float", + "label": "Total Qty", + "read_only": 1 + }, + { + "fieldname": "reference_tab", + "fieldtype": "Tab Break", + "label": "Reference" + }, + { + "fieldname": "voucher_type", + "fieldtype": "Link", + "label": "Voucher Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "label": "Voucher No", + "options": "voucher_type" + }, + { + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_cancelled", + "fieldtype": "Check", + "label": "Is Cancelled", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Serial and Batch No Bundle", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2023-03-05 17:38:51.871723", + "modified_by": "Administrator", + "module": "Stock", + "name": "Serial and Batch No Bundle", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "item_code" +} \ No newline at end of file diff --git a/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.py b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.py new file mode 100644 index 00000000000..46c0e5ae022 --- /dev/null +++ b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class SerialandBatchNoBundle(Document): + pass diff --git a/erpnext/stock/doctype/serial_and_batch_no_bundle/test_serial_and_batch_no_bundle.py b/erpnext/stock/doctype/serial_and_batch_no_bundle/test_serial_and_batch_no_bundle.py new file mode 100644 index 00000000000..2d5b9d3d061 --- /dev/null +++ b/erpnext/stock/doctype/serial_and_batch_no_bundle/test_serial_and_batch_no_bundle.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestSerialandBatchNoBundle(FrappeTestCase): + pass diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py new file mode 100644 index 00000000000..f32b79db678 --- /dev/null +++ b/erpnext/stock/serial_batch_bundle.py @@ -0,0 +1,385 @@ +import frappe +from frappe.model.naming import make_autoname +from frappe.query_builder.functions import CombineDatetime, Sum +from frappe.utils import cint, cstr, flt, now + +from erpnext.stock.valuation import round_off_if_near_zero + + +class SerialBatchBundle: + def __init__(self, **kwargs): + for key, value in kwargs.iteritems(): + setattr(self, key, value) + + self.set_item_details() + + def process_serial_and_batch_bundle(self): + if self.item_details.has_serial_no: + self.process_serial_no + elif self.item_details.has_batch_no: + self.process_batch_no + + def set_item_details(self): + fields = [ + "has_batch_no", + "has_serial_no", + "item_name", + "item_group", + "serial_no_series", + "create_new_batch", + "batch_number_series", + ] + + self.item_details = frappe.get_cached_value("Item", self.sle.item_code, fields, as_dict=1) + + def process_serial_no(self): + if ( + not self.sle.is_cancelled + and not self.sle.serial_and_batch_bundle + and self.sle.actual_qty > 0 + and self.item_details.has_serial_no == 1 + and self.item_details.serial_no_series + ): + sr_nos = self.auto_create_serial_nos() + self.make_serial_no_bundle(sr_nos) + + def auto_create_serial_nos(self): + sr_nos = [] + serial_nos_details = [] + + for i in range(cint(self.sle.actual_qty)): + serial_no = make_autoname(self.item_details.serial_no_series, "Serial No") + sr_nos.append(serial_no) + serial_nos_details.append( + ( + serial_no, + serial_no, + now(), + now(), + frappe.session.user, + frappe.session.user, + self.warehouse, + self.company, + self.item_code, + self.item_details.item_name, + self.item_details.description, + ) + ) + + if serial_nos_details: + fields = [ + "name", + "serial_no", + "creation", + "modified", + "owner", + "modified_by", + "warehouse", + "company", + "item_code", + "item_name", + "description", + ] + + frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details)) + + return sr_nos + + def make_serial_no_bundle(self, serial_nos=None): + sn_doc = frappe.new_doc("Serial and Batch Bundle") + sn_doc.item_code = self.item_code + sn_doc.item_name = self.item_details.item_name + sn_doc.item_group = self.item_details.item_group + sn_doc.has_serial_no = self.item_details.has_serial_no + sn_doc.has_batch_no = self.item_details.has_batch_no + sn_doc.voucher_type = self.sle.voucher_type + sn_doc.voucher_no = self.sle.voucher_no + sn_doc.flags.ignore_mandatory = True + sn_doc.flags.ignore_validate = True + sn_doc.total_qty = self.sle.actual_qty + sn_doc.avg_rate = self.sle.incoming_rate + sn_doc.total_amount = flt(self.sle.actual_qty) * flt(self.sle.incoming_rate) + sn_doc.insert() + + batch_no = "" + if self.item_details.has_batch_no: + batch_no = self.create_batch() + + if serial_nos: + self.add_serial_no_to_bundle(sn_doc, serial_nos, batch_no) + elif self.item_details.has_batch_no: + self.add_batch_no_to_bundle(sn_doc, batch_no) + sn_doc.save() + + sn_doc.load_from_db() + sn_doc.flags.ignore_validate = True + sn_doc.flags.ignore_mandatory = True + + sn_doc.submit() + + self.sle.serial_and_batch_bundle = sn_doc.name + + def add_serial_no_to_bundle(self, sn_doc, serial_nos, batch_no=None): + ledgers = [] + + fields = [ + "name", + "serial_no", + "batch_no", + "warehouse", + "item_code", + "qty", + "incoming_rate", + "parent", + "parenttype", + "parentfield", + ] + + for serial_no in serial_nos: + ledgers.append( + ( + frappe.generate_hash("Serial and Batch Ledger", 10), + serial_no, + batch_no, + self.warehouse, + self.item_details.item_code, + 1, + self.sle.incoming_rate, + sn_doc.name, + sn_doc.doctype, + "ledgers", + ) + ) + + frappe.db.bulk_insert("Serial and Batch Ledger", fields=fields, values=set(ledgers)) + + def add_batch_no_to_bundle(self, sn_doc, batch_no): + sn_doc.append( + "ledgers", + { + "batch_no": batch_no, + "qty": self.sle.actual_qty, + "incoming_rate": self.sle.incoming_rate, + }, + ) + + def create_batch(self): + from erpnext.stock.doctype.batch.batch import make_batch + + return make_batch( + frappe._dict( + { + "item": self.item_code, + "reference_doctype": self.sle.voucher_type, + "reference_name": self.sle.voucher_no, + } + ) + ) + + def process_batch_no(self): + if ( + not self.sle.is_cancelled + and not self.sle.serial_and_batch_bundle + and self.sle.actual_qty > 0 + and self.item_details.has_batch_no == 1 + and self.item_details.create_new_batch + and self.item_details.batch_number_series + ): + self.make_serial_no_bundle() + + +class RepostSerialBatchBundle: + def __init__(self, **kwargs): + for key, value in kwargs.iteritems(): + setattr(self, key, value) + + def get_valuation_rate(self): + if self.sle.actual_qty > 0: + self.sle.incoming_rate = self.sle.valuation_rate + + if self.sle.actual_qty < 0: + self.sle.outgoing_rate = self.sle.valuation_rate + + def get_valuation_rate_for_serial_nos(self): + serial_nos = self.get_serial_nos() + + subquery = f""" + SELECT + MAX(ledger.posting_date), name + FROM + ledger + WHERE + ledger.serial_no IN {tuple(serial_nos)} + AND ledger.is_outward = 0 + AND ledger.warehouse = {frappe.db.escape(self.sle.warehouse)} + AND ledger.item_code = {frappe.db.escape(self.sle.item_code)} + AND ( + ledger.posting_date < '{self.sle.posting_date}' + OR ( + ledger.posting_date = '{self.sle.posting_date}' + AND ledger.posting_time <= '{self.sle.posting_time}' + ) + ) + """ + + frappe.db.sql( + """ + SELECT + serial_no, incoming_rate + FROM + `tabSerial and Batch Ledger` AS ledger, + ({subquery}) AS SubQuery + WHERE + ledger.name = SubQuery.name + GROUP BY + ledger.serial_no + """ + ) + + def get_serial_nos(self): + ledgers = frappe.get_all( + "Serial and Batch Ledger", + fields=["serial_no"], + filters={"parent": self.sle.serial_and_batch_bundle, "is_outward": 1}, + ) + + return [d.serial_no for d in ledgers] + + +class DeprecatedRepostSerialBatchBundle(RepostSerialBatchBundle): + def get_serialized_values(self, sle): + incoming_rate = flt(sle.incoming_rate) + actual_qty = flt(sle.actual_qty) + serial_nos = cstr(sle.serial_no).split("\n") + + if incoming_rate < 0: + # wrong incoming rate + incoming_rate = self.wh_data.valuation_rate + + stock_value_change = 0 + if actual_qty > 0: + stock_value_change = actual_qty * incoming_rate + else: + # In case of delivery/stock issue, get average purchase rate + # of serial nos of current entry + if not sle.is_cancelled: + outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos) + stock_value_change = -1 * outgoing_value + else: + stock_value_change = actual_qty * sle.outgoing_rate + + new_stock_qty = self.wh_data.qty_after_transaction + actual_qty + + if new_stock_qty > 0: + new_stock_value = ( + self.wh_data.qty_after_transaction * self.wh_data.valuation_rate + ) + stock_value_change + if new_stock_value >= 0: + # calculate new valuation rate only if stock value is positive + # else it remains the same as that of previous entry + self.wh_data.valuation_rate = new_stock_value / new_stock_qty + + if not self.wh_data.valuation_rate and sle.voucher_detail_no: + allow_zero_rate = self.check_if_allow_zero_valuation_rate( + sle.voucher_type, sle.voucher_detail_no + ) + if not allow_zero_rate: + self.wh_data.valuation_rate = self.get_fallback_rate(sle) + + def get_incoming_value_for_serial_nos(self, sle, serial_nos): + # get rate from serial nos within same company + all_serial_nos = frappe.get_all( + "Serial No", fields=["purchase_rate", "name", "company"], filters={"name": ("in", serial_nos)} + ) + + incoming_values = sum(flt(d.purchase_rate) for d in all_serial_nos if d.company == sle.company) + + # Get rate for serial nos which has been transferred to other company + invalid_serial_nos = [d.name for d in all_serial_nos if d.company != sle.company] + for serial_no in invalid_serial_nos: + incoming_rate = frappe.db.sql( + """ + select incoming_rate + from `tabStock Ledger Entry` + where + company = %s + and actual_qty > 0 + and is_cancelled = 0 + and (serial_no = %s + or serial_no like %s + or serial_no like %s + or serial_no like %s + ) + order by posting_date desc + limit 1 + """, + (sle.company, serial_no, serial_no + "\n%", "%\n" + serial_no, "%\n" + serial_no + "\n%"), + ) + + incoming_values += flt(incoming_rate[0][0]) if incoming_rate else 0 + + return incoming_values + + def update_batched_values(self, sle): + incoming_rate = flt(sle.incoming_rate) + actual_qty = flt(sle.actual_qty) + + self.wh_data.qty_after_transaction = round_off_if_near_zero( + self.wh_data.qty_after_transaction + actual_qty + ) + + if actual_qty > 0: + stock_value_difference = incoming_rate * actual_qty + else: + outgoing_rate = get_batch_incoming_rate( + item_code=sle.item_code, + warehouse=sle.warehouse, + batch_no=sle.batch_no, + posting_date=sle.posting_date, + posting_time=sle.posting_time, + creation=sle.creation, + ) + if outgoing_rate is None: + # This can *only* happen if qty available for the batch is zero. + # in such case fall back various other rates. + # future entries will correct the overall accounting as each + # batch individually uses moving average rates. + outgoing_rate = self.get_fallback_rate(sle) + stock_value_difference = outgoing_rate * actual_qty + + self.wh_data.stock_value = round_off_if_near_zero( + self.wh_data.stock_value + stock_value_difference + ) + if self.wh_data.qty_after_transaction: + self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction + + +def get_batch_incoming_rate( + item_code, warehouse, batch_no, posting_date, posting_time, creation=None +): + + sle = frappe.qb.DocType("Stock Ledger Entry") + + timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime( + posting_date, posting_time + ) + if creation: + timestamp_condition |= ( + CombineDatetime(sle.posting_date, sle.posting_time) + == CombineDatetime(posting_date, posting_time) + ) & (sle.creation < creation) + + batch_details = ( + frappe.qb.from_(sle) + .select(Sum(sle.stock_value_difference).as_("batch_value"), Sum(sle.actual_qty).as_("batch_qty")) + .where( + (sle.item_code == item_code) + & (sle.warehouse == warehouse) + & (sle.batch_no == batch_no) + & (sle.is_cancelled == 0) + ) + .where(timestamp_condition) + ).run(as_dict=True) + + if batch_details and batch_details[0].batch_qty: + return batch_details[0].batch_value / batch_details[0].batch_qty