Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reserved production plan sub assembly items #37884

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"prod_plan_references",
"section_break_24",
"combine_sub_items",
"sub_assembly_warehouse",
"section_break_ucc4",
"skip_available_sub_assembly_item",
"column_break_igxl",
Expand Down Expand Up @@ -416,13 +417,19 @@
{
"fieldname": "column_break_igxl",
"fieldtype": "Column Break"
},
{
"fieldname": "sub_assembly_warehouse",
"fieldtype": "Link",
"label": "Sub Assembly Warehouse",
"options": "Warehouse"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-09-29 11:41:03.246059",
"modified": "2023-11-03 14:08:11.928027",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,12 @@ def update_bin_qty(self):
bin = frappe.get_doc("Bin", bin_name, for_update=True)
bin.update_reserved_qty_for_production_plan()

for d in self.sub_assembly_items:
if d.fg_warehouse and d.type_of_manufacturing == "In House":
bin_name = get_or_make_bin(d.production_item, d.fg_warehouse)
bin = frappe.get_doc("Bin", bin_name, for_update=True)
bin.update_reserved_qty_for_for_sub_assembly()

def delete_draft_work_order(self):
for d in frappe.get_all(
"Work Order", fields=["name"], filters={"docstatus": 0, "production_plan": ("=", self.name)}
Expand Down Expand Up @@ -809,7 +815,11 @@ def get_sub_assembly_items(self, manufacturing_type=None):

bom_data = []

warehouse = row.warehouse if self.skip_available_sub_assembly_item else None
warehouse = (
(self.sub_assembly_warehouse or row.warehouse)
if self.skip_available_sub_assembly_item
else None
)
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
sub_assembly_items_store.extend(bom_data)
Expand All @@ -831,7 +841,7 @@ def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_typ
for data in bom_data:
data.qty = data.stock_qty
data.production_plan_item = row.name
data.fg_warehouse = row.warehouse
data.fg_warehouse = self.sub_assembly_warehouse or row.warehouse
data.schedule_date = row.planned_start_date
data.type_of_manufacturing = manufacturing_type or (
"Subcontract" if data.is_sub_contracted_item else "In House"
Expand Down Expand Up @@ -1637,8 +1647,8 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):

query = query.run()

if not query:
return 0.0
if not query or query[0][0] is None:
return None

reserved_qty_for_production_plan = flt(query[0][0])

Expand Down Expand Up @@ -1780,3 +1790,29 @@ def sales_order_query(
query = query.offset(start)

return query.run()


def get_reserved_qty_for_sub_assembly(item_code, warehouse):
table = frappe.qb.DocType("Production Plan")
child = frappe.qb.DocType("Production Plan Sub Assembly Item")

query = (
frappe.qb.from_(table)
.inner_join(child)
.on(table.name == child.parent)
.select(Sum(child.qty - IfNull(child.wo_produced_qty, 0)))
.where(
(table.docstatus == 1)
& (child.production_item == item_code)
& (child.fg_warehouse == warehouse)
& (table.status.notin(["Completed", "Closed"]))
)
)

query = query.run()

if not query or query[0][0] is None:
return None

qty = flt(query[0][0])
return qty if qty > 0 else 0.0
Original file line number Diff line number Diff line change
Expand Up @@ -1042,13 +1042,14 @@ def test_resered_qty_for_production_plan_for_material_requests(self):
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))

self.assertEqual(after_qty - before_qty, 1)

pln = frappe.get_doc("Production Plan", pln.name)
pln.cancel()

bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))

pln.reload()
self.assertEqual(pln.docstatus, 2)
self.assertEqual(after_qty, before_qty)

def test_resered_qty_for_production_plan_for_work_order(self):
Expand Down Expand Up @@ -1359,6 +1360,93 @@ def test_mr_qty_for_same_rm_with_different_sub_assemblies(self):
if row.item_code == "ChildPart2 For SUB Test":
self.assertEqual(row.quantity, 2)

