From 89c52eacc72b8013d256e8326f3587cdf6f1bac4 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 23 Dec 2014 17:38:33 +0530 Subject: [PATCH] Actual operation cost in stock entry, fixed cost removed from bom and workstation --- erpnext/manufacturing/doctype/bom/bom.js | 23 +-- erpnext/manufacturing/doctype/bom/bom.json | 36 +--- erpnext/manufacturing/doctype/bom/bom.py | 46 +++--- .../manufacturing/doctype/bom/bom_list.html | 2 +- erpnext/manufacturing/doctype/bom/bom_list.js | 2 +- .../doctype/bom_operation/bom_operation.json | 17 +- .../bom_replace_tool/bom_replace_tool.py | 2 +- .../doctype/operation/operation.js | 14 -- .../production_order/production_order.js | 13 +- .../production_order/production_order.json | 30 ++-- .../production_order/production_order.py | 55 ++++--- .../production_order_operation.json | 154 ++++++++---------- .../doctype/workstation/test_records.json | 1 - .../doctype/workstation/workstation.js | 6 +- .../doctype/workstation/workstation.json | 33 +--- .../doctype/workstation/workstation.py | 7 +- erpnext/patches.txt | 1 - .../patches/v4_2/cost_of_production_cycle.py | 9 - erpnext/patches/v5_0/capacity_planning.py | 19 +-- erpnext/projects/doctype/time_log/time_log.js | 2 +- .../projects/doctype/time_log/time_log.json | 11 +- erpnext/projects/doctype/time_log/time_log.py | 41 ++--- .../stock/doctype/stock_entry/stock_entry.js | 2 - .../doctype/stock_entry/stock_entry.json | 10 +- .../stock/doctype/stock_entry/stock_entry.py | 44 ++++- .../doctype/stock_entry/test_stock_entry.py | 2 +- .../stock/report/item_prices/item_prices.py | 2 +- 27 files changed, 242 insertions(+), 342 deletions(-) delete mode 100644 erpnext/manufacturing/doctype/operation/operation.js delete mode 100644 erpnext/patches/v4_2/cost_of_production_cycle.py diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 1fef504592..7a8d75a788 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -38,8 +38,7 @@ erpnext.bom.set_operation = function(doc) { operations[i] = (i+1); } - frappe.meta.get_docfield("BOM Item", "operation", - cur_frm.docname).options = operations.join("\n"); + frappe.meta.get_docfield("BOM Item", "operation", cur_frm.docname).options = operations.join("\n"); refresh_field("bom_materials"); } @@ -54,7 +53,6 @@ cur_frm.add_fetch("item", "stock_uom", "uom"); cur_frm.cscript.hour_rate = function(doc, dt, dn) { erpnext.bom.calculate_op_cost(doc); - erpnext.bom.calculate_fixed_cost(doc); erpnext.bom.calculate_total(doc); } @@ -114,16 +112,14 @@ cur_frm.cscript.rate = function(doc, cdt, cdn) { erpnext.bom.calculate_op_cost = function(doc) { var op = doc.bom_operations || []; - doc.total_variable_cost, doc.total_fixed_cost = 0.0, 0.0; + doc.operating_cost = 0.0; for(var i=0;i 0""", args['item_code'], as_dict=1): + where item_code=%s""", args['item_code'], as_dict=1): total_qty += flt(d.actual_qty) total_value += flt(d.stock_value) - return total_value / total_qty if total_qty else 0.0 + if total_qty: + valuation_rate = total_value / total_qty + + if valuation_rate <= 0: + last_valuation_rate = frappe.db.sql("""select valuation_rate + from `tabStock Ledger Entry` + where item_code = %s and ifnull(valuation_rate, 0) > 0 + order by posting_date desc, posting_time desc, name desc limit 1""", args['item_code']) + + valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0 + + return valuation_rate def manage_default_bom(self): """ Uncheck others if current one is selected as default, @@ -247,28 +257,20 @@ class BOM(Document): """Calculate bom totals""" self.calculate_op_cost() self.calculate_rm_cost() - self.total_cost = self.total_operating_cost + self.raw_material_cost + self.total_cost = self.operating_cost + self.raw_material_cost def calculate_op_cost(self): """Update workstation rate and calculates totals""" - total_variable_cost, total_fixed_cost = 0, 0 + self.operating_cost = 0 for d in self.get('bom_operations'): if d.workstation: - w = frappe.db.get_value("Workstation", d.workstation, ["hour_rate", "fixed_cost"]) if not d.hour_rate: - d.hour_rate = flt(w[0]) - - total_fixed_cost += flt(w[1]) + d.hour_rate = flt(frappe.db.get_value("Workstation", d.workstation, "hour_rate")) if d.hour_rate and d.time_in_mins: - d.variable_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0 + d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0 - d.operating_cost = flt(d.fixed_cost) + flt(d.variable_cost) - total_variable_cost += flt(d.variable_cost) - - self.total_variable_cost = total_variable_cost - self.total_fixed_cost = total_fixed_cost - self.total_operating_cost = self.total_variable_cost + self.total_fixed_cost + self.operating_cost += flt(d.operating_cost) def calculate_rm_cost(self): """Fetch RM rate as per today's valuation rate and calculate totals""" diff --git a/erpnext/manufacturing/doctype/bom/bom_list.html b/erpnext/manufacturing/doctype/bom/bom_list.html index d3632a568f..8303f4a3d4 100644 --- a/erpnext/manufacturing/doctype/bom/bom_list.html +++ b/erpnext/manufacturing/doctype/bom/bom_list.html @@ -15,6 +15,6 @@
- {%= doc.get_formatted("total_variable_cost") %} + {%= doc.get_formatted("total_cost") %}
diff --git a/erpnext/manufacturing/doctype/bom/bom_list.js b/erpnext/manufacturing/doctype/bom/bom_list.js index 085e2dd0ea..71d54a20dc 100644 --- a/erpnext/manufacturing/doctype/bom/bom_list.js +++ b/erpnext/manufacturing/doctype/bom/bom_list.js @@ -1,3 +1,3 @@ frappe.listview_settings['BOM'] = { - add_fields: ["is_active", "is_default", "total_variable_cost"] + add_fields: ["is_active", "is_default", "total_cost"] }; diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 371f2a36be..a3678b54d6 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -40,13 +40,6 @@ "fieldtype": "Column Break", "permlevel": 0 }, - { - "fieldname": "fixed_cost", - "fieldtype": "Currency", - "in_list_view": 0, - "label": "Fixed Cost", - "permlevel": 0 - }, { "fieldname": "hour_rate", "fieldtype": "Float", @@ -69,14 +62,6 @@ "permlevel": 0, "reqd": 0 }, - { - "fieldname": "variable_cost", - "fieldtype": "Currency", - "label": "Variable Cost", - "permlevel": 0, - "precision": "", - "read_only": 1 - }, { "allow_on_submit": 0, "fieldname": "operating_cost", @@ -92,7 +77,7 @@ ], "idx": 1, "istable": 1, - "modified": "2014-12-17 17:54:34.313130", + "modified": "2014-12-23 15:01:54.340605", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py b/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py index a5b2a53792..63030b588b 100644 --- a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py +++ b/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py @@ -25,7 +25,7 @@ class BOMReplaceTool(Document): frappe.throw(_("Current BOM and New BOM can not be same")) def update_new_bom(self): - current_bom_unitcost = frappe.db.sql("""select total_variable_cost/quantity + current_bom_unitcost = frappe.db.sql("""select total_cost/quantity from `tabBOM` where name = %s""", self.current_bom) current_bom_unitcost = current_bom_unitcost and flt(current_bom_unitcost[0][0]) or 0 frappe.db.sql("""update `tabBOM Item` set bom_no=%s, diff --git a/erpnext/manufacturing/doctype/operation/operation.js b/erpnext/manufacturing/doctype/operation/operation.js deleted file mode 100644 index 3c5053b8b7..0000000000 --- a/erpnext/manufacturing/doctype/operation/operation.js +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -frappe.provide("erpnext.operation"); - -$.extend(cur_frm.cscript, { - time_in_min: function(doc) { - doc.operating_cost = flt(doc.hour_rate) * flt(doc.time_in_min) / 60.0; - refresh_field('operating_cost'); - } -}); - -cur_frm.add_fetch('workstation', 'hour_rate', 'hour_rate'); -cur_frm.add_fetch('workstation', 'fixed_cycle_cost', 'fixed_cycle_cost'); diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js index 5a6880945d..a949022db7 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.js +++ b/erpnext/manufacturing/doctype/production_order/production_order.js @@ -77,7 +77,8 @@ $.extend(cur_frm.cscript, { "from_time": child.planned_start_time, "to_time": child.planned_end_time, "project": doc.project, - "workstation": child.workstation + "workstation": child.workstation, + "qty": flt(doc.qty) - flt(child.completed_qty) }, callback: function(r) { var doclist = frappe.model.sync(r.message); @@ -164,11 +165,7 @@ cur_frm.set_query("bom_no", function(doc) { } else msgprint(__("Please enter Production Item first")); }); -cur_frm.add_fetch('bom_no', 'total_fixed_cost', 'total_fixed_cost'); -cur_frm.add_fetch('bom_no', 'total_variable_cost', 'planned_variable_cost'); -cur_frm.add_fetch('bom_no', 'total_operating_cost', 'total_operating_cost'); - -frappe.ui.form.on("Production Order", "total_fixed_cost", function(frm) { - var variable_cost = frm.doc.actual_variable_cost ? flt(frm.doc.actual_variable_cost) : flt(frm.doc.planned_variable_cost) - frm.set_value("total_operating_cost", (flt(frm.doc.total_fixed_cost) + variable_cost)) +frappe.ui.form.on("Production Order", "additional_operating_cost", function(frm) { + var variable_cost = frm.doc.actual_operating_cost ? flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost) + frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)) }) diff --git a/erpnext/manufacturing/doctype/production_order/production_order.json b/erpnext/manufacturing/doctype/production_order/production_order.json index da2bd5bf2a..9617ba0b6a 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.json +++ b/erpnext/manufacturing/doctype/production_order/production_order.json @@ -217,31 +217,34 @@ "precision": "" }, { - "depends_on": "", - "fieldname": "total_fixed_cost", + "fieldname": "planned_operating_cost", "fieldtype": "Currency", - "label": "Total Fixed Cost", - "options": "Company:company:default_currency", - "permlevel": 0 - }, - { - "fieldname": "planned_variable_cost", - "fieldtype": "Currency", - "label": "Planned Variable Cost", + "label": "Planned Operating Cost", + "no_copy": 0, "options": "Company:company:default_currency", "permlevel": 0, "precision": "", "read_only": 1 }, { - "fieldname": "actual_variable_cost", + "fieldname": "actual_operating_cost", "fieldtype": "Currency", - "label": "Actual Variable Cost", + "label": "Actual Operating Cost", + "no_copy": 1, "options": "Company:company:default_currency", "permlevel": 0, "precision": "", "read_only": 1 }, + { + "fieldname": "additional_operating_cost", + "fieldtype": "Currency", + "label": "Additional Operating Cost", + "no_copy": 1, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "" + }, { "fieldname": "column_break_24", "fieldtype": "Column Break", @@ -252,6 +255,7 @@ "fieldname": "total_operating_cost", "fieldtype": "Currency", "label": "Total Operating Cost", + "no_copy": 1, "options": "Company:company:default_currency", "permlevel": 0, "precision": "", @@ -347,7 +351,7 @@ "idx": 1, "in_create": 0, "is_submittable": 1, - "modified": "2014-12-19 14:23:50.701164", + "modified": "2014-12-23 15:07:26.516227", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Order", diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index a075f74ad0..2c2c04def2 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -59,20 +59,15 @@ class ProductionOrder(Document): validate_warehouse_company(w, self.company) def calculate_operating_cost(self): - if self.total_fixed_cost==None: - self.total_fixed_cost = frappe.db.get_value("BOM", self.bom_no, "total_fixed_cost") - - self.planned_variable_cost, self.actual_variable_cost = 0.0, 0.0 + self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0 for d in self.get("production_order_operations"): + d.actual_operating_cost = flt(d.hour_rate) * flt(d.actual_operation_time) / 60 - d.actual_variable_cost = flt(d.hour_rate) * flt(d.actual_operation_time) / 60 - - self.planned_variable_cost += flt(d.variable_cost) - self.actual_variable_cost += flt(d.actual_variable_cost) - - variable_cost = self.actual_variable_cost if self.actual_variable_cost else self.planned_variable_cost - self.total_operating_cost = flt(self.total_fixed_cost) + flt(variable_cost) + self.planned_operating_cost += flt(d.planned_operating_cost) + self.actual_operating_cost += flt(d.actual_operating_cost) + variable_cost = self.actual_operating_cost if self.actual_operating_cost else self.planned_operating_cost + self.total_operating_cost = flt(self.additional_operating_cost) + flt(variable_cost) def validate_production_order_against_so(self): # already ordered qty @@ -171,15 +166,16 @@ class ProductionOrder(Document): self.set('production_order_operations', []) operations = frappe.db.sql("""select operation, opn_description, workstation, - hour_rate, time_in_mins, fixed_cost, variable_cost, "Pending" as status + hour_rate, time_in_mins, operating_cost as "planned_operating_cost", "Pending" as status from `tabBOM Operation` where parent = %s""", self.bom_no, as_dict=1) self.set('production_order_operations', operations) self.plan_operations() + self.calculate_operating_cost() def plan_operations(self): - scheduled_datetime = self.production_start_date + scheduled_datetime = self.planned_start_date for d in self.get('production_order_operations'): while getdate(scheduled_datetime) in self.get_holidays(d.workstation): scheduled_datetime = get_datetime(scheduled_datetime) + relativedelta(days=1) @@ -188,7 +184,7 @@ class ProductionOrder(Document): scheduled_datetime = get_datetime(scheduled_datetime) + relativedelta(minutes=d.time_in_mins) d.planned_end_time = scheduled_datetime - self.production_end_date = scheduled_datetime + self.planned_end_date = scheduled_datetime def get_holidays(self, workstation): @@ -202,6 +198,17 @@ class ProductionOrder(Document): return self.holidays[holiday_list] + def update_operation_status(self): + for d in self.get("production_order_operations"): + if not d.completed_qty: + d.status = "Pending" + elif flt(d.completed_qty) < flt(self.qty): + d.status = "Work in Progress" + elif flt(d.completed_qty) == flt(self.qty): + d.status = "Completed" + else: + frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'")) + @frappe.whitelist() def get_item_details(item): @@ -213,10 +220,7 @@ def get_item_details(item): return {} res = res[0] - bom = frappe.db.sql("""select name as bom_no,total_fixed_cost from `tabBOM` where item=%s - and ifnull(is_default, 0)=1""", item, as_dict=1) - if bom: - res.update(bom[0]) + res["bom_no"] = frappe.db.get_value("BOM", filters={"item": item, "is_default": 1}) return res @frappe.whitelist() @@ -228,6 +232,7 @@ def make_stock_entry(production_order_id, purpose, qty=None): stock_entry.production_order = production_order_id stock_entry.company = production_order.company stock_entry.bom_no = production_order.bom_no + stock_entry.additional_operating_cost = production_order.additional_operating_cost stock_entry.use_multi_level_bom = production_order.use_multi_level_bom stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty)) @@ -267,7 +272,7 @@ def get_events(start, end, filters=None): return data @frappe.whitelist() -def make_time_log(name, operation, from_time, to_time, qty=None, project=None, workstation=None): +def make_time_log(name, operation, from_time, to_time, qty=None, project=None, workstation=None): time_log = frappe.new_doc("Time Log") time_log.time_log_for = 'Manufacturing' time_log.from_time = from_time @@ -276,16 +281,24 @@ def make_time_log(name, operation, from_time, to_time, qty=None, project=None, w time_log.project = project time_log.operation= operation time_log.workstation= workstation + time_log.completed_qty = flt(qty) if from_time and to_time : time_log.calculate_total_hours() return time_log @frappe.whitelist() def auto_make_time_log(production_order_id): + if frappe.db.get_value("Time Log", filters={"production_order": production_order_id}): + frappe.throw(_("Time logs already exists against this Production Order")) + + time_logs = [] prod_order = frappe.get_doc("Production Order", production_order_id) + for d in prod_order.production_order_operations: operation = cstr(d.idx) + ". " + d.operation time_log = make_time_log(prod_order.name, operation, d.planned_start_time, d.planned_end_time, - prod_order.qty, prod_order.project_name, d.workstation) + flt(prod_order.qty) - flt(d.completed_qty), prod_order.project_name, d.workstation) time_log.save() - frappe.msgprint(_("Time Logs created.")) + time_logs.append(time_log.name) + if time_logs: + frappe.msgprint(_("Time Logs created:") + "\n" + "\n".join(time_logs)) diff --git a/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json b/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json index f17a055970..f495b0bc19 100644 --- a/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json +++ b/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json @@ -79,12 +79,23 @@ "set_only_once": 0, "unique": 0 }, + { + "description": "Operation completed for how many finished goods?", + "fieldname": "completed_qty", + "fieldtype": "Float", + "label": "Completed Qty", + "no_copy": 1, + "permlevel": 0, + "precision": "", + "read_only": 1 + }, { "default": "Pending", "fieldname": "status", "fieldtype": "Select", "in_list_view": 1, "label": "Status", + "no_copy": 1, "options": "Pending\nWork in Progress\nCompleted", "permlevel": 0, "precision": "", @@ -121,57 +132,24 @@ "precision": "" }, { - "allow_on_submit": 0, - "fieldname": "hour_rate", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, + "fieldname": "planned_start_time", + "fieldtype": "Datetime", "in_list_view": 0, - "label": "Hour Rate", - "no_copy": 0, - "oldfieldname": "hour_rate", - "oldfieldtype": "Currency", + "label": "Planned Start Time", + "no_copy": 1, "permlevel": 0, "precision": "", - "print_hide": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "reqd": 1 }, { - "allow_on_submit": 0, - "fieldname": "fixed_cost", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, + "fieldname": "planned_end_time", + "fieldtype": "Datetime", "in_list_view": 0, - "label": "Fixed Cost", - "no_copy": 0, - "options": "Company:company:default_currency", + "label": "Planned End Time", + "no_copy": 1, "permlevel": 0, "precision": "", - "print_hide": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "fieldname": "variable_cost", - "fieldtype": "Currency", - "label": "Variable Cost", - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "read_only": 1 + "reqd": 1 }, { "fieldname": "column_break_10", @@ -203,56 +181,41 @@ "unique": 0 }, { - "fieldname": "planned_start_time", - "fieldtype": "Datetime", - "in_list_view": 0, - "label": "Planned Start Time", - "no_copy": 1, - "permlevel": 0, - "precision": "", - "reqd": 1 - }, - { - "fieldname": "planned_end_time", - "fieldtype": "Datetime", - "in_list_view": 0, - "label": "Planned End Time", - "no_copy": 1, - "permlevel": 0, - "precision": "", - "reqd": 1 - }, - { - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "label": "Actual Time and Cost", - "permlevel": 0, - "precision": "" - }, - { - "description": "in Minutes\nUpdated via 'Time Log'", - "fieldname": "actual_operation_time", + "allow_on_submit": 0, + "fieldname": "hour_rate", "fieldtype": "Float", - "label": "Actual Operation Time", - "no_copy": 1, + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Hour Rate", + "no_copy": 0, + "oldfieldname": "hour_rate", + "oldfieldtype": "Currency", "permlevel": 0, "precision": "", - "read_only": 1 + "print_hide": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { - "description": "Hour Rate * Actual Operating Cost", - "fieldname": "actual_variable_cost", + "fieldname": "planned_operating_cost", "fieldtype": "Currency", - "label": "Actual Variable Cost", - "no_copy": 1, + "label": "Planned Operating Cost", + "no_copy": 0, "options": "Company:company:default_currency", "permlevel": 0, "precision": "", "read_only": 1 }, { - "fieldname": "column_break_11", - "fieldtype": "Column Break", + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Actual Time and Cost", "permlevel": 0, "precision": "" }, @@ -275,6 +238,33 @@ "precision": "", "read_only": 1 }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "description": "in Minutes\nUpdated via 'Time Log'", + "fieldname": "actual_operation_time", + "fieldtype": "Float", + "label": "Actual Operation Time", + "no_copy": 1, + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "description": "Hour Rate * Actual Operating Cost", + "fieldname": "actual_operating_cost", + "fieldtype": "Currency", + "label": "Actual Operating Cost", + "no_copy": 1, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, { "allow_on_submit": 1, "depends_on": "eval:(doc.docstatus==1 && doc.status!=\"Completed\")", @@ -292,7 +282,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2014-12-19 12:49:49.918120", + "modified": "2014-12-23 15:42:34.892964", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Order Operation", diff --git a/erpnext/manufacturing/doctype/workstation/test_records.json b/erpnext/manufacturing/doctype/workstation/test_records.json index 685c84e2d7..37fb5aa4e4 100644 --- a/erpnext/manufacturing/doctype/workstation/test_records.json +++ b/erpnext/manufacturing/doctype/workstation/test_records.json @@ -4,7 +4,6 @@ "name": "_Test Workstation 1", "workstation_name": "_Test Workstation 1", "warehouse": "_Test warehouse - _TC", - "fixed_cycle_cost": 1000, "hour_rate":100, "holiday_list": "_Test Holiday List", "workstation_operation_hours": [ diff --git a/erpnext/manufacturing/doctype/workstation/workstation.js b/erpnext/manufacturing/doctype/workstation/workstation.js index d3c7b56e8a..17320520af 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.js +++ b/erpnext/manufacturing/doctype/workstation/workstation.js @@ -1,7 +1,7 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - + //--------- ONLOAD ------------- cur_frm.cscript.onload = function(doc, cdt, cdn) { @@ -15,7 +15,3 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) { } }) } - -cur_frm.cscript.refresh = function(doc, cdt, cdn) { - -} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/workstation/workstation.json b/erpnext/manufacturing/doctype/workstation/workstation.json index 37932bc78c..ecf0f667a5 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.json +++ b/erpnext/manufacturing/doctype/workstation/workstation.json @@ -60,19 +60,6 @@ "permlevel": 0, "precision": "" }, - { - "fieldname": "fixed_costs", - "fieldtype": "Section Break", - "label": "Fixed Costs", - "permlevel": 0, - "precision": "" - }, - { - "fieldname": "fixed_cost", - "fieldtype": "Float", - "label": "Fixed Cost", - "permlevel": 0 - }, { "fieldname": "over_heads", "fieldtype": "Section Break", @@ -98,15 +85,6 @@ "oldfieldtype": "Currency", "permlevel": 0 }, - { - "description": "per hour", - "fieldname": "hour_rate_rent", - "fieldtype": "Float", - "label": "Rent Cost", - "oldfieldname": "hour_rate_rent", - "oldfieldtype": "Currency", - "permlevel": 0 - }, { "fieldname": "column_break_11", "fieldtype": "Column Break", @@ -115,13 +93,12 @@ }, { "description": "per hour", - "fieldname": "total_variable_cost", + "fieldname": "hour_rate_rent", "fieldtype": "Float", - "label": "Total Variable Cost", - "oldfieldname": "overhead", + "label": "Rent Cost", + "oldfieldname": "hour_rate_rent", "oldfieldtype": "Currency", - "permlevel": 0, - "read_only": 1 + "permlevel": 0 }, { "description": "Wages per hour", @@ -161,7 +138,7 @@ ], "icon": "icon-wrench", "idx": 1, - "modified": "2014-12-22 14:18:40.253034", + "modified": "2014-12-23 15:27:58.477925", "modified_by": "Administrator", "module": "Manufacturing", "name": "Workstation", diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py index b95ca45dd7..62819774c7 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.py +++ b/erpnext/manufacturing/doctype/workstation/workstation.py @@ -25,9 +25,8 @@ class Workstation(Document): def on_update(self): self.validate_overlap_for_operation_timings() - frappe.db.set(self, 'total_variable_cost', flt(self.hour_rate_electricity) + + frappe.db.set(self, 'hour_rate', flt(self.hour_rate_labour) + flt(self.hour_rate_electricity) + flt(self.hour_rate_consumable) + flt(self.hour_rate_rent)) - frappe.db.set(self, 'hour_rate', flt(self.hour_rate_labour) + flt(self.total_variable_cost)) self.update_bom_operation() @@ -41,8 +40,8 @@ class Workstation(Document): (%s between start_time and end_time)) """, (self.name, d.name, d.start_time, d.end_time, d.start_time, d.end_time, d.start_time)) - if existing: - frappe.throw(_("Row #{0}: Timings conflicts with row {1}").format(d.idx, comma_and(existing)), OverlapError) + if existing: + frappe.throw(_("Row #{0}: Timings conflicts with row {1}").format(d.idx, comma_and(existing)), OverlapError) @frappe.whitelist() def get_default_holiday_list(): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 07cfdbf040..4997ede2cb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -80,7 +80,6 @@ execute:frappe.delete_doc("DocType", "Landed Cost Wizard") erpnext.patches.v4_2.default_website_style erpnext.patches.v4_2.set_company_country erpnext.patches.v4_2.update_sales_order_invoice_field_name -erpnext.patches.v4_2.cost_of_production_cycle erpnext.patches.v4_2.seprate_manufacture_and_repack execute:frappe.delete_doc("Report", "Warehouse-Wise Stock Balance") execute:frappe.delete_doc("DocType", "Purchase Request") diff --git a/erpnext/patches/v4_2/cost_of_production_cycle.py b/erpnext/patches/v4_2/cost_of_production_cycle.py deleted file mode 100644 index 26f0fcad5b..0000000000 --- a/erpnext/patches/v4_2/cost_of_production_cycle.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes 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", "bom") - frappe.db.sql("""update tabBOM set total_variable_cost = total_cost""") \ No newline at end of file diff --git a/erpnext/patches/v5_0/capacity_planning.py b/erpnext/patches/v5_0/capacity_planning.py index ce964353b0..291fc5c782 100644 --- a/erpnext/patches/v5_0/capacity_planning.py +++ b/erpnext/patches/v5_0/capacity_planning.py @@ -4,20 +4,5 @@ import frappe def execute(): - for dt in ["workstation", "bom", "bom_operation"]: - frappe.reload_doc("manufacturing", "doctype", dt) - - frappe.db.sql("update `tabWorkstation` set fixed_cost = fixed_cycle_cost, total_variable_cost = overhead") - - frappe.db.sql("update `tabBOM Operation` set fixed_cost = fixed_cycle_cost") - - for d in frappe.db.sql("select name from `tabBOM` where docstatus < 2"): - try: - bom = frappe.get_doc('BOM', d[0]) - if bom.docstatus == 1: - bom.ignore_validate_update_after_submit = True - bom.calculate_cost() - bom.save() - except: - print "error", frappe.get_traceback() - pass + frappe.reload_doc("stock", "doctype", "stock_entry") + frappe.db.sql("update tabBOM set additional_operating_cost = total_fixed_cost") diff --git a/erpnext/projects/doctype/time_log/time_log.js b/erpnext/projects/doctype/time_log/time_log.js index e255843872..b23b3eadf4 100644 --- a/erpnext/projects/doctype/time_log/time_log.js +++ b/erpnext/projects/doctype/time_log/time_log.js @@ -14,7 +14,7 @@ frappe.ui.form.on("Time Log", "refresh", function(frm) { var is_manufacturing = frm.doc.time_log_for=="Manufacturing" ? true : false; frm.toggle_reqd("production_order", is_manufacturing); frm.toggle_reqd("operation", is_manufacturing); - frm.toggle_reqd("operation_status", is_manufacturing); + frm.toggle_reqd("completed_qty", is_manufacturing); }); // set to time if hours is updated diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json index 3b27ab8952..343a697b10 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -116,11 +116,10 @@ "read_only": 1 }, { - "default": "Work in Progress", - "fieldname": "operation_status", - "fieldtype": "Select", - "label": "Operation Status", - "options": "\nWork in Progress\nCompleted", + "description": "Operation completed for how many finished goods?", + "fieldname": "completed_qty", + "fieldtype": "Float", + "label": "Completed Qty", "permlevel": 0, "precision": "" }, @@ -200,7 +199,7 @@ "icon": "icon-time", "idx": 1, "is_submittable": 1, - "modified": "2014-12-19 14:20:41.381152", + "modified": "2014-12-22 15:22:00.664972", "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 541154a4a6..52257437b3 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -23,7 +23,6 @@ class TimeLog(Document): self.validate_time_log_for() self.check_workstation_timings() self.validate_production_order() - self.validate_operation_status() def on_submit(self): self.update_production_order() @@ -77,7 +76,7 @@ class TimeLog(Document): def validate_time_log_for(self): if self.time_log_for == "Project": - for fld in ["production_order", "operation", "workstation", "operation_status"]: + for fld in ["production_order", "operation", "workstation", "completed_qty"]: self.set(fld, None) def check_workstation_timings(self): @@ -92,33 +91,25 @@ class TimeLog(Document): if frappe.db.get_value("Production Order", self.production_order, "docstatus") != 1 : frappe.throw(_("You can make a time log only against a submitted production order"), NotSubmittedError) - def validate_operation_status(self): - if self.time_log_for=="Manufacturing" and self.production_order and self.operation: - if self.operation_status == "Work in Progress": - latest_time_log = self.get_latest_time_log() - if latest_time_log and latest_time_log[0].operation_status == "Completed": - frappe.throw("Operation is already completed via Time Log {}".format(latest_time_log[0].name)) - def update_production_order(self): """Updates `start_date`, `end_date`, `status` for operation in Production Order.""" if self.time_log_for=="Manufacturing" and self.operation: + operation = self.operation.split('. ',1) + dates = self.get_operation_start_end_time() + tl = self.get_all_time_logs() - latest_time_log = self.get_latest_time_log() - op_status = latest_time_log[0].operation_status if latest_time_log else "Pending" - - actual_op_time = self.get_actual_op_time() - d = self.operation.split('. ',1) frappe.db.sql("""update `tabProduction Order Operation` - set actual_start_time = %s, actual_end_time = %s, status = %s, actual_operation_time = %s + set actual_start_time = %s, actual_end_time = %s, completed_qty = %s, actual_operation_time = %s where parent=%s and idx=%s and operation = %s""", - (dates.start_date, dates.end_date, op_status, - actual_op_time, self.production_order, d[0], d[1])) + (dates.start_date, dates.end_date, tl.completed_qty, + tl.hours, self.production_order, operation[0], operation[1])) pro_order = frappe.get_doc("Production Order", self.production_order) pro_order.ignore_validate_update_after_submit = True + pro_order.update_operation_status() pro_order.calculate_operating_cost() pro_order.save() @@ -128,17 +119,13 @@ class TimeLog(Document): where production_order = %s and operation = %s and docstatus=1""", (self.production_order, self.operation), as_dict=1)[0] - def get_actual_op_time(self): + def get_all_time_logs(self): """Returns 'Actual Operating Time'. """ - actual_time = frappe.db.sql("""select sum(hours*60) as time_diff from - `tabTime Log` where production_order = %s and operation = %s and docstatus=1""", - (self.production_order, self.operation)) - return actual_time[0][0] if actual_time else 0 - - def get_latest_time_log(self): - return frappe.db.sql("""select name, operation_status from `tabTime Log` - where production_order=%s and operation=%s and docstatus=1 - order by to_time desc limit 1""", (self.production_order, self.operation), as_dict=1) + return frappe.db.sql("""select + sum(hours*60) as hours, sum(ifnull(completed_qty, 0)) as completed_qty + from `tabTime Log` + where production_order = %s and operation = %s and docstatus=1""", + (self.production_order, self.operation), as_dict=1)[0] @frappe.whitelist() def get_workstation(production_order, operation): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index d961b40f62..b5bd455b09 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -461,8 +461,6 @@ cur_frm.fields_dict.customer.get_query = function(doc, cdt, cdn) { cur_frm.fields_dict.supplier.get_query = function(doc, cdt, cdn) { return { query: "erpnext.controllers.queries.supplier_query" } } -cur_frm.add_fetch('production_order', 'total_fixed_cost', 'total_fixed_cost'); -cur_frm.add_fetch('bom_no', 'total_fixed_cost', 'total_fixed_cost'); cur_frm.cscript.company = function(doc, cdt, cdn) { erpnext.get_fiscal_year(doc.company, doc.posting_date); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 8cbfbef1ea..8630d0376a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -299,9 +299,11 @@ }, { "depends_on": "eval:inList([\"Manufacture\", \"Repack\"], doc.purpose)", - "fieldname": "total_fixed_cost", - "fieldtype": "Float", - "label": "Total Fixed Cost", + "fieldname": "additional_operating_cost", + "fieldtype": "Currency", + "label": "Additional Operating Cost", + "no_copy": 1, + "options": "Company:company:default_currency", "permlevel": 0, "read_only": 0 }, @@ -585,7 +587,7 @@ "is_submittable": 1, "issingle": 0, "max_attachments": 0, - "modified": "2014-10-03 14:55:44.916658", + "modified": "2014-12-23 15:03:42.963697", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index a9a670dedd..cb21867661 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -171,10 +171,20 @@ class StockEntry(StockController): if not self.production_order: frappe.throw(_("Production order number is mandatory for stock entry purpose manufacture")) # check for double entry + self.check_if_operations_completed() self.check_duplicate_entry_for_production_order() elif self.purpose != "Material Transfer": self.production_order = None + def check_if_operations_completed(self): + prod_order = frappe.get_doc("Production Order", self.production_order) + if prod_order.actual_operating_cost: + for d in prod_order.get("production_order_operations"): + total_completed_qty = flt(self.fg_completed_qty) + flt(prod_order.produced_qty) + if total_completed_qty > flt(d.completed_qty): + frappe.throw(_("Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Production Order # {3}. Please update operation status via Time Logs") + .format(d.idx, d.operation, total_completed_qty, self.production_order)) + def check_duplicate_entry_for_production_order(self): other_ste = [t[0] for t in frappe.db.get_values("Stock Entry", { "production_order": self.production_order, @@ -258,14 +268,28 @@ class StockEntry(StockController): for d in self.get("mtn_details"): if d.bom_no or (d.t_warehouse and number_of_fg_items == 1): if not flt(d.incoming_rate) or force: - operation_cost_per_unit = 0 - if d.bom_no: - bom = frappe.db.get_value("BOM", d.bom_no, ["operating_cost", "quantity"], as_dict=1) - operation_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity) - d.incoming_rate = operation_cost_per_unit + (raw_material_cost + flt(self.total_fixed_cost)) / flt(d.transfer_qty) + operation_cost_per_unit = self.get_operation_cost_per_unit(d.bom_no, d.qty) + d.incoming_rate = operation_cost_per_unit + (raw_material_cost / flt(d.transfer_qty)) d.amount = flt(d.transfer_qty) * flt(d.incoming_rate) break + def get_operation_cost_per_unit(self, bom_no, qty): + operation_cost_per_unit = 0 + + if self.production_order: + pro_order = frappe.get_doc("Production Order", self.production_order) + for d in pro_order.get("production_order_operations"): + if flt(d.completed_qty): + operation_cost_per_unit += flt(d.actual_operating_cost) / flt(d.completed_qty) + else: + operation_cost_per_unit += flt(d.planned_operating_cost) / flt(self.qty) + + if not operation_cost_per_unit and bom_no: + bom = frappe.db.get_value("BOM", bom_no, ["operating_cost", "quantity"], as_dict=1) + operation_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity) + + return operation_cost_per_unit + flt(self.additional_operating_cost) / flt(qty) + def get_incoming_rate(self, args): incoming_rate = 0 if self.purpose == "Sales Return": @@ -643,10 +667,12 @@ def get_party_details(ref_dt, ref_dn): @frappe.whitelist() def get_production_order_details(production_order): - result = frappe.db.sql("""select bom_no, - ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty, use_multi_level_bom, - wip_warehouse from `tabProduction Order` where name = %s""", production_order, as_dict=1) - return result and result[0] or {} + res = frappe.db.sql("""select bom_no, use_multi_level_bom, wip_warehouse, + ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty, + (infull(additional_operating_cost, 0) / qty)*(ifnull(qty, 0) - ifnull(produced_qty, 0)) as additional_operating_cost + from `tabProduction Order` where name = %s""", production_order, as_dict=1) + + return res and res[0] or {} def query_sales_return_doc(doctype, txt, searchfield, start, page_len, filters): conditions = "" diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 7caf0627bf..cc539f6ef3 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -918,7 +918,7 @@ class TestStockEntry(unittest.TestCase): "production_order": production_order.name, "bom_no": bom_no, "fg_completed_qty": "1", - "total_fixed_cost": 1000 + "additional_operating_cost": 1000 }) stock_entry.get_items() diff --git a/erpnext/stock/report/item_prices/item_prices.py b/erpnext/stock/report/item_prices/item_prices.py index 2b413fd29b..bd885b94f4 100644 --- a/erpnext/stock/report/item_prices/item_prices.py +++ b/erpnext/stock/report/item_prices/item_prices.py @@ -115,7 +115,7 @@ def get_item_bom_rate(): item_bom_map = {} - for b in frappe.db.sql("""select item, (total_variable_cost/quantity) as bom_rate + for b in frappe.db.sql("""select item, (total_cost/quantity) as bom_rate from `tabBOM` where is_active=1 and is_default=1""", as_dict=1): item_bom_map.setdefault(b.item, flt(b.bom_rate))