From 934d63de16c49cc95b8e9366d2b7a7e2f6dd87db Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 11 Feb 2015 16:12:47 +0530 Subject: [PATCH 01/18] journal entry - accounts table cannot be blank - validation added --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 9930d8d38b..ca7e993d54 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -39,6 +39,7 @@ class JournalEntry(AccountsController): self.check_credit_days() self.validate_expense_claim() self.validate_credit_debit_note() + self.validate_accounts() def on_submit(self): self.check_credit_limit() @@ -450,6 +451,10 @@ class JournalEntry(AccountsController): }) if count: frappe.throw(_("{0} already made against stock entry {1}".format(self.voucher_type, self.stock_entry))) + + def validate_accounts(self): + if not self.accounts: + frappe.throw("Accounts table cannot be blank.") @frappe.whitelist() def get_default_bank_cash_account(company, voucher_type, mode_of_payment=None): From 802d717a9eaa215f3feba2578018495473c90c86 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 11 Feb 2015 17:27:58 +0530 Subject: [PATCH 02/18] fixes --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index ca7e993d54..05f2e5953a 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -39,7 +39,7 @@ class JournalEntry(AccountsController): self.check_credit_days() self.validate_expense_claim() self.validate_credit_debit_note() - self.validate_accounts() + self.validate_empty_accounts_table() def on_submit(self): self.check_credit_limit() @@ -452,8 +452,8 @@ class JournalEntry(AccountsController): if count: frappe.throw(_("{0} already made against stock entry {1}".format(self.voucher_type, self.stock_entry))) - def validate_accounts(self): - if not self.accounts: + def validate_empty_accounts_table(self): + if not self.get('accounts'): frappe.throw("Accounts table cannot be blank.") @frappe.whitelist() From 62885b3c56495b26eb485ebbb256247109d41573 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 12 Feb 2015 17:41:20 +0530 Subject: [PATCH 03/18] Price list name made -no copy --- erpnext/stock/doctype/price_list/price_list.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/price_list/price_list.json b/erpnext/stock/doctype/price_list/price_list.json index 3349559ca5..a944d20ef1 100644 --- a/erpnext/stock/doctype/price_list/price_list.json +++ b/erpnext/stock/doctype/price_list/price_list.json @@ -27,6 +27,7 @@ "fieldname": "price_list_name", "fieldtype": "Data", "label": "Price List Name", + "no_copy": 1, "oldfieldname": "price_list_name", "oldfieldtype": "Data", "permlevel": 0, @@ -74,7 +75,7 @@ "icon": "icon-tags", "idx": 1, "max_attachments": 1, - "modified": "2015-02-05 05:11:42.450750", + "modified": "2015-02-12 17:39:02.825767", "modified_by": "Administrator", "module": "Stock", "name": "Price List", From 06f712dcd7c37bf141added9f7312f81d88108df Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 12 Feb 2015 17:52:46 +0530 Subject: [PATCH 04/18] operation time made mandatory in bom operations --- .../manufacturing/doctype/bom_operation/bom_operation.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index a3678b54d6..aedc07461c 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -60,7 +60,7 @@ "oldfieldtype": "Currency", "options": "", "permlevel": 0, - "reqd": 0 + "reqd": 1 }, { "allow_on_submit": 0, @@ -77,7 +77,7 @@ ], "idx": 1, "istable": 1, - "modified": "2014-12-23 15:01:54.340605", + "modified": "2015-02-12 17:49:00.126034", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", From 4d4c643b7e72f48c2b31de644dd35d2c5620fa56 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Fri, 13 Feb 2015 13:09:17 +0530 Subject: [PATCH 05/18] fixes in stock entry --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 4faa644d47..7a202f032c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -662,7 +662,7 @@ def get_party_details(ref_dt, ref_dn): def get_production_order_details(production_order): 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 + (ifnull(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 {} From f120aec9c2a8e56619c2c59a92aad4aa387a5153 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 16 Feb 2015 15:10:29 +0530 Subject: [PATCH 06/18] workstation and operation time made editable in production order. Cost recalculation logic added --- .../production_order/production_order.js | 44 ++++++++++++++++++- .../production_order_operation.json | 6 +-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js index f7ebaa95d9..dde7b2155b 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.js +++ b/erpnext/manufacturing/doctype/production_order/production_order.js @@ -176,7 +176,47 @@ cur_frm.set_query("bom_no", function(doc) { } else msgprint(__("Please enter Production Item first")); }); -frappe.ui.form.on("Production Order", "additional_operating_cost", function(frm) { + +var calculate_total_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)) -}) +} + +frappe.ui.form.on("Production Order", "additional_operating_cost", function(frm) { + calculate_total_cost(frm); +}); + +frappe.ui.form.on("Production Order Operation", "workstation", function(frm, cdt, cdn) { + var d = locals[cdt][cdn]; + frappe.call({ + "method": "frappe.client.get", + args: { + doctype: "Workstation", + name: d.workstation + }, + callback: function (data) { + frappe.model.set_value(d.doctype, d.name, "hour_rate", data.message.hour_rate); + calculate_cost(frm.doc); + calculate_total_cost(frm); + } + }) +}); + +var calculate_cost = function(doc) { + if (doc.operations){ + var op = doc.operations; + doc.planned_operating_cost = 0.0; + for(var i=0;i Date: Mon, 16 Feb 2015 17:25:28 +0530 Subject: [PATCH 07/18] Update finished goods fix --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 4faa644d47..93b617c303 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -285,7 +285,7 @@ class StockEntry(StockController): 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) + operation_cost_per_unit += flt(d.planned_operating_cost) / flt(pro_order.qty) if not operation_cost_per_unit and bom_no: bom = frappe.db.get_value("BOM", bom_no, ["operating_cost", "quantity"], as_dict=1) From 06072c1e51e658490448b6da51ddc3a39f379b50 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 17 Feb 2015 11:50:45 +0530 Subject: [PATCH 08/18] [refactor] Material Transfer for Manufacture --- .../production_order/production_order.js | 6 +- .../production_order/production_order.py | 8 +- erpnext/patches.txt | 1 + ...pdate_material_transfer_for_manufacture.py | 5 + .../stock/doctype/stock_entry/stock_entry.js | 13 +- .../doctype/stock_entry/stock_entry.json | 1160 ++++++++--------- .../stock/doctype/stock_entry/stock_entry.py | 21 +- .../templates/form_grid/stock_entry_grid.html | 17 +- 8 files changed, 623 insertions(+), 608 deletions(-) create mode 100644 erpnext/patches/v5_0/update_material_transfer_for_manufacture.py diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js index f7ebaa95d9..2eeef0801e 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.js +++ b/erpnext/manufacturing/doctype/production_order/production_order.js @@ -7,7 +7,7 @@ $.extend(cur_frm.cscript, { cfn_set_fields(doc, dt, dn); this.frm.add_fetch("sales_order", "delivery_date", "expected_delivery_date"); - + if(doc.__islocal) { cur_frm.set_value({ "actual_start_date": "", @@ -70,7 +70,7 @@ $.extend(cur_frm.cscript, { method: "set_production_order_operations" }); }, - + planned_start_date: function() { return this.frm.call({ doc: this.frm.doc, @@ -144,7 +144,7 @@ cur_frm.cscript['Unstop Production Order'] = function() { } cur_frm.cscript['Transfer Raw Materials'] = function() { - cur_frm.cscript.make_se('Material Transfer'); + cur_frm.cscript.make_se('Material Transfer for Manufacture'); } cur_frm.cscript['Update Finished Goods'] = function() { diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index 67b53f633b..7375cf229e 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -210,7 +210,7 @@ class ProductionOrder(Document): d.status = "Completed" else: frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'")) - + def set_actual_dates(self): if self.get("operations"): actual_date = frappe.db.sql("""select min(actual_start_time) as start_date, max(actual_end_time) as end_date from `tabProduction Order Operation` @@ -220,11 +220,11 @@ class ProductionOrder(Document): else: self.actual_start_date = None self.actual_end_date = None - + def validate_delivery_date(self): if self.planned_start_date and self.expected_delivery_date and getdate(self.expected_delivery_date) < getdate(self.planned_start_date): frappe.throw(_("Expected Delivery Date cannot be greater than Planned Start Date")) - + if self.planned_end_date and self.expected_delivery_date and getdate(self.expected_delivery_date) < getdate(self.planned_end_date): frappe.msgprint(_("Production might not be able to finish by the Expected Delivery Date.")) @@ -254,7 +254,7 @@ def make_stock_entry(production_order_id, purpose, qty=None): 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)) - if purpose=="Material Transfer": + if purpose=="Material Transfer for Manufacture": stock_entry.to_warehouse = production_order.wip_warehouse else: stock_entry.from_warehouse = production_order.wip_warehouse diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 147525e2cb..d1955f2e57 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -111,3 +111,4 @@ erpnext.patches.v5_0.update_item_name_in_bom execute:frappe.reload_doc('crm', 'doctype', 'lead') execute:frappe.reload_doc('crm', 'doctype', 'opportunity') erpnext.patches.v5_0.rename_customer_issue +erpnext.patches.v5_0.update_material_transfer_for_manufacture diff --git a/erpnext/patches/v5_0/update_material_transfer_for_manufacture.py b/erpnext/patches/v5_0/update_material_transfer_for_manufacture.py new file mode 100644 index 0000000000..d862ad3d75 --- /dev/null +++ b/erpnext/patches/v5_0/update_material_transfer_for_manufacture.py @@ -0,0 +1,5 @@ +import frappe + +def execute(): + frappe.db.sql("""update `tabStock Entry` set purpose='Material Transfer for Manufacture' + where ifnull(production_order, '')!='' and purpose='Material Transfer'""") diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index cc7169bf85..a77aa748ea 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -122,7 +122,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ clean_up: function() { // Clear Production Order record from locals, because it is updated via Stock Entry if(this.frm.doc.production_order && - this.frm.doc.purpose == "Manufacture") { + in_list(["Manufacture", "Material Transfer for Manufacture"], this.frm.doc.purpose)) { frappe.model.remove_from_locals("Production Order", this.frm.doc.production_order); } @@ -164,7 +164,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ }, toggle_enable_bom: function() { - this.frm.toggle_enable("bom_no", this.frm.doc.purpose!="Manufacture"); + this.frm.toggle_enable("bom_no", !in_list(["Manufacture", "Material Transfer for Manufacture"], this.frm.doc.purpose)); }, get_doctype_docname: function() { @@ -233,8 +233,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse; }, - source_mandatory: ["Material Issue", "Material Transfer", "Purchase Return", "Subcontract"], - target_mandatory: ["Material Receipt", "Material Transfer", "Sales Return", "Subcontract"], + source_mandatory: ["Material Issue", "Material Transfer", "Purchase Return", "Subcontract", + "Material Transfer for Manufacture"], + target_mandatory: ["Material Receipt", "Material Transfer", "Sales Return", "Subcontract", + "Material Transfer for Manufacture"], from_warehouse: function(doc) { var me = this; @@ -251,6 +253,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ }, set_warehouse_if_missing: function(fieldname, value, condition) { + var changed = false; for (var i=0, l=(this.frm.doc.items || []).length; i{%= doc.description %}

{% } %} {% include "templates/form_grid/includes/visible_cols.html" %}
- {% if(doc.s_warehouse) { %} - {%= doc.s_warehouse || "" %}{% } %} - + {% if(doc.s_warehouse) { %} + + {%= doc.s_warehouse || "" %} + {% } %} + {% if(doc.t_warehouse) { %} {%= doc.t_warehouse || "" %}{% } %} + {% if(doc.s_warehouse && doc.actual_qty < doc.qty) { %} + + Not in Stock + + {% } %}
From 2712e36f089e4cde4ace34710ce15873c6abe180 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 17 Feb 2015 12:50:20 +0530 Subject: [PATCH 09/18] [stock reco] added items table --- .../stock_reconciliation.js | 143 ++++++------------ .../stock_reconciliation.json | 53 +++++-- .../stock_reconciliation.py | 67 +++----- .../stock_reconciliation_item/__init__.py | 0 .../stock_reconciliation_item.json | 110 ++++++++++++++ .../stock_reconciliation_item.py | 9 ++ erpnext/stock/utils.py | 13 +- 7 files changed, 232 insertions(+), 163 deletions(-) create mode 100644 erpnext/stock/doctype/stock_reconciliation_item/__init__.py create mode 100644 erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json create mode 100644 erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 27ed872400..c2cee3384f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -5,6 +5,26 @@ frappe.require("assets/erpnext/js/controllers/stock_controller.js"); frappe.require("assets/erpnext/js/utils.js"); frappe.provide("erpnext.stock"); +frappe.ui.form.on("Stock Reconciliation", "get_items", function(frm) { + frappe.prompt({label:"Warehouse", fieldtype:"Link", options:"Warehouse", reqd: 1}, + function(data) { + frappe.call({ + method:"erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.get_items", + args: {warehouse: data.warehouse}, + callback: function(r) { + var items = []; + frm.clear_table("items"); + for(var i=0; i< r.message.length; i++) { + var d = frm.add_child("items"); + $.extend(d, r.message[i]); + } + frm.refresh_field("items"); + } + }); + } + ); +}); + erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({ onload: function() { this.set_default_expense_account(); @@ -31,6 +51,8 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({ setup: function() { var me = this; + this.frm.get_field("items").grid.allow_build_edit(); + if (sys_defaults.auto_accounting_for_stock) { this.frm.add_fetch("company", "stock_adjustment_account", "expense_account"); this.frm.add_fetch("company", "cost_center", "cost_center"); @@ -55,108 +77,29 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({ }, refresh: function() { - if(this.frm.doc.docstatus===0) { - this.show_download_template(); - this.show_upload(); - if(this.frm.doc.reconciliation_json) { - this.frm.set_intro(__("You can submit this Stock Reconciliation.")); - } else { - this.frm.set_intro(__("Download the Template, fill appropriate data and attach the modified file.")); - } - } else if(this.frm.doc.docstatus == 1) { - this.frm.set_intro(__("Cancelling this Stock Reconciliation will nullify its effect.")); - this.show_stock_ledger(); - this.show_general_ledger(); - } else { - this.frm.set_intro(""); - } - this.show_reconciliation_data(); - this.show_download_reconciliation_data(); + // }, - show_download_template: function() { - var me = this; - this.frm.add_custom_button(__("Download Template"), function() { - this.title = __("Stock Reconcilation Template"); - frappe.tools.downloadify([[__("Stock Reconciliation")], - ["----"], - [__("Stock Reconciliation can be used to update the stock on a particular date, usually as per physical inventory.")], - [__("When submitted, the system creates difference entries to set the given stock and valuation on this date.")], - [__("It can also be used to create opening stock entries and to fix stock value.")], - ["----"], - [__("Notes:")], - [__("Item Code and Warehouse should already exist.")], - [__("You can update either Quantity or Valuation Rate or both.")], - [__("If no change in either Quantity or Valuation Rate, leave the cell blank.")], - ["----"], - ["Item Code", "Warehouse", "Quantity", "Valuation Rate"]], null, this); - return false; - }, "icon-download"); - }, + // show_download_template: function() { + // var me = this; + // this.frm.add_custom_button(__("Download Template"), function() { + // this.title = __("Stock Reconcilation Template"); + // frappe.tools.downloadify([[__("Stock Reconciliation")], + // ["----"], + // [__("Stock Reconciliation can be used to update the stock on a particular date, usually as per physical inventory.")], + // [__("When submitted, the system creates difference entries to set the given stock and valuation on this date.")], + // [__("It can also be used to create opening stock entries and to fix stock value.")], + // ["----"], + // [__("Notes:")], + // [__("Item Code and Warehouse should already exist.")], + // [__("You can update either Quantity or Valuation Rate or both.")], + // [__("If no change in either Quantity or Valuation Rate, leave the cell blank.")], + // ["----"], + // ["Item Code", "Warehouse", "Quantity", "Valuation Rate"]], null, this); + // return false; + // }, "icon-download"); + // }, - show_upload: function() { - var me = this; - var $wrapper = $(cur_frm.fields_dict.upload_html.wrapper).empty(); - - // upload - frappe.upload.make({ - parent: $wrapper, - args: { - method: 'erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.upload' - }, - sample_url: "e.g. http://example.com/somefile.csv", - callback: function(attachment, r) { - me.frm.set_value("reconciliation_json", JSON.stringify(r.message)); - me.show_reconciliation_data(); - me.frm.save(); - } - }); - - // rename button - $wrapper.find('form input[type="submit"]') - .attr('value', 'Upload') - - }, - - show_download_reconciliation_data: function() { - var me = this; - if(this.frm.doc.reconciliation_json) { - this.frm.add_custom_button(__("Download Reconcilation Data"), function() { - this.title = __("Stock Reconcilation Data"); - frappe.tools.downloadify(JSON.parse(me.frm.doc.reconciliation_json), null, this); - return false; - }, "icon-download", "btn-default"); - } - }, - - show_reconciliation_data: function() { - var $wrapper = $(cur_frm.fields_dict.reconciliation_html.wrapper).empty(); - if(this.frm.doc.reconciliation_json) { - var reconciliation_data = JSON.parse(this.frm.doc.reconciliation_json); - - var _make = function(data, header) { - var result = ""; - - var _render = header - ? function(col) { return "" + col + ""; } - : function(col) { return "" + col + ""; }; - - $.each(data, function(i, row) { - result += "" - + $.map(row, _render).join("") - + ""; - }); - return result; - }; - - var $reconciliation_table = $("
\ - \ - " + _make([reconciliation_data[0]], true) + "\ - " + _make(reconciliation_data.splice(1)) + "\ -
\ -
").appendTo($wrapper); - } - }, }); cur_frm.cscript = new erpnext.stock.StockReconciliation({frm: cur_frm}); @@ -167,4 +110,4 @@ cur_frm.cscript.company = function(doc, cdt, cdn) { cur_frm.cscript.posting_date = function(doc, cdt, cdn){ erpnext.get_fiscal_year(doc.company, doc.posting_date); -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json index a6c09eef32..6ca212db1f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json @@ -1,5 +1,5 @@ { - "allow_copy": 1, + "allow_copy": 0, "autoname": "SR/.######", "creation": "2013-03-28 10:35:31", "description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.", @@ -31,6 +31,14 @@ "read_only": 0, "reqd": 1 }, + { + "depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)", + "fieldname": "expense_account", + "fieldtype": "Link", + "label": "Difference Account", + "options": "Account", + "permlevel": 0 + }, { "fieldname": "amended_from", "fieldtype": "Link", @@ -42,6 +50,11 @@ "print_hide": 1, "read_only": 1 }, + { + "fieldname": "col1", + "fieldtype": "Column Break", + "permlevel": 0 + }, { "fieldname": "company", "fieldtype": "Link", @@ -59,14 +72,6 @@ "print_hide": 1, "reqd": 1 }, - { - "depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)", - "fieldname": "expense_account", - "fieldtype": "Link", - "label": "Difference Account", - "options": "Account", - "permlevel": 0 - }, { "depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)", "fieldname": "cost_center", @@ -76,9 +81,31 @@ "permlevel": 0 }, { - "fieldname": "col1", - "fieldtype": "Column Break", - "permlevel": 0 + "fieldname": "sb9", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "options": "Stock Reconciliation Item", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "get_items", + "fieldtype": "Button", + "label": "Get Items", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" }, { "fieldname": "upload_html", @@ -119,7 +146,7 @@ "idx": 1, "is_submittable": 1, "max_attachments": 1, - "modified": "2015-02-05 05:11:47.153367", + "modified": "2015-02-17 02:09:17.483016", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation", diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 9c85277a32..1bb3f56942 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import frappe import frappe.defaults -import json from frappe import msgprint, _ from frappe.utils import cstr, flt, cint from erpnext.stock.stock_ledger import update_entries_after @@ -28,61 +27,41 @@ class StockReconciliation(StockController): self.make_gl_entries_on_cancel() def validate_data(self): - if not self.reconciliation_json: - return - - data = json.loads(self.reconciliation_json) - - # strip out extra columns (if any) - data = [row[:4] for row in data] - - if self.head_row not in data: - msgprint(_("""Wrong Template: Unable to find head row."""), - raise_exception=1) - - # remove the help part and save the json - head_row_no = 0 - if data.index(self.head_row) != 0: - head_row_no = data.index(self.head_row) - data = data[head_row_no:] - self.reconciliation_json = json.dumps(data) - def _get_msg(row_num, msg): - return _("Row # {0}: ").format(row_num+head_row_no+2) + msg + return _("Row # {0}: ").format(row_num+1) + msg self.validation_messages = [] item_warehouse_combinations = [] # validate no of rows - rows = data[1:] + rows = self.items if len(rows) > 100: - msgprint(_("""Sorry! We can only allow upto 100 rows for Stock Reconciliation."""), - raise_exception=True) + frappe.throw(_("""Max 100 rows for Stock Reconciliation.""")) for row_num, row in enumerate(rows): # find duplicates - if [row[0], row[1]] in item_warehouse_combinations: + if [row.item_code, row.warehouse] in item_warehouse_combinations: self.validation_messages.append(_get_msg(row_num, _("Duplicate entry"))) else: - item_warehouse_combinations.append([row[0], row[1]]) + item_warehouse_combinations.append([row.item_code, row.warehouse]) - self.validate_item(row[0], row_num+head_row_no+2) + self.validate_item(row.item_code, row_num+1) # validate warehouse - if not frappe.db.get_value("Warehouse", row[1]): + if not frappe.db.get_value("Warehouse", row.warehouse): self.validation_messages.append(_get_msg(row_num, _("Warehouse not found in the system"))) # if both not specified - if row[2] in ["", None] and row[3] in ["", None]: + if row.qty in ["", None] and row.valuation_rate in ["", None]: self.validation_messages.append(_get_msg(row_num, _("Please specify either Quantity or Valuation Rate or both"))) # do not allow negative quantity - if flt(row[2]) < 0: + if flt(row.qty) < 0: self.validation_messages.append(_get_msg(row_num, _("Negative Quantity is not allowed"))) # do not allow negative valuation - if flt(row[3]) < 0: + if flt(row.valuation_rate) < 0: self.validation_messages.append(_get_msg(row_num, _("Negative Valuation Rate is not allowed"))) @@ -129,16 +108,7 @@ class StockReconciliation(StockController): and create stock ledger entries based on the difference""" from erpnext.stock.stock_ledger import get_previous_sle - row_template = ["item_code", "warehouse", "qty", "valuation_rate"] - - if not self.reconciliation_json: - msgprint(_("""Stock Reconciliation file not uploaded"""), raise_exception=1) - - data = json.loads(self.reconciliation_json) - for row_num, row in enumerate(data[data.index(self.head_row)+1:]): - row = frappe._dict(zip(row_template, row)) - row["row_num"] = row_num - + for row in self.items: if row.qty in ("", None) or row.valuation_rate in ("", None): previous_sle = get_previous_sle({ "item_code": row.item_code, @@ -216,7 +186,14 @@ class StockReconciliation(StockController): frappe.throw(_("Difference Account must be a 'Liability' type account, since this Stock Reconciliation is an Opening Entry")) @frappe.whitelist() -def upload(): - from frappe.utils.csvutils import read_csv_content_from_uploaded_file - csv_content = read_csv_content_from_uploaded_file() - return filter(lambda x: x and any(x), csv_content) +def get_items(warehouse): + from erpnext.stock.utils import get_stock_balance + items = frappe.get_list("Item", fields=["name"], filters= + {"is_stock_item": "Yes", "has_serial_no": "No", "has_batch_no": "No"}) + for item in items: + item.item_code = item.name + item.warehouse = warehouse + del item["name"] + item.qty, item.valuation_rate = get_stock_balance(item.name, warehouse, with_valuation_rate=True) + + return items diff --git a/erpnext/stock/doctype/stock_reconciliation_item/__init__.py b/erpnext/stock/doctype/stock_reconciliation_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json new file mode 100644 index 0000000000..aa55031312 --- /dev/null +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -0,0 +1,110 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "creation": "2015-02-17 01:06:05.072764", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Other", + "fields": [ + { + "allow_on_submit": 0, + "fieldname": "item_code", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Item Code", + "no_copy": 0, + "options": "Item", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "fieldname": "warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Warehouse", + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "description": "Leave blank if no change", + "fieldname": "qty", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Quantity", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "description": "Leave blank if no change", + "fieldname": "valuation_rate", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Valuation Rate", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "modified": "2015-02-17 01:07:50.200649", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Reconciliation Item", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py new file mode 100644 index 0000000000..6597efdc67 --- /dev/null +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py @@ -0,0 +1,9 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class StockReconciliationItem(Document): + pass diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index ceb0ebf4dd..9a337353df 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -34,19 +34,22 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None): return sum(sle_map.values()) -def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None): +def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None, with_valuation_rate=False): + """Returns stock balance quantity at given warehouse on given posting date or current date. + + If `with_valuation_rate` is True, will return tuple (qty, rate)""" if not posting_date: posting_date = nowdate() if not posting_time: posting_time = nowtime() - last_entry = frappe.db.sql("""select qty_after_transaction from `tabStock Ledger Entry` + last_entry = frappe.db.sql("""select qty_after_transaction, valuation_rate from `tabStock Ledger Entry` where item_code=%s and warehouse=%s and timestamp(posting_date, posting_time) < timestamp(%s, %s) order by timestamp(posting_date, posting_time) limit 1""", (item_code, warehouse, posting_date, posting_time)) - if last_entry: - return last_entry[0][0] + if with_valuation_rate: + return (last_entry[0][0], last_entry[0][1]) if last_entry else (0.0, 0.0) else: - return 0.0 + return last_entry[0][0] if last_entry else 0.0 def get_latest_stock_balance(): bin_map = {} From 0fe854692d4243aa5b0462a6cd5e5d9115b44daf Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Tue, 17 Feb 2015 14:37:45 +0530 Subject: [PATCH 10/18] [fix] add CRM module to user desktop items in patch --- erpnext/patches.txt | 3 +-- erpnext/patches/v5_0/new_crm_module.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v5_0/new_crm_module.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d1955f2e57..f7e7849627 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -108,7 +108,6 @@ erpnext.patches.v5_0.remove_shopping_cart_app erpnext.patches.v5_0.update_companywise_payment_account erpnext.patches.v5_0.remove_birthday_events erpnext.patches.v5_0.update_item_name_in_bom -execute:frappe.reload_doc('crm', 'doctype', 'lead') -execute:frappe.reload_doc('crm', 'doctype', 'opportunity') +erpnext.patches.v5_0.new_crm_module erpnext.patches.v5_0.rename_customer_issue erpnext.patches.v5_0.update_material_transfer_for_manufacture diff --git a/erpnext/patches/v5_0/new_crm_module.py b/erpnext/patches/v5_0/new_crm_module.py new file mode 100644 index 0000000000..ecf20e5853 --- /dev/null +++ b/erpnext/patches/v5_0/new_crm_module.py @@ -0,0 +1,24 @@ +# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import json +import frappe + +def execute(): + frappe.reload_doc('crm', 'doctype', 'lead') + frappe.reload_doc('crm', 'doctype', 'opportunity') + + add_crm_to_user_desktop_items() + +def add_crm_to_user_desktop_items(): + key = "_user_desktop_items" + for user in frappe.get_all("User", filters={"enabled": 1, "user_type": "System User"}): + user = user.name + user_desktop_items = frappe.db.get_defaults(key, parent=user) + if user_desktop_items: + user_desktop_items = json.loads(user_desktop_items) + if "CRM" not in user_desktop_items: + user_desktop_items.append("CRM") + frappe.db.set_default(key, json.dumps(user_desktop_items), parent=user) + + From a6c361c00ea962720a36eda7d58c8a544267a5bc Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Tue, 17 Feb 2015 15:55:36 +0530 Subject: [PATCH 11/18] Setup Wizard: Load number format based on currency #2627, login as first user on setup complete --- .../chart_of_accounts/chart_of_accounts.py | 2 +- .../setup/page/setup_wizard/setup_wizard.js | 35 ++++++++++++------- .../setup/page/setup_wizard/setup_wizard.py | 9 +++-- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index 02ffb99aa8..6fd980e4b7 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -97,7 +97,7 @@ def get_charts_for_country(country): with open(os.path.join(os.path.dirname(__file__), "syscohada_syscohada_chart_template.json"), "r") as f: _get_chart_name(f.read()) - if len(charts) > 1: + if len(charts) != 1: charts.append("Standard") return charts diff --git a/erpnext/setup/page/setup_wizard/setup_wizard.js b/erpnext/setup/page/setup_wizard/setup_wizard.js index a10dc80388..2dc93e0d0a 100644 --- a/erpnext/setup/page/setup_wizard/setup_wizard.js +++ b/erpnext/setup/page/setup_wizard/setup_wizard.js @@ -83,11 +83,11 @@ erpnext.wiz.Wizard = Class.extend({ this.hide_current_slide(); this.current_slide = this.slide_dict[id]; - this.current_slide.$wrapper.toggle(true); + this.current_slide.$wrapper.removeClass("hidden"); }, hide_current_slide: function() { if(this.current_slide) { - this.current_slide.$wrapper.toggle(false); + this.current_slide.$wrapper.addClass("hidden"); this.current_slide = null; } }, @@ -103,7 +103,7 @@ erpnext.wiz.Wizard = Class.extend({ erpnext.wiz.WizardSlide = Class.extend({ init: function(opts) { $.extend(this, opts); - this.$wrapper = $("
") + this.$wrapper = $('') .appendTo(this.wiz.parent) .attr("data-slide-id", this.id); }, @@ -305,10 +305,11 @@ $.extend(erpnext.wiz, { $timezone.empty(); // add country specific timezones first - if(country){ + if(country) { var timezone_list = data.country_info[country].timezones || []; $timezone.add_options(timezone_list.sort()); slide.get_field("currency").set_input(data.country_info[country].currency); + slide.get_field("currency").$input.trigger("change"); } // add all timezones at the end, so that user has the option to change it to any timezone @@ -320,6 +321,23 @@ $.extend(erpnext.wiz, { frappe.boot.sysdefaults.date_format = (data.country_info[country].date_format || "dd-mm-yyyy"); }); + + slide.get_input("currency").on("change", function() { + var currency = slide.get_input("currency").val(); + frappe.model.with_doc("Currency", currency, function() { + frappe.provide("locals.:Currency." + currency); + var currency_doc = frappe.model.get_doc("Currency", currency); + var number_format = currency_doc.number_format; + if (number_format==="#.###") { + number_format = "#.###,##"; + } else if (number_format==="#,###") { + number_format = "#,###.##" + } + + frappe.boot.sysdefaults.number_format = number_format; + locals[":Currency"][currency] = $.extend({}, currency_doc); + }); + }); } }, @@ -585,14 +603,7 @@ $.extend(erpnext.wiz, { callback: function(r) { wiz.show_complete(); setTimeout(function() { - if(user==="Administrator") { - msgprint(__("Login with your new User ID") + ": " + values.email); - setTimeout(function() { - frappe.app.logout(); - }, 2000); - } else { - window.location = "/desk"; - } + window.location = "/desk"; }, 2000); }, error: function(r) { diff --git a/erpnext/setup/page/setup_wizard/setup_wizard.py b/erpnext/setup/page/setup_wizard/setup_wizard.py index 445aa3dc87..7b9dce90ca 100644 --- a/erpnext/setup/page/setup_wizard/setup_wizard.py +++ b/erpnext/setup/page/setup_wizard/setup_wizard.py @@ -75,6 +75,8 @@ def setup_account(args=None): website_maker(args.company_name.strip(), args.company_tagline, args.name) create_logo(args) + login_as_first_user(args) + frappe.clear_cache() frappe.db.commit() @@ -432,6 +434,11 @@ def create_territories(): "is_group": "No" }).insert() +def login_as_first_user(args): + if args.get("email"): + frappe.local.login_manager.user = args.get("email") + frappe.local.login_manager.post_login() + @frappe.whitelist() def load_messages(language): frappe.clear_cache() @@ -444,8 +451,6 @@ def load_messages(language): @frappe.whitelist() def load_languages(): - from frappe.sessions import get_geo_from_ip - return { "default_language": get_language_from_code(frappe.local.lang), "languages": sorted(get_lang_dict().keys()) From b13c4e25161f2098850666c1358c9fcb5bd23855 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Fri, 13 Feb 2015 12:37:57 +0530 Subject: [PATCH 12/18] filters added to warehouses in prod order --- .../doctype/production_order/production_order.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js index 57363f0556..8ab6237104 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.js +++ b/erpnext/manufacturing/doctype/production_order/production_order.js @@ -219,4 +219,15 @@ var calculate_cost = function(doc) { frappe.ui.form.on("Production Order Operation", "time_in_mins", function(frm, cdt, cdn) { calculate_cost(frm.doc); calculate_total_cost(frm) -}); \ No newline at end of file +}); + +var company_filter = function(doc) { + return{ + filters: { + 'company': doc.company + } + } +} + +cur_frm.fields_dict.fg_warehouse.get_query = company_filter +cur_frm.fields_dict.wip_warehouse.get_query = company_filter From 744aec8def36fe62b1f10a25fa0a7f97a2f9c74d Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 12 Feb 2015 18:47:17 +0530 Subject: [PATCH 13/18] activity type fixs --- erpnext/patches.txt | 1 + erpnext/patches/v5_0/manufacturing_activity_type.py | 13 +++++++++++++ .../projects/doctype/activity_type/activity_type.py | 8 +++++++- erpnext/setup/page/setup_wizard/install_fixtures.py | 2 +- 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v5_0/manufacturing_activity_type.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f7e7849627..85f5bdae93 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -111,3 +111,4 @@ erpnext.patches.v5_0.update_item_name_in_bom erpnext.patches.v5_0.new_crm_module erpnext.patches.v5_0.rename_customer_issue erpnext.patches.v5_0.update_material_transfer_for_manufacture +erpnext.patches.v5_0.manufacturing_activity_type diff --git a/erpnext/patches/v5_0/manufacturing_activity_type.py b/erpnext/patches/v5_0/manufacturing_activity_type.py new file mode 100644 index 0000000000..d4e78733ba --- /dev/null +++ b/erpnext/patches/v5_0/manufacturing_activity_type.py @@ -0,0 +1,13 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + +def execute(): + if not frappe.db.exists('Activity Type','Manufacturing') { + doc = frappe.new_doc('Activity Type') + doc.update({ + 'activity_type' : 'Manufacturing' + }) + doc.save() + } \ No newline at end of file diff --git a/erpnext/projects/doctype/activity_type/activity_type.py b/erpnext/projects/doctype/activity_type/activity_type.py index a98d8cf0ec..7e316b532d 100644 --- a/erpnext/projects/doctype/activity_type/activity_type.py +++ b/erpnext/projects/doctype/activity_type/activity_type.py @@ -7,4 +7,10 @@ import frappe from frappe.model.document import Document class ActivityType(Document): - pass \ No newline at end of file + + def on_trash(self): + self.validate_manufacturing_type() + + def validate_manufacturing_type(self): + if self.activity_type == 'Manufacturing': + frappe.throw(_("Activity Type 'Manufacturing' cannot be deleted.")) \ No newline at end of file diff --git a/erpnext/setup/page/setup_wizard/install_fixtures.py b/erpnext/setup/page/setup_wizard/install_fixtures.py index 96ea0d93d9..b24eaa6cd7 100644 --- a/erpnext/setup/page/setup_wizard/install_fixtures.py +++ b/erpnext/setup/page/setup_wizard/install_fixtures.py @@ -135,7 +135,7 @@ def install(country=None): {'doctype': 'Activity Type', 'activity_type': _('Proposal Writing')}, {'doctype': 'Activity Type', 'activity_type': _('Execution')}, {'doctype': 'Activity Type', 'activity_type': _('Communication')}, - {'doctype': 'Activity Type', 'activity_type': _('Manufacturing')}, + {'doctype': 'Activity Type', 'activity_type': 'Manufacturing'}, # Industry Type {'doctype': 'Industry Type', 'industry': _('Accounting')}, From 7182d349dfb28b95cbfa00c1a3731554c934ab11 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 16 Feb 2015 18:47:58 +0530 Subject: [PATCH 14/18] fixes for capacity planning using production planning tool --- .../doctype/production_order/production_order.py | 13 ++++++++----- .../production_planning_tool.py | 5 ++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index 7375cf229e..3e9b710fd4 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -222,11 +222,14 @@ class ProductionOrder(Document): self.actual_end_date = None def validate_delivery_date(self): - if self.planned_start_date and self.expected_delivery_date and getdate(self.expected_delivery_date) < getdate(self.planned_start_date): - frappe.throw(_("Expected Delivery Date cannot be greater than Planned Start Date")) - - if self.planned_end_date and self.expected_delivery_date and getdate(self.expected_delivery_date) < getdate(self.planned_end_date): - frappe.msgprint(_("Production might not be able to finish by the Expected Delivery Date.")) + if self.docstatus==1: + if self.planned_start_date and self.expected_delivery_date \ + and getdate(self.expected_delivery_date) < getdate(self.planned_start_date): + frappe.throw(_("Expected Delivery Date cannot be greater than Planned Start Date")) + + if self.planned_end_date and self.expected_delivery_date \ + and getdate(self.expected_delivery_date) < getdate(self.planned_end_date): + frappe.msgprint(_("Production might not be able to finish by the Expected Delivery Date.")) @frappe.whitelist() def get_item_details(item): diff --git a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py index 24204f254a..47b3ccbfa4 100644 --- a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py +++ b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and +from frappe.utils import cstr, flt, cint, nowdate, now, add_days, comma_and from frappe import msgprint, _ @@ -218,6 +218,9 @@ class ProductionPlanningTool(Document): for key in items: pro = frappe.new_doc("Production Order") pro.update(items[key]) + + pro.planned_start_date = now() + pro.set_production_order_operations() frappe.flags.mute_messages = True try: From 8c0f05fbae83a613b50df4e96be7f6facb3d1284 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Tue, 17 Feb 2015 19:53:23 +0530 Subject: [PATCH 15/18] [minor] grid report loading --- .../page/financial_analytics/financial_analytics.js | 8 ++++---- .../buying/page/purchase_analytics/purchase_analytics.js | 5 +++-- erpnext/public/js/account_tree_grid.js | 2 ++ erpnext/public/js/stock_grid_report.js | 2 ++ erpnext/selling/page/sales_analytics/sales_analytics.js | 4 +++- erpnext/stock/page/stock_analytics/stock_analytics.js | 9 +++++---- erpnext/stock/page/stock_ledger/stock_ledger.js | 2 +- .../support/page/support_analytics/support_analytics.js | 2 ++ 8 files changed, 22 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/page/financial_analytics/financial_analytics.js b/erpnext/accounts/page/financial_analytics/financial_analytics.js index 4e507113ae..a895aadc4f 100644 --- a/erpnext/accounts/page/financial_analytics/financial_analytics.js +++ b/erpnext/accounts/page/financial_analytics/financial_analytics.js @@ -1,8 +1,6 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.require("assets/erpnext/js/account_tree_grid.js"); - frappe.pages['financial-analytics'].on_page_load = function(wrapper) { frappe.ui.make_app_page({ parent: wrapper, @@ -10,9 +8,11 @@ frappe.pages['financial-analytics'].on_page_load = function(wrapper) { single_column: true }); erpnext.financial_analytics = new erpnext.FinancialAnalytics(wrapper, 'Financial Analytics'); - frappe.add_breadcrumbs("Accounts") + frappe.add_breadcrumbs("Accounts"); -} +}; + +frappe.require("assets/erpnext/js/account_tree_grid.js"); erpnext.FinancialAnalytics = erpnext.AccountTreeGrid.extend({ filters: [ diff --git a/erpnext/buying/page/purchase_analytics/purchase_analytics.js b/erpnext/buying/page/purchase_analytics/purchase_analytics.js index 33fb9b2f54..470e44c15c 100644 --- a/erpnext/buying/page/purchase_analytics/purchase_analytics.js +++ b/erpnext/buying/page/purchase_analytics/purchase_analytics.js @@ -11,10 +11,11 @@ frappe.pages['purchase-analytics'].on_page_load = function(wrapper) { new erpnext.PurchaseAnalytics(wrapper); - frappe.add_breadcrumbs("Buying") - + frappe.add_breadcrumbs("Buying"); } +frappe.assets.views["Report"](); + erpnext.PurchaseAnalytics = frappe.views.TreeGridReport.extend({ init: function(wrapper) { this._super({ diff --git a/erpnext/public/js/account_tree_grid.js b/erpnext/public/js/account_tree_grid.js index dde943a478..729e88deee 100644 --- a/erpnext/public/js/account_tree_grid.js +++ b/erpnext/public/js/account_tree_grid.js @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +frappe.assets.views["Report"](); + erpnext.AccountTreeGrid = frappe.views.TreeGridReport.extend({ init: function(wrapper, title) { this._super({ diff --git a/erpnext/public/js/stock_grid_report.js b/erpnext/public/js/stock_grid_report.js index 726852f7ce..e1f97f8830 100644 --- a/erpnext/public/js/stock_grid_report.js +++ b/erpnext/public/js/stock_grid_report.js @@ -1,6 +1,8 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +frappe.assets.views["Report"](); + erpnext.StockGridReport = frappe.views.TreeGridReport.extend({ get_item_warehouse: function(warehouse, item) { if(!this.item_warehouse[item]) this.item_warehouse[item] = {}; diff --git a/erpnext/selling/page/sales_analytics/sales_analytics.js b/erpnext/selling/page/sales_analytics/sales_analytics.js index ad2102a877..bbe69d7c3e 100644 --- a/erpnext/selling/page/sales_analytics/sales_analytics.js +++ b/erpnext/selling/page/sales_analytics/sales_analytics.js @@ -12,7 +12,9 @@ frappe.pages['sales-analytics'].on_page_load = function(wrapper) { frappe.add_breadcrumbs("Selling") -} +}; + +frappe.assets.views["Report"](); erpnext.SalesAnalytics = frappe.views.TreeGridReport.extend({ init: function(wrapper) { diff --git a/erpnext/stock/page/stock_analytics/stock_analytics.js b/erpnext/stock/page/stock_analytics/stock_analytics.js index 346750bcd7..0b7acb82dc 100644 --- a/erpnext/stock/page/stock_analytics/stock_analytics.js +++ b/erpnext/stock/page/stock_analytics/stock_analytics.js @@ -2,7 +2,7 @@ // License: GNU General Public License v3. See license.txt -frappe.pages['stock-analytics'].on_page_load = function(wrapper) { +frappe.pages['stock-analytics'].on_page_load = function(wrapper) { frappe.ui.make_app_page({ parent: wrapper, title: __('Stock Analytics'), @@ -13,7 +13,8 @@ frappe.pages['stock-analytics'].on_page_load = function(wrapper) { frappe.add_breadcrumbs("Stock") - -} -frappe.require("assets/erpnext/js/stock_analytics.js"); \ No newline at end of file +}; + +frappe.assets.views["Report"](); +frappe.require("assets/erpnext/js/stock_analytics.js"); diff --git a/erpnext/stock/page/stock_ledger/stock_ledger.js b/erpnext/stock/page/stock_ledger/stock_ledger.js index 3f6a9e30bc..27525e93f3 100644 --- a/erpnext/stock/page/stock_ledger/stock_ledger.js +++ b/erpnext/stock/page/stock_ledger/stock_ledger.js @@ -10,7 +10,7 @@ frappe.pages['stock-ledger'].on_page_load = function(wrapper) { new erpnext.StockLedger(wrapper); frappe.add_breadcrumbs("Stock") -} +}; frappe.require("assets/erpnext/js/stock_grid_report.js"); diff --git a/erpnext/support/page/support_analytics/support_analytics.js b/erpnext/support/page/support_analytics/support_analytics.js index 4ca15a25e6..057aebc63b 100644 --- a/erpnext/support/page/support_analytics/support_analytics.js +++ b/erpnext/support/page/support_analytics/support_analytics.js @@ -15,6 +15,8 @@ frappe.pages['support-analytics'].on_page_load = function(wrapper) { } +frappe.assets.views["Report"](); + erpnext.SupportAnalytics = frappe.views.GridReportWithPlot.extend({ init: function(wrapper) { this._super({ From 0fb678e7cf64cca9d71e46891cb225721b494955 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 17 Feb 2015 16:09:25 +0530 Subject: [PATCH 16/18] [stock reco] cleanups --- .../stock_reconciliation.js | 29 ++++--------------- .../stock_reconciliation.py | 14 +++++++-- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index c2cee3384f..535a548a7e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -22,7 +22,7 @@ frappe.ui.form.on("Stock Reconciliation", "get_items", function(frm) { } }); } - ); + , __("Get Items"), __("Update")); }); erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({ @@ -51,7 +51,7 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({ setup: function() { var me = this; - this.frm.get_field("items").grid.allow_build_edit(); + this.frm.get_docfield("items").allow_bulk_edit = 1; if (sys_defaults.auto_accounting_for_stock) { this.frm.add_fetch("company", "stock_adjustment_account", "expense_account"); @@ -77,29 +77,12 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({ }, refresh: function() { - // + if(this.frm.doc.docstatus==1) { + this.show_stock_ledger(); + this.show_general_ledger(); + } }, - // show_download_template: function() { - // var me = this; - // this.frm.add_custom_button(__("Download Template"), function() { - // this.title = __("Stock Reconcilation Template"); - // frappe.tools.downloadify([[__("Stock Reconciliation")], - // ["----"], - // [__("Stock Reconciliation can be used to update the stock on a particular date, usually as per physical inventory.")], - // [__("When submitted, the system creates difference entries to set the given stock and valuation on this date.")], - // [__("It can also be used to create opening stock entries and to fix stock value.")], - // ["----"], - // [__("Notes:")], - // [__("Item Code and Warehouse should already exist.")], - // [__("You can update either Quantity or Valuation Rate or both.")], - // [__("If no change in either Quantity or Valuation Rate, leave the cell blank.")], - // ["----"], - // ["Item Code", "Warehouse", "Quantity", "Valuation Rate"]], null, this); - // return false; - // }, "icon-download"); - // }, - }); cur_frm.cscript = new erpnext.stock.StockReconciliation({frm: cur_frm}); diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 1bb3f56942..ef4403f5c2 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -33,11 +33,12 @@ class StockReconciliation(StockController): self.validation_messages = [] item_warehouse_combinations = [] + default_currency = frappe.db.get_default("currency") + # validate no of rows - rows = self.items - if len(rows) > 100: + if len(self.items) > 100: frappe.throw(_("""Max 100 rows for Stock Reconciliation.""")) - for row_num, row in enumerate(rows): + for row_num, row in enumerate(self.items): # find duplicates if [row.item_code, row.warehouse] in item_warehouse_combinations: self.validation_messages.append(_get_msg(row_num, _("Duplicate entry"))) @@ -65,6 +66,13 @@ class StockReconciliation(StockController): self.validation_messages.append(_get_msg(row_num, _("Negative Valuation Rate is not allowed"))) + if row.qty and not row.valuation_rate: + # try if there is a buying price list in default currency + buying_rate = frappe.db.get_value("Item Price", {"item_code": row.item_code, + "buying": 1, "currency": default_currency}, "price_list_rate") + if buying_rate: + row.valuation_rate = buying_rate + # throw all validation messages if self.validation_messages: for msg in self.validation_messages: From df9e80c87a2c054902742917d8ef8fa933da740f Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 17 Feb 2015 19:55:17 +0530 Subject: [PATCH 17/18] [refactor] stock entry, minor ux --- .../setup/page/setup_wizard/setup_wizard.py | 2 +- .../material_request/material_request_list.js | 9 +- .../purchase_receipt/purchase_receipt.py | 2 +- .../stock/doctype/stock_entry/stock_entry.py | 4 +- erpnext/stock/stock_ledger.py | 477 +++++++++--------- erpnext/templates/form_grid/item_grid.html | 26 +- .../form_grid/material_request_grid.html | 39 +- 7 files changed, 284 insertions(+), 275 deletions(-) diff --git a/erpnext/setup/page/setup_wizard/setup_wizard.py b/erpnext/setup/page/setup_wizard/setup_wizard.py index 7b9dce90ca..31dd03d56f 100644 --- a/erpnext/setup/page/setup_wizard/setup_wizard.py +++ b/erpnext/setup/page/setup_wizard/setup_wizard.py @@ -435,7 +435,7 @@ def create_territories(): }).insert() def login_as_first_user(args): - if args.get("email"): + if args.get("email") and hasattr(frappe.local, "login_manager"): frappe.local.login_manager.user = args.get("email") frappe.local.login_manager.post_login() diff --git a/erpnext/stock/doctype/material_request/material_request_list.js b/erpnext/stock/doctype/material_request/material_request_list.js index 173ed6791b..1b9bca3eca 100644 --- a/erpnext/stock/doctype/material_request/material_request_list.js +++ b/erpnext/stock/doctype/material_request/material_request_list.js @@ -1,12 +1,13 @@ frappe.listview_settings['Material Request'] = { add_fields: ["material_request_type", "status", "per_ordered"], - get_status: function(doc) { + get_indicator: function(doc) { + console.log() if(doc.status=="Stopped") { return [__("Stopped"), "red", "status,=,Stopped"]; - } if(doc.docstatus==1 && doc.per_ordered < 100) { + } else if(doc.docstatus==1 && doc.per_ordered < 100) { return [__("Pending"), "orange", "per_ordered,<,100"]; - } else if(doc.status==1 && doc.per_ordered == 100) { - return [__("Completed"), "green", "per_ordered,=,100"]; + } else if(doc.docstatus==1 && doc.per_ordered == 100) { + return [__("Ordered"), "green", "per_ordered,=,100"]; } } }; diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 5447f8a22c..504c9968e2 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -286,7 +286,7 @@ class PurchaseReceipt(BuyingController): if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty): if warehouse_account.get(d.warehouse): - val_rate_db_precision = 6 if cint(self.precision("valuation_rate")) <= 6 else 9 + val_rate_db_precision = 6 if cint(d.precision("valuation_rate")) <= 6 else 9 # warehouse account gl_entries.append(self.get_gl_dict({ diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index eb9777a049..ab9017036a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -9,7 +9,7 @@ from frappe.utils import cstr, cint, flt, comma_or, nowdate from frappe import _ from erpnext.stock.utils import get_incoming_rate -from erpnext.stock.stock_ledger import get_previous_sle +from erpnext.stock.stock_ledger import get_previous_sle, NegativeStockError from erpnext.controllers.queries import get_match_cond from erpnext.stock.get_item_details import get_available_qty, get_default_cost_center, get_conversion_factor from erpnext.manufacturing.doctype.bom.bom import validate_bom_no @@ -255,7 +255,7 @@ class StockEntry(StockController): if d.docstatus==1 and d.s_warehouse and not allow_negative_stock and d.actual_qty < d.transfer_qty: frappe.throw(_("""Row {0}: Qty not avalable in warehouse {1} on {2} {3}. Available Qty: {4}, Transfer Qty: {5}""").format(d.idx, d.s_warehouse, - self.posting_date, self.posting_time, d.actual_qty, d.transfer_qty)) + self.posting_date, self.posting_time, d.actual_qty, d.transfer_qty), NegativeStockError) # get incoming rate if not flt(d.incoming_rate) or d.s_warehouse or self.purpose == "Sales Return" or force: diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index a77a6292f9..da516deeac 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -59,278 +59,256 @@ def delete_cancelled_entry(voucher_type, voucher_no): frappe.db.sql("""delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) -def update_entries_after(args, allow_zero_rate=False, allow_negative_stock=False, verbose=1): +class update_entries_after(object): """ update valution rate and qty after transaction from the current time-bucket onwards - args = { - "item_code": "ABC", - "warehouse": "XYZ", - "posting_date": "2012-12-12", - "posting_time": "12:00" - } + :param args: args as dict + + args = { + "item_code": "ABC", + "warehouse": "XYZ", + "posting_date": "2012-12-12", + "posting_time": "12:00" + } """ - if not _exceptions: - frappe.local.stockledger_exceptions = [] + def __init__(self, args, allow_zero_rate=False, allow_negative_stock=None, verbose=1): + from frappe.model.meta import get_field_precision - if not allow_negative_stock: - allow_negative_stock = cint(frappe.db.get_default("allow_negative_stock")) + self.exceptions = [] + self.verbose = verbose + self.allow_zero_rate = allow_zero_rate + self.allow_negative_stock = allow_negative_stock - previous_sle = get_sle_before_datetime(args) + if self.allow_negative_stock==None: + self.allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", + "allow_negative_stock")) - qty_after_transaction = flt(previous_sle.get("qty_after_transaction")) - valuation_rate = flt(previous_sle.get("valuation_rate")) - stock_queue = json.loads(previous_sle.get("stock_queue") or "[]") - stock_value = flt(previous_sle.get("stock_value")) - prev_stock_value = flt(previous_sle.get("stock_value")) + self.args = args + for key, value in args.iteritems(): + setattr(self, key, value) - entries_to_fix = get_sle_after_datetime(previous_sle or \ - {"item_code": args["item_code"], "warehouse": args["warehouse"]}, for_update=True) - valuation_method = get_valuation_method(args["item_code"]) - stock_value_difference = 0.0 + self.previous_sle = self.get_sle_before_datetime() + self.previous_sle = self.previous_sle[0] if self.previous_sle else frappe._dict() - for sle in entries_to_fix: - if sle.serial_no or not cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock")): + for key in ("qty_after_transaction", "valuation_rate", "stock_value"): + setattr(self, key, flt(self.previous_sle.get(key))) + + self.company = frappe.db.get_value("Warehouse", self.warehouse, "company") + self.precision = get_field_precision(frappe.get_meta("Stock Ledger Entry").get_field("stock_value"), + currency=frappe.db.get_value("Company", self.company, "default_currency")) + + self.prev_stock_value = self.stock_value + self.stock_queue = json.loads(self.previous_sle.stock_queue or "[]") + self.valuation_method = get_valuation_method(self.item_code) + self.stock_value_difference = 0.0 + self.build() + + def build(self): + entries_to_fix = self.get_sle_after_datetime() + + for sle in entries_to_fix: + self.process_sle(sle) + + if self.exceptions: + self.raise_exceptions() + + self.update_bin() + + def update_bin(self): + # update bin + bin_name = frappe.db.get_value("Bin", { + "item_code": self.item_code, + "warehouse": self.warehouse + }) + + if not bin_name: + bin_doc = frappe.get_doc({ + "doctype": "Bin", + "item_code": self.item_code, + "warehouse": self.warehouse + }) + bin_doc.insert(ignore_permissions=True) + else: + bin_doc = frappe.get_doc("Bin", bin_name) + + bin_doc.update({ + "valuation_rate": self.valuation_rate, + "actual_qty": self.qty_after_transaction, + "stock_value": self.stock_value + }) + bin_doc.save(ignore_permissions=True) + + def process_sle(self, sle): + if sle.serial_no or not cint(self.allow_negative_stock): # validate negative stock for serialized items, fifo valuation # or when negative stock is not allowed for moving average - if not validate_negative_stock(qty_after_transaction, sle): - qty_after_transaction += flt(sle.actual_qty) - continue - + if not self.validate_negative_stock(sle): + self.qty_after_transaction += flt(sle.actual_qty) + return if sle.serial_no: - valuation_rate = get_serialized_values(qty_after_transaction, sle, valuation_rate) - qty_after_transaction += flt(sle.actual_qty) - + self.valuation_rate = self.get_serialized_values(sle) + self.qty_after_transaction += flt(sle.actual_qty) + self.stock_value = self.qty_after_transaction * self.valuation_rate else: if sle.voucher_type=="Stock Reconciliation": - valuation_rate = sle.valuation_rate - qty_after_transaction = sle.qty_after_transaction - stock_queue = [[qty_after_transaction, valuation_rate]] + # assert + self.valuation_rate = sle.valuation_rate + self.qty_after_transaction = sle.qty_after_transaction + self.stock_queue = [[self.qty_after_transaction, self.valuation_rate]] + self.stock_value = self.qty_after_transaction * self.valuation_rate else: - if valuation_method == "Moving Average": - valuation_rate = get_moving_average_values(qty_after_transaction, sle, valuation_rate, allow_zero_rate) + if self.valuation_method == "Moving Average": + self.get_moving_average_values(sle) + self.qty_after_transaction += flt(sle.actual_qty) + self.stock_value = self.qty_after_transaction * self.valuation_rate else: - valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue, allow_zero_rate) + self.get_fifo_values(sle) + self.qty_after_transaction += flt(sle.actual_qty) + self.stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.stock_queue)) - qty_after_transaction += flt(sle.actual_qty) - - # get stock value - if sle.serial_no: - stock_value = qty_after_transaction * valuation_rate - elif valuation_method == "Moving Average": - stock_value = qty_after_transaction * valuation_rate - else: - stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in stock_queue)) - # rounding as per precision - from frappe.model.meta import get_field_precision - meta = frappe.get_meta("Stock Ledger Entry") + self.stock_value = flt(self.stock_value, self.precision) - stock_value = flt(stock_value, get_field_precision(meta.get_field("stock_value"), - frappe._dict({"fields": sle}))) - - stock_value_difference = stock_value - prev_stock_value - prev_stock_value = stock_value + stock_value_difference = self.stock_value - self.prev_stock_value + self.prev_stock_value = self.stock_value # update current sle frappe.db.sql("""update `tabStock Ledger Entry` set qty_after_transaction=%s, valuation_rate=%s, stock_queue=%s, stock_value=%s, stock_value_difference=%s where name=%s""", - (qty_after_transaction, valuation_rate, - json.dumps(stock_queue), stock_value, stock_value_difference, sle.name)) + (self.qty_after_transaction, self.valuation_rate, + json.dumps(self.stock_queue), self.stock_value, stock_value_difference, sle.name)) - if _exceptions: - _raise_exceptions(args, verbose) + def validate_negative_stock(self, sle): + """ + validate negative stock for entries current datetime onwards + will not consider cancelled entries + """ + diff = self.qty_after_transaction + flt(sle.actual_qty) - # update bin - if not frappe.db.exists({"doctype": "Bin", "item_code": args["item_code"], - "warehouse": args["warehouse"]}): - bin_wrapper = frappe.get_doc({ - "doctype": "Bin", - "item_code": args["item_code"], - "warehouse": args["warehouse"], - }) - bin_wrapper.flags.ignore_permissions = 1 - bin_wrapper.insert() - - frappe.db.sql("""update `tabBin` set valuation_rate=%s, actual_qty=%s, - stock_value=%s, - projected_qty = (actual_qty + indented_qty + ordered_qty + planned_qty - reserved_qty) - where item_code=%s and warehouse=%s""", (valuation_rate, qty_after_transaction, - stock_value, args["item_code"], args["warehouse"])) - -def get_sle_before_datetime(args, for_update=False): - """ - get previous stock ledger entry before current time-bucket - - Details: - get the last sle before the current time-bucket, so that all values - are reposted from the current time-bucket onwards. - this is necessary because at the time of cancellation, there may be - entries between the cancelled entries in the same time-bucket - """ - sle = get_stock_ledger_entries(args, - ["timestamp(posting_date, posting_time) < timestamp(%(posting_date)s, %(posting_time)s)"], - "desc", "limit 1", for_update=for_update) - - return sle and sle[0] or frappe._dict() - -def get_sle_after_datetime(args, for_update=False): - """get Stock Ledger Entries after a particular datetime, for reposting""" - # NOTE: using for update of - conditions = ["timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)"] - - # Excluding name: Workaround for MariaDB timestamp() floating microsecond issue - if args.get("name"): - conditions.append("name!=%(name)s") - - return get_stock_ledger_entries(args, conditions, "asc", for_update=for_update) - -def get_stock_ledger_entries(args, conditions=None, order="desc", limit=None, for_update=False): - """get stock ledger entries filtered by specific posting datetime conditions""" - if not args.get("posting_date"): - args["posting_date"] = "1900-01-01" - if not args.get("posting_time"): - args["posting_time"] = "00:00" - - return frappe.db.sql("""select *, timestamp(posting_date, posting_time) as "timestamp" from `tabStock Ledger Entry` - where item_code = %%(item_code)s - and warehouse = %%(warehouse)s - and ifnull(is_cancelled, 'No')='No' - %(conditions)s - order by timestamp(posting_date, posting_time) %(order)s, name %(order)s - %(limit)s %(for_update)s""" % { - "conditions": conditions and ("and " + " and ".join(conditions)) or "", - "limit": limit or "", - "for_update": for_update and "for update" or "", - "order": order - }, args, as_dict=1) - -def validate_negative_stock(qty_after_transaction, sle): - """ - validate negative stock for entries current datetime onwards - will not consider cancelled entries - """ - diff = qty_after_transaction + flt(sle.actual_qty) - - if not _exceptions: - frappe.local.stockledger_exceptions = [] - - if diff < 0 and abs(diff) > 0.0001: - # negative stock! - exc = sle.copy().update({"diff": diff}) - _exceptions.append(exc) - return False - else: - return True - -def get_serialized_values(qty_after_transaction, sle, valuation_rate): - incoming_rate = flt(sle.incoming_rate) - actual_qty = flt(sle.actual_qty) - serial_no = cstr(sle.serial_no).split("\n") - - if incoming_rate < 0: - # wrong incoming rate - incoming_rate = valuation_rate - elif incoming_rate == 0 or flt(sle.actual_qty) < 0: - # In case of delivery/stock issue, get average purchase rate - # of serial nos of current entry - incoming_rate = flt(frappe.db.sql("""select avg(ifnull(purchase_rate, 0)) - from `tabSerial No` where name in (%s)""" % (", ".join(["%s"]*len(serial_no))), - tuple(serial_no))[0][0]) - - if incoming_rate and not valuation_rate: - valuation_rate = incoming_rate - else: - new_stock_qty = qty_after_transaction + actual_qty - if new_stock_qty > 0: - new_stock_value = qty_after_transaction * valuation_rate + actual_qty * incoming_rate - if new_stock_value > 0: - # calculate new valuation rate only if stock value is positive - # else it remains the same as that of previous entry - valuation_rate = new_stock_value / new_stock_qty - - return valuation_rate - -def get_moving_average_values(qty_after_transaction, sle, valuation_rate, allow_zero_rate): - incoming_rate = flt(sle.incoming_rate) - actual_qty = flt(sle.actual_qty) - - if flt(sle.actual_qty) > 0: - if qty_after_transaction < 0 and not valuation_rate: - # if negative stock, take current valuation rate as incoming rate - valuation_rate = incoming_rate - - new_stock_qty = abs(qty_after_transaction) + actual_qty - new_stock_value = (abs(qty_after_transaction) * valuation_rate) + (actual_qty * incoming_rate) - - if new_stock_qty: - valuation_rate = new_stock_value / flt(new_stock_qty) - elif not valuation_rate and qty_after_transaction <= 0: - valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, allow_zero_rate) - - return abs(flt(valuation_rate)) - -def get_fifo_values(qty_after_transaction, sle, stock_queue, allow_zero_rate): - incoming_rate = flt(sle.incoming_rate) - actual_qty = flt(sle.actual_qty) - - if actual_qty > 0: - if not stock_queue: - stock_queue.append([0, 0]) - - if stock_queue[-1][0] > 0: - stock_queue.append([actual_qty, incoming_rate]) + if diff < 0 and abs(diff) > 0.0001: + # negative stock! + exc = sle.copy().update({"diff": diff}) + self.exceptions.append(exc) + return False else: - qty = stock_queue[-1][0] + actual_qty - if qty == 0: - stock_queue.pop(-1) + return True + + def get_serialized_values(self, sle): + incoming_rate = flt(sle.incoming_rate) + actual_qty = flt(sle.actual_qty) + serial_no = cstr(sle.serial_no).split("\n") + + if incoming_rate < 0: + # wrong incoming rate + incoming_rate = self.valuation_rate + elif incoming_rate == 0 or flt(sle.actual_qty) < 0: + # In case of delivery/stock issue, get average purchase rate + # of serial nos of current entry + incoming_rate = flt(frappe.db.sql("""select avg(ifnull(purchase_rate, 0)) + from `tabSerial No` where name in (%s)""" % (", ".join(["%s"]*len(serial_no))), + tuple(serial_no))[0][0]) + + if incoming_rate and not self.valuation_rate: + self.valuation_rate = incoming_rate + else: + new_stock_qty = self.qty_after_transaction + actual_qty + if new_stock_qty > 0: + new_stock_value = self.qty_after_transaction * self.valuation_rate + actual_qty * incoming_rate + if new_stock_value > 0: + # calculate new valuation rate only if stock value is positive + # else it remains the same as that of previous entry + self.valuation_rate = new_stock_value / new_stock_qty + + def get_moving_average_values(self, sle): + incoming_rate = flt(sle.incoming_rate) + actual_qty = flt(sle.actual_qty) + + if flt(sle.actual_qty) > 0: + if self.qty_after_transaction < 0 and not self.valuation_rate: + # if negative stock, take current valuation rate as incoming rate + self.valuation_rate = incoming_rate + + new_stock_qty = abs(self.qty_after_transaction) + actual_qty + new_stock_value = (abs(self.qty_after_transaction) * self.valuation_rate) + (actual_qty * incoming_rate) + + if new_stock_qty: + self.valuation_rate = new_stock_value / flt(new_stock_qty) + elif not self.valuation_rate and self.qty_after_transaction <= 0: + valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, self.allow_zero_rate) + + return abs(flt(valuation_rate)) + + def get_fifo_values(self, sle): + incoming_rate = flt(sle.incoming_rate) + actual_qty = flt(sle.actual_qty) + + if actual_qty > 0: + if not self.stock_queue: + self.stock_queue.append([0, 0]) + + if self.stock_queue[-1][0] > 0: + self.stock_queue.append([actual_qty, incoming_rate]) else: - stock_queue[-1] = [qty, incoming_rate] - else: - qty_to_pop = abs(actual_qty) - while qty_to_pop: - if not stock_queue: - stock_queue.append([0, get_valuation_rate(sle.item_code, sle.warehouse, allow_zero_rate) - if qty_after_transaction <= 0 else 0]) + qty = self.stock_queue[-1][0] + actual_qty + if qty == 0: + self.stock_queue.pop(-1) + else: + self.stock_queue[-1] = [qty, incoming_rate] + else: + qty_to_pop = abs(actual_qty) + while qty_to_pop: + if not self.stock_queue: + if self.qty_after_transaction > 0: + _rate = get_valuation_rate(sle.item_code, sle.warehouse, self.allow_zero_rate) + else: + _rate = 0 + self.stock_queue.append([0, _rate]) - batch = stock_queue[0] + batch = self.stock_queue[0] - if qty_to_pop >= batch[0]: - # consume current batch - qty_to_pop = qty_to_pop - batch[0] - stock_queue.pop(0) - if not stock_queue and qty_to_pop: - # stock finished, qty still remains to be withdrawn - # negative stock, keep in as a negative batch - stock_queue.append([-qty_to_pop, batch[1]]) - break + if qty_to_pop >= batch[0]: + # consume current batch + qty_to_pop = qty_to_pop - batch[0] + self.stock_queue.pop(0) + if not self.stock_queue and qty_to_pop: + # stock finished, qty still remains to be withdrawn + # negative stock, keep in as a negative batch + self.stock_queue.append([-qty_to_pop, batch[1]]) + break - else: - # qty found in current batch - # consume it and exit - batch[0] = batch[0] - qty_to_pop - qty_to_pop = 0 + else: + # qty found in current batch + # consume it and exit + batch[0] = batch[0] - qty_to_pop + qty_to_pop = 0 - stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in stock_queue)) - stock_qty = sum((flt(batch[0]) for batch in stock_queue)) + stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.stock_queue)) + stock_qty = sum((flt(batch[0]) for batch in self.stock_queue)) - valuation_rate = (stock_value / flt(stock_qty)) if stock_qty else 0 + self.valuation_rate = (stock_value / flt(stock_qty)) if stock_qty else 0 - return abs(valuation_rate) + def get_sle_before_datetime(self): + """get previous stock ledger entry before current time-bucket""" + return get_stock_ledger_entries(self.args, "<", "desc", "limit 1", for_update=False) -def _raise_exceptions(args, verbose=1): - deficiency = min(e["diff"] for e in _exceptions) - msg = _("Negative Stock Error ({6}) for Item {0} in Warehouse {1} on {2} {3} in {4} {5}").format(args["item_code"], - args.get("warehouse"), _exceptions[0]["posting_date"], _exceptions[0]["posting_time"], - _(_exceptions[0]["voucher_type"]), _exceptions[0]["voucher_no"], deficiency) - if verbose: - frappe.throw(msg, NegativeStockError) - else: - raise NegativeStockError, msg + def get_sle_after_datetime(self): + """get Stock Ledger Entries after a particular datetime, for reposting""" + return get_stock_ledger_entries(self.previous_sle or self.args, ">", "asc", for_update=True) + + def raise_exceptions(self): + deficiency = min(e["diff"] for e in self.exceptions) + msg = _("Negative Stock Error ({6}) for Item {0} in Warehouse {1} on {2} {3} in {4} {5}").format(self.item_code, + self.warehouse, self.exceptions[0]["posting_date"], self.exceptions[0]["posting_time"], + _(self.exceptions[0]["voucher_type"]), self.exceptions[0]["voucher_no"], deficiency) + if self.verbose: + frappe.throw(msg, NegativeStockError) + else: + raise NegativeStockError, msg def get_previous_sle(args, for_update=False): """ @@ -346,13 +324,34 @@ def get_previous_sle(args, for_update=False): "sle": "name of reference Stock Ledger Entry" } """ - if not args.get("sle"): args["sle"] = "" - - sle = get_stock_ledger_entries(args, ["name != %(sle)s", - "timestamp(posting_date, posting_time) <= timestamp(%(posting_date)s, %(posting_time)s)"], - "desc", "limit 1", for_update=for_update) + args["name"] = args.get("sle", None) or "" + sle = get_stock_ledger_entries(args, "<=", "desc", "limit 1", for_update=for_update) return sle and sle[0] or {} +def get_stock_ledger_entries(previous_sle, operator=None, order="desc", limit=None, for_update=False): + """get stock ledger entries filtered by specific posting datetime conditions""" + conditions = "timestamp(posting_date, posting_time) {0} timestamp(%(posting_date)s, %(posting_time)s)".format(operator) + if not previous_sle.get("posting_date"): + previous_sle["posting_date"] = "1900-01-01" + if not previous_sle.get("posting_time"): + previous_sle["posting_time"] = "00:00" + + if operator in (">", "<=") and previous_sle.get("name"): + conditions += " and name!=%(name)s" + + return frappe.db.sql("""select *, timestamp(posting_date, posting_time) as "timestamp" from `tabStock Ledger Entry` + where item_code = %%(item_code)s + and warehouse = %%(warehouse)s + and ifnull(is_cancelled, 'No')='No' + and %(conditions)s + order by timestamp(posting_date, posting_time) %(order)s, name %(order)s + %(limit)s %(for_update)s""" % { + "conditions": conditions, + "limit": limit or "", + "for_update": for_update and "for update" or "", + "order": order + }, previous_sle, as_dict=1) + def get_valuation_rate(item_code, warehouse, allow_zero_rate=False): last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` diff --git a/erpnext/templates/form_grid/item_grid.html b/erpnext/templates/form_grid/item_grid.html index e792da0098..6bd5d48e8b 100644 --- a/erpnext/templates/form_grid/item_grid.html +++ b/erpnext/templates/form_grid/item_grid.html @@ -14,9 +14,9 @@
{%= doc.item_name %}{% } %} {% if(doc.item_name != doc.description) { %}

{%= doc.description %}

{% } %} -
+

{% if(doc.sales_order || doc.against_sales_order) { %} - {%= doc.sales_order || doc.against_sales_order %} @@ -36,22 +36,28 @@ } } %} - {%= doc.warehouse %} {% } %} -

