2013-11-20 07:29:58 +00:00
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
2013-08-05 09:29:54 +00:00
# License: GNU General Public License v3. See license.txt
2013-11-29 13:45:26 +00:00
from __future__ import unicode_literals
2013-01-08 12:59:24 +00:00
2014-02-14 10:17:51 +00:00
import frappe
2014-04-14 13:50:45 +00:00
from frappe import _
2014-02-14 10:17:51 +00:00
from frappe . utils import cint , flt , cstr , now
2013-12-12 13:42:19 +00:00
from erpnext . stock . utils import get_valuation_method
2013-01-09 09:53:05 +00:00
import json
2013-01-08 12:59:24 +00:00
# future reposting
2014-02-14 10:17:51 +00:00
class NegativeStockError ( frappe . ValidationError ) : pass
2013-01-08 12:59:24 +00:00
2014-02-14 10:17:51 +00:00
_exceptions = frappe . local ( ' stockledger_exceptions ' )
2013-09-18 13:01:03 +00:00
# _exceptions = []
2013-09-25 14:25:41 +00:00
2013-08-19 10:47:18 +00:00
def make_sl_entries ( sl_entries , is_amended = None ) :
2013-09-26 10:46:44 +00:00
if sl_entries :
2013-12-12 13:42:19 +00:00
from erpnext . stock . utils import update_bin
2014-04-07 06:32:57 +00:00
2013-09-26 10:46:44 +00:00
cancel = True if sl_entries [ 0 ] . get ( " is_cancelled " ) == " Yes " else False
if cancel :
set_as_cancel ( sl_entries [ 0 ] . get ( ' voucher_no ' ) , sl_entries [ 0 ] . get ( ' voucher_type ' ) )
2014-04-07 06:32:57 +00:00
2013-09-26 10:46:44 +00:00
for sle in sl_entries :
sle_id = None
if sle . get ( ' is_cancelled ' ) == ' Yes ' :
sle [ ' actual_qty ' ] = - flt ( sle [ ' actual_qty ' ] )
2014-04-07 06:32:57 +00:00
2014-10-07 06:47:31 +00:00
if sle . get ( " actual_qty " ) or sle . voucher_type == " Stock Reconciliation " :
2013-09-26 10:46:44 +00:00
sle_id = make_entry ( sle )
2014-04-07 06:32:57 +00:00
2013-09-26 10:46:44 +00:00
args = sle . copy ( )
args . update ( {
" sle_id " : sle_id ,
" is_amended " : is_amended
} )
update_bin ( args )
2014-10-06 06:23:52 +00:00
2013-09-26 10:46:44 +00:00
if cancel :
2014-10-06 06:23:52 +00:00
delete_cancelled_entry ( sl_entries [ 0 ] . get ( ' voucher_type ' ) , sl_entries [ 0 ] . get ( ' voucher_no ' ) )
2014-04-07 06:32:57 +00:00
2013-08-20 10:07:33 +00:00
def set_as_cancel ( voucher_type , voucher_no ) :
2014-02-26 07:05:33 +00:00
frappe . db . sql ( """ update `tabStock Ledger Entry` set is_cancelled= ' Yes ' ,
2013-08-20 10:07:33 +00:00
modified = % s , modified_by = % s
2014-04-07 06:32:57 +00:00
where voucher_no = % s and voucher_type = % s """ ,
2014-02-14 10:17:51 +00:00
( now ( ) , frappe . session . user , voucher_type , voucher_no ) )
2014-04-07 06:32:57 +00:00
2013-08-19 10:47:18 +00:00
def make_entry ( args ) :
args . update ( { " doctype " : " Stock Ledger Entry " } )
2014-04-04 06:46:26 +00:00
sle = frappe . get_doc ( args )
2013-08-19 10:47:18 +00:00
sle . ignore_permissions = 1
sle . insert ( )
2013-08-23 09:47:36 +00:00
sle . submit ( )
2014-03-28 08:25:00 +00:00
return sle . name
2014-04-07 06:32:57 +00:00
2013-08-20 10:07:33 +00:00
def delete_cancelled_entry ( voucher_type , voucher_no ) :
2014-04-07 06:32:57 +00:00
frappe . db . sql ( """ delete from `tabStock Ledger Entry`
2013-08-20 10:07:33 +00:00
where voucher_type = % s and voucher_no = % s """ , (voucher_type, voucher_no))
2013-08-19 10:47:18 +00:00
2013-01-08 12:59:24 +00:00
def update_entries_after ( args , verbose = 1 ) :
"""
2014-04-07 06:32:57 +00:00
update valution rate and qty after transaction
2013-01-08 12:59:24 +00:00
from the current time - bucket onwards
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
args = {
" item_code " : " ABC " ,
" warehouse " : " XYZ " ,
" posting_date " : " 2012-12-12 " ,
" posting_time " : " 12:00 "
}
"""
2013-09-18 13:01:03 +00:00
if not _exceptions :
2014-02-14 10:17:51 +00:00
frappe . local . stockledger_exceptions = [ ]
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
previous_sle = get_sle_before_datetime ( args )
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
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 " [] " )
2013-08-26 11:23:30 +00:00
stock_value = flt ( previous_sle . get ( " stock_value " ) )
prev_stock_value = flt ( previous_sle . get ( " stock_value " ) )
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
entries_to_fix = get_sle_after_datetime ( previous_sle or \
2013-01-11 06:14:49 +00:00
{ " item_code " : args [ " item_code " ] , " warehouse " : args [ " warehouse " ] } , for_update = True )
2013-01-08 12:59:24 +00:00
valuation_method = get_valuation_method ( args [ " item_code " ] )
2013-08-23 09:47:36 +00:00
stock_value_difference = 0.0
2013-08-29 12:49:37 +00:00
2013-01-08 12:59:24 +00:00
for sle in entries_to_fix :
2014-02-26 07:05:33 +00:00
if sle . serial_no or not cint ( frappe . db . get_default ( " allow_negative_stock " ) ) :
2014-04-07 06:32:57 +00:00
# validate negative stock for serialized items, fifo valuation
2013-01-08 12:59:24 +00:00
# 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
2013-08-29 12:49:37 +00:00
2014-10-07 05:55:04 +00:00
2013-01-10 13:59:51 +00:00
if sle . serial_no :
2013-01-17 11:31:51 +00:00
valuation_rate = get_serialized_values ( qty_after_transaction , sle , valuation_rate )
2014-10-07 05:55:04 +00:00
qty_after_transaction + = flt ( sle . actual_qty )
2013-01-08 12:59:24 +00:00
else :
2014-10-07 05:55:04 +00:00
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 " :
2014-10-10 12:33:27 +00:00
valuation_rate = get_moving_average_values ( qty_after_transaction , sle , valuation_rate )
2014-10-07 05:55:04 +00:00
else :
valuation_rate = get_fifo_values ( qty_after_transaction , sle , stock_queue )
2014-04-07 06:32:57 +00:00
2014-10-09 13:55:03 +00:00
2014-10-07 05:55:04 +00:00
qty_after_transaction + = flt ( sle . actual_qty )
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
# get stock value
2013-01-10 13:59:51 +00:00
if sle . serial_no :
2013-01-08 12:59:24 +00:00
stock_value = qty_after_transaction * valuation_rate
elif valuation_method == " Moving Average " :
2014-10-09 13:55:03 +00:00
stock_value = qty_after_transaction * valuation_rate
2013-01-08 12:59:24 +00:00
else :
stock_value = sum ( ( flt ( batch [ 0 ] ) * flt ( batch [ 1 ] ) for batch in stock_queue ) )
2014-04-07 06:32:57 +00:00
2013-12-23 06:44:45 +00:00
# rounding as per precision
2014-02-14 10:17:51 +00:00
from frappe . model . meta import get_field_precision
2014-03-27 12:21:41 +00:00
meta = frappe . get_meta ( " Stock Ledger Entry " )
2014-04-07 06:32:57 +00:00
stock_value = flt ( stock_value , get_field_precision ( meta . get_field ( " stock_value " ) ,
2014-02-14 10:17:51 +00:00
frappe . _dict ( { " fields " : sle } ) ) )
2014-04-07 06:32:57 +00:00
2013-08-23 09:47:36 +00:00
stock_value_difference = stock_value - prev_stock_value
prev_stock_value = stock_value
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
# update current sle
2014-02-26 07:05:33 +00:00
frappe . db . sql ( """ update `tabStock Ledger Entry`
2013-01-10 13:59:51 +00:00
set qty_after_transaction = % s , valuation_rate = % s , stock_queue = % s ,
2014-04-07 06:32:57 +00:00
stock_value = % s , stock_value_difference = % s where name = % s """ ,
2013-01-10 13:59:51 +00:00
( qty_after_transaction , valuation_rate ,
2013-08-23 09:47:36 +00:00
json . dumps ( stock_queue ) , stock_value , stock_value_difference , sle . name ) )
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
if _exceptions :
2013-01-10 05:10:37 +00:00
_raise_exceptions ( args , verbose )
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
# update bin
2014-04-07 06:32:57 +00:00
if not frappe . db . exists ( { " doctype " : " Bin " , " item_code " : args [ " item_code " ] ,
2013-01-11 13:55:46 +00:00
" warehouse " : args [ " warehouse " ] } ) :
2014-04-04 06:46:26 +00:00
bin_wrapper = frappe . get_doc ( {
2013-01-11 13:55:46 +00:00
" doctype " : " Bin " ,
" item_code " : args [ " item_code " ] ,
" warehouse " : args [ " warehouse " ] ,
2014-04-04 06:46:26 +00:00
} )
2013-01-14 10:16:17 +00:00
bin_wrapper . ignore_permissions = 1
bin_wrapper . insert ( )
2014-04-07 06:32:57 +00:00
2014-02-26 07:05:33 +00:00
frappe . db . sql ( """ update `tabBin` set valuation_rate= %s , actual_qty= %s ,
2014-04-07 06:32:57 +00:00
stock_value = % s ,
2013-01-08 12:59:24 +00:00
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 " ] ) )
2014-04-07 06:32:57 +00:00
2013-01-11 06:14:49 +00:00
def get_sle_before_datetime ( args , for_update = False ) :
2013-01-08 12:59:24 +00:00
"""
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 ,
2013-01-09 09:53:05 +00:00
[ " timestamp(posting_date, posting_time) < timestamp( %(posting_date)s , %(posting_time)s ) " ] ,
2013-01-11 06:14:49 +00:00
" desc " , " limit 1 " , for_update = for_update )
2014-04-07 06:32:57 +00:00
2014-02-14 10:17:51 +00:00
return sle and sle [ 0 ] or frappe . _dict ( )
2014-04-07 06:32:57 +00:00
2013-01-11 06:14:49 +00:00
def get_sle_after_datetime ( args , for_update = False ) :
2013-01-08 12:59:24 +00:00
""" get Stock Ledger Entries after a particular datetime, for reposting """
2014-04-07 06:32:57 +00:00
# NOTE: using for update of
2014-04-07 13:21:58 +00:00
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 )
2014-04-07 06:32:57 +00:00
2013-01-11 06:14:49 +00:00
def get_stock_ledger_entries ( args , conditions = None , order = " desc " , limit = None , for_update = False ) :
2013-01-08 12:59:24 +00:00
""" 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 " ) :
2013-03-13 07:27:04 +00:00
args [ " posting_time " ] = " 00:00 "
2014-04-07 06:32:57 +00:00
2014-04-07 13:21:58 +00:00
return frappe . db . sql ( """ select *, timestamp(posting_date, posting_time) as " timestamp " from `tabStock Ledger Entry`
2013-01-08 12:59:24 +00:00
where item_code = % % ( item_code ) s
and warehouse = % % ( warehouse ) s
2013-08-29 12:49:37 +00:00
and ifnull ( is_cancelled , ' No ' ) = ' No '
2013-01-08 12:59:24 +00:00
% ( conditions ) s
2013-01-10 05:10:37 +00:00
order by timestamp ( posting_date , posting_time ) % ( order ) s , name % ( order ) s
2013-01-11 06:14:49 +00:00
% ( limit ) s % ( for_update ) s """ % {
2013-01-08 12:59:24 +00:00
" conditions " : conditions and ( " and " + " and " . join ( conditions ) ) or " " ,
2013-01-10 05:10:37 +00:00
" limit " : limit or " " ,
2013-01-11 06:14:49 +00:00
" for_update " : for_update and " for update " or " " ,
2013-01-10 05:10:37 +00:00
" order " : order
2013-01-08 12:59:24 +00:00
} , args , as_dict = 1 )
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
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 )
2013-09-18 13:01:03 +00:00
if not _exceptions :
2014-02-14 10:17:51 +00:00
frappe . local . stockledger_exceptions = [ ]
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
if diff < 0 and abs ( diff ) > 0.0001 :
# negative stock!
exc = sle . copy ( ) . update ( { " diff " : diff } )
_exceptions . append ( exc )
return False
else :
return True
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
def get_serialized_values ( qty_after_transaction , sle , valuation_rate ) :
incoming_rate = flt ( sle . incoming_rate )
actual_qty = flt ( sle . actual_qty )
2013-01-10 13:59:51 +00:00
serial_no = cstr ( sle . serial_no ) . split ( " \n " )
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
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
2014-02-26 07:05:33 +00:00
incoming_rate = flt ( frappe . db . sql ( """ select avg(ifnull(purchase_rate, 0))
2013-01-10 13:59:51 +00:00
from ` tabSerial No ` where name in ( % s ) """ % ( " , " .join([ " %s " ]*len(serial_no))),
tuple ( serial_no ) ) [ 0 ] [ 0 ] )
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
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
2014-04-07 06:32:57 +00:00
2013-01-14 07:45:42 +00:00
return valuation_rate
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
def get_moving_average_values ( qty_after_transaction , sle , valuation_rate ) :
incoming_rate = flt ( sle . incoming_rate )
2014-04-07 06:32:57 +00:00
actual_qty = flt ( sle . actual_qty )
2014-10-10 12:33:27 +00:00
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
2014-04-07 06:32:57 +00:00
2014-10-10 12:33:27 +00:00
new_stock_qty = abs ( qty_after_transaction ) + actual_qty
new_stock_value = ( abs ( qty_after_transaction ) * valuation_rate ) + ( actual_qty * incoming_rate )
2014-04-07 06:32:57 +00:00
2014-10-10 12:33:27 +00:00
if new_stock_qty :
valuation_rate = new_stock_value / flt ( new_stock_qty )
2014-10-15 06:04:40 +00:00
elif not valuation_rate and qty_after_transaction < = 0 :
2014-10-10 12:33:27 +00:00
valuation_rate = get_valuation_rate ( sle . item_code , sle . warehouse )
2014-04-07 06:32:57 +00:00
2014-10-14 10:39:14 +00:00
return abs ( flt ( valuation_rate ) )
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
def get_fifo_values ( qty_after_transaction , sle , stock_queue ) :
incoming_rate = flt ( sle . incoming_rate )
actual_qty = flt ( sle . actual_qty )
2014-10-09 13:55:03 +00:00
2013-01-08 12:59:24 +00:00
if actual_qty > 0 :
2014-10-15 06:04:40 +00:00
if not stock_queue :
stock_queue . append ( [ 0 , 0 ] )
2013-01-08 12:59:24 +00:00
if stock_queue [ - 1 ] [ 0 ] > 0 :
stock_queue . append ( [ actual_qty , incoming_rate ] )
else :
qty = stock_queue [ - 1 ] [ 0 ] + actual_qty
2014-10-09 13:55:03 +00:00
if qty == 0 :
stock_queue . pop ( - 1 )
else :
stock_queue [ - 1 ] = [ qty , incoming_rate ]
2013-01-08 12:59:24 +00:00
else :
qty_to_pop = abs ( actual_qty )
while qty_to_pop :
2014-10-15 06:04:40 +00:00
if not stock_queue :
stock_queue . append ( [ 0 , get_valuation_rate ( sle . item_code , sle . warehouse )
if qty_after_transaction < = 0 else 0 ] )
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
batch = stock_queue [ 0 ]
2014-04-07 06:32:57 +00:00
2014-10-09 13:55:03 +00:00
if qty_to_pop > = batch [ 0 ] :
# consume current batch
qty_to_pop = qty_to_pop - batch [ 0 ]
2013-01-08 12:59:24 +00:00
stock_queue . pop ( 0 )
2014-10-09 13:55:03 +00:00
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
2013-01-08 12:59:24 +00:00
else :
2014-10-09 13:55:03 +00:00
# qty found in current batch
# consume it and exit
batch [ 0 ] = batch [ 0 ] - qty_to_pop
2013-01-08 12:59:24 +00:00
qty_to_pop = 0
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
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 ) )
2014-10-09 13:55:03 +00:00
valuation_rate = ( stock_value / flt ( stock_qty ) ) if stock_qty else 0
2013-01-10 05:10:37 +00:00
2014-10-09 13:55:03 +00:00
return abs ( valuation_rate )
2013-01-10 05:10:37 +00:00
def _raise_exceptions ( args , verbose = 1 ) :
2013-01-08 12:59:24 +00:00
deficiency = min ( e [ " diff " ] for e in _exceptions )
2014-04-14 13:50:45 +00:00
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 )
2013-01-08 12:59:24 +00:00
if verbose :
2014-04-14 13:50:45 +00:00
frappe . throw ( msg , NegativeStockError )
2013-01-08 12:59:24 +00:00
else :
2013-08-12 08:48:09 +00:00
raise NegativeStockError , msg
2014-04-07 06:32:57 +00:00
2013-01-11 06:14:49 +00:00
def get_previous_sle ( args , for_update = False ) :
2013-01-10 13:59:51 +00:00
"""
2014-04-07 06:32:57 +00:00
get the last sle on or before the current time - bucket ,
2013-01-10 13:59:51 +00:00
to get actual qty before transaction , this function
is called from various transaction like stock entry , reco etc
2014-04-07 06:32:57 +00:00
2013-01-10 13:59:51 +00:00
args = {
" item_code " : " ABC " ,
" warehouse " : " XYZ " ,
" posting_date " : " 2012-12-12 " ,
" posting_time " : " 12:00 " ,
" sle " : " name of reference Stock Ledger Entry "
}
"""
if not args . get ( " sle " ) : args [ " sle " ] = " "
2014-04-07 06:32:57 +00:00
2013-01-10 13:59:51 +00:00
sle = get_stock_ledger_entries ( args , [ " name != %(sle)s " ,
" timestamp(posting_date, posting_time) <= timestamp( %(posting_date)s , %(posting_time)s ) " ] ,
2013-01-11 06:14:49 +00:00
" desc " , " limit 1 " , for_update = for_update )
2013-09-18 13:01:03 +00:00
return sle and sle [ 0 ] or { }
2014-10-15 06:04:40 +00:00
def get_valuation_rate ( item_code , warehouse ) :
last_valuation_rate = frappe . db . sql ( """ select valuation_rate
from ` tabStock Ledger Entry `
where item_code = % s and warehouse = % s
and ifnull ( valuation_rate , 0 ) > 0
order by posting_date desc , posting_time desc , name desc limit 1 """ , (item_code, warehouse))
if not last_valuation_rate :
last_valuation_rate = frappe . db . sql ( """ select valuation_rate
from ` tabStock Ledger Entry `
where item_code = % s and ifnull ( valuation_rate , 0 ) > 0
order by posting_date desc , posting_time desc , name desc limit 1 """ , item_code)
valuation_rate = flt ( last_valuation_rate [ 0 ] [ 0 ] ) if last_valuation_rate else 0
if not valuation_rate :
valuation_rate = frappe . db . get_value ( " Item Price " , { " item_code " : item_code , " buying " : 1 } , " price_list_rate " )
if not valuation_rate and cint ( frappe . db . get_value ( " Accounts Settings " , None , " auto_accounting_for_stock " ) ) :
frappe . throw ( _ ( " Purchase rate for item: {0} not found, which is required to book accounting entry (expense). Please mention item price against a buying price list. " ) . format ( item_code ) )
return valuation_rate