feat: Job Card Enhancements
This commit is contained in:
parent
81d164134d
commit
6e81489095
@ -13,10 +13,10 @@
|
|||||||
"col_break1",
|
"col_break1",
|
||||||
"hour_rate",
|
"hour_rate",
|
||||||
"time_in_mins",
|
"time_in_mins",
|
||||||
"batch_size",
|
|
||||||
"operating_cost",
|
"operating_cost",
|
||||||
"base_hour_rate",
|
"base_hour_rate",
|
||||||
"base_operating_cost",
|
"base_operating_cost",
|
||||||
|
"batch_size",
|
||||||
"image"
|
"image"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -61,6 +61,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "In minutes",
|
"description": "In minutes",
|
||||||
|
"fetch_from": "operation.total_operation_time",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "time_in_mins",
|
"fieldname": "time_in_mins",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -104,7 +106,8 @@
|
|||||||
"label": "Image"
|
"label": "Image"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"fetch_from": "operation.batch_size",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "batch_size",
|
"fieldname": "batch_size",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Batch Size"
|
"label": "Batch Size"
|
||||||
@ -120,7 +123,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-13 18:14:10.018774",
|
"modified": "2020-12-14 15:01:33.142869",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Operation",
|
"name": "BOM Operation",
|
||||||
|
@ -11,6 +11,16 @@ frappe.ui.form.on('Job Card', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_indicator_formatter('sub_operation',
|
||||||
|
function(doc) {
|
||||||
|
if (doc.status == "Pending") {
|
||||||
|
return "red";
|
||||||
|
} else {
|
||||||
|
return doc.status === "Complete" ? "green" : "orange";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
@ -97,81 +107,76 @@ frappe.ui.form.on('Job Card', {
|
|||||||
|
|
||||||
prepare_timer_buttons: function(frm) {
|
prepare_timer_buttons: function(frm) {
|
||||||
frm.trigger("make_dashboard");
|
frm.trigger("make_dashboard");
|
||||||
if (!frm.doc.job_started) {
|
|
||||||
frm.add_custom_button(__("Start"), () => {
|
if (!frm.doc.started_time && !frm.doc.current_time) {
|
||||||
if (!frm.doc.employee) {
|
frm.add_custom_button(__("Start Job"), () => {
|
||||||
frappe.prompt({fieldtype: 'Link', label: __('Employee'), options: "Employee",
|
frappe.prompt({fieldtype: 'Table MultiSelect', label: __('Employee'), options: "Job Card Time Log",
|
||||||
fieldname: 'employee'}, d => {
|
fieldname: 'employee'}, d => {
|
||||||
if (d.employee) {
|
debugger
|
||||||
frm.set_value("employee", d.employee);
|
frm.events.start_job(frm, "Work In Progress", d.employee);
|
||||||
} else {
|
}, __("Assign Job to Employee"));
|
||||||
frm.events.start_job(frm);
|
|
||||||
}
|
|
||||||
}, __("Enter Value"), __("Start"));
|
|
||||||
} else {
|
|
||||||
frm.events.start_job(frm);
|
|
||||||
}
|
|
||||||
}).addClass("btn-primary");
|
}).addClass("btn-primary");
|
||||||
} else if (frm.doc.status == "On Hold") {
|
} else if (frm.doc.status == "On Hold") {
|
||||||
frm.add_custom_button(__("Resume"), () => {
|
frm.add_custom_button(__("Resume Job"), () => {
|
||||||
frappe.flags.resume_job = 1;
|
frm.events.start_job(frm, "Resume Job");
|
||||||
frm.events.start_job(frm);
|
|
||||||
}).addClass("btn-primary");
|
}).addClass("btn-primary");
|
||||||
} else {
|
} else {
|
||||||
frm.add_custom_button(__("Pause"), () => {
|
frm.add_custom_button(__("Pause Job"), () => {
|
||||||
frappe.flags.pause_job = 1;
|
frm.events.complete_job(frm, "On Hold");
|
||||||
frm.set_value("status", "On Hold");
|
|
||||||
frm.events.complete_job(frm);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.add_custom_button(__("Complete"), () => {
|
frm.add_custom_button(__("Complete Job"), () => {
|
||||||
let completed_time = frappe.datetime.now_datetime();
|
|
||||||
frm.trigger("hide_timer");
|
|
||||||
|
|
||||||
if (frm.doc.for_quantity) {
|
|
||||||
frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'),
|
frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'),
|
||||||
fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => {
|
fieldname: 'qty', default: frm.doc.for_quantity}, data => {
|
||||||
frm.events.complete_job(frm, completed_time, data.qty);
|
frm.events.complete_job(frm, "Complete", data.qty);
|
||||||
}, __("Enter Value"), __("Complete"));
|
}, __("Enter Value"));
|
||||||
} else {
|
|
||||||
frm.events.complete_job(frm, completed_time, 0);
|
|
||||||
}
|
|
||||||
}).addClass("btn-primary");
|
}).addClass("btn-primary");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
start_job: function(frm) {
|
start_job: function(frm, status, employee) {
|
||||||
let row = frappe.model.add_child(frm.doc, 'Job Card Time Log', 'time_logs');
|
const args = {
|
||||||
row.from_time = frappe.datetime.now_datetime();
|
job_card_id: frm.doc.name,
|
||||||
frm.set_value('job_started', 1);
|
start_time: frappe.datetime.now_datetime(),
|
||||||
frm.set_value('started_time' , row.from_time);
|
employee: employee,
|
||||||
frm.set_value("status", "Work In Progress");
|
status: status
|
||||||
|
};
|
||||||
if (!frappe.flags.resume_job) {
|
frm.events.make_time_log(frm, args);
|
||||||
frm.set_value('current_time' , 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
frm.save();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
complete_job: function(frm, completed_time, completed_qty) {
|
complete_job: function(frm, status, completed_qty) {
|
||||||
frm.doc.time_logs.forEach(d => {
|
const args = {
|
||||||
if (d.from_time && !d.to_time) {
|
job_card_id: frm.doc.name,
|
||||||
d.to_time = completed_time || frappe.datetime.now_datetime();
|
complete_time: frappe.datetime.now_datetime(),
|
||||||
d.completed_qty = completed_qty || 0;
|
status: status,
|
||||||
|
completed_qty: completed_qty
|
||||||
|
};
|
||||||
|
frm.events.make_time_log(frm, args);
|
||||||
|
},
|
||||||
|
|
||||||
if(frappe.flags.pause_job) {
|
make_time_log: function(frm, args) {
|
||||||
let currentIncrement = moment(d.to_time).diff(moment(d.from_time),"seconds") || 0;
|
frm.events.update_sub_operation(frm, args);
|
||||||
frm.set_value('current_time' , currentIncrement + (frm.doc.current_time || 0));
|
|
||||||
} else {
|
|
||||||
frm.set_value('started_time' , '');
|
|
||||||
frm.set_value('job_started', 0);
|
|
||||||
frm.set_value('current_time' , 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
frm.save();
|
frappe.call({
|
||||||
|
method: "erpnext.manufacturing.doctype.job_card.job_card.make_time_log",
|
||||||
|
args: {
|
||||||
|
args: args
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
callback: function (r) {
|
||||||
|
frm.reload_doc();
|
||||||
|
frm.trigger("make_dashboard");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
update_sub_operation: function(frm, args) {
|
||||||
|
if (frm.doc.sub_operations && frm.doc.sub_operations.length) {
|
||||||
|
let sub_operations = frm.doc.sub_operations.filter(d => d.status != 'Complete');
|
||||||
|
if (sub_operations && sub_operations.length) {
|
||||||
|
args["sub_operation"] = sub_operations[0].sub_operation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
validate: function(frm) {
|
validate: function(frm) {
|
||||||
@ -180,18 +185,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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
reset_timer: function(frm) {
|
reset_timer: function(frm) {
|
||||||
frm.set_value('started_time' , '');
|
frm.set_value('started_time' , '');
|
||||||
frm.set_value('job_started', 0);
|
|
||||||
frm.set_value('current_time' , 0);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
make_dashboard: function(frm) {
|
make_dashboard: function(frm) {
|
||||||
@ -297,7 +292,6 @@ frappe.ui.form.on('Job Card Time Log', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
to_time: function(frm) {
|
to_time: function(frm) {
|
||||||
frm.set_value('job_started', 0);
|
|
||||||
frm.set_value('started_time', '');
|
frm.set_value('started_time', '');
|
||||||
}
|
}
|
||||||
})
|
})
|
@ -9,30 +9,30 @@
|
|||||||
"naming_series",
|
"naming_series",
|
||||||
"work_order",
|
"work_order",
|
||||||
"bom_no",
|
"bom_no",
|
||||||
"workstation",
|
|
||||||
"operation",
|
|
||||||
"operation_row_number",
|
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"company",
|
"company",
|
||||||
"remarks",
|
|
||||||
"production_section",
|
"production_section",
|
||||||
"production_item",
|
"production_item",
|
||||||
"item_name",
|
"item_name",
|
||||||
"for_quantity",
|
"for_quantity",
|
||||||
"quality_inspection",
|
|
||||||
"wip_warehouse",
|
|
||||||
"column_break_12",
|
"column_break_12",
|
||||||
"employee",
|
"wip_warehouse",
|
||||||
"employee_name",
|
"quality_inspection",
|
||||||
"status",
|
|
||||||
"project",
|
"project",
|
||||||
|
"operation_section_section",
|
||||||
|
"operation",
|
||||||
|
"operation_row_number",
|
||||||
|
"column_break_18",
|
||||||
|
"workstation",
|
||||||
|
"section_break_21",
|
||||||
|
"sub_operations",
|
||||||
"timing_detail",
|
"timing_detail",
|
||||||
"time_logs",
|
"time_logs",
|
||||||
"section_break_13",
|
"section_break_13",
|
||||||
"total_completed_qty",
|
"total_completed_qty",
|
||||||
"total_time_in_mins",
|
|
||||||
"column_break_15",
|
"column_break_15",
|
||||||
|
"total_time_in_mins",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"items",
|
"items",
|
||||||
"more_information",
|
"more_information",
|
||||||
@ -40,7 +40,9 @@
|
|||||||
"sequence_id",
|
"sequence_id",
|
||||||
"transferred_qty",
|
"transferred_qty",
|
||||||
"requested_qty",
|
"requested_qty",
|
||||||
|
"status",
|
||||||
"column_break_20",
|
"column_break_20",
|
||||||
|
"remarks",
|
||||||
"barcode",
|
"barcode",
|
||||||
"job_started",
|
"job_started",
|
||||||
"started_time",
|
"started_time",
|
||||||
@ -117,13 +119,6 @@
|
|||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Timing Detail"
|
"label": "Timing Detail"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "employee",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Employee",
|
|
||||||
"options": "Employee"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 1,
|
"allow_bulk_edit": 1,
|
||||||
"fieldname": "time_logs",
|
"fieldname": "time_logs",
|
||||||
@ -133,9 +128,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_13",
|
"fieldname": "section_break_13",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
"fieldname": "total_completed_qty",
|
"fieldname": "total_completed_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Total Completed Qty",
|
"label": "Total Completed Qty",
|
||||||
@ -251,12 +248,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "employee.employee_name",
|
"collapsible": 1,
|
||||||
"fieldname": "employee_name",
|
|
||||||
"fieldtype": "Read Only",
|
|
||||||
"label": "Employee Name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "production_section",
|
"fieldname": "production_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Production"
|
"label": "Production"
|
||||||
@ -314,11 +306,33 @@
|
|||||||
"label": "Quality Inspection",
|
"label": "Quality Inspection",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Quality Inspection"
|
"options": "Quality Inspection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 1,
|
||||||
|
"fieldname": "sub_operations",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Sub Operations",
|
||||||
|
"options": "Job Card Operation",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "operation_section_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Operation Section"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_18",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_21",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-19 18:26:50.531664",
|
"modified": "2020-12-14 15:14:05.566271",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Job Card",
|
"name": "Job Card",
|
||||||
|
@ -4,12 +4,13 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
import datetime
|
import datetime, json
|
||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
|
from six import string_types
|
||||||
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,
|
||||||
get_time, add_to_date, time_diff, add_days, get_datetime_str, get_link_to_form)
|
get_time, add_to_date, time_diff, add_days, get_datetime_str, get_link_to_form, time_diff_in_seconds)
|
||||||
|
|
||||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||||
|
|
||||||
@ -25,9 +26,20 @@ class JobCard(Document):
|
|||||||
self.set_status()
|
self.set_status()
|
||||||
self.validate_operation_id()
|
self.validate_operation_id()
|
||||||
self.validate_sequence_id()
|
self.validate_sequence_id()
|
||||||
|
self.get_sub_operations()
|
||||||
|
self.update_sub_operation_status()
|
||||||
|
|
||||||
|
def get_sub_operations(self):
|
||||||
|
if self.operation:
|
||||||
|
self.sub_operations = []
|
||||||
|
for row in frappe.get_all("Sub Operation",
|
||||||
|
filters = {"parent": self.operation}, fields=["operation"]):
|
||||||
|
self.append("sub_operations", {
|
||||||
|
"sub_operation": row.operation,
|
||||||
|
"status": "Pending"
|
||||||
|
})
|
||||||
|
|
||||||
def validate_time_logs(self):
|
def validate_time_logs(self):
|
||||||
self.total_completed_qty = 0.0
|
|
||||||
self.total_time_in_mins = 0.0
|
self.total_time_in_mins = 0.0
|
||||||
|
|
||||||
if self.get('time_logs'):
|
if self.get('time_logs'):
|
||||||
@ -46,6 +58,8 @@ class JobCard(Document):
|
|||||||
|
|
||||||
if d.completed_qty:
|
if d.completed_qty:
|
||||||
self.total_completed_qty += d.completed_qty
|
self.total_completed_qty += d.completed_qty
|
||||||
|
else:
|
||||||
|
self.total_completed_qty = 0.0
|
||||||
|
|
||||||
self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty"))
|
self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty"))
|
||||||
|
|
||||||
@ -57,7 +71,7 @@ class JobCard(Document):
|
|||||||
self.workstation, 'production_capacity') or 1
|
self.workstation, 'production_capacity') or 1
|
||||||
validate_overlap_for = " and jc.workstation = %(workstation)s "
|
validate_overlap_for = " and jc.workstation = %(workstation)s "
|
||||||
|
|
||||||
if self.employee:
|
if args.get("employee"):
|
||||||
# override capacity for employee
|
# override capacity for employee
|
||||||
production_capacity = 1
|
production_capacity = 1
|
||||||
validate_overlap_for = " and jc.employee = %(employee)s "
|
validate_overlap_for = " and jc.employee = %(employee)s "
|
||||||
@ -80,7 +94,7 @@ class JobCard(Document):
|
|||||||
"to_time": args.to_time,
|
"to_time": args.to_time,
|
||||||
"name": args.name or "No Name",
|
"name": args.name or "No Name",
|
||||||
"parent": args.parent or "No Name",
|
"parent": args.parent or "No Name",
|
||||||
"employee": self.employee,
|
"employee": args.get("employee"),
|
||||||
"workstation": self.workstation
|
"workstation": self.workstation
|
||||||
}, as_dict=True)
|
}, as_dict=True)
|
||||||
|
|
||||||
@ -158,6 +172,66 @@ class JobCard(Document):
|
|||||||
row.planned_start_time = datetime.datetime.combine(start_date,
|
row.planned_start_time = datetime.datetime.combine(start_date,
|
||||||
get_time(workstation_doc.working_hours[0].start_time))
|
get_time(workstation_doc.working_hours[0].start_time))
|
||||||
|
|
||||||
|
def add_time_log(self, args):
|
||||||
|
last_row = []
|
||||||
|
if self.time_logs and len(self.time_logs) > 0:
|
||||||
|
last_row = self.time_logs[-1]
|
||||||
|
|
||||||
|
self.reset_timer_value(args)
|
||||||
|
if last_row and args.get("complete_time"):
|
||||||
|
last_row.update({
|
||||||
|
"to_time": get_datetime(args.get("complete_time")),
|
||||||
|
"operation": args.get("sub_operation"),
|
||||||
|
"completed_qty": args.get("completed_qty") or 0.0
|
||||||
|
})
|
||||||
|
elif args.get("start_time"):
|
||||||
|
self.append("time_logs", {
|
||||||
|
"from_time": get_datetime(args.get("start_time")),
|
||||||
|
"employee": args.get("employee"),
|
||||||
|
"operation": args.get("sub_operation"),
|
||||||
|
"completed_qty": 0.0
|
||||||
|
})
|
||||||
|
|
||||||
|
if self.status == "On Hold":
|
||||||
|
self.current_time = time_diff_in_seconds(last_row.to_time, last_row.from_time)
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def reset_timer_value(self, args):
|
||||||
|
self.started_time = None
|
||||||
|
|
||||||
|
if args.get("status") in ["Work In Progress", "Complete"]:
|
||||||
|
self.current_time = 0.0
|
||||||
|
|
||||||
|
if args.get("status") == "Work In Progress":
|
||||||
|
self.started_time = get_datetime(args.get("start_time"))
|
||||||
|
|
||||||
|
if args.get("status") == "Resume Job":
|
||||||
|
args["status"] = "Work In Progress"
|
||||||
|
|
||||||
|
if args.get("status"):
|
||||||
|
self.status = args.get("status")
|
||||||
|
|
||||||
|
def update_sub_operation_status(self):
|
||||||
|
if not (self.sub_operations and self.time_logs): return
|
||||||
|
|
||||||
|
operation_wise_completed_time = {}
|
||||||
|
for time_log in self.time_logs:
|
||||||
|
if time_log.operation not in operation_wise_completed_time:
|
||||||
|
operation_wise_completed_time.setdefault(time_log.operation,
|
||||||
|
frappe._dict({"status": "Pending", "completed_time": 0.0}))
|
||||||
|
|
||||||
|
op_row = operation_wise_completed_time[time_log.operation]
|
||||||
|
op_row.status = "Work In Progress" if not time_log.time_in_mins else "Complete"
|
||||||
|
if time_log.time_in_mins:
|
||||||
|
op_row.completed_time += time_log.time_in_mins
|
||||||
|
|
||||||
|
for row in self.sub_operations:
|
||||||
|
operation_deatils = operation_wise_completed_time.get(row.sub_operation)
|
||||||
|
if operation_deatils:
|
||||||
|
row.status = operation_deatils.status
|
||||||
|
row.completed_time = operation_deatils.completed_time
|
||||||
|
|
||||||
def update_time_logs(self, row):
|
def update_time_logs(self, row):
|
||||||
self.append("time_logs", {
|
self.append("time_logs", {
|
||||||
"from_time": row.planned_start_time,
|
"from_time": row.planned_start_time,
|
||||||
@ -376,6 +450,17 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def make_time_log(args):
|
||||||
|
if isinstance(args, string_types):
|
||||||
|
args = json.loads(args)
|
||||||
|
|
||||||
|
args = frappe._dict(args)
|
||||||
|
doc = frappe.get_doc("Job Card", args.job_card_id)
|
||||||
|
doc.validate_sequence_id()
|
||||||
|
doc.add_time_log(args)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_operation_details(work_order, operation):
|
def get_operation_details(work_order, operation):
|
||||||
if work_order and operation:
|
if work_order and operation:
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-12-07 16:58:38.449041",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"sub_operation",
|
||||||
|
"completed_time",
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"default": "Pending",
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Complete\nPause\nPending\nWork In Progress",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "In mins",
|
||||||
|
"fieldname": "completed_time",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Completed Time",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sub_operation",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Operation",
|
||||||
|
"options": "Operation",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-12-14 17:08:25.992957",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Manufacturing",
|
||||||
|
"name": "Job Card Operation",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class JobCardOperation(Document):
|
||||||
|
pass
|
@ -1,14 +1,17 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2019-03-08 23:56:43.187569",
|
"creation": "2019-03-08 23:56:43.187569",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"employee",
|
||||||
"from_time",
|
"from_time",
|
||||||
"to_time",
|
"to_time",
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"time_in_mins",
|
"time_in_mins",
|
||||||
"completed_qty"
|
"completed_qty",
|
||||||
|
"operation"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -41,10 +44,27 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Completed Qty",
|
"label": "Completed Qty",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "employee",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Employee",
|
||||||
|
"options": "Employee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "operation",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Operation",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Operation",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-12-03 12:56:02.285448",
|
"links": [],
|
||||||
|
"modified": "2020-12-23 14:30:00.970916",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Job Card Time Log",
|
"name": "Job Card Time Log",
|
||||||
|
@ -26,7 +26,9 @@
|
|||||||
"column_break_16",
|
"column_break_16",
|
||||||
"overproduction_percentage_for_work_order",
|
"overproduction_percentage_for_work_order",
|
||||||
"other_settings_section",
|
"other_settings_section",
|
||||||
"update_bom_costs_automatically"
|
"update_bom_costs_automatically",
|
||||||
|
"column_break_23",
|
||||||
|
"make_serial_no_batch_from_work_order"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -155,13 +157,24 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_5",
|
"fieldname": "column_break_5",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_23",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "System will automatically create the serial numbers / batch for the Finished Good on submission of work order",
|
||||||
|
"fieldname": "make_serial_no_batch_from_work_order",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Make Serial No / Batch from Work Order"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-wrench",
|
"icon": "icon-wrench",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-13 10:55:43.996581",
|
"modified": "2020-12-08 13:37:40.325838",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Manufacturing Settings",
|
"name": "Manufacturing Settings",
|
||||||
|
@ -2,7 +2,5 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Operation', {
|
frappe.ui.form.on('Operation', {
|
||||||
refresh: function(frm) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
@ -1,167 +1,133 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "Prompt",
|
"autoname": "Prompt",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2014-11-07 16:20:30.683186",
|
"creation": "2014-11-07 16:20:30.683186",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"editable_grid": 0,
|
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"workstation",
|
||||||
|
"data_2",
|
||||||
|
"cost_of_poor_quality_operation",
|
||||||
|
"job_card_section",
|
||||||
|
"create_job_card_based_on_batch_size",
|
||||||
|
"column_break_6",
|
||||||
|
"batch_size",
|
||||||
|
"sub_operations_section",
|
||||||
|
"sub_operations",
|
||||||
|
"total_operation_time",
|
||||||
|
"section_break_4",
|
||||||
|
"description"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "workstation",
|
"fieldname": "workstation",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Default Workstation",
|
"label": "Default Workstation",
|
||||||
"length": 0,
|
"options": "Workstation"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Workstation",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"collapsible": 1,
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_4",
|
"fieldname": "section_break_4",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"label": "Operation Description"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"hidden": 0,
|
"label": "Description"
|
||||||
"ignore_user_permissions": 0,
|
},
|
||||||
"ignore_xss_filter": 0,
|
{
|
||||||
"in_filter": 0,
|
"collapsible": 1,
|
||||||
"in_list_view": 0,
|
"fieldname": "sub_operations_section",
|
||||||
"in_standard_filter": 0,
|
"fieldtype": "Section Break",
|
||||||
"label": "Description",
|
"label": "Sub Operations"
|
||||||
"length": 0,
|
},
|
||||||
"no_copy": 0,
|
{
|
||||||
"permlevel": 0,
|
"fieldname": "sub_operations",
|
||||||
"precision": "",
|
"fieldtype": "Table",
|
||||||
"print_hide": 0,
|
"options": "Sub Operation"
|
||||||
"print_hide_if_no_value": 0,
|
},
|
||||||
"read_only": 0,
|
{
|
||||||
"remember_last_selected_value": 0,
|
"description": "Time in mins.",
|
||||||
"report_hide": 0,
|
"fieldname": "total_operation_time",
|
||||||
"reqd": 0,
|
"fieldtype": "Float",
|
||||||
"search_index": 0,
|
"label": "Total Operation Time",
|
||||||
"set_only_once": 0,
|
"read_only": 1
|
||||||
"unique": 0
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "data_2",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"depends_on": "create_job_card_based_on_batch_size",
|
||||||
|
"fieldname": "batch_size",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Batch Size",
|
||||||
|
"mandatory_depends_on": "create_job_card_based_on_batch_size"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "create_job_card_based_on_batch_size",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Create Job Card based on Batch Size"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Cost of poor quality operation",
|
||||||
|
"fieldname": "cost_of_poor_quality_operation",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is COPQ Operation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "job_card_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Job Card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_6",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "fa fa-wrench",
|
"icon": "fa fa-wrench",
|
||||||
"idx": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"image_view": 0,
|
"links": [],
|
||||||
"in_create": 0,
|
"modified": "2020-12-24 14:25:03.428303",
|
||||||
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2016-11-07 05:28:27.462413",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Operation",
|
"name": "Operation",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 0,
|
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 1,
|
"import": 1,
|
||||||
"is_custom": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 0,
|
|
||||||
"role": "Manufacturing User",
|
"role": "Manufacturing User",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 0,
|
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 1,
|
"import": 1,
|
||||||
"is_custom": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Manufacturing Manager",
|
"role": "Manufacturing Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_seen": 0
|
"track_changes": 1
|
||||||
}
|
}
|
@ -2,9 +2,35 @@
|
|||||||
# 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 import _
|
||||||
|
from frappe.utils import flt
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class Operation(Document):
|
class Operation(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if not self.description:
|
if not self.description:
|
||||||
self.description = self.name
|
self.description = self.name
|
||||||
|
|
||||||
|
self.duplicate_sub_operation()
|
||||||
|
self.set_total_time()
|
||||||
|
|
||||||
|
def duplicate_sub_operation(self):
|
||||||
|
operation_list = []
|
||||||
|
for row in self.sub_operations:
|
||||||
|
if row.operation in operation_list:
|
||||||
|
frappe.throw(_("The operation {0} can not add multiple times")
|
||||||
|
.format(frappe.bold(row.operation)))
|
||||||
|
|
||||||
|
if self.name == row.operation:
|
||||||
|
frappe.throw(_("The operation {0} can not be the sub operation")
|
||||||
|
.format(frappe.bold(row.operation)))
|
||||||
|
|
||||||
|
operation_list.append(row.operation)
|
||||||
|
|
||||||
|
def set_total_time(self):
|
||||||
|
self.total_operation_time = 0.0
|
||||||
|
|
||||||
|
for row in self.sub_operations:
|
||||||
|
self.total_operation_time += row.time_in_mins
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Sub Operation', {
|
||||||
|
// refresh: function(frm) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-12-07 15:39:47.488519",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"operation",
|
||||||
|
"time_in_mins",
|
||||||
|
"column_break_5",
|
||||||
|
"description"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "operation",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Operation",
|
||||||
|
"options": "Operation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Time in mins",
|
||||||
|
"fieldname": "time_in_mins",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Operation Time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_5",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Description"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-12-07 18:09:18.005578",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Manufacturing",
|
||||||
|
"name": "Sub Operation",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
10
erpnext/manufacturing/doctype/sub_operation/sub_operation.py
Normal file
10
erpnext/manufacturing/doctype/sub_operation/sub_operation.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class SubOperation(Document):
|
||||||
|
pass
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestSubOperation(unittest.TestCase):
|
||||||
|
pass
|
@ -141,8 +141,7 @@ frappe.ui.form.on("Work Order", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.docstatus === 1
|
if (frm.doc.docstatus === 1
|
||||||
&& frm.doc.operations && frm.doc.operations.length
|
&& frm.doc.operations && frm.doc.operations.length) {
|
||||||
&& frm.doc.qty != frm.doc.material_transferred_for_manufacturing) {
|
|
||||||
|
|
||||||
const not_completed = frm.doc.operations.filter(d => {
|
const not_completed = frm.doc.operations.filter(d => {
|
||||||
if(d.status != 'Completed') {
|
if(d.status != 'Completed') {
|
||||||
|
@ -21,6 +21,13 @@
|
|||||||
"produced_qty",
|
"produced_qty",
|
||||||
"sales_order",
|
"sales_order",
|
||||||
"project",
|
"project",
|
||||||
|
"serial_no_and_batch_for_finished_good_section",
|
||||||
|
"has_serial_no",
|
||||||
|
"has_batch_no",
|
||||||
|
"column_break_17",
|
||||||
|
"serial_no",
|
||||||
|
"batch_size",
|
||||||
|
"batches",
|
||||||
"settings_section",
|
"settings_section",
|
||||||
"allow_alternative_item",
|
"allow_alternative_item",
|
||||||
"use_multi_level_bom",
|
"use_multi_level_bom",
|
||||||
@ -488,6 +495,54 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Lead Time",
|
"label": "Lead Time",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
|
"fieldname": "serial_no_and_batch_for_finished_good_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Serial No and Batch for Finished Good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_17",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fetch_from": "production_item.has_serial_no",
|
||||||
|
"fieldname": "has_serial_no",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Has Serial No",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fetch_from": "production_item.has_batch_no",
|
||||||
|
"fieldname": "has_batch_no",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Has Batch No",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "has_serial_no",
|
||||||
|
"fieldname": "serial_no",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Serial Nos"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "has_batch_no",
|
||||||
|
"fieldname": "batch_size",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Batch Size"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "has_batch_no",
|
||||||
|
"fieldname": "batches",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Batches",
|
||||||
|
"options": "Work Order Batch",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cogs",
|
"icon": "fa fa-cogs",
|
||||||
|
@ -19,6 +19,8 @@ from frappe.utils.csvutils import getlink
|
|||||||
from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty
|
from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty
|
||||||
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
from erpnext.stock.doctype.batch.batch import make_batch
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_auto_serial_nos, auto_make_serial_nos
|
||||||
|
|
||||||
class OverProductionError(frappe.ValidationError): pass
|
class OverProductionError(frappe.ValidationError): pass
|
||||||
class CapacityError(frappe.ValidationError): pass
|
class CapacityError(frappe.ValidationError): pass
|
||||||
@ -40,6 +42,7 @@ class WorkOrder(Document):
|
|||||||
self.set_onload("overproduction_percentage", ms.overproduction_percentage_for_work_order)
|
self.set_onload("overproduction_percentage", ms.overproduction_percentage_for_work_order)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.set("batches", [])
|
||||||
self.validate_production_item()
|
self.validate_production_item()
|
||||||
if self.bom_no:
|
if self.bom_no:
|
||||||
validate_bom_no(self.production_item, self.bom_no)
|
validate_bom_no(self.production_item, self.bom_no)
|
||||||
@ -235,6 +238,9 @@ class WorkOrder(Document):
|
|||||||
|
|
||||||
production_plan.run_method("update_produced_qty", produced_qty, self.production_plan_item)
|
production_plan.run_method("update_produced_qty", produced_qty, self.production_plan_item)
|
||||||
|
|
||||||
|
def before_submit(self):
|
||||||
|
self.create_serial_no_batch_no()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if not self.wip_warehouse:
|
if not self.wip_warehouse:
|
||||||
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
|
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
|
||||||
@ -266,6 +272,67 @@ class WorkOrder(Document):
|
|||||||
self.update_planned_qty()
|
self.update_planned_qty()
|
||||||
self.update_ordered_qty()
|
self.update_ordered_qty()
|
||||||
self.update_reserved_qty_for_production()
|
self.update_reserved_qty_for_production()
|
||||||
|
self.delete_auto_created_batch_and_serial_no()
|
||||||
|
|
||||||
|
def create_serial_no_batch_no(self):
|
||||||
|
if not (self.has_serial_no or self.has_batch_no): return
|
||||||
|
|
||||||
|
if not cint(frappe.db.get_single_value("Manufacturing Settings",
|
||||||
|
"make_serial_no_batch_from_work_order")): return
|
||||||
|
|
||||||
|
if self.has_batch_no:
|
||||||
|
self.set("batches", [])
|
||||||
|
self.create_batch_for_finished_good()
|
||||||
|
|
||||||
|
args = {"item_code": self.production_item}
|
||||||
|
|
||||||
|
if self.has_serial_no:
|
||||||
|
self.make_serial_nos(args)
|
||||||
|
|
||||||
|
def create_batch_for_finished_good(self):
|
||||||
|
total_qty = self.qty
|
||||||
|
if not self.batch_size:
|
||||||
|
self.batch_size = total_qty
|
||||||
|
|
||||||
|
while total_qty > 0:
|
||||||
|
qty = self.batch_size
|
||||||
|
if self.batch_size >= total_qty:
|
||||||
|
qty = total_qty
|
||||||
|
|
||||||
|
if total_qty > self.batch_size:
|
||||||
|
total_qty -= self.batch_size
|
||||||
|
else:
|
||||||
|
qty = total_qty
|
||||||
|
total_qty = 0
|
||||||
|
|
||||||
|
batch = make_batch(self.production_item)
|
||||||
|
self.append("batches", {
|
||||||
|
"batch_no": batch,
|
||||||
|
"qty": qty,
|
||||||
|
})
|
||||||
|
|
||||||
|
def delete_auto_created_batch_and_serial_no(self):
|
||||||
|
if self.serial_no:
|
||||||
|
for d in get_serial_nos(self.serial_no):
|
||||||
|
frappe.delete_doc("Serial No", d)
|
||||||
|
|
||||||
|
for row in self.batches:
|
||||||
|
batch_no = row.batch_no
|
||||||
|
row.db_set("batch_no", None)
|
||||||
|
frappe.delete_doc("Batch", batch_no)
|
||||||
|
|
||||||
|
def make_serial_nos(self, args):
|
||||||
|
serial_no_series = frappe.get_cached_value("Item", self.production_item, "serial_no_series")
|
||||||
|
if serial_no_series:
|
||||||
|
self.serial_no = get_auto_serial_nos(serial_no_series, self.qty)
|
||||||
|
elif self.serial_no:
|
||||||
|
args.update({"serial_no": self.serial_no, "actual_qty": self.qty, "batch_no": self.batch_no})
|
||||||
|
self.serial_no = auto_make_serial_nos(args)
|
||||||
|
|
||||||
|
serial_nos_length = len(get_serial_nos(self.serial_no))
|
||||||
|
if serial_nos_length != self.qty:
|
||||||
|
frappe.throw(_("{0} Serial Numbers required for Item {1}. You have provided {2}.")
|
||||||
|
.format(self.qty, self.production_item, serial_nos_length), SerialNoQtyError)
|
||||||
|
|
||||||
def create_job_card(self):
|
def create_job_card(self):
|
||||||
manufacturing_settings_doc = frappe.get_doc("Manufacturing Settings")
|
manufacturing_settings_doc = frappe.get_doc("Manufacturing Settings")
|
||||||
@ -273,15 +340,38 @@ class WorkOrder(Document):
|
|||||||
enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning)
|
enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning)
|
||||||
plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30
|
plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30
|
||||||
|
|
||||||
for i, row in enumerate(self.operations):
|
for index, row in enumerate(self.operations):
|
||||||
self.set_operation_start_end_time(i, row)
|
qty = self.qty
|
||||||
|
i=0
|
||||||
|
while qty > 0:
|
||||||
|
i += 1
|
||||||
|
if not cint(frappe.db.get_value("Operation",
|
||||||
|
row.operation, "create_job_card_based_on_batch_size")):
|
||||||
|
row.batch_size = self.qty
|
||||||
|
|
||||||
|
job_card_qty = row.batch_size
|
||||||
|
if row.batch_size and qty >= row.batch_size:
|
||||||
|
qty -= row.batch_size
|
||||||
|
elif qty > 0:
|
||||||
|
job_card_qty = qty
|
||||||
|
|
||||||
|
if job_card_qty > 0:
|
||||||
|
self.prepare_data_for_job_card(row, job_card_qty, index,
|
||||||
|
plan_days, enable_capacity_planning)
|
||||||
|
|
||||||
|
planned_end_date = self.operations and self.operations[-1].planned_end_time
|
||||||
|
if planned_end_date:
|
||||||
|
self.db_set("planned_end_date", planned_end_date)
|
||||||
|
|
||||||
|
def prepare_data_for_job_card(self, row, job_card_qty, index, plan_days, enable_capacity_planning):
|
||||||
|
self.set_operation_start_end_time(index, row)
|
||||||
|
|
||||||
if not row.workstation:
|
if not row.workstation:
|
||||||
frappe.throw(_("Row {0}: select the workstation against the operation {1}")
|
frappe.throw(_("Row {0}: select the workstation against the operation {1}")
|
||||||
.format(row.idx, row.operation))
|
.format(row.idx, row.operation))
|
||||||
|
|
||||||
original_start_time = row.planned_start_time
|
original_start_time = row.planned_start_time
|
||||||
job_card_doc = create_job_card(self, row,
|
job_card_doc = create_job_card(self, row, qty=job_card_qty,
|
||||||
enable_capacity_planning=enable_capacity_planning, auto_create=True)
|
enable_capacity_planning=enable_capacity_planning, auto_create=True)
|
||||||
|
|
||||||
if enable_capacity_planning and job_card_doc:
|
if enable_capacity_planning and job_card_doc:
|
||||||
@ -295,10 +385,6 @@ class WorkOrder(Document):
|
|||||||
|
|
||||||
row.db_update()
|
row.db_update()
|
||||||
|
|
||||||
planned_end_date = self.operations and self.operations[-1].planned_end_time
|
|
||||||
if planned_end_date:
|
|
||||||
self.db_set("planned_end_date", planned_end_date)
|
|
||||||
|
|
||||||
def set_operation_start_end_time(self, idx, row):
|
def set_operation_start_end_time(self, idx, row):
|
||||||
"""Set start and end time for given operation. If first operation, set start as
|
"""Set start and end time for given operation. If first operation, set start as
|
||||||
`planned_start_date`, else add time diff to end time of earlier operation."""
|
`planned_start_date`, else add time diff to end time of earlier operation."""
|
||||||
@ -669,6 +755,15 @@ class WorkOrder(Document):
|
|||||||
bom.set_bom_material_details()
|
bom.set_bom_material_details()
|
||||||
return bom
|
return bom
|
||||||
|
|
||||||
|
def update_batch_qty(self):
|
||||||
|
if self.has_batch_no and self.batches:
|
||||||
|
for row in self.batches:
|
||||||
|
qty = frappe.get_all("Stock Entry Detail", fields = ["sum(transfer_qty)"],
|
||||||
|
filters = {"docstatus": 1, "batch_no": row.batch_no, "is_finished_item": 1}, as_list=1)
|
||||||
|
|
||||||
|
if qty:
|
||||||
|
frappe.db.set_value("Work Order Batch", row.name, "produced_qty", flt(qty[0][0]))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_bom_operations(doctype, txt, searchfield, start, page_len, filters):
|
def get_bom_operations(doctype, txt, searchfield, start, page_len, filters):
|
||||||
@ -826,6 +921,7 @@ def make_stock_entry(work_order_id, purpose, qty=None):
|
|||||||
|
|
||||||
stock_entry.set_stock_entry_type()
|
stock_entry.set_stock_entry_type()
|
||||||
stock_entry.get_items()
|
stock_entry.get_items()
|
||||||
|
stock_entry.set_serial_no_batch_for_finished_good()
|
||||||
return stock_entry.as_dict()
|
return stock_entry.as_dict()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-01-04 16:42:39.347528",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"batch_no",
|
||||||
|
"qty",
|
||||||
|
"produced_qty"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "batch_no",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Batch No",
|
||||||
|
"options": "Batch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Qty",
|
||||||
|
"non_negative": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "produced_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Produced Qty",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-01-05 10:57:07.278399",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Manufacturing",
|
||||||
|
"name": "Work Order Batch",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class WorkOrderBatch(Document):
|
||||||
|
pass
|
@ -309,3 +309,10 @@ def validate_serial_no_with_batch(serial_nos, item_code):
|
|||||||
message = "Serial Nos" if len(serial_nos) > 1 else "Serial No"
|
message = "Serial Nos" if len(serial_nos) > 1 else "Serial No"
|
||||||
frappe.throw(_("There is no batch found against the {0}: {1}")
|
frappe.throw(_("There is no batch found against the {0}: {1}")
|
||||||
.format(message, serial_no_link))
|
.format(message, serial_no_link))
|
||||||
|
|
||||||
|
def make_batch(item_code):
|
||||||
|
if frappe.db.get_value("Item", item_code, "has_batch_no"):
|
||||||
|
doc = frappe.new_doc("Batch")
|
||||||
|
doc.item = item_code
|
||||||
|
doc.save()
|
||||||
|
return doc.name
|
@ -498,6 +498,7 @@ class StockEntry(StockController):
|
|||||||
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
|
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
|
||||||
if not d.t_warehouse:
|
if not d.t_warehouse:
|
||||||
outgoing_items_cost += flt(d.basic_amount)
|
outgoing_items_cost += flt(d.basic_amount)
|
||||||
|
|
||||||
return outgoing_items_cost
|
return outgoing_items_cost
|
||||||
|
|
||||||
def get_args_for_incoming_rate(self, item):
|
def get_args_for_incoming_rate(self, item):
|
||||||
@ -854,6 +855,7 @@ class StockEntry(StockController):
|
|||||||
pro_doc.run_method("update_work_order_qty")
|
pro_doc.run_method("update_work_order_qty")
|
||||||
if self.purpose == "Manufacture":
|
if self.purpose == "Manufacture":
|
||||||
pro_doc.run_method("update_planned_qty")
|
pro_doc.run_method("update_planned_qty")
|
||||||
|
pro_doc.update_batch_qty()
|
||||||
|
|
||||||
if not pro_doc.operations:
|
if not pro_doc.operations:
|
||||||
pro_doc.set_actual_dates()
|
pro_doc.set_actual_dates()
|
||||||
@ -1076,8 +1078,7 @@ class StockEntry(StockController):
|
|||||||
# in case of BOM
|
# in case of BOM
|
||||||
to_warehouse = item.get("default_warehouse")
|
to_warehouse = item.get("default_warehouse")
|
||||||
|
|
||||||
self.add_to_stock_entry_detail({
|
args = {
|
||||||
item.name: {
|
|
||||||
"to_warehouse": to_warehouse,
|
"to_warehouse": to_warehouse,
|
||||||
"from_warehouse": "",
|
"from_warehouse": "",
|
||||||
"qty": self.fg_completed_qty,
|
"qty": self.fg_completed_qty,
|
||||||
@ -1088,6 +1089,34 @@ class StockEntry(StockController):
|
|||||||
"cost_center": item.get("buying_cost_center"),
|
"cost_center": item.get("buying_cost_center"),
|
||||||
"is_finished_item": 1
|
"is_finished_item": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.work_order and self.pro_doc.batches:
|
||||||
|
self.set_batchwise_finished_goods(args, item)
|
||||||
|
else:
|
||||||
|
self.add_finisged_goods(args, item)
|
||||||
|
|
||||||
|
def set_batchwise_finished_goods(self, args, item):
|
||||||
|
qty = flt(self.fg_completed_qty)
|
||||||
|
for row in self.pro_doc.batches:
|
||||||
|
batch_qty = flt(row.qty) - flt(row.produced_qty)
|
||||||
|
if not batch_qty: continue
|
||||||
|
|
||||||
|
if qty <=0:
|
||||||
|
break
|
||||||
|
|
||||||
|
fg_qty = batch_qty
|
||||||
|
if batch_qty >= qty:
|
||||||
|
fg_qty = qty
|
||||||
|
|
||||||
|
qty -= batch_qty
|
||||||
|
args["qty"] = fg_qty
|
||||||
|
args["batch_no"] = row.batch_no
|
||||||
|
|
||||||
|
self.add_finisged_goods(args, item)
|
||||||
|
|
||||||
|
def add_finisged_goods(self, args, item):
|
||||||
|
self.add_to_stock_entry_detail({
|
||||||
|
item.name: args
|
||||||
}, bom_no = self.bom_no)
|
}, bom_no = self.bom_no)
|
||||||
|
|
||||||
def get_bom_raw_materials(self, qty):
|
def get_bom_raw_materials(self, qty):
|
||||||
@ -1524,6 +1553,36 @@ class StockEntry(StockController):
|
|||||||
material_requests.append(material_request)
|
material_requests.append(material_request)
|
||||||
frappe.db.set_value('Material Request', material_request, 'transfer_status', status)
|
frappe.db.set_value('Material Request', material_request, 'transfer_status', status)
|
||||||
|
|
||||||
|
def set_serial_no_batch_for_finished_good(self):
|
||||||
|
args = {}
|
||||||
|
if self.pro_doc.serial_no or self.pro_doc.batch_no:
|
||||||
|
self.get_serial_nos_for_fg(args)
|
||||||
|
|
||||||
|
for row in self.items:
|
||||||
|
if row.is_finished_item and row.item_code == self.pro_doc.production_item:
|
||||||
|
if args.get("serial_no"):
|
||||||
|
row.serial_no = '\n'.join(args["serial_no"][0: cint(row.qty)])
|
||||||
|
|
||||||
|
def get_serial_nos_for_fg(self, args):
|
||||||
|
fields = ["`tabStock Entry`.`name`", "`tabStock Entry Detail`.`qty`",
|
||||||
|
"`tabStock Entry Detail`.`serial_no`", "`tabStock Entry Detail`.`batch_no`"]
|
||||||
|
|
||||||
|
filters = [["Stock Entry","work_order","=",self.work_order], ["Stock Entry","purpose","=","Manufacture"],
|
||||||
|
["Stock Entry","docstatus","=",1], ["Stock Entry Detail","item_code","=",self.pro_doc.production_item]]
|
||||||
|
|
||||||
|
stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
|
||||||
|
|
||||||
|
if self.pro_doc.serial_no:
|
||||||
|
args["serial_no"] = self.get_available_serial_nos(stock_entries)
|
||||||
|
|
||||||
|
def get_available_serial_nos(self, stock_entries):
|
||||||
|
used_serial_nos = []
|
||||||
|
for row in stock_entries:
|
||||||
|
if row.serial_no:
|
||||||
|
used_serial_nos.extend(get_serial_nos(row.serial_no))
|
||||||
|
|
||||||
|
return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos)))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def move_sample_to_retention_warehouse(company, items):
|
def move_sample_to_retention_warehouse(company, items):
|
||||||
if isinstance(items, string_types):
|
if isinstance(items, string_types):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user