blob: b48a8e611582b7d10556b7b4225f0c25ee9479eb [file] [log] [blame]
Anand Doshi885e0742015-03-03 14:55:30 +05301# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
Rushabh Mehtae67d1fb2013-08-05 14:59:54 +05302# License: GNU General Public License v3. See license.txt
Nabin Haitbf495c92013-01-30 12:49:08 +05303
Chillar Anand915b3432021-09-02 16:44:59 +05304
Nabin Hait004c1ed2022-01-31 13:20:18 +05305import copy
Nabin Hait5b0ec642022-01-31 18:07:04 +05306
7import frappe
Rushabh Mehta793ba6b2014-02-14 15:47:51 +05308from frappe import _
Nabin Haite2c200a2015-05-28 13:00:37 +05309from frappe.model.meta import get_field_precision
Chillar Anand915b3432021-09-02 16:44:59 +053010from frappe.utils import cint, cstr, flt, formatdate, getdate, now
11
12import erpnext
13from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
14 get_accounting_dimensions,
15)
mergify[bot]b4db5e92023-07-18 17:40:49 +053016from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
Nabin Haitb9bc7d62016-05-16 14:38:47 +053017from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
ruthra kumare8889752022-05-16 14:28:25 +053018from erpnext.accounts.utils import create_payment_ledger_entry
Chillar Anand915b3432021-09-02 16:44:59 +053019
Nabin Haitbf495c92013-01-30 12:49:08 +053020
Ankush Menat494bd9e2022-03-28 18:52:46 +053021def make_gl_entries(
22 gl_map,
23 cancel=False,
24 adv_adj=False,
25 merge_entries=True,
26 update_outstanding="Yes",
27 from_repost=False,
28):
Nabin Hait2e296fa2013-08-28 18:53:11 +053029 if gl_map:
30 if not cancel:
Deepesh Gargecca9cb2023-07-29 11:52:54 +053031 make_acc_dimensions_offsetting_entry(gl_map)
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +053032 validate_accounting_period(gl_map)
Saqib Ansari95b059a2022-05-11 13:26:15 +053033 validate_disabled_accounts(gl_map)
Nabin Hait2e296fa2013-08-28 18:53:11 +053034 gl_map = process_gl_map(gl_map, merge_entries)
nabinhaitc3432922014-07-29 18:06:18 +053035 if gl_map and len(gl_map) > 1:
ruthra kumar7312f222022-05-29 21:33:08 +053036 create_payment_ledger_entry(
37 gl_map,
38 cancel=0,
39 adv_adj=adv_adj,
40 update_outstanding=update_outstanding,
41 from_repost=from_repost,
42 )
Nabin Haita77b8c92020-12-21 14:45:50 +053043 save_entries(gl_map, adv_adj, update_outstanding, from_repost)
Deepesh Garg94749082023-10-20 19:49:41 +053044 # Post GL Map process there may no be any GL Entries
Deepesh Garg4c8d15b2021-04-26 15:24:34 +053045 elif gl_map:
Ankush Menat494bd9e2022-03-28 18:52:46 +053046 frappe.throw(
47 _(
48 "Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."
49 )
50 )
Nabin Hait2e296fa2013-08-28 18:53:11 +053051 else:
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +053052 make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
Anand Doshi652bc072014-04-16 15:21:46 +053053
Ankush Menat494bd9e2022-03-28 18:52:46 +053054
Gursheen Anand22ba1212023-07-17 15:17:53 +053055def make_acc_dimensions_offsetting_entry(gl_map):
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053056 accounting_dimensions_to_offset = get_accounting_dimensions_for_offsetting_entry(
57 gl_map, gl_map[0].company
58 )
Gursheen Anand1e1e4b92023-07-18 12:12:24 +053059 no_of_dimensions = len(accounting_dimensions_to_offset)
60 if no_of_dimensions == 0:
Gursheen Anand22ba1212023-07-17 15:17:53 +053061 return
62
63 offsetting_entries = []
Deepesh Gargecca9cb2023-07-29 11:52:54 +053064
Gursheen Anand22ba1212023-07-17 15:17:53 +053065 for gle in gl_map:
66 for dimension in accounting_dimensions_to_offset:
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053067 offsetting_entry = gle.copy()
Gursheen Anand4f9242d2023-07-27 15:45:48 +053068 debit = flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0
69 credit = flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053070 offsetting_entry.update(
71 {
Gursheen Anand4f9242d2023-07-27 15:45:48 +053072 "account": dimension.offsetting_account,
73 "debit": debit,
74 "credit": credit,
75 "debit_in_account_currency": debit,
76 "credit_in_account_currency": credit,
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053077 "remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension.name),
78 "against_voucher": None,
79 }
Gursheen Anand22ba1212023-07-17 15:17:53 +053080 )
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053081 offsetting_entry["against_voucher_type"] = None
82 offsetting_entries.append(offsetting_entry)
Deepesh Gargecca9cb2023-07-29 11:52:54 +053083
Gursheen Anand22ba1212023-07-17 15:17:53 +053084 gl_map += offsetting_entries
85
86
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053087def get_accounting_dimensions_for_offsetting_entry(gl_map, company):
88 acc_dimension = frappe.qb.DocType("Accounting Dimension")
89 dimension_detail = frappe.qb.DocType("Accounting Dimension Detail")
Deepesh Gargecca9cb2023-07-29 11:52:54 +053090
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053091 acc_dimensions = (
92 frappe.qb.from_(acc_dimension)
93 .inner_join(dimension_detail)
94 .on(acc_dimension.name == dimension_detail.parent)
Gursheen Anand4f9242d2023-07-27 15:45:48 +053095 .select(acc_dimension.fieldname, acc_dimension.name, dimension_detail.offsetting_account)
Gursheen Anand3a3ffa22023-07-18 12:51:09 +053096 .where(
97 (acc_dimension.disabled == 0)
98 & (dimension_detail.company == company)
99 & (dimension_detail.automatically_post_balancing_accounting_entry == 1)
100 )
101 ).run(as_dict=True)
Deepesh Gargecca9cb2023-07-29 11:52:54 +0530102
Gursheen Anand22ba1212023-07-17 15:17:53 +0530103 accounting_dimensions_to_offset = []
104 for acc_dimension in acc_dimensions:
Gursheen Anande19a6f52023-07-19 12:26:57 +0530105 values = set([entry.get(acc_dimension.fieldname) for entry in gl_map])
Gursheen Anand22ba1212023-07-17 15:17:53 +0530106 if len(values) > 1:
107 accounting_dimensions_to_offset.append(acc_dimension)
Deepesh Gargecca9cb2023-07-29 11:52:54 +0530108
Gursheen Anand22ba1212023-07-17 15:17:53 +0530109 return accounting_dimensions_to_offset
110
111
Saqib Ansari95b059a2022-05-11 13:26:15 +0530112def validate_disabled_accounts(gl_map):
113 accounts = [d.account for d in gl_map if d.account]
114
115 Account = frappe.qb.DocType("Account")
116
117 disabled_accounts = (
118 frappe.qb.from_(Account)
119 .where(Account.name.isin(accounts) & Account.disabled == 1)
120 .select(Account.name, Account.disabled)
121 ).run(as_dict=True)
122
123 if disabled_accounts:
124 account_list = "<br>"
125 account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts])
126 frappe.throw(
127 _("Cannot create accounting entries against disabled accounts: {0}").format(account_list),
128 title=_("Disabled Account Selected"),
129 )
130
131
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530132def validate_accounting_period(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530133 accounting_periods = frappe.db.sql(
134 """ SELECT
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530135 ap.name as name
136 FROM
137 `tabAccounting Period` ap, `tabClosed Document` cd
138 WHERE
139 ap.name = cd.parent
140 AND ap.company = %(company)s
141 AND cd.closed = 1
142 AND cd.document_type = %(voucher_type)s
143 AND %(date)s between ap.start_date and ap.end_date
Ankush Menat494bd9e2022-03-28 18:52:46 +0530144 """,
145 {
146 "date": gl_map[0].posting_date,
147 "company": gl_map[0].company,
148 "voucher_type": gl_map[0].voucher_type,
149 },
150 as_dict=1,
151 )
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530152
153 if accounting_periods:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530154 frappe.throw(
155 _(
156 "You cannot create or cancel any accounting entries with in the closed Accounting Period {0}"
157 ).format(frappe.bold(accounting_periods[0].name)),
158 ClosedAccountingPeriod,
159 )
160
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530161
Nabin Hait19f8fa52021-02-22 22:27:22 +0530162def process_gl_map(gl_map, merge_entries=True, precision=None):
Nabin Hait6099af52022-01-31 17:24:50 +0530163 if not gl_map:
164 return []
165
Nabin Hait666d9612023-07-26 13:03:29 +0530166 if gl_map[0].voucher_type != "Period Closing Voucher":
167 gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530168
Nabin Haitbf495c92013-01-30 12:49:08 +0530169 if merge_entries:
Nabin Hait19f8fa52021-02-22 22:27:22 +0530170 gl_map = merge_similar_entries(gl_map, precision)
Anand Doshi602e8252015-11-16 19:05:46 +0530171
Nabin Hait004c1ed2022-01-31 13:20:18 +0530172 gl_map = toggle_debit_credit_if_negative(gl_map)
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530173
Nabin Hait27994c22013-08-26 16:53:30 +0530174 return gl_map
Anand Doshi652bc072014-04-16 15:21:46 +0530175
Ankush Menat494bd9e2022-03-28 18:52:46 +0530176
Nabin Hait004c1ed2022-01-31 13:20:18 +0530177def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530178 cost_center_allocation = get_cost_center_allocation_data(
179 gl_map[0]["company"], gl_map[0]["posting_date"]
180 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530181 if not cost_center_allocation:
182 return gl_map
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530183
Nabin Hait004c1ed2022-01-31 13:20:18 +0530184 new_gl_map = []
185 for d in gl_map:
186 cost_center = d.get("cost_center")
Deepesh Garg4e26d422022-10-30 19:33:27 +0530187
188 # Validate budget against main cost center
189 validate_expense_against_budget(
190 d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
191 )
192
Nabin Hait004c1ed2022-01-31 13:20:18 +0530193 if cost_center and cost_center_allocation.get(cost_center):
194 for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
195 gle = copy.deepcopy(d)
196 gle.cost_center = sub_cost_center
ruthra kumar71f6f782022-06-24 13:36:15 +0530197 for field in ("debit", "credit", "debit_in_account_currency", "credit_in_account_currency"):
Nabin Hait004c1ed2022-01-31 13:20:18 +0530198 gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
199 new_gl_map.append(gle)
200 else:
201 new_gl_map.append(d)
202
203 return new_gl_map
204
Ankush Menat494bd9e2022-03-28 18:52:46 +0530205
Nabin Hait004c1ed2022-01-31 13:20:18 +0530206def get_cost_center_allocation_data(company, posting_date):
207 par = frappe.qb.DocType("Cost Center Allocation")
208 child = frappe.qb.DocType("Cost Center Allocation Percentage")
209
210 records = (
Ankush Menat494bd9e2022-03-28 18:52:46 +0530211 frappe.qb.from_(par)
212 .inner_join(child)
213 .on(par.name == child.parent)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530214 .select(par.main_cost_center, child.cost_center, child.percentage)
215 .where(par.docstatus == 1)
216 .where(par.company == company)
217 .where(par.valid_from <= posting_date)
218 .orderby(par.valid_from, order=frappe.qb.desc)
219 ).run(as_dict=True)
220
221 cc_allocation = frappe._dict()
222 for d in records:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530223 cc_allocation.setdefault(d.main_cost_center, frappe._dict()).setdefault(
224 d.cost_center, d.percentage
225 )
Nabin Hait5b0ec642022-01-31 18:07:04 +0530226
Nabin Hait004c1ed2022-01-31 13:20:18 +0530227 return cc_allocation
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530228
Ankush Menat494bd9e2022-03-28 18:52:46 +0530229
Nabin Hait19f8fa52021-02-22 22:27:22 +0530230def merge_similar_entries(gl_map, precision=None):
Nabin Haitbf495c92013-01-30 12:49:08 +0530231 merged_gl_map = []
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530232 accounting_dimensions = get_accounting_dimensions()
Nabin Hait914a3882022-07-19 12:32:54 +0530233
Nabin Haitbf495c92013-01-30 12:49:08 +0530234 for entry in gl_map:
Anand Doshi652bc072014-04-16 15:21:46 +0530235 # if there is already an entry in this account then just add it
Nabin Haitbf495c92013-01-30 12:49:08 +0530236 # to that entry
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530237 same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
Nabin Haitbf495c92013-01-30 12:49:08 +0530238 if same_head:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530239 same_head.debit = flt(same_head.debit) + flt(entry.debit)
240 same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
241 entry.debit_in_account_currency
242 )
Nabin Hait2e296fa2013-08-28 18:53:11 +0530243 same_head.credit = flt(same_head.credit) + flt(entry.credit)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530244 same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
245 entry.credit_in_account_currency
246 )
Nabin Haitbf495c92013-01-30 12:49:08 +0530247 else:
248 merged_gl_map.append(entry)
Anand Doshi652bc072014-04-16 15:21:46 +0530249
thefalconx33bfc43d32019-12-13 15:09:51 +0530250 company = gl_map[0].company if gl_map else erpnext.get_default_company()
251 company_currency = erpnext.get_company_currency(company)
Nabin Hait19f8fa52021-02-22 22:27:22 +0530252
253 if not precision:
254 precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
thefalconx33bfc43d32019-12-13 15:09:51 +0530255
Nabin Hait815a49e2013-08-07 17:00:01 +0530256 # filter zero debit and credit entries
Ankush Menat494bd9e2022-03-28 18:52:46 +0530257 merged_gl_map = filter(
ruthra kumar914b2302023-01-02 14:33:14 +0530258 lambda x: flt(x.debit, precision) != 0
259 or flt(x.credit, precision) != 0
260 or (
261 x.voucher_type == "Journal Entry"
262 and frappe.get_cached_value("Journal Entry", x.voucher_no, "voucher_type")
263 == "Exchange Gain Or Loss"
264 ),
265 merged_gl_map,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530266 )
Achilles Rasquinha908289d2018-03-08 13:10:51 +0530267 merged_gl_map = list(merged_gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530268
Nabin Haitbf495c92013-01-30 12:49:08 +0530269 return merged_gl_map
270
Ankush Menat494bd9e2022-03-28 18:52:46 +0530271
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530272def check_if_in_list(gle, gl_map, dimensions=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530273 account_head_fieldnames = [
274 "voucher_detail_no",
275 "party",
276 "against_voucher",
277 "cost_center",
278 "against_voucher_type",
279 "party_type",
280 "project",
281 "finance_book",
Gursheen Anand442e3f22023-06-16 13:38:47 +0530282 "voucher_no",
Gursheen Anand9aeb3932023-12-02 19:50:16 +0530283 "against_link",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530284 ]
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530285
286 if dimensions:
287 account_head_fieldnames = account_head_fieldnames + dimensions
288
Nabin Haitbb777562013-08-29 18:19:37 +0530289 for e in gl_map:
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530290 same_head = True
291 if e.account != gle.account:
292 same_head = False
Ankush91527152021-08-11 11:17:50 +0530293 continue
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530294
295 for fieldname in account_head_fieldnames:
296 if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
297 same_head = False
Ankush91527152021-08-11 11:17:50 +0530298 break
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530299
300 if same_head:
301 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
359 if gl_map:
360 check_freezing_date(gl_map[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530361 is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
362 if gl_map[0]["voucher_type"] != "Period Closing Voucher":
363 validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530364
Nabin Haitbf495c92013-01-30 12:49:08 +0530365 for entry in gl_map:
Nabin Haita77b8c92020-12-21 14:45:50 +0530366 make_entry(entry, adv_adj, update_outstanding, from_repost)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530367
Nabin Hait914a3882022-07-19 12:32:54 +0530368
Nabin Haita77b8c92020-12-21 14:45:50 +0530369def make_entry(args, adv_adj, update_outstanding, from_repost=False):
Nabin Haitddc3bb22020-03-17 17:04:18 +0530370 gle = frappe.new_doc("GL Entry")
371 gle.update(args)
Anand Doshi6dfd4302015-02-10 14:41:27 +0530372 gle.flags.ignore_permissions = 1
Nabin Haita77b8c92020-12-21 14:45:50 +0530373 gle.flags.from_repost = from_repost
Nabin Hait19f8fa52021-02-22 22:27:22 +0530374 gle.flags.adv_adj = adv_adj
Ankush Menat494bd9e2022-03-28 18:52:46 +0530375 gle.flags.update_outstanding = update_outstanding or "Yes"
Nabin Hait4caaab32022-07-18 17:59:42 +0530376 gle.flags.notify_update = False
Nabin Haitaeba24e2013-08-23 15:17:36 +0530377 gle.submit()
Anand Doshi652bc072014-04-16 15:21:46 +0530378
Nabin Hait4caaab32022-07-18 17:59:42 +0530379 if not from_repost and gle.voucher_type != "Period Closing Voucher":
Nabin Haita77b8c92020-12-21 14:45:50 +0530380 validate_expense_against_budget(args)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530381
Ankush Menat494bd9e2022-03-28 18:52:46 +0530382
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530383def validate_cwip_accounts(gl_map):
Ankush91527152021-08-11 11:17:50 +0530384 """Validate that CWIP account are not used in Journal Entry"""
385 if gl_map and gl_map[0].voucher_type != "Journal Entry":
386 return
Maricad00c5982019-11-12 19:17:43 +0530387
Ankush Menat494bd9e2022-03-28 18:52:46 +0530388 cwip_enabled = any(
389 cint(ac.enable_cwip_accounting)
390 for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
391 )
Ankush91527152021-08-11 11:17:50 +0530392 if cwip_enabled:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530393 cwip_accounts = [
394 d[0]
395 for d in frappe.db.sql(
396 """select name from tabAccount
397 where account_type = 'Capital Work in Progress' and is_group=0"""
398 )
399 ]
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530400
Ankush91527152021-08-11 11:17:50 +0530401 for entry in gl_map:
402 if entry.account in cwip_accounts:
403 frappe.throw(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530404 _(
405 "Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry"
406 ).format(entry.account)
407 )
408
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530409
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530410def process_debit_credit_difference(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530411 precision = get_field_precision(
412 frappe.get_meta("GL Entry").get_field("debit"),
413 currency=frappe.get_cached_value("Company", gl_map[0].company, "default_currency"),
414 )
Anand Doshi602e8252015-11-16 19:05:46 +0530415
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530416 voucher_type = gl_map[0].voucher_type
417 voucher_no = gl_map[0].voucher_no
418 allowance = get_debit_credit_allowance(voucher_type, precision)
419
420 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
ruthra kumar914b2302023-01-02 14:33:14 +0530421
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530422 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530423 if not (
424 voucher_type == "Journal Entry"
425 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
426 == "Exchange Gain Or Loss"
427 ):
428 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530429
430 elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
431 make_round_off_gle(gl_map, debit_credit_diff, precision)
432
433 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
434 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530435 if not (
436 voucher_type == "Journal Entry"
437 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
438 == "Exchange Gain Or Loss"
439 ):
440 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530441
442
443def get_debit_credit_difference(gl_map, precision):
Nabin Haite2c200a2015-05-28 13:00:37 +0530444 debit_credit_diff = 0.0
445 for entry in gl_map:
446 entry.debit = flt(entry.debit, precision)
447 entry.credit = flt(entry.credit, precision)
448 debit_credit_diff += entry.debit - entry.credit
Anand Doshi602e8252015-11-16 19:05:46 +0530449
Nabin Haite2c200a2015-05-28 13:00:37 +0530450 debit_credit_diff = flt(debit_credit_diff, precision)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530451
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530452 return debit_credit_diff
453
454
455def get_debit_credit_allowance(voucher_type, precision):
456 if voucher_type in ("Journal Entry", "Payment Entry"):
Nabin Haitfb24a272016-02-18 19:18:07 +0530457 allowance = 5.0 / (10**precision)
458 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530459 allowance = 0.5
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530460
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530461 return allowance
Anand Doshi602e8252015-11-16 19:05:46 +0530462
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530463
464def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no):
465 frappe.throw(
466 _("Debit and Credit not equal for {0} #{1}. Difference is {2}.").format(
467 voucher_type, voucher_no, debit_credit_diff
468 )
469 )
Anand Doshi602e8252015-11-16 19:05:46 +0530470
Ankush Menat494bd9e2022-03-28 18:52:46 +0530471
Nabin Hait34c551d2019-07-03 10:34:31 +0530472def make_round_off_gle(gl_map, debit_credit_diff, precision):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530473 round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530474 gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
Ankush Menat494bd9e2022-03-28 18:52:46 +0530475 )
Nabin Hait80069a62015-05-28 19:19:59 +0530476 round_off_gle = frappe._dict()
Nabin Hait022d8d52022-11-21 15:16:53 +0530477 round_off_account_exists = False
Zarrar3523b772018-08-14 16:28:14 +0530478
Nabin Hait022d8d52022-11-21 15:16:53 +0530479 if gl_map[0].voucher_type != "Period Closing Voucher":
480 for d in gl_map:
481 if d.account == round_off_account:
482 round_off_gle = d
483 if d.debit:
484 debit_credit_diff -= flt(d.debit) - flt(d.credit)
485 else:
486 debit_credit_diff += flt(d.credit)
487 round_off_account_exists = True
488
489 if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
490 gl_map.remove(round_off_gle)
491 return
Nabin Hait34c551d2019-07-03 10:34:31 +0530492
Zarrar3523b772018-08-14 16:28:14 +0530493 if not round_off_gle:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530494 for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
495 round_off_gle[k] = gl_map[0][k]
Anand Doshi602e8252015-11-16 19:05:46 +0530496
Ankush Menat494bd9e2022-03-28 18:52:46 +0530497 round_off_gle.update(
498 {
499 "account": round_off_account,
500 "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
501 "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
502 "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
503 "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
504 "cost_center": round_off_cost_center,
505 "party_type": None,
506 "party": None,
507 "is_opening": "No",
508 "against_voucher_type": None,
509 "against_voucher": None,
510 }
511 )
Anand Doshi602e8252015-11-16 19:05:46 +0530512
Deepesh Garg015812b2022-04-23 21:40:08 +0530513 update_accounting_dimensions(round_off_gle)
Zarrar3523b772018-08-14 16:28:14 +0530514 if not round_off_account_exists:
515 gl_map.append(round_off_gle)
Nabin Haite2c200a2015-05-28 13:00:37 +0530516
Ankush Menat494bd9e2022-03-28 18:52:46 +0530517
Deepesh Garg015812b2022-04-23 21:40:08 +0530518def update_accounting_dimensions(round_off_gle):
519 dimensions = get_accounting_dimensions()
Deepesh Gargc312cd32022-04-24 18:11:32 +0530520 meta = frappe.get_meta(round_off_gle["voucher_type"])
521 has_all_dimensions = True
Deepesh Garg015812b2022-04-23 21:40:08 +0530522
523 for dimension in dimensions:
Deepesh Gargc312cd32022-04-24 18:11:32 +0530524 if not meta.has_field(dimension):
525 has_all_dimensions = False
526
527 if dimensions and has_all_dimensions:
528 dimension_values = frappe.db.get_value(
Deepesh Garg3fa1c632022-04-25 16:29:26 +0530529 round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
Deepesh Gargc312cd32022-04-24 18:11:32 +0530530 )
531
532 for dimension in dimensions:
533 round_off_gle[dimension] = dimension_values.get(dimension)
Deepesh Garg015812b2022-04-23 21:40:08 +0530534
535
ruthra kumarebe67872023-04-28 14:07:28 +0530536def get_round_off_account_and_cost_center(
537 company, voucher_type, voucher_no, use_company_default=False
538):
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)
Nabin Hait27994c22013-08-26 16:53:30 +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):
673 if is_opening and frappe.db.exists(
674 "Period Closing Voucher", {"docstatus": 1, "company": company}
675 ):
676 frappe.throw(
677 _("Opening Entry can not be created after Period Closing Voucher is created."),
678 title=_("Invalid Opening Entry"),
679 )
680
681 last_pcv_date = frappe.db.get_value(
682 "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
683 )
684
685 if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
686 message = _("Books have been closed till the period ending on {0}").format(
687 formatdate(last_pcv_date)
688 )
689 message += "</br >"
Deepesh Garg8ce1da12023-03-23 19:14:12 +0530690 message += _("You cannot create/amend any accounting entries till this date.")
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530691 frappe.throw(message, title=_("Period Closed"))
692
693
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530694def set_as_cancel(voucher_type, voucher_no):
695 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530696 Set is_cancelled=1 in all original gl entries for the voucher
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530697 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530698 frappe.db.sql(
699 """UPDATE `tabGL Entry` SET is_cancelled = 1,
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530700 modified=%s, modified_by=%s
701 where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530702 (now(), frappe.session.user, voucher_type, voucher_no),
703 )