mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-03 09:44:39 +00:00
fix(manufacturing): sum semi-FG qty across split job cards
update_semi_finished_good_details assigned the current job card's
manufactured_qty to Work Order.produced_qty instead of accumulating it,
so a second job card on the same operation overwrote the first. Nothing
corrected it afterwards because StatusService.update_work_order_qty
returns early for track_semi_finished_goods work orders, leaving the
work order stuck below its planned qty with no way to progress.
Aggregate manufactured_qty and completed_qty over the operation's
submitted job cards instead.
(cherry picked from commit 5548f0726a)
This commit is contained in:
@@ -981,18 +981,32 @@ class JobCard(Document):
|
||||
self.update_work_order_data(for_quantity, process_loss_qty, pending_qty, time_in_mins, wo)
|
||||
|
||||
def update_semi_finished_good_details(self):
|
||||
if self.operation_id:
|
||||
qty = max(flt(self.manufactured_qty), flt(self.total_completed_qty))
|
||||
if not self.operation_id:
|
||||
return
|
||||
|
||||
frappe.db.set_value("Work Order Operation", self.operation_id, "completed_qty", qty)
|
||||
if (
|
||||
self.finished_good
|
||||
and frappe.get_cached_value("Work Order", self.work_order, "production_item")
|
||||
== self.finished_good
|
||||
):
|
||||
_wo_doc = frappe.get_doc("Work Order", self.work_order)
|
||||
_wo_doc.db_set("produced_qty", self.manufactured_qty)
|
||||
_wo_doc.db_set("status", _wo_doc.get_status())
|
||||
job_cards = frappe.get_all(
|
||||
"Job Card",
|
||||
filters={
|
||||
"work_order": self.work_order,
|
||||
"operation_id": self.operation_id,
|
||||
"docstatus": 1,
|
||||
},
|
||||
fields=["manufactured_qty", "total_completed_qty"],
|
||||
)
|
||||
|
||||
completed_qty = sum(
|
||||
max(flt(row.manufactured_qty), flt(row.total_completed_qty)) for row in job_cards
|
||||
)
|
||||
|
||||
frappe.db.set_value("Work Order Operation", self.operation_id, "completed_qty", completed_qty)
|
||||
if (
|
||||
self.finished_good
|
||||
and frappe.get_cached_value("Work Order", self.work_order, "production_item")
|
||||
== self.finished_good
|
||||
):
|
||||
_wo_doc = frappe.get_doc("Work Order", self.work_order)
|
||||
_wo_doc.db_set("produced_qty", sum(flt(row.manufactured_qty) for row in job_cards))
|
||||
_wo_doc.db_set("status", _wo_doc.get_status())
|
||||
|
||||
def update_corrective_in_work_order(self, wo):
|
||||
wo.corrective_operation_cost = 0.0
|
||||
|
||||
@@ -1162,6 +1162,109 @@ class TestJobCard(ERPNextTestSuite):
|
||||
self.assertEqual(manufacturing_entry.items[2].qty, 9)
|
||||
self.assertEqual(flt(manufacturing_entry.items[2].basic_rate, 3), 5.278)
|
||||
|
||||
def test_semi_fg_produced_qty_across_split_job_cards(self):
|
||||
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
||||
from erpnext.manufacturing.doctype.work_order.mapper import make_job_card
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
warehouse = "Stores - _TC"
|
||||
rm = make_item("Split JC RM 1", {"is_stock_item": 1}).name
|
||||
fg = make_item("Split JC FG 1", {"is_stock_item": 1}).name
|
||||
|
||||
fg_bom = frappe.new_doc(
|
||||
"BOM",
|
||||
company="_Test Company",
|
||||
item=fg,
|
||||
quantity=1,
|
||||
with_operations=1,
|
||||
track_semi_finished_goods=1,
|
||||
)
|
||||
fg_bom.append("items", {"item_code": rm, "qty": 1, "operation_row_id": 1})
|
||||
|
||||
operation = {
|
||||
"operation": "Split JC Op A",
|
||||
"workstation": "_Test Workstation A",
|
||||
"finished_good": fg,
|
||||
"finished_good_qty": 1,
|
||||
"is_final_finished_good": 1,
|
||||
"sequence_id": 1,
|
||||
"time_in_mins": 60,
|
||||
"source_warehouse": warehouse,
|
||||
"fg_warehouse": warehouse,
|
||||
"skip_material_transfer": 1,
|
||||
}
|
||||
make_workstation(operation)
|
||||
make_operation(operation)
|
||||
fg_bom.append("operations", operation)
|
||||
fg_bom.insert()
|
||||
fg_bom.submit()
|
||||
|
||||
work_order = make_wo_order_test_record(
|
||||
item=fg,
|
||||
qty=8,
|
||||
source_warehouse=warehouse,
|
||||
fg_warehouse=warehouse,
|
||||
bom_no=fg_bom.name,
|
||||
skip_transfer=1,
|
||||
do_not_save=True,
|
||||
)
|
||||
work_order.operations[0].time_in_mins = 60
|
||||
work_order.save()
|
||||
work_order.submit()
|
||||
|
||||
make_stock_entry(item_code=rm, target=warehouse, qty=100, basic_rate=100)
|
||||
|
||||
job_card = frappe.get_doc(
|
||||
"Job Card", frappe.db.get_value("Job Card", {"work_order": work_order.name}, "name")
|
||||
)
|
||||
job_card.for_quantity = 5
|
||||
job_card.append(
|
||||
"time_logs",
|
||||
{"from_time": "2024-02-01 08:00:00", "to_time": "2024-02-01 09:00:00", "completed_qty": 5},
|
||||
)
|
||||
job_card.save()
|
||||
job_card.submit()
|
||||
frappe.get_doc(job_card.make_stock_entry_for_semi_fg_item()).submit()
|
||||
|
||||
work_order.reload()
|
||||
self.assertEqual(flt(work_order.produced_qty), 5)
|
||||
|
||||
make_job_card(
|
||||
work_order.name,
|
||||
[
|
||||
{
|
||||
"name": work_order.operations[0].name,
|
||||
"operation": "Split JC Op A",
|
||||
"qty": 3,
|
||||
"pending_qty": 3,
|
||||
"skip_material_transfer": 1,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
job_card = frappe.get_doc(
|
||||
"Job Card", frappe.db.get_value("Job Card", {"work_order": work_order.name, "docstatus": 0})
|
||||
)
|
||||
job_card.append(
|
||||
"time_logs",
|
||||
{
|
||||
"from_time": "2024-02-02 08:00:00",
|
||||
"to_time": "2024-02-02 09:00:00",
|
||||
"completed_qty": job_card.for_quantity,
|
||||
},
|
||||
)
|
||||
job_card.save()
|
||||
job_card.submit()
|
||||
frappe.get_doc(job_card.make_stock_entry_for_semi_fg_item()).submit()
|
||||
|
||||
work_order.reload()
|
||||
self.assertEqual(flt(work_order.produced_qty), 8)
|
||||
self.assertEqual(work_order.status, "Completed")
|
||||
self.assertEqual(
|
||||
flt(frappe.db.get_value("Work Order Operation", work_order.operations[0].name, "completed_qty")),
|
||||
8,
|
||||
)
|
||||
|
||||
def test_semi_fg_batch_auto_pull_on_manufacture(self):
|
||||
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
Reference in New Issue
Block a user