blob: a26812c7a6e15e8925a2e048e10287e41035969a [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)
Nabin Haitb9bc7d62016-05-16 14:38:47 +053016from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
ruthra kumare8889752022-05-16 14:28:25 +053017from erpnext.accounts.utils import create_payment_ledger_entry
Chillar Anand915b3432021-09-02 16:44:59 +053018
Nabin Haitbf495c92013-01-30 12:49:08 +053019
Ankush Menat494bd9e2022-03-28 18:52:46 +053020class ClosedAccountingPeriod(frappe.ValidationError):
21 pass
Nabin Haitbf495c92013-01-30 12:49:08 +053022
Ankush Menat494bd9e2022-03-28 18:52:46 +053023
24def make_gl_entries(
25 gl_map,
26 cancel=False,
27 adv_adj=False,
28 merge_entries=True,
29 update_outstanding="Yes",
30 from_repost=False,
31):
Nabin Hait2e296fa2013-08-28 18:53:11 +053032 if gl_map:
Gursheen Anand22ba1212023-07-17 15:17:53 +053033 make_acc_dimensions_offsetting_entry(gl_map)
Nabin Hait2e296fa2013-08-28 18:53:11 +053034 if not cancel:
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053035 validate_accounting_period(gl_map)
Saqib Ansari95b059a2022-05-11 13:26:15 +053036 validate_disabled_accounts(gl_map)
Nabin Hait2e296fa2013-08-28 18:53:11 +053037 gl_map = process_gl_map(gl_map, merge_entries)
nabinhaitc3432922014-07-29 18:06:18 +053038 if gl_map and len(gl_map) > 1:
ruthra kumar7312f222022-05-29 21:33:08 +053039 create_payment_ledger_entry(
40 gl_map,
41 cancel=0,
42 adv_adj=adv_adj,
43 update_outstanding=update_outstanding,
44 from_repost=from_repost,
45 )
Nabin Haita77b8c92020-12-21 14:45:50 +053046 save_entries(gl_map, adv_adj, update_outstanding, from_repost)
Deepesh Garg4c8d15b2021-04-26 15:24:34 +053047 # Post GL Map proccess there may no be any GL Entries
48 elif gl_map:
Ankush Menat494bd9e2022-03-28 18:52:46 +053049 frappe.throw(
50 _(
51 "Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."
52 )
53 )
Nabin Hait2e296fa2013-08-28 18:53:11 +053054 else:
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +053055 make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
Anand Doshi652bc072014-04-16 15:21:46 +053056
Ankush Menat494bd9e2022-03-28 18:52:46 +053057
Gursheen Anand22ba1212023-07-17 15:17:53 +053058def make_acc_dimensions_offsetting_entry(gl_map):
59 accounting_dimensions_to_offset = get_accounting_dimensions_for_offsetting_entry(gl_map)
Gursheen Anand1e1e4b92023-07-18 12:12:24 +053060 no_of_dimensions = len(accounting_dimensions_to_offset)
61 if no_of_dimensions == 0:
Gursheen Anand22ba1212023-07-17 15:17:53 +053062 return
63
64 offsetting_entries = []
65 for gle in gl_map:
66 for dimension in accounting_dimensions_to_offset:
67 dimension_details = frappe.db.get_values(
68 "Accounting Dimension Detail",
69 {"parent": dimension, "company": gle.company},
70 ["automatically_post_balancing_accounting_entry", "offsetting_account"],
71 )
72 dimension_details = dimension_details[0] if len(dimension_details) > 0 else None
73 if dimension_details and dimension_details[0] == 1:
74 offsetting_account = dimension_details[1]
75 offsetting_entry = gle.copy()
76 offsetting_entry.update(
77 {
78 "account": offsetting_account,
Gursheen Anand1e1e4b92023-07-18 12:12:24 +053079 "debit": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0,
80 "credit": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0,
81 "debit_in_account_currency": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0,
82 "credit_in_account_currency": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0,
Gursheen Anand22ba1212023-07-17 15:17:53 +053083 "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension),
84 "against_voucher": None,
85 }
86 )
87 offsetting_entry["against_voucher_type"] = None
88 offsetting_entries.append(offsetting_entry)
89 gl_map += offsetting_entries
90
91
92def get_accounting_dimensions_for_offsetting_entry(gl_map):
93 acc_dimensions = frappe.db.get_list("Accounting Dimension", {"disabled": 0}, pluck="name")
94 accounting_dimensions_to_offset = []
95 for acc_dimension in acc_dimensions:
96 fieldname = acc_dimension.lower().replace(" ", "_")
Gursheen Ananded3bef12023-07-17 18:40:52 +053097 values = set([entry.get(fieldname) for entry in gl_map])
Gursheen Anand22ba1212023-07-17 15:17:53 +053098 if len(values) > 1:
99 accounting_dimensions_to_offset.append(acc_dimension)
100 return accounting_dimensions_to_offset
101
102
Saqib Ansari95b059a2022-05-11 13:26:15 +0530103def validate_disabled_accounts(gl_map):
104 accounts = [d.account for d in gl_map if d.account]
105
106 Account = frappe.qb.DocType("Account")
107
108 disabled_accounts = (
109 frappe.qb.from_(Account)
110 .where(Account.name.isin(accounts) & Account.disabled == 1)
111 .select(Account.name, Account.disabled)
112 ).run(as_dict=True)
113
114 if disabled_accounts:
115 account_list = "<br>"
116 account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts])
117 frappe.throw(
118 _("Cannot create accounting entries against disabled accounts: {0}").format(account_list),
119 title=_("Disabled Account Selected"),
120 )
121
122
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530123def validate_accounting_period(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530124 accounting_periods = frappe.db.sql(
125 """ SELECT
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530126 ap.name as name
127 FROM
128 `tabAccounting Period` ap, `tabClosed Document` cd
129 WHERE
130 ap.name = cd.parent
131 AND ap.company = %(company)s
132 AND cd.closed = 1
133 AND cd.document_type = %(voucher_type)s
134 AND %(date)s between ap.start_date and ap.end_date
Ankush Menat494bd9e2022-03-28 18:52:46 +0530135 """,
136 {
137 "date": gl_map[0].posting_date,
138 "company": gl_map[0].company,
139 "voucher_type": gl_map[0].voucher_type,
140 },
141 as_dict=1,
142 )
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530143
144 if accounting_periods:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530145 frappe.throw(
146 _(
147 "You cannot create or cancel any accounting entries with in the closed Accounting Period {0}"
148 ).format(frappe.bold(accounting_periods[0].name)),
149 ClosedAccountingPeriod,
150 )
151
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530152
Nabin Hait19f8fa52021-02-22 22:27:22 +0530153def process_gl_map(gl_map, merge_entries=True, precision=None):
Nabin Hait6099af52022-01-31 17:24:50 +0530154 if not gl_map:
155 return []
156
Nabin Hait004c1ed2022-01-31 13:20:18 +0530157 gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
158
Nabin Haitbf495c92013-01-30 12:49:08 +0530159 if merge_entries:
Nabin Hait19f8fa52021-02-22 22:27:22 +0530160 gl_map = merge_similar_entries(gl_map, precision)
Anand Doshi602e8252015-11-16 19:05:46 +0530161
Nabin Hait004c1ed2022-01-31 13:20:18 +0530162 gl_map = toggle_debit_credit_if_negative(gl_map)
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530163
Nabin Hait27994c22013-08-26 16:53:30 +0530164 return gl_map
Anand Doshi652bc072014-04-16 15:21:46 +0530165
Ankush Menat494bd9e2022-03-28 18:52:46 +0530166
Nabin Hait004c1ed2022-01-31 13:20:18 +0530167def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530168 cost_center_allocation = get_cost_center_allocation_data(
169 gl_map[0]["company"], gl_map[0]["posting_date"]
170 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530171 if not cost_center_allocation:
172 return gl_map
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530173
Nabin Hait004c1ed2022-01-31 13:20:18 +0530174 new_gl_map = []
175 for d in gl_map:
176 cost_center = d.get("cost_center")
Deepesh Garg4e26d422022-10-30 19:33:27 +0530177
178 # Validate budget against main cost center
179 validate_expense_against_budget(
180 d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
181 )
182
Nabin Hait004c1ed2022-01-31 13:20:18 +0530183 if cost_center and cost_center_allocation.get(cost_center):
184 for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
185 gle = copy.deepcopy(d)
186 gle.cost_center = sub_cost_center
ruthra kumar71f6f782022-06-24 13:36:15 +0530187 for field in ("debit", "credit", "debit_in_account_currency", "credit_in_account_currency"):
Nabin Hait004c1ed2022-01-31 13:20:18 +0530188 gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
189 new_gl_map.append(gle)
190 else:
191 new_gl_map.append(d)
192
193 return new_gl_map
194
Ankush Menat494bd9e2022-03-28 18:52:46 +0530195
Nabin Hait004c1ed2022-01-31 13:20:18 +0530196def get_cost_center_allocation_data(company, posting_date):
197 par = frappe.qb.DocType("Cost Center Allocation")
198 child = frappe.qb.DocType("Cost Center Allocation Percentage")
199
200 records = (
Ankush Menat494bd9e2022-03-28 18:52:46 +0530201 frappe.qb.from_(par)
202 .inner_join(child)
203 .on(par.name == child.parent)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530204 .select(par.main_cost_center, child.cost_center, child.percentage)
205 .where(par.docstatus == 1)
206 .where(par.company == company)
207 .where(par.valid_from <= posting_date)
208 .orderby(par.valid_from, order=frappe.qb.desc)
209 ).run(as_dict=True)
210
211 cc_allocation = frappe._dict()
212 for d in records:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530213 cc_allocation.setdefault(d.main_cost_center, frappe._dict()).setdefault(
214 d.cost_center, d.percentage
215 )
Nabin Hait5b0ec642022-01-31 18:07:04 +0530216
Nabin Hait004c1ed2022-01-31 13:20:18 +0530217 return cc_allocation
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530218
Ankush Menat494bd9e2022-03-28 18:52:46 +0530219
Nabin Hait19f8fa52021-02-22 22:27:22 +0530220def merge_similar_entries(gl_map, precision=None):
Nabin Haitbf495c92013-01-30 12:49:08 +0530221 merged_gl_map = []
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530222 accounting_dimensions = get_accounting_dimensions()
Nabin Hait914a3882022-07-19 12:32:54 +0530223
Nabin Haitbf495c92013-01-30 12:49:08 +0530224 for entry in gl_map:
Anand Doshi652bc072014-04-16 15:21:46 +0530225 # if there is already an entry in this account then just add it
Nabin Haitbf495c92013-01-30 12:49:08 +0530226 # to that entry
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530227 same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
Nabin Haitbf495c92013-01-30 12:49:08 +0530228 if same_head:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530229 same_head.debit = flt(same_head.debit) + flt(entry.debit)
230 same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
231 entry.debit_in_account_currency
232 )
Nabin Hait2e296fa2013-08-28 18:53:11 +0530233 same_head.credit = flt(same_head.credit) + flt(entry.credit)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530234 same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
235 entry.credit_in_account_currency
236 )
Nabin Haitbf495c92013-01-30 12:49:08 +0530237 else:
238 merged_gl_map.append(entry)
Anand Doshi652bc072014-04-16 15:21:46 +0530239
thefalconx33bfc43d32019-12-13 15:09:51 +0530240 company = gl_map[0].company if gl_map else erpnext.get_default_company()
241 company_currency = erpnext.get_company_currency(company)
Nabin Hait19f8fa52021-02-22 22:27:22 +0530242
243 if not precision:
244 precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
thefalconx33bfc43d32019-12-13 15:09:51 +0530245
Nabin Hait815a49e2013-08-07 17:00:01 +0530246 # filter zero debit and credit entries
Ankush Menat494bd9e2022-03-28 18:52:46 +0530247 merged_gl_map = filter(
ruthra kumar914b2302023-01-02 14:33:14 +0530248 lambda x: flt(x.debit, precision) != 0
249 or flt(x.credit, precision) != 0
250 or (
251 x.voucher_type == "Journal Entry"
252 and frappe.get_cached_value("Journal Entry", x.voucher_no, "voucher_type")
253 == "Exchange Gain Or Loss"
254 ),
255 merged_gl_map,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530256 )
Achilles Rasquinha908289d2018-03-08 13:10:51 +0530257 merged_gl_map = list(merged_gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530258
Nabin Haitbf495c92013-01-30 12:49:08 +0530259 return merged_gl_map
260
Ankush Menat494bd9e2022-03-28 18:52:46 +0530261
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530262def check_if_in_list(gle, gl_map, dimensions=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530263 account_head_fieldnames = [
264 "voucher_detail_no",
265 "party",
266 "against_voucher",
267 "cost_center",
268 "against_voucher_type",
269 "party_type",
270 "project",
271 "finance_book",
Gursheen Anand442e3f22023-06-16 13:38:47 +0530272 "voucher_no",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530273 ]
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530274
275 if dimensions:
276 account_head_fieldnames = account_head_fieldnames + dimensions
277
Nabin Haitbb777562013-08-29 18:19:37 +0530278 for e in gl_map:
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530279 same_head = True
280 if e.account != gle.account:
281 same_head = False
Ankush91527152021-08-11 11:17:50 +0530282 continue
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530283
284 for fieldname in account_head_fieldnames:
285 if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
286 same_head = False
Ankush91527152021-08-11 11:17:50 +0530287 break
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530288
289 if same_head:
290 return e
Nabin Haitbf495c92013-01-30 12:49:08 +0530291
Ankush Menat494bd9e2022-03-28 18:52:46 +0530292
Nabin Hait004c1ed2022-01-31 13:20:18 +0530293def toggle_debit_credit_if_negative(gl_map):
294 for entry in gl_map:
295 # toggle debit, credit if negative entry
296 if flt(entry.debit) < 0:
297 entry.credit = flt(entry.credit) - flt(entry.debit)
298 entry.debit = 0.0
299
300 if flt(entry.debit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530301 entry.credit_in_account_currency = flt(entry.credit_in_account_currency) - flt(
302 entry.debit_in_account_currency
303 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530304 entry.debit_in_account_currency = 0.0
305
306 if flt(entry.credit) < 0:
307 entry.debit = flt(entry.debit) - flt(entry.credit)
308 entry.credit = 0.0
309
310 if flt(entry.credit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530311 entry.debit_in_account_currency = flt(entry.debit_in_account_currency) - flt(
312 entry.credit_in_account_currency
313 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530314 entry.credit_in_account_currency = 0.0
315
316 update_net_values(entry)
317
318 return gl_map
319
Ankush Menat494bd9e2022-03-28 18:52:46 +0530320
Nabin Hait004c1ed2022-01-31 13:20:18 +0530321def update_net_values(entry):
322 # In some scenarios net value needs to be shown in the ledger
323 # This method updates net values as debit or credit
324 if entry.post_net_value and entry.debit and entry.credit:
325 if entry.debit > entry.credit:
326 entry.debit = entry.debit - entry.credit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530327 entry.debit_in_account_currency = (
328 entry.debit_in_account_currency - entry.credit_in_account_currency
329 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530330 entry.credit = 0
331 entry.credit_in_account_currency = 0
332 else:
333 entry.credit = entry.credit - entry.debit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530334 entry.credit_in_account_currency = (
335 entry.credit_in_account_currency - entry.debit_in_account_currency
336 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530337
338 entry.debit = 0
339 entry.debit_in_account_currency = 0
340
Ankush Menat494bd9e2022-03-28 18:52:46 +0530341
Nabin Haita77b8c92020-12-21 14:45:50 +0530342def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
343 if not from_repost:
344 validate_cwip_accounts(gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530345
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530346 process_debit_credit_difference(gl_map)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530347
348 if gl_map:
349 check_freezing_date(gl_map[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530350 is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
351 if gl_map[0]["voucher_type"] != "Period Closing Voucher":
352 validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530353
Nabin Haitbf495c92013-01-30 12:49:08 +0530354 for entry in gl_map:
Nabin Haita77b8c92020-12-21 14:45:50 +0530355 make_entry(entry, adv_adj, update_outstanding, from_repost)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530356
Nabin Hait914a3882022-07-19 12:32:54 +0530357
Nabin Haita77b8c92020-12-21 14:45:50 +0530358def make_entry(args, adv_adj, update_outstanding, from_repost=False):
Nabin Haitddc3bb22020-03-17 17:04:18 +0530359 gle = frappe.new_doc("GL Entry")
360 gle.update(args)
Anand Doshi6dfd4302015-02-10 14:41:27 +0530361 gle.flags.ignore_permissions = 1
Nabin Haita77b8c92020-12-21 14:45:50 +0530362 gle.flags.from_repost = from_repost
Nabin Hait19f8fa52021-02-22 22:27:22 +0530363 gle.flags.adv_adj = adv_adj
Ankush Menat494bd9e2022-03-28 18:52:46 +0530364 gle.flags.update_outstanding = update_outstanding or "Yes"
Nabin Hait4caaab32022-07-18 17:59:42 +0530365 gle.flags.notify_update = False
Nabin Haitaeba24e2013-08-23 15:17:36 +0530366 gle.submit()
Anand Doshi652bc072014-04-16 15:21:46 +0530367
Nabin Hait4caaab32022-07-18 17:59:42 +0530368 if not from_repost and gle.voucher_type != "Period Closing Voucher":
Nabin Haita77b8c92020-12-21 14:45:50 +0530369 validate_expense_against_budget(args)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530370
Ankush Menat494bd9e2022-03-28 18:52:46 +0530371
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530372def validate_cwip_accounts(gl_map):
Ankush91527152021-08-11 11:17:50 +0530373 """Validate that CWIP account are not used in Journal Entry"""
374 if gl_map and gl_map[0].voucher_type != "Journal Entry":
375 return
Maricad00c5982019-11-12 19:17:43 +0530376
Ankush Menat494bd9e2022-03-28 18:52:46 +0530377 cwip_enabled = any(
378 cint(ac.enable_cwip_accounting)
379 for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
380 )
Ankush91527152021-08-11 11:17:50 +0530381 if cwip_enabled:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530382 cwip_accounts = [
383 d[0]
384 for d in frappe.db.sql(
385 """select name from tabAccount
386 where account_type = 'Capital Work in Progress' and is_group=0"""
387 )
388 ]
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530389
Ankush91527152021-08-11 11:17:50 +0530390 for entry in gl_map:
391 if entry.account in cwip_accounts:
392 frappe.throw(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530393 _(
394 "Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry"
395 ).format(entry.account)
396 )
397
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530398
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530399def process_debit_credit_difference(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530400 precision = get_field_precision(
401 frappe.get_meta("GL Entry").get_field("debit"),
402 currency=frappe.get_cached_value("Company", gl_map[0].company, "default_currency"),
403 )
Anand Doshi602e8252015-11-16 19:05:46 +0530404
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530405 voucher_type = gl_map[0].voucher_type
406 voucher_no = gl_map[0].voucher_no
407 allowance = get_debit_credit_allowance(voucher_type, precision)
408
409 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
ruthra kumar914b2302023-01-02 14:33:14 +0530410
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530411 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530412 if not (
413 voucher_type == "Journal Entry"
414 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
415 == "Exchange Gain Or Loss"
416 ):
417 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530418
419 elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
420 make_round_off_gle(gl_map, debit_credit_diff, precision)
421
422 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
423 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530424 if not (
425 voucher_type == "Journal Entry"
426 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
427 == "Exchange Gain Or Loss"
428 ):
429 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530430
431
432def get_debit_credit_difference(gl_map, precision):
Nabin Haite2c200a2015-05-28 13:00:37 +0530433 debit_credit_diff = 0.0
434 for entry in gl_map:
435 entry.debit = flt(entry.debit, precision)
436 entry.credit = flt(entry.credit, precision)
437 debit_credit_diff += entry.debit - entry.credit
Anand Doshi602e8252015-11-16 19:05:46 +0530438
Nabin Haite2c200a2015-05-28 13:00:37 +0530439 debit_credit_diff = flt(debit_credit_diff, precision)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530440
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530441 return debit_credit_diff
442
443
444def get_debit_credit_allowance(voucher_type, precision):
445 if voucher_type in ("Journal Entry", "Payment Entry"):
Nabin Haitfb24a272016-02-18 19:18:07 +0530446 allowance = 5.0 / (10**precision)
447 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530448 allowance = 0.5
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530449
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530450 return allowance
Anand Doshi602e8252015-11-16 19:05:46 +0530451
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530452
453def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no):
454 frappe.throw(
455 _("Debit and Credit not equal for {0} #{1}. Difference is {2}.").format(
456 voucher_type, voucher_no, debit_credit_diff
457 )
458 )
Anand Doshi602e8252015-11-16 19:05:46 +0530459
Ankush Menat494bd9e2022-03-28 18:52:46 +0530460
Nabin Hait34c551d2019-07-03 10:34:31 +0530461def make_round_off_gle(gl_map, debit_credit_diff, precision):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530462 round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530463 gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
Ankush Menat494bd9e2022-03-28 18:52:46 +0530464 )
Nabin Hait80069a62015-05-28 19:19:59 +0530465 round_off_gle = frappe._dict()
Nabin Hait022d8d52022-11-21 15:16:53 +0530466 round_off_account_exists = False
Zarrar3523b772018-08-14 16:28:14 +0530467
Nabin Hait022d8d52022-11-21 15:16:53 +0530468 if gl_map[0].voucher_type != "Period Closing Voucher":
469 for d in gl_map:
470 if d.account == round_off_account:
471 round_off_gle = d
472 if d.debit:
473 debit_credit_diff -= flt(d.debit) - flt(d.credit)
474 else:
475 debit_credit_diff += flt(d.credit)
476 round_off_account_exists = True
477
478 if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
479 gl_map.remove(round_off_gle)
480 return
Nabin Hait34c551d2019-07-03 10:34:31 +0530481
Zarrar3523b772018-08-14 16:28:14 +0530482 if not round_off_gle:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530483 for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
484 round_off_gle[k] = gl_map[0][k]
Anand Doshi602e8252015-11-16 19:05:46 +0530485
Ankush Menat494bd9e2022-03-28 18:52:46 +0530486 round_off_gle.update(
487 {
488 "account": round_off_account,
489 "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
490 "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
491 "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
492 "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
493 "cost_center": round_off_cost_center,
494 "party_type": None,
495 "party": None,
496 "is_opening": "No",
497 "against_voucher_type": None,
498 "against_voucher": None,
499 }
500 )
Anand Doshi602e8252015-11-16 19:05:46 +0530501
Deepesh Garg015812b2022-04-23 21:40:08 +0530502 update_accounting_dimensions(round_off_gle)
Zarrar3523b772018-08-14 16:28:14 +0530503 if not round_off_account_exists:
504 gl_map.append(round_off_gle)
Nabin Haite2c200a2015-05-28 13:00:37 +0530505
Ankush Menat494bd9e2022-03-28 18:52:46 +0530506
Deepesh Garg015812b2022-04-23 21:40:08 +0530507def update_accounting_dimensions(round_off_gle):
508 dimensions = get_accounting_dimensions()
Deepesh Gargc312cd32022-04-24 18:11:32 +0530509 meta = frappe.get_meta(round_off_gle["voucher_type"])
510 has_all_dimensions = True
Deepesh Garg015812b2022-04-23 21:40:08 +0530511
512 for dimension in dimensions:
Deepesh Gargc312cd32022-04-24 18:11:32 +0530513 if not meta.has_field(dimension):
514 has_all_dimensions = False
515
516 if dimensions and has_all_dimensions:
517 dimension_values = frappe.db.get_value(
Deepesh Garg3fa1c632022-04-25 16:29:26 +0530518 round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
Deepesh Gargc312cd32022-04-24 18:11:32 +0530519 )
520
521 for dimension in dimensions:
522 round_off_gle[dimension] = dimension_values.get(dimension)
Deepesh Garg015812b2022-04-23 21:40:08 +0530523
524
ruthra kumarebe67872023-04-28 14:07:28 +0530525def get_round_off_account_and_cost_center(
526 company, voucher_type, voucher_no, use_company_default=False
527):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530528 round_off_account, round_off_cost_center = frappe.get_cached_value(
529 "Company", company, ["round_off_account", "round_off_cost_center"]
530 ) or [None, None]
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530531
Deepesh Gargc312cd32022-04-24 18:11:32 +0530532 meta = frappe.get_meta(voucher_type)
533
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530534 # Give first preference to parent cost center for round off GLE
ruthra kumarebe67872023-04-28 14:07:28 +0530535 if not use_company_default and meta.has_field("cost_center"):
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530536 parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
537 if parent_cost_center:
538 round_off_cost_center = parent_cost_center
539
Nabin Hait2e4de832017-09-19 14:53:16 +0530540 if not round_off_account:
541 frappe.throw(_("Please mention Round Off Account in Company"))
542
543 if not round_off_cost_center:
544 frappe.throw(_("Please mention Round Off Cost Center in Company"))
545
546 return round_off_account, round_off_cost_center
547
Ankush Menat494bd9e2022-03-28 18:52:46 +0530548
549def make_reverse_gl_entries(
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530550 gl_entries=None,
551 voucher_type=None,
552 voucher_no=None,
553 adv_adj=False,
554 update_outstanding="Yes",
555 partial_cancel=False,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530556):
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530557 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530558 Get original gl entries of the voucher
559 and make reverse gl entries by swapping debit and credit
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530560 """
Anand Doshi652bc072014-04-16 15:21:46 +0530561
Nabin Hait2e296fa2013-08-28 18:53:11 +0530562 if not gl_entries:
Deepesh Gargff574502022-02-06 22:56:12 +0530563 gl_entry = frappe.qb.DocType("GL Entry")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530564 gl_entries = (
565 frappe.qb.from_(gl_entry)
566 .select("*")
567 .where(gl_entry.voucher_type == voucher_type)
568 .where(gl_entry.voucher_no == voucher_no)
569 .where(gl_entry.is_cancelled == 0)
570 .for_update()
571 ).run(as_dict=1)
Nabin Hait56548cb2016-07-01 15:58:39 +0530572
Nabin Hait27994c22013-08-26 16:53:30 +0530573 if gl_entries:
ruthra kumar7312f222022-05-29 21:33:08 +0530574 create_payment_ledger_entry(
Deepesh Garg1e078d02023-06-29 12:18:25 +0530575 gl_entries,
576 cancel=1,
577 adv_adj=adv_adj,
578 update_outstanding=update_outstanding,
579 partial_cancel=partial_cancel,
ruthra kumar7312f222022-05-29 21:33:08 +0530580 )
Deepesh Garg069a54e2020-08-10 16:01:01 +0530581 validate_accounting_period(gl_entries)
Nabin Hait27994c22013-08-26 16:53:30 +0530582 check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530583
584 is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
585 validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530586 if not partial_cancel:
587 set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
Anand Doshi652bc072014-04-16 15:21:46 +0530588
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530589 for entry in gl_entries:
Deepesh Gargffec8652022-02-02 17:14:42 +0530590 new_gle = copy.deepcopy(entry)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530591 new_gle["name"] = None
592 debit = new_gle.get("debit", 0)
593 credit = new_gle.get("credit", 0)
Anand Doshi652bc072014-04-16 15:21:46 +0530594
Ankush Menat494bd9e2022-03-28 18:52:46 +0530595 debit_in_account_currency = new_gle.get("debit_in_account_currency", 0)
596 credit_in_account_currency = new_gle.get("credit_in_account_currency", 0)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530597
Ankush Menat494bd9e2022-03-28 18:52:46 +0530598 new_gle["debit"] = credit
599 new_gle["credit"] = debit
600 new_gle["debit_in_account_currency"] = credit_in_account_currency
601 new_gle["credit_in_account_currency"] = debit_in_account_currency
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530602
Ankush Menat494bd9e2022-03-28 18:52:46 +0530603 new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
604 new_gle["is_cancelled"] = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530605
Ankush Menat494bd9e2022-03-28 18:52:46 +0530606 if new_gle["debit"] or new_gle["credit"]:
Deepesh Gargffec8652022-02-02 17:14:42 +0530607 make_entry(new_gle, adv_adj, "Yes")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530608
609
610def check_freezing_date(posting_date, adv_adj=False):
611 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530612 Nobody can do GL Entries where posting date is before freezing date
613 except authorized person
Deepesh Garg13d2e7b2021-09-16 18:54:57 +0530614
Ankush Menat494bd9e2022-03-28 18:52:46 +0530615 Administrator has all the roles so this check will be bypassed if any role is allowed to post
616 Hence stop admin to bypass if accounts are freezed
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530617 """
618 if not adv_adj:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530619 acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530620 if acc_frozen_upto:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530621 frozen_accounts_modifier = frappe.db.get_value(
622 "Accounts Settings", None, "frozen_accounts_modifier"
623 )
624 if getdate(posting_date) <= getdate(acc_frozen_upto) and (
625 frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator"
626 ):
627 frappe.throw(
628 _("You are not authorized to add or update entries before {0}").format(
629 formatdate(acc_frozen_upto)
630 )
631 )
632
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530633
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530634def validate_against_pcv(is_opening, posting_date, company):
635 if is_opening and frappe.db.exists(
636 "Period Closing Voucher", {"docstatus": 1, "company": company}
637 ):
638 frappe.throw(
639 _("Opening Entry can not be created after Period Closing Voucher is created."),
640 title=_("Invalid Opening Entry"),
641 )
642
643 last_pcv_date = frappe.db.get_value(
644 "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
645 )
646
647 if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
648 message = _("Books have been closed till the period ending on {0}").format(
649 formatdate(last_pcv_date)
650 )
651 message += "</br >"
Deepesh Garg8ce1da12023-03-23 19:14:12 +0530652 message += _("You cannot create/amend any accounting entries till this date.")
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530653 frappe.throw(message, title=_("Period Closed"))
654
655
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530656def set_as_cancel(voucher_type, voucher_no):
657 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530658 Set is_cancelled=1 in all original gl entries for the voucher
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530659 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530660 frappe.db.sql(
661 """UPDATE `tabGL Entry` SET is_cancelled = 1,
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530662 modified=%s, modified_by=%s
663 where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530664 (now(), frappe.session.user, voucher_type, voucher_no),
665 )