blob: eacb318d040385dfb58b2268d5731e8bba67f6fd [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):
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053059 accounting_dimensions_to_offset = get_accounting_dimensions_for_offsetting_entry(
60 gl_map, gl_map[0].company
61 )
Gursheen Anand1e1e4b92023-07-18 12:12:24 +053062 no_of_dimensions = len(accounting_dimensions_to_offset)
63 if no_of_dimensions == 0:
Gursheen Anand22ba1212023-07-17 15:17:53 +053064 return
65
66 offsetting_entries = []
67 for gle in gl_map:
68 for dimension in accounting_dimensions_to_offset:
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053069 offsetting_account = dimension.offsetting_account
70 offsetting_entry = gle.copy()
71 offsetting_entry.update(
72 {
73 "account": offsetting_account,
74 "debit": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0,
75 "credit": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0,
76 "debit_in_account_currency": flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0,
77 "credit_in_account_currency": flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0,
78 "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension.name),
79 "against_voucher": None,
80 }
Gursheen Anand22ba1212023-07-17 15:17:53 +053081 )
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053082 offsetting_entry["against_voucher_type"] = None
83 offsetting_entries.append(offsetting_entry)
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")
90 acc_dimensions = (
91 frappe.qb.from_(acc_dimension)
92 .inner_join(dimension_detail)
93 .on(acc_dimension.name == dimension_detail.parent)
Gursheen Anande19a6f52023-07-19 12:26:57 +053094 .select(acc_dimension.fieldname, dimension_detail.offsetting_account)
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053095 .where(
96 (acc_dimension.disabled == 0)
97 & (dimension_detail.company == company)
98 & (dimension_detail.automatically_post_balancing_accounting_entry == 1)
99 )
100 ).run(as_dict=True)
Gursheen Anand22ba1212023-07-17 15:17:53 +0530101 accounting_dimensions_to_offset = []
102 for acc_dimension in acc_dimensions:
Gursheen Anande19a6f52023-07-19 12:26:57 +0530103 values = set([entry.get(acc_dimension.fieldname) for entry in gl_map])
Gursheen Anand22ba1212023-07-17 15:17:53 +0530104 if len(values) > 1:
105 accounting_dimensions_to_offset.append(acc_dimension)
106 return accounting_dimensions_to_offset
107
108
Saqib Ansari95b059a2022-05-11 13:26:15 +0530109def validate_disabled_accounts(gl_map):
110 accounts = [d.account for d in gl_map if d.account]
111
112 Account = frappe.qb.DocType("Account")
113
114 disabled_accounts = (
115 frappe.qb.from_(Account)
116 .where(Account.name.isin(accounts) & Account.disabled == 1)
117 .select(Account.name, Account.disabled)
118 ).run(as_dict=True)
119
120 if disabled_accounts:
121 account_list = "<br>"
122 account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts])
123 frappe.throw(
124 _("Cannot create accounting entries against disabled accounts: {0}").format(account_list),
125 title=_("Disabled Account Selected"),
126 )
127
128
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530129def validate_accounting_period(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530130 accounting_periods = frappe.db.sql(
131 """ SELECT
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530132 ap.name as name
133 FROM
134 `tabAccounting Period` ap, `tabClosed Document` cd
135 WHERE
136 ap.name = cd.parent
137 AND ap.company = %(company)s
138 AND cd.closed = 1
139 AND cd.document_type = %(voucher_type)s
140 AND %(date)s between ap.start_date and ap.end_date
Ankush Menat494bd9e2022-03-28 18:52:46 +0530141 """,
142 {
143 "date": gl_map[0].posting_date,
144 "company": gl_map[0].company,
145 "voucher_type": gl_map[0].voucher_type,
146 },
147 as_dict=1,
148 )
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530149
150 if accounting_periods:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530151 frappe.throw(
152 _(
153 "You cannot create or cancel any accounting entries with in the closed Accounting Period {0}"
154 ).format(frappe.bold(accounting_periods[0].name)),
155 ClosedAccountingPeriod,
156 )
157
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530158
Nabin Hait19f8fa52021-02-22 22:27:22 +0530159def process_gl_map(gl_map, merge_entries=True, precision=None):
Nabin Hait6099af52022-01-31 17:24:50 +0530160 if not gl_map:
161 return []
162
Nabin Hait004c1ed2022-01-31 13:20:18 +0530163 gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
164
Nabin Haitbf495c92013-01-30 12:49:08 +0530165 if merge_entries:
Nabin Hait19f8fa52021-02-22 22:27:22 +0530166 gl_map = merge_similar_entries(gl_map, precision)
Anand Doshi602e8252015-11-16 19:05:46 +0530167
Nabin Hait004c1ed2022-01-31 13:20:18 +0530168 gl_map = toggle_debit_credit_if_negative(gl_map)
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530169
Nabin Hait27994c22013-08-26 16:53:30 +0530170 return gl_map
Anand Doshi652bc072014-04-16 15:21:46 +0530171
Ankush Menat494bd9e2022-03-28 18:52:46 +0530172
Nabin Hait004c1ed2022-01-31 13:20:18 +0530173def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530174 cost_center_allocation = get_cost_center_allocation_data(
175 gl_map[0]["company"], gl_map[0]["posting_date"]
176 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530177 if not cost_center_allocation:
178 return gl_map
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530179
Nabin Hait004c1ed2022-01-31 13:20:18 +0530180 new_gl_map = []
181 for d in gl_map:
182 cost_center = d.get("cost_center")
Deepesh Garg4e26d422022-10-30 19:33:27 +0530183
184 # Validate budget against main cost center
185 validate_expense_against_budget(
186 d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
187 )
188
Nabin Hait004c1ed2022-01-31 13:20:18 +0530189 if cost_center and cost_center_allocation.get(cost_center):
190 for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
191 gle = copy.deepcopy(d)
192 gle.cost_center = sub_cost_center
ruthra kumar71f6f782022-06-24 13:36:15 +0530193 for field in ("debit", "credit", "debit_in_account_currency", "credit_in_account_currency"):
Nabin Hait004c1ed2022-01-31 13:20:18 +0530194 gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
195 new_gl_map.append(gle)
196 else:
197 new_gl_map.append(d)
198
199 return new_gl_map
200
Ankush Menat494bd9e2022-03-28 18:52:46 +0530201
Nabin Hait004c1ed2022-01-31 13:20:18 +0530202def get_cost_center_allocation_data(company, posting_date):
203 par = frappe.qb.DocType("Cost Center Allocation")
204 child = frappe.qb.DocType("Cost Center Allocation Percentage")
205
206 records = (
Ankush Menat494bd9e2022-03-28 18:52:46 +0530207 frappe.qb.from_(par)
208 .inner_join(child)
209 .on(par.name == child.parent)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530210 .select(par.main_cost_center, child.cost_center, child.percentage)
211 .where(par.docstatus == 1)
212 .where(par.company == company)
213 .where(par.valid_from <= posting_date)
214 .orderby(par.valid_from, order=frappe.qb.desc)
215 ).run(as_dict=True)
216
217 cc_allocation = frappe._dict()
218 for d in records:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530219 cc_allocation.setdefault(d.main_cost_center, frappe._dict()).setdefault(
220 d.cost_center, d.percentage
221 )
Nabin Hait5b0ec642022-01-31 18:07:04 +0530222
Nabin Hait004c1ed2022-01-31 13:20:18 +0530223 return cc_allocation
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530224
Ankush Menat494bd9e2022-03-28 18:52:46 +0530225
Nabin Hait19f8fa52021-02-22 22:27:22 +0530226def merge_similar_entries(gl_map, precision=None):
Nabin Haitbf495c92013-01-30 12:49:08 +0530227 merged_gl_map = []
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530228 accounting_dimensions = get_accounting_dimensions()
Nabin Hait914a3882022-07-19 12:32:54 +0530229
Nabin Haitbf495c92013-01-30 12:49:08 +0530230 for entry in gl_map:
Anand Doshi652bc072014-04-16 15:21:46 +0530231 # if there is already an entry in this account then just add it
Nabin Haitbf495c92013-01-30 12:49:08 +0530232 # to that entry
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530233 same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
Nabin Haitbf495c92013-01-30 12:49:08 +0530234 if same_head:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530235 same_head.debit = flt(same_head.debit) + flt(entry.debit)
236 same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
237 entry.debit_in_account_currency
238 )
Nabin Hait2e296fa2013-08-28 18:53:11 +0530239 same_head.credit = flt(same_head.credit) + flt(entry.credit)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530240 same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
241 entry.credit_in_account_currency
242 )
Nabin Haitbf495c92013-01-30 12:49:08 +0530243 else:
244 merged_gl_map.append(entry)
Anand Doshi652bc072014-04-16 15:21:46 +0530245
thefalconx33bfc43d32019-12-13 15:09:51 +0530246 company = gl_map[0].company if gl_map else erpnext.get_default_company()
247 company_currency = erpnext.get_company_currency(company)
Nabin Hait19f8fa52021-02-22 22:27:22 +0530248
249 if not precision:
250 precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
thefalconx33bfc43d32019-12-13 15:09:51 +0530251
Nabin Hait815a49e2013-08-07 17:00:01 +0530252 # filter zero debit and credit entries
Ankush Menat494bd9e2022-03-28 18:52:46 +0530253 merged_gl_map = filter(
ruthra kumar914b2302023-01-02 14:33:14 +0530254 lambda x: flt(x.debit, precision) != 0
255 or flt(x.credit, precision) != 0
256 or (
257 x.voucher_type == "Journal Entry"
258 and frappe.get_cached_value("Journal Entry", x.voucher_no, "voucher_type")
259 == "Exchange Gain Or Loss"
260 ),
261 merged_gl_map,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530262 )
Achilles Rasquinha908289d2018-03-08 13:10:51 +0530263 merged_gl_map = list(merged_gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530264
Nabin Haitbf495c92013-01-30 12:49:08 +0530265 return merged_gl_map
266
Ankush Menat494bd9e2022-03-28 18:52:46 +0530267
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530268def check_if_in_list(gle, gl_map, dimensions=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530269 account_head_fieldnames = [
270 "voucher_detail_no",
271 "party",
272 "against_voucher",
273 "cost_center",
274 "against_voucher_type",
275 "party_type",
276 "project",
277 "finance_book",
Gursheen Anand442e3f22023-06-16 13:38:47 +0530278 "voucher_no",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530279 ]
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530280
281 if dimensions:
282 account_head_fieldnames = account_head_fieldnames + dimensions
283
Nabin Haitbb777562013-08-29 18:19:37 +0530284 for e in gl_map:
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530285 same_head = True
286 if e.account != gle.account:
287 same_head = False
Ankush91527152021-08-11 11:17:50 +0530288 continue
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530289
290 for fieldname in account_head_fieldnames:
291 if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
292 same_head = False
Ankush91527152021-08-11 11:17:50 +0530293 break
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530294
295 if same_head:
296 return e
Nabin Haitbf495c92013-01-30 12:49:08 +0530297
Ankush Menat494bd9e2022-03-28 18:52:46 +0530298
Nabin Hait004c1ed2022-01-31 13:20:18 +0530299def toggle_debit_credit_if_negative(gl_map):
300 for entry in gl_map:
301 # toggle debit, credit if negative entry
302 if flt(entry.debit) < 0:
303 entry.credit = flt(entry.credit) - flt(entry.debit)
304 entry.debit = 0.0
305
306 if flt(entry.debit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530307 entry.credit_in_account_currency = flt(entry.credit_in_account_currency) - flt(
308 entry.debit_in_account_currency
309 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530310 entry.debit_in_account_currency = 0.0
311
312 if flt(entry.credit) < 0:
313 entry.debit = flt(entry.debit) - flt(entry.credit)
314 entry.credit = 0.0
315
316 if flt(entry.credit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530317 entry.debit_in_account_currency = flt(entry.debit_in_account_currency) - flt(
318 entry.credit_in_account_currency
319 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530320 entry.credit_in_account_currency = 0.0
321
322 update_net_values(entry)
323
324 return gl_map
325
Ankush Menat494bd9e2022-03-28 18:52:46 +0530326
Nabin Hait004c1ed2022-01-31 13:20:18 +0530327def update_net_values(entry):
328 # In some scenarios net value needs to be shown in the ledger
329 # This method updates net values as debit or credit
330 if entry.post_net_value and entry.debit and entry.credit:
331 if entry.debit > entry.credit:
332 entry.debit = entry.debit - entry.credit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530333 entry.debit_in_account_currency = (
334 entry.debit_in_account_currency - entry.credit_in_account_currency
335 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530336 entry.credit = 0
337 entry.credit_in_account_currency = 0
338 else:
339 entry.credit = entry.credit - entry.debit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530340 entry.credit_in_account_currency = (
341 entry.credit_in_account_currency - entry.debit_in_account_currency
342 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530343
344 entry.debit = 0
345 entry.debit_in_account_currency = 0
346
Ankush Menat494bd9e2022-03-28 18:52:46 +0530347
Nabin Haita77b8c92020-12-21 14:45:50 +0530348def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
349 if not from_repost:
350 validate_cwip_accounts(gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530351
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530352 process_debit_credit_difference(gl_map)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530353
354 if gl_map:
355 check_freezing_date(gl_map[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530356 is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
357 if gl_map[0]["voucher_type"] != "Period Closing Voucher":
358 validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530359
Nabin Haitbf495c92013-01-30 12:49:08 +0530360 for entry in gl_map:
Nabin Haita77b8c92020-12-21 14:45:50 +0530361 make_entry(entry, adv_adj, update_outstanding, from_repost)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530362
Nabin Hait914a3882022-07-19 12:32:54 +0530363
Nabin Haita77b8c92020-12-21 14:45:50 +0530364def make_entry(args, adv_adj, update_outstanding, from_repost=False):
Nabin Haitddc3bb22020-03-17 17:04:18 +0530365 gle = frappe.new_doc("GL Entry")
366 gle.update(args)
Anand Doshi6dfd4302015-02-10 14:41:27 +0530367 gle.flags.ignore_permissions = 1
Nabin Haita77b8c92020-12-21 14:45:50 +0530368 gle.flags.from_repost = from_repost
Nabin Hait19f8fa52021-02-22 22:27:22 +0530369 gle.flags.adv_adj = adv_adj
Ankush Menat494bd9e2022-03-28 18:52:46 +0530370 gle.flags.update_outstanding = update_outstanding or "Yes"
Nabin Hait4caaab32022-07-18 17:59:42 +0530371 gle.flags.notify_update = False
Nabin Haitaeba24e2013-08-23 15:17:36 +0530372 gle.submit()
Anand Doshi652bc072014-04-16 15:21:46 +0530373
Nabin Hait4caaab32022-07-18 17:59:42 +0530374 if not from_repost and gle.voucher_type != "Period Closing Voucher":
Nabin Haita77b8c92020-12-21 14:45:50 +0530375 validate_expense_against_budget(args)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530376
Ankush Menat494bd9e2022-03-28 18:52:46 +0530377
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530378def validate_cwip_accounts(gl_map):
Ankush91527152021-08-11 11:17:50 +0530379 """Validate that CWIP account are not used in Journal Entry"""
380 if gl_map and gl_map[0].voucher_type != "Journal Entry":
381 return
Maricad00c5982019-11-12 19:17:43 +0530382
Ankush Menat494bd9e2022-03-28 18:52:46 +0530383 cwip_enabled = any(
384 cint(ac.enable_cwip_accounting)
385 for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
386 )
Ankush91527152021-08-11 11:17:50 +0530387 if cwip_enabled:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530388 cwip_accounts = [
389 d[0]
390 for d in frappe.db.sql(
391 """select name from tabAccount
392 where account_type = 'Capital Work in Progress' and is_group=0"""
393 )
394 ]
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530395
Ankush91527152021-08-11 11:17:50 +0530396 for entry in gl_map:
397 if entry.account in cwip_accounts:
398 frappe.throw(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530399 _(
400 "Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry"
401 ).format(entry.account)
402 )
403
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530404
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530405def process_debit_credit_difference(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530406 precision = get_field_precision(
407 frappe.get_meta("GL Entry").get_field("debit"),
408 currency=frappe.get_cached_value("Company", gl_map[0].company, "default_currency"),
409 )
Anand Doshi602e8252015-11-16 19:05:46 +0530410
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530411 voucher_type = gl_map[0].voucher_type
412 voucher_no = gl_map[0].voucher_no
413 allowance = get_debit_credit_allowance(voucher_type, precision)
414
415 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
ruthra kumar914b2302023-01-02 14:33:14 +0530416
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530417 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530418 if not (
419 voucher_type == "Journal Entry"
420 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
421 == "Exchange Gain Or Loss"
422 ):
423 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530424
425 elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
426 make_round_off_gle(gl_map, debit_credit_diff, precision)
427
428 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
429 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530430 if not (
431 voucher_type == "Journal Entry"
432 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
433 == "Exchange Gain Or Loss"
434 ):
435 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530436
437
438def get_debit_credit_difference(gl_map, precision):
Nabin Haite2c200a2015-05-28 13:00:37 +0530439 debit_credit_diff = 0.0
440 for entry in gl_map:
441 entry.debit = flt(entry.debit, precision)
442 entry.credit = flt(entry.credit, precision)
443 debit_credit_diff += entry.debit - entry.credit
Anand Doshi602e8252015-11-16 19:05:46 +0530444
Nabin Haite2c200a2015-05-28 13:00:37 +0530445 debit_credit_diff = flt(debit_credit_diff, precision)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530446
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530447 return debit_credit_diff
448
449
450def get_debit_credit_allowance(voucher_type, precision):
451 if voucher_type in ("Journal Entry", "Payment Entry"):
Nabin Haitfb24a272016-02-18 19:18:07 +0530452 allowance = 5.0 / (10**precision)
453 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530454 allowance = 0.5
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530455
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530456 return allowance
Anand Doshi602e8252015-11-16 19:05:46 +0530457
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530458
459def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no):
460 frappe.throw(
461 _("Debit and Credit not equal for {0} #{1}. Difference is {2}.").format(
462 voucher_type, voucher_no, debit_credit_diff
463 )
464 )
Anand Doshi602e8252015-11-16 19:05:46 +0530465
Ankush Menat494bd9e2022-03-28 18:52:46 +0530466
Nabin Hait34c551d2019-07-03 10:34:31 +0530467def make_round_off_gle(gl_map, debit_credit_diff, precision):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530468 round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530469 gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
Ankush Menat494bd9e2022-03-28 18:52:46 +0530470 )
Nabin Hait80069a62015-05-28 19:19:59 +0530471 round_off_gle = frappe._dict()
Nabin Hait022d8d52022-11-21 15:16:53 +0530472 round_off_account_exists = False
Zarrar3523b772018-08-14 16:28:14 +0530473
Nabin Hait022d8d52022-11-21 15:16:53 +0530474 if gl_map[0].voucher_type != "Period Closing Voucher":
475 for d in gl_map:
476 if d.account == round_off_account:
477 round_off_gle = d
478 if d.debit:
479 debit_credit_diff -= flt(d.debit) - flt(d.credit)
480 else:
481 debit_credit_diff += flt(d.credit)
482 round_off_account_exists = True
483
484 if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
485 gl_map.remove(round_off_gle)
486 return
Nabin Hait34c551d2019-07-03 10:34:31 +0530487
Zarrar3523b772018-08-14 16:28:14 +0530488 if not round_off_gle:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530489 for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
490 round_off_gle[k] = gl_map[0][k]
Anand Doshi602e8252015-11-16 19:05:46 +0530491
Ankush Menat494bd9e2022-03-28 18:52:46 +0530492 round_off_gle.update(
493 {
494 "account": round_off_account,
495 "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
496 "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
497 "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
498 "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
499 "cost_center": round_off_cost_center,
500 "party_type": None,
501 "party": None,
502 "is_opening": "No",
503 "against_voucher_type": None,
504 "against_voucher": None,
505 }
506 )
Anand Doshi602e8252015-11-16 19:05:46 +0530507
Deepesh Garg015812b2022-04-23 21:40:08 +0530508 update_accounting_dimensions(round_off_gle)
Zarrar3523b772018-08-14 16:28:14 +0530509 if not round_off_account_exists:
510 gl_map.append(round_off_gle)
Nabin Haite2c200a2015-05-28 13:00:37 +0530511
Ankush Menat494bd9e2022-03-28 18:52:46 +0530512
Deepesh Garg015812b2022-04-23 21:40:08 +0530513def update_accounting_dimensions(round_off_gle):
514 dimensions = get_accounting_dimensions()
Deepesh Gargc312cd32022-04-24 18:11:32 +0530515 meta = frappe.get_meta(round_off_gle["voucher_type"])
516 has_all_dimensions = True
Deepesh Garg015812b2022-04-23 21:40:08 +0530517
518 for dimension in dimensions:
Deepesh Gargc312cd32022-04-24 18:11:32 +0530519 if not meta.has_field(dimension):
520 has_all_dimensions = False
521
522 if dimensions and has_all_dimensions:
523 dimension_values = frappe.db.get_value(
Deepesh Garg3fa1c632022-04-25 16:29:26 +0530524 round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
Deepesh Gargc312cd32022-04-24 18:11:32 +0530525 )
526
527 for dimension in dimensions:
528 round_off_gle[dimension] = dimension_values.get(dimension)
Deepesh Garg015812b2022-04-23 21:40:08 +0530529
530
ruthra kumarebe67872023-04-28 14:07:28 +0530531def get_round_off_account_and_cost_center(
532 company, voucher_type, voucher_no, use_company_default=False
533):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530534 round_off_account, round_off_cost_center = frappe.get_cached_value(
535 "Company", company, ["round_off_account", "round_off_cost_center"]
536 ) or [None, None]
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530537
Deepesh Gargc312cd32022-04-24 18:11:32 +0530538 meta = frappe.get_meta(voucher_type)
539
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530540 # Give first preference to parent cost center for round off GLE
ruthra kumarebe67872023-04-28 14:07:28 +0530541 if not use_company_default and meta.has_field("cost_center"):
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530542 parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
543 if parent_cost_center:
544 round_off_cost_center = parent_cost_center
545
Nabin Hait2e4de832017-09-19 14:53:16 +0530546 if not round_off_account:
547 frappe.throw(_("Please mention Round Off Account in Company"))
548
549 if not round_off_cost_center:
550 frappe.throw(_("Please mention Round Off Cost Center in Company"))
551
552 return round_off_account, round_off_cost_center
553
Ankush Menat494bd9e2022-03-28 18:52:46 +0530554
555def make_reverse_gl_entries(
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530556 gl_entries=None,
557 voucher_type=None,
558 voucher_no=None,
559 adv_adj=False,
560 update_outstanding="Yes",
561 partial_cancel=False,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530562):
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530563 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530564 Get original gl entries of the voucher
565 and make reverse gl entries by swapping debit and credit
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530566 """
Anand Doshi652bc072014-04-16 15:21:46 +0530567
Nabin Hait2e296fa2013-08-28 18:53:11 +0530568 if not gl_entries:
Deepesh Gargff574502022-02-06 22:56:12 +0530569 gl_entry = frappe.qb.DocType("GL Entry")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530570 gl_entries = (
571 frappe.qb.from_(gl_entry)
572 .select("*")
573 .where(gl_entry.voucher_type == voucher_type)
574 .where(gl_entry.voucher_no == voucher_no)
575 .where(gl_entry.is_cancelled == 0)
576 .for_update()
577 ).run(as_dict=1)
Nabin Hait56548cb2016-07-01 15:58:39 +0530578
Nabin Hait27994c22013-08-26 16:53:30 +0530579 if gl_entries:
ruthra kumar7312f222022-05-29 21:33:08 +0530580 create_payment_ledger_entry(
Deepesh Garg1e078d02023-06-29 12:18:25 +0530581 gl_entries,
582 cancel=1,
583 adv_adj=adv_adj,
584 update_outstanding=update_outstanding,
585 partial_cancel=partial_cancel,
ruthra kumar7312f222022-05-29 21:33:08 +0530586 )
Deepesh Garg069a54e2020-08-10 16:01:01 +0530587 validate_accounting_period(gl_entries)
Nabin Hait27994c22013-08-26 16:53:30 +0530588 check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530589
590 is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
591 validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530592 if not partial_cancel:
593 set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
Anand Doshi652bc072014-04-16 15:21:46 +0530594
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530595 for entry in gl_entries:
Deepesh Gargffec8652022-02-02 17:14:42 +0530596 new_gle = copy.deepcopy(entry)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530597 new_gle["name"] = None
598 debit = new_gle.get("debit", 0)
599 credit = new_gle.get("credit", 0)
Anand Doshi652bc072014-04-16 15:21:46 +0530600
Ankush Menat494bd9e2022-03-28 18:52:46 +0530601 debit_in_account_currency = new_gle.get("debit_in_account_currency", 0)
602 credit_in_account_currency = new_gle.get("credit_in_account_currency", 0)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530603
Ankush Menat494bd9e2022-03-28 18:52:46 +0530604 new_gle["debit"] = credit
605 new_gle["credit"] = debit
606 new_gle["debit_in_account_currency"] = credit_in_account_currency
607 new_gle["credit_in_account_currency"] = debit_in_account_currency
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530608
Ankush Menat494bd9e2022-03-28 18:52:46 +0530609 new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
610 new_gle["is_cancelled"] = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530611
Ankush Menat494bd9e2022-03-28 18:52:46 +0530612 if new_gle["debit"] or new_gle["credit"]:
Deepesh Gargffec8652022-02-02 17:14:42 +0530613 make_entry(new_gle, adv_adj, "Yes")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530614
615
616def check_freezing_date(posting_date, adv_adj=False):
617 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530618 Nobody can do GL Entries where posting date is before freezing date
619 except authorized person
Deepesh Garg13d2e7b2021-09-16 18:54:57 +0530620
Ankush Menat494bd9e2022-03-28 18:52:46 +0530621 Administrator has all the roles so this check will be bypassed if any role is allowed to post
622 Hence stop admin to bypass if accounts are freezed
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530623 """
624 if not adv_adj:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530625 acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530626 if acc_frozen_upto:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530627 frozen_accounts_modifier = frappe.db.get_value(
628 "Accounts Settings", None, "frozen_accounts_modifier"
629 )
630 if getdate(posting_date) <= getdate(acc_frozen_upto) and (
631 frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator"
632 ):
633 frappe.throw(
634 _("You are not authorized to add or update entries before {0}").format(
635 formatdate(acc_frozen_upto)
636 )
637 )
638
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530639
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530640def validate_against_pcv(is_opening, posting_date, company):
641 if is_opening and frappe.db.exists(
642 "Period Closing Voucher", {"docstatus": 1, "company": company}
643 ):
644 frappe.throw(
645 _("Opening Entry can not be created after Period Closing Voucher is created."),
646 title=_("Invalid Opening Entry"),
647 )
648
649 last_pcv_date = frappe.db.get_value(
650 "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
651 )
652
653 if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
654 message = _("Books have been closed till the period ending on {0}").format(
655 formatdate(last_pcv_date)
656 )
657 message += "</br >"
Deepesh Garg8ce1da12023-03-23 19:14:12 +0530658 message += _("You cannot create/amend any accounting entries till this date.")
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530659 frappe.throw(message, title=_("Period Closed"))
660
661
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530662def set_as_cancel(voucher_type, voucher_no):
663 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530664 Set is_cancelled=1 in all original gl entries for the voucher
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530665 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530666 frappe.db.sql(
667 """UPDATE `tabGL Entry` SET is_cancelled = 1,
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530668 modified=%s, modified_by=%s
669 where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530670 (now(), frappe.session.user, voucher_type, voucher_no),
671 )