feat: added sequence id in routing for the completion of operations sequentially (#23641)
* feat: added sequence id in routing for the completion of operations sequentially * fix: translation syntax
This commit is contained in:
parent
9d94f785e9
commit
93bbc52a68
@ -55,10 +55,11 @@ class BOM(WebsiteGenerator):
|
|||||||
conflicting_bom = frappe.get_doc("BOM", name)
|
conflicting_bom = frappe.get_doc("BOM", name)
|
||||||
|
|
||||||
if conflicting_bom.item != self.item:
|
if conflicting_bom.item != self.item:
|
||||||
|
msg = (_("A BOM with name {0} already exists for item {1}.")
|
||||||
|
.format(frappe.bold(name), frappe.bold(conflicting_bom.item)))
|
||||||
|
|
||||||
frappe.throw(_("""A BOM with name {0} already exists for item {1}.
|
frappe.throw(_("{0}{1} Did you rename the item? Please contact Administrator / Tech support")
|
||||||
<br> Did you rename the item? Please contact Administrator / Tech support
|
.format(msg, "<br>"))
|
||||||
""").format(frappe.bold(name), frappe.bold(conflicting_bom.item)))
|
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
@ -72,6 +73,7 @@ class BOM(WebsiteGenerator):
|
|||||||
self.validate_uom_is_interger()
|
self.validate_uom_is_interger()
|
||||||
self.set_bom_material_details()
|
self.set_bom_material_details()
|
||||||
self.validate_materials()
|
self.validate_materials()
|
||||||
|
self.set_routing_operations()
|
||||||
self.validate_operations()
|
self.validate_operations()
|
||||||
self.calculate_cost()
|
self.calculate_cost()
|
||||||
self.update_cost(update_parent=False, from_child_bom=True, save=False)
|
self.update_cost(update_parent=False, from_child_bom=True, save=False)
|
||||||
@ -111,18 +113,13 @@ class BOM(WebsiteGenerator):
|
|||||||
def get_routing(self):
|
def get_routing(self):
|
||||||
if self.routing:
|
if self.routing:
|
||||||
self.set("operations", [])
|
self.set("operations", [])
|
||||||
for d in frappe.get_all("BOM Operation", fields = ["*"],
|
fields = ["sequence_id", "operation", "workstation", "description",
|
||||||
filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="idx"):
|
"time_in_mins", "batch_size", "operating_cost", "idx", "hour_rate"]
|
||||||
child = self.append('operations', {
|
|
||||||
"operation": d.operation,
|
for row in frappe.get_all("BOM Operation", fields = fields,
|
||||||
"workstation": d.workstation,
|
filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="sequence_id, idx"):
|
||||||
"description": d.description,
|
child = self.append('operations', row)
|
||||||
"time_in_mins": d.time_in_mins,
|
child.hour_rate = flt(row.hour_rate / self.conversion_rate, 2)
|
||||||
"batch_size": d.batch_size,
|
|
||||||
"operating_cost": d.operating_cost,
|
|
||||||
"idx": d.idx
|
|
||||||
})
|
|
||||||
child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
|
|
||||||
|
|
||||||
def set_bom_material_details(self):
|
def set_bom_material_details(self):
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
@ -571,6 +568,10 @@ class BOM(WebsiteGenerator):
|
|||||||
if act_pbom and act_pbom[0][0]:
|
if act_pbom and act_pbom[0][0]:
|
||||||
frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs"))
|
frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs"))
|
||||||
|
|
||||||
|
def set_routing_operations(self):
|
||||||
|
if self.routing and self.with_operations and not self.operations:
|
||||||
|
self.get_routing()
|
||||||
|
|
||||||
def validate_operations(self):
|
def validate_operations(self):
|
||||||
if self.with_operations and not self.get('operations'):
|
if self.with_operations and not self.get('operations'):
|
||||||
frappe.throw(_("Operations cannot be left blank"))
|
frappe.throw(_("Operations cannot be left blank"))
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2013-02-22 01:27:49",
|
"creation": "2013-02-22 01:27:49",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"sequence_id",
|
||||||
"operation",
|
"operation",
|
||||||
"workstation",
|
"workstation",
|
||||||
"description",
|
"description",
|
||||||
@ -106,11 +108,19 @@
|
|||||||
"fieldname": "batch_size",
|
"fieldname": "batch_size",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Batch Size"
|
"label": "Batch Size"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.parenttype == \"Routing\"",
|
||||||
|
"fieldname": "sequence_id",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Sequence ID"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2020-06-16 17:01:11.128420",
|
"links": [],
|
||||||
|
"modified": "2020-10-13 18:14:10.018774",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Operation",
|
"name": "BOM Operation",
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
"items",
|
"items",
|
||||||
"more_information",
|
"more_information",
|
||||||
"operation_id",
|
"operation_id",
|
||||||
|
"sequence_id",
|
||||||
"transferred_qty",
|
"transferred_qty",
|
||||||
"requested_qty",
|
"requested_qty",
|
||||||
"column_break_20",
|
"column_break_20",
|
||||||
@ -297,10 +298,18 @@
|
|||||||
"fieldname": "operation_row_number",
|
"fieldname": "operation_row_number",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Operation Row Number"
|
"label": "Operation Row Number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sequence_id",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Sequence Id",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2020-08-24 15:21:21.398267",
|
"links": [],
|
||||||
|
"modified": "2020-10-14 12:58:25.327897",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Job Card",
|
"name": "Job Card",
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
import datetime
|
import datetime
|
||||||
from frappe import _
|
from frappe import _, bold
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,
|
from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,
|
||||||
@ -16,12 +16,14 @@ from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings
|
|||||||
class OverlapError(frappe.ValidationError): pass
|
class OverlapError(frappe.ValidationError): pass
|
||||||
|
|
||||||
class OperationMismatchError(frappe.ValidationError): pass
|
class OperationMismatchError(frappe.ValidationError): pass
|
||||||
|
class OperationSequenceError(frappe.ValidationError): pass
|
||||||
|
|
||||||
class JobCard(Document):
|
class JobCard(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_time_logs()
|
self.validate_time_logs()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.validate_operation_id()
|
self.validate_operation_id()
|
||||||
|
self.validate_sequence_id()
|
||||||
|
|
||||||
def validate_time_logs(self):
|
def validate_time_logs(self):
|
||||||
self.total_completed_qty = 0.0
|
self.total_completed_qty = 0.0
|
||||||
@ -196,14 +198,14 @@ class JobCard(Document):
|
|||||||
def validate_job_card(self):
|
def validate_job_card(self):
|
||||||
if not self.time_logs:
|
if not self.time_logs:
|
||||||
frappe.throw(_("Time logs are required for {0} {1}")
|
frappe.throw(_("Time logs are required for {0} {1}")
|
||||||
.format(frappe.bold("Job Card"), get_link_to_form("Job Card", self.name)))
|
.format(bold("Job Card"), get_link_to_form("Job Card", self.name)))
|
||||||
|
|
||||||
if self.for_quantity and self.total_completed_qty != self.for_quantity:
|
if self.for_quantity and self.total_completed_qty != self.for_quantity:
|
||||||
total_completed_qty = frappe.bold(_("Total Completed Qty"))
|
total_completed_qty = bold(_("Total Completed Qty"))
|
||||||
qty_to_manufacture = frappe.bold(_("Qty to Manufacture"))
|
qty_to_manufacture = bold(_("Qty to Manufacture"))
|
||||||
|
|
||||||
frappe.throw(_("The {0} ({1}) must be equal to {2} ({3})"
|
frappe.throw(_("The {0} ({1}) must be equal to {2} ({3})")
|
||||||
.format(total_completed_qty, frappe.bold(self.total_completed_qty), qty_to_manufacture,frappe.bold(self.for_quantity))))
|
.format(total_completed_qty, bold(self.total_completed_qty), qty_to_manufacture,bold(self.for_quantity)))
|
||||||
|
|
||||||
def update_work_order(self):
|
def update_work_order(self):
|
||||||
if not self.work_order:
|
if not self.work_order:
|
||||||
@ -213,10 +215,7 @@ class JobCard(Document):
|
|||||||
from_time_list, to_time_list = [], []
|
from_time_list, to_time_list = [], []
|
||||||
|
|
||||||
field = "operation_id"
|
field = "operation_id"
|
||||||
data = frappe.get_all('Job Card',
|
data = self.get_current_operation_data()
|
||||||
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, field: self.get(field)})
|
|
||||||
|
|
||||||
if data and len(data) > 0:
|
if data and len(data) > 0:
|
||||||
for_quantity = data[0].completed_qty
|
for_quantity = data[0].completed_qty
|
||||||
time_in_mins = data[0].time_in_mins
|
time_in_mins = data[0].time_in_mins
|
||||||
@ -246,6 +245,11 @@ class JobCard(Document):
|
|||||||
wo.set_actual_dates()
|
wo.set_actual_dates()
|
||||||
wo.save()
|
wo.save()
|
||||||
|
|
||||||
|
def get_current_operation_data(self):
|
||||||
|
return 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, "operation_id": self.operation_id})
|
||||||
|
|
||||||
def set_transferred_qty(self, update_status=False):
|
def set_transferred_qty(self, update_status=False):
|
||||||
if not self.items:
|
if not self.items:
|
||||||
self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
|
self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
|
||||||
@ -310,9 +314,32 @@ class JobCard(Document):
|
|||||||
def validate_operation_id(self):
|
def validate_operation_id(self):
|
||||||
if (self.get("operation_id") and self.get("operation_row_number") and self.operation and self.work_order and
|
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):
|
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))
|
work_order = bold(get_link_to_form("Work Order", self.work_order))
|
||||||
frappe.throw(_("Operation {0} does not belong to the work order {1}")
|
frappe.throw(_("Operation {0} does not belong to the work order {1}")
|
||||||
.format(frappe.bold(self.operation), work_order), OperationMismatchError)
|
.format(bold(self.operation), work_order), OperationMismatchError)
|
||||||
|
|
||||||
|
def validate_sequence_id(self):
|
||||||
|
if not (self.work_order and self.sequence_id): return
|
||||||
|
|
||||||
|
current_operation_qty = 0.0
|
||||||
|
data = self.get_current_operation_data()
|
||||||
|
if data and len(data) > 0:
|
||||||
|
current_operation_qty = flt(data[0].completed_qty)
|
||||||
|
|
||||||
|
current_operation_qty += flt(self.total_completed_qty)
|
||||||
|
|
||||||
|
data = frappe.get_all("Work Order Operation",
|
||||||
|
fields = ["operation", "status", "completed_qty"],
|
||||||
|
filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ('<', self.sequence_id)},
|
||||||
|
order_by = "sequence_id, idx")
|
||||||
|
|
||||||
|
message = "Job Card {0}: As per the sequence of the operations in the work order {1}".format(bold(self.name),
|
||||||
|
bold(get_link_to_form("Work Order", self.work_order)))
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
if row.status != "Completed" and row.completed_qty < current_operation_qty:
|
||||||
|
frappe.throw(_("{0}, complete the operation {1} before the operation {2}.")
|
||||||
|
.format(message, bold(row.operation), bold(self.operation)), OperationSequenceError)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_operation_details(work_order, operation):
|
def get_operation_details(work_order, operation):
|
||||||
|
|||||||
@ -9,3 +9,23 @@ test_records = frappe.get_test_records('Operation')
|
|||||||
|
|
||||||
class TestOperation(unittest.TestCase):
|
class TestOperation(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def make_operation(*args, **kwargs):
|
||||||
|
args = args if args else kwargs
|
||||||
|
if isinstance(args, tuple):
|
||||||
|
args = args[0]
|
||||||
|
|
||||||
|
args = frappe._dict(args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
doc = frappe.get_doc({
|
||||||
|
"doctype": "Operation",
|
||||||
|
"name": args.operation,
|
||||||
|
"workstation": args.workstation
|
||||||
|
})
|
||||||
|
|
||||||
|
doc.insert()
|
||||||
|
|
||||||
|
return doc
|
||||||
|
except frappe.DuplicateEntryError:
|
||||||
|
return frappe.get_doc("Operation", args.operation)
|
||||||
@ -237,7 +237,9 @@ def make_bom(**args):
|
|||||||
'item': args.item,
|
'item': args.item,
|
||||||
'currency': args.currency or 'USD',
|
'currency': args.currency or 'USD',
|
||||||
'quantity': args.quantity or 1,
|
'quantity': args.quantity or 1,
|
||||||
'company': args.company or '_Test Company'
|
'company': args.company or '_Test Company',
|
||||||
|
'routing': args.routing,
|
||||||
|
'with_operations': args.with_operations or 0
|
||||||
})
|
})
|
||||||
|
|
||||||
for item in args.raw_materials:
|
for item in args.raw_materials:
|
||||||
|
|||||||
@ -2,6 +2,13 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Routing', {
|
frappe.ui.form.on('Routing', {
|
||||||
|
setup: function(frm) {
|
||||||
|
frappe.meta.get_docfield("BOM Operation", "sequence_id",
|
||||||
|
frm.doc.name).in_list_view = true;
|
||||||
|
|
||||||
|
frm.fields_dict.operations.grid.refresh();
|
||||||
|
},
|
||||||
|
|
||||||
calculate_operating_cost: function(frm, child) {
|
calculate_operating_cost: function(frm, child) {
|
||||||
const operating_cost = flt(flt(child.hour_rate) * flt(child.time_in_mins) / 60, 2);
|
const operating_cost = flt(flt(child.hour_rate) * flt(child.time_in_mins) / 60, 2);
|
||||||
frappe.model.set_value(child.doctype, child.name, "operating_cost", operating_cost);
|
frappe.model.set_value(child.doctype, child.name, "operating_cost", operating_cost);
|
||||||
|
|||||||
@ -3,7 +3,22 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import cint
|
||||||
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class Routing(Document):
|
class Routing(Document):
|
||||||
pass
|
def validate(self):
|
||||||
|
self.set_routing_id()
|
||||||
|
|
||||||
|
def set_routing_id(self):
|
||||||
|
sequence_id = 0
|
||||||
|
for row in self.operations:
|
||||||
|
if not row.sequence_id:
|
||||||
|
row.sequence_id = sequence_id + 1
|
||||||
|
elif sequence_id and row.sequence_id and cint(sequence_id) > cint(row.sequence_id):
|
||||||
|
frappe.throw(_("At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2}")
|
||||||
|
.format(row.idx, row.sequence_id, sequence_id))
|
||||||
|
|
||||||
|
sequence_id = row.sequence_id
|
||||||
@ -4,6 +4,88 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
import frappe
|
||||||
|
from frappe.test_runner import make_test_records
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
||||||
|
from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError
|
||||||
|
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
|
||||||
|
|
||||||
class TestRouting(unittest.TestCase):
|
class TestRouting(unittest.TestCase):
|
||||||
pass
|
def test_sequence_id(self):
|
||||||
|
item_code = "Test Routing Item - A"
|
||||||
|
operations = [{"operation": "Test Operation A", "workstation": "Test Workstation A", "time_in_mins": 30},
|
||||||
|
{"operation": "Test Operation B", "workstation": "Test Workstation A", "time_in_mins": 20}]
|
||||||
|
|
||||||
|
make_test_records("UOM")
|
||||||
|
|
||||||
|
setup_operations(operations)
|
||||||
|
routing_doc = create_routing(routing_name="Testing Route", operations=operations)
|
||||||
|
bom_doc = setup_bom(item_code=item_code, routing=routing_doc.name)
|
||||||
|
wo_doc = make_wo_order_test_record(production_item = item_code, bom_no=bom_doc.name)
|
||||||
|
|
||||||
|
for row in routing_doc.operations:
|
||||||
|
self.assertEqual(row.sequence_id, row.idx)
|
||||||
|
|
||||||
|
for data in frappe.get_all("Job Card",
|
||||||
|
filters={"work_order": wo_doc.name}, order_by="sequence_id desc"):
|
||||||
|
job_card_doc = frappe.get_doc("Job Card", data.name)
|
||||||
|
job_card_doc.time_logs[0].completed_qty = 10
|
||||||
|
if job_card_doc.sequence_id != 1:
|
||||||
|
self.assertRaises(OperationSequenceError, job_card_doc.save)
|
||||||
|
else:
|
||||||
|
job_card_doc.save()
|
||||||
|
self.assertEqual(job_card_doc.total_completed_qty, 10)
|
||||||
|
|
||||||
|
wo_doc.cancel()
|
||||||
|
wo_doc.delete()
|
||||||
|
|
||||||
|
def setup_operations(rows):
|
||||||
|
for row in rows:
|
||||||
|
make_workstation(row)
|
||||||
|
make_operation(row)
|
||||||
|
|
||||||
|
def create_routing(**args):
|
||||||
|
args = frappe._dict(args)
|
||||||
|
|
||||||
|
doc = frappe.new_doc("Routing")
|
||||||
|
doc.update(args)
|
||||||
|
|
||||||
|
if not args.do_not_save:
|
||||||
|
try:
|
||||||
|
for operation in args.operations:
|
||||||
|
doc.append("operations", operation)
|
||||||
|
|
||||||
|
doc.insert()
|
||||||
|
except frappe.DuplicateEntryError:
|
||||||
|
doc = frappe.get_doc("Routing", args.routing_name)
|
||||||
|
|
||||||
|
return doc
|
||||||
|
|
||||||
|
def setup_bom(**args):
|
||||||
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
|
||||||
|
args = frappe._dict(args)
|
||||||
|
|
||||||
|
if not frappe.db.exists('Item', args.item_code):
|
||||||
|
make_item(args.item_code, {
|
||||||
|
'is_stock_item': 1
|
||||||
|
})
|
||||||
|
|
||||||
|
if not args.raw_materials:
|
||||||
|
if not frappe.db.exists('Item', "Test Extra Item 1"):
|
||||||
|
make_item("Test Extra Item N-1", {
|
||||||
|
'is_stock_item': 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
args.raw_materials = ['Test Extra Item N-1']
|
||||||
|
|
||||||
|
name = frappe.db.get_value('BOM', {'item': args.item_code}, 'name')
|
||||||
|
if not name:
|
||||||
|
bom_doc = make_bom(item = args.item_code, raw_materials = args.get("raw_materials"),
|
||||||
|
routing = args.routing, with_operations=1)
|
||||||
|
else:
|
||||||
|
bom_doc = frappe.get_doc("BOM", name)
|
||||||
|
|
||||||
|
return bom_doc
|
||||||
@ -378,7 +378,7 @@ class WorkOrder(Document):
|
|||||||
select
|
select
|
||||||
operation, description, workstation, idx,
|
operation, description, workstation, idx,
|
||||||
base_hour_rate as hour_rate, time_in_mins,
|
base_hour_rate as hour_rate, time_in_mins,
|
||||||
"Pending" as status, parent as bom, batch_size
|
"Pending" as status, parent as bom, batch_size, sequence_id
|
||||||
from
|
from
|
||||||
`tabBOM Operation`
|
`tabBOM Operation`
|
||||||
where
|
where
|
||||||
@ -865,6 +865,7 @@ def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto
|
|||||||
'bom_no': work_order.bom_no,
|
'bom_no': work_order.bom_no,
|
||||||
'project': work_order.project,
|
'project': work_order.project,
|
||||||
'company': work_order.company,
|
'company': work_order.company,
|
||||||
|
'sequence_id': row.get("sequence_id"),
|
||||||
'wip_warehouse': work_order.wip_warehouse
|
'wip_warehouse': work_order.wip_warehouse
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"details",
|
"details",
|
||||||
"operation",
|
"operation",
|
||||||
"bom",
|
"bom",
|
||||||
|
"sequence_id",
|
||||||
"description",
|
"description",
|
||||||
"col_break1",
|
"col_break1",
|
||||||
"completed_qty",
|
"completed_qty",
|
||||||
@ -187,11 +188,19 @@
|
|||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Batch Size",
|
"label": "Batch Size",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sequence_id",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Sequence ID",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2019-12-03 19:24:29.594189",
|
"modified": "2020-10-14 12:58:49.241252",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Work Order Operation",
|
"name": "Work Order Operation",
|
||||||
|
|||||||
@ -21,17 +21,22 @@ class TestWorkstation(unittest.TestCase):
|
|||||||
self.assertRaises(WorkstationHolidayError, check_if_within_operating_hours,
|
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")
|
"_Test Workstation 1", "Operation 1", "2013-02-01 10:00:00", "2013-02-02 20:00:00")
|
||||||
|
|
||||||
def make_workstation(**args):
|
def make_workstation(*args, **kwargs):
|
||||||
|
args = args if args else kwargs
|
||||||
|
if isinstance(args, tuple):
|
||||||
|
args = args[0]
|
||||||
|
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
|
workstation_name = args.workstation_name or args.workstation
|
||||||
try:
|
try:
|
||||||
doc = frappe.get_doc({
|
doc = frappe.get_doc({
|
||||||
"doctype": "Workstation",
|
"doctype": "Workstation",
|
||||||
"workstation_name": args.workstation_name
|
"workstation_name": workstation_name
|
||||||
})
|
})
|
||||||
|
|
||||||
doc.insert()
|
doc.insert()
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
return frappe.get_doc("Workstation", args.workstation_name)
|
return frappe.get_doc("Workstation", workstation_name)
|
||||||
Loading…
x
Reference in New Issue
Block a user