blob: d4967785ba08acd0afdd6be3d9baa76234144d75 [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:
Deepesh Gargecca9cb2023-07-29 11:52:54 +053031 make_acc_dimensions_offsetting_entry(gl_map)
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053032 validate_accounting_period(gl_map)
Saqib Ansari95b059a2022-05-11 13:26:15 +053033 validate_disabled_accounts(gl_map)
Nabin Hait2e296fa2013-08-28 18:53:11 +053034 gl_map = process_gl_map(gl_map, merge_entries)
nabinhaitc3432922014-07-29 18:06:18 +053035 if gl_map and len(gl_map) > 1:
ruthra kumar7312f222022-05-29 21:33:08 +053036 create_payment_ledger_entry(
37 gl_map,
38 cancel=0,
39 adv_adj=adv_adj,
40 update_outstanding=update_outstanding,
41 from_repost=from_repost,
42 )
Nabin Haita77b8c92020-12-21 14:45:50 +053043 save_entries(gl_map, adv_adj, update_outstanding, from_repost)
Deepesh Garg4c8d15b2021-04-26 15:24:34 +053044 # Post GL Map proccess there may no be any GL Entries
45 elif gl_map:
Ankush Menat494bd9e2022-03-28 18:52:46 +053046 frappe.throw(
47 _(
48 "Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."
49 )
50 )
Nabin Hait2e296fa2013-08-28 18:53:11 +053051 else:
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +053052 make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
Anand Doshi652bc072014-04-16 15:21:46 +053053
Ankush Menat494bd9e2022-03-28 18:52:46 +053054
Gursheen Anand22ba1212023-07-17 15:17:53 +053055def make_acc_dimensions_offsetting_entry(gl_map):
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053056 accounting_dimensions_to_offset = get_accounting_dimensions_for_offsetting_entry(
57 gl_map, gl_map[0].company
58 )
Gursheen Anand1e1e4b92023-07-18 12:12:24 +053059 no_of_dimensions = len(accounting_dimensions_to_offset)
60 if no_of_dimensions == 0:
Gursheen Anand22ba1212023-07-17 15:17:53 +053061 return
62
63 offsetting_entries = []
Deepesh Gargecca9cb2023-07-29 11:52:54 +053064
Gursheen Anand22ba1212023-07-17 15:17:53 +053065 for gle in gl_map:
66 for dimension in accounting_dimensions_to_offset:
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053067 offsetting_entry = gle.copy()
Gursheen Anand4f9242d2023-07-27 15:45:48 +053068 debit = flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0
69 credit = flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053070 offsetting_entry.update(
71 {
Gursheen Anand4f9242d2023-07-27 15:45:48 +053072 "account": dimension.offsetting_account,
73 "debit": debit,
74 "credit": credit,
75 "debit_in_account_currency": debit,
76 "credit_in_account_currency": credit,
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053077 "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension.name),
78 "against_voucher": None,
79 }
Gursheen Anand22ba1212023-07-17 15:17:53 +053080 )
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053081 offsetting_entry["against_voucher_type"] = None
82 offsetting_entries.append(offsetting_entry)
Deepesh Gargecca9cb2023-07-29 11:52:54 +053083
Gursheen Anand22ba1212023-07-17 15:17:53 +053084 gl_map += offsetting_entries
85
86
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053087def get_accounting_dimensions_for_offsetting_entry(gl_map, company):
88 acc_dimension = frappe.qb.DocType("Accounting Dimension")
89 dimension_detail = frappe.qb.DocType("Accounting Dimension Detail")
Deepesh Gargecca9cb2023-07-29 11:52:54 +053090
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053091 acc_dimensions = (
92 frappe.qb.from_(acc_dimension)
93 .inner_join(dimension_detail)
94 .on(acc_dimension.name == dimension_detail.parent)
Gursheen Anand4f9242d2023-07-27 15:45:48 +053095 .select(acc_dimension.fieldname, acc_dimension.name, dimension_detail.offsetting_account)
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053096 .where(
97 (acc_dimension.disabled == 0)
98 & (dimension_detail.company == company)
99 & (dimension_detail.automatically_post_balancing_accounting_entry == 1)
100 )
101 ).run(as_dict=True)
Deepesh Gargecca9cb2023-07-29 11:52:54 +0530102
Gursheen Anand22ba1212023-07-17 15:17:53 +0530103 accounting_dimensions_to_offset = []
104 for acc_dimension in acc_dimensions:
Gursheen Anande19a6f52023-07-19 12:26:57 +0530105 values = set([entry.get(acc_dimension.fieldname) for entry in gl_map])
Gursheen Anand22ba1212023-07-17 15:17:53 +0530106 if len(values) > 1:
107 accounting_dimensions_to_offset.append(acc_dimension)
Deepesh Gargecca9cb2023-07-29 11:52:54 +0530108
Gursheen Anand22ba1212023-07-17 15:17:53 +0530109 return accounting_dimensions_to_offset
110
111
Saqib Ansari95b059a2022-05-11 13:26:15 +0530112def validate_disabled_accounts(gl_map):
113 accounts = [d.account for d in gl_map if d.account]
114
115 Account = frappe.qb.DocType("Account")
116
117 disabled_accounts = (
118 frappe.qb.from_(Account)
119 .where(Account.name.isin(accounts) & Account.disabled == 1)
120 .select(Account.name, Account.disabled)
121 ).run(as_dict=True)
122
123 if disabled_accounts:
124 account_list = "<br>"
125 account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts])
126 frappe.throw(
127 _("Cannot create accounting entries against disabled accounts: {0}").format(account_list),
128 title=_("Disabled Account Selected"),
129 )
130
131
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530132def validate_accounting_period(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530133 accounting_periods = frappe.db.sql(
134 """ SELECT
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530135 ap.name as name
136 FROM
137 `tabAccounting Period` ap, `tabClosed Document` cd
138 WHERE
139 ap.name = cd.parent
140 AND ap.company = %(company)s
141 AND cd.closed = 1
142 AND cd.document_type = %(voucher_type)s
143 AND %(date)s between ap.start_date and ap.end_date
Ankush Menat494bd9e2022-03-28 18:52:46 +0530144 """,
145 {
146 "date": gl_map[0].posting_date,
147 "company": gl_map[0].company,
148 "voucher_type": gl_map[0].voucher_type,
149 },
150 as_dict=1,
151 )
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530152
153 if accounting_periods:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530154 frappe.throw(
155 _(
156 "You cannot create or cancel any accounting entries with in the closed Accounting Period {0}"
157 ).format(frappe.bold(accounting_periods[0].name)),
158 ClosedAccountingPeriod,
159 )
160
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530161
Nabin Hait19f8fa52021-02-22 22:27:22 +0530162def process_gl_map(gl_map, merge_entries=True, precision=None):
Nabin Hait6099af52022-01-31 17:24:50 +0530163 if not gl_map:
164 return []
165
Nabin Hait666d9612023-07-26 13:03:29 +0530166 if gl_map[0].voucher_type != "Period Closing Voucher":
167 gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530168
Nabin Haitbf495c92013-01-30 12:49:08 +0530169 if merge_entries:
Nabin Hait19f8fa52021-02-22 22:27:22 +0530170 gl_map = merge_similar_entries(gl_map, precision)
Anand Doshi602e8252015-11-16 19:05:46 +0530171
Nabin Hait004c1ed2022-01-31 13:20:18 +0530172 gl_map = toggle_debit_credit_if_negative(gl_map)
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530173
Nabin Hait27994c22013-08-26 16:53:30 +0530174 return gl_map
Anand Doshi652bc072014-04-16 15:21:46 +0530175
Ankush Menat494bd9e2022-03-28 18:52:46 +0530176
Nabin Hait004c1ed2022-01-31 13:20:18 +0530177def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530178 cost_center_allocation = get_cost_center_allocation_data(
179 gl_map[0]["company"], gl_map[0]["posting_date"]
180 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530181 if not cost_center_allocation:
182 return gl_map
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530183
Nabin Hait004c1ed2022-01-31 13:20:18 +0530184 new_gl_map = []
185 for d in gl_map:
186 cost_center = d.get("cost_center")
Deepesh Garg4e26d422022-10-30 19:33:27 +0530187
188 # Validate budget against main cost center
189 validate_expense_against_budget(
190 d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
191 )
192
Nabin Hait004c1ed2022-01-31 13:20:18 +0530193 if cost_center and cost_center_allocation.get(cost_center):
194 for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
195 gle = copy.deepcopy(d)
196 gle.cost_center = sub_cost_center
ruthra kumar71f6f782022-06-24 13:36:15 +0530197 for field in ("debit", "credit", "debit_in_account_currency", "credit_in_account_currency"):
Nabin Hait004c1ed2022-01-31 13:20:18 +0530198 gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
199 new_gl_map.append(gle)
200 else:
201 new_gl_map.append(d)
202
203 return new_gl_map
204
Ankush Menat494bd9e2022-03-28 18:52:46 +0530205
Nabin Hait004c1ed2022-01-31 13:20:18 +0530206def get_cost_center_allocation_data(company, posting_date):
207 par = frappe.qb.DocType("Cost Center Allocation")
208 child = frappe.qb.DocType("Cost Center Allocation Percentage")
209
210 records = (
Ankush Menat494bd9e2022-03-28 18:52:46 +0530211 frappe.qb.from_(par)
212 .inner_join(child)
213 .on(par.name == child.parent)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530214 .select(par.main_cost_center, child.cost_center, child.percentage)
215 .where(par.docstatus == 1)
216 .where(par.company == company)
217 .where(par.valid_from <= posting_date)
218 .orderby(par.valid_from, order=frappe.qb.desc)
219 ).run(as_dict=True)
220
221 cc_allocation = frappe._dict()
222 for d in records:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530223 cc_allocation.setdefault(d.main_cost_center, frappe._dict()).setdefault(
224 d.cost_center, d.percentage
225 )
Nabin Hait5b0ec642022-01-31 18:07:04 +0530226
Nabin Hait004c1ed2022-01-31 13:20:18 +0530227 return cc_allocation
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530228
Ankush Menat494bd9e2022-03-28 18:52:46 +0530229
Nabin Hait19f8fa52021-02-22 22:27:22 +0530230def merge_similar_entries(gl_map, precision=None):
Nabin Haitbf495c92013-01-30 12:49:08 +0530231 merged_gl_map = []
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530232 accounting_dimensions = get_accounting_dimensions()
Nabin Hait914a3882022-07-19 12:32:54 +0530233
Nabin Haitbf495c92013-01-30 12:49:08 +0530234 for entry in gl_map:
Anand Doshi652bc072014-04-16 15:21:46 +0530235 # if there is already an entry in this account then just add it
Nabin Haitbf495c92013-01-30 12:49:08 +0530236 # to that entry
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530237 same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
Nabin Haitbf495c92013-01-30 12:49:08 +0530238 if same_head:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530239 same_head.debit = flt(same_head.debit) + flt(entry.debit)
240 same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
241 entry.debit_in_account_currency
242 )
Nabin Hait2e296fa2013-08-28 18:53:11 +0530243 same_head.credit = flt(same_head.credit) + flt(entry.credit)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530244 same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
245 entry.credit_in_account_currency
246 )
Nabin Haitbf495c92013-01-30 12:49:08 +0530247 else:
248 merged_gl_map.append(entry)
Anand Doshi652bc072014-04-16 15:21:46 +0530249
thefalconx33bfc43d32019-12-13 15:09:51 +0530250 company = gl_map[0].company if gl_map else erpnext.get_default_company()
251 company_currency = erpnext.get_company_currency(company)
Nabin Hait19f8fa52021-02-22 22:27:22 +0530252
253 if not precision:
254 precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
thefalconx33bfc43d32019-12-13 15:09:51 +0530255
Nabin Hait815a49e2013-08-07 17:00:01 +0530256 # filter zero debit and credit entries
Ankush Menat494bd9e2022-03-28 18:52:46 +0530257 merged_gl_map = filter(
ruthra kumar914b2302023-01-02 14:33:14 +0530258 lambda x: flt(x.debit, precision) != 0
259 or flt(x.credit, precision) != 0
260 or (
261 x.voucher_type == "Journal Entry"
262 and frappe.get_cached_value("Journal Entry", x.voucher_no, "voucher_type")
263 == "Exchange Gain Or Loss"
264 ),
265 merged_gl_map,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530266 )
Achilles Rasquinha908289d2018-03-08 13:10:51 +0530267 merged_gl_map = list(merged_gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530268
Nabin Haitbf495c92013-01-30 12:49:08 +0530269 return merged_gl_map
270
Ankush Menat494bd9e2022-03-28 18:52:46 +0530271
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530272def check_if_in_list(gle, gl_map, dimensions=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530273 account_head_fieldnames = [
274 "voucher_detail_no",
275 "party",
276 "against_voucher",
277 "cost_center",
278 "against_voucher_type",
279 "party_type",
280 "project",
281 "finance_book",
Gursheen Anand442e3f22023-06-16 13:38:47 +0530282 "voucher_no",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530283 ]
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530284
285 if dimensions:
286 account_head_fieldnames = account_head_fieldnames + dimensions
287
Nabin Haitbb777562013-08-29 18:19:37 +0530288 for e in gl_map:
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530289 same_head = True
290 if e.account != gle.account:
291 same_head = False
Ankush91527152021-08-11 11:17:50 +0530292 continue
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530293
294 for fieldname in account_head_fieldnames:
295 if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
296 same_head = False
Ankush91527152021-08-11 11:17:50 +0530297 break
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530298
299 if same_head:
300 return e
Nabin Haitbf495c92013-01-30 12:49:08 +0530301
Ankush Menat494bd9e2022-03-28 18:52:46 +0530302
Nabin Hait004c1ed2022-01-31 13:20:18 +0530303def toggle_debit_credit_if_negative(gl_map):
304 for entry in gl_map:
305 # toggle debit, credit if negative entry
306 if flt(entry.debit) < 0:
307 entry.credit = flt(entry.credit) - flt(entry.debit)
308 entry.debit = 0.0
309
310 if flt(entry.debit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530311 entry.credit_in_account_currency = flt(entry.credit_in_account_currency) - flt(
312 entry.debit_in_account_currency
313 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530314 entry.debit_in_account_currency = 0.0
315
316 if flt(entry.credit) < 0:
317 entry.debit = flt(entry.debit) - flt(entry.credit)
318 entry.credit = 0.0
319
320 if flt(entry.credit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530321 entry.debit_in_account_currency = flt(entry.debit_in_account_currency) - flt(
322 entry.credit_in_account_currency
323 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530324 entry.credit_in_account_currency = 0.0
325
326 update_net_values(entry)
327
328 return gl_map
329
Ankush Menat494bd9e2022-03-28 18:52:46 +0530330
Nabin Hait004c1ed2022-01-31 13:20:18 +0530331def update_net_values(entry):
332 # In some scenarios net value needs to be shown in the ledger
333 # This method updates net values as debit or credit
334 if entry.post_net_value and entry.debit and entry.credit:
335 if entry.debit > entry.credit:
336 entry.debit = entry.debit - entry.credit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530337 entry.debit_in_account_currency = (
338 entry.debit_in_account_currency - entry.credit_in_account_currency
339 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530340 entry.credit = 0
341 entry.credit_in_account_currency = 0
342 else:
343 entry.credit = entry.credit - entry.debit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530344 entry.credit_in_account_currency = (
345 entry.credit_in_account_currency - entry.debit_in_account_currency
346 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530347
348 entry.debit = 0
349 entry.debit_in_account_currency = 0
350
Ankush Menat494bd9e2022-03-28 18:52:46 +0530351
Nabin Haita77b8c92020-12-21 14:45:50 +0530352def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
353 if not from_repost:
354 validate_cwip_accounts(gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530355
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530356 process_debit_credit_difference(gl_map)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530357
358 if gl_map:
359 check_freezing_date(gl_map[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530360 is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
361 if gl_map[0]["voucher_type"] != "Period Closing Voucher":
362 validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530363
Nabin Haitbf495c92013-01-30 12:49:08 +0530364 for entry in gl_map:
Nabin Haita77b8c92020-12-21 14:45:50 +0530365 make_entry(entry, adv_adj, update_outstanding, from_repost)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530366
Nabin Hait914a3882022-07-19 12:32:54 +0530367
Nabin Haita77b8c92020-12-21 14:45:50 +0530368def make_entry(args, adv_adj, update_outstanding, from_repost=False):
Nabin Haitddc3bb22020-03-17 17:04:18 +0530369 gle = frappe.new_doc("GL Entry")
370 gle.update(args)
Anand Doshi6dfd4302015-02-10 14:41:27 +0530371 gle.flags.ignore_permissions = 1
Nabin Haita77b8c92020-12-21 14:45:50 +0530372 gle.flags.from_repost = from_repost
Nabin Hait19f8fa52021-02-22 22:27:22 +0530373 gle.flags.adv_adj = adv_adj
Ankush Menat494bd9e2022-03-28 18:52:46 +0530374 gle.flags.update_outstanding = update_outstanding or "Yes"
Nabin Hait4caaab32022-07-18 17:59:42 +0530375 gle.flags.notify_update = False
Nabin Haitaeba24e2013-08-23 15:17:36 +0530376 gle.submit()
Anand Doshi652bc072014-04-16 15:21:46 +0530377
Nabin Hait4caaab32022-07-18 17:59:42 +0530378 if not from_repost and gle.voucher_type != "Period Closing Voucher":
Nabin Haita77b8c92020-12-21 14:45:50 +0530379 validate_expense_against_budget(args)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530380
Ankush Menat494bd9e2022-03-28 18:52:46 +0530381
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530382def validate_cwip_accounts(gl_map):
Ankush91527152021-08-11 11:17:50 +0530383 """Validate that CWIP account are not used in Journal Entry"""
384 if gl_map and gl_map[0].voucher_type != "Journal Entry":
385 return
Maricad00c5982019-11-12 19:17:43 +0530386
Ankush Menat494bd9e2022-03-28 18:52:46 +0530387 cwip_enabled = any(
388 cint(ac.enable_cwip_accounting)
389 for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
390 )
Ankush91527152021-08-11 11:17:50 +0530391 if cwip_enabled:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530392 cwip_accounts = [
393 d[0]
394 for d in frappe.db.sql(
395 """select name from tabAccount
396 where account_type = 'Capital Work in Progress' and is_group=0"""
397 )
398 ]
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530399
Ankush91527152021-08-11 11:17:50 +0530400 for entry in gl_map:
401 if entry.account in cwip_accounts:
402 frappe.throw(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530403 _(
404 "Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry"
405 ).format(entry.account)
406 )
407
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530408
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530409def process_debit_credit_difference(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530410 precision = get_field_precision(
411 frappe.get_meta("GL Entry").get_field("debit"),
412 currency=frappe.get_cached_value("Company", gl_map[0].company, "default_currency"),
413 )
Anand Doshi602e8252015-11-16 19:05:46 +0530414
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530415 voucher_type = gl_map[0].voucher_type
416 voucher_no = gl_map[0].voucher_no
417 allowance = get_debit_credit_allowance(voucher_type, precision)
418
419 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
ruthra kumar914b2302023-01-02 14:33:14 +0530420
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530421 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530422 if not (
423 voucher_type == "Journal Entry"
424 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
425 == "Exchange Gain Or Loss"
426 ):
427 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530428
429 elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
430 make_round_off_gle(gl_map, debit_credit_diff, precision)
431
432 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
433 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530434 if not (
435 voucher_type == "Journal Entry"
436 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
437 == "Exchange Gain Or Loss"
438 ):
439 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530440
441
442def get_debit_credit_difference(gl_map, precision):
Nabin Haite2c200a2015-05-28 13:00:37 +0530443 debit_credit_diff = 0.0
444 for entry in gl_map:
445 entry.debit = flt(entry.debit, precision)
446 entry.credit = flt(entry.credit, precision)
447 debit_credit_diff += entry.debit - entry.credit
Anand Doshi602e8252015-11-16 19:05:46 +0530448
Nabin Haite2c200a2015-05-28 13:00:37 +0530449 debit_credit_diff = flt(debit_credit_diff, precision)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530450
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530451 return debit_credit_diff
452
453
454def get_debit_credit_allowance(voucher_type, precision):
455 if voucher_type in ("Journal Entry", "Payment Entry"):
Nabin Haitfb24a272016-02-18 19:18:07 +0530456 allowance = 5.0 / (10**precision)
457 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530458 allowance = 0.5
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530459
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530460 return allowance
Anand Doshi602e8252015-11-16 19:05:46 +0530461
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530462
463def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no):
464 frappe.throw(
465 _("Debit and Credit not equal for {0} #{1}. Difference is {2}.").format(
466 voucher_type, voucher_no, debit_credit_diff
467 )
468 )
Anand Doshi602e8252015-11-16 19:05:46 +0530469
Ankush Menat494bd9e2022-03-28 18:52:46 +0530470
Nabin Hait34c551d2019-07-03 10:34:31 +0530471def make_round_off_gle(gl_map, debit_credit_diff, precision):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530472 round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530473 gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
Ankush Menat494bd9e2022-03-28 18:52:46 +0530474 )
Nabin Hait80069a62015-05-28 19:19:59 +0530475 round_off_gle = frappe._dict()
Nabin Hait022d8d52022-11-21 15:16:53 +0530476 round_off_account_exists = False
Zarrar3523b772018-08-14 16:28:14 +0530477
Nabin Hait022d8d52022-11-21 15:16:53 +0530478 if gl_map[0].voucher_type != "Period Closing Voucher":
479 for d in gl_map:
480 if d.account == round_off_account:
481 round_off_gle = d
482 if d.debit:
483 debit_credit_diff -= flt(d.debit) - flt(d.credit)
484 else:
485 debit_credit_diff += flt(d.credit)
486 round_off_account_exists = True
487
488 if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
489 gl_map.remove(round_off_gle)
490 return
Nabin Hait34c551d2019-07-03 10:34:31 +0530491
Zarrar3523b772018-08-14 16:28:14 +0530492 if not round_off_gle:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530493 for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
494 round_off_gle[k] = gl_map[0][k]
Anand Doshi602e8252015-11-16 19:05:46 +0530495
Ankush Menat494bd9e2022-03-28 18:52:46 +0530496 round_off_gle.update(
497 {
498 "account": round_off_account,
499 "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
500 "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
501 "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
502 "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
503 "cost_center": round_off_cost_center,
504 "party_type": None,
505 "party": None,
506 "is_opening": "No",
507 "against_voucher_type": None,
508 "against_voucher": None,
509 }
510 )
Anand Doshi602e8252015-11-16 19:05:46 +0530511
Deepesh Garg015812b2022-04-23 21:40:08 +0530512 update_accounting_dimensions(round_off_gle)
Zarrar3523b772018-08-14 16:28:14 +0530513 if not round_off_account_exists:
514 gl_map.append(round_off_gle)
Nabin Haite2c200a2015-05-28 13:00:37 +0530515
Ankush Menat494bd9e2022-03-28 18:52:46 +0530516
Deepesh Garg015812b2022-04-23 21:40:08 +0530517def update_accounting_dimensions(round_off_gle):
518 dimensions = get_accounting_dimensions()
Deepesh Gargc312cd32022-04-24 18:11:32 +0530519 meta = frappe.get_meta(round_off_gle["voucher_type"])
520 has_all_dimensions = True
Deepesh Garg015812b2022-04-23 21:40:08 +0530521
522 for dimension in dimensions:
Deepesh Gargc312cd32022-04-24 18:11:32 +0530523 if not meta.has_field(dimension):
524 has_all_dimensions = False
525
526 if dimensions and has_all_dimensions:
527 dimension_values = frappe.db.get_value(
Deepesh Garg3fa1c632022-04-25 16:29:26 +0530528 round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
Deepesh Gargc312cd32022-04-24 18:11:32 +0530529 )
530
531 for dimension in dimensions:
532 round_off_gle[dimension] = dimension_values.get(dimension)
Deepesh Garg015812b2022-04-23 21:40:08 +0530533
534
ruthra kumarebe67872023-04-28 14:07:28 +0530535def get_round_off_account_and_cost_center(
536 company, voucher_type, voucher_no, use_company_default=False
537):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530538 round_off_account, round_off_cost_center = frappe.get_cached_value(
539 "Company", company, ["round_off_account", "round_off_cost_center"]
540 ) or [None, None]
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530541
Deepesh Garg5f75aea2023-08-24 17:58:51 +0530542 # Use expense account as fallback
543 if not round_off_account:
544 round_off_account = frappe.get_cached_value("Company", company, "default_expense_account")
545
Deepesh Gargc312cd32022-04-24 18:11:32 +0530546 meta = frappe.get_meta(voucher_type)
547
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530548 # Give first preference to parent cost center for round off GLE
ruthra kumarebe67872023-04-28 14:07:28 +0530549 if not use_company_default and meta.has_field("cost_center"):
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530550 parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
551 if parent_cost_center:
552 round_off_cost_center = parent_cost_center
553
Nabin Hait2e4de832017-09-19 14:53:16 +0530554 if not round_off_account:
555 frappe.throw(_("Please mention Round Off Account in Company"))
556
557 if not round_off_cost_center:
558 frappe.throw(_("Please mention Round Off Cost Center in Company"))
559
560 return round_off_account, round_off_cost_center
561
Ankush Menat494bd9e2022-03-28 18:52:46 +0530562
563def make_reverse_gl_entries(
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530564 gl_entries=None,
565 voucher_type=None,
566 voucher_no=None,
567 adv_adj=False,
568 update_outstanding="Yes",
569 partial_cancel=False,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530570):
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530571 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530572 Get original gl entries of the voucher
573 and make reverse gl entries by swapping debit and credit
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530574 """
Anand Doshi652bc072014-04-16 15:21:46 +0530575
Nabin Hait2e296fa2013-08-28 18:53:11 +0530576 if not gl_entries:
Deepesh Gargff574502022-02-06 22:56:12 +0530577 gl_entry = frappe.qb.DocType("GL Entry")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530578 gl_entries = (
579 frappe.qb.from_(gl_entry)
580 .select("*")
581 .where(gl_entry.voucher_type == voucher_type)
582 .where(gl_entry.voucher_no == voucher_no)
583 .where(gl_entry.is_cancelled == 0)
584 .for_update()
585 ).run(as_dict=1)
Nabin Hait56548cb2016-07-01 15:58:39 +0530586
Nabin Hait27994c22013-08-26 16:53:30 +0530587 if gl_entries:
ruthra kumar7312f222022-05-29 21:33:08 +0530588 create_payment_ledger_entry(
Deepesh Garg1e078d02023-06-29 12:18:25 +0530589 gl_entries,
590 cancel=1,
591 adv_adj=adv_adj,
592 update_outstanding=update_outstanding,
593 partial_cancel=partial_cancel,
ruthra kumar7312f222022-05-29 21:33:08 +0530594 )
Deepesh Garg069a54e2020-08-10 16:01:01 +0530595 validate_accounting_period(gl_entries)
Nabin Hait27994c22013-08-26 16:53:30 +0530596 check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530597
598 is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
599 validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530600 if not partial_cancel:
601 set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
Anand Doshi652bc072014-04-16 15:21:46 +0530602
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530603 for entry in gl_entries:
Deepesh Gargffec8652022-02-02 17:14:42 +0530604 new_gle = copy.deepcopy(entry)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530605 new_gle["name"] = None
606 debit = new_gle.get("debit", 0)
607 credit = new_gle.get("credit", 0)
Anand Doshi652bc072014-04-16 15:21:46 +0530608
Ankush Menat494bd9e2022-03-28 18:52:46 +0530609 debit_in_account_currency = new_gle.get("debit_in_account_currency", 0)
610 credit_in_account_currency = new_gle.get("credit_in_account_currency", 0)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530611
Ankush Menat494bd9e2022-03-28 18:52:46 +0530612 new_gle["debit"] = credit
613 new_gle["credit"] = debit
614 new_gle["debit_in_account_currency"] = credit_in_account_currency
615 new_gle["credit_in_account_currency"] = debit_in_account_currency
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530616
Ankush Menat494bd9e2022-03-28 18:52:46 +0530617 new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
618 new_gle["is_cancelled"] = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530619
Ankush Menat494bd9e2022-03-28 18:52:46 +0530620 if new_gle["debit"] or new_gle["credit"]:
Deepesh Gargffec8652022-02-02 17:14:42 +0530621 make_entry(new_gle, adv_adj, "Yes")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530622
623
624def check_freezing_date(posting_date, adv_adj=False):
625 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530626 Nobody can do GL Entries where posting date is before freezing date
627 except authorized person
Deepesh Garg13d2e7b2021-09-16 18:54:57 +0530628
Ankush Menat494bd9e2022-03-28 18:52:46 +0530629 Administrator has all the roles so this check will be bypassed if any role is allowed to post
630 Hence stop admin to bypass if accounts are freezed
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530631 """
632 if not adv_adj:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530633 acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530634 if acc_frozen_upto:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530635 frozen_accounts_modifier = frappe.db.get_value(
636 "Accounts Settings", None, "frozen_accounts_modifier"
637 )
638 if getdate(posting_date) <= getdate(acc_frozen_upto) and (
639 frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator"
640 ):
641 frappe.throw(
642 _("You are not authorized to add or update entries before {0}").format(
643 formatdate(acc_frozen_upto)
644 )
645 )
646
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530647
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530648def validate_against_pcv(is_opening, posting_date, company):
649 if is_opening and frappe.db.exists(
650 "Period Closing Voucher", {"docstatus": 1, "company": company}
651 ):
652 frappe.throw(
653 _("Opening Entry can not be created after Period Closing Voucher is created."),
654 title=_("Invalid Opening Entry"),
655 )
656
657 last_pcv_date = frappe.db.get_value(
658 "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
659 )
660
661 if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
662 message = _("Books have been closed till the period ending on {0}").format(
663 formatdate(last_pcv_date)
664 )
665 message += "</br >"
Deepesh Garg8ce1da12023-03-23 19:14:12 +0530666 message += _("You cannot create/amend any accounting entries till this date.")
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530667 frappe.throw(message, title=_("Period Closed"))
668
669
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530670def set_as_cancel(voucher_type, voucher_no):
671 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530672 Set is_cancelled=1 in all original gl entries for the voucher
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530673 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530674 frappe.db.sql(
675 """UPDATE `tabGL Entry` SET is_cancelled = 1,
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530676 modified=%s, modified_by=%s
677 where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530678 (now(), frappe.session.user, voucher_type, voucher_no),
679 )