fix: Manufacturing UX, added calendar view for job card
This commit is contained in:
@@ -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"));
|
||||
|
||||
@@ -23,7 +23,5 @@ def get_data():
|
||||
'label': _('Subcontract'),
|
||||
'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
|
||||
}
|
||||
],
|
||||
'disable_create_buttons': ["Item", "Purchase Order", "Purchase Receipt",
|
||||
"Purchase Invoice", "Job Card", "Stock Entry"]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ frappe.ui.form.on('Job Card', {
|
||||
if(frm.doc.docstatus == 0) {
|
||||
frm.set_df_property("operation", "read_only", frm.doc.operation_id ? 1 : 0);
|
||||
}
|
||||
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) {
|
||||
@@ -18,44 +20,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>
|
||||
@@ -63,11 +120,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() {
|
||||
@@ -93,6 +155,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({
|
||||
@@ -122,5 +188,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);
|
||||
}
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, time_diff_in_hours, get_datetime
|
||||
from frappe.utils import flt, time_diff_in_hours, get_datetime, time_diff
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.document import Document
|
||||
|
||||
@@ -95,8 +95,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:
|
||||
@@ -172,6 +173,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",
|
||||
@@ -230,6 +233,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": {
|
||||
@@ -251,3 +255,48 @@ def make_stock_entry(source_name, target_doc=None):
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
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
|
||||
|
||||
@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
|
||||
|
||||
21
erpnext/manufacturing/doctype/job_card/job_card_calendar.js
Normal file
21
erpnext/manufacturing/doctype/job_card/job_card_calendar.js
Normal 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"
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ def get_data():
|
||||
'fieldname': 'production_plan',
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Related'),
|
||||
'label': _('Transactions'),
|
||||
'items': ['Work Order', 'Material Request']
|
||||
},
|
||||
]
|
||||
|
||||
@@ -6,7 +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',
|
||||
'Job Card': 'Create Job Card'
|
||||
};
|
||||
|
||||
// Set query for warehouses
|
||||
@@ -132,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
|
||||
@@ -180,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) {
|
||||
@@ -278,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;
|
||||
@@ -312,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({
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -711,21 +711,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, 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,
|
||||
@@ -738,7 +758,7 @@ def create_job_card(work_order, row, qty=0, auto_create=False):
|
||||
if auto_create:
|
||||
doc.flags.ignore_mandatory = True
|
||||
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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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']
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user