Merge pull request #23227 from rohitwaghchaure/multiple-job-card-fixes

Fix: job card issues
This commit is contained in:
rohitwaghchaure 2020-09-02 11:41:37 +05:30 committed by GitHub
commit f898c807b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 194 additions and 18 deletions

View File

@ -2,6 +2,17 @@
// For license information, please see license.txt
frappe.ui.form.on('Job Card', {
setup: function(frm) {
frm.set_query('operation', function() {
return {
query: 'erpnext.manufacturing.doctype.job_card.job_card.get_operations',
filters: {
'work_order': frm.doc.work_order
}
};
});
},
refresh: function(frm) {
frappe.flags.pause_job = 0;
frappe.flags.resume_job = 0;
@ -20,12 +31,60 @@ frappe.ui.form.on('Job Card', {
}
}
frm.trigger("toggle_operation_number");
if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity)
&& (!frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
&& (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
frm.trigger("prepare_timer_buttons");
}
},
operation: function(frm) {
frm.trigger("toggle_operation_number");
if (frm.doc.operation && frm.doc.work_order) {
frappe.call({
method: "erpnext.manufacturing.doctype.job_card.job_card.get_operation_details",
args: {
"work_order":frm.doc.work_order,
"operation":frm.doc.operation
},
callback: function (r) {
if (r.message) {
if (r.message.length == 1) {
frm.set_value("operation_id", r.message[0].name);
} else {
let args = [];
r.message.forEach((row) => {
args.push({ "label": row.idx, "value": row.name });
});
let description = __("Operation {0} added multiple times in the work order {1}",
[frm.doc.operation, frm.doc.work_order]);
frm.set_df_property("operation_row_number", "options", args);
frm.set_df_property("operation_row_number", "description", description);
}
frm.trigger("toggle_operation_number");
}
}
})
}
},
operation_row_number(frm) {
if (frm.doc.operation_row_number) {
frm.set_value("operation_id", frm.doc.operation_row_number);
}
},
toggle_operation_number(frm) {
frm.toggle_display("operation_row_number", !frm.doc.operation_id && frm.doc.operation);
frm.toggle_reqd("operation_row_number", !frm.doc.operation_id && frm.doc.operation);
},
prepare_timer_buttons: function(frm) {
frm.trigger("make_dashboard");
if (!frm.doc.job_started) {
@ -35,9 +94,9 @@ frappe.ui.form.on('Job Card', {
fieldname: 'employee'}, d => {
if (d.employee) {
frm.set_value("employee", d.employee);
} else {
frm.events.start_job(frm);
}
frm.events.start_job(frm);
}, __("Enter Value"), __("Start"));
} else {
frm.events.start_job(frm);
@ -82,9 +141,7 @@ frappe.ui.form.on('Job Card', {
frm.set_value('current_time' , 0);
}
frm.save("Save", () => {}, "", () => {
frm.doc.time_logs.pop(-1);
});
frm.save();
},
complete_job: function(frm, completed_time, completed_qty) {
@ -116,6 +173,8 @@ frappe.ui.form.on('Job Card', {
employee: function(frm) {
if (frm.doc.job_started && !frm.doc.current_time) {
frm.trigger("reset_timer");
} else {
frm.events.start_job(frm);
}
},

View File

@ -11,6 +11,7 @@
"bom_no",
"workstation",
"operation",
"operation_row_number",
"column_break_4",
"posting_date",
"company",
@ -291,11 +292,15 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "operation_row_number",
"fieldtype": "Select",
"label": "Operation Row Number"
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-04-20 15:14:00.273441",
"modified": "2020-08-24 15:21:21.398267",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
@ -347,7 +352,6 @@
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "operation",

View File

@ -15,10 +15,13 @@ from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings
class OverlapError(frappe.ValidationError): pass
class OperationMismatchError(frappe.ValidationError): pass
class JobCard(Document):
def validate(self):
self.validate_time_logs()
self.set_status()
self.validate_operation_id()
def validate_time_logs(self):
self.total_completed_qty = 0.0
@ -209,11 +212,10 @@ class JobCard(Document):
for_quantity, time_in_mins = 0, 0
from_time_list, to_time_list = [], []
field = "operation_id" if self.operation_id else "operation"
field = "operation_id"
data = frappe.get_all('Job Card',
fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
filters = {"docstatus": 1, "work_order": self.work_order,
"workstation": self.workstation, field: self.get(field)})
filters = {"docstatus": 1, "work_order": self.work_order, field: self.get(field)})
if data and len(data) > 0:
for_quantity = data[0].completed_qty
@ -226,14 +228,13 @@ class JobCard(Document):
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
WHERE
jctl.parent = jc.name and jc.work_order = %s
and jc.workstation = %s and jc.{0} = %s and jc.docstatus = 1
""".format(field), (self.work_order, self.workstation, self.get(field)), as_dict=1)
and jc.{0} = %s and jc.docstatus = 1
""".format(field), (self.work_order, self.get(field)), as_dict=1)
wo = frappe.get_doc('Work Order', self.work_order)
work_order_field = "name" if field == "operation_id" else field
for data in wo.operations:
if data.get(work_order_field) == self.get(field) and data.workstation == self.workstation:
if data.get("name") == self.get(field):
data.completed_qty = for_quantity
data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None
@ -306,6 +307,37 @@ class JobCard(Document):
if update_status:
self.db_set('status', self.status)
def validate_operation_id(self):
if (self.get("operation_id") and self.get("operation_row_number") and self.operation and self.work_order and
frappe.get_cached_value("Work Order Operation", self.operation_row_number, "name") != self.operation_id):
work_order = frappe.bold(get_link_to_form("Work Order", self.work_order))
frappe.throw(_("Operation {0} does not belong to the work order {1}")
.format(frappe.bold(self.operation), work_order), OperationMismatchError)
@frappe.whitelist()
def get_operation_details(work_order, operation):
if work_order and operation:
return frappe.get_all("Work Order Operation", fields = ["name", "idx"],
filters = {
"parent": work_order,
"operation": operation
}
)
@frappe.whitelist()
def get_operations(doctype, txt, searchfield, start, page_len, filters):
if filters.get("work_order"):
args = {"parent": filters.get("work_order")}
if txt:
args["operation"] = ("like", "%{0}%".format(txt))
return frappe.get_all("Work Order Operation",
filters = args,
fields = ["distinct operation as operation"],
limit_start = start,
limit_page_length = page_len,
order_by="idx asc", as_list=1)
@frappe.whitelist()
def make_material_request(source_name, target_doc=None):
def update_item(obj, target, source_parent):

View File

@ -4,6 +4,72 @@
from __future__ import unicode_literals
import unittest
import frappe
from frappe.utils import random_string
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError
class TestJobCard(unittest.TestCase):
pass
def test_job_card(self):
data = frappe.get_cached_value('BOM',
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
if data:
bom, bom_item = data
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
job_cards = frappe.get_all('Job Card',
filters = {'work_order': work_order.name}, fields = ["operation_id", "name"])
if job_cards:
job_card = job_cards[0]
frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id)
doc = frappe.get_doc("Job Card", job_card.name)
doc.operation_id = "Test Data"
self.assertRaises(OperationMismatchError, doc.save)
for d in job_cards:
frappe.delete_doc("Job Card", d.name)
def test_job_card_with_different_work_station(self):
data = frappe.get_cached_value('BOM',
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
if data:
bom, bom_item = data
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
job_cards = frappe.get_all('Job Card',
filters = {'work_order': work_order.name},
fields = ["operation_id", "workstation", "name", "for_quantity"])
job_card = job_cards[0]
if job_card:
workstation = frappe.db.get_value("Workstation",
{"name": ("not in", [job_card.workstation])}, "name")
if not workstation or job_card.workstation == workstation:
workstation = make_workstation(workstation_name=random_string(5)).name
doc = frappe.get_doc("Job Card", job_card.name)
doc.workstation = workstation
doc.append("time_logs", {
"from_time": "2009-01-01 12:06:25",
"to_time": "2009-01-01 12:37:25",
"time_in_mins": "31.00002",
"completed_qty": job_card.for_quantity
})
doc.submit()
completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty")
self.assertEqual(completed_qty, job_card.for_quantity)
doc.cancel()
for d in job_cards:
frappe.delete_doc("Job Card", d.name)

View File

@ -7,7 +7,7 @@ import unittest
import frappe
from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError)
from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.utils import get_bin

View File

@ -20,3 +20,18 @@ class TestWorkstation(unittest.TestCase):
"_Test Workstation 1", "Operation 1", "2013-02-02 05:00:00", "2013-02-02 20:00:00")
self.assertRaises(WorkstationHolidayError, check_if_within_operating_hours,
"_Test Workstation 1", "Operation 1", "2013-02-01 10:00:00", "2013-02-02 20:00:00")
def make_workstation(**args):
args = frappe._dict(args)
try:
doc = frappe.get_doc({
"doctype": "Workstation",
"workstation_name": args.workstation_name
})
doc.insert()
return doc
except frappe.DuplicateEntryError:
return frappe.get_doc("Workstation", args.workstation_name)