From 52729bf36934711d2d748f99b73be35fb4651da1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 10 Mar 2019 14:26:30 +0530 Subject: [PATCH] feat: child table to add multiple time logs in job card --- .../doctype/job_card/job_card.js | 29 ++- .../doctype/job_card/job_card.json | 205 +++++++++++++---- .../doctype/job_card/job_card.py | 109 +++++---- .../doctype/job_card_time_log/__init__.py | 0 .../job_card_time_log/job_card_time_log.json | 208 ++++++++++++++++++ .../job_card_time_log/job_card_time_log.py | 9 + .../doctype/work_order/test_work_order.py | 15 +- erpnext/patches.txt | 1 + .../patches/v11_1/make_job_card_time_logs.py | 29 +++ 9 files changed, 513 insertions(+), 92 deletions(-) create mode 100644 erpnext/manufacturing/doctype/job_card_time_log/__init__.py create mode 100644 erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json create mode 100644 erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py create mode 100644 erpnext/patches/v11_1/make_job_card_time_logs.py diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 3fe9b8af30..95549d5a24 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -18,20 +18,27 @@ frappe.ui.form.on('Job Card', { } if (frm.doc.docstatus == 0) { - if (!frm.doc.actual_start_date || !frm.doc.actual_end_date) { - frm.trigger("make_dashboard"); - } + frm.trigger("make_dashboard"); - if (!frm.doc.actual_start_date) { + if (!frm.doc.job_started) { frm.add_custom_button(__("Start Job"), () => { - frm.set_value('actual_start_date', frappe.datetime.now_datetime()); + 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 if (!frm.doc.actual_end_date) { + } else { frm.add_custom_button(__("Complete Job"), () => { - frm.set_value('actual_end_date', frappe.datetime.now_datetime()); - frm.save(); - frm.savesubmit(); + 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(); + } + }) }); } } @@ -53,8 +60,8 @@ frappe.ui.form.on('Job Card', { var section = frm.dashboard.add_section(timer); - if (frm.doc.actual_start_date) { - let currentIncrement = moment(frappe.datetime.now_datetime()).diff(moment(frm.doc.actual_start_date),"seconds"); + if (frm.doc.started_time) { + let currentIncrement = moment(frappe.datetime.now_datetime()).diff(moment(frm.doc.started_time),"seconds"); initialiseTimer(); function initialiseTimer() { diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index b020c89053..39c5cce313 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -21,6 +21,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "work_order", "fieldtype": "Link", "hidden": 0, @@ -54,6 +55,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "bom_no", "fieldtype": "Link", "hidden": 0, @@ -87,6 +89,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "workstation", "fieldtype": "Link", "hidden": 0, @@ -120,6 +123,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "operation", "fieldtype": "Link", "hidden": 0, @@ -153,6 +157,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_4", "fieldtype": "Column Break", "hidden": 0, @@ -185,6 +190,7 @@ "collapsible": 0, "columns": 0, "default": "Today", + "fetch_if_empty": 0, "fieldname": "posting_date", "fieldtype": "Date", "hidden": 0, @@ -217,6 +223,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "company", "fieldtype": "Link", "hidden": 0, @@ -250,6 +257,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "for_quantity", "fieldtype": "Float", "hidden": 0, @@ -282,6 +290,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "wip_warehouse", "fieldtype": "Link", "hidden": 0, @@ -315,6 +324,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "timing_detail", "fieldtype": "Section Break", "hidden": 0, @@ -347,6 +357,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "employee", "fieldtype": "Link", "hidden": 0, @@ -380,7 +391,74 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "time_in_mins", + "fetch_if_empty": 0, + "fieldname": "time_logs", + "fieldtype": "Table", + "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, + "label": "Time Logs", + "length": 0, + "no_copy": 0, + "options": "Job Card Time Log", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "section_break_13", + "fieldtype": "Section 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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "total_completed_qty", "fieldtype": "Float", "hidden": 0, "ignore_user_permissions": 0, @@ -389,7 +467,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Time In Mins", + "label": "Total Completed Qty", "length": 0, "no_copy": 0, "permlevel": 0, @@ -412,7 +490,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "column_break_13", + "fetch_if_empty": 0, + "fieldname": "column_break_15", "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, @@ -443,8 +522,9 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "actual_start_date", - "fieldtype": "Datetime", + "fetch_if_empty": 0, + "fieldname": "total_time_in_mins", + "fieldtype": "Float", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -452,46 +532,14 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Actual Start Date", + "label": "Total Time in Mins", "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "actual_end_date", - "fieldtype": "Datetime", - "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, - "label": "Actual End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, @@ -507,6 +555,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_8", "fieldtype": "Section Break", "hidden": 0, @@ -539,6 +588,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "items", "fieldtype": "Table", "hidden": 0, @@ -572,6 +622,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "more_information", "fieldtype": "Section Break", "hidden": 0, @@ -604,6 +655,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "operation_id", "fieldtype": "Data", "hidden": 1, @@ -637,6 +689,7 @@ "collapsible": 0, "columns": 0, "default": "0", + "fetch_if_empty": 0, "fieldname": "transferred_qty", "fieldtype": "Float", "hidden": 0, @@ -670,6 +723,7 @@ "collapsible": 0, "columns": 0, "default": "0", + "fetch_if_empty": 0, "fieldname": "requested_qty", "fieldtype": "Float", "hidden": 0, @@ -702,6 +756,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "project", "fieldtype": "Link", "hidden": 0, @@ -735,6 +790,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "remarks", "fieldtype": "Small Text", "hidden": 0, @@ -767,6 +823,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_20", "fieldtype": "Column Break", "hidden": 0, @@ -799,6 +856,7 @@ "collapsible": 0, "columns": 0, "default": "Open", + "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", "hidden": 0, @@ -832,6 +890,73 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, + "fieldname": "job_started", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Job Started", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "started_time", + "fieldtype": "Datetime", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Started Time", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "amended_from", "fieldtype": "Link", "hidden": 0, @@ -868,7 +993,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-12-13 17:23:57.986381", + "modified": "2019-03-10 17:38:37.499871", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index ea9f714fc8..23a4e5105c 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -11,44 +11,56 @@ from frappe.model.document import Document class JobCard(Document): def validate(self): - self.validate_actual_dates() - self.set_time_in_mins() + self.validate_time_logs() self.set_status() - def validate_actual_dates(self): - if get_datetime(self.actual_start_date) > get_datetime(self.actual_end_date): - frappe.throw(_("Actual start date must be less than actual end date")) + def validate_time_logs(self): + self.total_completed_qty = 0.0 + self.total_time_in_mins = 0.0 - if not (self.employee and self.actual_start_date and self.actual_end_date): - return + for d in self.get('time_logs'): + if get_datetime(d.from_time) > get_datetime(d.to_time): + frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx)) - data = frappe.db.sql(""" select name from `tabJob Card` - where - ((%(actual_start_date)s > actual_start_date and %(actual_start_date)s < actual_end_date) or - (%(actual_end_date)s > actual_start_date and %(actual_end_date)s < actual_end_date) or - (%(actual_start_date)s <= actual_start_date and %(actual_end_date)s >= actual_end_date)) and - name != %(name)s and employee = %(employee)s and docstatus =1 - """, { - 'actual_start_date': self.actual_start_date, - 'actual_end_date': self.actual_end_date, - 'employee': self.employee, - 'name': self.name - }, as_dict=1) + data = self.get_overlap_for(d) + if data: + frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}") + .format(d.idx, self.name, data.name)) - if data: - frappe.throw(_("Start date and end date is overlapping with the job card {1}") - .format(data[0].name, data[0].name)) + if d.from_time and d.to_time: + d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60 + self.total_time_in_mins += d.time_in_mins - def set_time_in_mins(self): - if self.actual_start_date and self.actual_end_date: - self.time_in_mins = time_diff_in_hours(self.actual_end_date, self.actual_start_date) * 60 + if d.completed_qty: + self.total_completed_qty += d.completed_qty + + def get_overlap_for(self, args): + existing = frappe.db.sql("""select jc.name as name from + `tabJob Card Time Log` jctl, `tabJob Card` jc where jctl.parent = jc.name and + ( + (%(from_time)s > jctl.from_time and %(from_time)s < jctl.to_time) or + (%(to_time)s > jctl.from_time and %(to_time)s < jctl.to_time) or + (%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time)) + and jctl.name!=%(name)s + and jc.name!=%(parent)s + and jc.docstatus < 2 + and jc.employee = %(employee)s """, + { + "from_time": args.from_time, + "to_time": args.to_time, + "name": args.name or "No Name", + "parent": args.parent or "No Name", + "employee": self.employee + }, as_dict=True) + + return existing[0] if existing else None def get_required_items(self): if not self.get('work_order'): return doc = frappe.get_doc('Work Order', self.get('work_order')) - if doc.transfer_material_against == 'Work Order' and doc.skip_transfer: + if doc.transfer_material_against == 'Work Order' or doc.skip_transfer: return for d in doc.required_items: @@ -67,36 +79,51 @@ class JobCard(Document): }) def on_submit(self): - self.validate_dates() + self.validate_job_card() self.update_work_order() self.set_transferred_qty() - def validate_dates(self): - if not self.actual_start_date and not self.actual_end_date: - frappe.throw(_("Actual start date and actual end date is mandatory")) - def on_cancel(self): self.update_work_order() self.set_transferred_qty() + def validate_job_card(self): + if not self.time_logs: + frappe.throw(_("Time logs are required for job card {0}").format(self.name)) + + 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")) + def update_work_order(self): if not self.work_order: return - data = frappe.db.get_value("Job Card", {'docstatus': 1, 'operation_id': self.operation_id}, - ['sum(time_in_mins)', 'min(actual_start_date)', 'max(actual_end_date)', 'sum(for_quantity)']) + for_quantity, time_in_mins = 0, 0 + from_time_list, to_time_list = [], [] - if data: - time_in_mins, actual_start_date, actual_end_date, for_quantity = data + for d in frappe.get_all('Job Card', + filters = {'docstatus': 1, 'operation_id': self.operation_id}): + doc = frappe.get_doc('Job Card', d.name) + + for_quantity += doc.total_completed_qty + time_in_mins += doc.total_time_in_mins + for time_log in doc.time_logs: + from_time_list.append(time_log.from_time) + to_time_list.append(time_log.to_time) + + if for_quantity: wo = frappe.get_doc('Work Order', self.work_order) for data in wo.operations: if data.name == self.operation_id: data.completed_qty = for_quantity data.actual_operation_time = time_in_mins - data.actual_start_time = actual_start_date - data.actual_end_time = actual_end_date + data.actual_start_time = min(from_time_list) + data.actual_end_time = max(to_time_list) wo.flags.ignore_validate_update_after_submit = True wo.update_operation_status() @@ -132,9 +159,11 @@ class JobCard(Document): break if completed: - job_cards = frappe.get_all('Job Card', filters = {'work_order': self.work_order, + job_cards = frappe.get_all('Job Card', filters = {'work_order': self.work_order, 'docstatus': ('!=', 2)}, fields = 'sum(transferred_qty) as qty', group_by='operation_id') - qty = min([d.qty for d in job_cards]) + + if job_cards: + qty = min([d.qty for d in job_cards]) doc.db_set('material_transferred_for_manufacturing', qty) @@ -147,7 +176,7 @@ class JobCard(Document): 2: "Cancelled" }[self.docstatus or 0] - if self.actual_start_date: + if self.time_logs: self.status = 'Work In Progress' if (self.docstatus == 1 and diff --git a/erpnext/manufacturing/doctype/job_card_time_log/__init__.py b/erpnext/manufacturing/doctype/job_card_time_log/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json new file mode 100644 index 0000000000..2aab71dee4 --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json @@ -0,0 +1,208 @@ +{ + "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", + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + } + ], + "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 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py new file mode 100644 index 0000000000..3dc6689121 --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class JobCardTimeLog(Document): + pass diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 69381c53b3..b292047aa6 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -302,6 +302,19 @@ class TestWorkOrder(unittest.TestCase): self.assertEqual(len(ste.additional_costs), 1) self.assertEqual(ste.total_additional_costs, 1000) + def test_job_card(self): + data = frappe.get_cached_value('BOM', + {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) + + if data: + bom, bom_item = data + + bom_doc = frappe.get_doc('BOM', bom) + work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) + + job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order}) + self.assertEqual(len(job_cards), len(bom_doc.operations)) + def test_work_order_with_non_transfer_item(self): items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0} for item, allow_transfer in items.items(): @@ -346,7 +359,7 @@ def make_wo_order_test_record(**args): wo_order = frappe.new_doc("Work Order") wo_order.production_item = args.production_item or args.item or args.item_code or "_Test FG Item" - wo_order.bom_no = frappe.db.get_value("BOM", {"item": wo_order.production_item, + wo_order.bom_no = args.bom_no or frappe.db.get_value("BOM", {"item": wo_order.production_item, "is_active": 1, "is_default": 1}) wo_order.qty = args.qty or 10 wo_order.wip_warehouse = args.wip_warehouse or "_Test Warehouse - _TC" diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 433141cdf6..7d49ad5fe3 100755 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -588,3 +588,4 @@ execute:frappe.delete_doc('DocType', 'Notification Control') erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019 erpnext.patches.v11_0.make_italian_localization_fields # 01-03-2019 +erpnext.patches.v11_1.make_job_card_time_logs \ No newline at end of file diff --git a/erpnext/patches/v11_1/make_job_card_time_logs.py b/erpnext/patches/v11_1/make_job_card_time_logs.py new file mode 100644 index 0000000000..6e708df48d --- /dev/null +++ b/erpnext/patches/v11_1/make_job_card_time_logs.py @@ -0,0 +1,29 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('manufacturing', 'doctype', 'job_card_time_log') + + if (frappe.db.table_exists("Job Card") + and frappe.get_meta("Job Card").has_field("actual_start_date")): + time_logs = [] + for d in frappe.get_all('Job Card', + fields = ["actual_start_date", "actual_end_date", "time_in_mins", "name", "for_quantity"], + filters = {'docstatus': ("<", 2)}): + if d.actual_start_date: + time_logs.append([d.actual_start_date, d.actual_end_date, d.time_in_mins, + d.for_quantity, d.name, 'Job Card', 'time_logs', frappe.generate_hash("", 10)]) + + if time_logs: + frappe.db.sql(""" INSERT INTO + `tabJob Card Time Log` + (from_time, to_time, time_in_mins, completed_qty, parent, parenttype, parentfield, name) + values {values} + """.format(values = ','.join(['%s'] * len(time_logs))), tuple(time_logs)) + + frappe.reload_doc('manufacturing', 'doctype', 'job_card') + frappe.db.sql(""" update `tabJob Card` set total_completed_qty = for_quantity, + total_time_in_mins = time_in_mins where docstatus < 2 """) \ No newline at end of file