feat: reserve qty against production plan raw materials in BIN

This commit is contained in:
Rohit Waghchaure 2023-05-03 18:06:47 +05:30
parent 9aa646512a
commit 06e91e758f
8 changed files with 143 additions and 19 deletions

View File

@ -16,6 +16,7 @@
"column_break_4",
"quantity",
"uom",
"conversion_factor",
"projected_qty",
"reserved_qty_for_production",
"safety_stock",
@ -169,11 +170,17 @@
"label": "Qty As Per BOM",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "conversion_factor",
"fieldtype": "Float",
"label": "Conversion Factor",
"read_only": 1
}
],
"istable": 1,
"links": [],
"modified": "2022-11-26 14:59:25.879631",
"modified": "2023-05-03 12:43:29.895754",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Material Request Plan Item",

View File

@ -336,10 +336,6 @@ frappe.ui.form.on('Production Plan', {
},
get_items_for_material_requests(frm, warehouses) {
let set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse',
'min_order_qty', 'required_bom_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty',
'reserved_qty_for_production', 'material_request_type'];
frappe.call({
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests",
freeze: true,
@ -352,11 +348,11 @@ frappe.ui.form.on('Production Plan', {
frm.set_value('mr_items', []);
r.message.forEach(row => {
let d = frm.add_child('mr_items');
set_fields.forEach(field => {
if (row[field]) {
for (let field in row) {
if (field !== 'name') {
d[field] = row[field];
}
});
}
});
}
refresh_field('mr_items');

View File

@ -28,6 +28,7 @@ from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.stock.utils import get_or_make_bin
from erpnext.utilities.transaction_base import validate_uom_is_integer
@ -398,9 +399,20 @@ class ProductionPlan(Document):
self.set_status()
self.db_set("status", self.status)
def on_submit(self):
self.update_bin_qty()
def on_cancel(self):
self.db_set("status", "Cancelled")
self.delete_draft_work_order()
self.update_bin_qty()
def update_bin_qty(self):
for d in self.mr_items:
if d.warehouse:
bin_name = get_or_make_bin(d.item_code, d.warehouse)
bin = frappe.get_doc("Bin", bin_name, for_update=True)
bin.update_reserved_qty_for_production_plan()
def delete_draft_work_order(self):
for d in frappe.get_all(
@ -1068,6 +1080,7 @@ def get_material_request_items(
"item_code": row.item_code,
"item_name": row.item_name,
"quantity": required_qty / conversion_factor,
"conversion_factor": conversion_factor,
"required_bom_qty": total_qty,
"stock_uom": row.get("stock_uom"),
"warehouse": warehouse
@ -1474,3 +1487,34 @@ def set_default_warehouses(row, default_warehouses):
for field in ["wip_warehouse", "fg_warehouse"]:
if not row.get(field):
row[field] = default_warehouses.get(field)
def get_reserved_qty_for_production_plan(item_code, warehouse):
from erpnext.manufacturing.doctype.work_order.work_order import get_reserved_qty_for_production
table = frappe.qb.DocType("Production Plan")
child = frappe.qb.DocType("Material Request Plan Item")
query = (
frappe.qb.from_(table)
.inner_join(child)
.on(table.name == child.parent)
.select(Sum(child.quantity * IfNull(child.conversion_factor, 1.0)))
.where(
(table.docstatus == 1)
& (child.item_code == item_code)
& (child.warehouse == warehouse)
& (table.status.notin(["Completed", "Closed"]))
)
).run()
if not query:
return 0.0
reserved_qty_for_production_plan = flt(query[0][0])
reserved_qty_for_production = flt(
get_reserved_qty_for_production(item_code, warehouse, check_production_plan=True)
)
return reserved_qty_for_production_plan - reserved_qty_for_production

View File

@ -868,6 +868,27 @@ class TestProductionPlan(FrappeTestCase):
for item_code in mr_items:
self.assertTrue(item_code in validate_mr_items)
def test_resered_qty_for_production_plan_for_material_requests(self):
from erpnext.stock.utils import get_or_make_bin
bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
pln = create_production_plan(item_code="Test Production Item 1")
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"))
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"))
self.assertEqual(after_qty, before_qty)
def create_production_plan(**args):
"""

View File

@ -558,12 +558,19 @@ class WorkOrder(Document):
and self.production_plan_item
and not self.production_plan_sub_assembly_item
):
qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0
table = frappe.qb.DocType("Work Order")
if self.docstatus == 1:
qty += self.qty
elif self.docstatus == 2:
qty -= self.qty
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()
qty = flt(query[0][0]) if query else 0
frappe.db.set_value("Production Plan Item", self.production_plan_item, "ordered_qty", qty)
@ -1476,12 +1483,14 @@ def create_pick_list(source_name, target_doc=None, for_qty=None):
return doc
def get_reserved_qty_for_production(item_code: str, warehouse: str) -> float:
def get_reserved_qty_for_production(
item_code: str, warehouse: str, check_production_plan: bool = False
) -> float:
"""Get total reserved quantity for any item in specified warehouse"""
wo = frappe.qb.DocType("Work Order")
wo_item = frappe.qb.DocType("Work Order Item")
return (
query = (
frappe.qb.from_(wo)
.from_(wo_item)
.select(
@ -1502,7 +1511,12 @@ def get_reserved_qty_for_production(item_code: str, warehouse: str) -> float:
| (wo_item.required_qty > wo_item.consumed_qty)
)
)
).run()[0][0] or 0.0
)
if check_production_plan:
query = query.where(wo.production_plan.isnotnull())
return query.run()[0][0] or 0.0
@frappe.whitelist()

View File

@ -15,6 +15,7 @@
"projected_qty",
"reserved_qty_for_production",
"reserved_qty_for_sub_contract",
"reserved_qty_for_production_plan",
"ma_rate",
"stock_uom",
"fcfs_rate",
@ -165,13 +166,19 @@
"oldfieldname": "stock_value",
"oldfieldtype": "Currency",
"read_only": 1
},
{
"fieldname": "reserved_qty_for_production_plan",
"fieldtype": "Float",
"label": "Reserved Qty for Production Plan",
"read_only": 1
}
],
"hide_toolbar": 1,
"idx": 1,
"in_create": 1,
"links": [],
"modified": "2022-03-30 07:22:23.868602",
"modified": "2023-05-02 23:26:21.806965",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",

View File

@ -24,8 +24,30 @@ class Bin(Document):
- flt(self.reserved_qty)
- flt(self.reserved_qty_for_production)
- flt(self.reserved_qty_for_sub_contract)
- flt(self.reserved_qty_for_production_plan)
)
def update_reserved_qty_for_production_plan(self, skip_project_qty_update=False):
"""Update qty reserved for production from Production Plan tables
in open production plan"""
from erpnext.manufacturing.doctype.production_plan.production_plan import (
get_reserved_qty_for_production_plan,
)
self.reserved_qty_for_production_plan = get_reserved_qty_for_production_plan(
self.item_code, self.warehouse
)
self.db_set(
"reserved_qty_for_production_plan",
flt(self.reserved_qty_for_production_plan),
update_modified=True,
)
if not skip_project_qty_update:
self.set_projected_qty()
self.db_set("projected_qty", self.projected_qty, update_modified=True)
def update_reserved_qty_for_production(self):
"""Update qty reserved for production from Production Item tables
in open work orders"""
@ -35,11 +57,13 @@ class Bin(Document):
self.item_code, self.warehouse
)
self.set_projected_qty()
self.db_set(
"reserved_qty_for_production", flt(self.reserved_qty_for_production), update_modified=True
)
self.update_reserved_qty_for_production_plan(skip_project_qty_update=True)
self.set_projected_qty()
self.db_set("projected_qty", self.projected_qty, update_modified=True)
def update_reserved_qty_for_sub_contracting(self, subcontract_doctype="Subcontracting Order"):
@ -141,6 +165,7 @@ def get_bin_details(bin_name):
"planned_qty",
"reserved_qty_for_production",
"reserved_qty_for_sub_contract",
"reserved_qty_for_production_plan",
],
as_dict=1,
)
@ -188,6 +213,7 @@ def update_qty(bin_name, args):
- flt(reserved_qty)
- flt(bin_details.reserved_qty_for_production)
- flt(bin_details.reserved_qty_for_sub_contract)
- flt(bin_details.reserved_qty_for_production_plan)
)
frappe.db.set_value(

View File

@ -76,6 +76,7 @@ def execute(filters=None):
bin.ordered_qty,
bin.reserved_qty,
bin.reserved_qty_for_production,
bin.reserved_qty_for_production_plan,
bin.reserved_qty_for_sub_contract,
reserved_qty_for_pos,
bin.projected_qty,
@ -173,6 +174,13 @@ def get_columns():
"width": 100,
"convertible": "qty",
},
{
"label": _("Reserved for Production Plan"),
"fieldname": "reserved_qty_for_production_plan",
"fieldtype": "Float",
"width": 100,
"convertible": "qty",
},
{
"label": _("Reserved for Sub Contracting"),
"fieldname": "reserved_qty_for_sub_contract",
@ -232,6 +240,7 @@ def get_bin_list(filters):
bin.reserved_qty,
bin.reserved_qty_for_production,
bin.reserved_qty_for_sub_contract,
bin.reserved_qty_for_production_plan,
bin.projected_qty,
)
.orderby(bin.item_code, bin.warehouse)