def test_reserve_sub_assembly_items(self):
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse

bom_tree = {
"Fininshed Goods Bicycle": {
"Frame Assembly": {"Frame": {}},
"Chain Assembly": {"Chain": {}},
}
}
parent_bom = create_nested_bom(bom_tree, prefix="")

warehouse = "_Test Warehouse - _TC"
company = "_Test Company"

sub_assembly_warehouse = create_warehouse("SUB ASSEMBLY WH", company=company)

for item_code in ["Frame", "Chain"]:
make_stock_entry(item_code=item_code, target=warehouse, qty=2, basic_rate=100)

before_qty = flt(
frappe.db.get_value(
"Bin",
{"item_code": "Frame Assembly", "warehouse": sub_assembly_warehouse},
"reserved_qty_for_production_plan",
)
)

plan = create_production_plan(
item_code=parent_bom.item,
planned_qty=2,
ignore_existing_ordered_qty=1,
do_not_submit=1,
skip_available_sub_assembly_item=1,
warehouse=warehouse,
sub_assembly_warehouse=sub_assembly_warehouse,
)

plan.get_sub_assembly_items()
plan.submit()

after_qty = flt(
frappe.db.get_value(
"Bin",
{"item_code": "Frame Assembly", "warehouse": sub_assembly_warehouse},
"reserved_qty_for_production_plan",
)
)

self.assertEqual(after_qty, before_qty + 2)

plan.make_work_order()
work_orders = frappe.get_all(
"Work Order",
fields=["name", "production_item"],
filters={"production_plan": plan.name},
order_by="creation desc",
)

for d in work_orders:
wo_doc = frappe.get_doc("Work Order", d.name)
wo_doc.skip_transfer = 1
wo_doc.from_wip_warehouse = 1

wo_doc.wip_warehouse = (
warehouse
if d.production_item in ["Frame Assembly", "Chain Assembly"]
else sub_assembly_warehouse
)

wo_doc.submit()

if d.production_item == "Frame Assembly":
self.assertEqual(wo_doc.fg_warehouse, sub_assembly_warehouse)
se_doc = frappe.get_doc(make_se_from_wo(wo_doc.name, "Manufacture", 2))
se_doc.submit()

after_qty = flt(
frappe.db.get_value(
"Bin",
{"item_code": "Frame Assembly", "warehouse": sub_assembly_warehouse},
"reserved_qty_for_production_plan",
)
)

self.assertEqual(after_qty, before_qty)


