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-03-19 06:31:24 +00:00
from __future__ import unicode_literals
2014-02-14 10:17:51 +00:00
import frappe
from frappe . utils import cint , flt , cstr
from frappe import msgprint , _
import frappe . defaults
2013-08-06 10:27:25 +00:00
2013-12-12 13:42:19 +00:00
from erpnext . controllers . accounts_controller import AccountsController
2014-10-06 06:23:52 +00:00
from erpnext . accounts . general_ledger import make_gl_entries , delete_gl_entries , process_gl_map
2013-03-19 06:31:24 +00:00
class StockController ( AccountsController ) :
2014-10-06 06:23:52 +00:00
def make_gl_entries ( self , repost_future_gle = True , allow_negative_stock = False ) :
2014-03-28 08:25:00 +00:00
if self . docstatus == 2 :
delete_gl_entries ( voucher_type = self . doctype , voucher_no = self . name )
2014-04-07 13:21:58 +00:00
2014-02-14 10:17:51 +00:00
if cint ( frappe . defaults . get_global_default ( " auto_accounting_for_stock " ) ) :
2014-09-26 08:52:18 +00:00
warehouse_account = get_warehouse_account ( )
2014-04-07 13:21:58 +00:00
2014-03-28 08:25:00 +00:00
if self . docstatus == 1 :
2014-10-08 05:30:38 +00:00
gl_entries = self . get_gl_entries ( warehouse_account , allow_negative_stock = allow_negative_stock )
2013-10-22 18:21:41 +00:00
make_gl_entries ( gl_entries )
2014-03-27 11:48:29 +00:00
if repost_future_gle :
2014-09-26 08:52:18 +00:00
items , warehouses = self . get_items_and_warehouses ( )
2014-10-06 06:23:52 +00:00
update_gl_entries_after ( self . posting_date , self . posting_time , warehouses , items ,
warehouse_account , allow_negative_stock )
2014-04-07 13:21:58 +00:00
2013-11-14 13:10:08 +00:00
def get_gl_entries ( self , warehouse_account = None , default_expense_account = None ,
2014-10-06 06:23:52 +00:00
default_cost_center = None , allow_negative_stock = False ) :
2014-10-14 10:37:56 +00:00
# block_negative_stock(allow_negative_stock)
2014-10-06 06:23:52 +00:00
2013-09-17 09:45:16 +00:00
if not warehouse_account :
2014-03-27 11:48:29 +00:00
warehouse_account = get_warehouse_account ( )
2014-04-07 13:21:58 +00:00
2014-04-17 06:07:46 +00:00
sle_map = self . get_stock_ledger_details ( )
voucher_details = self . get_voucher_details ( default_expense_account , default_cost_center , sle_map )
2014-04-07 13:21:58 +00:00
2013-08-28 13:23:11 +00:00
gl_list = [ ]
2013-09-17 04:51:20 +00:00
warehouse_with_no_account = [ ]
2013-08-28 13:23:11 +00:00
for detail in voucher_details :
2014-04-17 06:07:46 +00:00
sle_list = sle_map . get ( detail . name )
2013-08-28 13:23:11 +00:00
if sle_list :
for sle in sle_list :
if warehouse_account . get ( sle . warehouse ) :
# from warehouse account
2014-04-16 13:50:11 +00:00
2014-04-16 11:51:25 +00:00
self . check_expense_account ( detail )
2014-10-06 06:23:52 +00:00
stock_value_difference = flt ( sle . stock_value_difference , 2 )
if not stock_value_difference :
2014-10-08 12:36:14 +00:00
valuation_rate = get_valuation_rate ( sle . item_code , sle . warehouse )
2014-10-08 05:30:38 +00:00
stock_value_difference = flt ( sle . actual_qty ) * flt ( valuation_rate )
2014-10-06 06:23:52 +00:00
2013-08-28 13:23:11 +00:00
gl_list . append ( self . get_gl_dict ( {
" account " : warehouse_account [ sle . warehouse ] ,
" against " : detail . expense_account ,
" cost_center " : detail . cost_center ,
2014-04-07 06:32:57 +00:00
" remarks " : self . get ( " remarks " ) or " Accounting Entry for Stock " ,
2014-10-06 06:23:52 +00:00
" debit " : stock_value_difference
2013-08-28 13:23:11 +00:00
} ) )
# to target warehouse / expense account
gl_list . append ( self . get_gl_dict ( {
" account " : detail . expense_account ,
" against " : warehouse_account [ sle . warehouse ] ,
" cost_center " : detail . cost_center ,
2014-04-07 06:32:57 +00:00
" remarks " : self . get ( " remarks " ) or " Accounting Entry for Stock " ,
2014-10-06 06:23:52 +00:00
" credit " : stock_value_difference
2013-08-28 13:23:11 +00:00
} ) )
2013-09-17 04:51:20 +00:00
elif sle . warehouse not in warehouse_with_no_account :
warehouse_with_no_account . append ( sle . warehouse )
2014-04-07 13:21:58 +00:00
if warehouse_with_no_account :
2014-04-14 13:50:45 +00:00
msgprint ( _ ( " No accounting entries for the following warehouses " ) + " : \n " +
2013-09-17 04:51:20 +00:00
" \n " . join ( warehouse_with_no_account ) )
2014-04-07 13:21:58 +00:00
2013-08-28 13:23:11 +00:00
return process_gl_map ( gl_list )
2014-04-07 13:21:58 +00:00
2014-04-17 06:07:46 +00:00
def get_voucher_details ( self , default_expense_account , default_cost_center , sle_map ) :
if self . doctype == " Stock Reconciliation " :
return [ frappe . _dict ( { " name " : voucher_detail_no , " expense_account " : default_expense_account ,
" cost_center " : default_cost_center } ) for voucher_detail_no , sle in sle_map . items ( ) ]
else :
details = self . get ( self . fname )
if default_expense_account or default_cost_center :
for d in details :
if default_expense_account and not d . get ( " expense_account " ) :
d . expense_account = default_expense_account
if default_cost_center and not d . get ( " cost_center " ) :
d . cost_center = default_cost_center
return details
2014-04-07 13:21:58 +00:00
2014-09-26 08:52:18 +00:00
def get_items_and_warehouses ( self ) :
2014-03-27 11:48:29 +00:00
items , warehouses = [ ] , [ ]
2014-04-07 13:21:58 +00:00
2014-03-27 11:48:29 +00:00
if hasattr ( self , " fname " ) :
2014-04-02 12:39:34 +00:00
item_doclist = self . get ( self . fname )
2014-03-28 08:25:00 +00:00
elif self . doctype == " Stock Reconciliation " :
2014-03-27 11:48:29 +00:00
import json
item_doclist = [ ]
2014-03-28 08:25:00 +00:00
data = json . loads ( self . reconciliation_json )
2014-03-27 11:48:29 +00:00
for row in data [ data . index ( self . head_row ) + 1 : ] :
d = frappe . _dict ( zip ( [ " item_code " , " warehouse " , " qty " , " valuation_rate " ] , row ) )
item_doclist . append ( d )
2014-04-07 13:21:58 +00:00
2014-03-27 11:48:29 +00:00
if item_doclist :
for d in item_doclist :
if d . item_code and d . item_code not in items :
items . append ( d . item_code )
2014-04-07 13:21:58 +00:00
2014-04-07 06:32:57 +00:00
if d . get ( " warehouse " ) and d . warehouse not in warehouses :
2014-03-27 11:48:29 +00:00
warehouses . append ( d . warehouse )
2014-04-07 13:21:58 +00:00
2014-04-07 06:32:57 +00:00
if self . doctype == " Stock Entry " :
if d . get ( " s_warehouse " ) and d . s_warehouse not in warehouses :
warehouses . append ( d . s_warehouse )
if d . get ( " t_warehouse " ) and d . t_warehouse not in warehouses :
warehouses . append ( d . t_warehouse )
2014-03-27 11:48:29 +00:00
2014-09-26 08:52:18 +00:00
return items , warehouses
2014-04-07 13:21:58 +00:00
2013-08-28 13:23:11 +00:00
def get_stock_ledger_details ( self ) :
stock_ledger = { }
2014-10-08 05:30:38 +00:00
for sle in frappe . db . sql ( """ select warehouse, stock_value_difference,
voucher_detail_no , item_code , posting_date , actual_qty
2013-08-26 11:23:30 +00:00
from ` tabStock Ledger Entry ` where voucher_type = % s and voucher_no = % s """ ,
2014-03-28 08:25:00 +00:00
( self . doctype , self . name ) , as_dict = True ) :
2013-08-28 13:23:11 +00:00
stock_ledger . setdefault ( sle . voucher_detail_no , [ ] ) . append ( sle )
return stock_ledger
2014-04-07 13:21:58 +00:00
2013-08-28 13:23:11 +00:00
def make_adjustment_entry ( self , expected_gle , voucher_obj ) :
2013-12-12 13:42:19 +00:00
from erpnext . accounts . utils import get_stock_and_account_difference
2013-08-26 11:23:30 +00:00
account_list = [ d . account for d in expected_gle ]
acc_diff = get_stock_and_account_difference ( account_list , expected_gle [ 0 ] . posting_date )
2014-04-07 13:21:58 +00:00
2013-08-26 11:23:30 +00:00
cost_center = self . get_company_default ( " cost_center " )
stock_adjustment_account = self . get_company_default ( " stock_adjustment_account " )
2013-08-06 10:27:25 +00:00
gl_entries = [ ]
for account , diff in acc_diff . items ( ) :
if diff :
2013-08-26 11:23:30 +00:00
gl_entries . append ( [
# stock in hand account
2013-08-28 13:23:11 +00:00
voucher_obj . get_gl_dict ( {
2013-08-26 11:23:30 +00:00
" account " : account ,
" against " : stock_adjustment_account ,
" debit " : diff ,
" remarks " : " Adjustment Accounting Entry for Stock " ,
} ) ,
2014-04-07 13:21:58 +00:00
2013-08-26 11:23:30 +00:00
# account against stock in hand
2013-08-28 13:23:11 +00:00
voucher_obj . get_gl_dict ( {
2013-08-26 11:23:30 +00:00
" account " : stock_adjustment_account ,
" against " : account ,
" credit " : diff ,
" cost_center " : cost_center or None ,
" remarks " : " Adjustment Accounting Entry for Stock " ,
} ) ,
] )
2014-04-07 13:21:58 +00:00
2013-08-06 10:27:25 +00:00
if gl_entries :
2013-12-12 13:42:19 +00:00
from erpnext . accounts . general_ledger import make_gl_entries
2013-08-06 10:27:25 +00:00
make_gl_entries ( gl_entries )
2014-04-07 13:21:58 +00:00
2013-08-26 11:23:30 +00:00
def check_expense_account ( self , item ) :
2014-04-16 13:50:11 +00:00
if not item . get ( " expense_account " ) :
2014-05-01 12:12:21 +00:00
frappe . throw ( _ ( " Expense or Difference account is mandatory for Item {0} as it impacts overall stock value " ) . format ( item . item_code ) )
2014-04-07 13:21:58 +00:00
2014-06-19 13:55:19 +00:00
else :
2014-06-25 13:42:24 +00:00
is_expense_account = frappe . db . get_value ( " Account " ,
item . get ( " expense_account " ) , " report_type " ) == " Profit and Loss "
2014-07-10 13:36:39 +00:00
if self . doctype not in ( " Purchase Receipt " , " Stock Reconciliation " ) and not is_expense_account :
2014-06-25 13:42:24 +00:00
frappe . throw ( _ ( " Expense / Difference account ( {0} ) must be a ' Profit or Loss ' account " )
. format ( item . get ( " expense_account " ) ) )
2014-06-19 13:55:19 +00:00
if is_expense_account and not item . get ( " cost_center " ) :
frappe . throw ( _ ( " {0} {1} : Cost Center is mandatory for Item {2} " ) . format (
_ ( self . doctype ) , self . name , item . get ( " item_code " ) ) )
2014-04-07 13:21:58 +00:00
def get_sl_entries ( self , d , args ) :
2013-08-02 06:12:11 +00:00
sl_dict = {
2014-05-02 12:06:13 +00:00
" item_code " : d . get ( " item_code " , None ) ,
2014-04-07 06:32:57 +00:00
" warehouse " : d . get ( " warehouse " , None ) ,
2014-03-28 08:25:00 +00:00
" posting_date " : self . posting_date ,
" posting_time " : self . posting_time ,
" voucher_type " : self . doctype ,
" voucher_no " : self . name ,
2013-08-02 06:12:11 +00:00
" voucher_detail_no " : d . name ,
2014-04-07 06:32:57 +00:00
" actual_qty " : ( self . docstatus == 1 and 1 or - 1 ) * flt ( d . get ( " stock_qty " ) ) ,
2014-05-02 12:06:13 +00:00
" stock_uom " : d . get ( " stock_uom " ) ,
2013-08-02 06:12:11 +00:00
" incoming_rate " : 0 ,
2014-03-28 08:25:00 +00:00
" company " : self . company ,
" fiscal_year " : self . fiscal_year ,
2014-05-02 12:06:13 +00:00
" batch_no " : cstr ( d . get ( " batch_no " ) ) . strip ( ) ,
" serial_no " : d . get ( " serial_no " ) ,
2014-04-07 06:32:57 +00:00
" project " : d . get ( " project_name " ) ,
2014-03-28 08:25:00 +00:00
" is_cancelled " : self . docstatus == 2 and " Yes " or " No "
2013-08-02 06:12:11 +00:00
}
2014-04-07 13:21:58 +00:00
2013-08-02 06:12:11 +00:00
sl_dict . update ( args )
return sl_dict
2014-04-07 13:21:58 +00:00
2013-08-02 06:12:11 +00:00
def make_sl_entries ( self , sl_entries , is_amended = None ) :
2013-12-12 13:42:19 +00:00
from erpnext . stock . stock_ledger import make_sl_entries
2013-08-19 10:47:18 +00:00
make_sl_entries ( sl_entries , is_amended )
2014-04-07 13:21:58 +00:00
2014-07-16 14:18:29 +00:00
def make_gl_entries_on_cancel ( self ) :
2014-04-07 13:21:58 +00:00
if frappe . db . sql ( """ select name from `tabGL Entry` where voucher_type= %s
2014-03-28 08:25:00 +00:00
and voucher_no = % s """ , (self.doctype, self.name)):
2014-03-27 11:48:29 +00:00
self . make_gl_entries ( )
2014-04-07 13:21:58 +00:00
2014-06-25 08:01:02 +00:00
def get_serialized_items ( self ) :
serialized_items = [ ]
item_codes = list ( set ( [ d . item_code for d in self . get ( self . fname ) ] ) )
if item_codes :
serialized_items = frappe . db . sql_list ( """ select name from `tabItem`
where has_serial_no = ' Yes ' and name in ( { } ) """ .format( " , " .join([ " %s " ]*len(item_codes))),
tuple ( item_codes ) )
return serialized_items
2014-10-06 06:23:52 +00:00
def update_gl_entries_after ( posting_date , posting_time , for_warehouses = None , for_items = None ,
warehouse_account = None , allow_negative_stock = False ) :
2014-03-27 11:48:29 +00:00
def _delete_gl_entries ( voucher_type , voucher_no ) :
2014-04-07 13:21:58 +00:00
frappe . db . sql ( """ delete from `tabGL Entry`
2014-03-27 11:48:29 +00:00
where voucher_type = % s and voucher_no = % s """ , (voucher_type, voucher_no))
2014-04-07 13:21:58 +00:00
2014-03-27 11:48:29 +00:00
if not warehouse_account :
warehouse_account = get_warehouse_account ( )
2014-09-26 08:52:18 +00:00
future_stock_vouchers = get_future_stock_vouchers ( posting_date , posting_time , for_warehouses , for_items )
2014-03-27 11:48:29 +00:00
gle = get_voucherwise_gl_entries ( future_stock_vouchers , posting_date )
2013-08-07 13:57:30 +00:00
2014-03-27 11:48:29 +00:00
for voucher_type , voucher_no in future_stock_vouchers :
existing_gle = gle . get ( ( voucher_type , voucher_no ) , [ ] )
2014-03-31 11:57:06 +00:00
voucher_obj = frappe . get_doc ( voucher_type , voucher_no )
2014-10-08 05:30:38 +00:00
expected_gle = voucher_obj . get_gl_entries ( warehouse_account , allow_negative_stock = allow_negative_stock )
2014-03-27 11:48:29 +00:00
if expected_gle :
2014-04-07 13:21:58 +00:00
if not existing_gle or not compare_existing_and_expected_gle ( existing_gle ,
2014-03-27 11:48:29 +00:00
expected_gle ) :
_delete_gl_entries ( voucher_type , voucher_no )
2014-10-06 06:23:52 +00:00
voucher_obj . make_gl_entries ( repost_future_gle = False , allow_negative_stock = allow_negative_stock )
2014-03-27 11:48:29 +00:00
else :
_delete_gl_entries ( voucher_type , voucher_no )
2014-04-07 13:21:58 +00:00
2014-03-27 11:48:29 +00:00
def compare_existing_and_expected_gle ( existing_gle , expected_gle ) :
matched = True
for entry in expected_gle :
for e in existing_gle :
if entry . account == e . account and entry . against_account == e . against_account \
and entry . cost_center == e . cost_center \
and ( entry . debit != e . debit or entry . credit != e . credit ) :
matched = False
break
return matched
2013-03-19 06:31:24 +00:00
2014-09-26 08:52:18 +00:00
def get_future_stock_vouchers ( posting_date , posting_time , for_warehouses = None , for_items = None ) :
2014-03-27 11:48:29 +00:00
future_stock_vouchers = [ ]
2014-04-07 13:21:58 +00:00
2014-08-27 16:39:03 +00:00
values = [ ]
2014-03-27 11:48:29 +00:00
condition = " "
if for_items :
2014-08-27 16:39:03 +00:00
condition + = " and item_code in ( {} ) " . format ( " , " . join ( [ " %s " ] * len ( for_items ) ) )
values + = for_items
2014-04-07 13:21:58 +00:00
2014-09-26 08:52:18 +00:00
if for_warehouses :
condition + = " and warehouse in ( {} ) " . format ( " , " . join ( [ " %s " ] * len ( for_warehouses ) ) )
values + = for_warehouses
2014-04-07 13:21:58 +00:00
for d in frappe . db . sql ( """ select distinct sle.voucher_type, sle.voucher_no
2014-03-27 11:48:29 +00:00
from ` tabStock Ledger Entry ` sle
2014-08-27 16:39:03 +00:00
where timestamp ( sle . posting_date , sle . posting_time ) > = timestamp ( % s , % s ) { condition }
order by timestamp ( sle . posting_date , sle . posting_time ) asc , name asc """ .format(condition=condition),
tuple ( [ posting_date , posting_time ] + values ) , as_dict = True ) :
2014-03-27 11:48:29 +00:00
future_stock_vouchers . append ( [ d . voucher_type , d . voucher_no ] )
2014-04-07 13:21:58 +00:00
2014-03-27 11:48:29 +00:00
return future_stock_vouchers
2014-04-07 13:21:58 +00:00
2014-03-27 11:48:29 +00:00
def get_voucherwise_gl_entries ( future_stock_vouchers , posting_date ) :
gl_entries = { }
if future_stock_vouchers :
2014-04-07 13:21:58 +00:00
for d in frappe . db . sql ( """ select * from `tabGL Entry`
where posting_date > = % s and voucher_no in ( % s ) """ %
( ' %s ' , ' , ' . join ( [ ' %s ' ] * len ( future_stock_vouchers ) ) ) ,
2014-03-27 11:48:29 +00:00
tuple ( [ posting_date ] + [ d [ 1 ] for d in future_stock_vouchers ] ) , as_dict = 1 ) :
gl_entries . setdefault ( ( d . voucher_type , d . voucher_no ) , [ ] ) . append ( d )
2014-04-07 13:21:58 +00:00
2014-03-27 11:48:29 +00:00
return gl_entries
def get_warehouse_account ( ) :
2014-04-07 13:21:58 +00:00
warehouse_account = dict ( frappe . db . sql ( """ select master_name, name from tabAccount
2014-03-27 11:48:29 +00:00
where account_type = ' Warehouse ' and ifnull ( master_name , ' ' ) != ' ' """ ))
2014-03-28 08:11:59 +00:00
return warehouse_account
2014-10-06 06:23:52 +00:00
def block_negative_stock ( allow_negative_stock = False ) :
if cint ( frappe . defaults . get_global_default ( " auto_accounting_for_stock " ) ) and not allow_negative_stock :
if cint ( frappe . db . get_value ( " Stock Settings " , None , " allow_negative_stock " ) ) :
frappe . throw ( _ ( " Negative stock is not allowed in case of Perpetual Inventory, please disable it from Stock Settings " ) )
2014-10-08 12:36:14 +00:00
def get_valuation_rate ( item_code , warehouse ) :
2014-10-06 06:23:52 +00:00
last_valuation_rate = frappe . db . sql ( """ select valuation_rate
from ` tabStock Ledger Entry `
where item_code = % s and warehouse = % s
2014-10-09 13:55:03 +00:00
and ifnull ( valuation_rate , 0 ) > 0
2014-10-08 12:36:14 +00:00
order by posting_date desc , posting_time desc , name desc limit 1 """ , (item_code, warehouse))
2014-10-06 06:23:52 +00:00
2014-10-09 13:55:03 +00:00
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)
2014-10-06 06:23:52 +00:00
valuation_rate = flt ( last_valuation_rate [ 0 ] [ 0 ] ) if last_valuation_rate else 0
if not valuation_rate :
2014-10-08 05:30:38 +00:00
valuation_rate = frappe . db . get_value ( " Item Price " , { " item_code " : item_code , " buying " : 1 } , " price_list_rate " )
2014-10-06 06:23:52 +00:00
2014-10-14 10:37:56 +00:00
if not valuation_rate :
frappe . throw ( _ ( " Purchase rate for item: {0} not found, which is required to book accounting entry. Please mention item price against a buying price list. " ) . format ( item_code ) )
2014-10-06 06:23:52 +00:00
return valuation_rate