Skip to content

Commit 4b1437b

Browse files
committed
refactor: remove unnecessary functions, improve readability
- make gratuity settings a class property to avoid refetching - get rid of unnecessary variables - extract complex conditions into bool functions
1 parent c07543e commit 4b1437b

File tree

1 file changed

+94
-121
lines changed

1 file changed

+94
-121
lines changed

hrms/payroll/doctype/gratuity/gratuity.py

+94-121
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import frappe
88
from frappe import _, bold
99
from frappe.query_builder.functions import Sum
10-
from frappe.utils import flt, get_datetime, get_link_to_form
10+
from frappe.utils import cstr, flt, get_datetime, get_link_to_form
1111

1212
from erpnext.accounts.general_ledger import make_gl_entries
1313
from erpnext.controllers.accounts_controller import AccountsController
@@ -20,32 +20,32 @@ def validate(self):
2020
self.amount = data["amount"]
2121
self.set_status()
2222

23-
@frappe.whitelist()
24-
def calculate_work_experience_and_amount(self):
25-
rule = get_gratuity_rule_config(self.gratuity_rule)
26-
27-
if rule.method == "Manual":
28-
current_work_experience = flt(self.current_work_experience)
29-
else:
30-
current_work_experience = self.calculate_work_experience() or 0
31-
32-
gratuity_amount = self.calculate_gratuity_amount(current_work_experience) or 0
23+
@property
24+
def gratuity_settings(self):
25+
if not hasattr(self, "_gratuity_settings"):
26+
self._gratuity_settings = frappe.db.get_value(
27+
"Gratuity Rule",
28+
self.gratuity_rule,
29+
[
30+
"work_experience_calculation_function as method",
31+
"total_working_days_per_year",
32+
"minimum_year_for_gratuity",
33+
"calculate_gratuity_amount_based_on",
34+
],
35+
as_dict=True,
36+
)
3337

34-
return {"current_work_experience": current_work_experience, "amount": gratuity_amount}
38+
return self._gratuity_settings
3539

3640
def set_status(self, update=False):
37-
precision = self.precision("paid_amount")
38-
status = None
41+
status = {"0": "Draft", "1": "Submitted", "2": "Cancelled"}[cstr(self.docstatus or 0)]
3942

40-
if self.docstatus == 0:
41-
status = "Draft"
42-
elif self.docstatus == 1:
43+
if self.docstatus == 1:
44+
precision = self.precision("paid_amount")
4345
if flt(self.paid_amount) > 0 and flt(self.amount, precision) == flt(self.paid_amount, precision):
4446
status = "Paid"
4547
else:
4648
status = "Unpaid"
47-
elif self.docstatus == 2:
48-
status = "Cancelled"
4949

5050
if update:
5151
self.db_set("status", status)
@@ -140,13 +140,36 @@ def set_total_advance_paid(self):
140140
self.db_set("paid_amount", paid_amount)
141141
self.set_status(update=True)
142142

143-
def calculate_work_experience(self):
144-
rule = get_gratuity_rule_config(self.gratuity_rule)
145-
total_working_days = self.calculate_total_working_days()
146-
current_work_experience = self.get_work_experience(total_working_days, rule)
147-
return current_work_experience
143+
@frappe.whitelist()
144+
def calculate_work_experience_and_amount(self) -> dict:
145+
if self.gratuity_settings.method == "Manual":
146+
current_work_experience = flt(self.current_work_experience)
147+
else:
148+
current_work_experience = self.get_work_experience()
149+
150+
gratuity_amount = self.get_gratuity_amount(current_work_experience)
151+
152+
return {"current_work_experience": current_work_experience, "amount": gratuity_amount}
153+
154+
def get_work_experience(self) -> float:
155+
total_working_days = self.get_total_working_days()
156+
rule = self.gratuity_settings
157+
work_experience = total_working_days / (rule.total_working_days_per_year or 1)
158+
159+
if rule.method == "Round off Work Experience":
160+
work_experience = round(work_experience)
161+
else:
162+
work_experience = floor(work_experience)
163+
164+
if work_experience < rule.minimum_year_for_gratuity:
165+
frappe.throw(
166+
_("Employee: {0} have to complete minimum {1} years for gratuity").format(
167+
bold(self.employee), rule.minimum_year_for_gratuity
168+
)
169+
)
170+
return work_experience or 0
148171

