blob: 1299c443077fba98e299060cf2beaa388c9ad2bf [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)
60 if len(accounting_dimensions_to_offset) == 0:
61 return
62
63 offsetting_entries = []
64 for gle in gl_map:
65 for dimension in accounting_dimensions_to_offset:
66 dimension_details = frappe.db.get_values(
67 "Accounting Dimension Detail",
68 {"parent": dimension, "company": gle.company},
69 ["automatically_post_balancing_accounting_entry", "offsetting_account"],
70 )
71 dimension_details = dimension_details[0] if len(dimension_details) > 0 else None
72 if dimension_details and dimension_details[0] == 1:
73 offsetting_account = dimension_details[1]
74 offsetting_entry = gle.copy()
75 offsetting_entry.update(
76 {
77 "account": offsetting_account,
78 "debit": flt(gle.credit),
79 "credit": flt(gle.debit),
80 "debit_in_account_currency": flt(gle.credit_in_account_currency),
81 "credit_in_account_currency": flt(gle.debit_in_account_currency),
82 "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension),
83 "against_voucher": None,
84 }
85 )
86 offsetting_entry["against_voucher_type"] = None
87 offsetting_entries.append(offsetting_entry)
88 gl_map += offsetting_entries
89
90
91def get_accounting_dimensions_for_offsetting_entry(gl_map):
92 acc_dimensions = frappe.db.get_list("Accounting Dimension", {"disabled": 0}, pluck="name")
93 accounting_dimensions_to_offset = []
94 for acc_dimension in acc_dimensions:
95 fieldname = acc_dimension.lower().replace(" ", "_")
96 values = set([entry[fieldname] for entry in gl_map])
97 if len(values) > 1:
98 accounting_dimensions_to_offset.append(acc_dimension)
99 return accounting_dimensions_to_offset
100
101
Saqib Ansari95b059a2022-05-11 13:26:15 +0530102def validate_disabled_accounts(gl_map):
103 accounts = [d.account for d in gl_map if d.account]
104
105 Account = frappe.qb.DocType("Account")
106
107 disabled_accounts = (
108 frappe.qb.from_(Account)
109 .where(Account.name.isin(accounts) & Account.disabled == 1)
110 .select(Account.name, Account.disabled)
111 ).run(as_dict=True)
112
113 if disabled_accounts:
114 account_list = "<br>"
115 account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts])
116 frappe.throw(
117 _("Cannot create accounting entries against disabled accounts: {0}").format(account_list),
118 title=_("Disabled Account Selected"),
119 )
120
121
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530122def validate_accounting_period(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530123 accounting_periods = frappe.db.sql(
124 """ SELECT
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530125 ap.name as name
126 FROM
127 `tabAccounting Period` ap, `tabClosed Document` cd
128 WHERE
129 ap.name = cd.parent
130 AND ap.company = %(company)s
131 AND cd.closed = 1
132 AND cd.document_type = %(voucher_type)s
133 AND %(date)s between ap.start_date and ap.end_date
Ankush Menat494bd9e2022-03-28 18:52:46 +0530134 """,
135 {
136 "date": gl_map[0].posting_date,
137 "company": gl_map[0].company,
138 "voucher_type": gl_map[0].voucher_type,
139 },
140 as_dict=1,
141 )
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530142
143 if accounting_periods:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530144 frappe.throw(
145 _(
146 "You cannot create or cancel any accounting entries with in the closed Accounting Period {0}"
147 ).format(frappe.bold(accounting_periods[0].name)),
148 ClosedAccountingPeriod,
149 )
150
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530151
Nabin Hait19f8fa52021-02-22 22:27:22 +0530152def process_gl_map(gl_map, merge_entries=True, precision=None):
Nabin Hait6099af52022-01-31 17:24:50 +0530153 if not gl_map:
154 return []
155
Nabin Hait004c1ed2022-01-31 13:20:18 +0530156 gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
157
Nabin Haitbf495c92013-01-30 12:49:08 +0530158 if merge_entries:
Nabin Hait19f8fa52021-02-22 22:27:22 +0530159 gl_map = merge_similar_entries(gl_map, precision)
Anand Doshi602e8252015-11-16 19:05:46 +0530160
Nabin Hait004c1ed2022-01-31 13:20:18 +0530161 gl_map = toggle_debit_credit_if_negative(gl_map)
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530162
Nabin Hait27994c22013-08-26 16:53:30 +0530163 return gl_map
Anand Doshi652bc072014-04-16 15:21:46 +0530164
Ankush Menat494bd9e2022-03-28 18:52:46 +0530165
Nabin Hait004c1ed2022-01-31 13:20:18 +0530166def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530167 cost_center_allocation = get_cost_center_allocation_data(
168 gl_map[0]["company"], gl_map[0]["posting_date"]
169 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530170 if not cost_center_allocation:
171 return gl_map
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530172
Nabin Hait004c1ed2022-01-31 13:20:18 +0530173 new_gl_map = []
174 for d in gl_map:
175 cost_center = d.get("cost_center")
Deepesh Garg4e26d422022-10-30 19:33:27 +0530176
177 # Validate budget against main cost center
178 validate_expense_against_budget(
179 d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
180 )
181
Nabin Hait004c1ed2022-01-31 13:20:18 +0530182 if cost_center and cost_center_allocation.get(cost_center):
183 for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
184 gle = copy.deepcopy(d)
185 gle.cost_center = sub_cost_center
ruthra kumar71f6f782022-06-24 13:36:15 +0530186 for field in ("debit", "credit", "debit_in_account_currency", "credit_in_account_currency"):
Nabin Hait004c1ed2022-01-31 13:20:18 +0530187 gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
188 new_gl_map.append(gle)
189 else:
190 new_gl_map.append(d)
191
192 return new_gl_map
193
Ankush Menat494bd9e2022-03-28 18:52:46 +0530194
Nabin Hait004c1ed2022-01-31 13:20:18 +0530195def get_cost_center_allocation_data(company, posting_date):
196 par = frappe.qb.DocType("Cost Center Allocation")
197 child = frappe.qb.DocType("Cost Center Allocation Percentage")
198
199 records = (
Ankush Menat494bd9e2022-03-28 18:52:46 +0530200 frappe.qb.from_(par)
201 .inner_join(child)
202 .on(par.name == child.parent)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530203 .select(par.main_cost_center, child.cost_center, child.percentage)
204 .where(par.docstatus == 1)
205 .where(par.company == company)
206 .where(par.valid_from <= posting_date)
207 .orderby(par.valid_from, order=frappe.qb.desc)
208 ).run(as_dict=True)
209
210 cc_allocation = frappe._dict()
211 for d in records:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530212 cc_allocation.setdefault(d.main_cost_center, frappe._dict()).setdefault(
213 d.cost_center, d.percentage
214 )
Nabin Hait5b0ec642022-01-31 18:07:04 +0530215
Nabin Hait004c1ed2022-01-31 13:20:18 +0530216 return cc_allocation
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530217
Ankush Menat494bd9e2022-03-28 18:52:46 +0530218
Nabin Hait19f8fa52021-02-22 22:27:22 +0530219def merge_similar_entries(gl_map, precision=None):
Nabin Haitbf495c92013-01-30 12:49:08 +0530220 merged_gl_map = []
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530221 accounting_dimensions = get_accounting_dimensions()
Nabin Hait914a3882022-07-19 12:32:54 +0530222
Nabin Haitbf495c92013-01-30 12:49:08 +0530223 for entry in gl_map:
Anand Doshi652bc072014-04-16 15:21:46 +0530224 # if there is already an entry in this account then just add it
Nabin Haitbf495c92013-01-30 12:49:08 +0530225 # to that entry
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530226 same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
Nabin Haitbf495c92013-01-30 12:49:08 +0530227 if same_head:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530228 same_head.debit = flt(same_head.debit) + flt(entry.debit)
229 same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
230 entry.debit_in_account_currency
231 )
Nabin Hait2e296fa2013-08-28 18:53:11 +0530232 same_head.credit = flt(same_head.credit) + flt(entry.credit)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530233 same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
234 entry.credit_in_account_currency
235 )
Nabin Haitbf495c92013-01-30 12:49:08 +0530236 else:
237 merged_gl_map.append(entry)
Anand Doshi652bc072014-04-16 15:21:46 +0530238
thefalconx33bfc43d32019-12-13 15:09:51 +0530239 company = gl_map[0].company if gl_map else erpnext.get_default_company()
240 company_currency = erpnext.get_company_currency(company)
Nabin Hait19f8fa52021-02-22 22:27:22 +0530241
242 if not precision:
243 precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
thefalconx33bfc43d32019-12-13 15:09:51 +0530244
Nabin Hait815a49e2013-08-07 17:00:01 +0530245 # filter zero debit and credit entries
Ankush Menat494bd9e2022-03-28 18:52:46 +0530246 merged_gl_map = filter(
ruthra kumar914b2302023-01-02 14:33:14 +0530247 lambda x: flt(x.debit, precision) != 0
248 or flt(x.credit, precision) != 0
249 or (
250 x.voucher_type == "Journal Entry"
251 and frappe.get_cached_value("Journal Entry", x.voucher_no, "voucher_type")
252 == "Exchange Gain Or Loss"
253 ),
254 merged_gl_map,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530255 )
Achilles Rasquinha908289d2018-03-08 13:10:51 +0530256 merged_gl_map = list(merged_gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530257
Nabin Haitbf495c92013-01-30 12:49:08 +0530258 return merged_gl_map
259
Ankush Menat494bd9e2022-03-28 18:52:46 +0530260
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530261def check_if_in_list(gle, gl_map, dimensions=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530262 account_head_fieldnames = [
263 "voucher_detail_no",
264 "party",
265 "against_voucher",
266 "cost_center",
267 "against_voucher_type",
268 "party_type",
269 "project",
270 "finance_book",
Gursheen Anand442e3f22023-06-16 13:38:47 +0530271 "voucher_no",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530272 ]
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530273
274 if dimensions:
275 account_head_fieldnames = account_head_fieldnames + dimensions
276
Nabin Haitbb777562013-08-29 18:19:37 +0530277 for e in gl_map:
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530278 same_head = True
279 if e.account != gle.account:
280 same_head = False
Ankush91527152021-08-11 11:17:50 +0530281 continue
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530282
283 for fieldname in account_head_fieldnames:
284 if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
285 same_head = False
Ankush91527152021-08-11 11:17:50 +0530286 break
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530287
288 if same_head:
289 return e
Nabin Haitbf495c92013-01-30 12:49:08 +0530290
Ankush Menat494bd9e2022-03-28 18:52:46 +0530291
Nabin Hait004c1ed2022-01-31 13:20:18 +0530292def toggle_debit_credit_if_negative(gl_map):
293 for entry in gl_map:
294 # toggle debit, credit if negative entry
295 if flt(entry.debit) < 0:
296 entry.credit = flt(entry.credit) - flt(entry.debit)
297 entry.debit = 0.0
298
299 if flt(entry.debit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530300 entry.credit_in_account_currency = flt(entry.credit_in_account_currency) - flt(
301 entry.debit_in_account_currency
302 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530303 entry.debit_in_account_currency = 0.0
304
305 if flt(entry.credit) < 0:
306 entry.debit = flt(entry.debit) - flt(entry.credit)
307 entry.credit = 0.0
308
309 if flt(entry.credit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530310 entry.debit_in_account_currency = flt(entry.debit_in_account_currency) - flt(
311 entry.credit_in_account_currency
312 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530313 entry.credit_in_account_currency = 0.0
314
315 update_net_values(entry)
316
317 return gl_map
318
Ankush Menat494bd9e2022-03-28 18:52:46 +0530319
Nabin Hait004c1ed2022-01-31 13:20:18 +0530320def update_net_values(entry):
321 # In some scenarios net value needs to be shown in the ledger
322 # This method updates net values as debit or credit
323 if entry.post_net_value and entry.debit and entry.credit:
324 if entry.debit > entry.credit:
325 entry.debit = entry.debit - entry.credit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530326 entry.debit_in_account_currency = (
327 entry.debit_in_account_currency - entry.credit_in_account_currency
328 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530329 entry.credit = 0
330 entry.credit_in_account_currency = 0
331 else:
332 entry.credit = entry.credit - entry.debit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530333 entry.credit_in_account_currency = (
334 entry.credit_in_account_currency - entry.debit_in_account_currency
335 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530336
337 entry.debit = 0
338 entry.debit_in_account_currency = 0
339
Ankush Menat494bd9e2022-03-28 18:52:46 +0530340
Nabin Haita77b8c92020-12-21 14:45:50 +0530341def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
342 if not from_repost:
343 validate_cwip_accounts(gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530344
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530345 process_debit_credit_difference(gl_map)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530346
347 if gl_map:
348 check_freezing_date(gl_map[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530349 is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
350 if gl_map[0]["voucher_type"] != "Period Closing Voucher":
351 validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530352
Nabin Haitbf495c92013-01-30 12:49:08 +0530353 for entry in gl_map:
Nabin Haita77b8c92020-12-21 14:45:50 +0530354 make_entry(entry, adv_adj, update_outstanding, from_repost)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530355
Nabin Hait914a3882022-07-19 12:32:54 +0530356
Nabin Haita77b8c92020-12-21 14:45:50 +0530357def make_entry(args, adv_adj, update_outstanding, from_repost=False):
Nabin Haitddc3bb22020-03-17 17:04:18 +0530358 gle = frappe.new_doc("GL Entry")
359 gle.update(args)
Anand Doshi6dfd4302015-02-10 14:41:27 +0530360 gle.flags.ignore_permissions = 1
Nabin Haita77b8c92020-12-21 14:45:50 +0530361 gle.flags.from_repost = from_repost
Nabin Hait19f8fa52021-02-22 22:27:22 +0530362 gle.flags.adv_adj = adv_adj
Ankush Menat494bd9e2022-03-28 18:52:46 +0530363 gle.flags.update_outstanding = update_outstanding or "Yes"
Nabin Hait4caaab32022-07-18 17:59:42 +0530364 gle.flags.notify_update = False
Nabin Haitaeba24e2013-08-23 15:17:36 +0530365 gle.submit()
Anand Doshi652bc072014-04-16 15:21:46 +0530366
Nabin Hait4caaab32022-07-18 17:59:42 +0530367 if not from_repost and gle.voucher_type != "Period Closing Voucher":
Nabin Haita77b8c92020-12-21 14:45:50 +0530368 validate_expense_against_budget(args)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530369
Ankush Menat494bd9e2022-03-28 18:52:46 +0530370
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530371def validate_cwip_accounts(gl_map):
Ankush91527152021-08-11 11:17:50 +0530372 """Validate that CWIP account are not used in Journal Entry"""
373 if gl_map and gl_map[0].voucher_type != "Journal Entry":
374 return
Maricad00c5982019-11-12 19:17:43 +0530375
Ankush Menat494bd9e2022-03-28 18:52:46 +0530376 cwip_enabled = any(
377 cint(ac.enable_cwip_accounting)
378 for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
379 )
Ankush91527152021-08-11 11:17:50 +0530380 if cwip_enabled:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530381 cwip_accounts = [
382 d[0]
383 for d in frappe.db.sql(
384 """select name from tabAccount
385 where account_type = 'Capital Work in Progress' and is_group=0"""
386 )
387 ]
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530388
Ankush91527152021-08-11 11:17:50 +0530389 for entry in gl_map:
390 if entry.account in cwip_accounts:
391 frappe.throw(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530392 _(
393 "Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry"
394 ).format(entry.account)
395 )
396
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530397
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530398def process_debit_credit_difference(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530399 precision = get_field_precision(
400 frappe.get_meta("GL Entry").get_field("debit"),
401 currency=frappe.get_cached_value("Company", gl_map[0].company, "default_currency"),
402 )
Anand Doshi602e8252015-11-16 19:05:46 +0530403
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530404 voucher_type = gl_map[0].voucher_type
405 voucher_no = gl_map[0].voucher_no
406 allowance = get_debit_credit_allowance(voucher_type, precision)
407
408 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
ruthra kumar914b2302023-01-02 14:33:14 +0530409
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530410 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530411 if not (
412 voucher_type == "Journal Entry"
413 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
414 == "Exchange Gain Or Loss"
415 ):
416 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530417
418 elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
419 make_round_off_gle(gl_map, debit_credit_diff, precision)
420
421 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
422 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530423 if not (
424 voucher_type == "Journal Entry"
425 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
426 == "Exchange Gain Or Loss"
427 ):
428 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530429
430
431def get_debit_credit_difference(gl_map, precision):
Nabin Haite2c200a2015-05-28 13:00:37 +0530432 debit_credit_diff = 0.0
433 for entry in gl_map:
434 entry.debit = flt(entry.debit, precision)
435 entry.credit = flt(entry.credit, precision)
436 debit_credit_diff += entry.debit - entry.credit
Anand Doshi602e8252015-11-16 19:05:46 +0530437
Nabin Haite2c200a2015-05-28 13:00:37 +0530438 debit_credit_diff = flt(debit_credit_diff, precision)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530439
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530440 return debit_credit_diff
441
442
443def get_debit_credit_allowance(voucher_type, precision):
444 if voucher_type in ("Journal Entry", "Payment Entry"):
Nabin Haitfb24a272016-02-18 19:18:07 +0530445 allowance = 5.0 / (10**precision)
446 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530447 allowance = 0.5
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530448
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530449 return allowance
Anand Doshi602e8252015-11-16 19:05:46 +0530450
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530451
452def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no):
453 frappe.throw(
454 _("Debit and Credit not equal for {0} #{1}. Difference is {2}.").format(
455 voucher_type, voucher_no, debit_credit_diff
456 )
457 )
Anand Doshi602e8252015-11-16 19:05:46 +0530458
Ankush Menat494bd9e2022-03-28 18:52:46 +0530459
Nabin Hait34c551d2019-07-03 10:34:31 +0530460def make_round_off_gle(gl_map, debit_credit_diff, precision):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530461 round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530462 gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
Ankush Menat494bd9e2022-03-28 18:52:46 +0530463 )
Nabin Hait80069a62015-05-28 19:19:59 +0530464 round_off_gle = frappe._dict()
Nabin Hait022d8d52022-11-21 15:16:53 +0530465 round_off_account_exists = False
Zarrar3523b772018-08-14 16:28:14 +0530466
Nabin Hait022d8d52022-11-21 15:16:53 +0530467 if gl_map[0].voucher_type != "Period Closing Voucher":
468 for d in gl_map:
469 if d.account == round_off_account:
470 round_off_gle = d
471 if d.debit:
472 debit_credit_diff -= flt(d.debit) - flt(d.credit)
473 else:
474 debit_credit_diff += flt(d.credit)
475 round_off_account_exists = True
476
477 if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
478 gl_map.remove(round_off_gle)
479 return
Nabin Hait34c551d2019-07-03 10:34:31 +0530480
Zarrar3523b772018-08-14 16:28:14 +0530481 if not round_off_gle:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530482 for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
483 round_off_gle[k] = gl_map[0][k]
Anand Doshi602e8252015-11-16 19:05:46 +0530484
Ankush Menat494bd9e2022-03-28 18:52:46 +0530485 round_off_gle.update(
486 {
487 "account": round_off_account,
488 "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
489 "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
490 "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
491 "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
492 "cost_center": round_off_cost_center,
493 "party_type": None,
494 "party": None,
495 "is_opening": "No",
496 "against_voucher_type": None,
497 "against_voucher": None,
498 }
499 )
Anand Doshi602e8252015-11-16 19:05:46 +0530500
Deepesh Garg015812b2022-04-23 21:40:08 +0530501 update_accounting_dimensions(round_off_gle)
Zarrar3523b772018-08-14 16:28:14 +0530502 if not round_off_account_exists:
503 gl_map.append(round_off_gle)
Nabin Haite2c200a2015-05-28 13:00:37 +0530504
Ankush Menat494bd9e2022-03-28 18:52:46 +0530505
Deepesh Garg015812b2022-04-23 21:40:08 +0530506def update_accounting_dimensions(round_off_gle):
507 dimensions = get_accounting_dimensions()
Deepesh Gargc312cd32022-04-24 18:11:32 +0530508 meta = frappe.get_meta(round_off_gle["voucher_type"])
509 has_all_dimensions = True
Deepesh Garg015812b2022-04-23 21:40:08 +0530510
511 for dimension in dimensions:
Deepesh Gargc312cd32022-04-24 18:11:32 +0530512 if not meta.has_field(dimension):
513 has_all_dimensions = False
514
515 if dimensions and has_all_dimensions:
516 dimension_values = frappe.db.get_value(
Deepesh Garg3fa1c632022-04-25 16:29:26 +0530517 round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
Deepesh Gargc312cd32022-04-24 18:11:32 +0530518 )
519
520 for dimension in dimensions:
521 round_off_gle[dimension] = dimension_values.get(dimension)
Deepesh Garg015812b2022-04-23 21:40:08 +0530522
523
ruthra kumarebe67872023-04-28 14:07:28 +0530524def get_round_off_account_and_cost_center(
525 company, voucher_type, voucher_no, use_company_default=False
526):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530527 round_off_account, round_off_cost_center = frappe.get_cached_value(
528 "Company", company, ["round_off_account", "round_off_cost_center"]
529 ) or [None, None]
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530530
Deepesh Gargc312cd32022-04-24 18:11:32 +0530531 meta = frappe.get_meta(voucher_type)
532
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530533 # Give first preference to parent cost center for round off GLE
ruthra kumarebe67872023-04-28 14:07:28 +0530534 if not use_company_default and meta.has_field("cost_center"):
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530535 parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
536 if parent_cost_center:
537 round_off_cost_center = parent_cost_center
538
Nabin Hait2e4de832017-09-19 14:53:16 +0530539 if not round_off_account:
540 frappe.throw(_("Please mention Round Off Account in Company"))
541
542 if not round_off_cost_center:
543 frappe.throw(_("Please mention Round Off Cost Center in Company"))
544
545 return round_off_account, round_off_cost_center
546
Ankush Menat494bd9e2022-03-28 18:52:46 +0530547
548def make_reverse_gl_entries(
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530549 gl_entries=None,
550 voucher_type=None,
551 voucher_no=None,
552 adv_adj=False,
553 update_outstanding="Yes",
554 partial_cancel=False,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530555):
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530556 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530557 Get original gl entries of the voucher
558 and make reverse gl entries by swapping debit and credit
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530559 """
Anand Doshi652bc072014-04-16 15:21:46 +0530560
Nabin Hait2e296fa2013-08-28 18:53:11 +0530561 if not gl_entries:
Deepesh Gargff574502022-02-06 22:56:12 +0530562 gl_entry = frappe.qb.DocType("GL Entry")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530563 gl_entries = (
564 frappe.qb.from_(gl_entry)
565 .select("*")
566 .where(gl_entry.voucher_type == voucher_type)
567 .where(gl_entry.voucher_no == voucher_no)
568 .where(gl_entry.is_cancelled == 0)
569 .for_update()
570 ).run(as_dict=1)
Nabin Hait56548cb2016-07-01 15:58:39 +0530571
Nabin Hait27994c22013-08-26 16:53:30 +0530572 if gl_entries:
ruthra kumar7312f222022-05-29 21:33:08 +0530573 create_payment_ledger_entry(
Deepesh Garg1e078d02023-06-29 12:18:25 +0530574 gl_entries,
575 cancel=1,
576 adv_adj=adv_adj,
577 update_outstanding=update_outstanding,
578 partial_cancel=partial_cancel,
ruthra kumar7312f222022-05-29 21:33:08 +0530579 )
Deepesh Garg069a54e2020-08-10 16:01:01 +0530580 validate_accounting_period(gl_entries)
Nabin Hait27994c22013-08-26 16:53:30 +0530581 check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530582
583 is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
584 validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530585 if not partial_cancel:
586 set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
Anand Doshi652bc072014-04-16 15:21:46 +0530587
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530588 for entry in gl_entries:
Deepesh Gargffec8652022-02-02 17:14:42 +0530589 new_gle = copy.deepcopy(entry)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530590 new_gle["name"] = None
591 debit = new_gle.get("debit", 0)
592 credit = new_gle.get("credit", 0)
Anand Doshi652bc072014-04-16 15:21:46 +0530593
Ankush Menat494bd9e2022-03-28 18:52:46 +0530594 debit_in_account_currency = new_gle.get("debit_in_account_currency", 0)
595 credit_in_account_currency = new_gle.get("credit_in_account_currency", 0)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530596
Ankush Menat494bd9e2022-03-28 18:52:46 +0530597 new_gle["debit"] = credit
598 new_gle["credit"] = debit
599 new_gle["debit_in_account_currency"] = credit_in_account_currency
600 new_gle["credit_in_account_currency"] = debit_in_account_currency
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530601
Ankush Menat494bd9e2022-03-28 18:52:46 +0530602 new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
603 new_gle["is_cancelled"] = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530604
Ankush Menat494bd9e2022-03-28 18:52:46 +0530605 if new_gle["debit"] or new_gle["credit"]:
Deepesh Gargffec8652022-02-02 17:14:42 +0530606 make_entry(new_gle, adv_adj, "Yes")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530607
608
609def check_freezing_date(posting_date, adv_adj=False):
610 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530611 Nobody can do GL Entries where posting date is before freezing date
612 except authorized person
Deepesh Garg13d2e7b2021-09-16 18:54:57 +0530613
Ankush Menat494bd9e2022-03-28 18:52:46 +0530614 Administrator has all the roles so this check will be bypassed if any role is allowed to post
615 Hence stop admin to bypass if accounts are freezed
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530616 """
617 if not adv_adj:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530618 acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530619 if acc_frozen_upto:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530620 frozen_accounts_modifier = frappe.db.get_value(
621 "Accounts Settings", None, "frozen_accounts_modifier"
622 )
623 if getdate(posting_date) <= getdate(acc_frozen_upto) and (
624 frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator"
625 ):
626 frappe.throw(
627 _("You are not authorized to add or update entries before {0}").format(
628 formatdate(acc_frozen_upto)
629 )
630 )
631
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530632
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530633def validate_against_pcv(is_opening, posting_date, company):
634 if is_opening and frappe.db.exists(
635 "Period Closing Voucher", {"docstatus": 1, "company": company}
636 ):
637 frappe.throw(
638 _("Opening Entry can not be created after Period Closing Voucher is created."),
639 title=_("Invalid Opening Entry"),
640 )
641
642 last_pcv_date = frappe.db.get_value(
643 "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
644 )
645
646 if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
647 message = _("Books have been closed till the period ending on {0}").format(
648 formatdate(last_pcv_date)
649 )
650 message += "</br >"
Deepesh Garg8ce1da12023-03-23 19:14:12 +0530651 message += _("You cannot create/amend any accounting entries till this date.")
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530652 frappe.throw(message, title=_("Period Closed"))
653
654
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530655def set_as_cancel(voucher_type, voucher_no):
656 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530657 Set is_cancelled=1 in all original gl entries for the voucher
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530658 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530659 frappe.db.sql(
660 """UPDATE `tabGL Entry` SET is_cancelled = 1,
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530661 modified=%s, modified_by=%s
662 where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530663 (now(), frappe.session.user, voucher_type, voucher_no),
664 )