From 0de6256bccf8e6f8cf5a076c58f5442c143ed2ea Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 22 Dec 2014 14:31:53 +0530 Subject: [PATCH] time log and workstations --- .../manufacturing_settings.json | 40 +++++----- .../production_order/production_order.json | 30 ++++++-- .../production_order/production_order.py | 12 +-- .../production_order/test_production_order.py | 20 ++--- .../production_order_operation.json | 4 +- .../doctype/workstation/workstation.json | 4 +- .../doctype/workstation/workstation.py | 76 ++++++++++--------- .../__init__.py | 0 .../workstation_operation_hour.json} | 63 +++++++++++++-- .../workstation_operation_hour.py} | 2 +- erpnext/projects/doctype/time_log/time_log.js | 18 +++-- .../projects/doctype/time_log/time_log.json | 4 +- erpnext/projects/doctype/time_log/time_log.py | 39 +++++++--- 13 files changed, 203 insertions(+), 109 deletions(-) rename erpnext/manufacturing/doctype/{workstation_operation_hours => workstation_operation_hour}/__init__.py (100%) rename erpnext/manufacturing/doctype/{workstation_operation_hours/workstation_operation_hours.json => workstation_operation_hour/workstation_operation_hour.json} (53%) rename erpnext/manufacturing/doctype/{workstation_operation_hours/workstation_operation_hours.py => workstation_operation_hour/workstation_operation_hour.py} (85%) diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index 23c223e7bc..a5bdecd8db 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -9,33 +9,26 @@ "document_type": "Master", "fields": [ { - "allow_on_submit": 0, - "default": "30", - "description": "Maximum Overtime allowed against an workstation.\n( in mins )", - "fieldname": "max_overtime", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Maximum Overtime", - "no_copy": 0, + "description": "Will not allow to make time logs outside \"Workstation operation timings\"", + "fieldname": "dont_allow_overtime", + "fieldtype": "Check", + "label": "Don't allow overtime", "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "precision": "" }, { - "default": "No", + "default": "", "fieldname": "allow_production_on_holidays", - "fieldtype": "Select", + "fieldtype": "Check", + "in_list_view": 1, "label": "Allow Production on Holidays", - "options": "Yes\nNo", + "options": "", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break", "permlevel": 0, "precision": "" }, @@ -44,6 +37,7 @@ "description": "Delay in start time of production order operations if automatically make time logs is used.\n(in mins)", "fieldname": "operations_start_delay", "fieldtype": "Float", + "in_list_view": 1, "label": "Operations Start Delay", "permlevel": 0, "precision": "" @@ -57,7 +51,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2014-12-18 16:22:26.052642", + "modified": "2014-12-22 12:43:15.261503", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing Settings", diff --git a/erpnext/manufacturing/doctype/production_order/production_order.json b/erpnext/manufacturing/doctype/production_order/production_order.json index f34e83c423..da2bd5bf2a 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.json +++ b/erpnext/manufacturing/doctype/production_order/production_order.json @@ -122,6 +122,20 @@ "permlevel": 0, "read_only": 0 }, + { + "fieldname": "planned_start_date", + "fieldtype": "Datetime", + "label": "Planned Start Date", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "planned_end_date", + "fieldtype": "Datetime", + "label": "Planned End Date", + "permlevel": 0, + "precision": "" + }, { "fieldname": "column_break_13", "fieldtype": "Column Break", @@ -129,18 +143,20 @@ "precision": "" }, { - "fieldname": "production_start_date", + "fieldname": "actual_start_date", "fieldtype": "Datetime", - "label": "Production Start Date", + "label": "Actual Start Date", "permlevel": 0, - "precision": "" + "precision": "", + "read_only": 1 }, { - "fieldname": "production_end_date", + "fieldname": "actual_end_date", "fieldtype": "Datetime", - "label": "Production End Date", + "label": "Actual End Date", "permlevel": 0, - "precision": "" + "precision": "", + "read_only": 1 }, { "fieldname": "warehouses", @@ -331,7 +347,7 @@ "idx": 1, "in_create": 0, "is_submittable": 1, - "modified": "2014-12-18 15:06:41.802390", + "modified": "2014-12-19 14:23:50.701164", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Order", diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index 5fb92d14a8..a075f74ad0 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -64,8 +64,8 @@ class ProductionOrder(Document): self.planned_variable_cost, self.actual_variable_cost = 0.0, 0.0 for d in self.get("production_order_operations"): - d.actual_variable_cost = flt(d.hour_rate) * flt(d.actual_operation_time) / 60 \ - if d.actual_operation_time else d.time_in_mins + + d.actual_variable_cost = flt(d.hour_rate) * flt(d.actual_operation_time) / 60 self.planned_variable_cost += flt(d.variable_cost) self.actual_variable_cost += flt(d.actual_variable_cost) @@ -254,14 +254,16 @@ def get_events(start, end, filters=None): if filters[key]: conditions += " and " + key + ' = "' + filters[key].replace('"', '\"') + '"' - data = frappe.db.sql("""select name,production_item, production_start_date, production_end_date from `tabProduction Order` + data = frappe.db.sql("""select name,production_item, production_start_date, production_end_date + from `tabProduction Order` where ((ifnull(production_start_date, '0000-00-00')!= '0000-00-00') \ and (production_start_date between %(start)s and %(end)s) \ or ((ifnull(production_start_date, '0000-00-00')!= '0000-00-00') \ - and production_end_date between %(start)s and %(end)s)){conditions}""".format(conditions=conditions), { + and production_end_date between %(start)s and %(end)s)) {conditions} + """.format(conditions=conditions), { "start": start, "end": end - }, as_dict=True, update={"allDay": 0}) + }, as_dict=True, update={"allDay": 0}) return data @frappe.whitelist() diff --git a/erpnext/manufacturing/doctype/production_order/test_production_order.py b/erpnext/manufacturing/doctype/production_order/test_production_order.py index 945e986a3f..49db178f45 100644 --- a/erpnext/manufacturing/doctype/production_order/test_production_order.py +++ b/erpnext/manufacturing/doctype/production_order/test_production_order.py @@ -74,19 +74,19 @@ class TestProductionOrder(unittest.TestCase): prod_order.production_order_operations[0].update({ "planned_start_time": "2014-11-25 00:00:00", "planned_end_time": "2014-11-25 10:00:00", - "hour_rate": 10 + "hour_rate": 10 }) prod_order.insert() - + d = prod_order.production_order_operations[0] from erpnext.manufacturing.doctype.production_order.production_order import make_time_log from frappe.utils import cstr from frappe.utils import time_diff_in_hours - + prod_order.submit() - + time_log = make_time_log( prod_order.name, cstr(d.idx) + ". " + d.operation, \ d.planned_start_time, d.planned_end_time, prod_order.qty - d.qty_completed) @@ -100,11 +100,11 @@ class TestProductionOrder(unittest.TestCase): manufacturing_settings = frappe.get_doc({ "doctype": "Manufacturing Settings", "maximum_overtime": 30, - "allow_production_on_holidays": "No" + "allow_production_on_holidays": 0 }) - + manufacturing_settings.save() - + prod_order.load_from_db() self.assertEqual(prod_order.production_order_operations[0].status, "Completed") self.assertEqual(prod_order.production_order_operations[0].qty_completed, prod_order.qty) @@ -114,13 +114,13 @@ class TestProductionOrder(unittest.TestCase): self.assertEqual(prod_order.production_order_operations[0].actual_operation_time, 600) self.assertEqual(prod_order.production_order_operations[0].actual_operating_cost, 6000) - + time_log.cancel() prod_order.load_from_db() self.assertEqual(prod_order.production_order_operations[0].status, "Pending") self.assertEqual(prod_order.production_order_operations[0].qty_completed, 0) - + self.assertEqual(prod_order.production_order_operations[0].actual_operation_time, 0) self.assertEqual(prod_order.production_order_operations[0].actual_operating_cost, 0) @@ -132,5 +132,5 @@ class TestProductionOrder(unittest.TestCase): "docstatus": 0 }) self.assertRaises(OverProductionError, time_log2.save) - + test_records = frappe.get_test_records('Production Order') diff --git a/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json b/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json index 13b49c1ee8..f17a055970 100644 --- a/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json +++ b/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json @@ -277,7 +277,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.docstatus==1", + "depends_on": "eval:(doc.docstatus==1 && doc.status!=\"Completed\")", "fieldname": "make_time_log", "fieldtype": "Button", "label": "Make Time Log", @@ -292,7 +292,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2014-12-18 12:18:51.655535", + "modified": "2014-12-19 12:49:49.918120", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Order Operation", diff --git a/erpnext/manufacturing/doctype/workstation/workstation.json b/erpnext/manufacturing/doctype/workstation/workstation.json index 21cafc04fa..37932bc78c 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.json +++ b/erpnext/manufacturing/doctype/workstation/workstation.json @@ -154,14 +154,14 @@ "fieldname": "workstation_operation_hours", "fieldtype": "Table", "label": "Workstation Operation Hours", - "options": "Workstation Operation Hours", + "options": "Workstation Operation Hour", "permlevel": 0, "precision": "" } ], "icon": "icon-wrench", "idx": 1, - "modified": "2014-12-18 13:01:47.143326", + "modified": "2014-12-22 14:18:40.253034", "modified_by": "Administrator", "module": "Manufacturing", "name": "Workstation", diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py index a0b150c84f..b95ca45dd7 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.py +++ b/erpnext/manufacturing/doctype/workstation/workstation.py @@ -5,12 +5,13 @@ from __future__ import unicode_literals import frappe import datetime from frappe import _ -from frappe.utils import flt, cint +from frappe.utils import flt, cint, getdate, formatdate, comma_and from frappe.model.document import Document class WorkstationHolidayError(frappe.ValidationError): pass class WorkstationIsClosedError(frappe.ValidationError): pass +class OverlapError(frappe.ValidationError): pass class Workstation(Document): def update_bom_operation(self): @@ -22,51 +23,58 @@ class Workstation(Document): (self.hour_rate, bom_no[0], self.name)) def on_update(self): + self.validate_overlap_for_operation_timings() + frappe.db.set(self, 'total_variable_cost', flt(self.hour_rate_electricity) + - flt(self.hour_rate_consumable) + flt(self.hour_rate_rent)) + flt(self.hour_rate_consumable) + flt(self.hour_rate_rent)) frappe.db.set(self, 'hour_rate', flt(self.hour_rate_labour) + flt(self.total_variable_cost)) + self.update_bom_operation() + def validate_overlap_for_operation_timings(self): + for d in self.get("workstation_operation_hours"): + existing = frappe.db.sql_list("""select idx from `tabWorkstation Operation Hours` + where parent = %s and name != %s + and ( + (start_time between %s and %s) or + (end_time between %s and %s) or + (%s between start_time and end_time)) + """, (self.name, d.name, d.start_time, d.end_time, d.start_time, d.end_time, d.start_time)) + if existing: + frappe.throw(_("Row #{0}: Timings conflicts with row {1}").format(d.idx, comma_and(existing)), OverlapError) @frappe.whitelist() def get_default_holiday_list(): return frappe.db.get_value("Company", frappe.defaults.get_user_default("company"), "default_holiday_list") -def check_if_within_operating_hours(workstation, from_time, to_time): - if check_workstation_for_operation_time(workstation, from_time, to_time): - frappe.throw(_("Time Log timings outside workstation Operating Hours !"), WorkstationIsClosedError) +def check_if_within_operating_hours(workstation, from_datetime, to_datetime): + if not is_within_operating_hours(workstation, from_datetime, to_datetime): + frappe.throw(_("Time Log timings outside workstation operating hours"), WorkstationIsClosedError) - if frappe.db.get_value("Manufacturing Settings", "None", "allow_production_on_holidays") == "No": - msg = check_workstation_for_holiday(workstation, from_time, to_time) - if msg != None: - frappe.throw(msg, WorkstationHolidayError) + if not cint(frappe.db.get_value("Manufacturing Settings", "None", "allow_production_on_holidays")): + check_workstation_for_holiday(workstation, from_datetime, to_datetime) -def check_workstation_for_operation_time(workstation, from_time, to_time): - start_time = datetime.datetime.strptime(from_time,'%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') - end_time = datetime.datetime.strptime(to_time,'%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') - max_time_diff = frappe.db.get_value("Manufacturing Settings", "None", "max_overtime") +def is_within_operating_hours(workstation, from_datetime, to_datetime): + if not cint(frappe.db.get_value("Manufacturing Settings", None, "dont_allow_overtime")): + return True - for d in frappe.db.sql("""select time_to_sec(timediff( start_time, %s))/60 as st_diff , - time_to_sec(timediff( %s, end_time))/60 as et_diff from `tabWorkstation Operation Hours` - where parent = %s and (%s end_time )""", - (start_time, end_time, workstation, start_time, end_time), as_dict=1): - if cint(d.st_diff) > cint(max_time_diff): - return 1 - if cint(d.et_diff) > cint(max_time_diff): - return 1 + start_time = datetime.datetime.strptime(from_datetime,'%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') + end_time = datetime.datetime.strptime(to_datetime,'%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') -def check_workstation_for_holiday(workstation, from_time, to_time): + for d in frappe.db.sql("""select start_time, end_time from `tabWorkstation Operation Hours` + where parent = %s and ifnull(enabled, 0) = 1""", workstation, as_dict=1): + if d.end_time >= start_time >= d.start_time and d.end_time >= end_time >= d.start_time: + return True + +def check_workstation_for_holiday(workstation, from_datetime, to_datetime): holiday_list = frappe.db.get_value("Workstation", workstation, "holiday_list") - start_date = datetime.datetime.strptime(from_time,'%Y-%m-%d %H:%M:%S').strftime('%Y-%m-%d') - end_date = datetime.datetime.strptime(to_time,'%Y-%m-%d %H:%M:%S').strftime('%Y-%m-%d') - msg = _("Workstation is closed on the following dates as per Holiday List:") - flag = 0 - for d in frappe.db.sql("""select holiday_date from `tabHoliday` where parent = %s and holiday_date between - %s and %s """,(holiday_list, start_date, end_date), as_dict=1): - flag = 1 - msg = msg + "\n" + d.holiday_date - if flag ==1: - return msg - else: - return None + if holiday_list: + applicable_holidays = [] + for d in frappe.db.sql("""select holiday_date from `tabHoliday` where parent = %s + and holiday_date between %s and %s """, (holiday_list, getdate(from_datetime), getdate(to_datetime))): + applicable_holidays.append(formatdate(d[0])) + + if applicable_holidays: + frappe.throw(_("Workstation is closed on the following dates as per Holiday List: {0}") + .format(holiday_list) + "\n" + "\n".join(applicable_holidays), WorkstationHolidayError) diff --git a/erpnext/manufacturing/doctype/workstation_operation_hours/__init__.py b/erpnext/manufacturing/doctype/workstation_operation_hour/__init__.py similarity index 100% rename from erpnext/manufacturing/doctype/workstation_operation_hours/__init__.py rename to erpnext/manufacturing/doctype/workstation_operation_hour/__init__.py diff --git a/erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.json b/erpnext/manufacturing/doctype/workstation_operation_hour/workstation_operation_hour.json similarity index 53% rename from erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.json rename to erpnext/manufacturing/doctype/workstation_operation_hour/workstation_operation_hour.json index 8a064ca40f..ebfa0ef7c5 100644 --- a/erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.json +++ b/erpnext/manufacturing/doctype/workstation_operation_hour/workstation_operation_hour.json @@ -2,7 +2,7 @@ "allow_copy": 0, "allow_import": 0, "allow_rename": 0, - "creation": "2014-10-29 13:00:43.921508", + "creation": "2014-12-22 14:18:20.786493", "custom": 0, "docstatus": 0, "doctype": "DocType", @@ -29,10 +29,23 @@ "unique": 0 }, { - "fieldname": "section_break_2", + "allow_on_submit": 0, + "fieldname": "column_break_2", "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, "permlevel": 0, - "precision": "" + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { "allow_on_submit": 0, @@ -53,6 +66,46 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_on_submit": 0, + "fieldname": "section_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Enabled", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "hide_heading": 0, @@ -62,10 +115,10 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2014-10-29 13:02:24.631554", + "modified": "2014-12-22 14:18:31.091653", "modified_by": "Administrator", "module": "Manufacturing", - "name": "Workstation Operation Hours", + "name": "Workstation Operation Hour", "name_case": "", "owner": "Administrator", "permissions": [], diff --git a/erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.py b/erpnext/manufacturing/doctype/workstation_operation_hour/workstation_operation_hour.py similarity index 85% rename from erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.py rename to erpnext/manufacturing/doctype/workstation_operation_hour/workstation_operation_hour.py index dfac1f8c42..1b8c51960e 100644 --- a/erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.py +++ b/erpnext/manufacturing/doctype/workstation_operation_hour/workstation_operation_hour.py @@ -5,5 +5,5 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -class WorkstationOperationHours(Document): +class WorkstationOperationHour(Document): pass diff --git a/erpnext/projects/doctype/time_log/time_log.js b/erpnext/projects/doctype/time_log/time_log.js index 2c74bb328e..e255843872 100644 --- a/erpnext/projects/doctype/time_log/time_log.js +++ b/erpnext/projects/doctype/time_log/time_log.js @@ -5,6 +5,16 @@ frappe.provide("erpnext.projects"); frappe.ui.form.on("Time Log", "onload", function(frm) { frm.set_query("task", erpnext.queries.task); + if (frm.doc.time_log_for == "Manufacturing") { + frappe.ui.form.trigger("Time Log", "production_order"); + } +}); + +frappe.ui.form.on("Time Log", "refresh", function(frm) { + var is_manufacturing = frm.doc.time_log_for=="Manufacturing" ? true : false; + frm.toggle_reqd("production_order", is_manufacturing); + frm.toggle_reqd("operation", is_manufacturing); + frm.toggle_reqd("operation_status", is_manufacturing); }); // set to time if hours is updated @@ -45,7 +55,7 @@ $.extend(cur_frm.cscript, { $.each(doc.production_order_operations , function(i, row){ operations[i] = (i+1) +". "+ row.operation; }); - frappe.meta.get_docfield("Time Log", "operation", me.frm.doc.name).options = operations.join("\n"); + frappe.meta.get_docfield("Time Log", "operation", me.frm.doc.name).options = "\n" + operations.join("\n"); refresh_field("operation"); }) } @@ -60,14 +70,10 @@ $.extend(cur_frm.cscript, { }, callback: function(r) { if(!r.exc) { + console.log(r.message) cur_frm.set_value("workstation", r.message) } - doc.workstation = r.workstation; } }); } }); - -if (cur_frm.doc.time_log_for == "Manufacturing") { - cur_frm.cscript.onload = cur_frm.cscript.production_order; -} diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json index 4afc0a0469..3b27ab8952 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -152,7 +152,7 @@ "read_only": 0 }, { - "depends_on": "eval:doc.time_log_for == 'Project'", + "depends_on": "eval:doc.time_log_for", "fieldname": "project", "fieldtype": "Link", "in_list_view": 1, @@ -200,7 +200,7 @@ "icon": "icon-time", "idx": 1, "is_submittable": 1, - "modified": "2014-12-18 17:21:01.520646", + "modified": "2014-12-19 14:20:41.381152", "modified_by": "Administrator", "module": "Projects", "name": "Time Log", diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index db08abc852..541154a4a6 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -8,7 +8,6 @@ import frappe, json from frappe import _ from frappe.utils import cstr, comma_and, flt - class OverlapError(frappe.ValidationError): pass class OverProductionError(frappe.ValidationError): pass class NotSubmittedError(frappe.ValidationError): pass @@ -21,8 +20,10 @@ class TimeLog(Document): self.validate_overlap() self.validate_timings() self.calculate_total_hours() + self.validate_time_log_for() self.check_workstation_timings() self.validate_production_order() + self.validate_operation_status() def on_submit(self): self.update_production_order() @@ -74,6 +75,11 @@ class TimeLog(Document): from frappe.utils import time_diff_in_seconds self.hours = flt(time_diff_in_seconds(self.to_time, self.from_time)) / 3600 + def validate_time_log_for(self): + if self.time_log_for == "Project": + for fld in ["production_order", "operation", "workstation", "operation_status"]: + self.set(fld, None) + def check_workstation_timings(self): """Checks if **Time Log** is between operating hours of the **Workstation**.""" if self.workstation: @@ -82,28 +88,39 @@ class TimeLog(Document): def validate_production_order(self): """Throws 'NotSubmittedError' if **production order** is not submitted. """ - if self.production_order: if frappe.db.get_value("Production Order", self.production_order, "docstatus") != 1 : frappe.throw(_("You can make a time log only against a submitted production order"), NotSubmittedError) + def validate_operation_status(self): + if self.time_log_for=="Manufacturing" and self.production_order and self.operation: + if self.operation_status == "Work in Progress": + latest_time_log = self.get_latest_time_log() + if latest_time_log and latest_time_log[0].operation_status == "Completed": + frappe.throw("Operation is already completed via Time Log {}".format(latest_time_log[0].name)) + def update_production_order(self): """Updates `start_date`, `end_date`, `status` for operation in Production Order.""" if self.time_log_for=="Manufacturing" and self.operation: dates = self.get_operation_start_end_time() - op_status = self.get_op_status() - actual_op_time = self.get_actual_op_time() + latest_time_log = self.get_latest_time_log() + op_status = latest_time_log[0].operation_status if latest_time_log else "Pending" + + actual_op_time = self.get_actual_op_time() d = self.operation.split('. ',1) frappe.db.sql("""update `tabProduction Order Operation` set actual_start_time = %s, actual_end_time = %s, status = %s, actual_operation_time = %s where parent=%s and idx=%s and operation = %s""", - (dates.start_date, dates.end_date, op_status, actual_op_time, - self.production_order, d[0], d[1])) + (dates.start_date, dates.end_date, op_status, + actual_op_time, self.production_order, d[0], d[1])) - frappe.get_doc("Production Order", self.production_order).save() + pro_order = frappe.get_doc("Production Order", self.production_order) + pro_order.ignore_validate_update_after_submit = True + pro_order.calculate_operating_cost() + pro_order.save() def get_operation_start_end_time(self): """Returns Min From and Max To Dates of Time Logs against a specific Operation. """ @@ -118,12 +135,10 @@ class TimeLog(Document): (self.production_order, self.operation)) return actual_time[0][0] if actual_time else 0 - def get_op_status(self): - status = frappe.db.sql("""select operation_status from `tabTime Log` + def get_latest_time_log(self): + return frappe.db.sql("""select name, operation_status from `tabTime Log` where production_order=%s and operation=%s and docstatus=1 - order by to_time desc limit 1""", (self.production_order, self.operation)) - - return status if status else self.status + order by to_time desc limit 1""", (self.production_order, self.operation), as_dict=1) @frappe.whitelist() def get_workstation(production_order, operation):