Merge pull request #35144 from rohitwaghchaure/feat-reserved-qty-for-production-plan-in-bin
feat: reserve qty against production plan raw materials in BIN
This commit is contained in:
commit
dfee45f7ae
@ -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",
|
||||
|
@ -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');
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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()
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user