fix: do not create repeat work orders
(cherry picked from commit 384f4e120a)
# Conflicts:
# erpnext/manufacturing/doctype/production_plan/production_plan.js
# erpnext/manufacturing/doctype/production_plan/test_production_plan.py
# erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
This commit is contained in:
committed by
Mergify
parent
7348778220
commit
795108c1dd
@@ -116,7 +116,9 @@ frappe.ui.form.on("Production Plan", {
|
||||
);
|
||||
}
|
||||
|
||||
if (frm.doc.po_items && frm.doc.status !== "Closed") {
|
||||
let items = frm.events.get_items_for_work_order(frm);
|
||||
|
||||
if (items?.length && frm.doc.status !== "Closed") {
|
||||
frm.add_custom_button(
|
||||
__("Work Order / Subcontract PO"),
|
||||
() => {
|
||||
@@ -193,6 +195,93 @@ frappe.ui.form.on("Production Plan", {
|
||||
set_field_options("projected_qty_formula", projected_qty_formula);
|
||||
},
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
get_items_for_work_order(frm) {
|
||||
let items = frm.doc.po_items;
|
||||
if (frm.doc.sub_assembly_items?.length) {
|
||||
items = [...items, ...frm.doc.sub_assembly_items];
|
||||
}
|
||||
|
||||
let has_items =
|
||||
items.filter((item) => {
|
||||
if (item.pending_qty) {
|
||||
return item.pending_qty > item.ordered_qty;
|
||||
} else {
|
||||
return item.qty > (item.received_qty || item.ordered_qty);
|
||||
}
|
||||
}) || [];
|
||||
|
||||
return has_items;
|
||||
},
|
||||
|
||||
has_unreserved_stock(frm, table, qty_field = "required_qty") {
|
||||
let has_unreserved_stock = frm.doc[table].some(
|
||||
(item) => flt(item[qty_field]) > flt(item.stock_reserved_qty)
|
||||
);
|
||||
|
||||
return has_unreserved_stock;
|
||||
},
|
||||
|
||||
has_reserved_stock(frm, table) {
|
||||
let has_reserved_stock = frm.doc[table].some((item) => flt(item.stock_reserved_qty) > 0);
|
||||
|
||||
return has_reserved_stock;
|
||||
},
|
||||
|
||||
setup_stock_reservation_for_sub_assembly(frm) {
|
||||
if (frm.doc.docstatus === 1 && frm.doc.reserve_stock) {
|
||||
if (frm.events.has_unreserved_stock(frm, "sub_assembly_items")) {
|
||||
frm.add_custom_button(
|
||||
__("Reserve for Sub-assembly"),
|
||||
() => erpnext.stock_reservation.make_entries(frm, "sub_assembly_items"),
|
||||
__("Stock Reservation")
|
||||
);
|
||||
}
|
||||
|
||||
if (frm.events.has_reserved_stock(frm, "sub_assembly_items")) {
|
||||
frm.add_custom_button(
|
||||
__("Unreserve for Sub-assembly"),
|
||||
() => erpnext.stock_reservation.unreserve_stock(frm),
|
||||
__("Stock Reservation")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Reserved Stock for Sub-assembly"),
|
||||
() => erpnext.stock_reservation.show_reserved_stock(frm, "sub_assembly_items"),
|
||||
__("Stock Reservation")
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setup_stock_reservation_for_raw_materials(frm) {
|
||||
if (frm.doc.docstatus === 1 && frm.doc.reserve_stock) {
|
||||
if (frm.events.has_unreserved_stock(frm, "mr_items", "required_bom_qty")) {
|
||||
frm.add_custom_button(
|
||||
__("Reserve for Raw Materials"),
|
||||
() => erpnext.stock_reservation.make_entries(frm, "mr_items"),
|
||||
__("Stock Reservation")
|
||||
);
|
||||
}
|
||||
|
||||
if (frm.events.has_reserved_stock(frm, "mr_items")) {
|
||||
frm.add_custom_button(
|
||||
__("Unreserve for Raw Materials"),
|
||||
() => erpnext.stock_reservation.unreserve_stock(frm),
|
||||
__("Stock Reservation")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Reserved Stock for Raw Materials"),
|
||||
() => erpnext.stock_reservation.show_reserved_stock(frm, "mr_items"),
|
||||
__("Stock Reservation")
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
>>>>>>> 384f4e120a (fix: do not create repeat work orders)
|
||||
close_open_production_plan(frm, close = false) {
|
||||
frappe.call({
|
||||
method: "set_status",
|
||||
|
||||
@@ -751,7 +751,14 @@ class ProductionPlan(Document):
|
||||
"company": self.get("company"),
|
||||
}
|
||||
|
||||
if flt(row.qty) <= flt(row.ordered_qty):
|
||||
continue
|
||||
|
||||
self.prepare_data_for_sub_assembly_items(row, work_order_data)
|
||||
|
||||
if work_order_data.get("qty") <= 0:
|
||||
continue
|
||||
|
||||
work_order = self.create_work_order(work_order_data)
|
||||
if work_order:
|
||||
wo_list.append(work_order)
|
||||
@@ -771,6 +778,8 @@ class ProductionPlan(Document):
|
||||
if row.get(field):
|
||||
wo_data[field] = row.get(field)
|
||||
|
||||
wo_data["qty"] = flt(row.get("qty")) - flt(row.get("ordered_qty"))
|
||||
|
||||
wo_data.update(
|
||||
{
|
||||
"use_multi_level_bom": 0,
|
||||
|
||||
@@ -1693,6 +1693,477 @@ class TestProductionPlan(FrappeTestCase):
|
||||
self.assertEqual(mr_items[0].get("quantity"), 80)
|
||||
self.assertEqual(mr_items[1].get("quantity"), 70)
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
def test_stock_reservation_against_production_plan(self):
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 1)
|
||||
|
||||
bom_tree = {
|
||||
"Finished Good For SR": {
|
||||
"Sub Assembly For SR 1": {"Raw Material For SR 1": {}},
|
||||
"Sub Assembly For SR 2": {"Raw Material For SR 2": {}},
|
||||
"Sub Assembly For SR 3": {"Raw Material For SR 3": {}},
|
||||
}
|
||||
}
|
||||
parent_bom = create_nested_bom(bom_tree, prefix="")
|
||||
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
for item_code in [
|
||||
"Sub Assembly For SR 1",
|
||||
"Sub Assembly For SR 2",
|
||||
"Sub Assembly For SR 3",
|
||||
"Raw Material For SR 1",
|
||||
"Raw Material For SR 2",
|
||||
"Raw Material For SR 3",
|
||||
]:
|
||||
make_stock_entry(item_code=item_code, target=warehouse, qty=5, basic_rate=100)
|
||||
|
||||
plan = create_production_plan(
|
||||
item_code=parent_bom.item,
|
||||
planned_qty=15,
|
||||
skip_available_sub_assembly_item=1,
|
||||
ignore_existing_ordered_qty=1,
|
||||
do_not_submit=1,
|
||||
warehouse=warehouse,
|
||||
sub_assembly_warehouse=warehouse,
|
||||
for_warehouse=warehouse,
|
||||
reserve_stock=1,
|
||||
)
|
||||
|
||||
plan.get_sub_assembly_items()
|
||||
plan.set("mr_items", [])
|
||||
mr_items = get_items_for_material_requests(plan.as_dict())
|
||||
for d in mr_items:
|
||||
plan.append("mr_items", d)
|
||||
|
||||
plan.save()
|
||||
|
||||
self.assertTrue(len(plan.sub_assembly_items) == 3)
|
||||
for row in plan.sub_assembly_items:
|
||||
self.assertEqual(row.required_qty, 15.0)
|
||||
self.assertEqual(row.qty, 10.0)
|
||||
|
||||
self.assertTrue(len(plan.mr_items) == 3)
|
||||
for row in plan.mr_items:
|
||||
self.assertEqual(row.required_bom_qty, 10.0)
|
||||
self.assertEqual(row.quantity, 5.0)
|
||||
|
||||
plan.submit()
|
||||
|
||||
sre = StockReservation(plan)
|
||||
reserved_entries = sre.get_reserved_entries("Production Plan", plan.name)
|
||||
self.assertTrue(len(reserved_entries) == 6)
|
||||
|
||||
for row in reserved_entries:
|
||||
self.assertEqual(row.reserved_qty, 5.0)
|
||||
|
||||
plan.submit_material_request = 1
|
||||
plan.make_material_request()
|
||||
plan.make_work_order()
|
||||
|
||||
material_requests = frappe.get_all(
|
||||
"Material Request", filters={"production_plan": plan.name}, pluck="name"
|
||||
)
|
||||
|
||||
self.assertTrue(len(material_requests) > 0)
|
||||
for mr_name in list(set(material_requests)):
|
||||
po = make_purchase_order(mr_name)
|
||||
po.supplier = "_Test Supplier"
|
||||
po.submit()
|
||||
|
||||
pr = make_purchase_receipt(po.name)
|
||||
pr.submit()
|
||||
|
||||
sre = StockReservation(plan)
|
||||
reserved_entries = sre.get_reserved_entries("Production Plan", plan.name)
|
||||
self.assertTrue(len(reserved_entries) == 9)
|
||||
|
||||
work_orders = frappe.get_all("Work Order", filters={"production_plan": plan.name}, pluck="name")
|
||||
for wo_name in list(set(work_orders)):
|
||||
wo_doc = frappe.get_doc("Work Order", wo_name)
|
||||
self.assertEqual(wo_doc.reserve_stock, 1)
|
||||
|
||||
wo_doc.source_warehouse = warehouse
|
||||
wo_doc.wip_warehouse = warehouse
|
||||
wo_doc.fg_warehouse = warehouse
|
||||
wo_doc.submit()
|
||||
|
||||
sre = StockReservation(wo_doc)
|
||||
reserved_entries = sre.get_reserved_entries("Work Order", wo_doc.name)
|
||||
if wo_doc.production_item == "Finished Good For SR":
|
||||
self.assertEqual(len(reserved_entries), 3)
|
||||
else:
|
||||
# For raw materials 2 stock reservation entries
|
||||
# 5 qty was present already in stock and 5 added from new PO
|
||||
self.assertEqual(len(reserved_entries), 2)
|
||||
|
||||
sre = StockReservation(plan)
|
||||
reserved_entries = sre.get_reserved_entries("Production Plan", plan.name)
|
||||
self.assertTrue(len(reserved_entries) == 0)
|
||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 0)
|
||||
|
||||
def test_stock_reservation_of_serial_nos_against_production_plan(self):
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 1)
|
||||
|
||||
bom_tree = {
|
||||
"Finished Good For SR": {
|
||||
"SN Sub Assembly For SR 1": {"SN Raw Material For SR 1": {}},
|
||||
"SN Sub Assembly For SR 2": {"SN Raw Material For SR 2": {}},
|
||||
"SN Sub Assembly For SR 3": {"SN Raw Material For SR 3": {}},
|
||||
}
|
||||
}
|
||||
parent_bom = create_nested_bom(bom_tree, prefix="")
|
||||
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
for item_code in [
|
||||
"SN Sub Assembly For SR 1",
|
||||
"SN Sub Assembly For SR 2",
|
||||
"SN Sub Assembly For SR 3",
|
||||
"SN Raw Material For SR 1",
|
||||
"SN Raw Material For SR 2",
|
||||
"SN Raw Material For SR 3",
|
||||
]:
|
||||
doc = frappe.get_doc("Item", item_code)
|
||||
doc.has_serial_no = 1
|
||||
doc.serial_no_series = f"SNN-{item_code}.-.#####"
|
||||
doc.save()
|
||||
|
||||
make_stock_entry(item_code=item_code, target=warehouse, qty=5, basic_rate=100)
|
||||
|
||||
plan = create_production_plan(
|
||||
item_code=parent_bom.item,
|
||||
planned_qty=15,
|
||||
skip_available_sub_assembly_item=1,
|
||||
ignore_existing_ordered_qty=1,
|
||||
do_not_submit=1,
|
||||
warehouse=warehouse,
|
||||
sub_assembly_warehouse=warehouse,
|
||||
for_warehouse=warehouse,
|
||||
reserve_stock=1,
|
||||
)
|
||||
|
||||
plan.get_sub_assembly_items()
|
||||
plan.set("mr_items", [])
|
||||
mr_items = get_items_for_material_requests(plan.as_dict())
|
||||
for d in mr_items:
|
||||
plan.append("mr_items", d)
|
||||
|
||||
plan.save()
|
||||
|
||||
self.assertTrue(len(plan.sub_assembly_items) == 3)
|
||||
for row in plan.sub_assembly_items:
|
||||
self.assertEqual(row.required_qty, 15.0)
|
||||
self.assertEqual(row.qty, 10.0)
|
||||
|
||||
self.assertTrue(len(plan.mr_items) == 3)
|
||||
for row in plan.mr_items:
|
||||
self.assertEqual(row.required_bom_qty, 10.0)
|
||||
self.assertEqual(row.quantity, 5.0)
|
||||
|
||||
plan.submit()
|
||||
|
||||
sre = StockReservation(plan)
|
||||
reserved_entries = sre.get_reserved_entries("Production Plan", plan.name)
|
||||
self.assertTrue(len(reserved_entries) == 6)
|
||||
|
||||
for row in reserved_entries:
|
||||
self.assertEqual(row.reserved_qty, 5.0)
|
||||
|
||||
plan.submit_material_request = 1
|
||||
plan.make_material_request()
|
||||
plan.make_work_order()
|
||||
|
||||
material_requests = frappe.get_all(
|
||||
"Material Request", filters={"production_plan": plan.name}, pluck="name"
|
||||
)
|
||||
|
||||
additional_serial_nos = []
|
||||
|
||||
for item_code in [
|
||||
"SN Sub Assembly For SR 1",
|
||||
"SN Sub Assembly For SR 2",
|
||||
"SN Sub Assembly For SR 3",
|
||||
"SN Raw Material For SR 1",
|
||||
"SN Raw Material For SR 2",
|
||||
"SN Raw Material For SR 3",
|
||||
]:
|
||||
se = make_stock_entry(item_code=item_code, target=warehouse, qty=5, basic_rate=100)
|
||||
additional_serial_nos.extend(get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle))
|
||||
|
||||
self.assertTrue(additional_serial_nos)
|
||||
|
||||
self.assertTrue(len(material_requests) > 0)
|
||||
for mr_name in list(set(material_requests)):
|
||||
po = make_purchase_order(mr_name)
|
||||
po.supplier = "_Test Supplier"
|
||||
po.submit()
|
||||
|
||||
pr = make_purchase_receipt(po.name)
|
||||
pr.submit()
|
||||
|
||||
sre = StockReservation(plan)
|
||||
reserved_entries = sre.get_reserved_entries("Production Plan", plan.name)
|
||||
self.assertTrue(len(reserved_entries) == 9)
|
||||
serial_nos_res_for_pp = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": ("in", [x.name for x in reserved_entries]), "docstatus": 1},
|
||||
pluck="serial_no",
|
||||
)
|
||||
|
||||
work_orders = frappe.get_all("Work Order", filters={"production_plan": plan.name}, pluck="name")
|
||||
for wo_name in list(set(work_orders)):
|
||||
wo_doc = frappe.get_doc("Work Order", wo_name)
|
||||
self.assertEqual(wo_doc.reserve_stock, 1)
|
||||
|
||||
wo_doc.source_warehouse = warehouse
|
||||
wo_doc.wip_warehouse = warehouse
|
||||
wo_doc.fg_warehouse = warehouse
|
||||
wo_doc.submit()
|
||||
|
||||
sre = StockReservation(wo_doc)
|
||||
reserved_entries = sre.get_reserved_entries("Work Order", wo_doc.name)
|
||||
serial_nos_res_for_wo = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": ("in", [x.name for x in reserved_entries]), "docstatus": 1},
|
||||
pluck="serial_no",
|
||||
)
|
||||
|
||||
for serial_no in serial_nos_res_for_wo:
|
||||
self.assertTrue(serial_no in serial_nos_res_for_pp)
|
||||
self.assertFalse(serial_no in additional_serial_nos)
|
||||
|
||||
if wo_doc.production_item == "Finished Good For SR":
|
||||
self.assertEqual(len(reserved_entries), 3)
|
||||
else:
|
||||
# For raw materials 2 stock reservation entries
|
||||
# 5 qty was present already in stock and 5 added from new PO
|
||||
self.assertEqual(len(reserved_entries), 2)
|
||||
|
||||
sre = StockReservation(plan)
|
||||
reserved_entries = sre.get_reserved_entries("Production Plan", plan.name)
|
||||
self.assertTrue(len(reserved_entries) == 0)
|
||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 0)
|
||||
|
||||
def test_stock_reservation_of_batch_nos_against_production_plan(self):
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 1)
|
||||
|
||||
bom_tree = {
|
||||
"Finished Good For SR": {
|
||||
"Batch Sub Assembly For SR 1": {"Batch Raw Material For SR 1": {}},
|
||||
"Batch Sub Assembly For SR 2": {"Batch Raw Material For SR 2": {}},
|
||||
"Batch Sub Assembly For SR 3": {"Batch Raw Material For SR 3": {}},
|
||||
}
|
||||
}
|
||||
parent_bom = create_nested_bom(bom_tree, prefix="")
|
||||
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
for item_code in [
|
||||
"Batch Sub Assembly For SR 1",
|
||||
"Batch Sub Assembly For SR 2",
|
||||
"Batch Sub Assembly For SR 3",
|
||||
"Batch Raw Material For SR 1",
|
||||
"Batch Raw Material For SR 2",
|
||||
"Batch Raw Material For SR 3",
|
||||
]:
|
||||
doc = frappe.get_doc("Item", item_code)
|
||||
doc.has_batch_no = 1
|
||||
doc.create_new_batch = 1
|
||||
doc.batch_number_series = f"BCH-{item_code}.-.#####"
|
||||
doc.save()
|
||||
|
||||
make_stock_entry(item_code=item_code, target=warehouse, qty=5, basic_rate=100)
|
||||
|
||||
plan = create_production_plan(
|
||||
item_code=parent_bom.item,
|
||||
planned_qty=15,
|
||||
skip_available_sub_assembly_item=1,
|
||||
ignore_existing_ordered_qty=1,
|
||||
do_not_submit=1,
|
||||
warehouse=warehouse,
|
||||
sub_assembly_warehouse=warehouse,
|
||||
for_warehouse=warehouse,
|
||||
reserve_stock=1,
|
||||
)
|
||||
|
||||
plan.get_sub_assembly_items()
|
||||
plan.set("mr_items", [])
|
||||
mr_items = get_items_for_material_requests(plan.as_dict())
|
||||
for d in mr_items:
|
||||
plan.append("mr_items", d)
|
||||
|
||||
plan.save()
|
||||
|
||||
self.assertTrue(len(plan.sub_assembly_items) == 3)
|
||||
for row in plan.sub_assembly_items:
|
||||
self.assertEqual(row.required_qty, 15.0)
|
||||
self.assertEqual(row.qty, 10.0)
|
||||
|
||||
self.assertTrue(len(plan.mr_items) == 3)
|
||||
for row in plan.mr_items:
|
||||
self.assertEqual(row.required_bom_qty, 10.0)
|
||||
self.assertEqual(row.quantity, 5.0)
|
||||
|
||||
plan.submit()
|
||||
|
||||
sre = StockReservation(plan)
|
||||
reserved_entries = sre.get_reserved_entries("Production Plan", plan.name)
|
||||
self.assertTrue(len(reserved_entries) == 6)
|
||||
|
||||
for row in reserved_entries:
|
||||
self.assertEqual(row.reserved_qty, 5.0)
|
||||
|
||||
plan.submit_material_request = 1
|
||||
plan.make_material_request()
|
||||
plan.make_work_order()
|
||||
|
||||
material_requests = frappe.get_all(
|
||||
"Material Request", filters={"production_plan": plan.name}, pluck="name"
|
||||
)
|
||||
|
||||
additional_batches = []
|
||||
|
||||
for item_code in [
|
||||
"Batch Sub Assembly For SR 1",
|
||||
"Batch Sub Assembly For SR 2",
|
||||
"Batch Sub Assembly For SR 3",
|
||||
"Batch Raw Material For SR 1",
|
||||
"Batch Raw Material For SR 2",
|
||||
"Batch Raw Material For SR 3",
|
||||
]:
|
||||
se = make_stock_entry(item_code=item_code, target=warehouse, qty=5, basic_rate=100)
|
||||
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
additional_batches.append(batch_no)
|
||||
|
||||
self.assertTrue(additional_batches)
|
||||
|
||||
self.assertTrue(len(material_requests) > 0)
|
||||
for mr_name in list(set(material_requests)):
|
||||
po = make_purchase_order(mr_name)
|
||||
po.supplier = "_Test Supplier"
|
||||
po.submit()
|
||||
|
||||
pr = make_purchase_receipt(po.name)
|
||||
pr.submit()
|
||||
|
||||
sre = StockReservation(plan)
|
||||
reserved_entries = sre.get_reserved_entries("Production Plan", plan.name)
|
||||
self.assertTrue(len(reserved_entries) == 9)
|
||||
batches_reserved_for_pp = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": ("in", [x.name for x in reserved_entries]), "docstatus": 1},
|
||||
pluck="batch_no",
|
||||
)
|
||||
|
||||
work_orders = frappe.get_all("Work Order", filters={"production_plan": plan.name}, pluck="name")
|
||||
for wo_name in list(set(work_orders)):
|
||||
wo_doc = frappe.get_doc("Work Order", wo_name)
|
||||
self.assertEqual(wo_doc.reserve_stock, 1)
|
||||
|
||||
wo_doc.source_warehouse = warehouse
|
||||
wo_doc.wip_warehouse = warehouse
|
||||
wo_doc.fg_warehouse = warehouse
|
||||
wo_doc.submit()
|
||||
|
||||
sre = StockReservation(wo_doc)
|
||||
reserved_entries = sre.get_reserved_entries("Work Order", wo_doc.name)
|
||||
batches_reserved_for_wo = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": ("in", [x.name for x in reserved_entries]), "docstatus": 1},
|
||||
pluck="batch_no",
|
||||
)
|
||||
|
||||
for batch_no in batches_reserved_for_wo:
|
||||
self.assertTrue(batch_no in batches_reserved_for_pp)
|
||||
self.assertFalse(batch_no in additional_batches)
|
||||
|
||||
if wo_doc.production_item == "Finished Good For SR":
|
||||
self.assertEqual(len(reserved_entries), 3)
|
||||
else:
|
||||
# For raw materials 2 stock reservation entries
|
||||
# 5 qty was present already in stock and 5 added from new PO
|
||||
self.assertEqual(len(reserved_entries), 2)
|
||||
|
||||
sre = StockReservation(plan)
|
||||
reserved_entries = sre.get_reserved_entries("Production Plan", plan.name)
|
||||
self.assertTrue(len(reserved_entries) == 0)
|
||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 0)
|
||||
|
||||
def test_production_plan_for_partial_sub_assembly_items(self):
|
||||
from erpnext.controllers.status_updater import OverAllowanceError
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
from erpnext.subcontracting.doctype.subcontracting_bom.test_subcontracting_bom import (
|
||||
create_subcontracting_bom,
|
||||
)
|
||||
|
||||
frappe.flags.test_print = False
|
||||
|
||||
fg_wo_item = "Test Motherboard 11"
|
||||
bom_tree_1 = {"Test Laptop 11": {fg_wo_item: {"Test Motherboard Wires 11": {}}}}
|
||||
create_nested_bom(bom_tree_1, prefix="")
|
||||
|
||||
plan = create_production_plan(
|
||||
item_code="Test Laptop 11",
|
||||
planned_qty=10,
|
||||
use_multi_level_bom=1,
|
||||
do_not_submit=True,
|
||||
company="_Test Company",
|
||||
skip_getting_mr_items=True,
|
||||
)
|
||||
plan.get_sub_assembly_items()
|
||||
plan.submit()
|
||||
plan.make_work_order()
|
||||
|
||||
work_order = frappe.db.get_value("Work Order", {"production_plan": plan.name, "docstatus": 0}, "name")
|
||||
wo_doc = frappe.get_doc("Work Order", work_order)
|
||||
|
||||
wo_doc.qty = 5.0
|
||||
wo_doc.skip_transfer = 1
|
||||
wo_doc.from_wip_warehouse = 1
|
||||
wo_doc.wip_warehouse = "_Test Warehouse - _TC"
|
||||
wo_doc.fg_warehouse = "_Test Warehouse - _TC"
|
||||
wo_doc.submit()
|
||||
|
||||
plan.reload()
|
||||
|
||||
for row in plan.sub_assembly_items:
|
||||
self.assertEqual(row.ordered_qty, 5.0)
|
||||
|
||||
plan.make_work_order()
|
||||
|
||||
work_order = frappe.db.get_value("Work Order", {"production_plan": plan.name, "docstatus": 0}, "name")
|
||||
wo_doc = frappe.get_doc("Work Order", work_order)
|
||||
self.assertEqual(wo_doc.qty, 5.0)
|
||||
|
||||
wo_doc.skip_transfer = 1
|
||||
wo_doc.from_wip_warehouse = 1
|
||||
wo_doc.wip_warehouse = "_Test Warehouse - _TC"
|
||||
wo_doc.fg_warehouse = "_Test Warehouse - _TC"
|
||||
wo_doc.submit()
|
||||
|
||||
plan.reload()
|
||||
|
||||
for row in plan.sub_assembly_items:
|
||||
self.assertEqual(row.ordered_qty, 10.0)
|
||||
|
||||
>>>>>>> 384f4e120a (fix: do not create repeat work orders)
|
||||
|
||||
def create_production_plan(**args):
|
||||
"""
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"purchase_order",
|
||||
"production_plan_item",
|
||||
"column_break_7",
|
||||
"ordered_qty",
|
||||
"received_qty",
|
||||
"indent",
|
||||
"section_break_19",
|
||||
@@ -204,12 +205,46 @@
|
||||
"fieldtype": "Float",
|
||||
"label": "Produced Qty",
|
||||
"read_only": 1
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "required_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Required Qty"
|
||||
},
|
||||
{
|
||||
"fieldname": "subcontracting_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Subcontracting"
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_reserved_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Stock Reserved Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "ordered_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Ordered Qty",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
>>>>>>> 384f4e120a (fix: do not create repeat work orders)
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
<<<<<<< HEAD
|
||||
"modified": "2024-02-27 13:45:17.422435",
|
||||
=======
|
||||
"modified": "2025-06-10 13:36:24.759101",
|
||||
>>>>>>> 384f4e120a (fix: do not create repeat work orders)
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Plan Sub Assembly Item",
|
||||
|
||||
@@ -22,6 +22,7 @@ class ProductionPlanSubAssemblyItem(Document):
|
||||
fg_warehouse: DF.Link | None
|
||||
indent: DF.Int
|
||||
item_name: DF.Data | None
|
||||
ordered_qty: DF.Float
|
||||
parent: DF.Data
|
||||
parent_item_code: DF.Link | None
|
||||
parentfield: DF.Data
|
||||
|
||||
@@ -762,22 +762,34 @@ class WorkOrder(Document):
|
||||
)
|
||||
|
||||
def update_ordered_qty(self):
|
||||
if self.production_plan and self.production_plan_item and not self.production_plan_sub_assembly_item:
|
||||
if self.production_plan and (self.production_plan_item or self.production_plan_sub_assembly_item):
|
||||
table = frappe.qb.DocType("Work Order")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.select(Sum(table.qty))
|
||||
.where(
|
||||
(table.production_plan == self.production_plan)
|
||||
& (table.production_plan_item == self.production_plan_item)
|
||||
& (table.docstatus == 1)
|
||||
)
|
||||
).run()
|
||||
.where((table.production_plan == self.production_plan) & (table.docstatus == 1))
|
||||
)
|
||||
|
||||
if self.production_plan_item:
|
||||
query = query.where(table.production_plan_item == self.production_plan_item)
|
||||
elif self.production_plan_sub_assembly_item:
|
||||
query = query.where(
|
||||
table.production_plan_sub_assembly_item == self.production_plan_sub_assembly_item
|
||||
)
|
||||
|
||||
query = query.run()
|
||||
qty = flt(query[0][0]) if query else 0
|
||||
|
||||
frappe.db.set_value("Production Plan Item", self.production_plan_item, "ordered_qty", qty)
|
||||
if self.production_plan_item:
|
||||
frappe.db.set_value("Production Plan Item", self.production_plan_item, "ordered_qty", qty)
|
||||
elif self.production_plan_sub_assembly_item:
|
||||
frappe.db.set_value(
|
||||
"Production Plan Sub Assembly Item",
|
||||
self.production_plan_sub_assembly_item,
|
||||
"ordered_qty",
|
||||
qty,
|
||||
)
|
||||
|
||||
doc = frappe.get_doc("Production Plan", self.production_plan)
|
||||
doc.set_status()
|
||||
|
||||
Reference in New Issue
Block a user