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:
parent
ce0514c8db
commit
342d09a671
@ -249,6 +249,7 @@ class BuyingController(StockController, Subcontracting):
|
|||||||
"posting_time": self.get('posting_time'),
|
"posting_time": self.get('posting_time'),
|
||||||
"qty": -1 * flt(d.get('stock_qty')),
|
"qty": -1 * flt(d.get('stock_qty')),
|
||||||
"serial_no": d.get('serial_no'),
|
"serial_no": d.get('serial_no'),
|
||||||
|
"batch_no": d.get("batch_no"),
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
|
|||||||
@ -420,6 +420,7 @@ def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None
|
|||||||
"posting_time": sle.get('posting_time'),
|
"posting_time": sle.get('posting_time'),
|
||||||
"qty": sle.actual_qty,
|
"qty": sle.actual_qty,
|
||||||
"serial_no": sle.get('serial_no'),
|
"serial_no": sle.get('serial_no'),
|
||||||
|
"batch_no": sle.get("batch_no"),
|
||||||
"company": sle.company,
|
"company": sle.company,
|
||||||
"voucher_type": sle.voucher_type,
|
"voucher_type": sle.voucher_type,
|
||||||
"voucher_no": sle.voucher_no
|
"voucher_no": sle.voucher_no
|
||||||
|
|||||||
@ -394,6 +394,7 @@ class SellingController(StockController):
|
|||||||
"posting_time": self.get('posting_time') or nowtime(),
|
"posting_time": self.get('posting_time') or nowtime(),
|
||||||
"qty": qty if cint(self.get("is_return")) else (-1 * qty),
|
"qty": qty if cint(self.get("is_return")) else (-1 * qty),
|
||||||
"serial_no": d.get('serial_no'),
|
"serial_no": d.get('serial_no'),
|
||||||
|
"batch_no": d.get("batch_no"),
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
|
|||||||
@ -719,6 +719,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
'posting_time': posting_time,
|
'posting_time': posting_time,
|
||||||
'qty': item.qty * item.conversion_factor,
|
'qty': item.qty * item.conversion_factor,
|
||||||
'serial_no': item.serial_no,
|
'serial_no': item.serial_no,
|
||||||
|
'batch_no': item.batch_no,
|
||||||
'voucher_type': voucher_type,
|
'voucher_type': voucher_type,
|
||||||
'company': company,
|
'company': company,
|
||||||
'allow_zero_valuation_rate': item.allow_zero_valuation_rate
|
'allow_zero_valuation_rate': item.allow_zero_valuation_rate
|
||||||
|
|||||||
@ -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.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.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_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.get_item_details import get_item_details
|
||||||
|
from erpnext.stock.stock_ledger import get_valuation_rate
|
||||||
from erpnext.tests.utils import ERPNextTestCase
|
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
|
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):
|
def create_batch(item_code, rate, create_item_price_for_batch):
|
||||||
pi = make_purchase_invoice(company="_Test Company",
|
pi = make_purchase_invoice(company="_Test Company",
|
||||||
|
|||||||
@ -425,6 +425,7 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
'posting_time' : frm.doc.posting_time,
|
'posting_time' : frm.doc.posting_time,
|
||||||
'warehouse' : cstr(item.s_warehouse) || cstr(item.t_warehouse),
|
'warehouse' : cstr(item.s_warehouse) || cstr(item.t_warehouse),
|
||||||
'serial_no' : item.serial_no,
|
'serial_no' : item.serial_no,
|
||||||
|
'batch_no' : item.batch_no,
|
||||||
'company' : frm.doc.company,
|
'company' : frm.doc.company,
|
||||||
'qty' : item.s_warehouse ? -1*flt(item.transfer_qty) : flt(item.transfer_qty),
|
'qty' : item.s_warehouse ? -1*flt(item.transfer_qty) : flt(item.transfer_qty),
|
||||||
'voucher_type' : frm.doc.doctype,
|
'voucher_type' : frm.doc.doctype,
|
||||||
@ -457,6 +458,7 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
'warehouse': cstr(child.s_warehouse) || cstr(child.t_warehouse),
|
'warehouse': cstr(child.s_warehouse) || cstr(child.t_warehouse),
|
||||||
'transfer_qty': child.transfer_qty,
|
'transfer_qty': child.transfer_qty,
|
||||||
'serial_no': child.serial_no,
|
'serial_no': child.serial_no,
|
||||||
|
'batch_no': child.batch_no,
|
||||||
'qty': child.s_warehouse ? -1* child.transfer_qty : child.transfer_qty,
|
'qty': child.s_warehouse ? -1* child.transfer_qty : child.transfer_qty,
|
||||||
'posting_date': frm.doc.posting_date,
|
'posting_date': frm.doc.posting_date,
|
||||||
'posting_time': frm.doc.posting_time,
|
'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),
|
'warehouse' : cstr(d.s_warehouse) || cstr(d.t_warehouse),
|
||||||
'transfer_qty' : d.transfer_qty,
|
'transfer_qty' : d.transfer_qty,
|
||||||
'serial_no' : d.serial_no,
|
'serial_no' : d.serial_no,
|
||||||
|
'batch_no' : d.batch_no,
|
||||||
'bom_no' : d.bom_no,
|
'bom_no' : d.bom_no,
|
||||||
'expense_account' : d.expense_account,
|
'expense_account' : d.expense_account,
|
||||||
'cost_center' : d.cost_center,
|
'cost_center' : d.cost_center,
|
||||||
|
|||||||
@ -510,7 +510,7 @@ class StockEntry(StockController):
|
|||||||
d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse,
|
d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse,
|
||||||
self.doctype, self.name, d.allow_zero_valuation_rate,
|
self.doctype, self.name, d.allow_zero_valuation_rate,
|
||||||
currency=erpnext.get_company_currency(self.company), company=self.company,
|
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"))
|
d.basic_rate = flt(d.basic_rate, d.precision("basic_rate"))
|
||||||
if d.is_process_loss:
|
if d.is_process_loss:
|
||||||
@ -541,6 +541,7 @@ class StockEntry(StockController):
|
|||||||
"posting_time": self.posting_time,
|
"posting_time": self.posting_time,
|
||||||
"qty": item.s_warehouse and -1*flt(item.transfer_qty) or flt(item.transfer_qty),
|
"qty": item.s_warehouse and -1*flt(item.transfer_qty) or flt(item.transfer_qty),
|
||||||
"serial_no": item.serial_no,
|
"serial_no": item.serial_no,
|
||||||
|
"batch_no": item.batch_no,
|
||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
|
|||||||
@ -634,7 +634,7 @@ class update_entries_after(object):
|
|||||||
if not allow_zero_rate:
|
if not allow_zero_rate:
|
||||||
self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
|
self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
|
||||||
sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
|
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):
|
def get_incoming_value_for_serial_nos(self, sle, serial_nos):
|
||||||
# get rate from serial nos within same company
|
# get rate from serial nos within same company
|
||||||
@ -702,7 +702,7 @@ class update_entries_after(object):
|
|||||||
if not allow_zero_valuation_rate:
|
if not allow_zero_valuation_rate:
|
||||||
self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
|
self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
|
||||||
sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
|
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):
|
def update_queue_values(self, sle):
|
||||||
incoming_rate = flt(sle.incoming_rate)
|
incoming_rate = flt(sle.incoming_rate)
|
||||||
@ -722,7 +722,7 @@ class update_entries_after(object):
|
|||||||
if not allow_zero_valuation_rate:
|
if not allow_zero_valuation_rate:
|
||||||
return get_valuation_rate(sle.item_code, sle.warehouse,
|
return get_valuation_rate(sle.item_code, sle.warehouse,
|
||||||
sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
|
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:
|
else:
|
||||||
return 0.0
|
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,
|
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:
|
if not company:
|
||||||
company = frappe.get_cached_value("Warehouse", warehouse, "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
|
# Get valuation rate from last sle for the same item and warehouse
|
||||||
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
if not last_valuation_rate or last_valuation_rate[0][0] is None:
|
||||||
from `tabStock Ledger Entry` force index (item_warehouse)
|
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
||||||
where
|
from `tabStock Ledger Entry` force index (item_warehouse)
|
||||||
item_code = %s
|
where
|
||||||
AND warehouse = %s
|
item_code = %s
|
||||||
AND valuation_rate >= 0
|
AND warehouse = %s
|
||||||
AND is_cancelled = 0
|
AND valuation_rate >= 0
|
||||||
AND NOT (voucher_no = %s AND voucher_type = %s)
|
AND is_cancelled = 0
|
||||||
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type))
|
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:
|
if not last_valuation_rate:
|
||||||
# Get valuation rate from last sle for the item against any warehouse
|
# Get valuation rate from last sle for the item against any warehouse
|
||||||
|
|||||||
@ -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'),
|
in_rate = get_valuation_rate(args.get('item_code'), args.get('warehouse'),
|
||||||
args.get('voucher_type'), voucher_no, args.get('allow_zero_valuation'),
|
args.get('voucher_type'), voucher_no, args.get('allow_zero_valuation'),
|
||||||
currency=erpnext.get_company_currency(args.get('company')), company=args.get('company'),
|
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)
|
return flt(in_rate)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user