From dc93e0adc610ea120d361b2d58e84b41facd9840 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 20 Feb 2015 15:11:56 +0530 Subject: [PATCH] [fixes] stock reco --- erpnext/patches.txt | 1 + .../v5_0/convert_stock_reconcilition.py | 21 ++++ erpnext/selling/doctype/customer/customer.js | 32 +++--- .../selling/doctype/customer/customer.json | 11 ++- erpnext/stock/doctype/item/item.py | 4 +- .../stock/doctype/item_price/item_price.json | 4 +- .../stock_reconciliation.js | 6 +- .../stock_reconciliation.json | 98 ++++++++++--------- .../stock_reconciliation.py | 35 ++++++- .../stock_reconciliation_item.json | 32 +++++- erpnext/stock/utils.py | 18 ++-- 11 files changed, 173 insertions(+), 89 deletions(-) create mode 100644 erpnext/patches/v5_0/convert_stock_reconcilition.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 17e6435429..8560a7e8f8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -120,3 +120,4 @@ erpnext.patches.v5_0.manufacturing_activity_type erpnext.patches.v5_0.update_item_description_and_image erpnext.patches.v5_0.update_material_transferred_for_qty erpnext.patches.v5_0.stock_entry_update_value +erpnext.patches.v5_0.convert_stock_reconciliation diff --git a/erpnext/patches/v5_0/convert_stock_reconcilition.py b/erpnext/patches/v5_0/convert_stock_reconcilition.py new file mode 100644 index 0000000000..ce649c0d60 --- /dev/null +++ b/erpnext/patches/v5_0/convert_stock_reconcilition.py @@ -0,0 +1,21 @@ +import frappe, json + +def execute(): + # stock reco now amendable + frappe.db.sql("""update tabDocPerm set `amend` = 1 where parent='Stock Reconciliation' and submit = 1""") + + if frappe.db.has_column("Stock Reconciliation", "reconciliation_json"): + for sr in frappe.db.get_all("Stock Reconciliation", ["name"], + {"reconciliation_json": ["!=", ""]}): + sr = frappe.get_doc("Stock Reconciliation", sr.name) + for item in json.loads(sr.reconciliation_json): + sr.append("items", { + "item_code": item.item_code, + "warehouse": item.warehouse, + "valuation_rate": item.valuation_rate, + "qty": item.qty + }) + + for item in sr.items: + item.db_update() + diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 5d349f68f5..fa42e407a3 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -1,6 +1,20 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +frappe.ui.form.on("Customer", "refresh", function(frm) { + cur_frm.cscript.setup_dashboard(frm.doc); + + if(frappe.defaults.get_default("cust_master_name")!="Naming Series") { + frm.toggle_display("naming_series", false); + } else { + erpnext.toggle_naming_series(); + } + + frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal); + + if(!frm.doc.__islocal) erpnext.utils.render_address_and_contact(frm); +}) + cur_frm.cscript.onload = function(doc, dt, dn) { cur_frm.cscript.load_defaults(doc, dt, dn); } @@ -16,24 +30,6 @@ cur_frm.cscript.load_defaults = function(doc, dt, dn) { cur_frm.add_fetch('lead_name', 'company_name', 'customer_name'); cur_frm.add_fetch('default_sales_partner','commission_rate','default_commission_rate'); -cur_frm.cscript.refresh = function(doc, dt, dn) { - cur_frm.cscript.setup_dashboard(doc); - - if(frappe.defaults.get_default("cust_master_name")!="Naming Series") { - cur_frm.toggle_display("naming_series", false); - } else { - erpnext.toggle_naming_series(); - } - - if(doc.__islocal){ - hide_field(['address_html','contact_html']); - }else{ - unhide_field(['address_html','contact_html']); - // make lists - erpnext.utils.render_address_and_contact(cur_frm); - } -} - cur_frm.cscript.validate = function(doc, dt, dn) { if(doc.lead_name) frappe.model.clear_doc("Lead", doc.lead_name); } diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 562a9dc952..dcfbf1eada 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -11,7 +11,7 @@ { "fieldname": "basic_info", "fieldtype": "Section Break", - "label": "Basic Info", + "label": "", "oldfieldtype": "Section Break", "options": "icon-user", "permlevel": 0, @@ -105,7 +105,7 @@ "depends_on": "eval:!doc.__islocal", "fieldname": "address_contacts", "fieldtype": "Section Break", - "label": "Address & Contacts", + "label": "", "options": "icon-map-marker", "permlevel": 0 }, @@ -137,6 +137,7 @@ "permlevel": 0 }, { + "description": "", "fieldname": "accounts", "fieldtype": "Table", "label": "Accounts", @@ -146,7 +147,7 @@ { "fieldname": "more_info", "fieldtype": "Section Break", - "label": "More Info", + "label": "", "oldfieldtype": "Section Break", "options": "icon-file-text", "permlevel": 0 @@ -223,7 +224,7 @@ { "fieldname": "sales_team_section_break", "fieldtype": "Section Break", - "label": "Sales Team", + "label": "", "oldfieldtype": "Section Break", "options": "icon-group", "permlevel": 0 @@ -267,7 +268,7 @@ ], "icon": "icon-user", "idx": 1, - "modified": "2015-02-05 05:11:36.603762", + "modified": "2015-02-20 01:39:41.062529", "modified_by": "Administrator", "module": "Selling", "name": "Customer", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 8988ca32b9..aa338ec5d0 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -160,7 +160,7 @@ class Item(WebsiteGenerator): # delete missing variants existing_variants = [d.name for d in frappe.get_all("Item", - {"variant_of":self.name})] + filters={"variant_of":self.name})] updated, deleted = [], [] for existing_variant in existing_variants: @@ -385,7 +385,7 @@ class Item(WebsiteGenerator): super(Item, self).on_trash() frappe.db.sql("""delete from tabBin where item_code=%s""", self.item_code) frappe.db.sql("delete from `tabItem Price` where item_code=%s", self.name) - for variant_of in frappe.get_all("Item", {"variant_of": self.name}): + for variant_of in frappe.get_all("Item", filters={"variant_of": self.name}): frappe.delete_doc("Item", variant_of.name) def before_rename(self, olddn, newdn, merge=False): diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index da5e196fb9..bda78e9f44 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -1,6 +1,6 @@ { "allow_import": 1, - "autoname": "RFD/.#####", + "autoname": "ITEM-PRICE-.#####", "creation": "2013-05-02 16:29:48", "description": "Multiple Item prices.", "docstatus": 0, @@ -105,7 +105,7 @@ "idx": 1, "in_create": 0, "istable": 0, - "modified": "2015-02-05 05:11:39.951804", + "modified": "2015-02-20 00:26:20.161437", "modified_by": "Administrator", "module": "Stock", "name": "Item Price", diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 535a548a7e..2a9cb3fb8e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -10,7 +10,11 @@ frappe.ui.form.on("Stock Reconciliation", "get_items", function(frm) { function(data) { frappe.call({ method:"erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.get_items", - args: {warehouse: data.warehouse}, + args: { + warehouse: data.warehouse, + posting_date: frm.doc.posting_date, + posting_time: frm.doc.posting_time + }, callback: function(r) { var items = []; frm.clear_table("items"); diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json index 6ca212db1f..763680f780 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json @@ -32,11 +32,8 @@ "reqd": 1 }, { - "depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)", - "fieldname": "expense_account", - "fieldtype": "Link", - "label": "Difference Account", - "options": "Account", + "fieldname": "col1", + "fieldtype": "Column Break", "permlevel": 0 }, { @@ -50,11 +47,6 @@ "print_hide": 1, "read_only": 1 }, - { - "fieldname": "col1", - "fieldtype": "Column Break", - "permlevel": 0 - }, { "fieldname": "company", "fieldtype": "Link", @@ -63,23 +55,6 @@ "permlevel": 0, "reqd": 1 }, - { - "fieldname": "fiscal_year", - "fieldtype": "Link", - "label": "Fiscal Year", - "options": "Fiscal Year", - "permlevel": 0, - "print_hide": 1, - "reqd": 1 - }, - { - "depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)", - "fieldname": "cost_center", - "fieldtype": "Link", - "label": "Cost Center", - "options": "Cost Center", - "permlevel": 0 - }, { "fieldname": "sb9", "fieldtype": "Section Break", @@ -108,28 +83,20 @@ "precision": "" }, { - "fieldname": "upload_html", - "fieldtype": "HTML", - "label": "Upload HTML", - "permlevel": 0, - "print_hide": 1, - "read_only": 1 - }, - { - "depends_on": "reconciliation_json", - "fieldname": "sb2", - "fieldtype": "Section Break", - "label": "Reconciliation Data", + "depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)", + "fieldname": "expense_account", + "fieldtype": "Link", + "label": "Difference Account", + "options": "Account", "permlevel": 0 }, { - "fieldname": "reconciliation_html", - "fieldtype": "HTML", - "hidden": 0, - "label": "Reconciliation HTML", - "permlevel": 0, - "print_hide": 0, - "read_only": 1 + "depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)", + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center", + "permlevel": 0 }, { "fieldname": "reconciliation_json", @@ -140,20 +107,55 @@ "permlevel": 0, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "difference_amount", + "fieldtype": "Currency", + "label": "Difference Amount", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "fieldname": "fold_15", + "fieldtype": "Fold", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "section_break_16", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "fiscal_year", + "fieldtype": "Link", + "label": "Fiscal Year", + "options": "Fiscal Year", + "permlevel": 0, + "print_hide": 1, + "reqd": 1 } ], "icon": "icon-upload-alt", "idx": 1, "is_submittable": 1, "max_attachments": 1, - "modified": "2015-02-17 02:09:17.483016", + "modified": "2015-02-20 04:39:46.585018", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation", "owner": "Administrator", "permissions": [ { - "amend": 0, + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index ef4403f5c2..14a54e9615 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -8,6 +8,7 @@ from frappe import msgprint, _ from frappe.utils import cstr, flt, cint from erpnext.stock.stock_ledger import update_entries_after from erpnext.controllers.stock_controller import StockController +from erpnext.stock.utils import get_stock_balance class StockReconciliation(StockController): def __init__(self, arg1, arg2=None): @@ -15,6 +16,7 @@ class StockReconciliation(StockController): self.head_row = ["Item Code", "Warehouse", "Quantity", "Valuation Rate"] def validate(self): + self.remove_items_with_no_change() self.validate_data() self.validate_expense_account() @@ -26,6 +28,28 @@ class StockReconciliation(StockController): self.delete_and_repost_sle() self.make_gl_entries_on_cancel() + def remove_items_with_no_change(self): + """Remove items if qty or rate is not changed""" + self.difference_amount = 0.0 + def _changed(item): + qty, rate = get_stock_balance(item.item_code, item.warehouse, + self.posting_date, self.posting_time, with_valuation_rate=True) + if (item.qty==None or item.qty==qty) and (item.valuation_rate==None or item.valuation_rate==rate): + return False + else: + item.current_qty = qty + item.current_valuation_rate = rate + self.difference_amount += ((item.qty or qty) * (item.valuation_rate or rate) - (qty * rate)) + return True + + items = filter(lambda d: _changed(d), self.items) + + if len(items) != len(self.items): + self.items = items + for i, item in enumerate(self.items): + item.idx = i + 1 + frappe.msgprint(_("Removed items with no change in quantity or value.")) + def validate_data(self): def _get_msg(row_num, msg): return _("Row # {0}: ").format(row_num+1) + msg @@ -38,6 +62,7 @@ class StockReconciliation(StockController): # validate no of rows if len(self.items) > 100: frappe.throw(_("""Max 100 rows for Stock Reconciliation.""")) + for row_num, row in enumerate(self.items): # find duplicates if [row.item_code, row.warehouse] in item_warehouse_combinations: @@ -88,8 +113,6 @@ class StockReconciliation(StockController): try: item = frappe.get_doc("Item", item_code) - if not item: - raise frappe.ValidationError, (_("Item: {0} not found in the system").format(item_code)) # end of life and stock item validate_end_of_life(item_code, item.end_of_life, verbose=0) @@ -194,14 +217,16 @@ class StockReconciliation(StockController): frappe.throw(_("Difference Account must be a 'Liability' type account, since this Stock Reconciliation is an Opening Entry")) @frappe.whitelist() -def get_items(warehouse): - from erpnext.stock.utils import get_stock_balance +def get_items(warehouse, posting_date, posting_time): 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 + item.qty, item.valuation_rate = get_stock_balance(item.name, warehouse, + posting_date, posting_time, with_valuation_rate=True) + item.current_qty = item.qty + item.current_valuation_rate = item.valuation_rate 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/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index aa55031312..c94b940762 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -48,6 +48,12 @@ "search_index": 0, "set_only_once": 0 }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, { "allow_on_submit": 0, "description": "Leave blank if no change", @@ -87,6 +93,30 @@ "reqd": 0, "search_index": 0, "set_only_once": 0 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "description": "Before reconciliation", + "fieldname": "current_qty", + "fieldtype": "Float", + "label": "Current Qty", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "description": "Before reconciliation", + "fieldname": "current_valuation_rate", + "fieldtype": "Read Only", + "label": "Current Valuation Rate", + "permlevel": 0, + "precision": "", + "read_only": 1 } ], "hide_heading": 0, @@ -96,7 +126,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-02-17 01:07:50.200649", + "modified": "2015-02-20 04:20:46.692967", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 9a337353df..4b1567c94b 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -38,18 +38,22 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None """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)""" + + from erpnext.stock.stock_ledger import get_previous_sle + if not posting_date: posting_date = nowdate() if not posting_time: posting_time = nowtime() - 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)) + + last_entry = get_previous_sle({ + "item_code": item_code, + "warehouse":warehouse, + "posting_date": posting_date, + "posting_time": posting_time }) if with_valuation_rate: - return (last_entry[0][0], last_entry[0][1]) if last_entry else (0.0, 0.0) + return (last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0) else: - return last_entry[0][0] if last_entry else 0.0 + return last_entry.qty_after_transaction or 0.0 def get_latest_stock_balance(): bin_map = {}