blob: 2e8288675595bc367f0ede9806e2d7583bcc72a8 [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)
Deepesh Gargb834ed12024-02-05 14:05:01 +053016from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
17 get_dimension_filter_map,
18)
mergify[bot]b4db5e92023-07-18 17:40:49 +053019from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
Nabin Haitb9bc7d62016-05-16 14:38:47 +053020from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
ruthra kumare8889752022-05-16 14:28:25 +053021from erpnext.accounts.utils import create_payment_ledger_entry
Deepesh Gargb834ed12024-02-05 14:05:01 +053022from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
Chillar Anand915b3432021-09-02 16:44:59 +053023
Nabin Haitbf495c92013-01-30 12:49:08 +053024
Ankush Menat494bd9e2022-03-28 18:52:46 +053025def make_gl_entries(
26 gl_map,
27 cancel=False,
28 adv_adj=False,
29 merge_entries=True,
30 update_outstanding="Yes",
31 from_repost=False,
32):
Nabin Hait2e296fa2013-08-28 18:53:11 +053033 if gl_map:
34 if not cancel:
Deepesh Gargecca9cb2023-07-29 11:52:54 +053035 make_acc_dimensions_offsetting_entry(gl_map)
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053036 validate_accounting_period(gl_map)
Saqib Ansari95b059a2022-05-11 13:26:15 +053037 validate_disabled_accounts(gl_map)
Nabin Hait2e296fa2013-08-28 18:53:11 +053038 gl_map = process_gl_map(gl_map, merge_entries)
nabinhaitc3432922014-07-29 18:06:18 +053039 if gl_map and len(gl_map) > 1:
ruthra kumar7312f222022-05-29 21:33:08 +053040 create_payment_ledger_entry(
41 gl_map,
42 cancel=0,
43 adv_adj=adv_adj,
44 update_outstanding=update_outstanding,
45 from_repost=from_repost,
46 )
Nabin Haita77b8c92020-12-21 14:45:50 +053047 save_entries(gl_map, adv_adj, update_outstanding, from_repost)
Deepesh Garg94749082023-10-20 19:49:41 +053048 # Post GL Map process there may no be any GL Entries
Deepesh Garg4c8d15b2021-04-26 15:24:34 +053049 elif gl_map:
Ankush Menat494bd9e2022-03-28 18:52:46 +053050 frappe.throw(
51 _(
52 "Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."
53 )
54 )
Nabin Hait2e296fa2013-08-28 18:53:11 +053055 else:
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +053056 make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
Anand Doshi652bc072014-04-16 15:21:46 +053057
Ankush Menat494bd9e2022-03-28 18:52:46 +053058
Gursheen Anand22ba1212023-07-17 15:17:53 +053059def make_acc_dimensions_offsetting_entry(gl_map):
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053060 accounting_dimensions_to_offset = get_accounting_dimensions_for_offsetting_entry(
61 gl_map, gl_map[0].company
62 )
Gursheen Anand1e1e4b92023-07-18 12:12:24 +053063 no_of_dimensions = len(accounting_dimensions_to_offset)
64 if no_of_dimensions == 0:
Gursheen Anand22ba1212023-07-17 15:17:53 +053065 return
66
67 offsetting_entries = []
Deepesh Gargecca9cb2023-07-29 11:52:54 +053068
Gursheen Anand22ba1212023-07-17 15:17:53 +053069 for gle in gl_map:
70 for dimension in accounting_dimensions_to_offset:
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053071 offsetting_entry = gle.copy()
Gursheen Anand4f9242d2023-07-27 15:45:48 +053072 debit = flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0
73 credit = flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053074 offsetting_entry.update(
75 {
Gursheen Anand4f9242d2023-07-27 15:45:48 +053076 "account": dimension.offsetting_account,
77 "debit": debit,
78 "credit": credit,
79 "debit_in_account_currency": debit,
80 "credit_in_account_currency": credit,
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053081 "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension.name),
82 "against_voucher": None,
83 }
Gursheen Anand22ba1212023-07-17 15:17:53 +053084 )
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053085 offsetting_entry["against_voucher_type"] = None
86 offsetting_entries.append(offsetting_entry)
Deepesh Gargecca9cb2023-07-29 11:52:54 +053087
Gursheen Anand22ba1212023-07-17 15:17:53 +053088 gl_map += offsetting_entries
89
90
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053091def get_accounting_dimensions_for_offsetting_entry(gl_map, company):
92 acc_dimension = frappe.qb.DocType("Accounting Dimension")
93 dimension_detail = frappe.qb.DocType("Accounting Dimension Detail")
Deepesh Gargecca9cb2023-07-29 11:52:54 +053094
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053095 acc_dimensions = (
96 frappe.qb.from_(acc_dimension)
97 .inner_join(dimension_detail)
98 .on(acc_dimension.name == dimension_detail.parent)
Gursheen Anand4f9242d2023-07-27 15:45:48 +053099 .select(acc_dimension.fieldname, acc_dimension.name, dimension_detail.offsetting_account)
Gursheen Anand3a3ffa22023-07-18 12:51:09 +0530100 .where(
101 (acc_dimension.disabled == 0)
102 & (dimension_detail.company == company)
103 & (dimension_detail.automatically_post_balancing_accounting_entry == 1)
104 )
105 ).run(as_dict=True)
Deepesh Gargecca9cb2023-07-29 11:52:54 +0530106
Gursheen Anand22ba1212023-07-17 15:17:53 +0530107 accounting_dimensions_to_offset = []
108 for acc_dimension in acc_dimensions:
Gursheen Anande19a6f52023-07-19 12:26:57 +0530109 values = set([entry.get(acc_dimension.fieldname) for entry in gl_map])
Gursheen Anand22ba1212023-07-17 15:17:53 +0530110 if len(values) > 1:
111 accounting_dimensions_to_offset.append(acc_dimension)
Deepesh Gargecca9cb2023-07-29 11:52:54 +0530112
Gursheen Anand22ba1212023-07-17 15:17:53 +0530113 return accounting_dimensions_to_offset
114
115
Saqib Ansari95b059a2022-05-11 13:26:15 +0530116def validate_disabled_accounts(gl_map):
117 accounts = [d.account for d in gl_map if d.account]
118
119 Account = frappe.qb.DocType("Account")
120
121 disabled_accounts = (
122 frappe.qb.from_(Account)
123 .where(Account.name.isin(accounts) & Account.disabled == 1)
124 .select(Account.name, Account.disabled)
125 ).run(as_dict=True)
126
127 if disabled_accounts:
128 account_list = "<br>"
129 account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts])
130 frappe.throw(
131 _("Cannot create accounting entries against disabled accounts: {0}").format(account_list),
132 title=_("Disabled Account Selected"),
133 )
134
135
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530136def validate_accounting_period(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530137 accounting_periods = frappe.db.sql(
138 """ SELECT
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530139 ap.name as name
140 FROM
141 `tabAccounting Period` ap, `tabClosed Document` cd
142 WHERE
143 ap.name = cd.parent
144 AND ap.company = %(company)s
145 AND cd.closed = 1
146 AND cd.document_type = %(voucher_type)s
147 AND %(date)s between ap.start_date and ap.end_date
Ankush Menat494bd9e2022-03-28 18:52:46 +0530148 """,
149 {
150 "date": gl_map[0].posting_date,
151 "company": gl_map[0].company,
152 "voucher_type": gl_map[0].voucher_type,
153 },
154 as_dict=1,
155 )
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530156
157 if accounting_periods:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530158 frappe.throw(
159 _(
160 "You cannot create or cancel any accounting entries with in the closed Accounting Period {0}"
161 ).format(frappe.bold(accounting_periods[0].name)),
162 ClosedAccountingPeriod,
163 )
164
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530165
Nabin Hait19f8fa52021-02-22 22:27:22 +0530166def process_gl_map(gl_map, merge_entries=True, precision=None):
Nabin Hait6099af52022-01-31 17:24:50 +0530167 if not gl_map:
168 return []
169
Nabin Hait666d9612023-07-26 13:03:29 +0530170 if gl_map[0].voucher_type != "Period Closing Voucher":
171 gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530172
Nabin Haitbf495c92013-01-30 12:49:08 +0530173 if merge_entries:
Nabin Hait19f8fa52021-02-22 22:27:22 +0530174 gl_map = merge_similar_entries(gl_map, precision)
Anand Doshi602e8252015-11-16 19:05:46 +0530175
Nabin Hait004c1ed2022-01-31 13:20:18 +0530176 gl_map = toggle_debit_credit_if_negative(gl_map)
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530177
Nabin Hait27994c22013-08-26 16:53:30 +0530178 return gl_map
Anand Doshi652bc072014-04-16 15:21:46 +0530179
Ankush Menat494bd9e2022-03-28 18:52:46 +0530180
Nabin Hait004c1ed2022-01-31 13:20:18 +0530181def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530182 cost_center_allocation = get_cost_center_allocation_data(
183 gl_map[0]["company"], gl_map[0]["posting_date"]
184 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530185 if not cost_center_allocation:
186 return gl_map
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530187
Nabin Hait004c1ed2022-01-31 13:20:18 +0530188 new_gl_map = []
189 for d in gl_map:
190 cost_center = d.get("cost_center")
Deepesh Garg4e26d422022-10-30 19:33:27 +0530191
192 # Validate budget against main cost center
193 validate_expense_against_budget(
194 d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
195 )
196
Nabin Hait004c1ed2022-01-31 13:20:18 +0530197 if cost_center and cost_center_allocation.get(cost_center):
198 for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
199 gle = copy.deepcopy(d)
200 gle.cost_center = sub_cost_center
ruthra kumar71f6f782022-06-24 13:36:15 +0530201 for field in ("debit", "credit", "debit_in_account_currency", "credit_in_account_currency"):
Nabin Hait004c1ed2022-01-31 13:20:18 +0530202 gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
203 new_gl_map.append(gle)
204 else:
205 new_gl_map.append(d)
206
207 return new_gl_map
208
Ankush Menat494bd9e2022-03-28 18:52:46 +0530209
Nabin Hait004c1ed2022-01-31 13:20:18 +0530210def get_cost_center_allocation_data(company, posting_date):
211 par = frappe.qb.DocType("Cost Center Allocation")
212 child = frappe.qb.DocType("Cost Center Allocation Percentage")
213
214 records = (
Ankush Menat494bd9e2022-03-28 18:52:46 +0530215 frappe.qb.from_(par)
216 .inner_join(child)
217 .on(par.name == child.parent)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530218 .select(par.main_cost_center, child.cost_center, child.percentage)
219 .where(par.docstatus == 1)
220 .where(par.company == company)
221 .where(par.valid_from <= posting_date)
222 .orderby(par.valid_from, order=frappe.qb.desc)
223 ).run(as_dict=True)
224
225 cc_allocation = frappe._dict()
226 for d in records:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530227 cc_allocation.setdefault(d.main_cost_center, frappe._dict()).setdefault(
228 d.cost_center, d.percentage
229 )
Nabin Hait5b0ec642022-01-31 18:07:04 +0530230
Nabin Hait004c1ed2022-01-31 13:20:18 +0530231 return cc_allocation
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530232
Ankush Menat494bd9e2022-03-28 18:52:46 +0530233
Nabin Hait19f8fa52021-02-22 22:27:22 +0530234def merge_similar_entries(gl_map, precision=None):
Nabin Haitbf495c92013-01-30 12:49:08 +0530235 merged_gl_map = []
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530236 accounting_dimensions = get_accounting_dimensions()
Nabin Hait914a3882022-07-19 12:32:54 +0530237
Nabin Haitbf495c92013-01-30 12:49:08 +0530238 for entry in gl_map:
Anand Doshi652bc072014-04-16 15:21:46 +0530239 # if there is already an entry in this account then just add it
Nabin Haitbf495c92013-01-30 12:49:08 +0530240 # to that entry
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530241 same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
Nabin Haitbf495c92013-01-30 12:49:08 +0530242 if same_head:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530243 same_head.debit = flt(same_head.debit) + flt(entry.debit)
244 same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
245 entry.debit_in_account_currency
246 )
Nabin Hait2e296fa2013-08-28 18:53:11 +0530247 same_head.credit = flt(same_head.credit) + flt(entry.credit)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530248 same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
249 entry.credit_in_account_currency
250 )
Nabin Haitbf495c92013-01-30 12:49:08 +0530251 else:
252 merged_gl_map.append(entry)
Anand Doshi652bc072014-04-16 15:21:46 +0530253
thefalconx33bfc43d32019-12-13 15:09:51 +0530254 company = gl_map[0].company if gl_map else erpnext.get_default_company()
255 company_currency = erpnext.get_company_currency(company)
Nabin Hait19f8fa52021-02-22 22:27:22 +0530256
257 if not precision:
258 precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
thefalconx33bfc43d32019-12-13 15:09:51 +0530259
Nabin Hait815a49e2013-08-07 17:00:01 +0530260 # filter zero debit and credit entries
Ankush Menat494bd9e2022-03-28 18:52:46 +0530261 merged_gl_map = filter(
ruthra kumar914b2302023-01-02 14:33:14 +0530262 lambda x: flt(x.debit, precision) != 0
263 or flt(x.credit, precision) != 0
264 or (
265 x.voucher_type == "Journal Entry"
266 and frappe.get_cached_value("Journal Entry", x.voucher_no, "voucher_type")
267 == "Exchange Gain Or Loss"
268 ),
269 merged_gl_map,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530270 )
Achilles Rasquinha908289d2018-03-08 13:10:51 +0530271 merged_gl_map = list(merged_gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530272
Nabin Haitbf495c92013-01-30 12:49:08 +0530273 return merged_gl_map
274
Ankush Menat494bd9e2022-03-28 18:52:46 +0530275
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530276def check_if_in_list(gle, gl_map, dimensions=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530277 account_head_fieldnames = [
278 "voucher_detail_no",
279 "party",
280 "against_voucher",
281 "cost_center",
282 "against_voucher_type",
283 "party_type",
284 "project",
285 "finance_book",
Gursheen Anand442e3f22023-06-16 13:38:47 +0530286 "voucher_no",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530287 ]
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530288
289 if dimensions:
290 account_head_fieldnames = account_head_fieldnames + dimensions
291
Nabin Haitbb777562013-08-29 18:19:37 +0530292 for e in gl_map:
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530293 same_head = True
294 if e.account != gle.account:
295 same_head = False
Ankush91527152021-08-11 11:17:50 +0530296 continue
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530297
298 for fieldname in account_head_fieldnames:
299 if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
300 same_head = False
Ankush91527152021-08-11 11:17:50 +0530301 break
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530302
303 if same_head:
304 return e
Nabin Haitbf495c92013-01-30 12:49:08 +0530305
Ankush Menat494bd9e2022-03-28 18:52:46 +0530306
Nabin Hait004c1ed2022-01-31 13:20:18 +0530307def toggle_debit_credit_if_negative(gl_map):
308 for entry in gl_map:
309 # toggle debit, credit if negative entry
310 if flt(entry.debit) < 0:
311 entry.credit = flt(entry.credit) - flt(entry.debit)
312 entry.debit = 0.0
313
314 if flt(entry.debit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530315 entry.credit_in_account_currency = flt(entry.credit_in_account_currency) - flt(
316 entry.debit_in_account_currency
317 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530318 entry.debit_in_account_currency = 0.0
319
320 if flt(entry.credit) < 0:
321 entry.debit = flt(entry.debit) - flt(entry.credit)
322 entry.credit = 0.0
323
324 if flt(entry.credit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530325 entry.debit_in_account_currency = flt(entry.debit_in_account_currency) - flt(
326 entry.credit_in_account_currency
327 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530328 entry.credit_in_account_currency = 0.0
329
330 update_net_values(entry)
331
332 return gl_map
333
Ankush Menat494bd9e2022-03-28 18:52:46 +0530334
Nabin Hait004c1ed2022-01-31 13:20:18 +0530335def update_net_values(entry):
336 # In some scenarios net value needs to be shown in the ledger
337 # This method updates net values as debit or credit
338 if entry.post_net_value and entry.debit and entry.credit:
339 if entry.debit > entry.credit:
340 entry.debit = entry.debit - entry.credit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530341 entry.debit_in_account_currency = (
342 entry.debit_in_account_currency - entry.credit_in_account_currency
343 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530344 entry.credit = 0
345 entry.credit_in_account_currency = 0
346 else:
347 entry.credit = entry.credit - entry.debit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530348 entry.credit_in_account_currency = (
349 entry.credit_in_account_currency - entry.debit_in_account_currency
350 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530351
352 entry.debit = 0
353 entry.debit_in_account_currency = 0
354
Ankush Menat494bd9e2022-03-28 18:52:46 +0530355
Nabin Haita77b8c92020-12-21 14:45:50 +0530356def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
357 if not from_repost:
358 validate_cwip_accounts(gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530359
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530360 process_debit_credit_difference(gl_map)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530361
Deepesh Gargb834ed12024-02-05 14:05:01 +0530362 dimension_filter_map = get_dimension_filter_map()
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530363 if gl_map:
Deepesh Garg3aa17fa2024-01-17 12:47:27 +0530364 check_freezing_date(gl_map[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530365 is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
366 if gl_map[0]["voucher_type"] != "Period Closing Voucher":
367 validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530368
Nabin Haitbf495c92013-01-30 12:49:08 +0530369 for entry in gl_map:
Deepesh Gargb834ed12024-02-05 14:05:01 +0530370 validate_allowed_dimensions(entry, dimension_filter_map)
Nabin Haita77b8c92020-12-21 14:45:50 +0530371 make_entry(entry, adv_adj, update_outstanding, from_repost)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530372
Nabin Hait914a3882022-07-19 12:32:54 +0530373
Nabin Haita77b8c92020-12-21 14:45:50 +0530374def make_entry(args, adv_adj, update_outstanding, from_repost=False):
Nabin Haitddc3bb22020-03-17 17:04:18 +0530375 gle = frappe.new_doc("GL Entry")
376 gle.update(args)
Anand Doshi6dfd4302015-02-10 14:41:27 +0530377 gle.flags.ignore_permissions = 1
Nabin Haita77b8c92020-12-21 14:45:50 +0530378 gle.flags.from_repost = from_repost
Nabin Hait19f8fa52021-02-22 22:27:22 +0530379 gle.flags.adv_adj = adv_adj
Ankush Menat494bd9e2022-03-28 18:52:46 +0530380 gle.flags.update_outstanding = update_outstanding or "Yes"
Nabin Hait4caaab32022-07-18 17:59:42 +0530381 gle.flags.notify_update = False
Nabin Haitaeba24e2013-08-23 15:17:36 +0530382 gle.submit()
Anand Doshi652bc072014-04-16 15:21:46 +0530383
Nabin Hait4caaab32022-07-18 17:59:42 +0530384 if not from_repost and gle.voucher_type != "Period Closing Voucher":
Nabin Haita77b8c92020-12-21 14:45:50 +0530385 validate_expense_against_budget(args)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530386
Ankush Menat494bd9e2022-03-28 18:52:46 +0530387
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530388def validate_cwip_accounts(gl_map):
Ankush91527152021-08-11 11:17:50 +0530389 """Validate that CWIP account are not used in Journal Entry"""
390 if gl_map and gl_map[0].voucher_type != "Journal Entry":
391 return
Maricad00c5982019-11-12 19:17:43 +0530392
Ankush Menat494bd9e2022-03-28 18:52:46 +0530393 cwip_enabled = any(
394 cint(ac.enable_cwip_accounting)
395 for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
396 )
Ankush91527152021-08-11 11:17:50 +0530397 if cwip_enabled:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530398 cwip_accounts = [
399 d[0]
400 for d in frappe.db.sql(
401 """select name from tabAccount
402 where account_type = 'Capital Work in Progress' and is_group=0"""
403 )
404 ]
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530405
Ankush91527152021-08-11 11:17:50 +0530406 for entry in gl_map:
407 if entry.account in cwip_accounts:
408 frappe.throw(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530409 _(
410 "Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry"
411 ).format(entry.account)
412 )
413
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530414
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530415def process_debit_credit_difference(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530416 precision = get_field_precision(
417 frappe.get_meta("GL Entry").get_field("debit"),
418 currency=frappe.get_cached_value("Company", gl_map[0].company, "default_currency"),
419 )
Anand Doshi602e8252015-11-16 19:05:46 +0530420
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530421 voucher_type = gl_map[0].voucher_type
422 voucher_no = gl_map[0].voucher_no
423 allowance = get_debit_credit_allowance(voucher_type, precision)
424
425 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
ruthra kumar914b2302023-01-02 14:33:14 +0530426
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530427 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530428 if not (
429 voucher_type == "Journal Entry"
430 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
431 == "Exchange Gain Or Loss"
432 ):
433 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530434
435 elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
436 make_round_off_gle(gl_map, debit_credit_diff, precision)
437
438 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
439 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530440 if not (
441 voucher_type == "Journal Entry"
442 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
443 == "Exchange Gain Or Loss"
444 ):
445 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530446
447
448def get_debit_credit_difference(gl_map, precision):
Nabin Haite2c200a2015-05-28 13:00:37 +0530449 debit_credit_diff = 0.0
450 for entry in gl_map:
451 entry.debit = flt(entry.debit, precision)
452 entry.credit = flt(entry.credit, precision)
453 debit_credit_diff += entry.debit - entry.credit
Anand Doshi602e8252015-11-16 19:05:46 +0530454
Nabin Haite2c200a2015-05-28 13:00:37 +0530455 debit_credit_diff = flt(debit_credit_diff, precision)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530456
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530457 return debit_credit_diff
458
459
460def get_debit_credit_allowance(voucher_type, precision):
461 if voucher_type in ("Journal Entry", "Payment Entry"):
Nabin Haitfb24a272016-02-18 19:18:07 +0530462 allowance = 5.0 / (10**precision)
463 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530464 allowance = 0.5
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530465
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530466 return allowance
Anand Doshi602e8252015-11-16 19:05:46 +0530467
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530468
469def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no):
470 frappe.throw(
471 _("Debit and Credit not equal for {0} #{1}. Difference is {2}.").format(
472 voucher_type, voucher_no, debit_credit_diff
473 )
474 )
Anand Doshi602e8252015-11-16 19:05:46 +0530475
Ankush Menat494bd9e2022-03-28 18:52:46 +0530476
Nabin Hait34c551d2019-07-03 10:34:31 +0530477def make_round_off_gle(gl_map, debit_credit_diff, precision):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530478 round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530479 gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
Ankush Menat494bd9e2022-03-28 18:52:46 +0530480 )
Nabin Hait80069a62015-05-28 19:19:59 +0530481 round_off_gle = frappe._dict()
Nabin Hait022d8d52022-11-21 15:16:53 +0530482 round_off_account_exists = False
Zarrar3523b772018-08-14 16:28:14 +0530483
Nabin Hait022d8d52022-11-21 15:16:53 +0530484 if gl_map[0].voucher_type != "Period Closing Voucher":
485 for d in gl_map:
486 if d.account == round_off_account:
487 round_off_gle = d
488 if d.debit:
489 debit_credit_diff -= flt(d.debit) - flt(d.credit)
490 else:
491 debit_credit_diff += flt(d.credit)
492 round_off_account_exists = True
493
494 if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
495 gl_map.remove(round_off_gle)
496 return
Nabin Hait34c551d2019-07-03 10:34:31 +0530497
Zarrar3523b772018-08-14 16:28:14 +0530498 if not round_off_gle:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530499 for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
500 round_off_gle[k] = gl_map[0][k]
Anand Doshi602e8252015-11-16 19:05:46 +0530501
Ankush Menat494bd9e2022-03-28 18:52:46 +0530502 round_off_gle.update(
503 {
504 "account": round_off_account,
505 "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
506 "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
507 "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
508 "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
509 "cost_center": round_off_cost_center,
510 "party_type": None,
511 "party": None,
512 "is_opening": "No",
513 "against_voucher_type": None,
514 "against_voucher": None,
515 }
516 )
Anand Doshi602e8252015-11-16 19:05:46 +0530517
Deepesh Garg015812b2022-04-23 21:40:08 +0530518 update_accounting_dimensions(round_off_gle)
Zarrar3523b772018-08-14 16:28:14 +0530519 if not round_off_account_exists:
520 gl_map.append(round_off_gle)
Nabin Haite2c200a2015-05-28 13:00:37 +0530521
Ankush Menat494bd9e2022-03-28 18:52:46 +0530522
Deepesh Garg015812b2022-04-23 21:40:08 +0530523def update_accounting_dimensions(round_off_gle):
524 dimensions = get_accounting_dimensions()
Deepesh Gargc312cd32022-04-24 18:11:32 +0530525 meta = frappe.get_meta(round_off_gle["voucher_type"])
526 has_all_dimensions = True
Deepesh Garg015812b2022-04-23 21:40:08 +0530527
528 for dimension in dimensions:
Deepesh Gargc312cd32022-04-24 18:11:32 +0530529 if not meta.has_field(dimension):
530 has_all_dimensions = False
531
532 if dimensions and has_all_dimensions:
533 dimension_values = frappe.db.get_value(
Deepesh Garg3fa1c632022-04-25 16:29:26 +0530534 round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
Deepesh Gargc312cd32022-04-24 18:11:32 +0530535 )
536
537 for dimension in dimensions:
538 round_off_gle[dimension] = dimension_values.get(dimension)
Deepesh Garg015812b2022-04-23 21:40:08 +0530539
540
ruthra kumarebe67872023-04-28 14:07:28 +0530541def get_round_off_account_and_cost_center(
542 company, voucher_type, voucher_no, use_company_default=False
543):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530544 round_off_account, round_off_cost_center = frappe.get_cached_value(
545 "Company", company, ["round_off_account", "round_off_cost_center"]
546 ) or [None, None]
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530547
Deepesh Garg5f75aea2023-08-24 17:58:51 +0530548 # Use expense account as fallback
549 if not round_off_account:
550 round_off_account = frappe.get_cached_value("Company", company, "default_expense_account")
551
Deepesh Gargc312cd32022-04-24 18:11:32 +0530552 meta = frappe.get_meta(voucher_type)
553
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530554 # Give first preference to parent cost center for round off GLE
ruthra kumarebe67872023-04-28 14:07:28 +0530555 if not use_company_default and meta.has_field("cost_center"):
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530556 parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
557 if parent_cost_center:
558 round_off_cost_center = parent_cost_center
559
Nabin Hait2e4de832017-09-19 14:53:16 +0530560 if not round_off_account:
561 frappe.throw(_("Please mention Round Off Account in Company"))
562
563 if not round_off_cost_center:
564 frappe.throw(_("Please mention Round Off Cost Center in Company"))
565
566 return round_off_account, round_off_cost_center
567
Ankush Menat494bd9e2022-03-28 18:52:46 +0530568
569def make_reverse_gl_entries(
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530570 gl_entries=None,
571 voucher_type=None,
572 voucher_no=None,
573 adv_adj=False,
574 update_outstanding="Yes",
575 partial_cancel=False,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530576):
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530577 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530578 Get original gl entries of the voucher
579 and make reverse gl entries by swapping debit and credit
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530580 """
Anand Doshi652bc072014-04-16 15:21:46 +0530581
Nabin Hait2e296fa2013-08-28 18:53:11 +0530582 if not gl_entries:
Deepesh Gargff574502022-02-06 22:56:12 +0530583 gl_entry = frappe.qb.DocType("GL Entry")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530584 gl_entries = (
585 frappe.qb.from_(gl_entry)
586 .select("*")
587 .where(gl_entry.voucher_type == voucher_type)
588 .where(gl_entry.voucher_no == voucher_no)
589 .where(gl_entry.is_cancelled == 0)
590 .for_update()
591 ).run(as_dict=1)
Nabin Hait56548cb2016-07-01 15:58:39 +0530592
Nabin Hait27994c22013-08-26 16:53:30 +0530593 if gl_entries:
ruthra kumar7312f222022-05-29 21:33:08 +0530594 create_payment_ledger_entry(
Deepesh Garg1e078d02023-06-29 12:18:25 +0530595 gl_entries,
596 cancel=1,
597 adv_adj=adv_adj,
598 update_outstanding=update_outstanding,
599 partial_cancel=partial_cancel,
ruthra kumar7312f222022-05-29 21:33:08 +0530600 )
Deepesh Garg069a54e2020-08-10 16:01:01 +0530601 validate_accounting_period(gl_entries)
Deepesh Garg3aa17fa2024-01-17 12:47:27 +0530602 check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530603
604 is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
605 validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
ruthra kumar2633d7d2023-11-28 17:05:29 +0530606 if partial_cancel:
607 # Partial cancel is only used by `Advance` in separate account feature.
608 # Only cancel GL entries for unlinked reference using `voucher_detail_no`
609 gle = frappe.qb.DocType("GL Entry")
610 for x in gl_entries:
611 query = (
612 frappe.qb.update(gle)
613 .set(gle.is_cancelled, True)
614 .set(gle.modified, now())
615 .set(gle.modified_by, frappe.session.user)
616 .where(
617 (gle.company == x.company)
618 & (gle.account == x.account)
619 & (gle.party_type == x.party_type)
620 & (gle.party == x.party)
621 & (gle.voucher_type == x.voucher_type)
622 & (gle.voucher_no == x.voucher_no)
623 & (gle.against_voucher_type == x.against_voucher_type)
624 & (gle.against_voucher == x.against_voucher)
625 & (gle.voucher_detail_no == x.voucher_detail_no)
626 )
627 )
628 query.run()
629 else:
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530630 set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
Anand Doshi652bc072014-04-16 15:21:46 +0530631
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530632 for entry in gl_entries:
Deepesh Gargffec8652022-02-02 17:14:42 +0530633 new_gle = copy.deepcopy(entry)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530634 new_gle["name"] = None
635 debit = new_gle.get("debit", 0)
636 credit = new_gle.get("credit", 0)
Anand Doshi652bc072014-04-16 15:21:46 +0530637
Ankush Menat494bd9e2022-03-28 18:52:46 +0530638 debit_in_account_currency = new_gle.get("debit_in_account_currency", 0)
639 credit_in_account_currency = new_gle.get("credit_in_account_currency", 0)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530640
Ankush Menat494bd9e2022-03-28 18:52:46 +0530641 new_gle["debit"] = credit
642 new_gle["credit"] = debit
643 new_gle["debit_in_account_currency"] = credit_in_account_currency
644 new_gle["credit_in_account_currency"] = debit_in_account_currency
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530645
Ankush Menat494bd9e2022-03-28 18:52:46 +0530646 new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
647 new_gle["is_cancelled"] = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530648
Ankush Menat494bd9e2022-03-28 18:52:46 +0530649 if new_gle["debit"] or new_gle["credit"]:
Deepesh Gargffec8652022-02-02 17:14:42 +0530650 make_entry(new_gle, adv_adj, "Yes")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530651
652
653def check_freezing_date(posting_date, adv_adj=False):
654 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530655 Nobody can do GL Entries where posting date is before freezing date
656 except authorized person
Deepesh Garg13d2e7b2021-09-16 18:54:57 +0530657
Ankush Menat494bd9e2022-03-28 18:52:46 +0530658 Administrator has all the roles so this check will be bypassed if any role is allowed to post
659 Hence stop admin to bypass if accounts are freezed
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530660 """
661 if not adv_adj:
Ankush Menatbb18ae82024-01-09 21:56:47 +0530662 acc_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530663 if acc_frozen_upto:
Ankush Menatbb18ae82024-01-09 21:56:47 +0530664 frozen_accounts_modifier = frappe.db.get_single_value(
665 "Accounts Settings", "frozen_accounts_modifier"
Ankush Menat494bd9e2022-03-28 18:52:46 +0530666 )
667 if getdate(posting_date) <= getdate(acc_frozen_upto) and (
668 frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator"
669 ):
670 frappe.throw(
671 _("You are not authorized to add or update entries before {0}").format(
672 formatdate(acc_frozen_upto)
673 )
674 )
675
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530676
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530677def validate_against_pcv(is_opening, posting_date, company):
678 if is_opening and frappe.db.exists(
679 "Period Closing Voucher", {"docstatus": 1, "company": company}
680 ):
681 frappe.throw(
682 _("Opening Entry can not be created after Period Closing Voucher is created."),
683 title=_("Invalid Opening Entry"),
684 )
685
686 last_pcv_date = frappe.db.get_value(
687 "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
688 )
689
690 if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
691 message = _("Books have been closed till the period ending on {0}").format(
692 formatdate(last_pcv_date)
693 )
694 message += "</br >"
Deepesh Garg8ce1da12023-03-23 19:14:12 +0530695 message += _("You cannot create/amend any accounting entries till this date.")
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530696 frappe.throw(message, title=_("Period Closed"))
697
698
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530699def set_as_cancel(voucher_type, voucher_no):
700 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530701 Set is_cancelled=1 in all original gl entries for the voucher
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530702 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530703 frappe.db.sql(
704 """UPDATE `tabGL Entry` SET is_cancelled = 1,
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530705 modified=%s, modified_by=%s
706 where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530707 (now(), frappe.session.user, voucher_type, voucher_no),
708 )
Deepesh Gargb834ed12024-02-05 14:05:01 +0530709
710
711def validate_allowed_dimensions(gl_entry, dimension_filter_map):
712 for key, value in dimension_filter_map.items():
713 dimension = key[0]
714 account = key[1]
715
716 if gl_entry.account == account:
717 if value["is_mandatory"] and not gl_entry.get(dimension):
718 frappe.throw(
719 _("{0} is mandatory for account {1}").format(
720 frappe.bold(frappe.unscrub(dimension)), frappe.bold(gl_entry.account)
721 ),
722 MandatoryAccountDimensionError,
723 )
724
725 if value["allow_or_restrict"] == "Allow":
726 if gl_entry.get(dimension) and gl_entry.get(dimension) not in value["allowed_dimensions"]:
727 frappe.throw(
728 _("Invalid value {0} for {1} against account {2}").format(
729 frappe.bold(gl_entry.get(dimension)),
730 frappe.bold(frappe.unscrub(dimension)),
731 frappe.bold(gl_entry.account),
732 ),
733 InvalidAccountDimensionError,
734 )
735 else:
736 if gl_entry.get(dimension) and gl_entry.get(dimension) in value["allowed_dimensions"]:
737 frappe.throw(
738 _("Invalid value {0} for {1} against account {2}").format(
739 frappe.bold(gl_entry.get(dimension)),
740 frappe.bold(frappe.unscrub(dimension)),
741 frappe.bold(gl_entry.account),
742 ),
743 InvalidAccountDimensionError,
744 )