feat: get_valuation_rate batch wise

This function is used to show valuation rate on frontend and also as
fallback in case values aren't available. Add "batch_no" param to get
batch specific valuation rates.

Co-Authored-By: Alan Tom <2.alan.tom@gmail.com>
This commit is contained in:
Ankush Menat 2022-02-19 14:28:51 +05:30 committed by Ankush Menat
parent ce0514c8db
commit 342d09a671
9 changed files with 79 additions and 15 deletions

View File

@ -249,6 +249,7 @@ class BuyingController(StockController, Subcontracting):
"posting_time": self.get('posting_time'),
"qty": -1 * flt(d.get('stock_qty')),
"serial_no": d.get('serial_no'),
"batch_no": d.get("batch_no"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,

View File

@ -420,6 +420,7 @@ def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None
"posting_time": sle.get('posting_time'),
"qty": sle.actual_qty,
"serial_no": sle.get('serial_no'),
"batch_no": sle.get("batch_no"),
"company": sle.company,
"voucher_type": sle.voucher_type,
"voucher_no": sle.voucher_no

View File

@ -394,6 +394,7 @@ class SellingController(StockController):
"posting_time": self.get('posting_time') or nowtime(),
"qty": qty if cint(self.get("is_return")) else (-1 * qty),
"serial_no": d.get('serial_no'),
"batch_no": d.get("batch_no"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,

View File

@ -719,6 +719,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
'posting_time': posting_time,
'qty': item.qty * item.conversion_factor,
'serial_no': item.serial_no,
'batch_no': item.batch_no,
'voucher_type': voucher_type,
'company': company,
'allow_zero_valuation_rate': item.allow_zero_valuation_rate

View File

@ -8,7 +8,11 @@ from frappe.utils import cint, flt
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError, get_batch_no, get_batch_qty
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
)
from erpnext.stock.get_item_details import get_item_details
from erpnext.stock.stock_ledger import get_valuation_rate
from erpnext.tests.utils import ERPNextTestCase
@ -345,6 +349,41 @@ class TestBatch(ERPNextTestCase):
self.assertEqual(sle.stock_queue, []) # queues don't apply on batched items
def test_moving_batch_valuation_rates(self):
item_code = "_TestBatchWiseVal"
warehouse = "_Test Warehouse - _TC"
self.make_batch_item(item_code)
def assertValuation(expected):
actual = get_valuation_rate(item_code, warehouse, "voucher_type", "voucher_no", batch_no=batch_no)
self.assertAlmostEqual(actual, expected)
se = make_stock_entry(item_code=item_code, qty=100, rate=10, target=warehouse)
batch_no = se.items[0].batch_no
assertValuation(10)
# consumption should never affect current valuation rate
make_stock_entry(item_code=item_code, qty=20, source=warehouse)
assertValuation(10)
make_stock_entry(item_code=item_code, qty=30, source=warehouse)
assertValuation(10)
# 50 * 10 = 500 current value, add more item with higher valuation
make_stock_entry(item_code=item_code, qty=50, rate=20, target=warehouse, batch_no=batch_no)
assertValuation(15)
# consuming again shouldn't do anything
make_stock_entry(item_code=item_code, qty=20, source=warehouse)
assertValuation(15)
# reset rate with stock reconiliation
create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=10, rate=25, batch_no=batch_no)
assertValuation(25)
make_stock_entry(item_code=item_code, qty=20, rate=20, target=warehouse, batch_no=batch_no)
assertValuation((20 * 20 + 10 * 25) / (10 + 20))
def create_batch(item_code, rate, create_item_price_for_batch):
pi = make_purchase_invoice(company="_Test Company",

View File

@ -425,6 +425,7 @@ frappe.ui.form.on('Stock Entry', {
'posting_time' : frm.doc.posting_time,
'warehouse' : cstr(item.s_warehouse) || cstr(item.t_warehouse),
'serial_no' : item.serial_no,
'batch_no' : item.batch_no,
'company' : frm.doc.company,
'qty' : item.s_warehouse ? -1*flt(item.transfer_qty) : flt(item.transfer_qty),
'voucher_type' : frm.doc.doctype,
@ -457,6 +458,7 @@ frappe.ui.form.on('Stock Entry', {
'warehouse': cstr(child.s_warehouse) || cstr(child.t_warehouse),
'transfer_qty': child.transfer_qty,
'serial_no': child.serial_no,
'batch_no': child.batch_no,
'qty': child.s_warehouse ? -1* child.transfer_qty : child.transfer_qty,
'posting_date': frm.doc.posting_date,
'posting_time': frm.doc.posting_time,
@ -680,6 +682,7 @@ frappe.ui.form.on('Stock Entry Detail', {
'warehouse' : cstr(d.s_warehouse) || cstr(d.t_warehouse),
'transfer_qty' : d.transfer_qty,
'serial_no' : d.serial_no,
'batch_no' : d.batch_no,
'bom_no' : d.bom_no,
'expense_account' : d.expense_account,
'cost_center' : d.cost_center,

View File

@ -510,7 +510,7 @@ class StockEntry(StockController):
d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse,
self.doctype, self.name, d.allow_zero_valuation_rate,
currency=erpnext.get_company_currency(self.company), company=self.company,
raise_error_if_no_rate=raise_error_if_no_rate)
raise_error_if_no_rate=raise_error_if_no_rate, batch_no=d.batch_no)
d.basic_rate = flt(d.basic_rate, d.precision("basic_rate"))
if d.is_process_loss:
@ -541,6 +541,7 @@ class StockEntry(StockController):
"posting_time": self.posting_time,
"qty": item.s_warehouse and -1*flt(item.transfer_qty) or flt(item.transfer_qty),
"serial_no": item.serial_no,
"batch_no": item.batch_no,
"voucher_type": self.doctype,
"voucher_no": self.name,
"company": self.company,

View File

@ -634,7 +634,7 @@ class update_entries_after(object):
if not allow_zero_rate:
self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
currency=erpnext.get_company_currency(sle.company), company=sle.company)
currency=erpnext.get_company_currency(sle.company), company=sle.company, batch_no=sle.batch_no)
def get_incoming_value_for_serial_nos(self, sle, serial_nos):
# get rate from serial nos within same company
@ -702,7 +702,7 @@ class update_entries_after(object):
if not allow_zero_valuation_rate:
self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
currency=erpnext.get_company_currency(sle.company), company=sle.company)
currency=erpnext.get_company_currency(sle.company), company=sle.company, batch_no=sle.batch_no)
def update_queue_values(self, sle):
incoming_rate = flt(sle.incoming_rate)
@ -722,7 +722,7 @@ class update_entries_after(object):
if not allow_zero_valuation_rate:
return get_valuation_rate(sle.item_code, sle.warehouse,
sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
currency=erpnext.get_company_currency(sle.company), company=sle.company)
currency=erpnext.get_company_currency(sle.company), company=sle.company, batch_no=sle.batch_no)
else:
return 0.0
@ -950,21 +950,38 @@ def _get_batch_outgoing_rate(item_code, warehouse, batch_no, posting_date, posti
def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True):
allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True, batch_no=None):
if not company:
company = frappe.get_cached_value("Warehouse", warehouse, "company")
last_valuation_rate = None
# Get moving average rate of a specific batch number
if warehouse and batch_no and frappe.db.get_value("Batch", batch_no, "use_batchwise_valuation"):
last_valuation_rate = frappe.db.sql("""
select sum(stock_value_difference) / sum(actual_qty)
from `tabStock Ledger Entry`
where
item_code = %s
AND warehouse = %s
AND batch_no = %s
AND is_cancelled = 0
AND NOT (voucher_no = %s AND voucher_type = %s)
""",
(item_code, warehouse, batch_no, voucher_no, voucher_type))
# Get valuation rate from last sle for the same item and warehouse
last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry` force index (item_warehouse)
where
item_code = %s
AND warehouse = %s
AND valuation_rate >= 0
AND is_cancelled = 0
AND NOT (voucher_no = %s AND voucher_type = %s)
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type))
if not last_valuation_rate or last_valuation_rate[0][0] is None:
last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry` force index (item_warehouse)
where
item_code = %s
AND warehouse = %s
AND valuation_rate >= 0
AND is_cancelled = 0
AND NOT (voucher_no = %s AND voucher_type = %s)
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type))
if not last_valuation_rate:
# Get valuation rate from last sle for the item against any warehouse

View File

@ -231,7 +231,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
in_rate = get_valuation_rate(args.get('item_code'), args.get('warehouse'),
args.get('voucher_type'), voucher_no, args.get('allow_zero_valuation'),
currency=erpnext.get_company_currency(args.get('company')), company=args.get('company'),
raise_error_if_no_rate=raise_error_if_no_rate)
raise_error_if_no_rate=raise_error_if_no_rate, batch_no=args.get("batch_no"))
return flt(in_rate)