mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-17 13:55:10 +00:00
fix: operation status and bom validation
(cherry picked from commit 95baf953a8)
This commit is contained in:
committed by
Mergify
parent
8c536df5f2
commit
11222653ce
@@ -580,9 +580,12 @@ frappe.ui.form.on("BOM", {
|
|||||||
frappe.ui.form.on("BOM Operation", {
|
frappe.ui.form.on("BOM Operation", {
|
||||||
finished_good(frm, cdt, cdn) {
|
finished_good(frm, cdt, cdn) {
|
||||||
let row = locals[cdt][cdn];
|
let row = locals[cdt][cdn];
|
||||||
if (row.finished_good === frm.doc.item) {
|
frappe.model.set_value(
|
||||||
frappe.model.set_value(row.doctype, row.name, "is_final_finished_good", 1);
|
row.doctype,
|
||||||
}
|
row.name,
|
||||||
|
"is_final_finished_good",
|
||||||
|
row.finished_good === frm.doc.item
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
bom_no(frm, cdt, cdn) {
|
bom_no(frm, cdt, cdn) {
|
||||||
|
|||||||
@@ -296,6 +296,55 @@ class BOM(WebsiteGenerator):
|
|||||||
self.set_process_loss_qty()
|
self.set_process_loss_qty()
|
||||||
self.validate_scrap_items()
|
self.validate_scrap_items()
|
||||||
self.set_default_uom()
|
self.set_default_uom()
|
||||||
|
self.validate_semi_finished_goods()
|
||||||
|
self.validate_raw_materials_of_operation()
|
||||||
|
|
||||||
|
def validate_semi_finished_goods(self):
|
||||||
|
if not self.track_semi_finished_goods or not self.operations:
|
||||||
|
return
|
||||||
|
|
||||||
|
fg_items = []
|
||||||
|
for row in self.operations:
|
||||||
|
if not row.is_final_finished_good:
|
||||||
|
continue
|
||||||
|
|
||||||
|
fg_items.append(row.finished_good)
|
||||||
|
|
||||||
|
if not fg_items:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Since you have enabled 'Track Semi Finished Goods', at least one operation must have 'Is Final Finished Good' checked. For that set the FG / Semi FG Item as {0} against an operation."
|
||||||
|
).format(bold(self.item)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if fg_items and len(fg_items) > 1:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Only one operation can have 'Is Final Finished Good' checked when 'Track Semi Finished Goods' is enabled."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_raw_materials_of_operation(self):
|
||||||
|
if not self.track_semi_finished_goods or not self.operations:
|
||||||
|
return
|
||||||
|
|
||||||
|
operation_idx_with_no_rm = {}
|
||||||
|
for row in self.operations:
|
||||||
|
if row.bom_no:
|
||||||
|
continue
|
||||||
|
|
||||||
|
operation_idx_with_no_rm[row.idx] = row.operation
|
||||||
|
|
||||||
|
for row in self.items:
|
||||||
|
if row.operation_row_id and row.operation_row_id in operation_idx_with_no_rm:
|
||||||
|
del operation_idx_with_no_rm[row.operation_row_id]
|
||||||
|
|
||||||
|
for idx, row in operation_idx_with_no_rm.items():
|
||||||
|
frappe.throw(
|
||||||
|
_("For operation {0} at row {1}, please add raw materials or set a BOM against it.").format(
|
||||||
|
bold(row.operation), idx
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def set_default_uom(self):
|
def set_default_uom(self):
|
||||||
if not self.get("items"):
|
if not self.get("items"):
|
||||||
|
|||||||
@@ -166,6 +166,25 @@ class JobCard(Document):
|
|||||||
|
|
||||||
self.validate_work_order()
|
self.validate_work_order()
|
||||||
self.set_employees()
|
self.set_employees()
|
||||||
|
self.validate_semi_finished_goods()
|
||||||
|
|
||||||
|
def validate_semi_finished_goods(self):
|
||||||
|
if not self.track_semi_finished_goods:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.items and not self.transferred_qty and not self.skip_material_transfer:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Materials needs to be transferred to the work in progress warehouse for the job card {0}"
|
||||||
|
).format(self.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.docstatus == 1 and not self.total_completed_qty:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Total Completed Qty is required for Job Card {0}, please start and complete the job card before submission"
|
||||||
|
).format(self.name)
|
||||||
|
)
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.validate_job_card_qty()
|
self.validate_job_card_qty()
|
||||||
@@ -1354,6 +1373,9 @@ class JobCard(Document):
|
|||||||
employees=self.employee,
|
employees=self.employee,
|
||||||
sub_operation=kwargs.get("sub_operation"),
|
sub_operation=kwargs.get("sub_operation"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.docstatus == 1:
|
||||||
|
self.update_work_order()
|
||||||
else:
|
else:
|
||||||
self.add_time_logs(completed_qty=kwargs.qty, employees=self.employee)
|
self.add_time_logs(completed_qty=kwargs.qty, employees=self.employee)
|
||||||
self.save()
|
self.save()
|
||||||
|
|||||||
@@ -809,7 +809,7 @@ erpnext.work_order = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.status != "Stopped") {
|
if (frm.doc.status != "Stopped" && !frm.doc.track_semi_finished_goods) {
|
||||||
// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
|
// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
|
||||||
if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
|
if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
|
||||||
if (flt(doc.material_transferred_for_manufacturing) > 0 || frm.doc.skip_transfer) {
|
if (flt(doc.material_transferred_for_manufacturing) > 0 || frm.doc.skip_transfer) {
|
||||||
|
|||||||
@@ -248,6 +248,16 @@ class WorkOrder(Document):
|
|||||||
if self.is_new() and frappe.db.get_single_value("Stock Settings", "auto_reserve_stock"):
|
if self.is_new() and frappe.db.get_single_value("Stock Settings", "auto_reserve_stock"):
|
||||||
self.reserve_stock = 1
|
self.reserve_stock = 1
|
||||||
|
|
||||||
|
def before_save(self):
|
||||||
|
self.set_skip_transfer_for_operations()
|
||||||
|
|
||||||
|
def set_skip_transfer_for_operations(self):
|
||||||
|
if not self.track_semi_finished_goods:
|
||||||
|
return
|
||||||
|
|
||||||
|
for op in self.operations:
|
||||||
|
op.skip_material_transfer = self.skip_transfer
|
||||||
|
|
||||||
def validate_operations_sequence(self):
|
def validate_operations_sequence(self):
|
||||||
if all([not op.sequence_id for op in self.operations]):
|
if all([not op.sequence_id for op in self.operations]):
|
||||||
for op in self.operations:
|
for op in self.operations:
|
||||||
|
|||||||
@@ -2006,6 +2006,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
else:
|
else:
|
||||||
job_doc.set_consumed_qty_in_job_card_item(self)
|
job_doc.set_consumed_qty_in_job_card_item(self)
|
||||||
job_doc.set_manufactured_qty()
|
job_doc.set_manufactured_qty()
|
||||||
|
job_doc.update_work_order()
|
||||||
|
|
||||||
if self.work_order:
|
if self.work_order:
|
||||||
pro_doc = frappe.get_doc("Work Order", self.work_order)
|
pro_doc = frappe.get_doc("Work Order", self.work_order)
|
||||||
|
|||||||
@@ -110,7 +110,9 @@ class ManufactureEntry:
|
|||||||
_dict.from_warehouse = self.source_wh.get(item_code) or self.wip_warehouse
|
_dict.from_warehouse = self.source_wh.get(item_code) or self.wip_warehouse
|
||||||
_dict.to_warehouse = ""
|
_dict.to_warehouse = ""
|
||||||
|
|
||||||
if backflush_based_on != "BOM":
|
if backflush_based_on != "BOM" and not frappe.db.get_value(
|
||||||
|
"Job Card", self.job_card, "skip_material_transfer"
|
||||||
|
):
|
||||||
calculated_qty = flt(_dict.transferred_qty) - flt(_dict.consumed_qty)
|
calculated_qty = flt(_dict.transferred_qty) - flt(_dict.consumed_qty)
|
||||||
if calculated_qty < 0:
|
if calculated_qty < 0:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
|
|||||||
Reference in New Issue
Block a user