blob: 994c7c30aa8de30ae3b2985227381c5b9e6bff37 [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)
94 .select(acc_dimension.name, dimension_detail.offsetting_account)
95 .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 Anand3a3ffa22023-07-18 12:51:09 +0530103 fieldname = acc_dimension.name.lower().replace(" ", "_")
Gursheen Ananded3bef12023-07-17 18:40:52 +0530104 values = set([entry.get(fieldname) for entry in gl_map])
Gursheen Anand22ba1212023-07-17 15:17:53 +0530105 if len(values) > 1:
106 accounting_dimensions_to_offset.append(acc_dimension)
107 return accounting_dimensions_to_offset
108
109
Saqib Ansari95b059a2022-05-11 13:26:15 +0530110def validate_disabled_accounts(gl_map):
111 accounts = [d.account for d in gl_map if d.account]
112
113 Account = frappe.qb.DocType("Account")
114
115 disabled_accounts = (
116 frappe.qb.from_(Account)
117 .where(Account.name.isin(accounts) & Account.disabled == 1)
118 .select(Account.name, Account.disabled)
119 ).run(as_dict=True)
120
121 if disabled_accounts:
122 account_list = "<br>"
123 account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts])
124 frappe.throw(
125 _("Cannot create accounting entries against disabled accounts: {0}").format(account_list),
126 title=_("Disabled Account Selected"),
127 )
128
129
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530130def validate_accounting_period(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530131 accounting_periods = frappe.db.sql(
132 """ SELECT
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530133 ap.name as name
134 FROM
135 `tabAccounting Period` ap, `tabClosed Document` cd
136 WHERE
137 ap.name = cd.parent
138 AND ap.company = %(company)s
139 AND cd.closed = 1
140 AND cd.document_type = %(voucher_type)s
141 AND %(date)s between ap.start_date and ap.end_date
Ankush Menat494bd9e2022-03-28 18:52:46 +0530142 """,
143 {
144 "date": gl_map[0].posting_date,
145 "company": gl_map[0].company,
146 "voucher_type": gl_map[0].voucher_type,
147 },
148 as_dict=1,
149 )
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530150
151 if accounting_periods:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530152 frappe.throw(
153 _(
154 "You cannot create or cancel any accounting entries with in the closed Accounting Period {0}"
155 ).format(frappe.bold(accounting_periods[0].name)),
156 ClosedAccountingPeriod,
157 )
158
Mangesh-Khairnare5c733b2019-08-08 15:44:11 +0530159
Nabin Hait19f8fa52021-02-22 22:27:22 +0530160def process_gl_map(gl_map, merge_entries=True, precision=None):
Nabin Hait6099af52022-01-31 17:24:50 +0530161 if not gl_map:
162 return []
163
Nabin Hait004c1ed2022-01-31 13:20:18 +0530164 gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
165
Nabin Haitbf495c92013-01-30 12:49:08 +0530166 if merge_entries:
Nabin Hait19f8fa52021-02-22 22:27:22 +0530167 gl_map = merge_similar_entries(gl_map, precision)
Anand Doshi602e8252015-11-16 19:05:46 +0530168
Nabin Hait004c1ed2022-01-31 13:20:18 +0530169 gl_map = toggle_debit_credit_if_negative(gl_map)
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530170
Nabin Hait27994c22013-08-26 16:53:30 +0530171 return gl_map
Anand Doshi652bc072014-04-16 15:21:46 +0530172
Ankush Menat494bd9e2022-03-28 18:52:46 +0530173
Nabin Hait004c1ed2022-01-31 13:20:18 +0530174def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530175 cost_center_allocation = get_cost_center_allocation_data(
176 gl_map[0]["company"], gl_map[0]["posting_date"]
177 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530178 if not cost_center_allocation:
179 return gl_map
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530180
Nabin Hait004c1ed2022-01-31 13:20:18 +0530181 new_gl_map = []
182 for d in gl_map:
183 cost_center = d.get("cost_center")
Deepesh Garg4e26d422022-10-30 19:33:27 +0530184
185 # Validate budget against main cost center
186 validate_expense_against_budget(
187 d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
188 )
189
Nabin Hait004c1ed2022-01-31 13:20:18 +0530190 if cost_center and cost_center_allocation.get(cost_center):
191 for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
192 gle = copy.deepcopy(d)
193 gle.cost_center = sub_cost_center
ruthra kumar71f6f782022-06-24 13:36:15 +0530194 for field in ("debit", "credit", "debit_in_account_currency", "credit_in_account_currency"):
Nabin Hait004c1ed2022-01-31 13:20:18 +0530195 gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
196 new_gl_map.append(gle)
197 else:
198 new_gl_map.append(d)
199
200 return new_gl_map
201
Ankush Menat494bd9e2022-03-28 18:52:46 +0530202
Nabin Hait004c1ed2022-01-31 13:20:18 +0530203def get_cost_center_allocation_data(company, posting_date):
204 par = frappe.qb.DocType("Cost Center Allocation")
205 child = frappe.qb.DocType("Cost Center Allocation Percentage")
206
207 records = (
Ankush Menat494bd9e2022-03-28 18:52:46 +0530208 frappe.qb.from_(par)
209 .inner_join(child)
210 .on(par.name == child.parent)
Nabin Hait004c1ed2022-01-31 13:20:18 +0530211 .select(par.main_cost_center, child.cost_center, child.percentage)
212 .where(par.docstatus == 1)
213 .where(par.company == company)
214 .where(par.valid_from <= posting_date)
215 .orderby(par.valid_from, order=frappe.qb.desc)
216 ).run(as_dict=True)
217
218 cc_allocation = frappe._dict()
219 for d in records:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530220 cc_allocation.setdefault(d.main_cost_center, frappe._dict()).setdefault(
221 d.cost_center, d.percentage
222 )
Nabin Hait5b0ec642022-01-31 18:07:04 +0530223
Nabin Hait004c1ed2022-01-31 13:20:18 +0530224 return cc_allocation
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530225
Ankush Menat494bd9e2022-03-28 18:52:46 +0530226
Nabin Hait19f8fa52021-02-22 22:27:22 +0530227def merge_similar_entries(gl_map, precision=None):
Nabin Haitbf495c92013-01-30 12:49:08 +0530228 merged_gl_map = []
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530229 accounting_dimensions = get_accounting_dimensions()
Nabin Hait914a3882022-07-19 12:32:54 +0530230
Nabin Haitbf495c92013-01-30 12:49:08 +0530231 for entry in gl_map:
Anand Doshi652bc072014-04-16 15:21:46 +0530232 # if there is already an entry in this account then just add it
Nabin Haitbf495c92013-01-30 12:49:08 +0530233 # to that entry
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530234 same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
Nabin Haitbf495c92013-01-30 12:49:08 +0530235 if same_head:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530236 same_head.debit = flt(same_head.debit) + flt(entry.debit)
237 same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
238 entry.debit_in_account_currency
239 )
Nabin Hait2e296fa2013-08-28 18:53:11 +0530240 same_head.credit = flt(same_head.credit) + flt(entry.credit)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530241 same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
242 entry.credit_in_account_currency
243 )
Nabin Haitbf495c92013-01-30 12:49:08 +0530244 else:
245 merged_gl_map.append(entry)
Anand Doshi652bc072014-04-16 15:21:46 +0530246
thefalconx33bfc43d32019-12-13 15:09:51 +0530247 company = gl_map[0].company if gl_map else erpnext.get_default_company()
248 company_currency = erpnext.get_company_currency(company)
Nabin Hait19f8fa52021-02-22 22:27:22 +0530249
250 if not precision:
251 precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
thefalconx33bfc43d32019-12-13 15:09:51 +0530252
Nabin Hait815a49e2013-08-07 17:00:01 +0530253 # filter zero debit and credit entries
Ankush Menat494bd9e2022-03-28 18:52:46 +0530254 merged_gl_map = filter(
ruthra kumar914b2302023-01-02 14:33:14 +0530255 lambda x: flt(x.debit, precision) != 0
256 or flt(x.credit, precision) != 0
257 or (
258 x.voucher_type == "Journal Entry"
259 and frappe.get_cached_value("Journal Entry", x.voucher_no, "voucher_type")
260 == "Exchange Gain Or Loss"
261 ),
262 merged_gl_map,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530263 )
Achilles Rasquinha908289d2018-03-08 13:10:51 +0530264 merged_gl_map = list(merged_gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530265
Nabin Haitbf495c92013-01-30 12:49:08 +0530266 return merged_gl_map
267
Ankush Menat494bd9e2022-03-28 18:52:46 +0530268
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530269def check_if_in_list(gle, gl_map, dimensions=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530270 account_head_fieldnames = [
271 "voucher_detail_no",
272 "party",
273 "against_voucher",
274 "cost_center",
275 "against_voucher_type",
276 "party_type",
277 "project",
278 "finance_book",
Gursheen Anand442e3f22023-06-16 13:38:47 +0530279 "voucher_no",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530280 ]
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530281
282 if dimensions:
283 account_head_fieldnames = account_head_fieldnames + dimensions
284
Nabin Haitbb777562013-08-29 18:19:37 +0530285 for e in gl_map:
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530286 same_head = True
287 if e.account != gle.account:
288 same_head = False
Ankush91527152021-08-11 11:17:50 +0530289 continue
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530290
291 for fieldname in account_head_fieldnames:
292 if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
293 same_head = False
Ankush91527152021-08-11 11:17:50 +0530294 break
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530295
296 if same_head:
297 return e
Nabin Haitbf495c92013-01-30 12:49:08 +0530298
Ankush Menat494bd9e2022-03-28 18:52:46 +0530299
Nabin Hait004c1ed2022-01-31 13:20:18 +0530300def toggle_debit_credit_if_negative(gl_map):
301 for entry in gl_map:
302 # toggle debit, credit if negative entry
303 if flt(entry.debit) < 0:
304 entry.credit = flt(entry.credit) - flt(entry.debit)
305 entry.debit = 0.0
306
307 if flt(entry.debit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530308 entry.credit_in_account_currency = flt(entry.credit_in_account_currency) - flt(
309 entry.debit_in_account_currency
310 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530311 entry.debit_in_account_currency = 0.0
312
313 if flt(entry.credit) < 0:
314 entry.debit = flt(entry.debit) - flt(entry.credit)
315 entry.credit = 0.0
316
317 if flt(entry.credit_in_account_currency) < 0:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530318 entry.debit_in_account_currency = flt(entry.debit_in_account_currency) - flt(
319 entry.credit_in_account_currency
320 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530321 entry.credit_in_account_currency = 0.0
322
323 update_net_values(entry)
324
325 return gl_map
326
Ankush Menat494bd9e2022-03-28 18:52:46 +0530327
Nabin Hait004c1ed2022-01-31 13:20:18 +0530328def update_net_values(entry):
329 # In some scenarios net value needs to be shown in the ledger
330 # This method updates net values as debit or credit
331 if entry.post_net_value and entry.debit and entry.credit:
332 if entry.debit > entry.credit:
333 entry.debit = entry.debit - entry.credit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530334 entry.debit_in_account_currency = (
335 entry.debit_in_account_currency - entry.credit_in_account_currency
336 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530337 entry.credit = 0
338 entry.credit_in_account_currency = 0
339 else:
340 entry.credit = entry.credit - entry.debit
Ankush Menat494bd9e2022-03-28 18:52:46 +0530341 entry.credit_in_account_currency = (
342 entry.credit_in_account_currency - entry.debit_in_account_currency
343 )
Nabin Hait004c1ed2022-01-31 13:20:18 +0530344
345 entry.debit = 0
346 entry.debit_in_account_currency = 0
347
Ankush Menat494bd9e2022-03-28 18:52:46 +0530348
Nabin Haita77b8c92020-12-21 14:45:50 +0530349def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
350 if not from_repost:
351 validate_cwip_accounts(gl_map)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530352
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530353 process_debit_credit_difference(gl_map)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530354
355 if gl_map:
356 check_freezing_date(gl_map[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530357 is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
358 if gl_map[0]["voucher_type"] != "Period Closing Voucher":
359 validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530360
Nabin Haitbf495c92013-01-30 12:49:08 +0530361 for entry in gl_map:
Nabin Haita77b8c92020-12-21 14:45:50 +0530362 make_entry(entry, adv_adj, update_outstanding, from_repost)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530363
Nabin Hait914a3882022-07-19 12:32:54 +0530364
Nabin Haita77b8c92020-12-21 14:45:50 +0530365def make_entry(args, adv_adj, update_outstanding, from_repost=False):
Nabin Haitddc3bb22020-03-17 17:04:18 +0530366 gle = frappe.new_doc("GL Entry")
367 gle.update(args)
Anand Doshi6dfd4302015-02-10 14:41:27 +0530368 gle.flags.ignore_permissions = 1
Nabin Haita77b8c92020-12-21 14:45:50 +0530369 gle.flags.from_repost = from_repost
Nabin Hait19f8fa52021-02-22 22:27:22 +0530370 gle.flags.adv_adj = adv_adj
Ankush Menat494bd9e2022-03-28 18:52:46 +0530371 gle.flags.update_outstanding = update_outstanding or "Yes"
Nabin Hait4caaab32022-07-18 17:59:42 +0530372 gle.flags.notify_update = False
Nabin Haitaeba24e2013-08-23 15:17:36 +0530373 gle.submit()
Anand Doshi652bc072014-04-16 15:21:46 +0530374
Nabin Hait4caaab32022-07-18 17:59:42 +0530375 if not from_repost and gle.voucher_type != "Period Closing Voucher":
Nabin Haita77b8c92020-12-21 14:45:50 +0530376 validate_expense_against_budget(args)
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530377
Ankush Menat494bd9e2022-03-28 18:52:46 +0530378
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530379def validate_cwip_accounts(gl_map):
Ankush91527152021-08-11 11:17:50 +0530380 """Validate that CWIP account are not used in Journal Entry"""
381 if gl_map and gl_map[0].voucher_type != "Journal Entry":
382 return
Maricad00c5982019-11-12 19:17:43 +0530383
Ankush Menat494bd9e2022-03-28 18:52:46 +0530384 cwip_enabled = any(
385 cint(ac.enable_cwip_accounting)
386 for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")
387 )
Ankush91527152021-08-11 11:17:50 +0530388 if cwip_enabled:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530389 cwip_accounts = [
390 d[0]
391 for d in frappe.db.sql(
392 """select name from tabAccount
393 where account_type = 'Capital Work in Progress' and is_group=0"""
394 )
395 ]
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530396
Ankush91527152021-08-11 11:17:50 +0530397 for entry in gl_map:
398 if entry.account in cwip_accounts:
399 frappe.throw(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530400 _(
401 "Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry"
402 ).format(entry.account)
403 )
404
Anurag Mishra7e1987e2019-08-05 10:18:57 +0530405
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530406def process_debit_credit_difference(gl_map):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530407 precision = get_field_precision(
408 frappe.get_meta("GL Entry").get_field("debit"),
409 currency=frappe.get_cached_value("Company", gl_map[0].company, "default_currency"),
410 )
Anand Doshi602e8252015-11-16 19:05:46 +0530411
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530412 voucher_type = gl_map[0].voucher_type
413 voucher_no = gl_map[0].voucher_no
414 allowance = get_debit_credit_allowance(voucher_type, precision)
415
416 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
ruthra kumar914b2302023-01-02 14:33:14 +0530417
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530418 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530419 if not (
420 voucher_type == "Journal Entry"
421 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
422 == "Exchange Gain Or Loss"
423 ):
424 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530425
426 elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
427 make_round_off_gle(gl_map, debit_credit_diff, precision)
428
429 debit_credit_diff = get_debit_credit_difference(gl_map, precision)
430 if abs(debit_credit_diff) > allowance:
ruthra kumar914b2302023-01-02 14:33:14 +0530431 if not (
432 voucher_type == "Journal Entry"
433 and frappe.get_cached_value("Journal Entry", voucher_no, "voucher_type")
434 == "Exchange Gain Or Loss"
435 ):
436 raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530437
438
439def get_debit_credit_difference(gl_map, precision):
Nabin Haite2c200a2015-05-28 13:00:37 +0530440 debit_credit_diff = 0.0
441 for entry in gl_map:
442 entry.debit = flt(entry.debit, precision)
443 entry.credit = flt(entry.credit, precision)
444 debit_credit_diff += entry.debit - entry.credit
Anand Doshi602e8252015-11-16 19:05:46 +0530445
Nabin Haite2c200a2015-05-28 13:00:37 +0530446 debit_credit_diff = flt(debit_credit_diff, precision)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530447
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530448 return debit_credit_diff
449
450
451def get_debit_credit_allowance(voucher_type, precision):
452 if voucher_type in ("Journal Entry", "Payment Entry"):
Nabin Haitfb24a272016-02-18 19:18:07 +0530453 allowance = 5.0 / (10**precision)
454 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530455 allowance = 0.5
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530456
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530457 return allowance
Anand Doshi602e8252015-11-16 19:05:46 +0530458
Saqib Ansaribfc34e12022-04-01 11:03:16 +0530459
460def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no):
461 frappe.throw(
462 _("Debit and Credit not equal for {0} #{1}. Difference is {2}.").format(
463 voucher_type, voucher_no, debit_credit_diff
464 )
465 )
Anand Doshi602e8252015-11-16 19:05:46 +0530466
Ankush Menat494bd9e2022-03-28 18:52:46 +0530467
Nabin Hait34c551d2019-07-03 10:34:31 +0530468def make_round_off_gle(gl_map, debit_credit_diff, precision):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530469 round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530470 gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
Ankush Menat494bd9e2022-03-28 18:52:46 +0530471 )
Nabin Hait80069a62015-05-28 19:19:59 +0530472 round_off_gle = frappe._dict()
Nabin Hait022d8d52022-11-21 15:16:53 +0530473 round_off_account_exists = False
Zarrar3523b772018-08-14 16:28:14 +0530474
Nabin Hait022d8d52022-11-21 15:16:53 +0530475 if gl_map[0].voucher_type != "Period Closing Voucher":
476 for d in gl_map:
477 if d.account == round_off_account:
478 round_off_gle = d
479 if d.debit:
480 debit_credit_diff -= flt(d.debit) - flt(d.credit)
481 else:
482 debit_credit_diff += flt(d.credit)
483 round_off_account_exists = True
484
485 if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
486 gl_map.remove(round_off_gle)
487 return
Nabin Hait34c551d2019-07-03 10:34:31 +0530488
Zarrar3523b772018-08-14 16:28:14 +0530489 if not round_off_gle:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530490 for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
491 round_off_gle[k] = gl_map[0][k]
Anand Doshi602e8252015-11-16 19:05:46 +0530492
Ankush Menat494bd9e2022-03-28 18:52:46 +0530493 round_off_gle.update(
494 {
495 "account": round_off_account,
496 "debit_in_account_currency": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
497 "credit_in_account_currency": debit_credit_diff if debit_credit_diff > 0 else 0,
498 "debit": abs(debit_credit_diff) if debit_credit_diff < 0 else 0,
499 "credit": debit_credit_diff if debit_credit_diff > 0 else 0,
500 "cost_center": round_off_cost_center,
501 "party_type": None,
502 "party": None,
503 "is_opening": "No",
504 "against_voucher_type": None,
505 "against_voucher": None,
506 }
507 )
Anand Doshi602e8252015-11-16 19:05:46 +0530508
Deepesh Garg015812b2022-04-23 21:40:08 +0530509 update_accounting_dimensions(round_off_gle)
Zarrar3523b772018-08-14 16:28:14 +0530510 if not round_off_account_exists:
511 gl_map.append(round_off_gle)
Nabin Haite2c200a2015-05-28 13:00:37 +0530512
Ankush Menat494bd9e2022-03-28 18:52:46 +0530513
Deepesh Garg015812b2022-04-23 21:40:08 +0530514def update_accounting_dimensions(round_off_gle):
515 dimensions = get_accounting_dimensions()
Deepesh Gargc312cd32022-04-24 18:11:32 +0530516 meta = frappe.get_meta(round_off_gle["voucher_type"])
517 has_all_dimensions = True
Deepesh Garg015812b2022-04-23 21:40:08 +0530518
519 for dimension in dimensions:
Deepesh Gargc312cd32022-04-24 18:11:32 +0530520 if not meta.has_field(dimension):
521 has_all_dimensions = False
522
523 if dimensions and has_all_dimensions:
524 dimension_values = frappe.db.get_value(
Deepesh Garg3fa1c632022-04-25 16:29:26 +0530525 round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
Deepesh Gargc312cd32022-04-24 18:11:32 +0530526 )
527
528 for dimension in dimensions:
529 round_off_gle[dimension] = dimension_values.get(dimension)
Deepesh Garg015812b2022-04-23 21:40:08 +0530530
531
ruthra kumarebe67872023-04-28 14:07:28 +0530532def get_round_off_account_and_cost_center(
533 company, voucher_type, voucher_no, use_company_default=False
534):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530535 round_off_account, round_off_cost_center = frappe.get_cached_value(
536 "Company", company, ["round_off_account", "round_off_cost_center"]
537 ) or [None, None]
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530538
Deepesh Gargc312cd32022-04-24 18:11:32 +0530539 meta = frappe.get_meta(voucher_type)
540
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530541 # Give first preference to parent cost center for round off GLE
ruthra kumarebe67872023-04-28 14:07:28 +0530542 if not use_company_default and meta.has_field("cost_center"):
Deepesh Garg0ac11a52022-04-20 12:18:11 +0530543 parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
544 if parent_cost_center:
545 round_off_cost_center = parent_cost_center
546
Nabin Hait2e4de832017-09-19 14:53:16 +0530547 if not round_off_account:
548 frappe.throw(_("Please mention Round Off Account in Company"))
549
550 if not round_off_cost_center:
551 frappe.throw(_("Please mention Round Off Cost Center in Company"))
552
553 return round_off_account, round_off_cost_center
554
Ankush Menat494bd9e2022-03-28 18:52:46 +0530555
556def make_reverse_gl_entries(
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530557 gl_entries=None,
558 voucher_type=None,
559 voucher_no=None,
560 adv_adj=False,
561 update_outstanding="Yes",
562 partial_cancel=False,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530563):
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530564 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530565 Get original gl entries of the voucher
566 and make reverse gl entries by swapping debit and credit
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530567 """
Anand Doshi652bc072014-04-16 15:21:46 +0530568
Nabin Hait2e296fa2013-08-28 18:53:11 +0530569 if not gl_entries:
Deepesh Gargff574502022-02-06 22:56:12 +0530570 gl_entry = frappe.qb.DocType("GL Entry")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530571 gl_entries = (
572 frappe.qb.from_(gl_entry)
573 .select("*")
574 .where(gl_entry.voucher_type == voucher_type)
575 .where(gl_entry.voucher_no == voucher_no)
576 .where(gl_entry.is_cancelled == 0)
577 .for_update()
578 ).run(as_dict=1)
Nabin Hait56548cb2016-07-01 15:58:39 +0530579
Nabin Hait27994c22013-08-26 16:53:30 +0530580 if gl_entries:
ruthra kumar7312f222022-05-29 21:33:08 +0530581 create_payment_ledger_entry(
Deepesh Garg1e078d02023-06-29 12:18:25 +0530582 gl_entries,
583 cancel=1,
584 adv_adj=adv_adj,
585 update_outstanding=update_outstanding,
586 partial_cancel=partial_cancel,
ruthra kumar7312f222022-05-29 21:33:08 +0530587 )
Deepesh Garg069a54e2020-08-10 16:01:01 +0530588 validate_accounting_period(gl_entries)
Nabin Hait27994c22013-08-26 16:53:30 +0530589 check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530590
591 is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
592 validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
Deepesh Gargda6bc1a2023-06-23 20:57:51 +0530593 if not partial_cancel:
594 set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
Anand Doshi652bc072014-04-16 15:21:46 +0530595
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530596 for entry in gl_entries:
Deepesh Gargffec8652022-02-02 17:14:42 +0530597 new_gle = copy.deepcopy(entry)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530598 new_gle["name"] = None
599 debit = new_gle.get("debit", 0)
600 credit = new_gle.get("credit", 0)
Anand Doshi652bc072014-04-16 15:21:46 +0530601
Ankush Menat494bd9e2022-03-28 18:52:46 +0530602 debit_in_account_currency = new_gle.get("debit_in_account_currency", 0)
603 credit_in_account_currency = new_gle.get("credit_in_account_currency", 0)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530604
Ankush Menat494bd9e2022-03-28 18:52:46 +0530605 new_gle["debit"] = credit
606 new_gle["credit"] = debit
607 new_gle["debit_in_account_currency"] = credit_in_account_currency
608 new_gle["credit_in_account_currency"] = debit_in_account_currency
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530609
Ankush Menat494bd9e2022-03-28 18:52:46 +0530610 new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
611 new_gle["is_cancelled"] = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530612
Ankush Menat494bd9e2022-03-28 18:52:46 +0530613 if new_gle["debit"] or new_gle["credit"]:
Deepesh Gargffec8652022-02-02 17:14:42 +0530614 make_entry(new_gle, adv_adj, "Yes")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530615
616
617def check_freezing_date(posting_date, adv_adj=False):
618 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530619 Nobody can do GL Entries where posting date is before freezing date
620 except authorized person
Deepesh Garg13d2e7b2021-09-16 18:54:57 +0530621
Ankush Menat494bd9e2022-03-28 18:52:46 +0530622 Administrator has all the roles so this check will be bypassed if any role is allowed to post
623 Hence stop admin to bypass if accounts are freezed
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530624 """
625 if not adv_adj:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530626 acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto")
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530627 if acc_frozen_upto:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530628 frozen_accounts_modifier = frappe.db.get_value(
629 "Accounts Settings", None, "frozen_accounts_modifier"
630 )
631 if getdate(posting_date) <= getdate(acc_frozen_upto) and (
632 frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator"
633 ):
634 frappe.throw(
635 _("You are not authorized to add or update entries before {0}").format(
636 formatdate(acc_frozen_upto)
637 )
638 )
639
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530640
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530641def validate_against_pcv(is_opening, posting_date, company):
642 if is_opening and frappe.db.exists(
643 "Period Closing Voucher", {"docstatus": 1, "company": company}
644 ):
645 frappe.throw(
646 _("Opening Entry can not be created after Period Closing Voucher is created."),
647 title=_("Invalid Opening Entry"),
648 )
649
650 last_pcv_date = frappe.db.get_value(
651 "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
652 )
653
654 if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
655 message = _("Books have been closed till the period ending on {0}").format(
656 formatdate(last_pcv_date)
657 )
658 message += "</br >"
Deepesh Garg8ce1da12023-03-23 19:14:12 +0530659 message += _("You cannot create/amend any accounting entries till this date.")
Deepesh Gargf92c63f2023-02-26 15:53:33 +0530660 frappe.throw(message, title=_("Period Closed"))
661
662
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530663def set_as_cancel(voucher_type, voucher_no):
664 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530665 Set is_cancelled=1 in all original gl entries for the voucher
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530666 """
Ankush Menat494bd9e2022-03-28 18:52:46 +0530667 frappe.db.sql(
668 """UPDATE `tabGL Entry` SET is_cancelled = 1,
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530669 modified=%s, modified_by=%s
670 where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
Ankush Menat494bd9e2022-03-28 18:52:46 +0530671 (now(), frappe.session.user, voucher_type, voucher_no),
672 )