fix: transferred batches are not fetched while making Manufacture stock entry
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index f1df54d..293c2e5 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1656,118 +1656,58 @@
)
def get_transfered_raw_materials(self):
- transferred_materials = frappe.db.sql(
- """
- select
- item_name, original_item, item_code, sum(qty) as qty, sed.t_warehouse as warehouse,
- description, stock_uom, expense_account, cost_center
- from `tabStock Entry` se,`tabStock Entry Detail` sed
- where
- se.name = sed.parent and se.docstatus=1 and se.purpose='Material Transfer for Manufacture'
- and se.work_order= %s and ifnull(sed.t_warehouse, '') != ''
- group by sed.item_code, sed.t_warehouse
- """,
+ available_materials = get_available_materials(self.work_order)
+
+ wo_data = frappe.db.get_value(
+ "Work Order",
self.work_order,
+ ["qty", "produced_qty", "material_transferred_for_manufacturing as trans_qty"],
as_dict=1,
)
- materials_already_backflushed = frappe.db.sql(
- """
- select
- item_code, sed.s_warehouse as warehouse, sum(qty) as qty
- from
- `tabStock Entry` se, `tabStock Entry Detail` sed
- where
- se.name = sed.parent and se.docstatus=1
- and (se.purpose='Manufacture' or se.purpose='Material Consumption for Manufacture')
- and se.work_order= %s and ifnull(sed.s_warehouse, '') != ''
- group by sed.item_code, sed.s_warehouse
- """,
- self.work_order,
- as_dict=1,
- )
-
- backflushed_materials = {}
- for d in materials_already_backflushed:
- backflushed_materials.setdefault(d.item_code, []).append({d.warehouse: d.qty})
-
- po_qty = frappe.db.sql(
- """select qty, produced_qty, material_transferred_for_manufacturing from
- `tabWork Order` where name=%s""",
- self.work_order,
- as_dict=1,
- )[0]
-
- manufacturing_qty = flt(po_qty.qty) or 1
- produced_qty = flt(po_qty.produced_qty)
- trans_qty = flt(po_qty.material_transferred_for_manufacturing) or 1
-
- for item in transferred_materials:
- qty = item.qty
- item_code = item.original_item or item.item_code
- req_items = frappe.get_all(
- "Work Order Item",
- filters={"parent": self.work_order, "item_code": item_code},
- fields=["required_qty", "consumed_qty"],
+ for key, row in available_materials.items():
+ qty = (flt(row.qty) * flt(self.fg_completed_qty)) / (
+ flt(wo_data.trans_qty) - flt(wo_data.produced_qty)
)
- req_qty = flt(req_items[0].required_qty) if req_items else flt(4)
- req_qty_each = flt(req_qty / manufacturing_qty)
- consumed_qty = flt(req_items[0].consumed_qty) if req_items else 0
-
- if trans_qty and manufacturing_qty > (produced_qty + flt(self.fg_completed_qty)):
- if qty >= req_qty:
- qty = (req_qty / trans_qty) * flt(self.fg_completed_qty)
- else:
- qty = qty - consumed_qty
-
- if self.purpose == "Manufacture":
- # If Material Consumption is booked, must pull only remaining components to finish product
- if consumed_qty != 0:
- remaining_qty = consumed_qty - (produced_qty * req_qty_each)
- exhaust_qty = req_qty_each * produced_qty
- if remaining_qty > exhaust_qty:
- if (remaining_qty / (req_qty_each * flt(self.fg_completed_qty))) >= 1:
- qty = 0
- else:
- qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
- else:
- if self.flags.backflush_based_on == "Material Transferred for Manufacture":
- qty = (item.qty / trans_qty) * flt(self.fg_completed_qty)
- else:
- qty = req_qty_each * flt(self.fg_completed_qty)
-
- elif backflushed_materials.get(item.item_code):
- precision = frappe.get_precision("Stock Entry Detail", "qty")
- for d in backflushed_materials.get(item.item_code):
- if d.get(item.warehouse) > 0:
- if qty > req_qty:
- qty = (
- (flt(qty, precision) - flt(d.get(item.warehouse), precision))
- / (flt(trans_qty, precision) - flt(produced_qty, precision))
- ) * flt(self.fg_completed_qty)
-
- d[item.warehouse] -= qty
-
+ item = row.item_details
if cint(frappe.get_cached_value("UOM", item.stock_uom, "must_be_whole_number")):
qty = frappe.utils.ceil(qty)
- if qty > 0:
- self.add_to_stock_entry_detail(
- {
- item.item_code: {
- "from_warehouse": item.warehouse,
- "to_warehouse": "",
- "qty": qty,
- "item_name": item.item_name,
- "description": item.description,
- "stock_uom": item.stock_uom,
- "expense_account": item.expense_account,
- "cost_center": item.buying_cost_center,
- "original_item": item.original_item,
- }
- }
- )
+ if row.batch_details:
+ for batch_no, batch_qty in row.batch_details.items():
+ if qty <= 0:
+ continue
+
+ if batch_qty > qty:
+ batch_qty = qty
+
+ item.batch_no = batch_no
+ self.update_item_in_stock_entry_detail(row, item, batch_qty)
+
+ row.batch_details[batch_no] -= batch_qty
+ qty -= batch_qty
+ else:
+ self.update_item_in_stock_entry_detail(row, item, qty)
+
+ def update_item_in_stock_entry_detail(self, row, item, qty):
+ ste_item_details = {
+ "from_warehouse": item.warehouse,
+ "to_warehouse": "",
+ "qty": qty,
+ "item_name": item.item_name,
+ "batch_no": item.batch_no,
+ "description": item.description,
+ "stock_uom": item.stock_uom,
+ "expense_account": item.expense_account,
+ "cost_center": item.buying_cost_center,
+ "original_item": item.original_item,
+ }
+
+ if item.serial_no:
+ ste_item_details["serial_no"] = "\n".join(item.serial_no[0 : cint(qty)])
+
+ self.add_to_stock_entry_detail({item.item_code: ste_item_details})
def get_pending_raw_materials(self, backflush_based_on=None):
"""
@@ -2521,3 +2461,80 @@
)
return supplied_item_details
+
+
+def get_available_materials(work_order) -> dict:
+ data = get_stock_entry_data(work_order)
+
+ available_materials = {}
+ for row in data:
+ key = (row.item_code, row.warehouse)
+ if row.purpose != "Material Transfer for Manufacture":
+ key = (row.item_code, row.s_warehouse)
+
+ if key not in available_materials:
+ available_materials.setdefault(
+ key,
+ frappe._dict(
+ {"item_details": row, "batch_details": defaultdict(float), "qty": 0, "serial_nos": []}
+ ),
+ )
+
+ item_data = available_materials[key]
+
+ if row.purpose == "Material Transfer for Manufacture":
+ item_data.qty += row.qty
+ if row.batch_no:
+ item_data.batch_details[row.batch_no] += row.qty
+
+ if row.serial_no:
+ item_data.serial_nos.extend(get_serial_nos(row.serial_no))
+ else:
+ # Consume raw material qty in case of 'Manufacture' or 'Material Consumption for Manufacture'
+
+ item_data.qty -= row.qty
+ if row.batch_no:
+ item_data.batch_details[row.batch_no] -= row.qty
+
+ if row.serial_no:
+ for serial_no in get_serial_nos(row.serial_no):
+ item_data.serial_nos.remove(serial_no)
+
+ return available_materials
+
+
+def get_stock_entry_data(work_order):
+ stock_entry = frappe.qb.DocType("Stock Entry")
+ stock_entry_detail = frappe.qb.DocType("Stock Entry Detail")
+
+ return (
+ frappe.qb.from_(stock_entry)
+ .from_(stock_entry_detail)
+ .select(
+ stock_entry_detail.item_name,
+ stock_entry_detail.original_item,
+ stock_entry_detail.item_code,
+ stock_entry_detail.qty,
+ (stock_entry_detail.t_warehouse).as_("warehouse"),
+ (stock_entry_detail.s_warehouse).as_("s_warehouse"),
+ stock_entry_detail.description,
+ stock_entry_detail.stock_uom,
+ stock_entry_detail.expense_account,
+ stock_entry_detail.cost_center,
+ stock_entry_detail.batch_no,
+ stock_entry_detail.serial_no,
+ stock_entry.purpose,
+ )
+ .where(
+ (stock_entry.name == stock_entry_detail.parent)
+ & (stock_entry.work_order == work_order)
+ & (stock_entry.docstatus == 1)
+ & (stock_entry_detail.s_warehouse.isnotnull())
+ & (
+ stock_entry.purpose.isin(
+ ["Manufacture", "Material Consumption for Manufacture", "Material Transfer for Manufacture"]
+ )
+ )
+ )
+ .orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx)
+ ).run(as_dict=1, debug=1)