blob: 700d777f25150b2c7fa8d0d1b2258e77f02f628c [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
Nabin Hait26202d92024-03-13 18:17:41 +053010from frappe.utils import cint, flt, formatdate, getdate, now
Chillar Anand915b3432021-09-02 16:44:59 +053011
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,
Akhil Narang3effaf22024-03-27 11:37:26 +053081 "remarks": _("Offsetting for Accounting Dimension") + f" - {dimension.name}",
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053082 "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):
Akhil Narang3effaf22024-03-27 11:37:26 +0530182 cost_center_allocation = get_cost_center_allocation_data(gl_map[0]["company"], gl_map[0]["posting_date"])
Nabin Hait004c1ed2022-01-31 13:20:18 +0530183 if not cost_center_allocation:
184 return gl_map
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530185
Nabin Hait004c1ed2022-01-31 13:20:18 +0530186 new_gl_map = []
187 for d in gl_map:
188 cost_center = d.get("cost_center")
Deepesh Garg4e26d422022-10-30 19:33:27 +0530189
190 # Validate budget against main cost center
Akhil Narang3effaf22024-03-27 11:37:26 +0530191 validate_expense_against_budget(d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision))
Deepesh Garg4e26d422022-10-30 19:33:27 +0530192
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:
Akhil Narang3effaf22024-03-27 11:37:26 +0530223 cc_allocation.setdefault(d.main_cost_center, frappe._dict()).setdefault(d.cost_center, d.percentage)
Nabin Hait5b0ec642022-01-31 18:07:04 +0530224
Nabin Hait004c1ed2022-01-31 13:20:18 +0530225 return cc_allocation
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530226
Ankush Menat494bd9e2022-03-28 18:52:46 +0530227
Nabin Hait19f8fa52021-02-22 22:27:22 +0530228def merge_similar_entries(gl_map, precision=None):
Nabin Haitbf495c92013-01-30 12:49:08 +0530229 merged_gl_map = []
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530230 accounting_dimensions = get_accounting_dimensions()
Nabin Hait26202d92024-03-13 18:17:41 +0530231 merge_properties = get_merge_properties(accounting_dimensions)
Nabin Hait914a3882022-07-19 12:32:54 +0530232
Nabin Haitbf495c92013-01-30 12:49:08 +0530233 for entry in gl_map:
Nabin Hait26202d92024-03-13 18:17:41 +0530234 entry.merge_key = get_merge_key(entry, merge_properties)
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
Nabin Hait26202d92024-03-13 18:17:41 +0530237 same_head = check_if_in_list(entry, merged_gl_map)
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
Nabin Hait26202d92024-03-13 18:17:41 +0530272def get_merge_properties(dimensions=None):
273 merge_properties = [
274 "account",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530275 "cost_center",
Nabin Hait26202d92024-03-13 18:17:41 +0530276 "party",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530277 "party_type",
Nabin Hait26202d92024-03-13 18:17:41 +0530278 "voucher_detail_no",
279 "against_voucher",
280 "against_voucher_type",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530281 "project",
282 "finance_book",
Gursheen Anand442e3f22023-06-16 13:38:47 +0530283 "voucher_no",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530284 ]
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530285 if dimensions:
Nabin Hait26202d92024-03-13 18:17:41 +0530286 merge_properties.extend(dimensions)
287 return merge_properties
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530288
Nabin Hait26202d92024-03-13 18:17:41 +0530289
290def get_merge_key(entry, merge_properties):
291 merge_key = []
292 for fieldname in merge_properties:
293 merge_key.append(entry.get(fieldname, ""))
294
295 return tuple(merge_key)
296
297
298def check_if_in_list(gle, gl_map):
Nabin Haitbb777562013-08-29 18:19:37 +0530299 for e in gl_map:
Nabin Hait26202d92024-03-13 18:17:41 +0530300 if e.merge_key == gle.merge_key:
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530301 return e
Nabin Haitbf495c92013-01-30 12:49:08 +0530302
Ankush Menat494bd9e2022-03-28 18:52:46 +0530303
Nabin Hait004c1ed2022-01-31 13:20:18 +0530304def toggle_debit_credit_if_negative(gl_map):
305 for entry in gl_map:
306 # toggle debit, credit if negative entry
307 if flt(entry.debit) < 0:
308 entry.credit = flt(entry.credit) - flt(entry.debit)
309 entry.debit = 0.0
310
311 if flt(entry.debit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530312 entry.credit_in_account_currency = flt(entry.credit_in_account_currency) - flt(
313 entry.debit_in_account_currency
314 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530315 entry.debit_in_account_currency = 0.0
316
317 if flt(entry.credit) < 0:
318 entry.debit = flt(entry.debit) - flt(entry.credit)
319 entry.credit = 0.0
320
321 if flt(entry.credit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530322 entry.debit_in_account_currency = flt(entry.debit_in_account_currency) - flt(
323 entry.credit_in_account_currency
324 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530325 entry.credit_in_account_currency = 0.0
326
327 update_net_values(entry)
328
329 return gl_map
330
Ankush Menat494bd9e2022-03-28 18:52:46 +0530331
Nabin Hait004c1ed2022-01-31 13:20:18 +0530332def update_net_values(entry):
333 # In some scenarios net value needs to be shown in the ledger
334 # This method updates net values as debit or credit
335 if entry.post_net_value and entry.debit and entry.credit:
336 if entry.debit > entry.credit:
337 entry.debit = entry.debit - entry.credit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530338 entry.debit_in_account_currency = (
339 entry.debit_in_account_currency - entry.credit_in_account_currency
340 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530341 entry.credit = 0
342 entry.credit_in_account_currency = 0
343 else:
344 entry.credit = entry.credit - entry.debit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530345 entry.credit_in_account_currency = (
346 entry.credit_in_account_currency - entry.debit_in_account_currency
347 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530348
349 entry.debit = 0
350 entry.debit_in_account_currency = 0
351
Ankush Menat494bd9e2022-03-28 18:52:46 +0530352
Nabin Haita77b8c92020-12-21 14:45:50 +0530353def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
354 if not from_repost:
355 validate_cwip_accounts(gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530356
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530357 process_debit_credit_difference(gl_map)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530358
Deepesh Gargb834ed12024-02-05 14:05:01 +0530359 dimension_filter_map = get_dimension_filter_map()
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530360 if gl_map:
Deepesh Garg3aa17fa2024-01-17 12:47:27 +0530361 check_freezing_date(gl_map[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530362 is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
363 if gl_map[0]["voucher_type"] != "Period Closing Voucher":
364 validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530365
Nabin Haitbf495c92013-01-30 12:49:08 +0530366 for entry in gl_map:
Deepesh Gargb834ed12024-02-05 14:05:01 +0530367 validate_allowed_dimensions(entry, dimension_filter_map)
Nabin Haita77b8c92020-12-21 14:45:50 +0530368 make_entry(entry, adv_adj, update_outstanding, from_repost)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530369
Nabin Hait914a3882022-07-19 12:32:54 +0530370
Nabin Haita77b8c92020-12-21 14:45:50 +0530371def make_entry(args, adv_adj, update_outstanding, from_repost=False):
Nabin Haitddc3bb22020-03-17 17:04:18 +0530372 gle = frappe.new_doc("GL Entry")
373 gle.update(args)
Anand Doshi6dfd4302015-02-10 14:41:27 +0530374 gle.flags.ignore_permissions = 1
Nabin Haita77b8c92020-12-21 14:45:50 +0530375 gle.flags.from_repost = from_repost
Nabin Hait19f8fa52021-02-22 22:27:22 +0530376 gle.flags.adv_adj = adv_adj
Ankush Menat494bd9e2022-03-28 18:52:46 +0530377 gle.flags.update_outstanding = update_outstanding or "Yes"
Nabin Hait4caaab32022-07-18 17:59:42 +0530378 gle.flags.notify_update = False
Nabin Haitaeba24e2013-08-23 15:17:36 +0530379 gle.submit()
Anand Doshi652bc072014-04-16 15:21:46 +0530380
Nabin Hait4caaab32022-07-18 17:59:42 +0530381 if not from_repost and gle.voucher_type != "Period Closing Voucher":
Nabin Haita77b8c92020-12-21 14:45:50 +0530382 validate_expense_against_budget(args)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530383
Ankush Menat494bd9e2022-03-28 18:52:46 +0530384
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530385def validate_cwip_accounts(gl_map):
Ankush91527152021-08-11 11:17:50 +0530386 """Validate that CWIP account are not used in Journal Entry"""
387 if gl_map and gl_map[0].voucher_type != "Journal Entry":
388 return
Maricad00c5982019-11-12 19:17:43 +0530389
Ankush Menat494bd9e2022-03-28 18:52:46 +0530390 cwip_enabled = any(
391 cint(ac.enable_cwip_accounting)
392 for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
393 )
Ankush91527152021-08-11 11:17:50 +0530394 if cwip_enabled:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530395 cwip_accounts = [
396 d[0]
397 for d in frappe.db.sql(
398 """select name from tabAccount
399 where account_type = 'Capital Work in Progress' and is_group=0"""
400 )
401 ]
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530402
Ankush91527152021-08-11 11:17:50 +0530403 for entry in gl_map:
404 if entry.account in cwip_accounts:
405 frappe.throw(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530406 _(
407 "Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry"
408 ).format(entry.account)
409 )
410
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530411
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530412def process_debit_credit_difference(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530413 precision = get_field_precision(
414 frappe.get_meta("GL Entry").get_field("debit"),
415 currency=frappe.get_cached_value("Company", gl_map[0].company, "default_currency"),
416 )
Anand Doshi602e8252015-11-16 19:05:46 +0530417
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530418 voucher_type = gl_map[0].voucher_type
419 voucher_no = gl_map[0].voucher_no
420 allowance = get_debit_credit_allowance(voucher_type, precision)
421
422 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
ruthra kumar914b2302023-01-02 14:33:14 +0530423
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530424 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530425 if not (
426 voucher_type == "Journal Entry"
427 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
428 == "Exchange Gain Or Loss"
429 ):
430 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530431
432 elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
433 make_round_off_gle(gl_map, debit_credit_diff, precision)
434
435 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
436 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530437 if not (
438 voucher_type == "Journal Entry"
439 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
440 == "Exchange Gain Or Loss"
441 ):
442 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530443
444
445def get_debit_credit_difference(gl_map, precision):
Nabin Haite2c200a2015-05-28 13:00:37 +0530446 debit_credit_diff = 0.0
447 for entry in gl_map:
448 entry.debit = flt(entry.debit, precision)
449 entry.credit = flt(entry.credit, precision)
450 debit_credit_diff += entry.debit - entry.credit
Anand Doshi602e8252015-11-16 19:05:46 +0530451
Nabin Haite2c200a2015-05-28 13:00:37 +0530452 debit_credit_diff = flt(debit_credit_diff, precision)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530453
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530454 return debit_credit_diff
455
456
457def get_debit_credit_allowance(voucher_type, precision):
458 if voucher_type in ("Journal Entry", "Payment Entry"):
Nabin Haitfb24a272016-02-18 19:18:07 +0530459 allowance = 5.0 / (10**precision)
460 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530461 allowance = 0.5
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530462
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530463 return allowance
Anand Doshi602e8252015-11-16 19:05:46 +0530464
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530465
466def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no):
467 frappe.throw(
468 _("Debit and Credit not equal for {0} #{1}. Difference is {2}.").format(
469 voucher_type, voucher_no, debit_credit_diff
470 )
471 )
Anand Doshi602e8252015-11-16 19:05:46 +0530472
Ankush Menat494bd9e2022-03-28 18:52:46 +0530473
Nabin Hait34c551d2019-07-03 10:34:31 +0530474def make_round_off_gle(gl_map, debit_credit_diff, precision):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530475 round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530476 gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
Ankush Menat494bd9e2022-03-28 18:52:46 +0530477 )
Nabin Hait80069a62015-05-28 19:19:59 +0530478 round_off_gle = frappe._dict()
Nabin Hait022d8d52022-11-21 15:16:53 +0530479 round_off_account_exists = False
Zarrar3523b772018-08-14 16:28:14 +0530480
Nabin Hait022d8d52022-11-21 15:16:53 +0530481 if gl_map[0].voucher_type != "Period Closing Voucher":
482 for d in gl_map:
483 if d.account == round_off_account:
484 round_off_gle = d
485 if d.debit:
486 debit_credit_diff -= flt(d.debit) - flt(d.credit)
487 else:
488 debit_credit_diff += flt(d.credit)
489 round_off_account_exists = True
490
491 if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
492 gl_map.remove(round_off_gle)
493 return
Nabin Hait34c551d2019-07-03 10:34:31 +0530494
Zarrar3523b772018-08-14 16:28:14 +0530495 if not round_off_gle:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530496 for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
497 round_off_gle[k] = gl_map[0][k]
Anand Doshi602e8252015-11-16 19:05:46 +0530498
Ankush Menat494bd9e2022-03-28 18:52:46 +0530499 round_off_gle.update(
500 {
501 "account": round_off_account,
502 "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
503 "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
504 "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
505 "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
506 "cost_center": round_off_cost_center,
507 "party_type": None,
508 "party": None,
509 "is_opening": "No",
510 "against_voucher_type": None,
511 "against_voucher": None,
512 }
513 )
Anand Doshi602e8252015-11-16 19:05:46 +0530514
Deepesh Garg015812b2022-04-23 21:40:08 +0530515 update_accounting_dimensions(round_off_gle)
Zarrar3523b772018-08-14 16:28:14 +0530516 if not round_off_account_exists:
517 gl_map.append(round_off_gle)
Nabin Haite2c200a2015-05-28 13:00:37 +0530518
Ankush Menat494bd9e2022-03-28 18:52:46 +0530519
Deepesh Garg015812b2022-04-23 21:40:08 +0530520def update_accounting_dimensions(round_off_gle):
521 dimensions = get_accounting_dimensions()
Deepesh Gargc312cd32022-04-24 18:11:32 +0530522 meta = frappe.get_meta(round_off_gle["voucher_type"])
523 has_all_dimensions = True
Deepesh Garg015812b2022-04-23 21:40:08 +0530524
525 for dimension in dimensions:
Deepesh Gargc312cd32022-04-24 18:11:32 +0530526 if not meta.has_field(dimension):
527 has_all_dimensions = False
528
529 if dimensions and has_all_dimensions:
530 dimension_values = frappe.db.get_value(
Deepesh Garg3fa1c632022-04-25 16:29:26 +0530531 round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
Deepesh Gargc312cd32022-04-24 18:11:32 +0530532 )
533
534 for dimension in dimensions:
535 round_off_gle[dimension] = dimension_values.get(dimension)
Deepesh Garg015812b2022-04-23 21:40:08 +0530536
537
Akhil Narang3effaf22024-03-27 11:37:26 +0530538def get_round_off_account_and_cost_center(company, voucher_type, voucher_no, use_company_default=False):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530539 round_off_account, round_off_cost_center = frappe.get_cached_value(
540 "Company", company, ["round_off_account", "round_off_cost_center"]
541 ) or [None, None]
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530542
Deepesh Garg5f75aea2023-08-24 17:58:51 +0530543 # Use expense account as fallback
544 if not round_off_account:
545 round_off_account = frappe.get_cached_value("Company", company, "default_expense_account")
546
Deepesh Gargc312cd32022-04-24 18:11:32 +0530547 meta = frappe.get_meta(voucher_type)
548
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530549 # Give first preference to parent cost center for round off GLE
ruthra kumarebe67872023-04-28 14:07:28 +0530550 if not use_company_default and meta.has_field("cost_center"):
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530551 parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
552 if parent_cost_center:
553 round_off_cost_center = parent_cost_center
554
Nabin Hait2e4de832017-09-19 14:53:16 +0530555 if not round_off_account:
556 frappe.throw(_("Please mention Round Off Account in Company"))
557
558 if not round_off_cost_center:
559 frappe.throw(_("Please mention Round Off Cost Center in Company"))
560
561 return round_off_account, round_off_cost_center
562
Ankush Menat494bd9e2022-03-28 18:52:46 +0530563
564def make_reverse_gl_entries(
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530565 gl_entries=None,
566 voucher_type=None,
567 voucher_no=None,
568 adv_adj=False,
569 update_outstanding="Yes",
570 partial_cancel=False,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530571):
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530572 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530573 Get original gl entries of the voucher
574 and make reverse gl entries by swapping debit and credit
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530575 """
Anand Doshi652bc072014-04-16 15:21:46 +0530576
Nabin Hait2e296fa2013-08-28 18:53:11 +0530577 if not gl_entries:
Deepesh Gargff574502022-02-06 22:56:12 +0530578 gl_entry = frappe.qb.DocType("GL Entry")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530579 gl_entries = (
580 frappe.qb.from_(gl_entry)
581 .select("*")
582 .where(gl_entry.voucher_type == voucher_type)
583 .where(gl_entry.voucher_no == voucher_no)
584 .where(gl_entry.is_cancelled == 0)
585 .for_update()
586 ).run(as_dict=1)
Nabin Hait56548cb2016-07-01 15:58:39 +0530587
Nabin Hait27994c22013-08-26 16:53:30 +0530588 if gl_entries:
ruthra kumar7312f222022-05-29 21:33:08 +0530589 create_payment_ledger_entry(
Deepesh Garg1e078d02023-06-29 12:18:25 +0530590 gl_entries,
591 cancel=1,
592 adv_adj=adv_adj,
593 update_outstanding=update_outstanding,
594 partial_cancel=partial_cancel,
ruthra kumar7312f222022-05-29 21:33:08 +0530595 )
Deepesh Garg069a54e2020-08-10 16:01:01 +0530596 validate_accounting_period(gl_entries)
Deepesh Garg3aa17fa2024-01-17 12:47:27 +0530597 check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530598
599 is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
600 validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
ruthra kumar2633d7d2023-11-28 17:05:29 +0530601 if partial_cancel:
602 # Partial cancel is only used by `Advance` in separate account feature.
603 # Only cancel GL entries for unlinked reference using `voucher_detail_no`
604 gle = frappe.qb.DocType("GL Entry")
605 for x in gl_entries:
606 query = (
607 frappe.qb.update(gle)
608 .set(gle.is_cancelled, True)
609 .set(gle.modified, now())
610 .set(gle.modified_by, frappe.session.user)
611 .where(
612 (gle.company == x.company)
613 & (gle.account == x.account)
614 & (gle.party_type == x.party_type)
615 & (gle.party == x.party)
616 & (gle.voucher_type == x.voucher_type)
617 & (gle.voucher_no == x.voucher_no)
618 & (gle.against_voucher_type == x.against_voucher_type)
619 & (gle.against_voucher == x.against_voucher)
620 & (gle.voucher_detail_no == x.voucher_detail_no)
621 )
622 )
623 query.run()
624 else:
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530625 set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
Anand Doshi652bc072014-04-16 15:21:46 +0530626
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530627 for entry in gl_entries:
Deepesh Gargffec8652022-02-02 17:14:42 +0530628 new_gle = copy.deepcopy(entry)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530629 new_gle["name"] = None
630 debit = new_gle.get("debit", 0)
631 credit = new_gle.get("credit", 0)
Anand Doshi652bc072014-04-16 15:21:46 +0530632
Ankush Menat494bd9e2022-03-28 18:52:46 +0530633 debit_in_account_currency = new_gle.get("debit_in_account_currency", 0)
634 credit_in_account_currency = new_gle.get("credit_in_account_currency", 0)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530635
Ankush Menat494bd9e2022-03-28 18:52:46 +0530636 new_gle["debit"] = credit
637 new_gle["credit"] = debit
638 new_gle["debit_in_account_currency"] = credit_in_account_currency
639 new_gle["credit_in_account_currency"] = debit_in_account_currency
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530640
Ankush Menat494bd9e2022-03-28 18:52:46 +0530641 new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
642 new_gle["is_cancelled"] = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530643
Ankush Menat494bd9e2022-03-28 18:52:46 +0530644 if new_gle["debit"] or new_gle["credit"]:
Deepesh Gargffec8652022-02-02 17:14:42 +0530645 make_entry(new_gle, adv_adj, "Yes")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530646
647
648def check_freezing_date(posting_date, adv_adj=False):
649 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530650 Nobody can do GL Entries where posting date is before freezing date
651 except authorized person
Deepesh Garg13d2e7b2021-09-16 18:54:57 +0530652
Ankush Menat494bd9e2022-03-28 18:52:46 +0530653 Administrator has all the roles so this check will be bypassed if any role is allowed to post
654 Hence stop admin to bypass if accounts are freezed
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530655 """
656 if not adv_adj:
Ankush Menatbb18ae82024-01-09 21:56:47 +0530657 acc_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530658 if acc_frozen_upto:
Ankush Menatbb18ae82024-01-09 21:56:47 +0530659 frozen_accounts_modifier = frappe.db.get_single_value(
660 "Accounts Settings", "frozen_accounts_modifier"
Ankush Menat494bd9e2022-03-28 18:52:46 +0530661 )
662 if getdate(posting_date) <= getdate(acc_frozen_upto) and (
663 frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator"
664 ):
665 frappe.throw(
666 _("You are not authorized to add or update entries before {0}").format(
667 formatdate(acc_frozen_upto)
668 )
669 )
670
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530671
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530672def validate_against_pcv(is_opening, posting_date, company):
Akhil Narang3effaf22024-03-27 11:37:26 +0530673 if is_opening and frappe.db.exists("Period Closing Voucher", {"docstatus": 1, "company": company}):
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530674 frappe.throw(
675 _("Opening Entry can not be created after Period Closing Voucher is created."),
676 title=_("Invalid Opening Entry"),
677 )
678
679 last_pcv_date = frappe.db.get_value(
680 "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
681 )
682
683 if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
Akhil Narang3effaf22024-03-27 11:37:26 +0530684 message = _("Books have been closed till the period ending on {0}").format(formatdate(last_pcv_date))
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530685 message += "</br >"
Deepesh Garg8ce1da12023-03-23 19:14:12 +0530686 message += _("You cannot create/amend any accounting entries till this date.")
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530687 frappe.throw(message, title=_("Period Closed"))
688
689
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530690def set_as_cancel(voucher_type, voucher_no):
691 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530692 Set is_cancelled=1 in all original gl entries for the voucher
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530693 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530694 frappe.db.sql(
695 """UPDATE `tabGL Entry` SET is_cancelled = 1,
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530696 modified=%s, modified_by=%s
697 where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530698 (now(), frappe.session.user, voucher_type, voucher_no),
699 )
Deepesh Gargb834ed12024-02-05 14:05:01 +0530700
701
702def validate_allowed_dimensions(gl_entry, dimension_filter_map):
703 for key, value in dimension_filter_map.items():
704 dimension = key[0]
705 account = key[1]
706
707 if gl_entry.account == account:
708 if value["is_mandatory"] and not gl_entry.get(dimension):
709 frappe.throw(
710 _("{0} is mandatory for account {1}").format(
711 frappe.bold(frappe.unscrub(dimension)), frappe.bold(gl_entry.account)
712 ),
713 MandatoryAccountDimensionError,
714 )
715
716 if value["allow_or_restrict"] == "Allow":
717 if gl_entry.get(dimension) and gl_entry.get(dimension) not in value["allowed_dimensions"]:
718 frappe.throw(
719 _("Invalid value {0} for {1} against account {2}").format(
720 frappe.bold(gl_entry.get(dimension)),
721 frappe.bold(frappe.unscrub(dimension)),
722 frappe.bold(gl_entry.account),
723 ),
724 InvalidAccountDimensionError,
725 )
726 else:
727 if gl_entry.get(dimension) and gl_entry.get(dimension) in value["allowed_dimensions"]:
728 frappe.throw(
729 _("Invalid value {0} for {1} against account {2}").format(
730 frappe.bold(gl_entry.get(dimension)),
731 frappe.bold(frappe.unscrub(dimension)),
732 frappe.bold(gl_entry.account),
733 ),
734 InvalidAccountDimensionError,
735 )