def create_production_plan(**args):
"""
Expand All @@ -1379,6 +1467,7 @@ def create_production_plan(**args):
"ignore_existing_ordered_qty": args.ignore_existing_ordered_qty or 0,
"get_items_from": "Sales Order",
"skip_available_sub_assembly_item": args.skip_available_sub_assembly_item or 0,
"sub_assembly_warehouse": args.sub_assembly_warehouse,
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@
"type_of_manufacturing",
"supplier",
"work_order_details_section",
"work_order",
"wo_produced_qty",
"purchase_order",
"production_plan_item",
"column_break_7",
"produced_qty",
"received_qty",
"indent",
"section_break_19",
Expand Down Expand Up @@ -52,13 +51,6 @@
"fieldtype": "Section Break",
"label": "Reference"
},
{
"fieldname": "work_order",
"fieldtype": "Link",
"label": "Work Order",
"options": "Work Order",
"read_only": 1
},
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
Expand All @@ -81,7 +73,8 @@
{
"fieldname": "received_qty",
"fieldtype": "Float",
"label": "Received Qty"
"label": "Received Qty",
"read_only": 1
},
{
"fieldname": "bom_no",
Expand Down Expand Up @@ -161,12 +154,6 @@
"label": "Target Warehouse",
"options": "Warehouse"
},
{
"fieldname": "produced_qty",
"fieldtype": "Data",
"label": "Produced Quantity",
"read_only": 1
},
{
"default": "In House",
"fieldname": "type_of_manufacturing",
Expand Down Expand Up @@ -209,12 +196,18 @@
"label": "Projected Qty",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "wo_produced_qty",
"fieldtype": "Float",
"label": "Produced Qty",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-05-22 17:52:34.708879",
"modified": "2023-11-03 13:33:42.959387",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Sub Assembly Item",
Expand Down
36 changes: 35 additions & 1 deletion erpnext/manufacturing/doctype/work_order/work_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ def update_work_order_qty(self):
update_produced_qty_in_so_item(self.sales_order, self.sales_order_item)

if self.production_plan:
self.set_produced_qty_for_sub_assembly_item()
self.update_production_plan_status()

def get_transferred_or_manufactured_qty(self, purpose):
Expand Down Expand Up @@ -569,16 +570,49 @@ def validate_cancel(self):
)

def update_planned_qty(self):
from erpnext.manufacturing.doctype.production_plan.production_plan import (
get_reserved_qty_for_sub_assembly,
)

qty_dict = {"planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)}

if self.production_plan_sub_assembly_item and self.production_plan:
qty_dict["reserved_qty_for_production_plan"] = get_reserved_qty_for_sub_assembly(
self.production_item, self.fg_warehouse
)

update_bin_qty(
self.production_item,
self.fg_warehouse,
{"planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)},
qty_dict,
)

if self.material_request:
mr_obj = frappe.get_doc("Material Request", self.material_request)
mr_obj.update_requested_qty([self.material_request_item])

def set_produced_qty_for_sub_assembly_item(self):
table = frappe.qb.DocType("Work Order")

query = (
frappe.qb.from_(table)
.select(Sum(table.produced_qty))
.where(
(table.production_plan == self.production_plan)
& (table.production_plan_sub_assembly_item == self.production_plan_sub_assembly_item)
& (table.docstatus == 1)
)
).run()

produced_qty = flt(query[0][0]) if query else 0

frappe.db.set_value(
"Production Plan Sub Assembly Item",
self.production_plan_sub_assembly_item,
"wo_produced_qty",
produced_qty,
)

def update_ordered_qty(self):
if (
self.production_plan
Expand Down
30 changes: 29 additions & 1 deletion erpnext/stock/doctype/bin/bin.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,15 @@ def update_reserved_qty_for_production_plan(self, skip_project_qty_update=False)
get_reserved_qty_for_production_plan,
)

self.reserved_qty_for_production_plan = get_reserved_qty_for_production_plan(
reserved_qty_for_production_plan = get_reserved_qty_for_production_plan(
self.item_code, self.warehouse
)

if reserved_qty_for_production_plan is None and not self.reserved_qty_for_production_plan:
return

self.reserved_qty_for_production_plan = flt(reserved_qty_for_production_plan)

self.db_set(
"reserved_qty_for_production_plan",
flt(self.reserved_qty_for_production_plan),
Expand All @@ -48,6 +53,29 @@ def update_reserved_qty_for_production_plan(self, skip_project_qty_update=False)
self.set_projected_qty()
self.db_set("projected_qty", self.projected_qty, update_modified=True)

def update_reserved_qty_for_for_sub_assembly(self):
from erpnext.manufacturing.doctype.production_plan.production_plan import (
get_reserved_qty_for_sub_assembly,
)

reserved_qty_for_production_plan = get_reserved_qty_for_sub_assembly(
self.item_code, self.warehouse
)

if reserved_qty_for_production_plan is None and not self.reserved_qty_for_production_plan:
return

self.reserved_qty_for_production_plan = flt(reserved_qty_for_production_plan)
self.set_projected_qty()

self.db_set(
{
"projected_qty": self.projected_qty,
"reserved_qty_for_production_plan": flt(self.reserved_qty_for_production_plan),
},
update_modified=True,
)

def update_reserved_qty_for_production(self):
"""Update qty reserved for production from Production Item tables
in open work orders"""
Expand Down
Loading