From b96c014daf77aea33afdd9cf4de16f69453902ee Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 7 Oct 2014 11:25:04 +0530 Subject: [PATCH] Stock Reconciliation logic simplified --- erpnext/public/js/stock_analytics.js | 14 +- erpnext/startup/report_data_map.py | 3 +- erpnext/stock/doctype/bin/bin.py | 33 ++--- .../stock_ledger_entry/stock_ledger_entry.py | 2 +- .../stock_reconciliation.py | 120 +++--------------- .../stock/page/stock_balance/stock_balance.js | 9 +- erpnext/stock/stock_ledger.py | 19 ++- 7 files changed, 75 insertions(+), 125 deletions(-) diff --git a/erpnext/public/js/stock_analytics.js b/erpnext/public/js/stock_analytics.js index d4f43e98b97..84c0386c575 100644 --- a/erpnext/public/js/stock_analytics.js +++ b/erpnext/public/js/stock_analytics.js @@ -138,9 +138,17 @@ erpnext.StockAnalytics = erpnext.StockGridReport.extend({ item.valuation_method : sys_defaults.valuation_method; var is_fifo = valuation_method == "FIFO"; - var diff = me.get_value_diff(wh, sl, is_fifo); + if(sl.voucher_type=="Stock Reconciliation") { + var diff = (sl.qty_after_transaction * sl.valuation_rate) - item.closing_qty_value; + } else { + var diff = me.get_value_diff(wh, sl, is_fifo); + } } else { - var diff = sl.qty; + if(sl.voucher_type=="Stock Reconciliation") { + var diff = sl.qty_after_transaction - item.closing_qty_value; + } else { + var diff = sl.qty; + } } if(posting_datetime < from_date) { @@ -150,6 +158,8 @@ erpnext.StockAnalytics = erpnext.StockGridReport.extend({ } else { break; } + + item.closing_qty_value += diff; } } }, diff --git a/erpnext/startup/report_data_map.py b/erpnext/startup/report_data_map.py index ce71310ad9e..81d378cd189 100644 --- a/erpnext/startup/report_data_map.py +++ b/erpnext/startup/report_data_map.py @@ -78,7 +78,8 @@ data_map = { "Stock Ledger Entry": { "columns": ["name", "posting_date", "posting_time", "item_code", "warehouse", "actual_qty as qty", "voucher_type", "voucher_no", "project", - "ifnull(incoming_rate,0) as incoming_rate", "stock_uom", "serial_no"], + "ifnull(incoming_rate,0) as incoming_rate", "stock_uom", "serial_no", + "qty_after_transaction", "valuation_rate"], "order_by": "posting_date, posting_time, name", "links": { "item_code": ["Item", "name"], diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 3f74c5c7d35..0244213a96d 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -11,27 +11,27 @@ class Bin(Document): def validate(self): if self.get("__islocal") or not self.stock_uom: self.stock_uom = frappe.db.get_value('Item', self.item_code, 'stock_uom') - + self.validate_mandatory() - + self.projected_qty = flt(self.actual_qty) + flt(self.ordered_qty) + \ flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty) - + def validate_mandatory(self): qf = ['actual_qty', 'reserved_qty', 'ordered_qty', 'indented_qty'] for f in qf: - if (not getattr(self, f, None)) or (not self.get(f)): + if (not getattr(self, f, None)) or (not self.get(f)): self.set(f, 0.0) - + def update_stock(self, args): self.update_qty(args) - + if args.get("actual_qty"): from erpnext.stock.stock_ledger import update_entries_after - + if not args.get("posting_date"): args["posting_date"] = nowdate() - + # update valuation and qty after transaction for post dated entry update_entries_after({ "item_code": self.item_code, @@ -39,21 +39,24 @@ class Bin(Document): "posting_date": args.get("posting_date"), "posting_time": args.get("posting_time") }) - + def update_qty(self, args): # update the stock values (for current quantities) - - self.actual_qty = flt(self.actual_qty) + flt(args.get("actual_qty")) + if args.get("voucher_type")=="Stock Reconciliation": + self.actual_qty = args.get("qty_after_transaction") + else: + self.actual_qty = flt(self.actual_qty) + flt(args.get("actual_qty")) + self.ordered_qty = flt(self.ordered_qty) + flt(args.get("ordered_qty")) self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty")) self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty")) self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty")) - + self.projected_qty = flt(self.actual_qty) + flt(self.ordered_qty) + \ flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty) - + self.save() - + def get_first_sle(self): sle = frappe.db.sql(""" select * from `tabStock Ledger Entry` @@ -62,4 +65,4 @@ class Bin(Document): order by timestamp(posting_date, posting_time) asc, name asc limit 1 """, (self.item_code, self.warehouse), as_dict=1) - return sle and sle[0] or None \ No newline at end of file + return sle and sle[0] or None diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 0e92b5d4710..1bb189bd744 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -44,7 +44,7 @@ class StockLedgerEntry(Document): formatdate(self.posting_date), self.posting_time)) def validate_mandatory(self): - mandatory = ['warehouse','posting_date','voucher_type','voucher_no','actual_qty','company'] + mandatory = ['warehouse','posting_date','voucher_type','voucher_no','company'] for k in mandatory: if not self.get(k): frappe.throw(_("{0} is required").format(self.meta.get_label(k))) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 40a980debb7..f39829de98b 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -129,7 +129,6 @@ class StockReconciliation(StockController): def insert_stock_ledger_entries(self): """ find difference between current and expected entries and create stock ledger entries based on the difference""" - from erpnext.stock.utils import get_valuation_method from erpnext.stock.stock_ledger import get_previous_sle row_template = ["item_code", "warehouse", "qty", "valuation_rate"] @@ -141,105 +140,27 @@ class StockReconciliation(StockController): 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 - previous_sle = get_previous_sle({ - "item_code": row.item_code, - "warehouse": row.warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time - }) - # check valuation rate mandatory - if row.qty not in ["", None] and not row.valuation_rate and \ - flt(previous_sle.get("qty_after_transaction")) <= 0: + if row.qty in ("", None) or row.valuation_rate in ("", None): + previous_sle = get_previous_sle({ + "item_code": row.item_code, + "warehouse": row.warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time + }) + + if row.qty in ("", None): + row.qty = previous_sle.get("qty_after_transaction") + + if row.valuation_rate in ("", None): + row.valuation_rate = previous_sle.get("valuation_rate") + + if row.qty and not row.valuation_rate: frappe.throw(_("Valuation Rate required for Item {0}").format(row.item_code)) - change_in_qty = row.qty not in ["", None] and \ - (flt(row.qty) - flt(previous_sle.get("qty_after_transaction"))) + self.insert_entries(row) - change_in_rate = row.valuation_rate not in ["", None] and \ - (flt(row.valuation_rate) - flt(previous_sle.get("valuation_rate"))) - - if get_valuation_method(row.item_code) == "Moving Average": - self.sle_for_moving_avg(row, previous_sle, change_in_qty, change_in_rate) - - else: - self.sle_for_fifo(row, previous_sle, change_in_qty, change_in_rate) - - def sle_for_moving_avg(self, row, previous_sle, change_in_qty, change_in_rate): - """Insert Stock Ledger Entries for Moving Average valuation""" - def _get_incoming_rate(qty, valuation_rate, previous_qty, previous_valuation_rate): - if previous_valuation_rate == 0: - return flt(valuation_rate) - else: - if valuation_rate in ["", None]: - valuation_rate = previous_valuation_rate - return (qty * valuation_rate - previous_qty * previous_valuation_rate) \ - / flt(qty - previous_qty) - - if change_in_qty: - # if change in qty, irrespective of change in rate - incoming_rate = _get_incoming_rate(flt(row.qty), flt(row.valuation_rate), - flt(previous_sle.get("qty_after_transaction")), flt(previous_sle.get("valuation_rate"))) - - row["voucher_detail_no"] = "Row: " + cstr(row.row_num) + "/Actual Entry" - self.insert_entries({"actual_qty": change_in_qty, "incoming_rate": incoming_rate}, row) - - elif change_in_rate and flt(previous_sle.get("qty_after_transaction")) > 0: - # if no change in qty, but change in rate - # and positive actual stock before this reconciliation - incoming_rate = _get_incoming_rate( - flt(previous_sle.get("qty_after_transaction"))+1, flt(row.valuation_rate), - flt(previous_sle.get("qty_after_transaction")), - flt(previous_sle.get("valuation_rate"))) - - # +1 entry - row["voucher_detail_no"] = "Row: " + cstr(row.row_num) + "/Valuation Adjustment +1" - self.insert_entries({"actual_qty": 1, "incoming_rate": incoming_rate}, row) - - # -1 entry - row["voucher_detail_no"] = "Row: " + cstr(row.row_num) + "/Valuation Adjustment -1" - self.insert_entries({"actual_qty": -1}, row) - - def sle_for_fifo(self, row, previous_sle, change_in_qty, change_in_rate): - """Insert Stock Ledger Entries for FIFO valuation""" - previous_stock_queue = json.loads(previous_sle.get("stock_queue") or "[]") - previous_stock_qty = sum((batch[0] for batch in previous_stock_queue)) - previous_stock_value = sum((batch[0] * batch[1] for batch in \ - previous_stock_queue)) - - def _insert_entries(): - if previous_stock_queue != [[row.qty, row.valuation_rate]]: - # make entry as per attachment - if flt(row.qty): - row["voucher_detail_no"] = "Row: " + cstr(row.row_num) + "/Actual Entry" - self.insert_entries({"actual_qty": row.qty, - "incoming_rate": flt(row.valuation_rate)}, row) - - # Make reverse entry - if previous_stock_qty: - row["voucher_detail_no"] = "Row: " + cstr(row.row_num) + "/Reverse Entry" - self.insert_entries({"actual_qty": -1 * previous_stock_qty, - "incoming_rate": previous_stock_qty < 0 and - flt(row.valuation_rate) or 0}, row) - - - if change_in_qty: - if row.valuation_rate in ["", None]: - # dont want change in valuation - if previous_stock_qty > 0: - # set valuation_rate as previous valuation_rate - row.valuation_rate = previous_stock_value / flt(previous_stock_qty) - - _insert_entries() - - elif change_in_rate and previous_stock_qty > 0: - # if no change in qty, but change in rate - # and positive actual stock before this reconciliation - - row.qty = previous_stock_qty - _insert_entries() - - def insert_entries(self, opts, row): + def insert_entries(self, row): """Insert Stock Ledger Entries""" args = frappe._dict({ "doctype": "Stock Ledger Entry", @@ -253,9 +174,10 @@ class StockReconciliation(StockController): "stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"), "voucher_detail_no": row.voucher_detail_no, "fiscal_year": self.fiscal_year, - "is_cancelled": "No" + "is_cancelled": "No", + "qty_after_transaction": row.qty, + "valuation_rate": row.valuation_rate }) - args.update(opts) self.make_sl_entries([args]) # append to entries @@ -295,7 +217,7 @@ class StockReconciliation(StockController): if not self.expense_account: msgprint(_("Please enter Expense Account"), raise_exception=1) - elif not frappe.db.sql("""select * from `tabStock Ledger Entry`"""): + elif not frappe.db.sql("""select name from `tabStock Ledger Entry` limit 1"""): if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss": frappe.throw(_("Difference Account must be a 'Liability' type account, since this Stock Reconciliation is an Opening Entry")) diff --git a/erpnext/stock/page/stock_balance/stock_balance.js b/erpnext/stock/page/stock_balance/stock_balance.js index 10834147251..c2ffc3701b2 100644 --- a/erpnext/stock/page/stock_balance/stock_balance.js +++ b/erpnext/stock/page/stock_balance/stock_balance.js @@ -104,8 +104,13 @@ erpnext.StockBalance = erpnext.StockAnalytics.extend({ item.valuation_method : sys_defaults.valuation_method; var is_fifo = valuation_method == "FIFO"; - var qty_diff = sl.qty; - var value_diff = me.get_value_diff(wh, sl, is_fifo); + if(sl.voucher_type=="Stock Reconciliation") { + var qty_diff = sl.qty_after_trasaction - item.closing_qty; + var value_diff = (sl.valuation_rate * sl.qty_after_trasaction) - item.closing_value; + } else { + var qty_diff = sl.qty; + var value_diff = me.get_value_diff(wh, sl, is_fifo); + } if(sl_posting_date < from_date) { item.opening_qty += qty_diff; diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 3717bf1595b..03bbf2f1386 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -95,14 +95,23 @@ def update_entries_after(args, verbose=1): qty_after_transaction += flt(sle.actual_qty) continue + if sle.serial_no: valuation_rate = get_serialized_values(qty_after_transaction, sle, valuation_rate) - elif valuation_method == "Moving Average": - valuation_rate = get_moving_average_values(qty_after_transaction, sle, valuation_rate) - else: - valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue) + qty_after_transaction += flt(sle.actual_qty) - qty_after_transaction += flt(sle.actual_qty) + 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]] + else: + if valuation_method == "Moving Average": + valuation_rate = get_moving_average_values(qty_after_transaction, sle, valuation_rate) + else: + valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue) + + qty_after_transaction += flt(sle.actual_qty) # get stock value if sle.serial_no: