2015-03-03 09:25:30 +00:00
# Copyright (c) 2015, Frappe 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
2017-03-31 07:14:29 +00:00
import frappe , erpnext
2014-04-14 13:50:45 +00:00
from frappe import _
2020-04-30 05:08:58 +00:00
from frappe . utils import cint , flt , cstr , now , now_datetime
from erpnext . stock . utils import get_valuation_method , get_incoming_outgoing_rate_for_cancel
2013-01-09 09:53:05 +00:00
import json
2013-01-08 12:59:24 +00:00
2018-02-14 11:38:59 +00:00
from six import iteritems
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
2020-04-30 05:08:58 +00:00
def make_sl_entries ( sl_entries , allow_negative_stock = False , via_landed_cost_voucher = False ) :
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
2020-04-30 05:08:58 +00:00
cancel = sl_entries [ 0 ] . get ( " is_cancelled " )
2013-09-26 10:46:44 +00:00
if cancel :
2020-04-30 05:08:58 +00:00
set_as_cancel ( sl_entries [ 0 ] . get ( ' voucher_type ' ) , sl_entries [ 0 ] . get ( ' voucher_no ' ) )
2014-04-07 06:32:57 +00:00
2013-09-26 10:46:44 +00:00
for sle in sl_entries :
sle_id = None
2020-04-30 05:08:58 +00:00
if via_landed_cost_voucher or cancel :
sle [ ' posting_date ' ] = now_datetime ( ) . strftime ( ' % Y- % m- %d ' )
sle [ ' posting_time ' ] = now_datetime ( ) . strftime ( ' % H: % M: % S. %f ' )
if cancel :
sle [ ' actual_qty ' ] = - flt ( sle . get ( ' actual_qty ' ) , 0 )
if sle [ ' actual_qty ' ] < 0 and not sle . get ( ' outgoing_rate ' ) :
sle [ ' outgoing_rate ' ] = get_incoming_outgoing_rate_for_cancel ( sle . item_code ,
sle . voucher_type , sle . voucher_no , sle . voucher_detail_no )
sle [ ' incoming_rate ' ] = 0.0
if sle [ ' actual_qty ' ] > 0 and not sle . get ( ' incoming_rate ' ) :
sle [ ' incoming_rate ' ] = get_incoming_outgoing_rate_for_cancel ( sle . item_code ,
sle . voucher_type , sle . voucher_no , sle . voucher_detail_no )
sle [ ' outgoing_rate ' ] = 0.0
2014-04-07 06:32:57 +00:00
2014-11-03 09:38:21 +00:00
if sle . get ( " actual_qty " ) or sle . get ( " voucher_type " ) == " Stock Reconciliation " :
2015-03-27 10:08:31 +00:00
sle_id = make_entry ( sle , allow_negative_stock , via_landed_cost_voucher )
2014-04-07 06:32:57 +00:00
2013-09-26 10:46:44 +00:00
args = sle . copy ( )
args . update ( {
2020-04-30 05:08:58 +00:00
" sle_id " : sle_id
2013-09-26 10:46:44 +00:00
} )
2015-03-27 10:08:31 +00:00
update_bin ( args , allow_negative_stock , via_landed_cost_voucher )
2014-10-06 06:23:52 +00:00
2014-04-07 06:32:57 +00:00
2013-08-20 10:07:33 +00:00
def set_as_cancel ( voucher_type , voucher_no ) :
2020-04-30 05:08:58 +00:00
frappe . db . sql ( """ update `tabStock Ledger Entry` set is_cancelled=1,
2013-08-20 10:07:33 +00:00
modified = % s , modified_by = % s
2020-04-30 05:08:58 +00:00
where voucher_type = % s and voucher_no = % s and is_cancelled = 0 """ ,
2014-02-14 10:17:51 +00:00
( now ( ) , frappe . session . user , voucher_type , voucher_no ) )
2014-04-07 06:32:57 +00:00
2015-03-27 10:08:31 +00:00
def make_entry ( args , allow_negative_stock = False , via_landed_cost_voucher = False ) :
2013-08-19 10:47:18 +00:00
args . update ( { " doctype " : " Stock Ledger Entry " } )
2014-04-04 06:46:26 +00:00
sle = frappe . get_doc ( args )
2015-02-10 09:11:27 +00:00
sle . flags . ignore_permissions = 1
2015-01-23 06:48:01 +00:00
sle . allow_negative_stock = allow_negative_stock
2015-03-27 10:08:31 +00:00
sle . via_landed_cost_voucher = via_landed_cost_voucher
2013-08-19 10:47:18 +00:00
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-19 10:47:18 +00:00
2015-02-17 14:25:17 +00:00
class update_entries_after ( object ) :
2013-01-08 12:59:24 +00:00
"""
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
2015-02-17 14:25:17 +00:00
: param args : args as dict
args = {
" item_code " : " ABC " ,
" warehouse " : " XYZ " ,
" posting_date " : " 2012-12-12 " ,
" posting_time " : " 12:00 "
}
2013-01-08 12:59:24 +00:00
"""
2015-04-06 07:29:34 +00:00
def __init__ ( self , args , allow_zero_rate = False , allow_negative_stock = None , via_landed_cost_voucher = False , verbose = 1 ) :
2015-02-17 14:25:17 +00:00
from frappe . model . meta import get_field_precision
2014-04-07 06:32:57 +00:00
2015-02-17 14:25:17 +00:00
self . exceptions = [ ]
self . verbose = verbose
self . allow_zero_rate = allow_zero_rate
self . allow_negative_stock = allow_negative_stock
2015-04-06 07:29:34 +00:00
self . via_landed_cost_voucher = via_landed_cost_voucher
2015-03-03 08:37:07 +00:00
if not self . allow_negative_stock :
2015-02-17 14:25:17 +00:00
self . allow_negative_stock = cint ( frappe . db . get_single_value ( " Stock Settings " ,
" allow_negative_stock " ) )
2014-04-07 06:32:57 +00:00
2015-02-17 14:25:17 +00:00
self . args = args
2018-02-14 11:38:59 +00:00
for key , value in iteritems ( args ) :
2015-02-17 14:25:17 +00:00
setattr ( self , key , value )
2014-04-07 06:32:57 +00:00
2015-02-17 14:25:17 +00:00
self . previous_sle = self . get_sle_before_datetime ( )
self . previous_sle = self . previous_sle [ 0 ] if self . previous_sle else frappe . _dict ( )
2013-08-29 12:49:37 +00:00
2015-02-17 14:25:17 +00:00
for key in ( " qty_after_transaction " , " valuation_rate " , " stock_value " ) :
setattr ( self , key , flt ( self . previous_sle . get ( key ) ) )
2013-08-29 12:49:37 +00:00
2015-02-17 14:25:17 +00:00
self . company = frappe . db . get_value ( " Warehouse " , self . warehouse , " company " )
self . precision = get_field_precision ( frappe . get_meta ( " Stock Ledger Entry " ) . get_field ( " stock_value " ) ,
2018-08-08 11:07:31 +00:00
currency = frappe . get_cached_value ( ' Company ' , self . company , " default_currency " ) )
2014-10-07 05:55:04 +00:00
2015-02-19 10:58:35 +00:00
self . prev_stock_value = self . previous_sle . stock_value or 0.0
2015-02-17 14:25:17 +00:00
self . stock_queue = json . loads ( self . previous_sle . stock_queue or " [] " )
self . valuation_method = get_valuation_method ( self . item_code )
self . stock_value_difference = 0.0
2020-04-30 05:08:58 +00:00
self . build ( args . get ( ' sle_id ' ) )
2014-10-07 05:55:04 +00:00
2020-04-30 05:08:58 +00:00
def build ( self , sle_id ) :
if sle_id :
sle = get_sle_by_id ( sle_id )
2015-02-17 14:25:17 +00:00
self . process_sle ( sle )
2020-04-30 05:08:58 +00:00
else :
# includes current entry!
entries_to_fix = self . get_sle_after_datetime ( )
for sle in entries_to_fix :
self . process_sle ( sle )
2015-02-17 14:25:17 +00:00
if self . exceptions :
self . raise_exceptions ( )
self . update_bin ( )
def update_bin ( self ) :
# update bin
bin_name = frappe . db . get_value ( " Bin " , {
" item_code " : self . item_code ,
" warehouse " : self . warehouse
} )
if not bin_name :
bin_doc = frappe . get_doc ( {
" doctype " : " Bin " ,
" item_code " : self . item_code ,
" warehouse " : self . warehouse
} )
bin_doc . insert ( ignore_permissions = True )
2013-01-08 12:59:24 +00:00
else :
2015-02-17 14:25:17 +00:00
bin_doc = frappe . get_doc ( " Bin " , bin_name )
2014-04-07 06:32:57 +00:00
2015-02-17 14:25:17 +00:00
bin_doc . update ( {
" valuation_rate " : self . valuation_rate ,
" actual_qty " : self . qty_after_transaction ,
" stock_value " : self . stock_value
} )
2015-12-28 07:55:35 +00:00
bin_doc . flags . via_stock_ledger_entry = True
2016-06-12 05:33:00 +00:00
2015-02-17 14:25:17 +00:00
bin_doc . save ( ignore_permissions = True )
2014-10-09 13:55:03 +00:00
2015-02-17 14:25:17 +00:00
def process_sle ( self , sle ) :
2015-04-06 07:29:34 +00:00
if ( sle . serial_no and not self . via_landed_cost_voucher ) or not cint ( self . allow_negative_stock ) :
2015-02-17 14:25:17 +00:00
# validate negative stock for serialized items, fifo valuation
# or when negative stock is not allowed for moving average
if not self . validate_negative_stock ( sle ) :
self . qty_after_transaction + = flt ( sle . actual_qty )
return
2014-04-07 06:32:57 +00:00
2013-01-10 13:59:51 +00:00
if sle . serial_no :
2015-02-25 09:38:42 +00:00
self . get_serialized_values ( sle )
2015-02-17 14:25:17 +00:00
self . qty_after_transaction + = flt ( sle . actual_qty )
2019-04-28 13:09:18 +00:00
if sle . voucher_type == " Stock Reconciliation " :
self . qty_after_transaction = sle . qty_after_transaction
2015-02-18 14:52:59 +00:00
self . stock_value = flt ( self . qty_after_transaction ) * flt ( self . valuation_rate )
2013-01-08 12:59:24 +00:00
else :
2019-05-24 11:23:51 +00:00
if sle . voucher_type == " Stock Reconciliation " and not sle . batch_no :
2015-02-17 14:25:17 +00:00
# assert
self . valuation_rate = sle . valuation_rate
self . qty_after_transaction = sle . qty_after_transaction
self . stock_queue = [ [ self . qty_after_transaction , self . valuation_rate ] ]
2015-02-18 14:52:59 +00:00
self . stock_value = flt ( self . qty_after_transaction ) * flt ( self . valuation_rate )
2015-02-17 14:25:17 +00:00
else :
if self . valuation_method == " Moving Average " :
self . get_moving_average_values ( sle )
self . qty_after_transaction + = flt ( sle . actual_qty )
2015-02-18 14:52:59 +00:00
self . stock_value = flt ( self . qty_after_transaction ) * flt ( self . valuation_rate )
2015-02-17 14:25:17 +00:00
else :
self . get_fifo_values ( sle )
self . qty_after_transaction + = flt ( sle . actual_qty )
self . stock_value = sum ( ( flt ( batch [ 0 ] ) * flt ( batch [ 1 ] ) for batch in self . stock_queue ) )
2014-04-07 06:32:57 +00:00
2015-02-17 14:25:17 +00:00
# rounding as per precision
self . stock_value = flt ( self . stock_value , self . precision )
2014-04-07 06:32:57 +00:00
2019-09-05 06:48:33 +00:00
stock_value_difference = self . stock_value - self . prev_stock_value
2018-11-21 17:48:41 +00:00
2015-02-17 14:25:17 +00:00
self . prev_stock_value = self . stock_value
2014-04-07 06:32:57 +00:00
2013-01-08 12:59:24 +00:00
# update current sle
2015-02-18 06:08:05 +00:00
sle . qty_after_transaction = self . qty_after_transaction
sle . valuation_rate = self . valuation_rate
2015-02-19 14:35:45 +00:00
sle . stock_value = self . stock_value
2015-02-18 06:08:05 +00:00
sle . stock_queue = json . dumps ( self . stock_queue )
sle . stock_value_difference = stock_value_difference
2015-02-18 14:52:59 +00:00
sle . doctype = " Stock Ledger Entry "
frappe . get_doc ( sle ) . db_update ( )
2015-02-17 14:25:17 +00:00
def validate_negative_stock ( self , sle ) :
"""
validate negative stock for entries current datetime onwards
will not consider cancelled entries
"""
diff = self . qty_after_transaction + flt ( sle . actual_qty )
if diff < 0 and abs ( diff ) > 0.0001 :
# negative stock!
exc = sle . copy ( ) . update ( { " diff " : diff } )
self . exceptions . append ( exc )
return False
2013-01-08 12:59:24 +00:00
else :
2015-02-17 14:25:17 +00:00
return True
def get_serialized_values ( self , sle ) :
incoming_rate = flt ( sle . incoming_rate )
actual_qty = flt ( sle . actual_qty )
2020-01-02 13:30:32 +00:00
serial_nos = cstr ( sle . serial_no ) . split ( " \n " )
2015-02-17 14:25:17 +00:00
if incoming_rate < 0 :
# wrong incoming rate
incoming_rate = self . valuation_rate
2016-06-12 05:33:00 +00:00
2016-02-29 06:00:27 +00:00
stock_value_change = 0
if incoming_rate :
stock_value_change = actual_qty * incoming_rate
elif actual_qty < 0 :
# In case of delivery/stock issue, get average purchase rate
# of serial nos of current entry
2020-01-02 13:30:32 +00:00
outgoing_value = self . get_incoming_value_for_serial_nos ( sle , serial_nos )
stock_value_change = - 1 * outgoing_value
2016-02-29 06:00:27 +00:00
new_stock_qty = self . qty_after_transaction + actual_qty
2018-07-27 05:03:30 +00:00
2016-02-29 06:00:27 +00:00
if new_stock_qty > 0 :
new_stock_value = ( self . qty_after_transaction * self . valuation_rate ) + stock_value_change
2018-07-27 05:03:30 +00:00
if new_stock_value > = 0 :
2016-02-29 06:00:27 +00:00
# calculate new valuation rate only if stock value is positive
# else it remains the same as that of previous entry
self . valuation_rate = new_stock_value / new_stock_qty
2016-07-08 12:54:46 +00:00
2017-12-01 10:39:02 +00:00
if not self . valuation_rate and sle . voucher_detail_no :
allow_zero_rate = self . check_if_allow_zero_valuation_rate ( sle . voucher_type , sle . voucher_detail_no )
if not allow_zero_rate :
self . 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 ) )
2020-01-02 13:30:32 +00:00
def get_incoming_value_for_serial_nos ( self , sle , serial_nos ) :
# get rate from serial nos within same company
all_serial_nos = frappe . get_all ( " Serial No " ,
fields = [ " purchase_rate " , " name " , " company " ] ,
filters = { ' name ' : ( ' in ' , serial_nos ) } )
incoming_values = sum ( [ flt ( d . purchase_rate ) for d in all_serial_nos if d . company == sle . company ] )
# Get rate for serial nos which has been transferred to other company
invalid_serial_nos = [ d . name for d in all_serial_nos if d . company != sle . company ]
for serial_no in invalid_serial_nos :
incoming_rate = frappe . db . sql ( """
select incoming_rate
from ` tabStock Ledger Entry `
where
company = % s
and actual_qty > 0
and ( serial_no = % s
or serial_no like % s
or serial_no like % s
or serial_no like % s
)
order by posting_date desc
limit 1
""" , (sle.company, serial_no, serial_no+ ' \n % ' , ' % \n ' +serial_no, ' % \n ' +serial_no+ ' \n % ' ))
incoming_values + = flt ( incoming_rate [ 0 ] [ 0 ] ) if incoming_rate else 0
return incoming_values
2015-02-17 14:25:17 +00:00
def get_moving_average_values ( self , sle ) :
actual_qty = flt ( sle . actual_qty )
2016-06-24 06:58:55 +00:00
new_stock_qty = flt ( self . qty_after_transaction ) + actual_qty
if new_stock_qty > = 0 :
if actual_qty > 0 :
if flt ( self . qty_after_transaction ) < = 0 :
self . valuation_rate = sle . incoming_rate
else :
new_stock_value = ( self . qty_after_transaction * self . valuation_rate ) + \
( actual_qty * sle . incoming_rate )
2015-10-15 06:58:20 +00:00
2016-06-24 06:58:55 +00:00
self . valuation_rate = new_stock_value / new_stock_qty
2015-02-17 14:25:17 +00:00
2016-06-24 06:58:55 +00:00
elif sle . outgoing_rate :
if new_stock_qty :
new_stock_value = ( self . qty_after_transaction * self . valuation_rate ) + \
( actual_qty * sle . outgoing_rate )
2015-02-17 14:25:17 +00:00
2016-06-24 06:58:55 +00:00
self . valuation_rate = new_stock_value / new_stock_qty
else :
2016-07-27 11:14:17 +00:00
self . valuation_rate = sle . outgoing_rate
2015-10-15 06:58:20 +00:00
2016-06-24 06:58:55 +00:00
else :
if flt ( self . qty_after_transaction ) > = 0 and sle . outgoing_rate :
self . valuation_rate = sle . outgoing_rate
2015-02-17 14:25:17 +00:00
2016-06-24 06:58:55 +00:00
if not self . valuation_rate and actual_qty > 0 :
self . valuation_rate = sle . incoming_rate
2017-03-31 07:14:29 +00:00
2017-05-04 04:05:19 +00:00
# Get valuation rate from previous SLE or Item master, if item does not have the
2017-04-14 10:24:04 +00:00
# allow zero valuration rate flag set
2017-02-06 11:43:39 +00:00
if not self . valuation_rate and sle . voucher_detail_no :
2017-04-14 10:24:04 +00:00
allow_zero_valuation_rate = self . check_if_allow_zero_valuation_rate ( sle . voucher_type , sle . voucher_detail_no )
if not allow_zero_valuation_rate :
2017-03-31 07:14:29 +00:00
self . 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 ) )
2015-02-17 14:25:17 +00:00
def get_fifo_values ( self , sle ) :
incoming_rate = flt ( sle . incoming_rate )
actual_qty = flt ( sle . actual_qty )
2015-07-17 09:39:56 +00:00
outgoing_rate = flt ( sle . outgoing_rate )
2015-02-17 14:25:17 +00:00
if actual_qty > 0 :
if not self . stock_queue :
self . stock_queue . append ( [ 0 , 0 ] )
2015-02-19 14:35:45 +00:00
# last row has the same rate, just updated the qty
if self . stock_queue [ - 1 ] [ 1 ] == incoming_rate :
self . stock_queue [ - 1 ] [ 0 ] + = actual_qty
2013-01-08 12:59:24 +00:00
else :
2015-02-19 14:35:45 +00:00
if self . stock_queue [ - 1 ] [ 0 ] > 0 :
self . stock_queue . append ( [ actual_qty , incoming_rate ] )
2015-02-17 14:25:17 +00:00
else :
2015-02-19 14:35:45 +00:00
qty = self . stock_queue [ - 1 ] [ 0 ] + actual_qty
2016-06-24 06:58:55 +00:00
self . stock_queue [ - 1 ] = [ qty , incoming_rate ]
2015-02-17 14:25:17 +00:00
else :
qty_to_pop = abs ( actual_qty )
while qty_to_pop :
if not self . stock_queue :
2017-01-18 13:05:58 +00:00
# Get valuation rate from last sle if exists or from valuation rate field in item master
2017-04-14 10:24:04 +00:00
allow_zero_valuation_rate = self . check_if_allow_zero_valuation_rate ( sle . voucher_type , sle . voucher_detail_no )
if not allow_zero_valuation_rate :
2017-03-31 07:14:29 +00:00
_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 ) )
2017-02-06 19:53:26 +00:00
else :
_rate = 0
2017-03-31 07:14:29 +00:00
2015-02-17 14:25:17 +00:00
self . stock_queue . append ( [ 0 , _rate ] )
2015-07-17 09:39:56 +00:00
index = None
if outgoing_rate > 0 :
# Find the entry where rate matched with outgoing rate
for i , v in enumerate ( self . stock_queue ) :
if v [ 1 ] == outgoing_rate :
index = i
break
2015-10-15 06:58:20 +00:00
2015-07-17 09:39:56 +00:00
# If no entry found with outgoing rate, collapse stack
if index == None :
new_stock_value = sum ( ( d [ 0 ] * d [ 1 ] for d in self . stock_queue ) ) - qty_to_pop * outgoing_rate
new_stock_qty = sum ( ( d [ 0 ] for d in self . stock_queue ) ) - qty_to_pop
self . stock_queue = [ [ new_stock_qty , new_stock_value / new_stock_qty if new_stock_qty > 0 else outgoing_rate ] ]
break
else :
index = 0
2016-07-08 12:54:46 +00:00
2015-07-17 09:39:56 +00:00
# select first batch or the batch with same rate
batch = self . stock_queue [ index ]
2015-08-05 13:27:26 +00:00
if qty_to_pop > = batch [ 0 ] :
# consume current batch
qty_to_pop = qty_to_pop - batch [ 0 ]
self . stock_queue . pop ( index )
if not self . stock_queue and qty_to_pop :
# stock finished, qty still remains to be withdrawn
# negative stock, keep in as a negative batch
self . stock_queue . append ( [ - qty_to_pop , outgoing_rate or batch [ 1 ] ] )
break
2015-02-17 14:25:17 +00:00
2015-08-05 13:27:26 +00:00
else :
# qty found in current batch
# consume it and exit
batch [ 0 ] = batch [ 0 ] - qty_to_pop
qty_to_pop = 0
2015-02-17 14:25:17 +00:00
stock_value = sum ( ( flt ( batch [ 0 ] ) * flt ( batch [ 1 ] ) for batch in self . stock_queue ) )
stock_qty = sum ( ( flt ( batch [ 0 ] ) for batch in self . stock_queue ) )
2016-06-24 06:58:55 +00:00
if stock_qty :
self . valuation_rate = stock_value / flt ( stock_qty )
2016-07-08 12:54:46 +00:00
2016-06-24 06:58:55 +00:00
if not self . stock_queue :
self . stock_queue . append ( [ 0 , sle . incoming_rate or sle . outgoing_rate or self . valuation_rate ] )
2017-03-31 07:14:29 +00:00
2017-04-14 10:24:04 +00:00
def check_if_allow_zero_valuation_rate ( self , voucher_type , voucher_detail_no ) :
2019-07-30 13:19:19 +00:00
ref_item_dt = " "
if voucher_type == " Stock Entry " :
ref_item_dt = voucher_type + " Detail "
elif voucher_type in [ " Purchase Invoice " , " Sales Invoice " , " Delivery Note " , " Purchase Receipt " ] :
ref_item_dt = voucher_type + " Item "
if ref_item_dt :
return frappe . db . get_value ( ref_item_dt , voucher_detail_no , " allow_zero_valuation_rate " )
else :
return 0
2017-03-31 07:14:29 +00:00
2015-02-17 14:25:17 +00:00
def get_sle_before_datetime ( self ) :
""" get previous stock ledger entry before current time-bucket """
2020-04-30 05:08:58 +00:00
if self . args . get ( ' sle_id ' ) :
self . args [ ' name ' ] = self . args . get ( ' sle_id ' )
return get_stock_ledger_entries ( self . args , " <= " , " desc " , " limit 1 " , for_update = False )
2015-02-17 14:25:17 +00:00
def get_sle_after_datetime ( self ) :
""" get Stock Ledger Entries after a particular datetime, for reposting """
2015-02-19 14:35:45 +00:00
return get_stock_ledger_entries ( self . previous_sle or frappe . _dict ( {
" item_code " : self . args . get ( " item_code " ) , " warehouse " : self . args . get ( " warehouse " ) } ) ,
2019-05-24 11:23:51 +00:00
" > " , " asc " , for_update = True , check_serial_no = False )
2015-02-17 14:25:17 +00:00
def raise_exceptions ( self ) :
deficiency = min ( e [ " diff " ] for e in self . exceptions )
2016-06-12 05:33:00 +00:00
2016-07-20 10:00:17 +00:00
if ( ( self . exceptions [ 0 ] [ " voucher_type " ] , self . exceptions [ 0 ] [ " voucher_no " ] ) in
frappe . local . flags . currently_saving ) :
2016-07-20 10:43:18 +00:00
2016-06-12 05:33:00 +00:00
msg = _ ( " {0} units of {1} needed in {2} to complete this transaction. " ) . format (
abs ( deficiency ) , frappe . get_desk_link ( ' Item ' , self . item_code ) ,
frappe . get_desk_link ( ' Warehouse ' , self . warehouse ) )
else :
msg = _ ( " {0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction. " ) . format (
abs ( deficiency ) , frappe . get_desk_link ( ' Item ' , self . item_code ) ,
frappe . get_desk_link ( ' Warehouse ' , self . warehouse ) ,
self . exceptions [ 0 ] [ " posting_date " ] , self . exceptions [ 0 ] [ " posting_time " ] ,
frappe . get_desk_link ( self . exceptions [ 0 ] [ " voucher_type " ] , self . exceptions [ 0 ] [ " voucher_no " ] ) )
2015-02-17 14:25:17 +00:00
if self . verbose :
2020-04-01 05:34:14 +00:00
frappe . throw ( msg , NegativeStockError , title = ' Insufficient Stock ' )
2015-02-17 14:25:17 +00:00
else :
2017-07-27 05:08:35 +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 "
}
"""
2015-02-17 14:25:17 +00:00
args [ " name " ] = args . get ( " sle " , None ) or " "
sle = get_stock_ledger_entries ( args , " <= " , " 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
2019-05-24 11:23:51 +00:00
def get_stock_ledger_entries ( previous_sle , operator = None ,
order = " desc " , limit = None , for_update = False , debug = False , check_serial_no = True ) :
2015-02-17 14:25:17 +00:00
""" get stock ledger entries filtered by specific posting datetime conditions """
2018-02-01 09:28:50 +00:00
conditions = " and timestamp(posting_date, posting_time) {0} timestamp( %(posting_date)s , %(posting_time)s ) " . format ( operator )
if previous_sle . get ( " warehouse " ) :
conditions + = " and warehouse = %(warehouse)s "
elif previous_sle . get ( " warehouse_condition " ) :
conditions + = " and " + previous_sle . get ( " warehouse_condition " )
2019-05-24 11:23:51 +00:00
if check_serial_no and previous_sle . get ( " serial_no " ) :
2019-04-28 13:09:18 +00:00
conditions + = " and serial_no like {} " . format ( frappe . db . escape ( ' % {0} % ' . format ( previous_sle . get ( " serial_no " ) ) ) )
2015-02-17 14:25:17 +00:00
if not previous_sle . get ( " posting_date " ) :
previous_sle [ " posting_date " ] = " 1900-01-01 "
if not previous_sle . get ( " posting_time " ) :
previous_sle [ " posting_time " ] = " 00:00 "
if operator in ( " > " , " <= " ) and previous_sle . get ( " name " ) :
conditions + = " and name!= %(name)s "
2020-04-30 05:08:58 +00:00
return frappe . db . sql ( """
select * , timestamp ( posting_date , posting_time ) as " timestamp "
from ` tabStock Ledger Entry `
2015-02-17 14:25:17 +00:00
where item_code = % % ( item_code ) s
2018-02-01 09:28:50 +00:00
% ( conditions ) s
2019-01-07 16:37:13 +00:00
order by timestamp ( posting_date , posting_time ) % ( order ) s , creation % ( order ) s
2015-02-17 14:25:17 +00:00
% ( limit ) s % ( for_update ) s """ % {
" conditions " : conditions ,
" limit " : limit or " " ,
" for_update " : for_update and " for update " or " " ,
" order " : order
2015-02-19 14:35:45 +00:00
} , previous_sle , as_dict = 1 , debug = debug )
2015-02-17 14:25:17 +00:00
2020-04-30 05:08:58 +00:00
def get_sle_by_id ( sle_id ) :
return frappe . db . get_all ( ' Stock Ledger Entry ' ,
fields = [ ' * ' , ' timestamp(posting_date, posting_time) as timestamp ' ] ,
filters = { ' name ' : sle_id } ) [ 0 ]
2017-03-31 07:14:29 +00:00
def get_valuation_rate ( item_code , warehouse , voucher_type , voucher_no ,
2018-02-01 05:21:27 +00:00
allow_zero_rate = False , currency = None , company = None , raise_error_if_no_rate = True ) :
2017-01-18 13:05:58 +00:00
# Get valuation rate from last sle for the same item and warehouse
2017-06-16 09:51:36 +00:00
if not company :
company = erpnext . get_default_company ( )
2014-10-15 06:04:40 +00:00
last_valuation_rate = frappe . db . sql ( """ select valuation_rate
from ` tabStock Ledger Entry `
2019-08-19 04:34:52 +00:00
where
item_code = % s
AND warehouse = % s
AND valuation_rate > = 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))
2014-10-15 06:04:40 +00:00
if not last_valuation_rate :
2017-01-18 13:05:58 +00:00
# Get valuation rate from last sle for the item against any warehouse
2014-10-15 06:04:40 +00:00
last_valuation_rate = frappe . db . sql ( """ select valuation_rate
from ` tabStock Ledger Entry `
2019-08-19 04:34:52 +00:00
where
item_code = % s
AND valuation_rate > 0
AND NOT ( voucher_no = % s AND voucher_type = % s )
order by posting_date desc , posting_time desc , name desc limit 1 """ , (item_code, voucher_no, voucher_type))
2014-10-15 06:04:40 +00:00
2018-03-01 05:01:24 +00:00
if last_valuation_rate :
return flt ( last_valuation_rate [ 0 ] [ 0 ] ) # as there is previous records, it might come with zero rate
# If negative stock allowed, and item delivered without any incoming entry,
# system does not found any SLE, then take valuation rate from Item
valuation_rate = frappe . db . get_value ( " Item " , item_code , " valuation_rate " )
2014-10-15 06:04:40 +00:00
if not valuation_rate :
2018-03-01 05:01:24 +00:00
# try Item Standard rate
valuation_rate = frappe . db . get_value ( " Item " , item_code , " standard_rate " )
2017-01-18 13:05:58 +00:00
2017-05-04 04:05:19 +00:00
if not valuation_rate :
2018-03-01 05:01:24 +00:00
# try in price list
valuation_rate = frappe . db . get_value ( ' Item Price ' ,
dict ( item_code = item_code , buying = 1 , currency = currency ) ,
' price_list_rate ' )
2017-03-31 07:14:29 +00:00
2018-02-01 05:21:27 +00:00
if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \
2017-06-19 07:24:59 +00:00
and cint ( erpnext . is_perpetual_inventory_enabled ( company ) ) :
2017-03-28 12:09:34 +00:00
frappe . local . message_log = [ ]
2019-07-22 11:30:32 +00:00
frappe . throw ( _ ( " Valuation rate not found for the Item {0} , which is required to do accounting entries for {1} {2} . If the item is transacting as a zero valuation rate item in the {1} , please mention that in the {1} Item table. Otherwise, please create an incoming stock transaction for the item or mention valuation rate in the Item record, and then try submiting / cancelling this entry. " )
. format ( item_code , voucher_type , voucher_no ) )
2014-10-15 06:04:40 +00:00
return valuation_rate