From 637d804abd98e0a055f6372c750881586cb2a6f6 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 16 Oct 2014 15:05:30 +0530 Subject: [PATCH] Operations added in Production Order DocType Auto-Fetch added to operations in Production Order DocType made operations in production order read-only capacity & duration of cycle added to workstation workstation operation timings added operation master added, Time log added Time Log Fixed Test Records added to Production Order Test Recorde for over Production Time Log added Validation added to check if time log timings are within working hours of the workstation. Validation added to check if workstation is closed on time log dates. Test Case added for Time Log --- erpnext/config/manufacturing.py | 5 + .../hr/doctype/holiday_list/test_records.json | 10 +- erpnext/manufacturing/doctype/bom/bom.js | 63 ++-- erpnext/manufacturing/doctype/bom/bom.py | 19 +- .../doctype/bom/test_records.json | 10 +- .../doctype/bom_item/bom_item.json | 234 +++++++------- .../doctype/bom_operation/bom_operation.json | 146 ++++----- .../doctype/operation/__init__.py | 0 .../doctype/operation/operation.js | 14 + .../doctype/operation/operation.json | 98 ++++++ .../doctype/operation/operation.py | 14 + .../doctype/operation/test_operation.py | 10 + .../doctype/operation/test_records.json | 7 + .../production_order/production_order.js | 32 +- .../production_order/production_order.json | 53 +++- .../production_order/production_order.py | 29 +- .../production_order/test_production_order.py | 55 ++++ .../production_order_operation/__init__.py | 0 .../production_order_operation.json | 290 ++++++++++++++++++ .../production_order_operation.py | 9 + .../doctype/workstation/test_records.json | 8 +- .../doctype/workstation/test_workstation.py | 5 +- .../doctype/workstation/workstation.json | 284 +++++++++-------- .../doctype/workstation/workstation.py | 36 ++- .../workstation_operation_hours/__init__.py | 0 .../workstation_operation_hours.json | 76 +++++ .../workstation_operation_hours.py | 9 + .../doctype/time_log/test_time_log.py | 2 +- erpnext/projects/doctype/time_log/time_log.js | 33 ++ .../projects/doctype/time_log/time_log.json | 50 ++- erpnext/projects/doctype/time_log/time_log.py | 77 ++++- erpnext/setup/doctype/company/company.py | 8 +- 32 files changed, 1287 insertions(+), 399 deletions(-) create mode 100644 erpnext/manufacturing/doctype/operation/__init__.py create mode 100644 erpnext/manufacturing/doctype/operation/operation.js create mode 100644 erpnext/manufacturing/doctype/operation/operation.json create mode 100644 erpnext/manufacturing/doctype/operation/operation.py create mode 100644 erpnext/manufacturing/doctype/operation/test_operation.py create mode 100644 erpnext/manufacturing/doctype/operation/test_records.json create mode 100644 erpnext/manufacturing/doctype/production_order_operation/__init__.py create mode 100644 erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json create mode 100644 erpnext/manufacturing/doctype/production_order_operation/production_order_operation.py create mode 100644 erpnext/manufacturing/doctype/workstation_operation_hours/__init__.py create mode 100644 erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.json create mode 100644 erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.py diff --git a/erpnext/config/manufacturing.py b/erpnext/config/manufacturing.py index a1644a2da9..43b46381a5 100644 --- a/erpnext/config/manufacturing.py +++ b/erpnext/config/manufacturing.py @@ -27,6 +27,11 @@ def get_data(): "name": "Workstation", "description": _("Where manufacturing operations are carried out."), }, + { + "type": "doctype", + "name": "Operation", + "description": _("Details of the operations carried out."), + }, ] }, diff --git a/erpnext/hr/doctype/holiday_list/test_records.json b/erpnext/hr/doctype/holiday_list/test_records.json index 9ef8c8efde..1c4abe7862 100644 --- a/erpnext/hr/doctype/holiday_list/test_records.json +++ b/erpnext/hr/doctype/holiday_list/test_records.json @@ -5,11 +5,11 @@ "holiday_list_details": [ { "description": "New Year", - "doctype": "Holiday", - "holiday_date": "2013-01-01", - "parent": "_Test Holiday List", - "parentfield": "holiday_list_details", - "parenttype": "Holiday List" + "holiday_date": "2013-01-01" + }, + { + "description": "Test Holiday", + "holiday_date": "2013-02-01" } ], "holiday_list_name": "_Test Holiday List", diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 34598b662b..5c099c43e3 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -12,7 +12,7 @@ cur_frm.cscript.refresh = function(doc,dt,dn){ } cur_frm.cscript.with_operations(doc); - erpnext.bom.set_operation_no(doc); + erpnext.bom.set_operation(doc); } cur_frm.cscript.update_cost = function() { @@ -26,62 +26,41 @@ cur_frm.cscript.update_cost = function() { } cur_frm.cscript.with_operations = function(doc) { - cur_frm.fields_dict["bom_materials"].grid.set_column_disp("operation_no", doc.with_operations); - cur_frm.fields_dict["bom_materials"].grid.toggle_reqd("operation_no", doc.with_operations); + cur_frm.fields_dict["bom_materials"].grid.set_column_disp("operation", doc.with_operations); + cur_frm.fields_dict["bom_materials"].grid.toggle_reqd("operation", doc.with_operations); } -cur_frm.cscript.operation_no = function(doc, cdt, cdn) { - var child = locals[cdt][cdn]; - if(child.parentfield=="bom_operations") erpnext.bom.set_operation_no(doc); -} - -erpnext.bom.set_operation_no = function(doc) { +erpnext.bom.set_operation = function(doc) { var op_table = doc.bom_operations || []; var operations = []; for (var i=0, j=op_table.length; i end_time )""",(self.workstation_name, start_time, end_time), as_dict=1): + return 1 + + def check_workstation_for_holiday(self, from_time, to_time): + holiday_list = frappe.db.get_value("Workstation", self.workstation_name, "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 \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/workstation_operation_hours/__init__.py b/erpnext/manufacturing/doctype/workstation_operation_hours/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.json b/erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.json new file mode 100644 index 0000000000..8a064ca40f --- /dev/null +++ b/erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.json @@ -0,0 +1,76 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "creation": "2014-10-29 13:00:43.921508", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "fields": [ + { + "allow_on_submit": 0, + "fieldname": "start_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Start Time", + "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 + }, + { + "fieldname": "section_break_2", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "allow_on_submit": 0, + "fieldname": "end_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "End Time", + "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, + "hide_toolbar": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "modified": "2014-10-29 13:02:24.631554", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Workstation Operation Hours", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.py b/erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.py new file mode 100644 index 0000000000..dfac1f8c42 --- /dev/null +++ b/erpnext/manufacturing/doctype/workstation_operation_hours/workstation_operation_hours.py @@ -0,0 +1,9 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class WorkstationOperationHours(Document): + pass diff --git a/erpnext/projects/doctype/time_log/test_time_log.py b/erpnext/projects/doctype/time_log/test_time_log.py index 4a312adb41..bc0a9dc24b 100644 --- a/erpnext/projects/doctype/time_log/test_time_log.py +++ b/erpnext/projects/doctype/time_log/test_time_log.py @@ -16,6 +16,6 @@ class TestTimeLog(unittest.TestCase): self.assertRaises(OverlapError, ts.insert) frappe.db.sql("delete from `tabTime Log`") - + test_records = frappe.get_test_records('Time Log') test_ignore = ["Time Log Batch", "Sales Invoice"] diff --git a/erpnext/projects/doctype/time_log/time_log.js b/erpnext/projects/doctype/time_log/time_log.js index d4d109d6f9..36ca2d2ba8 100644 --- a/erpnext/projects/doctype/time_log/time_log.js +++ b/erpnext/projects/doctype/time_log/time_log.js @@ -27,3 +27,36 @@ frappe.ui.form.on("Time Log", "to_time", function(frm) { }); cur_frm.add_fetch('task','project','project'); + +$.extend(cur_frm.cscript, { + production_order: function(doc) { + if (doc.production_order){ + var operations = []; + frappe.model.with_doc("Production Order", doc.production_order, function(pro) { + doc = frappe.get_doc("Production Order",pro); + $.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"); + refresh_field("operation"); + }) + } + }, + + operation: function(doc) { + return cur_frm.call({ + method: "erpnext.projects.doctype.time_log.time_log.get_workstation", + args: { + "production_order": doc.production_order, + "operation": doc.operation + }, + callback: function(r) { + doc.workstation = r.workstation; + } + }); + } +}); + +if (cur_frm.doc.time_log_for == "Manufacturing") { + cur_frm.cscript.onload = cur_frm.cscript.production_order; +} \ No newline at end of file diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json index 0eed1fc9af..6e2706de2c 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -16,6 +16,15 @@ "read_only": 0, "reqd": 1 }, + { + "fieldname": "time_log_for", + "fieldtype": "Select", + "label": "Time Log For", + "options": "Project\nManufacturing", + "permlevel": 0, + "precision": "", + "reqd": 1 + }, { "fieldname": "from_time", "fieldtype": "Datetime", @@ -59,6 +68,7 @@ "reqd": 0 }, { + "depends_on": "eval:doc.time_log_for == 'Project'", "fieldname": "activity_type", "fieldtype": "Link", "in_list_view": 1, @@ -66,9 +76,10 @@ "options": "Activity Type", "permlevel": 0, "read_only": 0, - "reqd": 1 + "reqd": 0 }, { + "depends_on": "eval:doc.time_log_for == 'Project'", "fieldname": "task", "fieldtype": "Link", "label": "Task", @@ -76,6 +87,41 @@ "permlevel": 0, "read_only": 0 }, + { + "depends_on": "eval:doc.time_log_for == 'Manufacturing'", + "fieldname": "production_order", + "fieldtype": "Link", + "label": "Production Order", + "options": "Production Order", + "permlevel": 0, + "precision": "" + }, + { + "depends_on": "eval:doc.time_log_for == 'Manufacturing'", + "fieldname": "operation", + "fieldtype": "Select", + "label": "Operation", + "options": "", + "permlevel": 0, + "precision": "" + }, + { + "depends_on": "eval:doc.time_log_for == 'Manufacturing'", + "fieldname": "workstation", + "fieldtype": "Link", + "label": "Workstation", + "options": "Workstation", + "permlevel": 0, + "precision": "" + }, + { + "depends_on": "eval:doc.time_log_for == 'Manufacturing'", + "fieldname": "qty", + "fieldtype": "Float", + "label": "Quantity", + "permlevel": 0, + "precision": "" + }, { "fieldname": "billable", "fieldtype": "Check", @@ -151,7 +197,7 @@ "icon": "icon-time", "idx": 1, "is_submittable": 1, - "modified": "2014-10-22 16:53:26.993828", + "modified": "2014-11-19 11:39:02.633802", "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 6678392037..84430e9e0d 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -6,7 +6,8 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import cstr, comma_and +from frappe.utils import cstr, cint, comma_and + class OverlapError(frappe.ValidationError): pass @@ -19,6 +20,14 @@ class TimeLog(Document): self.set_status() self.validate_overlap() self.calculate_total_hours() + self.check_workstation_timings() + self.validate_qty() + + def on_submit(self): + self.update_production_order() + + def on_cancel(self): + self.update_production_order_on_cancel() def calculate_total_hours(self): from frappe.utils import time_diff_in_hours @@ -59,6 +68,72 @@ class TimeLog(Document): def before_update_after_submit(self): self.set_status() + def update_production_order(self): + if self.time_log_for=="Manufacturing" and self.operation: + d = self.get_qty_and_status() + required_qty = cint(frappe.db.get_value("Production Order" , self.production_order, "qty")) + if d.get('qty') == required_qty: + d['status'] = "Completed" + + dates = self.get_production_dates() + if self.from_time < dates.start_date: + dates.start_date = self.from_time + if self.to_time > dates.end_date: + dates.end_date = self.to_time + + self.production_order_update(dates, d.get('qty'), d['status']) + + def update_production_order_on_cancel(self): + if self.time_log_for=="Manufacturing" and self.operation: + d = frappe._dict() + d = self.get_qty_and_status() + dates = self.get_production_dates() + self.production_order_update(dates, d.get('qty'), d.get('status')) + + def get_qty_and_status(self): + status = "Work in Progress" + qty = cint(frappe.db.sql("""select sum(qty) as qty from `tabTime Log` where production_order = %s + and operation = %s and docstatus=1""", (self.production_order, self.operation),as_dict=1)[0].qty) + if qty == 0: + status = "Pending" + return { + "qty": qty, + "status": status + } + + def get_production_dates(self): + return frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date from `tabTime Log` + where production_order = %s and operation = %s and docstatus=1""", + (self.production_order, self.operation), as_dict=1)[0] + + def production_order_update(self, dates, qty, status): + d = self.operation.split('. ',1) + frappe.db.sql("""update `tabProduction Order Operation` set actual_start_time = %s, actual_end_time = %s, + qty_completed = %s, status = %s where idx=%s and parent=%s and operation = %s """, + (dates.start_date, dates.end_date, qty, status, d[0], self.production_order, d[1] )) + + def check_workstation_timings(self): + if self.workstation: + frappe.get_doc("Workstation", self.workstation).check_if_within_operating_hours(self.from_time, self.to_time) + + def validate_qty(self): + if self.qty == None: + self.qty=0 + required_qty = cint(frappe.db.get_value("Production Order" , self.production_order, "qty")) + completed_qty = self.get_qty_and_status().get('qty') + if (completed_qty + cint(self.qty)) > required_qty: + frappe.throw(_("Quantity cannot be greater than pending quantity that is {0}").format(required_qty)) + +@frappe.whitelist() +def get_workstation(production_order, operation): + if operation: + d = operation.split('. ',1) + idx = d[0] + operation = d[1] + + return frappe.db.sql("""select workstation from `tabProduction Order Operation` where idx=%s and + parent=%s and operation = %s""", (idx, production_order, operation), as_dict=1)[0] + @frappe.whitelist() def get_events(start, end): from frappe.desk.reportview import build_match_conditions diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 5868fd4cf8..308eb744fa 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -82,10 +82,10 @@ class Company(Document): def create_default_accounts(self): if not self.chart_of_accounts: - frappe.throw(_("Please select Chart of Accounts")) - else: - from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts - create_charts(self.chart_of_accounts, self.name) + self.chart_of_accounts = "Standard" + + from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts + create_charts(self.chart_of_accounts, self.name) frappe.db.set(self, "default_receivable_account", frappe.db.get_value("Account", {"company": self.name, "account_type": "Receivable"}))