149-
def calculate_total_working_days(self):
172+
def get_total_working_days(self) -> float:
150173
date_of_joining, relieving_date = frappe.db.get_value(
151174
"Employee", self.employee, ["date_of_joining", "relieving_date"]
152175
)
@@ -169,7 +192,7 @@ def calculate_total_working_days(self):
169192

170193
return total_working_days
171194

172-
def get_non_working_days(self, relieving_date, status):
195+
def get_non_working_days(self, relieving_date: str, status: str) -> float:
173196
filters = {
174197
"docstatus": 1,
175198
"status": status,
@@ -184,91 +207,63 @@ def get_non_working_days(self, relieving_date, status):
184207
record = frappe.get_all("Attendance", filters=filters, fields=["COUNT(*) as total_lwp"])
185208
return record[0].total_lwp if len(record) else 0
186209

187-
def get_work_experience(self, total_working_days, rule):
188-
work_experience = total_working_days / rule.total_working_days_per_year or 1
189-
190-
if rule.method == "Round off Work Experience":
191-
work_experience = round(work_experience)
192-
else:
193-
work_experience = floor(work_experience)
194-
195-
if work_experience < rule.minimum_year_for_gratuity:
196-
frappe.throw(
197-
_("Employee: {0} have to complete minimum {1} years for gratuity").format(
198-
bold(self.employee), rule.minimum_year_for_gratuity
199-
)
200-
)
201-
return work_experience
202-
203-
def calculate_gratuity_amount(self, experience):
204-
applicable_earning_components = self.get_applicable_components()
205-
total_applicable_components_amount = self.get_total_component_amount(applicable_earning_components)
210+
def get_gratuity_amount(self, experience: float) -> float:
211+
total_component_amount = self.get_total_component_amount()
212+
calculate_amount_based_on = self.gratuity_settings.calculate_gratuity_amount_based_on
206213

207-
calculate_gratuity_amount_based_on = frappe.db.get_value(
208-
"Gratuity Rule", self.gratuity_rule, "calculate_gratuity_amount_based_on"
209-
)
210214
gratuity_amount = 0
211215
slabs = get_gratuity_rule_slabs(self.gratuity_rule)
212216
slab_found = False
213-
year_left = experience
217+
years_left = experience
214218

215219
for slab in slabs:
216-
if calculate_gratuity_amount_based_on == "Current Slab":
217-
slab_found, gratuity_amount = self.calculate_amount_based_on_current_slab(
218-
slab.from_year,
219-
slab.to_year,
220-
experience,
221-
total_applicable_components_amount,
222-
slab.fraction_of_applicable_earnings,
223-
)
220+
if calculate_amount_based_on == "Current Slab":
221+
if self._is_experience_within_slab(slab, experience):
222+
gratuity_amount = (
223+
total_component_amount * experience * slab.fraction_of_applicable_earnings
224+
)
225+
if slab.fraction_of_applicable_earnings:
226+
slab_found = True
227+
224228
if slab_found:
225229
break
226230

227-
elif calculate_gratuity_amount_based_on == "Sum of all previous slabs":
231+
elif calculate_amount_based_on == "Sum of all previous slabs":
232+
# no slabs, fraction applicable for all years
228233
if slab.to_year == 0 and slab.from_year == 0:
229234
gratuity_amount += (
230-
year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings
235+
years_left * total_component_amount * slab.fraction_of_applicable_earnings
231236
)
232237
slab_found = True
233238
break
234239

235-
if experience > slab.to_year and experience > slab.from_year and slab.to_year != 0:
240+
# completed more years than the current slab, so consider fraction for current slab too
241+
if self._is_experience_beyond_slab(slab, experience):
236242
gratuity_amount += (
237243
(slab.to_year - slab.from_year)
238-
* total_applicable_components_amount
244+
* total_component_amount
239245
* slab.fraction_of_applicable_earnings
240246
)
241-
year_left -= slab.to_year - slab.from_year
247+
years_left -= slab.to_year - slab.from_year
242248
slab_found = True
243-
elif slab.from_year <= experience and (experience < slab.to_year or slab.to_year == 0):
249+
250+
elif self._is_experience_within_slab(slab, experience):
244251
gratuity_amount += (
245-
year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings
252+
years_left * total_component_amount * slab.fraction_of_applicable_earnings
246253
)
247254
slab_found = True
248255

249256
if not slab_found:
250257
frappe.throw(
251-
_("No Suitable Slab found for Calculation of gratuity amount in Gratuity Rule: {0}").format(
252-
bold(self.gratuity_rule)
253-
)
254-
)
255-
256-
return gratuity_amount
257-
258-
def get_applicable_components(self):
259-
applicable_earning_components = frappe.get_all(
260-
"Gratuity Applicable Component", filters={"parent": self.gratuity_rule}, pluck="salary_component"
261-
)
262-
if not applicable_earning_components:
263-
frappe.throw(
264-
_("No applicable Earning components found for Gratuity Rule: {0}").format(
265-
bold(get_link_to_form("Gratuity Rule", self.gratuity_rule))
266-
)
258+
_(
259+
"No applicable slab found for the calculation of gratuity amount as per the Gratuity Rule: {0}"
260+
).format(bold(self.gratuity_rule))
267261
)
268262

269-
return applicable_earning_components
263+
return flt(gratuity_amount, self.precision("amount"))
270264

271-
def get_total_component_amount(self, applicable_earning_components):
265+
def get_total_component_amount(self) -> float:
266+
applicable_earning_components = self.get_applicable_components()
272267
salary_slip = get_last_salary_slip(self.employee)
273268
if not salary_slip:
274269
frappe.throw(_("No Salary Slip found for Employee: {0}").format(bold(self.employee)))
@@ -294,37 +289,24 @@ def get_total_component_amount(self, applicable_earning_components):
294289

295290
return total_amount
296291

297-
def calculate_amount_based_on_current_slab(
298-
self,
299-
from_year,
300-
to_year,
301-
experience,
302-
total_applicable_components_amount,
303-
fraction_of_applicable_earnings,
304-
):
305-
slab_found = False
306-
gratuity_amount = 0
307-
if experience >= from_year and (to_year == 0 or experience < to_year):
308-
gratuity_amount = (
309-
total_applicable_components_amount * experience * fraction_of_applicable_earnings
292+
def get_applicable_components(self) -> list[str]:
293+
applicable_earning_components = frappe.get_all(
294+
"Gratuity Applicable Component", filters={"parent": self.gratuity_rule}, pluck="salary_component"
295+
)
296+
if not applicable_earning_components:
297+
frappe.throw(
298+
_("No applicable Earning components found for Gratuity Rule: {0}").format(
299+
bold(get_link_to_form("Gratuity Rule", self.gratuity_rule))
300+
)
310301
)
311-
if fraction_of_applicable_earnings:
312-
slab_found = True
313-
314-
return slab_found, gratuity_amount
315-
316-
317-
def get_gratuity_rule_config(gratuity_rule: str) -> dict:
318-
return frappe.db.get_value(
319-
"Gratuity Rule",
320-
gratuity_rule,
321-
[
322-
"work_experience_calculation_function as method",
323-
"total_working_days_per_year",
324-
"minimum_year_for_gratuity",
325-
],
326-
as_dict=True,
327-
)
302+
303+
return applicable_earning_components
304+
305+
def _is_experience_within_slab(self, slab: dict, experience: float) -> bool:
306+
return bool(slab.from_year <= experience and (experience < slab.to_year or slab.to_year == 0))
307+
308+
def _is_experience_beyond_slab(self, slab: dict, experience: float) -> bool:
309+
return bool(slab.from_year < experience and (slab.to_year < experience and slab.to_year != 0))
328310

329311

330312
def get_gratuity_rule_slabs(gratuity_rule):
@@ -333,15 +315,6 @@ def get_gratuity_rule_slabs(gratuity_rule):
333315
)
334316

335317

336-
def get_salary_structure(employee):
337-
return frappe.get_list(
338-
"Salary Structure Assignment",
339-
filters={"employee": employee, "docstatus": 1},
340-
fields=["from_date", "salary_structure"],
341-
order_by="from_date desc",
342-
)[0].salary_structure
343-
344-
345318
def get_last_salary_slip(employee: str) -> dict | None:
346319
salary_slip = frappe.db.get_value(
347320
"Salary Slip", {"employee": employee, "docstatus": 1}, order_by="start_date desc"

0 commit comments

Comments
 (0)