+

{% include "templates/form_grid/includes/visible_cols.html" %} {% if(doc.schedule_date) { %} -
- {%= doc.get_formatted("schedule_date") %} -
+
+
+ {% if(frappe.datetime.get_diff(doc.schedule_date) < 1 && doc.received_qty < doc.qty) { %} + + {%= __("Overdue on {0}", [doc.get_formatted("schedule_date")]) %} + + {% } else { %} + + {%= __("Due on {0}", [doc.get_formatted("schedule_date")]) %} + + {% } %} +
{% } %}
diff --git a/erpnext/templates/form_grid/material_request_grid.html b/erpnext/templates/form_grid/material_request_grid.html index c30cf582d0..1e25909d7e 100644 --- a/erpnext/templates/form_grid/material_request_grid.html +++ b/erpnext/templates/form_grid/material_request_grid.html @@ -13,24 +13,7 @@
{%= doc.item_name %}{% } %} {% if(doc.item_name != doc.description) { %}

{%= doc.description %}

{% } %} - {% include "templates/form_grid/includes/visible_cols.html" %} - {% if(doc.schedule_date) { %} -
- {%= doc.get_formatted("schedule_date") %} - {% } %} - - - -
- {%= doc.get_formatted("qty") %} - {%= doc.uom || doc.stock_uom %} - {% var completed = - 100 - cint((doc.qty - cint(doc.ordered_qty)) * 100 / doc.qty), - title = __("Ordered"); %} - {% include "templates/form_grid/includes/progress.html" %} +
{% if(doc.warehouse) { %}
{% } %} + {% if(doc.schedule_date) { %} + + {%= doc.get_formatted("schedule_date") %} + {% } %} +
+ {% include "templates/form_grid/includes/visible_cols.html" %} +
+ + +
+ {%= doc.get_formatted("qty") %} +
{%= doc.uom || doc.stock_uom %}
+ {% if(doc.qty == doc.ordered_qty) { %} +
{%= __("Ordered") %}
+ {% } else { %} +
{%= __("{0} Ordered", doc.ordered_qty) %}
+ {% } %}
{% } %} From 2e0e711eb23f9a46fff2e75fa51d6be97213b08b Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 18 Feb 2015 11:38:05 +0530 Subject: [PATCH 18/18] [minor] stock_ledger_entry.py --- erpnext/stock/stock_ledger.py | 10 +++++----- .../templates/form_grid/material_request_grid.html | 11 ++++------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index da516deeac..a4c54b445c 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -177,11 +177,11 @@ class update_entries_after(object): self.prev_stock_value = self.stock_value # update current sle - frappe.db.sql("""update `tabStock Ledger Entry` - set qty_after_transaction=%s, valuation_rate=%s, stock_queue=%s, - stock_value=%s, stock_value_difference=%s where name=%s""", - (self.qty_after_transaction, self.valuation_rate, - json.dumps(self.stock_queue), self.stock_value, stock_value_difference, sle.name)) + sle.qty_after_transaction = self.qty_after_transaction + sle.valuation_rate = self.valuation_rate + sle.stock_queue = json.dumps(self.stock_queue) + sle.stock_value_difference = stock_value_difference + sle.save() def validate_negative_stock(self, sle): """ diff --git a/erpnext/templates/form_grid/material_request_grid.html b/erpnext/templates/form_grid/material_request_grid.html index 1e25909d7e..812245a8c1 100644 --- a/erpnext/templates/form_grid/material_request_grid.html +++ b/erpnext/templates/form_grid/material_request_grid.html @@ -15,13 +15,10 @@

{%= doc.description %}

{% } %}
{% if(doc.warehouse) { %} -
- - {%= doc.warehouse %} - -
+ + {%= doc.warehouse %} + {% } %} {% if(doc.schedule_date) { %}