blob: b942a0cbfd8114d34de59b88598498badca879fe [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
Chillar Anand915b3432021-09-02 16:44:59 +05304
Nabin Hait004c1ed2022-01-31 13:20:18 +05305import copy
Nabin Hait5b0ec642022-01-31 18:07:04 +05306
7import frappe
Rushabh Mehta793ba6b2014-02-14 15:47:51 +05308from frappe import _
Nabin Haite2c200a2015-05-28 13:00:37 +05309from frappe.model.meta import get_field_precision
Chillar Anand915b3432021-09-02 16:44:59 +053010from frappe.utils import cint, cstr, flt, formatdate, getdate, now
11
12import erpnext
13from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
14 get_accounting_dimensions,
15)
mergify[bot]b4db5e92023-07-18 17:40:49 +053016from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
Nabin Haitb9bc7d62016-05-16 14:38:47 +053017from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
ruthra kumare8889752022-05-16 14:28:25 +053018from erpnext.accounts.utils import create_payment_ledger_entry
Chillar Anand915b3432021-09-02 16:44:59 +053019
Nabin Haitbf495c92013-01-30 12:49:08 +053020
Ankush Menat494bd9e2022-03-28 18:52:46 +053021def make_gl_entries(
22 gl_map,
23 cancel=False,
24 adv_adj=False,
25 merge_entries=True,
26 update_outstanding="Yes",
27 from_repost=False,
28):
Nabin Hait2e296fa2013-08-28 18:53:11 +053029 if gl_map:
30 if not cancel:
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053031 validate_accounting_period(gl_map)
Saqib Ansari95b059a2022-05-11 13:26:15 +053032 validate_disabled_accounts(gl_map)
Nabin Hait2e296fa2013-08-28 18:53:11 +053033 gl_map = process_gl_map(gl_map, merge_entries)
nabinhaitc3432922014-07-29 18:06:18 +053034 if gl_map and len(gl_map) > 1:
ruthra kumar7312f222022-05-29 21:33:08 +053035 create_payment_ledger_entry(
36 gl_map,
37 cancel=0,
38 adv_adj=adv_adj,
39 update_outstanding=update_outstanding,
40 from_repost=from_repost,
41 )
Nabin Haita77b8c92020-12-21 14:45:50 +053042 save_entries(gl_map, adv_adj, update_outstanding, from_repost)
Deepesh Garg4c8d15b2021-04-26 15:24:34 +053043 # Post GL Map proccess there may no be any GL Entries
44 elif gl_map:
Ankush Menat494bd9e2022-03-28 18:52:46 +053045 frappe.throw(
46 _(
47 "Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."
48 )
49 )
Nabin Hait2e296fa2013-08-28 18:53:11 +053050 else:
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +053051 make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
Anand Doshi652bc072014-04-16 15:21:46 +053052
Ankush Menat494bd9e2022-03-28 18:52:46 +053053
Saqib Ansari95b059a2022-05-11 13:26:15 +053054def validate_disabled_accounts(gl_map):
55 accounts = [d.account for d in gl_map if d.account]
56
57 Account = frappe.qb.DocType("Account")
58
59 disabled_accounts = (
60 frappe.qb.from_(Account)
61 .where(Account.name.isin(accounts) & Account.disabled == 1)
62 .select(Account.name, Account.disabled)
63 ).run(as_dict=True)
64
65 if disabled_accounts:
66 account_list = "<br>"
67 account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts])
68 frappe.throw(
69 _("Cannot create accounting entries against disabled accounts: {0}").format(account_list),
70 title=_("Disabled Account Selected"),
71 )
72
73
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053074def validate_accounting_period(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +053075 accounting_periods = frappe.db.sql(
76 """ SELECT
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053077 ap.name as name
78 FROM
79 `tabAccounting Period` ap, `tabClosed Document` cd
80 WHERE
81 ap.name = cd.parent
82 AND ap.company = %(company)s
83 AND cd.closed = 1
84 AND cd.document_type = %(voucher_type)s
85 AND %(date)s between ap.start_date and ap.end_date
Ankush Menat494bd9e2022-03-28 18:52:46 +053086 """,
87 {
88 "date": gl_map[0].posting_date,
89 "company": gl_map[0].company,
90 "voucher_type": gl_map[0].voucher_type,
91 },
92 as_dict=1,
93 )
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053094
95 if accounting_periods:
Ankush Menat494bd9e2022-03-28 18:52:46 +053096 frappe.throw(
97 _(
98 "You cannot create or cancel any accounting entries with in the closed Accounting Period {0}"
99 ).format(frappe.bold(accounting_periods[0].name)),
100 ClosedAccountingPeriod,
101 )
102
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530103
Nabin Hait19f8fa52021-02-22 22:27:22 +0530104def process_gl_map(gl_map, merge_entries=True, precision=None):
Nabin Hait6099af52022-01-31 17:24:50 +0530105 if not gl_map:
106 return []
107
Nabin Hait666d9612023-07-26 13:03:29 +0530108 if gl_map[0].voucher_type != "Period Closing Voucher":
109 gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530110
Nabin Haitbf495c92013-01-30 12:49:08 +0530111 if merge_entries:
Nabin Hait19f8fa52021-02-22 22:27:22 +0530112 gl_map = merge_similar_entries(gl_map, precision)
Anand Doshi602e8252015-11-16 19:05:46 +0530113
Nabin Hait004c1ed2022-01-31 13:20:18 +0530114 gl_map = toggle_debit_credit_if_negative(gl_map)
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530115
Nabin Hait27994c22013-08-26 16:53:30 +0530116 return gl_map
Anand Doshi652bc072014-04-16 15:21:46 +0530117
Ankush Menat494bd9e2022-03-28 18:52:46 +0530118
Nabin Hait004c1ed2022-01-31 13:20:18 +0530119def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530120 cost_center_allocation = get_cost_center_allocation_data(
121 gl_map[0]["company"], gl_map[0]["posting_date"]
122 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530123 if not cost_center_allocation:
124 return gl_map
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530125
Nabin Hait004c1ed2022-01-31 13:20:18 +0530126 new_gl_map = []
127 for d in gl_map:
128 cost_center = d.get("cost_center")
Deepesh Garg4e26d422022-10-30 19:33:27 +0530129
130 # Validate budget against main cost center
131 validate_expense_against_budget(
132 d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
133 )
134
Nabin Hait004c1ed2022-01-31 13:20:18 +0530135 if cost_center and cost_center_allocation.get(cost_center):
136 for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
137 gle = copy.deepcopy(d)
138 gle.cost_center = sub_cost_center
ruthra kumar71f6f782022-06-24 13:36:15 +0530139 for field in ("debit", "credit", "debit_in_account_currency", "credit_in_account_currency"):
Nabin Hait004c1ed2022-01-31 13:20:18 +0530140 gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
141 new_gl_map.append(gle)
142 else:
143 new_gl_map.append(d)
144
145 return new_gl_map
146
Ankush Menat494bd9e2022-03-28 18:52:46 +0530147
Nabin Hait004c1ed2022-01-31 13:20:18 +0530148def get_cost_center_allocation_data(company, posting_date):
149 par = frappe.qb.DocType("Cost Center Allocation")
150 child = frappe.qb.DocType("Cost Center Allocation Percentage")
151
152 records = (
Ankush Menat494bd9e2022-03-28 18:52:46 +0530153 frappe.qb.from_(par)
154 .inner_join(child)
155 .on(par.name == child.parent)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530156 .select(par.main_cost_center, child.cost_center, child.percentage)
157 .where(par.docstatus == 1)
158 .where(par.company == company)
159 .where(par.valid_from <= posting_date)
160 .orderby(par.valid_from, order=frappe.qb.desc)
161 ).run(as_dict=True)
162
163 cc_allocation = frappe._dict()
164 for d in records:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530165 cc_allocation.setdefault(d.main_cost_center, frappe._dict()).setdefault(
166 d.cost_center, d.percentage
167 )
Nabin Hait5b0ec642022-01-31 18:07:04 +0530168
Nabin Hait004c1ed2022-01-31 13:20:18 +0530169 return cc_allocation
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530170
Ankush Menat494bd9e2022-03-28 18:52:46 +0530171
Nabin Hait19f8fa52021-02-22 22:27:22 +0530172def merge_similar_entries(gl_map, precision=None):
Nabin Haitbf495c92013-01-30 12:49:08 +0530173 merged_gl_map = []
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530174 accounting_dimensions = get_accounting_dimensions()
Nabin Hait914a3882022-07-19 12:32:54 +0530175
Nabin Haitbf495c92013-01-30 12:49:08 +0530176 for entry in gl_map:
Anand Doshi652bc072014-04-16 15:21:46 +0530177 # if there is already an entry in this account then just add it
Nabin Haitbf495c92013-01-30 12:49:08 +0530178 # to that entry
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530179 same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
Nabin Haitbf495c92013-01-30 12:49:08 +0530180 if same_head:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530181 same_head.debit = flt(same_head.debit) + flt(entry.debit)
182 same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
183 entry.debit_in_account_currency
184 )
Nabin Hait2e296fa2013-08-28 18:53:11 +0530185 same_head.credit = flt(same_head.credit) + flt(entry.credit)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530186 same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
187 entry.credit_in_account_currency
188 )
Nabin Haitbf495c92013-01-30 12:49:08 +0530189 else:
190 merged_gl_map.append(entry)
Anand Doshi652bc072014-04-16 15:21:46 +0530191
thefalconx33bfc43d32019-12-13 15:09:51 +0530192 company = gl_map[0].company if gl_map else erpnext.get_default_company()
193 company_currency = erpnext.get_company_currency(company)
Nabin Hait19f8fa52021-02-22 22:27:22 +0530194
195 if not precision:
196 precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
thefalconx33bfc43d32019-12-13 15:09:51 +0530197
Nabin Hait815a49e2013-08-07 17:00:01 +0530198 # filter zero debit and credit entries
Ankush Menat494bd9e2022-03-28 18:52:46 +0530199 merged_gl_map = filter(
ruthra kumar914b2302023-01-02 14:33:14 +0530200 lambda x: flt(x.debit, precision) != 0
201 or flt(x.credit, precision) != 0
202 or (
203 x.voucher_type == "Journal Entry"
204 and frappe.get_cached_value("Journal Entry", x.voucher_no, "voucher_type")
205 == "Exchange Gain Or Loss"
206 ),
207 merged_gl_map,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530208 )
Achilles Rasquinha908289d2018-03-08 13:10:51 +0530209 merged_gl_map = list(merged_gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530210
Nabin Haitbf495c92013-01-30 12:49:08 +0530211 return merged_gl_map
212
Ankush Menat494bd9e2022-03-28 18:52:46 +0530213
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530214def check_if_in_list(gle, gl_map, dimensions=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530215 account_head_fieldnames = [
216 "voucher_detail_no",
217 "party",
218 "against_voucher",
219 "cost_center",
220 "against_voucher_type",
221 "party_type",
222 "project",
223 "finance_book",
Gursheen Anand442e3f22023-06-16 13:38:47 +0530224 "voucher_no",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530225 ]
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530226
227 if dimensions:
228 account_head_fieldnames = account_head_fieldnames + dimensions
229
Nabin Haitbb777562013-08-29 18:19:37 +0530230 for e in gl_map:
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530231 same_head = True
232 if e.account != gle.account:
233 same_head = False
Ankush91527152021-08-11 11:17:50 +0530234 continue
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530235
236 for fieldname in account_head_fieldnames:
237 if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
238 same_head = False
Ankush91527152021-08-11 11:17:50 +0530239 break
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530240
241 if same_head:
242 return e
Nabin Haitbf495c92013-01-30 12:49:08 +0530243
Ankush Menat494bd9e2022-03-28 18:52:46 +0530244
Nabin Hait004c1ed2022-01-31 13:20:18 +0530245def toggle_debit_credit_if_negative(gl_map):
246 for entry in gl_map:
247 # toggle debit, credit if negative entry
248 if flt(entry.debit) < 0:
249 entry.credit = flt(entry.credit) - flt(entry.debit)
250 entry.debit = 0.0
251
252 if flt(entry.debit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530253 entry.credit_in_account_currency = flt(entry.credit_in_account_currency) - flt(
254 entry.debit_in_account_currency
255 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530256 entry.debit_in_account_currency = 0.0
257
258 if flt(entry.credit) < 0:
259 entry.debit = flt(entry.debit) - flt(entry.credit)
260 entry.credit = 0.0
261
262 if flt(entry.credit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530263 entry.debit_in_account_currency = flt(entry.debit_in_account_currency) - flt(
264 entry.credit_in_account_currency
265 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530266 entry.credit_in_account_currency = 0.0
267
268 update_net_values(entry)
269
270 return gl_map
271
Ankush Menat494bd9e2022-03-28 18:52:46 +0530272
Nabin Hait004c1ed2022-01-31 13:20:18 +0530273def update_net_values(entry):
274 # In some scenarios net value needs to be shown in the ledger
275 # This method updates net values as debit or credit
276 if entry.post_net_value and entry.debit and entry.credit:
277 if entry.debit > entry.credit:
278 entry.debit = entry.debit - entry.credit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530279 entry.debit_in_account_currency = (
280 entry.debit_in_account_currency - entry.credit_in_account_currency
281 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530282 entry.credit = 0
283 entry.credit_in_account_currency = 0
284 else:
285 entry.credit = entry.credit - entry.debit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530286 entry.credit_in_account_currency = (
287 entry.credit_in_account_currency - entry.debit_in_account_currency
288 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530289
290 entry.debit = 0
291 entry.debit_in_account_currency = 0
292
Ankush Menat494bd9e2022-03-28 18:52:46 +0530293
Nabin Haita77b8c92020-12-21 14:45:50 +0530294def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
295 if not from_repost:
296 validate_cwip_accounts(gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530297
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530298 process_debit_credit_difference(gl_map)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530299
300 if gl_map:
301 check_freezing_date(gl_map[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530302 is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
303 if gl_map[0]["voucher_type"] != "Period Closing Voucher":
304 validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530305
Nabin Haitbf495c92013-01-30 12:49:08 +0530306 for entry in gl_map:
Nabin Haita77b8c92020-12-21 14:45:50 +0530307 make_entry(entry, adv_adj, update_outstanding, from_repost)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530308
Nabin Hait914a3882022-07-19 12:32:54 +0530309
Nabin Haita77b8c92020-12-21 14:45:50 +0530310def make_entry(args, adv_adj, update_outstanding, from_repost=False):
Nabin Haitddc3bb22020-03-17 17:04:18 +0530311 gle = frappe.new_doc("GL Entry")
312 gle.update(args)
Anand Doshi6dfd4302015-02-10 14:41:27 +0530313 gle.flags.ignore_permissions = 1
Nabin Haita77b8c92020-12-21 14:45:50 +0530314 gle.flags.from_repost = from_repost
Nabin Hait19f8fa52021-02-22 22:27:22 +0530315 gle.flags.adv_adj = adv_adj
Ankush Menat494bd9e2022-03-28 18:52:46 +0530316 gle.flags.update_outstanding = update_outstanding or "Yes"
Nabin Hait4caaab32022-07-18 17:59:42 +0530317 gle.flags.notify_update = False
Nabin Haitaeba24e2013-08-23 15:17:36 +0530318 gle.submit()
Anand Doshi652bc072014-04-16 15:21:46 +0530319
Nabin Hait4caaab32022-07-18 17:59:42 +0530320 if not from_repost and gle.voucher_type != "Period Closing Voucher":
Nabin Haita77b8c92020-12-21 14:45:50 +0530321 validate_expense_against_budget(args)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530322
Ankush Menat494bd9e2022-03-28 18:52:46 +0530323
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530324def validate_cwip_accounts(gl_map):
Ankush91527152021-08-11 11:17:50 +0530325 """Validate that CWIP account are not used in Journal Entry"""
326 if gl_map and gl_map[0].voucher_type != "Journal Entry":
327 return
Maricad00c5982019-11-12 19:17:43 +0530328
Ankush Menat494bd9e2022-03-28 18:52:46 +0530329 cwip_enabled = any(
330 cint(ac.enable_cwip_accounting)
331 for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
332 )
Ankush91527152021-08-11 11:17:50 +0530333 if cwip_enabled:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530334 cwip_accounts = [
335 d[0]
336 for d in frappe.db.sql(
337 """select name from tabAccount
338 where account_type = 'Capital Work in Progress' and is_group=0"""
339 )
340 ]
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530341
Ankush91527152021-08-11 11:17:50 +0530342 for entry in gl_map:
343 if entry.account in cwip_accounts:
344 frappe.throw(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530345 _(
346 "Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry"
347 ).format(entry.account)
348 )
349
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530350
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530351def process_debit_credit_difference(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530352 precision = get_field_precision(
353 frappe.get_meta("GL Entry").get_field("debit"),
354 currency=frappe.get_cached_value("Company", gl_map[0].company, "default_currency"),
355 )
Anand Doshi602e8252015-11-16 19:05:46 +0530356
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530357 voucher_type = gl_map[0].voucher_type
358 voucher_no = gl_map[0].voucher_no
359 allowance = get_debit_credit_allowance(voucher_type, precision)
360
361 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
ruthra kumar914b2302023-01-02 14:33:14 +0530362
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530363 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530364 if not (
365 voucher_type == "Journal Entry"
366 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
367 == "Exchange Gain Or Loss"
368 ):
369 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530370
371 elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
372 make_round_off_gle(gl_map, debit_credit_diff, precision)
373
374 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
375 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530376 if not (
377 voucher_type == "Journal Entry"
378 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
379 == "Exchange Gain Or Loss"
380 ):
381 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530382
383
384def get_debit_credit_difference(gl_map, precision):
Nabin Haite2c200a2015-05-28 13:00:37 +0530385 debit_credit_diff = 0.0
386 for entry in gl_map:
387 entry.debit = flt(entry.debit, precision)
388 entry.credit = flt(entry.credit, precision)
389 debit_credit_diff += entry.debit - entry.credit
Anand Doshi602e8252015-11-16 19:05:46 +0530390
Nabin Haite2c200a2015-05-28 13:00:37 +0530391 debit_credit_diff = flt(debit_credit_diff, precision)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530392
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530393 return debit_credit_diff
394
395
396def get_debit_credit_allowance(voucher_type, precision):
397 if voucher_type in ("Journal Entry", "Payment Entry"):
Nabin Haitfb24a272016-02-18 19:18:07 +0530398 allowance = 5.0 / (10**precision)
399 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530400 allowance = 0.5
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530401
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530402 return allowance
Anand Doshi602e8252015-11-16 19:05:46 +0530403
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530404
405def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no):
406 frappe.throw(
407 _("Debit and Credit not equal for {0} #{1}. Difference is {2}.").format(
408 voucher_type, voucher_no, debit_credit_diff
409 )
410 )
Anand Doshi602e8252015-11-16 19:05:46 +0530411
Ankush Menat494bd9e2022-03-28 18:52:46 +0530412
Nabin Hait34c551d2019-07-03 10:34:31 +0530413def make_round_off_gle(gl_map, debit_credit_diff, precision):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530414 round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530415 gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
Ankush Menat494bd9e2022-03-28 18:52:46 +0530416 )
Nabin Hait80069a62015-05-28 19:19:59 +0530417 round_off_gle = frappe._dict()
Nabin Hait022d8d52022-11-21 15:16:53 +0530418 round_off_account_exists = False
Zarrar3523b772018-08-14 16:28:14 +0530419
Nabin Hait022d8d52022-11-21 15:16:53 +0530420 if gl_map[0].voucher_type != "Period Closing Voucher":
421 for d in gl_map:
422 if d.account == round_off_account:
423 round_off_gle = d
424 if d.debit:
425 debit_credit_diff -= flt(d.debit) - flt(d.credit)
426 else:
427 debit_credit_diff += flt(d.credit)
428 round_off_account_exists = True
429
430 if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
431 gl_map.remove(round_off_gle)
432 return
Nabin Hait34c551d2019-07-03 10:34:31 +0530433
Zarrar3523b772018-08-14 16:28:14 +0530434 if not round_off_gle:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530435 for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
436 round_off_gle[k] = gl_map[0][k]
Anand Doshi602e8252015-11-16 19:05:46 +0530437
Ankush Menat494bd9e2022-03-28 18:52:46 +0530438 round_off_gle.update(
439 {
440 "account": round_off_account,
441 "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
442 "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
443 "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
444 "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
445 "cost_center": round_off_cost_center,
446 "party_type": None,
447 "party": None,
448 "is_opening": "No",
449 "against_voucher_type": None,
450 "against_voucher": None,
451 }
452 )
Anand Doshi602e8252015-11-16 19:05:46 +0530453
Deepesh Garg015812b2022-04-23 21:40:08 +0530454 update_accounting_dimensions(round_off_gle)
Zarrar3523b772018-08-14 16:28:14 +0530455 if not round_off_account_exists:
456 gl_map.append(round_off_gle)
Nabin Haite2c200a2015-05-28 13:00:37 +0530457
Ankush Menat494bd9e2022-03-28 18:52:46 +0530458
Deepesh Garg015812b2022-04-23 21:40:08 +0530459def update_accounting_dimensions(round_off_gle):
460 dimensions = get_accounting_dimensions()
Deepesh Gargc312cd32022-04-24 18:11:32 +0530461 meta = frappe.get_meta(round_off_gle["voucher_type"])
462 has_all_dimensions = True
Deepesh Garg015812b2022-04-23 21:40:08 +0530463
464 for dimension in dimensions:
Deepesh Gargc312cd32022-04-24 18:11:32 +0530465 if not meta.has_field(dimension):
466 has_all_dimensions = False
467
468 if dimensions and has_all_dimensions:
469 dimension_values = frappe.db.get_value(
Deepesh Garg3fa1c632022-04-25 16:29:26 +0530470 round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
Deepesh Gargc312cd32022-04-24 18:11:32 +0530471 )
472
473 for dimension in dimensions:
474 round_off_gle[dimension] = dimension_values.get(dimension)
Deepesh Garg015812b2022-04-23 21:40:08 +0530475
476
ruthra kumarebe67872023-04-28 14:07:28 +0530477def get_round_off_account_and_cost_center(
478 company, voucher_type, voucher_no, use_company_default=False
479):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530480 round_off_account, round_off_cost_center = frappe.get_cached_value(
481 "Company", company, ["round_off_account", "round_off_cost_center"]
482 ) or [None, None]
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530483
Deepesh Gargc312cd32022-04-24 18:11:32 +0530484 meta = frappe.get_meta(voucher_type)
485
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530486 # Give first preference to parent cost center for round off GLE
ruthra kumarebe67872023-04-28 14:07:28 +0530487 if not use_company_default and meta.has_field("cost_center"):
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530488 parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
489 if parent_cost_center:
490 round_off_cost_center = parent_cost_center
491
Nabin Hait2e4de832017-09-19 14:53:16 +0530492 if not round_off_account:
493 frappe.throw(_("Please mention Round Off Account in Company"))
494
495 if not round_off_cost_center:
496 frappe.throw(_("Please mention Round Off Cost Center in Company"))
497
498 return round_off_account, round_off_cost_center
499
Ankush Menat494bd9e2022-03-28 18:52:46 +0530500
501def make_reverse_gl_entries(
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530502 gl_entries=None,
503 voucher_type=None,
504 voucher_no=None,
505 adv_adj=False,
506 update_outstanding="Yes",
507 partial_cancel=False,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530508):
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530509 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530510 Get original gl entries of the voucher
511 and make reverse gl entries by swapping debit and credit
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530512 """
Anand Doshi652bc072014-04-16 15:21:46 +0530513
Nabin Hait2e296fa2013-08-28 18:53:11 +0530514 if not gl_entries:
Deepesh Gargff574502022-02-06 22:56:12 +0530515 gl_entry = frappe.qb.DocType("GL Entry")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530516 gl_entries = (
517 frappe.qb.from_(gl_entry)
518 .select("*")
519 .where(gl_entry.voucher_type == voucher_type)
520 .where(gl_entry.voucher_no == voucher_no)
521 .where(gl_entry.is_cancelled == 0)
522 .for_update()
523 ).run(as_dict=1)
Nabin Hait56548cb2016-07-01 15:58:39 +0530524
Nabin Hait27994c22013-08-26 16:53:30 +0530525 if gl_entries:
ruthra kumar7312f222022-05-29 21:33:08 +0530526 create_payment_ledger_entry(
Deepesh Garg1e078d02023-06-29 12:18:25 +0530527 gl_entries,
528 cancel=1,
529 adv_adj=adv_adj,
530 update_outstanding=update_outstanding,
531 partial_cancel=partial_cancel,
ruthra kumar7312f222022-05-29 21:33:08 +0530532 )
Deepesh Garg069a54e2020-08-10 16:01:01 +0530533 validate_accounting_period(gl_entries)
Nabin Hait27994c22013-08-26 16:53:30 +0530534 check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530535
536 is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
537 validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530538 if not partial_cancel:
539 set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
Anand Doshi652bc072014-04-16 15:21:46 +0530540
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530541 for entry in gl_entries:
Deepesh Gargffec8652022-02-02 17:14:42 +0530542 new_gle = copy.deepcopy(entry)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530543 new_gle["name"] = None
544 debit = new_gle.get("debit", 0)
545 credit = new_gle.get("credit", 0)
Anand Doshi652bc072014-04-16 15:21:46 +0530546
Ankush Menat494bd9e2022-03-28 18:52:46 +0530547 debit_in_account_currency = new_gle.get("debit_in_account_currency", 0)
548 credit_in_account_currency = new_gle.get("credit_in_account_currency", 0)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530549
Ankush Menat494bd9e2022-03-28 18:52:46 +0530550 new_gle["debit"] = credit
551 new_gle["credit"] = debit
552 new_gle["debit_in_account_currency"] = credit_in_account_currency
553 new_gle["credit_in_account_currency"] = debit_in_account_currency
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530554
Ankush Menat494bd9e2022-03-28 18:52:46 +0530555 new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
556 new_gle["is_cancelled"] = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530557
Ankush Menat494bd9e2022-03-28 18:52:46 +0530558 if new_gle["debit"] or new_gle["credit"]:
Deepesh Gargffec8652022-02-02 17:14:42 +0530559 make_entry(new_gle, adv_adj, "Yes")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530560
561
562def check_freezing_date(posting_date, adv_adj=False):
563 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530564 Nobody can do GL Entries where posting date is before freezing date
565 except authorized person
Deepesh Garg13d2e7b2021-09-16 18:54:57 +0530566
Ankush Menat494bd9e2022-03-28 18:52:46 +0530567 Administrator has all the roles so this check will be bypassed if any role is allowed to post
568 Hence stop admin to bypass if accounts are freezed
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530569 """
570 if not adv_adj:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530571 acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530572 if acc_frozen_upto:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530573 frozen_accounts_modifier = frappe.db.get_value(
574 "Accounts Settings", None, "frozen_accounts_modifier"
575 )
576 if getdate(posting_date) <= getdate(acc_frozen_upto) and (
577 frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator"
578 ):
579 frappe.throw(
580 _("You are not authorized to add or update entries before {0}").format(
581 formatdate(acc_frozen_upto)
582 )
583 )
584
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530585
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530586def validate_against_pcv(is_opening, posting_date, company):
587 if is_opening and frappe.db.exists(
588 "Period Closing Voucher", {"docstatus": 1, "company": company}
589 ):
590 frappe.throw(
591 _("Opening Entry can not be created after Period Closing Voucher is created."),
592 title=_("Invalid Opening Entry"),
593 )
594
595 last_pcv_date = frappe.db.get_value(
596 "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
597 )
598
599 if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
600 message = _("Books have been closed till the period ending on {0}").format(
601 formatdate(last_pcv_date)
602 )
603 message += "</br >"
Deepesh Garg8ce1da12023-03-23 19:14:12 +0530604 message += _("You cannot create/amend any accounting entries till this date.")
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530605 frappe.throw(message, title=_("Period Closed"))
606
607
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530608def set_as_cancel(voucher_type, voucher_no):
609 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530610 Set is_cancelled=1 in all original gl entries for the voucher
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530611 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530612 frappe.db.sql(
613 """UPDATE `tabGL Entry` SET is_cancelled = 1,
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530614 modified=%s, modified_by=%s
615 where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530616 (now(), frappe.session.user, voucher_type, voucher_no),
617 )