diff --git a/erpnext/stock/doctype/stock_entry/services/manufacturing.py b/erpnext/stock/doctype/stock_entry/services/manufacturing.py index 0b5889824e1..f23206a71e1 100644 --- a/erpnext/stock/doctype/stock_entry/services/manufacturing.py +++ b/erpnext/stock/doctype/stock_entry/services/manufacturing.py @@ -668,6 +668,8 @@ class ManufactureStockEntry(BaseManufactureStockEntry): qty = row.qty if is_return else (flt(row.qty) * flt(self.doc.fg_completed_qty)) / pending_qty_to_mfg item_args["qty"] = ceil_qty_if_uom_has_whole_number(qty, row.uom) item_args["transfer_qty"] = item_args["qty"] + if not flt(item_args["qty"], frappe.get_precision("Stock Entry Detail", "qty")): + return if is_return: if row.get("original_item"): item_args["original_item"] = row.original_item @@ -760,7 +762,7 @@ class ManufactureStockEntry(BaseManufactureStockEntry): def add_materials_from_transfer(self): for row in self._transfer_entries: row.warehouse = row.t_warehouse - key = (row.item_code, row.warehouse) + key = (row.item_code, row.warehouse, row.original_item or None) if key not in self.available_materials: self.available_materials[key] = frappe._dict(row) else: @@ -790,20 +792,49 @@ class ManufactureStockEntry(BaseManufactureStockEntry): def remove_consumed_materials_from_available(self): for row in self._consumption_entries: row.warehouse = row.s_warehouse - key = (row.item_code, row.warehouse) - self.available_materials[key].qty -= row.qty + buckets = self._get_available_buckets(row.item_code, row.warehouse) + self._deduct_consumed_qty(buckets, flt(row.qty)) if row.serial_and_batch_bundle: - self._deduct_consumed_serial_batch(key, row.serial_and_batch_bundle) + self._deduct_consumed_serial_batch(buckets, row.serial_and_batch_bundle) - def _deduct_consumed_serial_batch(self, key, sabb_name): + def _get_available_buckets(self, item_code, warehouse): + return [ + bucket + for key, bucket in self.available_materials.items() + if key[0] == item_code and key[1] == warehouse + ] + + def _deduct_consumed_qty(self, buckets, consumed_qty): + for bucket in buckets[:-1]: + deducted = min(max(flt(bucket.qty), 0.0), consumed_qty) + bucket.qty -= deducted + consumed_qty -= deducted + buckets[-1].qty -= consumed_qty + + def _deduct_consumed_serial_batch(self, buckets, sabb_name): _details = self.get_sabb_details(sabb_name) if _details.serial_nos: - for sn in _details.serial_nos: - self.available_materials[key].serial_nos.remove(sn) + for serial_no in _details.serial_nos: + self._get_serial_no_bucket(buckets, serial_no).serial_nos.remove(serial_no) elif _details.batches: for batch_no, qty in _details.batches.items(): - # qty is negative, so add instead of subtract - self.available_materials[key].batches[batch_no] += qty + self._deduct_consumed_batch_qty(buckets, batch_no, -qty) + + def _get_serial_no_bucket(self, buckets, serial_no): + for bucket in buckets: + if bucket.serial_nos and serial_no in bucket.serial_nos: + return bucket + return buckets[-1] + + def _deduct_consumed_batch_qty(self, buckets, batch_no, consumed_qty): + holders = [bucket for bucket in buckets if bucket.batches and batch_no in bucket.batches] + if not holders: + holders = buckets[-1:] + for bucket in holders[:-1]: + deducted = min(max(flt(bucket.batches[batch_no]), 0.0), consumed_qty) + bucket.batches[batch_no] -= deducted + consumed_qty -= deducted + holders[-1].batches[batch_no] -= consumed_qty def add_additional_cost(self): if not self.wo_doc: