From 4c17f9420e61901c26243bbf5bea47c580834b3c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 12 Aug 2013 14:18:09 +0530 Subject: [PATCH] [manufacturing] Added New Reports --- .../production_order/production_order.js | 33 ++++---- .../production_order/production_order.py | 21 ++++- .../manufacturing_home/manufacturing_home.js | 15 ++++ .../completed_production_orders/__init__.py | 0 .../completed_production_orders.txt | 22 +++++ .../report/open_production_orders/__init__.py | 0 .../open_production_orders.txt | 22 +++++ .../production_orders_in_progress/__init__.py | 0 .../production_orders_in_progress.txt | 22 +++++ stock/doctype/stock_entry/stock_entry.js | 81 +++++++++---------- stock/doctype/stock_entry/stock_entry.py | 3 +- stock/stock_ledger.py | 5 +- stock/utils.py | 2 +- utilities/make_demo.py | 63 +++++++++++++-- 14 files changed, 219 insertions(+), 70 deletions(-) create mode 100644 manufacturing/report/completed_production_orders/__init__.py create mode 100644 manufacturing/report/completed_production_orders/completed_production_orders.txt create mode 100644 manufacturing/report/open_production_orders/__init__.py create mode 100644 manufacturing/report/open_production_orders/open_production_orders.txt create mode 100644 manufacturing/report/production_orders_in_progress/__init__.py create mode 100644 manufacturing/report/production_orders_in_progress/production_orders_in_progress.txt diff --git a/manufacturing/doctype/production_order/production_order.js b/manufacturing/doctype/production_order/production_order.js index 012c27980d..f680776a56 100644 --- a/manufacturing/doctype/production_order/production_order.js +++ b/manufacturing/doctype/production_order/production_order.js @@ -61,30 +61,25 @@ cur_frm.cscript['Unstop Production Order'] = function() { } cur_frm.cscript['Transfer Raw Materials'] = function() { - var doc = cur_frm.doc; - cur_frm.cscript.make_se(doc, 'Material Transfer'); + cur_frm.cscript.make_se('Material Transfer'); } cur_frm.cscript['Update Finished Goods'] = function() { - var doc = cur_frm.doc; - cur_frm.cscript.make_se(doc, 'Manufacture/Repack'); + cur_frm.cscript.make_se('Manufacture/Repack'); } -cur_frm.cscript.make_se = function(doc, purpose) { - var se = wn.model.get_new_doc("Stock Entry"); - se.purpose = purpose; - se.production_order = doc.name; - if(purpose==="Material Transfer") { - se.to_warehouse = doc.wip_warehouse; - } else { - se.from_warehouse = doc.wip_warehouse; - se.to_warehouse = doc.fg_warehouse; - } - se.company = doc.company; - se.fg_completed_qty = doc.qty - doc.produced_qty; - se.bom_no = doc.bom_no; - se.use_multi_level_bom = doc.use_multi_level_bom; - loaddoc('Stock Entry', se.name); +cur_frm.cscript.make_se = function(purpose) { + wn.call({ + method:"manufacturing.doctype.production_order.production_order.make_stock_entry", + args: { + "production_order_id": cur_frm.doc.name, + "purpose": purpose + }, + callback: function(r) { + var doclist = wn.model.sync(r.message); + wn.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }) } cur_frm.fields_dict['production_item'].get_query = function(doc) { diff --git a/manufacturing/doctype/production_order/production_order.py b/manufacturing/doctype/production_order/production_order.py index 2f311808a3..90a74e9110 100644 --- a/manufacturing/doctype/production_order/production_order.py +++ b/manufacturing/doctype/production_order/production_order.py @@ -137,4 +137,23 @@ def get_item_details(item): if bom: res.bom_no = bom[0][0] - return res \ No newline at end of file + return res + +@webnotes.whitelist() +def make_stock_entry(production_order_id, purpose): + production_order = webnotes.bean("Production Order", production_order_id) + + stock_entry = webnotes.new_bean("Stock Entry") + stock_entry.doc.purpose = purpose + stock_entry.doc.production_order = production_order_id + stock_entry.doc.company = production_order.doc.company + stock_entry.doc.bom_no = production_order.doc.bom_no + stock_entry.doc.fg_completed_qty = flt(production_order.doc.qty) - flt(production_order.doc.produced_qty) + + if purpose=="Material Transfer": + stock_entry.doc.to_warehouse = production_order.doc.wip_warehouse + else: + stock_entry.doc.from_warehouse = production_order.doc.wip_warehouse + stock_entry.doc.to_warehouse = production_order.doc.fg_warehouse + + return [d.fields for d in stock_entry.doclist] diff --git a/manufacturing/page/manufacturing_home/manufacturing_home.js b/manufacturing/page/manufacturing_home/manufacturing_home.js index b29bbbbeed..a2a4eaa261 100644 --- a/manufacturing/page/manufacturing_home/manufacturing_home.js +++ b/manufacturing/page/manufacturing_home/manufacturing_home.js @@ -64,11 +64,26 @@ wn.module_page["Manufacturing"] = [ right: true, icon: "icon-list", items: [ + { + "label":wn._("Open Production Orders"), + route: "query-report/Open Production Orders", + doctype:"Production Order" + }, + { + "label":wn._("Production Orders in Progress"), + route: "query-report/Production Orders in Progress", + doctype:"Production Order" + }, { "label":wn._("Issued Items Against Production Order"), route: "query-report/Issued Items Against Production Order", doctype:"Production Order" }, + { + "label":wn._("Completed Production Orders"), + route: "query-report/Completed Production Orders", + doctype:"Production Order" + }, ] } ] diff --git a/manufacturing/report/completed_production_orders/__init__.py b/manufacturing/report/completed_production_orders/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manufacturing/report/completed_production_orders/completed_production_orders.txt b/manufacturing/report/completed_production_orders/completed_production_orders.txt new file mode 100644 index 0000000000..facda7e903 --- /dev/null +++ b/manufacturing/report/completed_production_orders/completed_production_orders.txt @@ -0,0 +1,22 @@ +[ + { + "creation": "2013-08-12 12:44:27", + "docstatus": 0, + "modified": "2013-08-12 12:44:27", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Report", + "is_standard": "Yes", + "name": "__common__", + "query": "SELECT\n `tabProduction Order`.name as \"Production Order:Link/Production Order:200\",\n `tabProduction Order`.creation as \"Date:Date:120\",\n `tabProduction Order`.production_item as \"Item:Link/Item:150\",\n `tabProduction Order`.qty as \"To Produce:Int:100\",\n `tabProduction Order`.produced_qty as \"Produced:Int:100\"\nFROM\n `tabProduction Order`\nWHERE\n `tabProduction Order`.docstatus=1\n AND ifnull(`tabProduction Order`.produced_qty,0) = `tabProduction Order`.qty", + "ref_doctype": "Production Order", + "report_name": "Completed Production Orders", + "report_type": "Query Report" + }, + { + "doctype": "Report", + "name": "Completed Production Orders" + } +] \ No newline at end of file diff --git a/manufacturing/report/open_production_orders/__init__.py b/manufacturing/report/open_production_orders/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manufacturing/report/open_production_orders/open_production_orders.txt b/manufacturing/report/open_production_orders/open_production_orders.txt new file mode 100644 index 0000000000..f92bdd32d3 --- /dev/null +++ b/manufacturing/report/open_production_orders/open_production_orders.txt @@ -0,0 +1,22 @@ +[ + { + "creation": "2013-08-12 12:32:30", + "docstatus": 0, + "modified": "2013-08-12 12:42:29", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Report", + "is_standard": "Yes", + "name": "__common__", + "query": "SELECT\n `tabProduction Order`.name as \"Production Order:Link/Production Order:200\",\n `tabProduction Order`.creation as \"Date:Date:120\",\n `tabProduction Order`.production_item as \"Item:Link/Item:150\",\n `tabProduction Order`.qty as \"To Produce:Int:100\",\n `tabProduction Order`.produced_qty as \"Produced:Int:100\"\nFROM\n `tabProduction Order`\nWHERE\n `tabProduction Order`.docstatus=1\n AND ifnull(`tabProduction Order`.produced_qty,0) < `tabProduction Order`.qty\n AND NOT EXISTS (SELECT name from `tabStock Entry` where production_order =`tabProduction Order`.name) ", + "ref_doctype": "Production Order", + "report_name": "Open Production Orders", + "report_type": "Query Report" + }, + { + "doctype": "Report", + "name": "Open Production Orders" + } +] \ No newline at end of file diff --git a/manufacturing/report/production_orders_in_progress/__init__.py b/manufacturing/report/production_orders_in_progress/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manufacturing/report/production_orders_in_progress/production_orders_in_progress.txt b/manufacturing/report/production_orders_in_progress/production_orders_in_progress.txt new file mode 100644 index 0000000000..3d3493f8a1 --- /dev/null +++ b/manufacturing/report/production_orders_in_progress/production_orders_in_progress.txt @@ -0,0 +1,22 @@ +[ + { + "creation": "2013-08-12 12:43:47", + "docstatus": 0, + "modified": "2013-08-12 12:43:47", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Report", + "is_standard": "Yes", + "name": "__common__", + "query": "SELECT\n `tabProduction Order`.name as \"Production Order:Link/Production Order:200\",\n `tabProduction Order`.creation as \"Date:Date:120\",\n `tabProduction Order`.production_item as \"Item:Link/Item:150\",\n `tabProduction Order`.qty as \"To Produce:Int:100\",\n `tabProduction Order`.produced_qty as \"Produced:Int:100\"\nFROM\n `tabProduction Order`\nWHERE\n `tabProduction Order`.docstatus=1\n AND ifnull(`tabProduction Order`.produced_qty,0) < `tabProduction Order`.qty\n AND EXISTS (SELECT name from `tabStock Entry` where production_order =`tabProduction Order`.name) ", + "ref_doctype": "Production Order", + "report_name": "Production Orders in Progress", + "report_type": "Query Report" + }, + { + "doctype": "Report", + "name": "Production Orders in Progress" + } +] \ No newline at end of file diff --git a/stock/doctype/stock_entry/stock_entry.js b/stock/doctype/stock_entry/stock_entry.js index 4fa6c6c55a..53998f83e8 100644 --- a/stock/doctype/stock_entry/stock_entry.js +++ b/stock/doctype/stock_entry/stock_entry.js @@ -4,34 +4,7 @@ wn.require("public/app/js/controllers/stock_controller.js"); wn.provide("erpnext.stock"); -erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ - onload: function() { - this.set_default_account(); - }, - - set_default_account: function() { - var me = this; - - if (cint(wn.defaults.get_default("auto_inventory_accounting")) && !this.frm.doc.expense_adjustment_account) { - if (this.frm.doc.purpose == "Sales Return") - account_for = "stock_in_hand_account"; - else if (this.frm.doc.purpose == "Purchase Return") - account_for = "stock_received_but_not_billed"; - else account_for = "stock_adjustment_account"; - - return this.frm.call({ - method: "accounts.utils.get_company_default", - args: { - "fieldname": account_for, - "company": this.frm.doc.company - }, - callback: function(r) { - if (!r.exc) me.frm.set_value("expense_adjustment_account", r.message); - } - }); - } - }, - +erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ setup: function() { var me = this; @@ -80,11 +53,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ }, onload_post_render: function() { - if(this.frm.doc.__islocal && (this.frm.doc.production_order || this.frm.doc.bom_no) - && !getchildren('Stock Entry Detail', this.frm.doc.name, 'mtn_details').length) { - // if production order / bom is mentioned, get items - this.get_items(); - } + this.set_default_account(); }, refresh: function() { @@ -115,6 +84,33 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ after_cancel: function() { this.clean_up(); }, + + set_default_account: function() { + var me = this; + + if (cint(wn.defaults.get_default("auto_inventory_accounting")) && !this.frm.doc.expense_adjustment_account) { + var account_for = "stock_adjustment_account"; + if (this.frm.doc.purpose == "Sales Return") + account_for = "stock_in_hand_account"; + else if (this.frm.doc.purpose == "Purchase Return") + account_for = "stock_received_but_not_billed"; + + return this.frm.call({ + method: "accounts.utils.get_company_default", + args: { + "fieldname": account_for, + "company": this.frm.doc.company + }, + callback: function(r) { + if (!r.exc) me.frm.set_value("expense_adjustment_account", r.message); + + me.get_items(); + } + }); + } else { + me.get_items(); + } + }, clean_up: function() { // Clear Production Order record from locals, because it is updated via Stock Entry @@ -126,13 +122,17 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ }, get_items: function() { - return this.frm.call({ - doc: this.frm.doc, - method: "get_items", - callback: function(r) { - if(!r.exc) refresh_field("mtn_details"); - } - }); + if(this.frm.doc.__islocal && (this.frm.doc.production_order || this.frm.doc.bom_no) + && !getchildren('Stock Entry Detail', this.frm.doc.name, 'mtn_details').length) { + // if production order / bom is mentioned, get items + return this.frm.call({ + doc: this.frm.doc, + method: "get_items", + callback: function(r) { + if(!r.exc) refresh_field("mtn_details"); + } + }); + } }, qty: function(doc, cdt, cdn) { @@ -212,7 +212,6 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ }); loaddoc("Journal Voucher", jv_name); } - } }); } diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py index 66d1dbf7c2..b702316d9b 100644 --- a/stock/doctype/stock_entry/stock_entry.py +++ b/stock/doctype/stock_entry/stock_entry.py @@ -19,6 +19,7 @@ sql = webnotes.conn.sql class NotUpdateStockError(webnotes.ValidationError): pass class StockOverReturnError(webnotes.ValidationError): pass +class IncorrectValuationRateError(webnotes.ValidationError): pass from controllers.stock_controller import StockController @@ -245,7 +246,7 @@ class DocType(StockController): def validate_incoming_rate(self): for d in getlist(self.doclist, 'mtn_details'): if d.t_warehouse: - self.validate_value("incoming_rate", ">", 0, d) + self.validate_value("incoming_rate", ">", 0, d, raise_exception=IncorrectValuationRateError) def validate_bom(self): for d in getlist(self.doclist, 'mtn_details'): diff --git a/stock/stock_ledger.py b/stock/stock_ledger.py index 4dcca6708e..f0619c7384 100644 --- a/stock/stock_ledger.py +++ b/stock/stock_ledger.py @@ -8,6 +8,7 @@ from stock.utils import get_valuation_method import json # future reposting +class NegativeStockError(webnotes.ValidationError): pass _exceptions = [] def update_entries_after(args, verbose=1): @@ -253,9 +254,9 @@ def _raise_exceptions(args, verbose=1): _exceptions[0]["voucher_type"], _exceptions[0]["voucher_no"], abs(deficiency)) if verbose: - msgprint(msg, raise_exception=1) + msgprint(msg, raise_exception=NegativeStockError) else: - raise webnotes.ValidationError, msg + raise NegativeStockError, msg def get_previous_sle(args, for_update=False): """ diff --git a/stock/utils.py b/stock/utils.py index 96eeef644e..5c53d84945 100644 --- a/stock/utils.py +++ b/stock/utils.py @@ -69,7 +69,7 @@ def get_incoming_rate(args): if valuation_method == 'FIFO': if not previous_sle: return 0.0 - previous_stock_queue = json.loads(previous_sle.get('stock_queue', '[]')) + previous_stock_queue = json.loads(previous_sle.get('stock_queue', '[]') or '[]') in_rate = previous_stock_queue and \ get_fifo_rate(previous_stock_queue, args.get("qty") or 0) or 0 elif valuation_method == 'Moving Average': diff --git a/utilities/make_demo.py b/utilities/make_demo.py index 124259fe4e..a5228620f2 100644 --- a/utilities/make_demo.py +++ b/utilities/make_demo.py @@ -19,15 +19,18 @@ runs_for = 20 prob = { "Quotation": { "make": 0.5, "qty": (1,5) }, "Sales Order": { "make": 0.5, "qty": (1,4) }, + "Purchase Order": { "make": 0.7, "qty": (1,4) }, + "Purchase Receipt": { "make": 0.7, "qty": (1,4) }, "Supplier Quotation": { "make": 0.5, "qty": (1, 3) } } -def make(): +def make(reset=False): webnotes.connect() webnotes.print_messages = True webnotes.mute_emails = True - #setup() + if reset: + setup() simulate() def setup(): @@ -68,13 +71,29 @@ def run_sales(current_date): make_sales_order(current_date) def run_stock(current_date): - pass # make purchase requests + if can_make("Purchase Receipt"): + from buying.doctype.purchase_order.purchase_order import make_purchase_receipt + report = "Purchase Order Items To Be Received" + for po in list(set([r[0] for r in query_report.run(report)["result"] if r[0]!="Total"]))[:how_many("Purchase Receipt")]: + pr = webnotes.bean(make_purchase_receipt(po)) + pr.doc.posting_date = current_date + pr.doc.fiscal_year = "2010" + pr.insert() + pr.submit() # make delivery notes (if possible) + if can_make("Delivery Note"): + from selling.doctype.sales_order.sales_order import make_delivery_note + report = "Ordered Items To Be Delivered" + for so in list(set([r[0] for r in query_report.run(report)["result"] if r[0]!="Total"]))[:how_many("Delivery Note")]: + dn = webnotes.bean(make_delivery_note(so)) + dn.doc.posting_date = current_date + dn.doc.fiscal_year = "2010" + dn.insert() + dn.submit() + - # make stock entry (from production order) - def run_purchase(current_date): # make supplier quotations if can_make("Supplier Quotation"): @@ -121,6 +140,40 @@ def run_manufacturing(current_date): b = webnotes.bean("Material Request", pro[0]) b.submit() + # stores -> wip + if can_make("Stock Entry for WIP"): + for pro in query_report.run("Open Production Orders")["result"][:how_many("Stock Entry for WIP")]: + make_stock_entry_from_pro(pro[0], "Material Transfer", current_date) + + # wip -> fg + if can_make("Stock Entry for FG"): + for pro in query_report.run("Production Orders in Progress")["result"][:how_many("Stock Entry for FG")]: + make_stock_entry_from_pro(pro[0], "Manufacture/Repack", current_date) + + # try posting older drafts (if exists) + for st in webnotes.conn.get_values("Stock Entry", {"docstatus":0}): + try: + webnotes.bean("Stock Entry", st[0]).submit() + except NegativeStockError: pass + except IncorrectValuationRateError: pass + + +def make_stock_entry_from_pro(pro_id, purpose, current_date): + from manufacturing.doctype.production_order.production_order import make_stock_entry + from stock.stock_ledger import NegativeStockError + from stock.doctype.stock_entry.stock_entry import IncorrectValuationRateError + + st = webnotes.bean(make_stock_entry(pro_id, purpose)) + st.run_method("get_items") + st.doc.posting_date = current_date + st.doc.fiscal_year = "2010" + st.doc.expense_adjustment_account = "Stock in Hand - WP" + try: + st.insert() + st.submit() + except NegativeStockError: pass + except IncorrectValuationRateError: pass + def make_quotation(current_date): b = webnotes.bean([{ "creation": current_date,