fix: Manufacturing UX, added calendar view for job card

This commit is contained in:
Rohit Waghchaure 2019-12-02 09:37:09 +05:30
parent 36b60dbbef
commit 8ab8376975
17 changed files with 715 additions and 1685 deletions

View File

@ -6,7 +6,6 @@ frappe.provide("erpnext.bom");
frappe.ui.form.on("BOM", {
setup: function(frm) {
frm.custom_make_buttons = {
'BOM': 'Duplicate BOM',
'Work Order': 'Work Order',
'Quality Inspection': 'Quality Inspection'
};
@ -91,10 +90,6 @@ frappe.ui.form.on("BOM", {
}
if(frm.doc.docstatus!=0) {
frm.add_custom_button(__("Duplicate BOM"), function() {
frm.copy_doc();
}, __("Create"));
frm.add_custom_button(__("Work Order"), function() {
frm.trigger("make_work_order");
}, __("Create"));

View File

@ -25,5 +25,5 @@ def get_data():
}
],
'disable_create_buttons': ["Item", "Purchase Order", "Purchase Receipt",
"Purchase Invoice", "Job Card", "Stock Entry"]
"Purchase Invoice", "Job Card", "Stock Entry", "BOM"]
}

View File

@ -3,6 +3,9 @@
frappe.ui.form.on('Job Card', {
refresh: function(frm) {
frappe.flags.pause_job = 0;
frappe.flags.resume_job = 0;
if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length) {
if (frm.doc.for_quantity != frm.doc.transferred_qty) {
frm.add_custom_button(__("Material Request"), () => {
@ -13,44 +16,99 @@ frappe.ui.form.on('Job Card', {
if (frm.doc.for_quantity != frm.doc.transferred_qty) {
frm.add_custom_button(__("Material Transfer"), () => {
frm.trigger("make_stock_entry");
});
}).addClass("btn-primary");
}
}
if (frm.doc.docstatus == 0) {
frm.trigger("make_dashboard");
if (frm.doc.docstatus == 0 && frm.doc.for_quantity > frm.doc.total_completed_qty
&& (!frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
frm.trigger("prepare_timer_buttons");
}
},
if (!frm.doc.job_started) {
frm.add_custom_button(__("Start Job"), () => {
let row = frappe.model.add_child(frm.doc, 'Job Card Time Log', 'time_logs');
row.from_time = frappe.datetime.now_datetime();
frm.set_value('job_started', 1);
frm.set_value('started_time' , row.from_time);
frm.save();
});
} else {
frm.add_custom_button(__("Complete Job"), () => {
let completed_time = frappe.datetime.now_datetime();
frm.doc.time_logs.forEach(d => {
if (d.from_time && !d.to_time) {
d.to_time = completed_time;
frm.set_value('started_time' , '');
frm.set_value('job_started', 0);
frm.save();
prepare_timer_buttons: function(frm) {
frm.trigger("make_dashboard");
if (!frm.doc.job_started) {
frm.add_custom_button(__("Start"), () => {
if (!frm.doc.employee) {
frappe.prompt({fieldtype: 'Link', label: __('Employee'), options: "Employee",
fieldname: 'employee'}, d => {
if (d.employee) {
frm.set_value("employee", d.employee);
}
})
});
}
frm.events.start_job(frm);
}, __("Enter Value"), __("Start"));
} else {
frm.events.start_job(frm);
}
}).addClass("btn-primary");
} else if (frm.doc.status == "On Hold") {
frm.add_custom_button(__("Resume"), () => {
frappe.flags.resume_job = 1;
frm.events.start_job(frm);
}).addClass("btn-primary");
} else {
frm.add_custom_button(__("Pause"), () => {
frappe.flags.pause_job = 1;
frm.set_value("status", "On Hold");
frm.events.complete_job(frm);
});
frm.add_custom_button(__("Complete"), () => {
let completed_time = frappe.datetime.now_datetime();
frm.trigger("hide_timer");
frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'),
fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => {
frm.events.complete_job(frm, completed_time, data.qty);
}, __("Enter Value"), __("Complete"));
}).addClass("btn-primary");
}
},
start_job: function(frm) {
let row = frappe.model.add_child(frm.doc, 'Job Card Time Log', 'time_logs');
row.from_time = frappe.datetime.now_datetime();
frm.set_value('job_started', 1);
frm.set_value('started_time' , row.from_time);
frm.set_value("status", "Work In Progress");
if (!frappe.flags.resume_job) {
frm.set_value('current_time' , 0);
}
frm.save();
},
complete_job: function(frm, completed_time, completed_qty) {
frm.doc.time_logs.forEach(d => {
if (d.from_time && !d.to_time) {
d.to_time = completed_time || frappe.datetime.now_datetime();
d.completed_qty = completed_qty || 0;
if(frappe.flags.pause_job) {
let currentIncrement = moment(d.to_time).diff(moment(d.from_time),"seconds") || 0;
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();
}
});
},
make_dashboard: function(frm) {
if(frm.doc.__islocal)
return;
frm.dashboard.refresh();
const timer = `
<div class="stopwatch" style="font-weight:bold">
<div class="stopwatch" style="font-weight:bold;margin:0px 13px 0px 2px;
color:#545454;font-size:18px;display:inline-block;vertical-align:text-bottom;>
<span class="hours">00</span>
<span class="colon">:</span>
<span class="minutes">00</span>
@ -58,11 +116,16 @@ frappe.ui.form.on('Job Card', {
<span class="seconds">00</span>
</div>`;
var section = frm.dashboard.add_section(timer);
var section = frm.toolbar.page.add_inner_message(timer);
if (frm.doc.started_time) {
let currentIncrement = moment(frappe.datetime.now_datetime()).diff(moment(frm.doc.started_time),"seconds");
initialiseTimer();
let currentIncrement = frm.doc.current_time || 0;
if (frm.doc.started_time || frm.doc.current_time) {
if (frm.doc.status == "On Hold") {
updateStopwatch(currentIncrement);
} else {
currentIncrement += moment(frappe.datetime.now_datetime()).diff(moment(frm.doc.started_time),"seconds");
initialiseTimer();
}
function initialiseTimer() {
const interval = setInterval(function() {
@ -70,12 +133,12 @@ frappe.ui.form.on('Job Card', {
updateStopwatch(current);
}, 1000);
}
function updateStopwatch(increment) {
var hours = Math.floor(increment / 3600);
var minutes = Math.floor((increment - (hours * 3600)) / 60);
var seconds = increment - (hours * 3600) - (minutes * 60);
$(section).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString());
$(section).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString());
$(section).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString());
@ -88,6 +151,10 @@ frappe.ui.form.on('Job Card', {
}
},
hide_timer: function(frm) {
frm.toolbar.page.inner_toolbar.find(".stopwatch").remove();
},
for_quantity: function(frm) {
frm.doc.items = [];
frm.call({
@ -117,5 +184,22 @@ frappe.ui.form.on('Job Card', {
timer: function(frm) {
return `<button> Start </button>`
},
set_total_completed_qty: function(frm) {
frm.doc.total_completed_qty = 0;
frm.doc.time_logs.forEach(d => {
if (d.completed_qty) {
frm.doc.total_completed_qty += d.completed_qty;
}
});
refresh_field("total_completed_qty");
}
});
});
frappe.ui.form.on('Job Card Time Log', {
completed_qty: function(frm) {
frm.events.set_total_completed_qty(frm);
}
})

View File

@ -13,10 +13,18 @@
"column_break_4",
"posting_date",
"company",
"remarks",
"production_section",
"production_item",
"item_name",
"for_quantity",
"wip_warehouse",
"timing_detail",
"column_break_12",
"employee",
"employee_name",
"status",
"project",
"timing_detail",
"time_logs",
"section_break_13",
"total_completed_qty",
@ -28,12 +36,11 @@
"operation_id",
"transferred_qty",
"requested_qty",
"project",
"remarks",
"column_break_20",
"status",
"barcode",
"job_started",
"started_time",
"current_time",
"amended_from"
],
"fields": [
@ -41,13 +48,14 @@
"fieldname": "work_order",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Work Order",
"options": "Work Order",
"read_only": 1,
"reqd": 1,
"search_index": 1
},
{
"fetch_from": "work_order.bom_no",
"fieldname": "bom_no",
"fieldtype": "Link",
"label": "BOM No",
@ -91,7 +99,7 @@
"fieldname": "for_quantity",
"fieldtype": "Float",
"in_list_view": 1,
"label": "For Quantity",
"label": "Qty To Manufacture",
"reqd": 1
},
{
@ -109,6 +117,7 @@
{
"fieldname": "employee",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Employee",
"options": "Employee"
},
@ -198,7 +207,7 @@
"fieldtype": "Select",
"label": "Status",
"no_copy": 1,
"options": "Open\nWork In Progress\nMaterial Transferred\nSubmitted\nCancelled\nCompleted",
"options": "Open\nWork In Progress\nMaterial Transferred\nOn Hold\nSubmitted\nCancelled\nCompleted",
"read_only": 1
},
{
@ -236,10 +245,52 @@
"label": "Naming Series",
"options": "PO-JOB.#####",
"reqd": 1
},
{
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Read Only",
"label": "Employee Name"
},
{
"fieldname": "production_section",
"fieldtype": "Section Break",
"label": "Production"
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"fetch_from": "work_order.production_item",
"fieldname": "production_item",
"fieldtype": "Read Only",
"label": "Production Item"
},
{
"fieldname": "barcode",
"fieldtype": "Barcode",
"label": "Barcode",
"read_only": 1
},
{
"fetch_from": "work_order.item_name",
"fieldname": "item_name",
"fieldtype": "Read Only",
"label": "Item Name"
},
{
"fieldname": "current_time",
"fieldtype": "Int",
"hidden": 1,
"label": "Current Time",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"is_submittable": 1,
"modified": "2019-10-30 01:49:19.606178",
"modified": "2019-12-03 13:08:57.926201",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",

View File

@ -194,8 +194,9 @@ class JobCard(Document):
if self.total_completed_qty <= 0.0:
frappe.throw(_("Total completed qty must be greater than zero"))
if self.total_completed_qty > self.for_quantity:
frappe.throw(_("Total completed qty can not be greater than for quantity"))
if self.total_completed_qty != self.for_quantity:
frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})"
.format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity))))
def update_work_order(self):
if not self.work_order:
@ -271,6 +272,8 @@ class JobCard(Document):
self.set_status(update_status)
def set_status(self, update_status=False):
if self.status == "On Hold": return
self.status = {
0: "Open",
1: "Submitted",
@ -329,6 +332,7 @@ def make_stock_entry(source_name, target_doc=None):
target.fg_completed_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0)
target.calculate_rate_and_amount()
target.set_missing_values()
target.set_stock_entry_type()
doclist = get_mapped_doc("Job Card", source_name, {
"Job Card": {
@ -352,4 +356,46 @@ def make_stock_entry(source_name, target_doc=None):
return doclist
def time_diff_in_minutes(string_ed_date, string_st_date):
return time_diff(string_ed_date, string_st_date).total_seconds() / 60
return time_diff(string_ed_date, string_st_date).total_seconds() / 60
@frappe.whitelist()
def get_job_details(start, end, filters=None):
events = []
event_color = {
"Completed": "#cdf5a6",
"Material Transferred": "#ffdd9e",
"Work In Progress": "#D3D3D3"
}
from frappe.desk.reportview import get_filters_cond
conditions = get_filters_cond("Job Card", filters, [])
job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order,
`tabJob Card`.employee_name, `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''),
min(`tabJob Card Time Log`.from_time) as from_time,
max(`tabJob Card Time Log`.to_time) as to_time
FROM `tabJob Card` , `tabJob Card Time Log`
WHERE
`tabJob Card`.name = `tabJob Card Time Log`.parent {0}
group by `tabJob Card`.name""".format(conditions), as_dict=1)
for d in job_cards:
subject_data = []
for field in ["name", "work_order", "remarks", "employee_name"]:
if not d.get(field): continue
subject_data.append(d.get(field))
color = event_color.get(d.status)
job_card_data = {
'from_time': d.from_time,
'to_time': d.to_time,
'name': d.name,
'subject': '\n'.join(subject_data),
'color': color if color else "#89bcde"
}
events.append(job_card_data)
return events

View File

@ -0,0 +1,21 @@
frappe.views.calendar["Job Card"] = {
field_map: {
"start": "from_time",
"end": "to_time",
"id": "name",
"title": "subject",
"color": "color",
"allDay": "allDay",
"progress": "progress"
},
gantt: true,
filters: [
{
"fieldtype": "Link",
"fieldname": "employee",
"options": "Employee",
"label": __("Employee")
}
],
get_events_method: "erpnext.manufacturing.doctype.job_card.job_card.get_job_details"
};

View File

@ -1,208 +1,57 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2019-03-08 23:56:43.187569",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"creation": "2019-03-08 23:56:43.187569",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"from_time",
"to_time",
"column_break_2",
"time_in_mins",
"completed_qty"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "from_time",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "From Time",
"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,
"translatable": 0,
"unique": 0
},
"fieldname": "from_time",
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "From Time"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "to_time",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "To Time",
"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,
"translatable": 0,
"unique": 0
},
"fieldname": "to_time",
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "To Time"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 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,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "time_in_mins",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Time In Mins",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "time_in_mins",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Time In Mins",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fetch_if_empty": 0,
"fieldname": "completed_qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Completed Qty",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"default": "0",
"fieldname": "completed_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Completed Qty",
"reqd": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-03-10 17:08:46.504910",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card Time Log",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"istable": 1,
"modified": "2019-12-03 12:56:02.285448",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card Time Log",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}

View File

@ -71,12 +71,13 @@ frappe.ui.form.on('Production Plan', {
}, __('Create'));
}
frm.page.set_inner_btn_group_as_primary(__('Create'));
frm.trigger("material_requirement");
const projected_qty_formula = ` <table class="table table-bordered" style="background-color: #f9f9f9;">
<tr><td style="padding-left:25px">
<div>
<h3>
<h3 style="text-decoration: underline;">
<a href = "https://erpnext.com/docs/user/manual/en/stock/projected-quantity">
${__("Projected Quantity Formula")}
</a>

View File

@ -6,7 +6,7 @@ def get_data():
'fieldname': 'production_plan',
'transactions': [
{
'label': _('Related'),
'label': _('Transactions'),
'items': ['Work Order', 'Material Request']
},
]

View File

@ -6,6 +6,7 @@ frappe.ui.form.on("Work Order", {
frm.custom_make_buttons = {
'Stock Entry': 'Start',
'Pick List': 'Create Pick List',
'Job Card': 'Create Job Card'
};
// Set query for warehouses
@ -131,7 +132,8 @@ frappe.ui.form.on("Work Order", {
}
if (frm.doc.docstatus===1) {
frm.trigger('show_progress');
frm.trigger('show_progress_for_items');
frm.trigger('show_progress_for_operations');
}
if (frm.doc.docstatus === 1
@ -179,89 +181,72 @@ frappe.ui.form.on("Work Order", {
make_job_card: function(frm) {
let qty = 0;
const fields = [{
fieldtype: "Link",
fieldname: "operation",
options: "Operation",
label: __("Operation"),
get_query: () => {
const filter_workstation = frm.doc.operations.filter(d => {
if (d.status != "Completed") {
return d;
}
});
let operations_data = [];
return {
filters: {
name: ["in", (filter_workstation || []).map(d => d.operation)]
}
};
},
reqd: true
}, {
fieldtype: "Link",
fieldname: "workstation",
options: "Workstation",
label: __("Workstation"),
get_query: () => {
const operation = dialog.get_value("operation");
const filter_workstation = frm.doc.operations.filter(d => {
if (d.operation == operation) {
return d;
}
});
return {
filters: {
name: ["in", (filter_workstation || []).map(d => d.workstation)]
}
};
},
onchange: () => {
const operation = dialog.get_value("operation");
const workstation = dialog.get_value("workstation");
if (operation && workstation) {
const row = frm.doc.operations.filter(d => d.operation == operation && d.workstation == workstation)[0];
qty = frm.doc.qty - row.completed_qty;
if (qty > 0) {
dialog.set_value("qty", qty);
}
}
},
reqd: true
}, {
fieldtype: "Float",
fieldname: "qty",
label: __("For Quantity"),
reqd: true
}];
const dialog = frappe.prompt(fields, function(data) {
if (data.qty > qty) {
frappe.throw(__("For Quantity must be less than quantity {0}", [qty]));
const dialog = frappe.prompt({fieldname: 'operations', fieldtype: 'Table', label: __('Operations'),
fields: [
{
fieldtype:'Link',
fieldname:'operation',
label: __('Operation'),
read_only:1,
in_list_view:1
},
{
fieldtype:'Link',
fieldname:'workstation',
label: __('Workstation'),
read_only:1,
in_list_view:1
},
{
fieldtype:'Data',
fieldname:'name',
label: __('Operation Id')
},
{
fieldtype:'Float',
fieldname:'pending_qty',
label: __('Pending Qty'),
},
{
fieldtype:'Float',
fieldname:'qty',
label: __('Quantity to Manufacture'),
read_only:0,
in_list_view:1,
},
],
data: operations_data,
in_place_edit: true,
get_data: function() {
return operations_data;
}
if (data.qty <= 0) {
frappe.throw(__("For Quantity must be greater than zero"));
}
}, function(data) {
frappe.call({
method: "erpnext.manufacturing.doctype.work_order.work_order.make_job_card",
args: {
work_order: frm.doc.name,
operation: data.operation,
workstation: data.workstation,
qty: data.qty
},
callback: function(r){
if (r.message) {
var doc = frappe.model.sync(r.message)[0];
frappe.set_route("Form", doc.doctype, doc.name);
}
operations: data.operations,
}
});
}, __("For Job Card"));
}, __("Job Card"), __("Create"));
var pending_qty = 0;
frm.doc.operations.forEach(data => {
if(data.completed_qty != frm.doc.qty) {
pending_qty = frm.doc.qty - flt(data.completed_qty);
dialog.fields_dict.operations.df.data.push({
'name': data.name,
'operation': data.operation,
'workstation': data.workstation,
'qty': pending_qty,
'pending_qty': pending_qty,
});
}
});
dialog.fields_dict.operations.grid.refresh();
},
make_bom: function(frm) {
@ -277,7 +262,7 @@ frappe.ui.form.on("Work Order", {
});
},
show_progress: function(frm) {
show_progress_for_items: function(frm) {
var bars = [];
var message = '';
var added_min = false;
@ -311,6 +296,44 @@ frappe.ui.form.on("Work Order", {
frm.dashboard.add_progress(__('Status'), bars, message);
},
show_progress_for_operations: function(frm) {
if (frm.doc.operations && frm.doc.operations.length) {
let progress_class = {
"Work in Progress": "progress-bar-warning",
"Completed": "progress-bar-success"
};
let bars = [];
let message = '';
let title = '';
let status_wise_oprtation_data = {};
let total_completed_qty = frm.doc.qty * frm.doc.operations.length;
frm.doc.operations.forEach(d => {
if (!status_wise_oprtation_data[d.status]) {
status_wise_oprtation_data[d.status] = [d.completed_qty, d.operation];
} else {
status_wise_oprtation_data[d.status][0] += d.completed_qty;
status_wise_oprtation_data[d.status][1] += ', ' + d.operation;
}
});
for (let key in status_wise_oprtation_data) {
title = __("{0} Operations: {1}", [key, status_wise_oprtation_data[key][1].bold()]);
bars.push({
'title': title,
'width': status_wise_oprtation_data[key][0] / total_completed_qty * 100 + '%',
'progress_class': progress_class[key]
});
message += title + '. ';
}
frm.dashboard.add_progress(__('Status'), bars, message);
}
},
production_item: function(frm) {
if (frm.doc.production_item) {
frappe.call({

View File

@ -1,4 +1,5 @@
{
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-01-10 16:34:16",
@ -468,7 +469,8 @@
"idx": 1,
"image_field": "image",
"is_submittable": 1,
"modified": "2019-08-28 12:29:35.315239",
"links": [],
"modified": "2019-12-04 11:20:04.695123",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",

View File

@ -6,7 +6,7 @@ import frappe
import json
import math
from frappe import _
from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate
from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form
from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict
from dateutil.relativedelta import relativedelta
@ -755,21 +755,41 @@ def query_sales_order(production_item):
return out
@frappe.whitelist()
def make_job_card(work_order, operation, workstation, qty=0):
def make_job_card(work_order, operations):
if isinstance(operations, string_types):
operations = json.loads(operations)
work_order = frappe.get_doc('Work Order', work_order)
row = get_work_order_operation_data(work_order, operation, workstation)
if row:
return create_job_card(work_order, row, qty)
for row in operations:
validate_operation_data(row)
create_job_card(work_order, row, row.get("qty"), auto_create=True)
def validate_operation_data(row):
if row.get("qty") <= 0:
frappe.throw(_("Quantity to Manufacture can not be zero for the operation {0}")
.format(
frappe.bold(row.get("operation"))
)
)
if row.get("qty") > row.get("pending_qty"):
frappe.throw(_("For operation {0}: Quantity ({1}) can not be greter than pending quantity({2})")
.format(
frappe.bold(row.get("operation")),
frappe.bold(row.get("qty")),
frappe.bold(row.get("pending_qty"))
)
)
def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto_create=False):
doc = frappe.new_doc("Job Card")
doc.update({
'work_order': work_order.name,
'operation': row.operation,
'workstation': row.workstation,
'operation': row.get("operation"),
'workstation': row.get("workstation"),
'posting_date': nowdate(),
'for_quantity': qty or work_order.get('qty', 0),
'operation_id': row.name,
'operation_id': row.get("name"),
'bom_no': work_order.bom_no,
'project': work_order.project,
'company': work_order.company,
@ -785,7 +805,7 @@ def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto
doc.schedule_time_logs(row)
doc.insert()
frappe.msgprint(_("Job card {0} created").format(doc.name))
frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name)))
return doc

View File

@ -2,11 +2,13 @@
// For license information, please see license.txt
frappe.views.calendar["Work Order"] = {
fields: ["planned_start_date", "planned_end_date", "status", "produced_qty", "qty", "name", "name"],
field_map: {
"start": "planned_start_date",
"end": "planned_end_date",
"id": "name",
"title": "name",
"status": "status",
"allDay": "allDay",
"progress": function(data) {
return flt(data.produced_qty) / data.qty * 100;

View File

@ -6,7 +6,8 @@ def get_data():
'fieldname': 'work_order',
'transactions': [
{
'items': ['Pick List', 'Stock Entry', 'Job Card']
'label': _('Transactions'),
'items': ['Stock Entry', 'Job Card', 'Pick List']
}
]
}

View File

@ -1,4 +1,5 @@
{
"actions": [],
"creation": "2014-10-16 14:35:41.950175",
"doctype": "DocType",
"editable_grid": 1,
@ -68,6 +69,7 @@
"description": "Operation completed for how many finished goods?",
"fieldname": "completed_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Completed Qty",
"no_copy": 1,
"read_only": 1
@ -188,8 +190,9 @@
}
],
"istable": 1,
"modified": "2019-07-16 23:01:07.720337",
"modified_by": "govindsmenokee@gmail.com",
"links": [],
"modified": "2019-12-03 19:24:29.594189",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Operation",
"owner": "Administrator",
@ -197,4 +200,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@ -13,5 +13,7 @@ def get_data():
'label': _('Transaction'),
'items': ['Work Order', 'Job Card', 'Timesheet']
}
]
],
'disable_create_buttons': ['BOM', 'Routing', 'Operation',
'Work Order', 'Job Card', 'Timesheet']
}