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-01-30 07:19:08 +00:00
from __future__ import unicode_literals
2017-06-19 07:24:59 +00:00
import frappe , erpnext
2019-10-31 10:25:03 +00:00
from frappe . utils import flt , cstr , cint , comma_and
2014-02-14 10:17:51 +00:00
from frappe import _
2019-10-31 10:25:03 +00:00
from erpnext . accounts . utils import get_stock_and_account_balance
2015-05-28 07:30:37 +00:00
from frappe . model . meta import get_field_precision
2016-05-16 09:08:47 +00:00
from erpnext . accounts . doctype . budget . budget import validate_expense_against_budget
2019-05-18 18:32:01 +00:00
from erpnext . accounts . doctype . accounting_dimension . accounting_dimension import get_accounting_dimensions
2013-01-30 07:19:08 +00:00
2013-08-29 12:49:37 +00:00
2019-08-08 10:14:11 +00:00
class ClosedAccountingPeriod ( frappe . ValidationError ) : pass
2014-02-14 10:17:51 +00:00
class StockAccountInvalidTransaction ( frappe . ValidationError ) : pass
2019-10-31 10:25:03 +00:00
class StockValueAndAccountBalanceOutOfSync ( frappe . ValidationError ) : pass
2013-01-30 07:19:08 +00:00
2016-12-30 10:51:35 +00:00
def make_gl_entries ( gl_map , cancel = False , adv_adj = False , merge_entries = True , update_outstanding = ' Yes ' , from_repost = False ) :
2013-08-28 13:23:11 +00:00
if gl_map :
if not cancel :
2019-08-08 10:14:11 +00:00
validate_accounting_period ( gl_map )
2013-08-28 13:23:11 +00:00
gl_map = process_gl_map ( gl_map , merge_entries )
2014-07-29 12:36:18 +00:00
if gl_map and len ( gl_map ) > 1 :
2016-12-30 10:51:35 +00:00
save_entries ( gl_map , adv_adj , update_outstanding , from_repost )
2014-07-29 12:36:18 +00:00
else :
2014-07-30 08:58:24 +00:00
frappe . throw ( _ ( " Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction. " ) )
2013-08-28 13:23:11 +00:00
else :
delete_gl_entries ( gl_map , adv_adj = adv_adj , update_outstanding = update_outstanding )
2014-04-16 09:51:46 +00:00
2019-08-08 10:14:11 +00:00
def validate_accounting_period ( gl_map ) :
accounting_periods = frappe . db . sql ( """ SELECT
ap . name as name
FROM
` tabAccounting Period ` ap , ` tabClosed Document ` cd
WHERE
ap . name = cd . parent
AND ap . company = % ( company ) s
AND cd . closed = 1
AND cd . document_type = % ( voucher_type ) s
AND % ( date ) s between ap . start_date and ap . end_date
""" , {
' date ' : gl_map [ 0 ] . posting_date ,
' company ' : gl_map [ 0 ] . company ,
' voucher_type ' : gl_map [ 0 ] . voucher_type
} , as_dict = 1 )
if accounting_periods :
frappe . throw ( _ ( " You can ' t create accounting entries in the closed accounting period {0} " )
. format ( accounting_periods [ 0 ] . name ) , ClosedAccountingPeriod )
2013-08-26 11:23:30 +00:00
def process_gl_map ( gl_map , merge_entries = True ) :
2013-01-30 07:19:08 +00:00
if merge_entries :
gl_map = merge_similar_entries ( gl_map )
2013-08-26 11:23:30 +00:00
for entry in gl_map :
2015-11-16 13:35:46 +00:00
# toggle debit, credit if negative entry
2013-10-23 10:59:19 +00:00
if flt ( entry . debit ) < 0 :
entry . credit = flt ( entry . credit ) - flt ( entry . debit )
entry . debit = 0.0
2015-11-16 13:35:46 +00:00
2015-08-19 13:52:34 +00:00
if flt ( entry . debit_in_account_currency ) < 0 :
entry . credit_in_account_currency = \
flt ( entry . credit_in_account_currency ) - flt ( entry . debit_in_account_currency )
entry . debit_in_account_currency = 0.0
2015-11-16 13:35:46 +00:00
2013-10-23 10:59:19 +00:00
if flt ( entry . credit ) < 0 :
entry . debit = flt ( entry . debit ) - flt ( entry . credit )
entry . credit = 0.0
2015-11-16 13:35:46 +00:00
2015-08-19 13:52:34 +00:00
if flt ( entry . credit_in_account_currency ) < 0 :
entry . debit_in_account_currency = \
flt ( entry . debit_in_account_currency ) - flt ( entry . credit_in_account_currency )
entry . credit_in_account_currency = 0.0
2015-11-16 13:35:46 +00:00
2013-08-26 11:23:30 +00:00
return gl_map
2014-04-16 09:51:46 +00:00
2013-01-30 07:19:08 +00:00
def merge_similar_entries ( gl_map ) :
merged_gl_map = [ ]
2019-05-18 18:32:01 +00:00
accounting_dimensions = get_accounting_dimensions ( )
2013-01-30 07:19:08 +00:00
for entry in gl_map :
2014-04-16 09:51:46 +00:00
# if there is already an entry in this account then just add it
2013-01-30 07:19:08 +00:00
# to that entry
2019-05-18 18:32:01 +00:00
same_head = check_if_in_list ( entry , merged_gl_map , accounting_dimensions )
2013-01-30 07:19:08 +00:00
if same_head :
2013-08-28 13:23:11 +00:00
same_head . debit = flt ( same_head . debit ) + flt ( entry . debit )
2015-08-19 13:52:34 +00:00
same_head . debit_in_account_currency = \
flt ( same_head . debit_in_account_currency ) + flt ( entry . debit_in_account_currency )
2013-08-28 13:23:11 +00:00
same_head . credit = flt ( same_head . credit ) + flt ( entry . credit )
2015-08-19 13:52:34 +00:00
same_head . credit_in_account_currency = \
flt ( same_head . credit_in_account_currency ) + flt ( entry . credit_in_account_currency )
2013-01-30 07:19:08 +00:00
else :
merged_gl_map . append ( entry )
2014-04-16 09:51:46 +00:00
2013-08-07 11:30:01 +00:00
# filter zero debit and credit entries
2015-08-13 06:49:20 +00:00
merged_gl_map = filter ( lambda x : flt ( x . debit , 9 ) != 0 or flt ( x . credit , 9 ) != 0 , merged_gl_map )
2018-03-08 07:40:51 +00:00
merged_gl_map = list ( merged_gl_map )
2018-08-08 11:07:31 +00:00
2013-01-30 07:19:08 +00:00
return merged_gl_map
2019-05-18 18:32:01 +00:00
def check_if_in_list ( gle , gl_map , dimensions = None ) :
account_head_fieldnames = [ ' party_type ' , ' party ' , ' against_voucher ' , ' against_voucher_type ' ,
' cost_center ' , ' project ' ]
if dimensions :
account_head_fieldnames = account_head_fieldnames + dimensions
2013-08-29 12:49:37 +00:00
for e in gl_map :
2019-05-18 18:32:01 +00:00
same_head = True
if e . account != gle . account :
same_head = False
for fieldname in account_head_fieldnames :
if cstr ( e . get ( fieldname ) ) != cstr ( gle . get ( fieldname ) ) :
same_head = False
if same_head :
return e
2013-01-30 07:19:08 +00:00
2016-12-30 10:51:35 +00:00
def save_entries ( gl_map , adv_adj , update_outstanding , from_repost = False ) :
if not from_repost :
2019-08-05 04:48:57 +00:00
validate_cwip_accounts ( gl_map )
2018-08-08 11:07:31 +00:00
2015-05-28 07:30:37 +00:00
round_off_debit_credit ( gl_map )
2013-01-30 07:19:08 +00:00
for entry in gl_map :
2016-12-30 10:51:35 +00:00
make_entry ( entry , adv_adj , update_outstanding , from_repost )
2018-08-08 11:07:31 +00:00
2013-08-26 11:23:30 +00:00
# check against budget
2016-12-30 10:51:35 +00:00
if not from_repost :
validate_expense_against_budget ( entry )
2014-04-16 09:51:46 +00:00
2019-10-31 10:25:03 +00:00
if not from_repost :
validate_account_for_perpetual_inventory ( gl_map )
2016-12-30 10:51:35 +00:00
def make_entry ( args , adv_adj , update_outstanding , from_repost = False ) :
2013-08-23 09:47:36 +00:00
args . update ( { " doctype " : " GL Entry " } )
2014-04-04 06:46:26 +00:00
gle = frappe . get_doc ( args )
2015-02-10 09:11:27 +00:00
gle . flags . ignore_permissions = 1
2016-12-30 10:51:35 +00:00
gle . flags . from_repost = from_repost
2013-08-23 09:47:36 +00:00
gle . insert ( )
2016-12-30 10:51:35 +00:00
gle . run_method ( " on_update_with_args " , adv_adj , update_outstanding , from_repost )
2013-08-23 09:47:36 +00:00
gle . submit ( )
2014-04-16 09:51:46 +00:00
2017-06-16 09:51:36 +00:00
def validate_account_for_perpetual_inventory ( gl_map ) :
2019-10-31 10:25:03 +00:00
if cint ( erpnext . is_perpetual_inventory_enabled ( gl_map [ 0 ] . company ) ) :
account_list = [ gl_entries . account for gl_entries in gl_map ]
2014-04-16 09:51:46 +00:00
2019-10-31 10:25:03 +00:00
aii_accounts = [ d . name for d in frappe . get_all ( " Account " ,
filters = { ' account_type ' : ' Stock ' , ' is_group ' : 0 , ' company ' : gl_map [ 0 ] . company } ) ]
for account in account_list :
if account not in aii_accounts :
continue
account_bal , stock_bal , warehouse_list = get_stock_and_account_balance ( account ,
gl_map [ 0 ] . posting_date , gl_map [ 0 ] . company )
if gl_map [ 0 ] . voucher_type == " Journal Entry " :
# In case of Journal Entry, there are no corresponding SL entries,
# hence deducting currency amount
account_bal - = flt ( gl_map [ 0 ] . debit ) - flt ( gl_map [ 0 ] . credit )
if account_bal == stock_bal :
2015-06-22 02:01:49 +00:00
frappe . throw ( _ ( " Account: {0} can only be updated via Stock Transactions " )
2019-10-31 10:25:03 +00:00
. format ( account ) , StockAccountInvalidTransaction )
elif account_bal != stock_bal :
frappe . throw ( _ ( " Account Balance ( {0} ) and Stock Value ( {1} ) is out of sync for account {2} and linked warehouse ( {3} ). Please create adjustment Journal Entry for amount {4} . " )
. format ( account_bal , stock_bal , account , comma_and ( warehouse_list ) , stock_bal - account_bal ) ,
StockValueAndAccountBalanceOutOfSync )
2014-04-16 09:51:46 +00:00
2019-08-05 04:48:57 +00:00
def validate_cwip_accounts ( gl_map ) :
if not cint ( frappe . db . get_value ( " Asset Settings " , None , " disable_cwip_accounting " ) ) \
and gl_map [ 0 ] . voucher_type == " Journal Entry " :
cwip_accounts = [ d [ 0 ] for d in frappe . db . sql ( """ select name from tabAccount
where account_type = ' Capital Work in Progress ' and is_group = 0 """ )]
for entry in gl_map :
if entry . account in cwip_accounts :
frappe . throw ( _ ( " Account: <b> {0} </b> is capital Work in progress and can not be updated by Journal Entry " ) . format ( entry . account ) )
2015-05-28 07:30:37 +00:00
def round_off_debit_credit ( gl_map ) :
precision = get_field_precision ( frappe . get_meta ( " GL Entry " ) . get_field ( " debit " ) ,
2018-08-08 11:07:31 +00:00
currency = frappe . get_cached_value ( ' Company ' , gl_map [ 0 ] . company , " default_currency " ) )
2015-11-16 13:35:46 +00:00
2015-05-28 07:30:37 +00:00
debit_credit_diff = 0.0
for entry in gl_map :
entry . debit = flt ( entry . debit , precision )
entry . credit = flt ( entry . credit , precision )
debit_credit_diff + = entry . debit - entry . credit
2015-11-16 13:35:46 +00:00
2015-05-28 07:30:37 +00:00
debit_credit_diff = flt ( debit_credit_diff , precision )
2018-08-08 11:07:31 +00:00
2017-05-05 05:11:16 +00:00
if gl_map [ 0 ] [ " voucher_type " ] in ( " Journal Entry " , " Payment Entry " ) :
2016-02-18 13:48:07 +00:00
allowance = 5.0 / ( 10 * * precision )
else :
2017-05-05 05:11:16 +00:00
allowance = .5
2018-08-08 11:07:31 +00:00
2016-02-18 13:48:07 +00:00
if abs ( debit_credit_diff ) > = allowance :
2015-05-28 07:30:37 +00:00
frappe . throw ( _ ( " Debit and Credit not equal for {0} # {1} . Difference is {2} . " )
. format ( gl_map [ 0 ] . voucher_type , gl_map [ 0 ] . voucher_no , debit_credit_diff ) )
2015-11-16 13:35:46 +00:00
2015-05-28 07:30:37 +00:00
elif abs ( debit_credit_diff ) > = ( 1.0 / ( 10 * * precision ) ) :
2019-07-03 05:04:31 +00:00
make_round_off_gle ( gl_map , debit_credit_diff , precision )
2015-11-16 13:35:46 +00:00
2019-07-03 05:04:31 +00:00
def make_round_off_gle ( gl_map , debit_credit_diff , precision ) :
2017-09-19 09:23:16 +00:00
round_off_account , round_off_cost_center = get_round_off_account_and_cost_center ( gl_map [ 0 ] . company )
2018-08-14 10:58:14 +00:00
round_off_account_exists = False
2015-05-28 13:49:59 +00:00
round_off_gle = frappe . _dict ( )
2018-08-14 10:58:14 +00:00
for d in gl_map :
if d . account == round_off_account :
round_off_gle = d
if d . debit_in_account_currency :
debit_credit_diff - = flt ( d . debit_in_account_currency )
else :
debit_credit_diff + = flt ( d . credit_in_account_currency )
round_off_account_exists = True
2019-07-03 05:04:31 +00:00
if round_off_account_exists and abs ( debit_credit_diff ) < = ( 1.0 / ( 10 * * precision ) ) :
gl_map . remove ( round_off_gle )
return
2018-08-14 10:58:14 +00:00
if not round_off_gle :
for k in [ " voucher_type " , " voucher_no " , " company " ,
" posting_date " , " remarks " , " is_opening " ] :
round_off_gle [ k ] = gl_map [ 0 ] [ k ]
2015-11-16 13:35:46 +00:00
2015-05-28 07:30:37 +00:00
round_off_gle . update ( {
" account " : round_off_account ,
2016-01-20 10:44:27 +00:00
" debit_in_account_currency " : abs ( debit_credit_diff ) if debit_credit_diff < 0 else 0 ,
" credit_in_account_currency " : debit_credit_diff if debit_credit_diff > 0 else 0 ,
2015-05-28 07:30:37 +00:00
" debit " : abs ( debit_credit_diff ) if debit_credit_diff < 0 else 0 ,
" credit " : debit_credit_diff if debit_credit_diff > 0 else 0 ,
2015-05-28 07:49:01 +00:00
" cost_center " : round_off_cost_center ,
" party_type " : None ,
" party " : None ,
" against_voucher_type " : None ,
" against_voucher " : None
2015-05-28 07:30:37 +00:00
} )
2015-11-16 13:35:46 +00:00
2018-08-14 10:58:14 +00:00
if not round_off_account_exists :
gl_map . append ( round_off_gle )
2015-05-28 07:30:37 +00:00
2017-09-19 09:23:16 +00:00
def get_round_off_account_and_cost_center ( company ) :
2018-08-08 11:07:31 +00:00
round_off_account , round_off_cost_center = frappe . get_cached_value ( ' Company ' , company ,
2017-09-19 09:23:16 +00:00
[ " round_off_account " , " round_off_cost_center " ] ) or [ None , None ]
if not round_off_account :
frappe . throw ( _ ( " Please mention Round Off Account in Company " ) )
if not round_off_cost_center :
frappe . throw ( _ ( " Please mention Round Off Cost Center in Company " ) )
return round_off_account , round_off_cost_center
2014-04-16 09:51:46 +00:00
def delete_gl_entries ( gl_entries = None , voucher_type = None , voucher_no = None ,
2013-08-28 13:23:11 +00:00
adv_adj = False , update_outstanding = " Yes " ) :
2014-04-16 09:51:46 +00:00
2014-03-03 13:48:17 +00:00
from erpnext . accounts . doctype . gl_entry . gl_entry import validate_balance_type , \
2013-09-24 09:06:55 +00:00
check_freezing_date , update_outstanding_amt , validate_frozen_account
2014-04-16 09:51:46 +00:00
2013-08-28 13:23:11 +00:00
if not gl_entries :
2016-07-01 10:28:39 +00:00
gl_entries = frappe . db . sql ( """
2016-10-03 06:55:04 +00:00
select account , posting_date , party_type , party , cost_center , fiscal_year , voucher_type ,
voucher_no , against_voucher_type , against_voucher , cost_center , company
2016-07-01 10:28:39 +00:00
from ` tabGL Entry `
2013-08-28 13:23:11 +00:00
where voucher_type = % s and voucher_no = % s """ , (voucher_type, voucher_no), as_dict=True)
2016-07-01 10:28:39 +00:00
2013-08-26 11:23:30 +00:00
if gl_entries :
check_freezing_date ( gl_entries [ 0 ] [ " posting_date " ] , adv_adj )
2014-04-16 09:51:46 +00:00
frappe . db . sql ( """ delete from `tabGL Entry` where voucher_type= %s and voucher_no= %s """ ,
2013-08-28 13:23:11 +00:00
( voucher_type or gl_entries [ 0 ] [ " voucher_type " ] , voucher_no or gl_entries [ 0 ] [ " voucher_no " ] ) )
2014-04-16 09:51:46 +00:00
2013-08-21 12:17:11 +00:00
for entry in gl_entries :
2013-09-24 09:06:55 +00:00
validate_frozen_account ( entry [ " account " ] , adv_adj )
2014-03-03 13:48:17 +00:00
validate_balance_type ( entry [ " account " ] , adv_adj )
2018-01-29 11:15:43 +00:00
if not adv_adj :
validate_expense_against_budget ( entry )
2018-08-08 11:07:31 +00:00
2018-01-29 11:15:43 +00:00
if entry . get ( " against_voucher " ) and update_outstanding == ' Yes ' and not adv_adj :
2014-08-29 05:48:32 +00:00
update_outstanding_amt ( entry [ " account " ] , entry . get ( " party_type " ) , entry . get ( " party " ) , entry . get ( " against_voucher_type " ) ,
2014-04-16 11:51:25 +00:00
entry . get ( " against_voucher " ) , on_cancel = True )