diff --git a/erpnext/config/manufacturing.py b/erpnext/config/manufacturing.py index 2c18eeb83a..012f1cad0a 100644 --- a/erpnext/config/manufacturing.py +++ b/erpnext/config/manufacturing.py @@ -120,13 +120,7 @@ def get_data(): { "type": "report", "is_query_report": True, - "name": "Open Work Orders", - "doctype": "Work Order" - }, - { - "type": "report", - "is_query_report": True, - "name": "Work Orders in Progress", + "name": "Work Order Summary", "doctype": "Work Order" }, { @@ -135,12 +129,6 @@ def get_data(): "name": "Issued Items Against Work Order", "doctype": "Work Order" }, - { - "type": "report", - "is_query_report": True, - "name": "Completed Work Orders", - "doctype": "Work Order" - }, { "type": "report", "is_query_report": True, diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json index 18604e283a..0464b763c0 100644 --- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json @@ -3,7 +3,7 @@ { "hidden": 0, "label": "Production", - "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Activity Type\"\n ],\n \"description\": \"Time Sheet for manufacturing.\",\n \"label\": \"Timesheet\",\n \"name\": \"Timesheet\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Downtime Entry\",\n \"name\": \"Downtime Entry\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, @@ -13,7 +13,7 @@ { "hidden": 0, "label": "Reports", - "links": "[\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Open Work Orders\",\n \"name\": \"Open Work Orders\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Work Orders in Progress\",\n \"name\": \"Work Orders in Progress\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Issued Items Against Work Order\",\n \"name\": \"Issued Items Against Work Order\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Completed Work Orders\",\n \"name\": \"Completed Work Orders\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Production Analytics\",\n \"name\": \"Production Analytics\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Search\",\n \"name\": \"BOM Search\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Stock Report\",\n \"name\": \"BOM Stock Report\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Work Order Summary\",\n \"name\": \"Work Order Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Issued Items Against Work Order\",\n \"name\": \"Issued Items Against Work Order\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Production Analytics\",\n \"name\": \"Production Analytics\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Quality Inspection\"\n ],\n \"doctype\": \"Quality Inspection\",\n \"is_query_report\": true,\n \"label\": \"Quality Inspection Summary\",\n \"name\": \"Quality Inspection Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Downtime Entry\"\n ],\n \"doctype\": \"Downtime Entry\",\n \"is_query_report\": true,\n \"label\": \"Downtime Analysis\",\n \"name\": \"Downtime Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Job Card\"\n ],\n \"doctype\": \"Job Card\",\n \"is_query_report\": true,\n \"label\": \"Job Card Summary\",\n \"name\": \"Job Card Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Search\",\n \"name\": \"BOM Search\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Stock Report\",\n \"name\": \"BOM Stock Report\",\n \"type\": \"report\"\n }\n]" }, { "hidden": 0, @@ -32,7 +32,12 @@ } ], "category": "Domains", - "charts": [], + "charts": [ + { + "chart_name": "Production Analysis", + "label": "Production Analysis" + } + ], "creation": "2020-03-02 17:11:37.032604", "developer_mode_only": 0, "disable_user_customization": 0, @@ -42,7 +47,7 @@ "idx": 0, "is_standard": 1, "label": "Manufacturing", - "modified": "2020-04-01 11:28:50.979358", + "modified": "2020-04-27 00:17:26.323677", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", @@ -50,5 +55,21 @@ "pin_to_bottom": 0, "pin_to_top": 0, "restrict_to_domain": "Manufacturing", - "shortcuts": [] + "shortcuts": [ + { + "label": "Item", + "link_to": "Item", + "type": "DocType" + }, + { + "label": "BOM", + "link_to": "BOM", + "type": "DocType" + }, + { + "label": "Work Order", + "link_to": "Work Order", + "type": "DocType" + } + ] } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/downtime_entry/__init__.py b/erpnext/manufacturing/doctype/downtime_entry/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js new file mode 100644 index 0000000000..3b7f5ba8d7 --- /dev/null +++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Downtime Entry', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json new file mode 100644 index 0000000000..6ec088ad9e --- /dev/null +++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json @@ -0,0 +1,120 @@ +{ + "actions": [], + "allow_import": 1, + "creation": "2020-04-18 04:50:46.187638", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "workstation", + "operator", + "column_break_4", + "from_time", + "to_time", + "downtime", + "downtime_reason_section", + "reason" + ], + "fields": [ + { + "fieldname": "workstation", + "fieldtype": "Link", + "label": "Workstation / Machine", + "options": "Workstation", + "reqd": 1 + }, + { + "fieldname": "from_time", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "From Time", + "reqd": 1 + }, + { + "fieldname": "to_time", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "To Time", + "reqd": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "reason", + "fieldtype": "Text", + "label": "Reason", + "reqd": 1 + }, + { + "fieldname": "operator", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Operator", + "options": "Employee", + "reqd": 1 + }, + { + "fieldname": "downtime_reason_section", + "fieldtype": "Section Break", + "label": "Downtime Reason" + }, + { + "description": "In Mins", + "fieldname": "downtime", + "fieldtype": "Float", + "label": "Downtime", + "read_only": 1 + } + ], + "links": [], + "modified": "2020-04-20 17:34:51.299607", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Downtime Entry", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "workstation", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py new file mode 100644 index 0000000000..56ec4356af --- /dev/null +++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import time_diff_in_hours +from frappe.model.document import Document + +class DowntimeEntry(Document): + def validate(self): + if self.from_time and self.to_time: + self.downtime = time_diff_in_hours(self.to_time, self.from_time) * 60 diff --git a/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py new file mode 100644 index 0000000000..8b2a8d36c1 --- /dev/null +++ b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestDowntimeEntry(unittest.TestCase): + pass diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 7661fffa86..fba670c1c1 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2018-07-09 17:23:29.518745", "doctype": "DocType", @@ -264,8 +265,10 @@ { "fetch_from": "work_order.production_item", "fieldname": "production_item", - "fieldtype": "Read Only", - "label": "Production Item" + "fieldtype": "Link", + "label": "Production Item", + "options": "Item", + "read_only": 1 }, { "fieldname": "barcode", @@ -274,7 +277,8 @@ "read_only": 1 }, { - "fetch_from": "work_order.item_name", + "fetch_from": "production_item.item_name", + "fetch_if_empty": 1, "fieldname": "item_name", "fieldtype": "Read Only", "label": "Item Name" @@ -290,7 +294,8 @@ } ], "is_submittable": 1, - "modified": "2020-03-27 13:36:35.417502", + "links": [], + "modified": "2020-04-20 15:14:00.273441", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 00a67a03d6..8be22875a3 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -38,11 +38,12 @@ "required_items", "time", "planned_start_date", - "actual_start_date", - "column_break_13", "planned_end_date", - "actual_end_date", "expected_delivery_date", + "column_break_13", + "actual_start_date", + "actual_end_date", + "lead_time", "operations_section", "transfer_material_against", "operations", @@ -108,6 +109,8 @@ }, { "depends_on": "eval:doc.production_item", + "fetch_from": "production_item.item_name", + "fetch_if_empty": 1, "fieldname": "item_name", "fieldtype": "Data", "label": "Item Name", @@ -281,27 +284,30 @@ "reqd": 1 }, { + "allow_on_submit": 1, "fieldname": "actual_start_date", "fieldtype": "Datetime", "label": "Actual Start Date", - "read_only": 1 + "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0" }, { "fieldname": "column_break_13", "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "fieldname": "planned_end_date", "fieldtype": "Datetime", "label": "Planned End Date", "no_copy": 1, - "read_only": 1 + "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0" }, { + "allow_on_submit": 1, "fieldname": "actual_end_date", "fieldtype": "Datetime", "label": "Actual End Date", - "read_only": 1 + "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0" }, { "allow_on_submit": 1, @@ -476,6 +482,13 @@ "fieldtype": "Link", "label": "Source Warehouse", "options": "Warehouse" + }, + { + "description": "In Mins", + "fieldname": "lead_time", + "fieldtype": "Float", + "label": "Lead Time", + "read_only": 1 } ], "icon": "fa fa-cogs", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 8301f30d83..c2789559b0 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -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, get_link_to_form +from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form, time_diff_in_hours 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 @@ -279,7 +279,7 @@ class WorkOrder(Document): if enable_capacity_planning and job_card_doc: row.planned_start_time = job_card_doc.time_logs[-1].from_time row.planned_end_time = job_card_doc.time_logs[-1].to_time - print(row.planned_start_time, original_start_time, plan_days) + if date_diff(row.planned_start_time, original_start_time) > plan_days: frappe.message_log.pop() frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.") @@ -437,8 +437,6 @@ class WorkOrder(Document): frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'")) def set_actual_dates(self): - self.actual_start_date = None - self.actual_end_date = None if self.get("operations"): actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time] if actual_start_dates: @@ -447,6 +445,27 @@ class WorkOrder(Document): actual_end_dates = [d.actual_end_time for d in self.get("operations") if d.actual_end_time] if actual_end_dates: self.actual_end_date = max(actual_end_dates) + else: + data = frappe.get_all("Stock Entry", + fields = ["timestamp(posting_date, posting_time) as posting_datetime"], + filters = { + "work_order": self.name, + "purpose": ("in", ["Material Transfer for Manufacture", "Manufacture"]) + } + ) + + if data and len(data): + dates = [d.posting_datetime for d in data] + self.actual_start_date = min(dates) + + if self.status == "Completed": + self.actual_end_date = max(dates) + + self.set_lead_time() + + def set_lead_time(self): + if self.actual_start_date and self.actual_end_date: + self.lead_time = flt(time_diff_in_hours(self.actual_end_date, self.actual_start_date) * 60) def delete_job_card(self): for d in frappe.get_all("Job Card", ["name"], {"work_order": self.name}): diff --git a/erpnext/manufacturing/report/downtime_analysis/__init__.py b/erpnext/manufacturing/report/downtime_analysis/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js new file mode 100644 index 0000000000..e20342792f --- /dev/null +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js @@ -0,0 +1,28 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Downtime Analysis"] = { + "filters": [ + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + reqd: 1 + }, + { + label: __("To Date"), + fieldname:"to_date", + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1, + }, + { + label: __("Machine"), + fieldname: "workstation", + fieldtype: "Link", + options: "Workstation" + } + ] +}; diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json new file mode 100644 index 0000000000..5edc7781a2 --- /dev/null +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json @@ -0,0 +1,31 @@ +{ + "add_total_row": 1, + "creation": "2020-04-20 18:26:04.345289", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "Gadgets International", + "modified": "2020-04-20 18:26:04.345289", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Downtime Analysis", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Downtime Entry", + "report_name": "Downtime Analysis", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Manufacturing User" + }, + { + "role": "Manufacturing Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py new file mode 100644 index 0000000000..d69ec189ba --- /dev/null +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py @@ -0,0 +1,96 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import flt +from frappe import _ + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns(filters) + chart_data = get_chart_data(data, filters) + return columns, data, None, chart_data + +def get_data(filters): + query_filters = {} + + fields = ["workstation", "operator", "from_time", "to_time", "downtime", "reason"] + + query_filters["from_time"] = (">=", filters.get("from_date")) + query_filters["to_time"] = ("<=", filters.get("to_date")) + + if filters.get("workstation"): + query_filters["workstation"] = filters.get("workstation") + + return frappe.get_all("Downtime Entry", fields= fields, filters=query_filters) + +def get_chart_data(data, columns): + labels = sorted(list(set([d.workstation for d in data]))) + + workstation_wise_data = {} + for d in data: + if d.workstation not in workstation_wise_data: + workstation_wise_data[d.workstation] = 0 + + workstation_wise_data[d.workstation] += flt(d.downtime, 2) + + datasets = [] + for label in labels: + datasets.append(workstation_wise_data.get(label, 0)) + + chart = { + "data": { + "labels": labels, + "datasets": [ + {"name": "Dataset 1", "values": datasets} + ] + }, + "type": "bar", + "colors": ["#ff5858"] + } + + return chart + +def get_columns(filters): + return [ + { + "label": _("Machine"), + "fieldname": "workstation", + "fieldtype": "Link", + "options": "Workstation", + "width": 100 + }, + { + "label": _("Operator"), + "fieldname": "operator", + "fieldtype": "Link", + "options": "Employee", + "width": 130 + }, + { + "label": _("From Time"), + "fieldname": "from_time", + "fieldtype": "Datetime", + "width": 160 + }, + { + "label": _("To Time"), + "fieldname": "to_time", + "fieldtype": "Datetime", + "width": 160 + }, + { + "label": _("Downtime (In Mins)"), + "fieldname": "downtime", + "fieldtype": "Float", + "width": 150 + }, + { + "label": _("Reason"), + "fieldname": "reason", + "fieldtype": "Text", + "width": 180 + } + ] \ No newline at end of file diff --git a/erpnext/manufacturing/report/job_card_summary/__init__.py b/erpnext/manufacturing/report/job_card_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js new file mode 100644 index 0000000000..b7e307183f --- /dev/null +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js @@ -0,0 +1,52 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Job Card Summary"] = { + "filters": [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -12), + reqd: 1 + }, + { + label: __("To Date"), + fieldname:"to_date", + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1, + }, + { + label: __("Status"), + fieldname: "status", + fieldtype: "Select", + options: ["", "Open", "Work In Progress", "Completed", "On Hold"] + }, + { + label: __("Sales Orders"), + fieldname: "sales_order", + fieldtype: "MultiSelectList", + get_data: function(txt) { + return frappe.db.get_link_options('Sales Order', txt); + } + }, + { + label: __("Production Item"), + fieldname: "production_item", + fieldtype: "MultiSelectList", + get_data: function(txt) { + return frappe.db.get_link_options('Item', txt); + } + } + ] +}; diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.json b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json new file mode 100644 index 0000000000..9f08fc34cb --- /dev/null +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json @@ -0,0 +1,34 @@ +{ + "add_total_row": 0, + "creation": "2020-04-20 12:00:21.436619", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "Gadgets International", + "modified": "2020-04-20 12:00:21.436619", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Job Card Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Job Card", + "report_name": "Job Card Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing User" + }, + { + "role": "Stock User" + }, + { + "role": "Manufacturing Manager" + }, + { + "role": "Stock Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py new file mode 100644 index 0000000000..0f60c132ec --- /dev/null +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -0,0 +1,197 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import getdate, flt +from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period) + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns(filters) + chart_data = get_chart_data(data, filters) + return columns, data, None, chart_data + +def get_data(filters): + query_filters = {"docstatus": ("<", 2)} + + fields = ["name", "status", "work_order", "production_item", "item_name", + "total_completed_qty", "workstation", "operation", "employee_name", "total_time_in_mins"] + + for field in ["work_order", "workstation", "operation", "company"]: + if filters.get(field): + query_filters[field] = ("in", filters.get(field)) + + data = frappe.get_all("Job Card", + fields= fields, filters=query_filters) + + if not data: return [] + + job_cards = [d.name for d in data] + job_card_time_details = {} + for job_card_data in frappe.get_all("Job Card Time Log", + fields=["min(from_time) as from_time", "max(to_time) as to_time", "parent"], + filters={"docstatus": ("<", 2), "parent": ("in", job_cards)}, group_by="parent"): + job_card_time_details[job_card_data.parent] = job_card_data + + for d in data: + if d.status == "Material Transferred": + d.status = "Open" + + if job_card_time_details.get(d.name): + d.from_time = job_card_time_details.get(d.name).from_time + d.to_time = job_card_time_details.get(d.name).to_time + + return data + +def get_chart_data(job_card_details, filters): + labels, periodic_data = prepare_chart_data(job_card_details, filters) + + not_start, in_progress, on_hold, completed = [], [], [] , [] + datasets = [] + + for d in labels: + not_start.append(periodic_data.get("Open").get(d)) + in_progress.append(periodic_data.get("Work In Progress").get(d)) + on_hold.append(periodic_data.get("On Hold").get(d)) + completed.append(periodic_data.get("Completed").get(d)) + + datasets.append({'name':'Open', 'values': not_start}) + datasets.append({'name':'Work In Progress', 'values': in_progress}) + datasets.append({'name':'On Hold', 'values': on_hold}) + datasets.append({'name':'Completed', 'values': completed}) + + chart = { + "data": { + 'labels': labels, + 'datasets': datasets + }, + "type": "bar", + "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"], + "axisOptions": { + "xAxisMode": "tick" + }, + "barOptions": { + "stacked": 1 + } + } + + return chart + +def prepare_chart_data(job_card_details, filters): + labels = [] + + periodic_data = { + "Open": {}, + "Work In Progress": {}, + "On Hold": {}, + "Completed": {} + } + + filters.range = "Monthly" + + ranges = get_period_date_ranges(filters) + for from_date, end_date in ranges: + period = get_period(end_date, filters) + if period not in labels: + labels.append(period) + + for d in job_card_details: + if getdate(d.from_time) >= from_date and getdate(d.to_time) <= end_date: + if periodic_data.get(d.status) and periodic_data.get(d.status).get(period): + periodic_data[d.status][period] += 1 + else: + periodic_data[d.status][period] = 1 + + return labels, periodic_data + +def get_columns(filters): + columns = [ + { + "label": _("Id"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Job Card", + "width": 100 + }, + ] + + if not filters.get("status"): + columns.append( + { + "label": _("Status"), + "fieldname": "status", + "width": 100 + }, + ) + + columns.extend([ + { + "label": _("Work Order"), + "fieldname": "work_order", + "fieldtype": "Link", + "options": "Work Order", + "width": 100 + }, + { + "label": _("Production Item"), + "fieldname": "production_item", + "fieldtype": "Link", + "options": "Item", + "width": 110 + }, + { + "label": _("Item Name"), + "fieldname": "item_name", + "fieldtype": "Data", + "width": 100 + }, + { + "label": _("Workstation"), + "fieldname": "workstation", + "fieldtype": "Link", + "options": "Workstation", + "width": 110 + }, + { + "label": _("Operation"), + "fieldname": "operation", + "fieldtype": "Link", + "options": "Operation", + "width": 110 + }, + { + "label": _("Employee Name"), + "fieldname": "employee_name", + "fieldtype": "Data", + "width": 110 + }, + { + "label": _("Total Completed Qty"), + "fieldname": "total_completed_qty", + "fieldtype": "Float", + "width": 120 + }, + { + "label": _("From Time"), + "fieldname": "from_time", + "fieldtype": "Datetime", + "width": 120 + }, + { + "label": _("To Time"), + "fieldname": "to_time", + "fieldtype": "Datetime", + "width": 120 + }, + { + "label": _("Time Required (In Mins)"), + "fieldname": "total_time_in_mins", + "fieldtype": "Float", + "width": 100 + } + ]) + + return columns \ No newline at end of file diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py index 7447a1f670..f0bdfeda21 100644 --- a/erpnext/manufacturing/report/production_analytics/production_analytics.py +++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py @@ -38,7 +38,6 @@ def get_columns(filters): def get_periodic_data(filters, entry): periodic_data = { - "All Work Orders": {}, "Not Started": {}, "Overdue": {}, "Pending": {}, @@ -51,7 +50,6 @@ def get_periodic_data(filters, entry): period = get_period(end_date, filters) for d in entry: if getdate(d.creation) <= getdate(from_date) or getdate(d.creation) <= getdate(end_date) : - periodic_data = update_periodic_data(periodic_data, "All Work Orders", period) if d.status == 'Completed': if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate(from_date): periodic_data = update_periodic_data(periodic_data, "Completed", period) @@ -61,7 +59,7 @@ def get_periodic_data(filters, entry): elif getdate(d.planned_start_date) < getdate(from_date) : periodic_data = update_periodic_data(periodic_data, "Overdue", period) - + else: periodic_data = update_periodic_data(periodic_data, "Not Started", period) @@ -99,7 +97,7 @@ def get_data(filters, columns): periodic_data = get_periodic_data(filters,entry) - labels = ["All Work Orders", "Not Started", "Overdue", "Pending", "Completed"] + labels = ["Not Started", "Overdue", "Pending", "Completed"] chart_data = get_chart_data(periodic_data,columns) ranges = get_period_date_ranges(filters) @@ -123,13 +121,11 @@ def get_chart_data(periodic_data, columns): datasets = [] for d in labels: - all_data.append(periodic_data.get("All Work Orders").get(d)) not_start.append(periodic_data.get("Not Started").get(d)) overdue.append(periodic_data.get("Overdue").get(d)) pending.append(periodic_data.get("Pending").get(d)) completed.append(periodic_data.get("Completed").get(d)) - datasets.append({'name':'All Work Orders', 'values': all_data}) datasets.append({'name':'Not Started', 'values': not_start}) datasets.append({'name':'Overdue', 'values': overdue}) datasets.append({'name':'Pending', 'values': pending}) @@ -139,9 +135,10 @@ def get_chart_data(periodic_data, columns): "data": { 'labels': labels, 'datasets': datasets - } + }, + "type": "bar", + "colors": ["#5e64ff", "#ff5858", "#ffa00a", "#98d85b"] } - chart["type"] = "line" return chart diff --git a/erpnext/manufacturing/report/quality_inspection_summary/__init__.py b/erpnext/manufacturing/report/quality_inspection_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js new file mode 100644 index 0000000000..d4587aa661 --- /dev/null +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js @@ -0,0 +1,40 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Quality Inspection Summary"] = { + "filters": [ + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -12), + reqd: 1 + }, + { + label: __("To Date"), + fieldname:"to_date", + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1, + }, + { + label: __("Status"), + fieldname: "status", + fieldtype: "Select", + options: ["", "Accepted", "Rejected"] + }, + { + label: __("Item Code"), + fieldname: "item_code", + fieldtype: "Link", + options: "Item" + }, + { + label: __("Inspected By"), + fieldname: "inspected_by", + fieldtype: "Link", + options: "User" + } + ] +}; diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json new file mode 100644 index 0000000000..48226e6b21 --- /dev/null +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "creation": "2020-04-26 18:23:53.475110", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letter_head": "Gadgets International", + "modified": "2020-04-26 18:24:50.529940", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Quality Inspection Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Quality Inspection", + "report_name": "Quality Inspection Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Quality Manager" + }, + { + "role": "Stock User" + }, + { + "role": "Stock Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py new file mode 100644 index 0000000000..9c1bfa4630 --- /dev/null +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py @@ -0,0 +1,133 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns(filters) + chart_data = get_chart_data(data, filters) + return columns, data , None, chart_data + +def get_data(filters): + query_filters = {"docstatus": ("<", 2)} + + fields = ["name", "status", "report_date", "item_code", "item_name", "sample_size", + "inspection_type", "reference_type", "reference_name", "inspected_by"] + + for field in ["status", "item_code", "status", "inspected_by"]: + if filters.get(field): + query_filters[field] = ("in", filters.get(field)) + + query_filters["report_date"] = (">=", filters.get("from_date")) + query_filters["report_date"] = ("<=", filters.get("to_date")) + + return frappe.get_all("Quality Inspection", + fields= fields, filters=query_filters, order_by="report_date asc") + +def get_chart_data(periodic_data, columns): + labels = ["Rejected", "Accepted"] + + status_wise_data = { + "Accepted": 0, + "Rejected": 0 + } + + datasets = [] + + for d in periodic_data: + status_wise_data[d.status] += 1 + + datasets.append({'name':'Qty Wise Chart', + 'values': [status_wise_data.get("Rejected"), status_wise_data.get("Accepted")]}) + + chart = { + "data": { + 'labels': labels, + 'datasets': datasets + }, + "type": "donut", + "height": 300, + "colors": ["#ff5858", "#98d85b"] + } + + return chart + +def get_columns(filters): + columns = [ + { + "label": _("Id"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Work Order", + "width": 100 + }, + { + "label": _("Report Date"), + "fieldname": "report_date", + "fieldtype": "Date", + "width": 150 + } + ] + + if not filters.get("status"): + columns.append( + { + "label": _("Status"), + "fieldname": "status", + "width": 100 + }, + ) + + columns.extend([ + { + "label": _("Item Code"), + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 130 + }, + { + "label": _("Item Name"), + "fieldname": "item_name", + "fieldtype": "Data", + "width": 130 + }, + { + "label": _("Sample Size"), + "fieldname": "sample_size", + "fieldtype": "Float", + "width": 110 + }, + { + "label": _("Inspection Type"), + "fieldname": "inspection_type", + "fieldtype": "Data", + "width": 110 + }, + { + "label": _("Document Type"), + "fieldname": "reference_type", + "fieldtype": "Data", + "width": 90 + }, + { + "label": _("Document Name"), + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "options": "reference_type", + "width": 150 + }, + { + "label": _("Inspected By"), + "fieldname": "inspected_by", + "fieldtype": "Link", + "options": "User", + "width": 150 + } + ]) + + return columns \ No newline at end of file diff --git a/erpnext/manufacturing/report/work_order_summary/__init__.py b/erpnext/manufacturing/report/work_order_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js new file mode 100644 index 0000000000..33b147df49 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js @@ -0,0 +1,58 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Work Order Summary"] = { + "filters": [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -12), + reqd: 1 + }, + { + label: __("To Date"), + fieldname:"to_date", + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1, + }, + { + label: __("Status"), + fieldname: "status", + fieldtype: "Select", + options: ["", "Not Started", "In Process", "Completed", "Stopped"] + }, + { + label: __("Sales Orders"), + fieldname: "sales_order", + fieldtype: "MultiSelectList", + get_data: function(txt) { + return frappe.db.get_link_options('Sales Order', txt); + } + }, + { + label: __("Production Item"), + fieldname: "production_item", + fieldtype: "MultiSelectList", + get_data: function(txt) { + return frappe.db.get_link_options('Item', txt); + } + }, + { + label: __("Age"), + fieldname:"age", + fieldtype: "Int", + default: "0" + }, + ] +}; diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.json b/erpnext/manufacturing/report/work_order_summary/work_order_summary.json new file mode 100644 index 0000000000..0d093e22e9 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.json @@ -0,0 +1,31 @@ +{ + "add_total_row": 0, + "creation": "2020-04-17 17:07:56.830358", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "Gadgets International", + "modified": "2020-04-19 16:59:47.979278", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Work Order Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Work Order", + "report_name": "Work Order Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing User" + }, + { + "role": "Stock User" + }, + { + "role": "Manufacturing Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py new file mode 100644 index 0000000000..92c31ab382 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -0,0 +1,173 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import date_diff, today +from frappe import _ + +def execute(filters=None): + columns, data = [], [] + + if not filters.get("age"): + filters["age"] = 0 + + data = get_data(filters) + columns = get_columns(filters) + chart_data = get_chart_data(data, filters) + return columns, data, None, chart_data + +def get_data(filters): + query_filters = {"docstatus": 1} + + fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty", + "planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"] + + for field in ["sales_order", "production_item", "status", "company"]: + if filters.get(field): + query_filters[field] = ("in", filters.get(field)) + + query_filters["planned_start_date"] = (">=", filters.get("from_date")) + query_filters["planned_end_date"] = ("<=", filters.get("to_date")) + + data = frappe.get_all("Work Order", + fields= fields, filters=query_filters, order_by="planned_start_date asc") + + res = [] + for d in data: + start_date = d.actual_start_date or d.planned_start_date + d.age = 0 + + if d.status != 'Completed': + d.age = date_diff(today(), start_date) + + if filters.get("age") <= d.age: + res.append(d) + + return res + +def get_chart_data(periodic_data, columns): + labels = ["Not Started", "In Process", "Stopped", "Completed"] + + status_wise_data = { + "Not Started": 0, + "In Process": 0, + "Stopped": 0, + "Completed": 0 + } + + for d in periodic_data: + if d.status == "In Process" and d.produced_qty: + status_wise_data["Completed"] += d.produced_qty + + status_wise_data[d.status] += d.qty + + values = [status_wise_data["Not Started"], status_wise_data["In Process"], + status_wise_data["Stopped"], status_wise_data["Completed"]] + + chart = { + "data": { + 'labels': labels, + 'datasets': [{'name':'Qty Wise Chart', 'values': values}] + }, + "type": "donut", + "height": 300, + "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"] + } + + return chart + +def get_columns(filters): + columns = [ + { + "label": _("Id"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Work Order", + "width": 100 + }, + ] + + if not filters.get("status"): + columns.append( + { + "label": _("Status"), + "fieldname": "status", + "width": 100 + }, + ) + + columns.extend([ + { + "label": _("Production Item"), + "fieldname": "production_item", + "fieldtype": "Link", + "options": "Item", + "width": 130 + }, + { + "label": _("Produce Qty"), + "fieldname": "qty", + "fieldtype": "Float", + "width": 110 + }, + { + "label": _("Produced Qty"), + "fieldname": "produced_qty", + "fieldtype": "Float", + "width": 110 + }, + { + "label": _("Sales Order"), + "fieldname": "sales_order", + "fieldtype": "Link", + "options": "Sales Order", + "width": 90 + }, + { + "label": _("Planned Start Date"), + "fieldname": "planned_start_date", + "fieldtype": "Date", + "width": 150 + }, + { + "label": _("Planned End Date"), + "fieldname": "planned_end_date", + "fieldtype": "Date", + "width": 150 + } + ]) + + if filters.get("status") != 'Not Started': + columns.extend([ + { + "label": _("Actual Start Date"), + "fieldname": "actual_start_date", + "fieldtype": "Date", + "width": 100 + }, + { + "label": _("Actual End Date"), + "fieldname": "actual_end_date", + "fieldtype": "Date", + "width": 100 + }, + { + "label": _("Age"), + "fieldname": "age", + "fieldtype": "Float", + "width": 110 + }, + ]) + + if filters.get("status") == 'Completed': + columns.extend([ + { + "label": _("Lead Time (in mins)"), + "fieldname": "lead_time", + "fieldtype": "Float", + "width": 110 + }, + ]) + + return columns \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4ae591b54b..b920d0334f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -685,3 +685,4 @@ execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation erpnext.patches.v12_0.set_serial_no_status +erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo diff --git a/erpnext/patches/v13_0/__init__.py b/erpnext/patches/v13_0/__init__.py index e69de29bb2..baffc48825 100644 --- a/erpnext/patches/v13_0/__init__.py +++ b/erpnext/patches/v13_0/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py new file mode 100644 index 0000000000..3fab0040fb --- /dev/null +++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py @@ -0,0 +1,43 @@ + +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from frappe.utils import add_to_date + +def execute(): + frappe.reload_doc("manufacturing", "doctype", "work_order") + frappe.reload_doc("manufacturing", "doctype", "work_order_item") + frappe.reload_doc("manufacturing", "doctype", "job_card") + + data = frappe.get_all("Work Order", + filters = { + "docstatus": 1, + "status": ("in", ["In Process", "Completed"]) + }) + + for d in data: + doc = frappe.get_doc("Work Order", d.name) + doc.set_actual_dates() + doc.db_set("actual_start_date", doc.actual_start_date, update_modified=False) + + if doc.status == "Completed": + frappe.db.set_value("Work Order", d.name, { + "actual_end_date": doc.actual_end_date, + "lead_time": doc.lead_time + }, update_modified=False) + + if not doc.planned_end_date: + planned_end_date = add_to_date(doc.planned_start_date, minutes=doc.lead_time) + doc.db_set("planned_end_date", doc.actual_start_date, update_modified=False) + + + frappe.db.sql(""" UPDATE `tabJob Card` as jc, `tabWork Order` as wo + SET + jc.production_item = wo.production_item, jc.item_name = wo.item_name + WHERE + jc.work_order = wo.name and IFNULL(jc.production_item, "") = "" + """) + diff --git a/erpnext/setup/setup_wizard/data/dashboard_charts.py b/erpnext/setup/setup_wizard/data/dashboard_charts.py new file mode 100644 index 0000000000..f5b6681767 --- /dev/null +++ b/erpnext/setup/setup_wizard/data/dashboard_charts.py @@ -0,0 +1,247 @@ +from __future__ import unicode_literals +from frappe import _ +import frappe +import json + +def get_company_for_dashboards(): + company = frappe.defaults.get_defaults().company + if company: + return company + else: + company_list = frappe.get_list("Company") + if company_list: + return company_list[0].name + return None + +def get_default_dashboards(): + company = frappe.get_doc("Company", get_company_for_dashboards()) + income_account = company.default_income_account or get_account("Income Account", company.name) + expense_account = company.default_expense_account or get_account("Expense Account", company.name) + bank_account = company.default_bank_account or get_account("Bank", company.name) + + return { + "Dashboards": [ + { + "doctype": "Dashboard", + "dashboard_name": "Accounts", + "charts": [ + { "chart": "Outgoing Bills (Sales Invoice)" }, + { "chart": "Incoming Bills (Purchase Invoice)" }, + { "chart": "Bank Balance" }, + { "chart": "Income" }, + { "chart": "Expenses" }, + { "chart": "Patient Appointments" } + ] + }, + { + "doctype": "Dashboard", + "dashboard_name": "Manufacturing", + "charts": [ + { "chart": "Work Order Analysis", "width": "Half" }, + { "chart": "Quality Inspection Analysis", "width": "Half" }, + { "chart": "Long Time Pending Work Orders", "width": "Half" }, + { "chart": "Downtime Analysis", "width": "Half" }, + { "chart": "Production Analysis", "width": "Full" }, + { "chart": "Job Card Analysis", "width": "Full" } + ] + } + ], + "Charts": [ + { + "doctype": "Dashboard Chart", + "time_interval": "Quarterly", + "chart_name": "Income", + "timespan": "Last Year", + "color": None, + "filters_json": json.dumps({"company": company.name, "account": income_account}), + "source": "Account Balance Timeline", + "chart_type": "Custom", + "timeseries": 1, + "owner": "Administrator", + "type": "Line", + "width": "Half" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Quarterly", + "chart_name": "Expenses", + "timespan": "Last Year", + "color": None, + "filters_json": json.dumps({"company": company.name, "account": expense_account}), + "source": "Account Balance Timeline", + "chart_type": "Custom", + "timeseries": 1, + "owner": "Administrator", + "type": "Line", + "width": "Half" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Quarterly", + "chart_name": "Bank Balance", + "timespan": "Last Year", + "color": "#ffb868", + "filters_json": json.dumps({"company": company.name, "account": bank_account}), + "source": "Account Balance Timeline", + "chart_type": "Custom", + "timeseries": 1, + "owner": "Administrator", + "type": "Line", + "width": "Half" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Monthly", + "chart_name": "Incoming Bills (Purchase Invoice)", + "timespan": "Last Year", + "color": "#a83333", + "value_based_on": "base_grand_total", + "filters_json": json.dumps({}), + "chart_type": "Sum", + "timeseries": 1, + "based_on": "posting_date", + "owner": "Administrator", + "document_type": "Purchase Invoice", + "type": "Bar", + "width": "Half" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Monthly", + "chart_name": "Outgoing Bills (Sales Invoice)", + "timespan": "Last Year", + "color": "#7b933d", + "value_based_on": "base_grand_total", + "filters_json": json.dumps({}), + "chart_type": "Sum", + "timeseries": 1, + "based_on": "posting_date", + "owner": "Administrator", + "document_type": "Sales Invoice", + "type": "Bar", + "width": "Half" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Daily", + "chart_name": "Patient Appointments", + "timespan": "Last Month", + "color": "#77ecca", + "filters_json": json.dumps({}), + "chart_type": "Count", + "timeseries": 1, + "based_on": "appointment_datetime", + "owner": "Administrator", + "document_type": "Patient Appointment", + "type": "Line", + "width": "Half" + } + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Work Order Analysis", + "timespan": "Last Year", + "report_name": "Work Order Summary", + "owner": "Administrator", + "filters_json": json.dumps({"company": company.name}), + "bar": "Donut", + "custom_options": json.dumps({ + "axisOptions": { + "shortenYAxisNumbers": 1 + }, + "height": 300, + "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"] + }), + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Quality Inspection Analysis", + "timespan": "Last Year", + "report_name": "Quality Inspection Summary", + "owner": "Administrator", + "filters_json": json.dumps({}), + "bar": "Donut", + "custom_options": json.dumps({ + "axisOptions": { + "shortenYAxisNumbers": 1 + }, + "height": 300, + "colors": ["#ff5858", "#98d85b"] + }), + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Long Time Pending Work Orders", + "timespan": "Last Year", + "report_name": "Work Order Summary", + "filters_json": json.dumps({"company": company.name, "age":180}), + "owner": "Administrator", + "bar": "Bar", + "custom_options": json.dumps({ + "colors": ["#ff5858"], + "x_field": "name", + "y_fields": ["age"], + "y_axis_fields": [{"__islocal": "true", "idx": 1, "y_field": "age"}], + "chart_type": "Bar" + }), + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Downtime Analysis", + "timespan": "Last Year", + "filters_json": json.dumps({}), + "report_name": "Downtime Analysis", + "owner": "Administrator", + "bar": "Bar", + "custom_options": json.dumps({ + "colors": ["#ff5858"] + }), + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Production Analysis", + "timespan": "Last Year", + "report_name": "Production Analytics", + "owner": "Administrator", + "filters_json": json.dumps({"company": company.name, "range":"Monthly"}), + "bar": "Bar", + "custom_options": json.dumps({ + "colors": ["#7cd6fd", "#ff5858", "#ffa00a", "#98d85b"] + }), + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Job Card Analysis", + "timespan": "Last Year", + "report_name": "Job Card Summary", + "owner": "Administrator", + "filters_json": json.dumps({"company": company.name, "range":"Monthly"}), + "bar": "Bar", + "custom_options": json.dumps({ + "axisOptions": { + "xAxisMode": "tick" + }, + "barOptions": { + "stacked": 1 + }, + "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"] + }), + }, + ] + } + +def get_account(account_type, company): + accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company}) + if accounts: + return accounts[0].name diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json index a9f3cd09ef..c951066aa8 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2013-04-30 13:13:03", "doctype": "DocType", @@ -8,6 +9,7 @@ "field_order": [ "naming_series", "report_date", + "status", "column_break_4", "inspection_type", "reference_type", @@ -20,17 +22,16 @@ "column_break1", "item_name", "description", - "status", + "bom_no", + "specification_details", + "quality_inspection_template", + "readings", "section_break_14", "inspected_by", "verified_by", - "bom_no", "column_break_17", "remarks", - "amended_from", - "specification_details", - "quality_inspection_template", - "readings" + "amended_from" ], "fields": [ { @@ -231,7 +232,8 @@ "icon": "fa fa-search", "idx": 1, "is_submittable": 1, - "modified": "2019-07-12 12:07:23.153698", + "links": [], + "modified": "2020-04-26 17:50:25.068222", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index be2dd526a6..18d68539da 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -732,11 +732,15 @@ class StockEntry(StockController): pro_doc = frappe.get_doc("Work Order", self.work_order) _validate_work_order(pro_doc) pro_doc.run_method("update_status") + if self.fg_completed_qty: pro_doc.run_method("update_work_order_qty") if self.purpose == "Manufacture": pro_doc.run_method("update_planned_qty") + if not pro_doc.operations: + pro_doc.set_actual_dates() + def get_item_details(self, args=None, for_update=False): item = frappe.db.sql("""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group, i.has_batch_no, i.sample_quantity, i.has_serial_no,