feat: reserve qty against production plan raw materials in BIN
This commit is contained in:
parent
9aa646512a
commit
06e91e758f
@ -16,6 +16,7 @@
|
|||||||
"column_break_4",
|
"column_break_4",
|
||||||
"quantity",
|
"quantity",
|
||||||
"uom",
|
"uom",
|
||||||
|
"conversion_factor",
|
||||||
"projected_qty",
|
"projected_qty",
|
||||||
"reserved_qty_for_production",
|
"reserved_qty_for_production",
|
||||||
"safety_stock",
|
"safety_stock",
|
||||||
@ -169,11 +170,17 @@
|
|||||||
"label": "Qty As Per BOM",
|
"label": "Qty As Per BOM",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "conversion_factor",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Conversion Factor",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-26 14:59:25.879631",
|
"modified": "2023-05-03 12:43:29.895754",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Material Request Plan Item",
|
"name": "Material Request Plan Item",
|
||||||
|
@ -336,10 +336,6 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
get_items_for_material_requests(frm, warehouses) {
|
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({
|
frappe.call({
|
||||||
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests",
|
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests",
|
||||||
freeze: true,
|
freeze: true,
|
||||||
@ -352,11 +348,11 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
frm.set_value('mr_items', []);
|
frm.set_value('mr_items', []);
|
||||||
r.message.forEach(row => {
|
r.message.forEach(row => {
|
||||||
let d = frm.add_child('mr_items');
|
let d = frm.add_child('mr_items');
|
||||||
set_fields.forEach(field => {
|
for (let field in row) {
|
||||||
if (row[field]) {
|
if (field !== 'name') {
|
||||||
d[field] = row[field];
|
d[field] = row[field];
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
refresh_field('mr_items');
|
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.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.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||||
from erpnext.stock.get_item_details import get_conversion_factor
|
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
|
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
||||||
|
|
||||||
|
|
||||||
@ -398,9 +399,20 @@ class ProductionPlan(Document):
|
|||||||
self.set_status()
|
self.set_status()
|
||||||
self.db_set("status", self.status)
|
self.db_set("status", self.status)
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
self.update_bin_qty()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.db_set("status", "Cancelled")
|
self.db_set("status", "Cancelled")
|
||||||
self.delete_draft_work_order()
|
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):
|
def delete_draft_work_order(self):
|
||||||
for d in frappe.get_all(
|
for d in frappe.get_all(
|
||||||
@ -1068,6 +1080,7 @@ def get_material_request_items(
|
|||||||
"item_code": row.item_code,
|
"item_code": row.item_code,
|
||||||
"item_name": row.item_name,
|
"item_name": row.item_name,
|
||||||
"quantity": required_qty / conversion_factor,
|
"quantity": required_qty / conversion_factor,
|
||||||
|
"conversion_factor": conversion_factor,
|
||||||
"required_bom_qty": total_qty,
|
"required_bom_qty": total_qty,
|
||||||
"stock_uom": row.get("stock_uom"),
|
"stock_uom": row.get("stock_uom"),
|
||||||
"warehouse": warehouse
|
"warehouse": warehouse
|
||||||
@ -1474,3 +1487,34 @@ def set_default_warehouses(row, default_warehouses):
|
|||||||
for field in ["wip_warehouse", "fg_warehouse"]:
|
for field in ["wip_warehouse", "fg_warehouse"]:
|
||||||
if not row.get(field):
|
if not row.get(field):
|
||||||
row[field] = default_warehouses.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:
|
for item_code in mr_items:
|
||||||
self.assertTrue(item_code in validate_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):
|
def create_production_plan(**args):
|
||||||
"""
|
"""
|
||||||
|
@ -558,12 +558,19 @@ class WorkOrder(Document):
|
|||||||
and self.production_plan_item
|
and self.production_plan_item
|
||||||
and not self.production_plan_sub_assembly_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:
|
query = (
|
||||||
qty += self.qty
|
frappe.qb.from_(table)
|
||||||
elif self.docstatus == 2:
|
.select(Sum(table.qty))
|
||||||
qty -= self.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)
|
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
|
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"""
|
"""Get total reserved quantity for any item in specified warehouse"""
|
||||||
wo = frappe.qb.DocType("Work Order")
|
wo = frappe.qb.DocType("Work Order")
|
||||||
wo_item = frappe.qb.DocType("Work Order Item")
|
wo_item = frappe.qb.DocType("Work Order Item")
|
||||||
|
|
||||||
return (
|
query = (
|
||||||
frappe.qb.from_(wo)
|
frappe.qb.from_(wo)
|
||||||
.from_(wo_item)
|
.from_(wo_item)
|
||||||
.select(
|
.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)
|
| (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()
|
@frappe.whitelist()
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"projected_qty",
|
"projected_qty",
|
||||||
"reserved_qty_for_production",
|
"reserved_qty_for_production",
|
||||||
"reserved_qty_for_sub_contract",
|
"reserved_qty_for_sub_contract",
|
||||||
|
"reserved_qty_for_production_plan",
|
||||||
"ma_rate",
|
"ma_rate",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"fcfs_rate",
|
"fcfs_rate",
|
||||||
@ -165,13 +166,19 @@
|
|||||||
"oldfieldname": "stock_value",
|
"oldfieldname": "stock_value",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reserved_qty_for_production_plan",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Reserved Qty for Production Plan",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_toolbar": 1,
|
"hide_toolbar": 1,
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-03-30 07:22:23.868602",
|
"modified": "2023-05-02 23:26:21.806965",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Bin",
|
"name": "Bin",
|
||||||
|
@ -24,8 +24,30 @@ class Bin(Document):
|
|||||||
- flt(self.reserved_qty)
|
- flt(self.reserved_qty)
|
||||||
- flt(self.reserved_qty_for_production)
|
- flt(self.reserved_qty_for_production)
|
||||||
- flt(self.reserved_qty_for_sub_contract)
|
- 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):
|
def update_reserved_qty_for_production(self):
|
||||||
"""Update qty reserved for production from Production Item tables
|
"""Update qty reserved for production from Production Item tables
|
||||||
in open work orders"""
|
in open work orders"""
|
||||||
@ -35,11 +57,13 @@ class Bin(Document):
|
|||||||
self.item_code, self.warehouse
|
self.item_code, self.warehouse
|
||||||
)
|
)
|
||||||
|
|
||||||
self.set_projected_qty()
|
|
||||||
|
|
||||||
self.db_set(
|
self.db_set(
|
||||||
"reserved_qty_for_production", flt(self.reserved_qty_for_production), update_modified=True
|
"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)
|
self.db_set("projected_qty", self.projected_qty, update_modified=True)
|
||||||
|
|
||||||
def update_reserved_qty_for_sub_contracting(self, subcontract_doctype="Subcontracting Order"):
|
def update_reserved_qty_for_sub_contracting(self, subcontract_doctype="Subcontracting Order"):
|
||||||
@ -141,6 +165,7 @@ def get_bin_details(bin_name):
|
|||||||
"planned_qty",
|
"planned_qty",
|
||||||
"reserved_qty_for_production",
|
"reserved_qty_for_production",
|
||||||
"reserved_qty_for_sub_contract",
|
"reserved_qty_for_sub_contract",
|
||||||
|
"reserved_qty_for_production_plan",
|
||||||
],
|
],
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
@ -188,6 +213,7 @@ def update_qty(bin_name, args):
|
|||||||
- flt(reserved_qty)
|
- flt(reserved_qty)
|
||||||
- flt(bin_details.reserved_qty_for_production)
|
- flt(bin_details.reserved_qty_for_production)
|
||||||
- flt(bin_details.reserved_qty_for_sub_contract)
|
- flt(bin_details.reserved_qty_for_sub_contract)
|
||||||
|
- flt(bin_details.reserved_qty_for_production_plan)
|
||||||
)
|
)
|
||||||
|
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
|
@ -76,6 +76,7 @@ def execute(filters=None):
|
|||||||
bin.ordered_qty,
|
bin.ordered_qty,
|
||||||
bin.reserved_qty,
|
bin.reserved_qty,
|
||||||
bin.reserved_qty_for_production,
|
bin.reserved_qty_for_production,
|
||||||
|
bin.reserved_qty_for_production_plan,
|
||||||
bin.reserved_qty_for_sub_contract,
|
bin.reserved_qty_for_sub_contract,
|
||||||
reserved_qty_for_pos,
|
reserved_qty_for_pos,
|
||||||
bin.projected_qty,
|
bin.projected_qty,
|
||||||
@ -173,6 +174,13 @@ def get_columns():
|
|||||||
"width": 100,
|
"width": 100,
|
||||||
"convertible": "qty",
|
"convertible": "qty",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": _("Reserved for Production Plan"),
|
||||||
|
"fieldname": "reserved_qty_for_production_plan",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 100,
|
||||||
|
"convertible": "qty",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": _("Reserved for Sub Contracting"),
|
"label": _("Reserved for Sub Contracting"),
|
||||||
"fieldname": "reserved_qty_for_sub_contract",
|
"fieldname": "reserved_qty_for_sub_contract",
|
||||||
@ -232,6 +240,7 @@ def get_bin_list(filters):
|
|||||||
bin.reserved_qty,
|
bin.reserved_qty,
|
||||||
bin.reserved_qty_for_production,
|
bin.reserved_qty_for_production,
|
||||||
bin.reserved_qty_for_sub_contract,
|
bin.reserved_qty_for_sub_contract,
|
||||||
|
bin.reserved_qty_for_production_plan,
|
||||||
bin.projected_qty,
|
bin.projected_qty,
|
||||||
)
|
)
|
||||||
.orderby(bin.item_code, bin.warehouse)
|
.orderby(bin.item_code, bin.warehouse)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user