7
7
import frappe
8
8
from frappe import _ , bold
9
9
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
11
11
12
12
from erpnext .accounts .general_ledger import make_gl_entries
13
13
from erpnext .controllers .accounts_controller import AccountsController
@@ -20,32 +20,32 @@ def validate(self):
20
20
self .amount = data ["amount" ]
21
21
self .set_status ()
22
22
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
+ )
33
37
34
- return { "current_work_experience" : current_work_experience , "amount" : gratuity_amount }
38
+ return self . _gratuity_settings
35
39
36
40
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 )]
39
42
40
- if self .docstatus == 0 :
41
- status = "Draft"
42
- elif self .docstatus == 1 :
43
+ if self .docstatus == 1 :
44
+ precision = self .precision ("paid_amount" )
43
45
if flt (self .paid_amount ) > 0 and flt (self .amount , precision ) == flt (self .paid_amount , precision ):
44
46
status = "Paid"
45
47
else :
46
48
status = "Unpaid"
47
- elif self .docstatus == 2 :
48
- status = "Cancelled"
49
49
50
50
if update :
51
51
self .db_set ("status" , status )
@@ -140,13 +140,36 @@ def set_total_advance_paid(self):
140
140
self .db_set ("paid_amount" , paid_amount )
141
141
self .set_status (update = True )
142
142
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 )
148
163
149
- def calculate_total_working_days (self ):
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
171
+
172
+ def get_total_working_days (self ) -> float :
150
173
date_of_joining , relieving_date = frappe .db .get_value (
151
174
"Employee" , self .employee , ["date_of_joining" , "relieving_date" ]
152
175
)
@@ -169,7 +192,7 @@ def calculate_total_working_days(self):
169
192
170
193
return total_working_days
171
194
172
- def get_non_working_days (self , relieving_date , status ) :
195
+ def get_non_working_days (self , relieving_date : str , status : str ) -> float :
173
196
filters = {
174
197
"docstatus" : 1 ,
175
198
"status" : status ,
@@ -184,91 +207,63 @@ def get_non_working_days(self, relieving_date, status):
184
207
record = frappe .get_all ("Attendance" , filters = filters , fields = ["COUNT(*) as total_lwp" ])
185
208
return record [0 ].total_lwp if len (record ) else 0
186
209
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
206
213
207
- calculate_gratuity_amount_based_on = frappe .db .get_value (
208
- "Gratuity Rule" , self .gratuity_rule , "calculate_gratuity_amount_based_on"
209
- )
210
214
gratuity_amount = 0
211
215
slabs = get_gratuity_rule_slabs (self .gratuity_rule )
212
216
slab_found = False
213
- year_left = experience
217
+ years_left = experience
214
218
215
219
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
+
224
228
if slab_found :
225
229
break
226
230
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
228
233
if slab .to_year == 0 and slab .from_year == 0 :
229
234
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
231
236
)
232
237
slab_found = True
233
238
break
234
239
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 ):
236
242
gratuity_amount += (
237
243
(slab .to_year - slab .from_year )
238
- * total_applicable_components_amount
244
+ * total_component_amount
239
245
* slab .fraction_of_applicable_earnings
240
246
)
241
- year_left -= slab .to_year - slab .from_year
247
+ years_left -= slab .to_year - slab .from_year
242
248
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 ):
244
251
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
246
253
)
247
254
slab_found = True
248
255
249
256
if not slab_found :
250
257
frappe .throw (
251
- _ ("No Suitable Slab found for Calculation of gratuity amount in Gratuity Rule: {0}" ). format (
252
- bold ( self . gratuity_rule )
253
- )
258
+ _ (
259
+ "No applicable slab found for the calculation of gratuity amount as per the Gratuity Rule: {0}"
260
+ ). format ( bold ( self . gratuity_rule ))
254
261
)
255
262
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
- )
267
- )
263
+ return flt (gratuity_amount , self .precision ("amount" ))
268
264
269
- return applicable_earning_components
270
-
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 ()
272
267
salary_slip = get_last_salary_slip (self .employee )
273
268
if not salary_slip :
274
269
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):
294
289
295
290
return total_amount
296
291
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
+ )
310
301
)
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 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 slab .to_year < experience and slab .from_year < experience and slab .to_year != 0
328
310
329
311
330
312
def get_gratuity_rule_slabs (gratuity_rule ):
0 commit comments