Merge pull request #28150 from anupamvs/manufacturing-work-order-stop
feat: provision to close the Work Order
This commit is contained in:
commit
f57538bd2a
@ -28,6 +28,11 @@ frappe.ui.form.on('Job Card', {
|
|||||||
frappe.flags.resume_job = 0;
|
frappe.flags.resume_job = 0;
|
||||||
let has_items = frm.doc.items && frm.doc.items.length;
|
let has_items = frm.doc.items && frm.doc.items.length;
|
||||||
|
|
||||||
|
if (frm.doc.__onload.work_order_stopped) {
|
||||||
|
frm.disable_save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) {
|
if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) {
|
||||||
let to_request = frm.doc.for_quantity > frm.doc.transferred_qty;
|
let to_request = frm.doc.for_quantity > frm.doc.transferred_qty;
|
||||||
let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer;
|
let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer;
|
||||||
|
@ -36,6 +36,7 @@ class JobCard(Document):
|
|||||||
def onload(self):
|
def onload(self):
|
||||||
excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
|
excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
|
||||||
self.set_onload("job_card_excess_transfer", excess_transfer)
|
self.set_onload("job_card_excess_transfer", excess_transfer)
|
||||||
|
self.set_onload("work_order_stopped", self.is_work_order_stopped())
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_time_logs()
|
self.validate_time_logs()
|
||||||
@ -44,6 +45,7 @@ class JobCard(Document):
|
|||||||
self.validate_sequence_id()
|
self.validate_sequence_id()
|
||||||
self.set_sub_operations()
|
self.set_sub_operations()
|
||||||
self.update_sub_operation_status()
|
self.update_sub_operation_status()
|
||||||
|
self.validate_work_order()
|
||||||
|
|
||||||
def set_sub_operations(self):
|
def set_sub_operations(self):
|
||||||
if self.operation:
|
if self.operation:
|
||||||
@ -548,6 +550,18 @@ class JobCard(Document):
|
|||||||
frappe.throw(_("{0}, complete the operation {1} before the operation {2}.")
|
frappe.throw(_("{0}, complete the operation {1} before the operation {2}.")
|
||||||
.format(message, bold(row.operation), bold(self.operation)), OperationSequenceError)
|
.format(message, bold(row.operation), bold(self.operation)), OperationSequenceError)
|
||||||
|
|
||||||
|
def validate_work_order(self):
|
||||||
|
if self.is_work_order_stopped():
|
||||||
|
frappe.throw(_("You can't make any changes to Job Card since Work Order is stopped."))
|
||||||
|
|
||||||
|
def is_work_order_stopped(self):
|
||||||
|
if self.work_order:
|
||||||
|
status = frappe.get_value('Work Order', self.work_order)
|
||||||
|
|
||||||
|
if status == "Closed":
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_time_log(args):
|
def make_time_log(args):
|
||||||
|
@ -12,6 +12,7 @@ from erpnext.manufacturing.doctype.work_order.work_order import (
|
|||||||
ItemHasVariantError,
|
ItemHasVariantError,
|
||||||
OverProductionError,
|
OverProductionError,
|
||||||
StockOverProductionError,
|
StockOverProductionError,
|
||||||
|
close_work_order,
|
||||||
make_stock_entry,
|
make_stock_entry,
|
||||||
stop_unstop,
|
stop_unstop,
|
||||||
)
|
)
|
||||||
@ -800,6 +801,46 @@ class TestWorkOrder(unittest.TestCase):
|
|||||||
if row.is_scrap_item:
|
if row.is_scrap_item:
|
||||||
self.assertEqual(row.qty, 1)
|
self.assertEqual(row.qty, 1)
|
||||||
|
|
||||||
|
def test_close_work_order(self):
|
||||||
|
items = ['Test FG Item for Closed WO', 'Test RM Item 1 for Closed WO',
|
||||||
|
'Test RM Item 2 for Closed WO']
|
||||||
|
|
||||||
|
company = '_Test Company with perpetual inventory'
|
||||||
|
for item_code in items:
|
||||||
|
create_item(item_code = item_code, is_stock_item = 1,
|
||||||
|
is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1')
|
||||||
|
|
||||||
|
item = 'Test FG Item for Closed WO'
|
||||||
|
raw_materials = ['Test RM Item 1 for Closed WO', 'Test RM Item 2 for Closed WO']
|
||||||
|
if not frappe.db.get_value('BOM', {'item': item}):
|
||||||
|
bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True)
|
||||||
|
bom.with_operations = 1
|
||||||
|
bom.append('operations', {
|
||||||
|
'operation': '_Test Operation 1',
|
||||||
|
'workstation': '_Test Workstation 1',
|
||||||
|
'hour_rate': 20,
|
||||||
|
'time_in_mins': 60
|
||||||
|
})
|
||||||
|
|
||||||
|
bom.submit()
|
||||||
|
|
||||||
|
wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1)
|
||||||
|
job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name')
|
||||||
|
|
||||||
|
if len(job_cards) == len(bom.operations):
|
||||||
|
for jc in job_cards:
|
||||||
|
job_card_doc = frappe.get_doc('Job Card', jc)
|
||||||
|
job_card_doc.append('time_logs', {
|
||||||
|
'from_time': now(),
|
||||||
|
'time_in_mins': 60,
|
||||||
|
'completed_qty': job_card_doc.for_quantity
|
||||||
|
})
|
||||||
|
|
||||||
|
job_card_doc.submit()
|
||||||
|
|
||||||
|
close_work_order(wo_order, "Closed")
|
||||||
|
self.assertEqual(wo_order.get('status'), "Closed")
|
||||||
|
|
||||||
def update_job_card(job_card):
|
def update_job_card(job_card):
|
||||||
job_card_doc = frappe.get_doc('Job Card', job_card)
|
job_card_doc = frappe.get_doc('Job Card', job_card)
|
||||||
job_card_doc.set('scrap_items', [
|
job_card_doc.set('scrap_items', [
|
||||||
|
@ -135,24 +135,26 @@ frappe.ui.form.on("Work Order", {
|
|||||||
frm.set_intro(__("Submit this Work Order for further processing."));
|
frm.set_intro(__("Submit this Work Order for further processing."));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.docstatus===1) {
|
if (frm.doc.status != "Closed") {
|
||||||
frm.trigger('show_progress_for_items');
|
if (frm.doc.docstatus===1) {
|
||||||
frm.trigger('show_progress_for_operations');
|
frm.trigger('show_progress_for_items');
|
||||||
}
|
frm.trigger('show_progress_for_operations');
|
||||||
|
}
|
||||||
|
|
||||||
if (frm.doc.docstatus === 1
|
if (frm.doc.docstatus === 1
|
||||||
&& frm.doc.operations && frm.doc.operations.length) {
|
&& frm.doc.operations && frm.doc.operations.length) {
|
||||||
|
|
||||||
const not_completed = frm.doc.operations.filter(d => {
|
const not_completed = frm.doc.operations.filter(d => {
|
||||||
if(d.status != 'Completed') {
|
if (d.status != 'Completed') {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (not_completed && not_completed.length) {
|
||||||
|
frm.add_custom_button(__('Create Job Card'), () => {
|
||||||
|
frm.trigger("make_job_card");
|
||||||
|
}).addClass('btn-primary');
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if(not_completed && not_completed.length) {
|
|
||||||
frm.add_custom_button(__('Create Job Card'), () => {
|
|
||||||
frm.trigger("make_job_card");
|
|
||||||
}).addClass('btn-primary');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,14 +519,22 @@ frappe.ui.form.on("Work Order Operation", {
|
|||||||
erpnext.work_order = {
|
erpnext.work_order = {
|
||||||
set_custom_buttons: function(frm) {
|
set_custom_buttons: function(frm) {
|
||||||
var doc = frm.doc;
|
var doc = frm.doc;
|
||||||
if (doc.docstatus === 1) {
|
if (doc.docstatus === 1 && doc.status != "Closed") {
|
||||||
|
frm.add_custom_button(__('Close'), function() {
|
||||||
|
frappe.confirm(__("Once the Work Order is Closed. It can't be resumed."),
|
||||||
|
() => {
|
||||||
|
erpnext.work_order.change_work_order_status(frm, "Closed");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, __("Status"));
|
||||||
|
|
||||||
if (doc.status != 'Stopped' && doc.status != 'Completed') {
|
if (doc.status != 'Stopped' && doc.status != 'Completed') {
|
||||||
frm.add_custom_button(__('Stop'), function() {
|
frm.add_custom_button(__('Stop'), function() {
|
||||||
erpnext.work_order.stop_work_order(frm, "Stopped");
|
erpnext.work_order.change_work_order_status(frm, "Stopped");
|
||||||
}, __("Status"));
|
}, __("Status"));
|
||||||
} else if (doc.status == 'Stopped') {
|
} else if (doc.status == 'Stopped') {
|
||||||
frm.add_custom_button(__('Re-open'), function() {
|
frm.add_custom_button(__('Re-open'), function() {
|
||||||
erpnext.work_order.stop_work_order(frm, "Resumed");
|
erpnext.work_order.change_work_order_status(frm, "Resumed");
|
||||||
}, __("Status"));
|
}, __("Status"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -713,9 +723,10 @@ erpnext.work_order = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
stop_work_order: function(frm, status) {
|
change_work_order_status: function(frm, status) {
|
||||||
|
let method_name = status=="Closed" ? "close_work_order" : "stop_unstop";
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.manufacturing.doctype.work_order.work_order.stop_unstop",
|
method: `erpnext.manufacturing.doctype.work_order.work_order.${method_name}`,
|
||||||
freeze: true,
|
freeze: true,
|
||||||
freeze_message: __("Updating Work Order status"),
|
freeze_message: __("Updating Work Order status"),
|
||||||
args: {
|
args: {
|
||||||
|
@ -99,7 +99,7 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "status",
|
"oldfieldname": "status",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled",
|
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nClosed\nCancelled",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
@ -573,7 +573,8 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-10-27 19:21:35.139888",
|
"migration_hash": "a18118963f4fcdb7f9d326de5f4063ba",
|
||||||
|
"modified": "2021-10-29 15:12:32.203605",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Work Order",
|
"name": "Work Order",
|
||||||
|
@ -175,7 +175,7 @@ class WorkOrder(Document):
|
|||||||
|
|
||||||
def update_status(self, status=None):
|
def update_status(self, status=None):
|
||||||
'''Update status of work order if unknown'''
|
'''Update status of work order if unknown'''
|
||||||
if status != "Stopped":
|
if status != "Stopped" and status != "Closed":
|
||||||
status = self.get_status(status)
|
status = self.get_status(status)
|
||||||
|
|
||||||
if status != self.status:
|
if status != self.status:
|
||||||
@ -624,7 +624,6 @@ class WorkOrder(Document):
|
|||||||
def validate_operation_time(self):
|
def validate_operation_time(self):
|
||||||
for d in self.operations:
|
for d in self.operations:
|
||||||
if not d.time_in_mins > 0:
|
if not d.time_in_mins > 0:
|
||||||
print(self.bom_no, self.production_item)
|
|
||||||
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation))
|
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation))
|
||||||
|
|
||||||
def update_required_items(self):
|
def update_required_items(self):
|
||||||
@ -967,6 +966,10 @@ def stop_unstop(work_order, status):
|
|||||||
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
||||||
|
|
||||||
pro_order = frappe.get_doc("Work Order", work_order)
|
pro_order = frappe.get_doc("Work Order", work_order)
|
||||||
|
|
||||||
|
if pro_order.status == "Closed":
|
||||||
|
frappe.throw(_("Closed Work Order can not be stopped or Re-opened"))
|
||||||
|
|
||||||
pro_order.update_status(status)
|
pro_order.update_status(status)
|
||||||
pro_order.update_planned_qty()
|
pro_order.update_planned_qty()
|
||||||
frappe.msgprint(_("Work Order has been {0}").format(status))
|
frappe.msgprint(_("Work Order has been {0}").format(status))
|
||||||
@ -1001,6 +1004,29 @@ def make_job_card(work_order, operations):
|
|||||||
if row.job_card_qty > 0:
|
if row.job_card_qty > 0:
|
||||||
create_job_card(work_order, row, auto_create=True)
|
create_job_card(work_order, row, auto_create=True)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def close_work_order(work_order, status):
|
||||||
|
if not frappe.has_permission("Work Order", "write"):
|
||||||
|
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
||||||
|
|
||||||
|
work_order = frappe.get_doc("Work Order", work_order)
|
||||||
|
if work_order.get("operations"):
|
||||||
|
job_cards = frappe.get_list("Job Card",
|
||||||
|
filters={
|
||||||
|
"work_order": work_order.name,
|
||||||
|
"status": "Work In Progress"
|
||||||
|
}, pluck='name')
|
||||||
|
|
||||||
|
if job_cards:
|
||||||
|
job_cards = ", ".join(job_cards)
|
||||||
|
frappe.throw(_("Can not close Work Order. Since {0} Job Cards are in Work In Progress state.").format(job_cards))
|
||||||
|
|
||||||
|
work_order.update_status(status)
|
||||||
|
work_order.update_planned_qty()
|
||||||
|
frappe.msgprint(_("Work Order has been {0}").format(status))
|
||||||
|
work_order.notify_update()
|
||||||
|
return work_order.status
|
||||||
|
|
||||||
def split_qty_based_on_batch_size(wo_doc, row, qty):
|
def split_qty_based_on_batch_size(wo_doc, row, qty):
|
||||||
if not cint(frappe.db.get_value("Operation",
|
if not cint(frappe.db.get_value("Operation",
|
||||||
row.operation, "create_job_card_based_on_batch_size")):
|
row.operation, "create_job_card_based_on_batch_size")):
|
||||||
|
@ -160,7 +160,7 @@ def get_ordered_qty(item_code, warehouse):
|
|||||||
def get_planned_qty(item_code, warehouse):
|
def get_planned_qty(item_code, warehouse):
|
||||||
planned_qty = frappe.db.sql("""
|
planned_qty = frappe.db.sql("""
|
||||||
select sum(qty - produced_qty) from `tabWork Order`
|
select sum(qty - produced_qty) from `tabWork Order`
|
||||||
where production_item = %s and fg_warehouse = %s and status not in ("Stopped", "Completed")
|
where production_item = %s and fg_warehouse = %s and status not in ("Stopped", "Completed", "Closed")
|
||||||
and docstatus=1 and qty > produced_qty""", (item_code, warehouse))
|
and docstatus=1 and qty > produced_qty""", (item_code, warehouse))
|
||||||
|
|
||||||
return flt(planned_qty[0][0]) if planned_qty else 0
|
return flt(planned_qty[0][0]) if planned_qty else 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user