blob: 0cee6f5b3aa194577bc775af5d224c4e2eab8649 [file] [log] [blame]
Anand Doshi885e0742015-03-03 14:55:30 +05301# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
Rushabh Mehtae67d1fb2013-08-05 14:59:54 +05302# License: GNU General Public License v3. See license.txt
Nabin Haitbf495c92013-01-30 12:49:08 +05303
4from __future__ import unicode_literals
Chillar Anand915b3432021-09-02 16:44:59 +05305
6import frappe
Rushabh Mehta793ba6b2014-02-14 15:47:51 +05307from frappe import _
Nabin Haite2c200a2015-05-28 13:00:37 +05308from frappe.model.meta import get_field_precision
Chillar Anand915b3432021-09-02 16:44:59 +05309from frappe.utils import cint, cstr, flt, formatdate, getdate, now
10
11import erpnext
12from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
13 get_accounting_dimensions,
14)
Nabin Haitb9bc7d62016-05-16 14:38:47 +053015from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
Chillar Anand915b3432021-09-02 16:44:59 +053016
Nabin Haitbf495c92013-01-30 12:49:08 +053017
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053018class ClosedAccountingPeriod(frappe.ValidationError): pass
Nabin Haitbf495c92013-01-30 12:49:08 +053019
Nabin Haita77b8c92020-12-21 14:45:50 +053020def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
Nabin Hait2e296fa2013-08-28 18:53:11 +053021 if gl_map:
22 if not cancel:
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053023 validate_accounting_period(gl_map)
Nabin Hait2e296fa2013-08-28 18:53:11 +053024 gl_map = process_gl_map(gl_map, merge_entries)
nabinhaitc3432922014-07-29 18:06:18 +053025 if gl_map and len(gl_map) > 1:
Nabin Haita77b8c92020-12-21 14:45:50 +053026 save_entries(gl_map, adv_adj, update_outstanding, from_repost)
Deepesh Garg4c8d15b2021-04-26 15:24:34 +053027 # Post GL Map proccess there may no be any GL Entries
28 elif gl_map:
nabinhait4859b482014-07-30 14:28:24 +053029 frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
Nabin Hait2e296fa2013-08-28 18:53:11 +053030 else:
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +053031 make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
Anand Doshi652bc072014-04-16 15:21:46 +053032
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053033def validate_accounting_period(gl_map):
34 accounting_periods = frappe.db.sql(""" SELECT
35 ap.name as name
36 FROM
37 `tabAccounting Period` ap, `tabClosed Document` cd
38 WHERE
39 ap.name = cd.parent
40 AND ap.company = %(company)s
41 AND cd.closed = 1
42 AND cd.document_type = %(voucher_type)s
43 AND %(date)s between ap.start_date and ap.end_date
44 """, {
45 'date': gl_map[0].posting_date,
46 'company': gl_map[0].company,
47 'voucher_type': gl_map[0].voucher_type
48 }, as_dict=1)
49
50 if accounting_periods:
Deepesh Garg069a54e2020-08-10 16:01:01 +053051 frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}")
52 .format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053053
Nabin Hait19f8fa52021-02-22 22:27:22 +053054def process_gl_map(gl_map, merge_entries=True, precision=None):
Nabin Haitbf495c92013-01-30 12:49:08 +053055 if merge_entries:
Nabin Hait19f8fa52021-02-22 22:27:22 +053056 gl_map = merge_similar_entries(gl_map, precision)
Nabin Hait27994c22013-08-26 16:53:30 +053057 for entry in gl_map:
Anand Doshi602e8252015-11-16 19:05:46 +053058 # toggle debit, credit if negative entry
Nabin Hait88f3cd52013-10-23 16:29:19 +053059 if flt(entry.debit) < 0:
60 entry.credit = flt(entry.credit) - flt(entry.debit)
61 entry.debit = 0.0
Anand Doshi602e8252015-11-16 19:05:46 +053062
Nabin Haitc561a492015-08-19 19:22:34 +053063 if flt(entry.debit_in_account_currency) < 0:
64 entry.credit_in_account_currency = \
65 flt(entry.credit_in_account_currency) - flt(entry.debit_in_account_currency)
66 entry.debit_in_account_currency = 0.0
Anand Doshi602e8252015-11-16 19:05:46 +053067
Nabin Hait88f3cd52013-10-23 16:29:19 +053068 if flt(entry.credit) < 0:
69 entry.debit = flt(entry.debit) - flt(entry.credit)
70 entry.credit = 0.0
Anand Doshi602e8252015-11-16 19:05:46 +053071
Nabin Haitc561a492015-08-19 19:22:34 +053072 if flt(entry.credit_in_account_currency) < 0:
73 entry.debit_in_account_currency = \
74 flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
75 entry.credit_in_account_currency = 0.0
Anand Doshi602e8252015-11-16 19:05:46 +053076
Nabin Hait27994c22013-08-26 16:53:30 +053077 return gl_map
Anand Doshi652bc072014-04-16 15:21:46 +053078
Nabin Hait19f8fa52021-02-22 22:27:22 +053079def merge_similar_entries(gl_map, precision=None):
Nabin Haitbf495c92013-01-30 12:49:08 +053080 merged_gl_map = []
deepeshgarg0073d11ac02019-05-19 00:02:01 +053081 accounting_dimensions = get_accounting_dimensions()
Nabin Haitbf495c92013-01-30 12:49:08 +053082 for entry in gl_map:
Anand Doshi652bc072014-04-16 15:21:46 +053083 # if there is already an entry in this account then just add it
Nabin Haitbf495c92013-01-30 12:49:08 +053084 # to that entry
deepeshgarg0073d11ac02019-05-19 00:02:01 +053085 same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
Nabin Haitbf495c92013-01-30 12:49:08 +053086 if same_head:
Nabin Hait2e296fa2013-08-28 18:53:11 +053087 same_head.debit = flt(same_head.debit) + flt(entry.debit)
Nabin Haitc561a492015-08-19 19:22:34 +053088 same_head.debit_in_account_currency = \
89 flt(same_head.debit_in_account_currency) + flt(entry.debit_in_account_currency)
Nabin Hait2e296fa2013-08-28 18:53:11 +053090 same_head.credit = flt(same_head.credit) + flt(entry.credit)
Nabin Haitc561a492015-08-19 19:22:34 +053091 same_head.credit_in_account_currency = \
92 flt(same_head.credit_in_account_currency) + flt(entry.credit_in_account_currency)
Nabin Haitbf495c92013-01-30 12:49:08 +053093 else:
94 merged_gl_map.append(entry)
Anand Doshi652bc072014-04-16 15:21:46 +053095
thefalconx33bfc43d32019-12-13 15:09:51 +053096 company = gl_map[0].company if gl_map else erpnext.get_default_company()
97 company_currency = erpnext.get_company_currency(company)
Nabin Hait19f8fa52021-02-22 22:27:22 +053098
99 if not precision:
100 precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
thefalconx33bfc43d32019-12-13 15:09:51 +0530101
Nabin Hait815a49e2013-08-07 17:00:01 +0530102 # filter zero debit and credit entries
thefalconx33bfc43d32019-12-13 15:09:51 +0530103 merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map)
Achilles Rasquinha908289d2018-03-08 13:10:51 +0530104 merged_gl_map = list(merged_gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530105
Nabin Haitbf495c92013-01-30 12:49:08 +0530106 return merged_gl_map
107
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530108def check_if_in_list(gle, gl_map, dimensions=None):
Ankush91527152021-08-11 11:17:50 +0530109 account_head_fieldnames = ['voucher_detail_no', 'party', 'against_voucher',
Saqib9198caa2021-08-24 17:26:23 +0530110 'cost_center', 'against_voucher_type', 'party_type', 'project', 'finance_book']
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530111
112 if dimensions:
113 account_head_fieldnames = account_head_fieldnames + dimensions
114
Nabin Haitbb777562013-08-29 18:19:37 +0530115 for e in gl_map:
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530116 same_head = True
117 if e.account != gle.account:
118 same_head = False
Ankush91527152021-08-11 11:17:50 +0530119 continue
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530120
121 for fieldname in account_head_fieldnames:
122 if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
123 same_head = False
Ankush91527152021-08-11 11:17:50 +0530124 break
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530125
126 if same_head:
127 return e
Nabin Haitbf495c92013-01-30 12:49:08 +0530128
Nabin Haita77b8c92020-12-21 14:45:50 +0530129def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
130 if not from_repost:
131 validate_cwip_accounts(gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530132
Nabin Haite2c200a2015-05-28 13:00:37 +0530133 round_off_debit_credit(gl_map)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530134
135 if gl_map:
136 check_freezing_date(gl_map[0]["posting_date"], adv_adj)
137
Nabin Haitbf495c92013-01-30 12:49:08 +0530138 for entry in gl_map:
Nabin Haita77b8c92020-12-21 14:45:50 +0530139 make_entry(entry, adv_adj, update_outstanding, from_repost)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530140
Nabin Haita77b8c92020-12-21 14:45:50 +0530141def make_entry(args, adv_adj, update_outstanding, from_repost=False):
Nabin Haitddc3bb22020-03-17 17:04:18 +0530142 gle = frappe.new_doc("GL Entry")
143 gle.update(args)
Anand Doshi6dfd4302015-02-10 14:41:27 +0530144 gle.flags.ignore_permissions = 1
Nabin Haita77b8c92020-12-21 14:45:50 +0530145 gle.flags.from_repost = from_repost
Nabin Hait19f8fa52021-02-22 22:27:22 +0530146 gle.flags.adv_adj = adv_adj
147 gle.flags.update_outstanding = update_outstanding or 'Yes'
Nabin Haitaeba24e2013-08-23 15:17:36 +0530148 gle.submit()
Anand Doshi652bc072014-04-16 15:21:46 +0530149
Nabin Haita77b8c92020-12-21 14:45:50 +0530150 if not from_repost:
151 validate_expense_against_budget(args)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530152
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530153def validate_cwip_accounts(gl_map):
Ankush91527152021-08-11 11:17:50 +0530154 """Validate that CWIP account are not used in Journal Entry"""
155 if gl_map and gl_map[0].voucher_type != "Journal Entry":
156 return
Maricad00c5982019-11-12 19:17:43 +0530157
Ankush91527152021-08-11 11:17:50 +0530158 cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting"))
159 if cwip_enabled:
160 cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
161 where account_type = 'Capital Work in Progress' and is_group=0""")]
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530162
Ankush91527152021-08-11 11:17:50 +0530163 for entry in gl_map:
164 if entry.account in cwip_accounts:
165 frappe.throw(
166 _("Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530167
Nabin Haite2c200a2015-05-28 13:00:37 +0530168def round_off_debit_credit(gl_map):
169 precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530170 currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
Anand Doshi602e8252015-11-16 19:05:46 +0530171
Nabin Haite2c200a2015-05-28 13:00:37 +0530172 debit_credit_diff = 0.0
173 for entry in gl_map:
174 entry.debit = flt(entry.debit, precision)
175 entry.credit = flt(entry.credit, precision)
176 debit_credit_diff += entry.debit - entry.credit
Anand Doshi602e8252015-11-16 19:05:46 +0530177
Nabin Haite2c200a2015-05-28 13:00:37 +0530178 debit_credit_diff = flt(debit_credit_diff, precision)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530179
Nabin Haitd2a966e2017-05-05 10:41:16 +0530180 if gl_map[0]["voucher_type"] in ("Journal Entry", "Payment Entry"):
Nabin Haitfb24a272016-02-18 19:18:07 +0530181 allowance = 5.0 / (10**precision)
182 else:
Nabin Haitd2a966e2017-05-05 10:41:16 +0530183 allowance = .5
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530184
Saqib Ansarib0e160f2021-04-22 13:23:50 +0530185 if abs(debit_credit_diff) > allowance:
Nabin Haite2c200a2015-05-28 13:00:37 +0530186 frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.")
187 .format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff))
Anand Doshi602e8252015-11-16 19:05:46 +0530188
Saqib Ansariaa4f7502021-04-28 14:42:49 +0530189 elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
Nabin Hait34c551d2019-07-03 10:34:31 +0530190 make_round_off_gle(gl_map, debit_credit_diff, precision)
Anand Doshi602e8252015-11-16 19:05:46 +0530191
Nabin Hait34c551d2019-07-03 10:34:31 +0530192def make_round_off_gle(gl_map, debit_credit_diff, precision):
Nabin Hait2e4de832017-09-19 14:53:16 +0530193 round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(gl_map[0].company)
Zarrar3523b772018-08-14 16:28:14 +0530194 round_off_account_exists = False
Nabin Hait80069a62015-05-28 19:19:59 +0530195 round_off_gle = frappe._dict()
Zarrar3523b772018-08-14 16:28:14 +0530196 for d in gl_map:
197 if d.account == round_off_account:
198 round_off_gle = d
Saqibc6e016e2021-06-04 10:08:22 +0530199 if d.debit:
200 debit_credit_diff -= flt(d.debit)
Zarrar3523b772018-08-14 16:28:14 +0530201 else:
Saqibc6e016e2021-06-04 10:08:22 +0530202 debit_credit_diff += flt(d.credit)
Zarrar3523b772018-08-14 16:28:14 +0530203 round_off_account_exists = True
204
Nabin Hait34c551d2019-07-03 10:34:31 +0530205 if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
206 gl_map.remove(round_off_gle)
207 return
208
Zarrar3523b772018-08-14 16:28:14 +0530209 if not round_off_gle:
210 for k in ["voucher_type", "voucher_no", "company",
Ankush Menat23c30e42021-02-24 11:03:24 +0530211 "posting_date", "remarks"]:
Zarrar3523b772018-08-14 16:28:14 +0530212 round_off_gle[k] = gl_map[0][k]
Anand Doshi602e8252015-11-16 19:05:46 +0530213
Nabin Haite2c200a2015-05-28 13:00:37 +0530214 round_off_gle.update({
215 "account": round_off_account,
Nabin Haitd4507ac2016-01-20 16:14:27 +0530216 "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
217 "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
Nabin Haite2c200a2015-05-28 13:00:37 +0530218 "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
219 "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
Nabin Hait3c671462015-05-28 13:19:01 +0530220 "cost_center": round_off_cost_center,
221 "party_type": None,
222 "party": None,
Ankush Menat23c30e42021-02-24 11:03:24 +0530223 "is_opening": "No",
Nabin Hait3c671462015-05-28 13:19:01 +0530224 "against_voucher_type": None,
225 "against_voucher": None
Nabin Haite2c200a2015-05-28 13:00:37 +0530226 })
Anand Doshi602e8252015-11-16 19:05:46 +0530227
Zarrar3523b772018-08-14 16:28:14 +0530228 if not round_off_account_exists:
229 gl_map.append(round_off_gle)
Nabin Haite2c200a2015-05-28 13:00:37 +0530230
Nabin Hait2e4de832017-09-19 14:53:16 +0530231def get_round_off_account_and_cost_center(company):
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530232 round_off_account, round_off_cost_center = frappe.get_cached_value('Company', company,
Nabin Hait2e4de832017-09-19 14:53:16 +0530233 ["round_off_account", "round_off_cost_center"]) or [None, None]
234 if not round_off_account:
235 frappe.throw(_("Please mention Round Off Account in Company"))
236
237 if not round_off_cost_center:
238 frappe.throw(_("Please mention Round Off Cost Center in Company"))
239
240 return round_off_account, round_off_cost_center
241
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530242def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
243 adv_adj=False, update_outstanding="Yes"):
244 """
245 Get original gl entries of the voucher
246 and make reverse gl entries by swapping debit and credit
247 """
Anand Doshi652bc072014-04-16 15:21:46 +0530248
Nabin Hait2e296fa2013-08-28 18:53:11 +0530249 if not gl_entries:
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530250 gl_entries = frappe.get_all("GL Entry",
251 fields = ["*"],
252 filters = {
253 "voucher_type": voucher_type,
Deepesh Gargb821f222020-05-06 18:12:23 +0530254 "voucher_no": voucher_no,
255 "is_cancelled": 0
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530256 })
Nabin Hait56548cb2016-07-01 15:58:39 +0530257
Nabin Hait27994c22013-08-26 16:53:30 +0530258 if gl_entries:
Deepesh Garg069a54e2020-08-10 16:01:01 +0530259 validate_accounting_period(gl_entries)
Nabin Hait27994c22013-08-26 16:53:30 +0530260 check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
Deepesh Garg069a54e2020-08-10 16:01:01 +0530261 set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
Anand Doshi652bc072014-04-16 15:21:46 +0530262
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530263 for entry in gl_entries:
264 entry['name'] = None
265 debit = entry.get('debit', 0)
266 credit = entry.get('credit', 0)
Anand Doshi652bc072014-04-16 15:21:46 +0530267
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530268 debit_in_account_currency = entry.get('debit_in_account_currency', 0)
269 credit_in_account_currency = entry.get('credit_in_account_currency', 0)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530270
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530271 entry['debit'] = credit
272 entry['credit'] = debit
273 entry['debit_in_account_currency'] = credit_in_account_currency
274 entry['credit_in_account_currency'] = debit_in_account_currency
275
276 entry['remarks'] = "On cancellation of " + entry['voucher_no']
277 entry['is_cancelled'] = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530278
279 if entry['debit'] or entry['credit']:
280 make_entry(entry, adv_adj, "Yes")
281
282
283def check_freezing_date(posting_date, adv_adj=False):
284 """
285 Nobody can do GL Entries where posting date is before freezing date
286 except authorized person
Deepesh Garg13d2e7b2021-09-16 18:54:57 +0530287
288 Administrator has all the roles so this check will be bypassed if any role is allowed to post
289 Hence stop admin to bypass if accounts are freezed
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530290 """
291 if not adv_adj:
292 acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
293 if acc_frozen_upto:
294 frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
295 if getdate(posting_date) <= getdate(acc_frozen_upto) \
Deepesh Garg13d2e7b2021-09-16 18:54:57 +0530296 and not frozen_accounts_modifier in frappe.get_roles() or frappe.session.user == 'Administrator':
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530297 frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto)))
298
299def set_as_cancel(voucher_type, voucher_no):
300 """
301 Set is_cancelled=1 in all original gl entries for the voucher
302 """
Deepesh Garg069a54e2020-08-10 16:01:01 +0530303 frappe.db.sql("""UPDATE `tabGL Entry` SET is_cancelled = 1,
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530304 modified=%s, modified_by=%s
305 where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
306 (now(), frappe.session.user, voucher_type, voucher_no))