blob: 23de0753d904686c69eba2f7ee2ef9863ae398ca [file] [log] [blame]
Sagar Vora12b7e142022-06-24 13:20:46 +05301import math
2
3import frappe
4from frappe import _
5from frappe.utils import add_days, date_diff, flt, get_link_to_form, month_diff
6
7from erpnext.hr.utils import get_salary_assignments
8from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
9
10
11def calculate_annual_eligible_hra_exemption(doc):
12 basic_component, hra_component = frappe.db.get_value(
13 "Company", doc.company, ["basic_component", "hra_component"]
14 )
15
16 if not (basic_component and hra_component):
17 frappe.throw(
18 _("Please set Basic and HRA component in Company {0}").format(
19 get_link_to_form("Company", doc.company)
20 )
21 )
22
23 annual_exemption = monthly_exemption = hra_amount = basic_amount = 0
24
25 if hra_component and basic_component:
26 assignments = get_salary_assignments(doc.employee, doc.payroll_period)
27
28 if not assignments and doc.docstatus == 1:
29 frappe.throw(
30 _("Salary Structure must be submitted before submission of {0}").format(doc.doctype)
31 )
32
33 assignment_dates = [assignment.from_date for assignment in assignments]
34
35 for idx, assignment in enumerate(assignments):
36 if has_hra_component(assignment.salary_structure, hra_component):
37 basic_salary_amt, hra_salary_amt = get_component_amt_from_salary_slip(
38 doc.employee,
39 assignment.salary_structure,
40 basic_component,
41 hra_component,
42 assignment.from_date,
43 )
44 to_date = get_end_date_for_assignment(assignment_dates, idx, doc.payroll_period)
45
46 frequency = frappe.get_value(
47 "Salary Structure", assignment.salary_structure, "payroll_frequency"
48 )
49 basic_amount += get_component_pay(frequency, basic_salary_amt, assignment.from_date, to_date)
50 hra_amount += get_component_pay(frequency, hra_salary_amt, assignment.from_date, to_date)
51
52 if hra_amount:
53 if doc.monthly_house_rent:
54 annual_exemption = calculate_hra_exemption(
55 basic_amount,
56 hra_amount,
57 doc.monthly_house_rent,
58 doc.rented_in_metro_city,
59 )
60 if annual_exemption > 0:
61 monthly_exemption = annual_exemption / 12
62 else:
63 annual_exemption = 0
64
65 return frappe._dict(
66 {
67 "hra_amount": hra_amount,
68 "annual_exemption": annual_exemption,
69 "monthly_exemption": monthly_exemption,
70 }
71 )
72
73
74def has_hra_component(salary_structure, hra_component):
75 return frappe.db.exists(
76 "Salary Detail",
77 {
78 "parent": salary_structure,
79 "salary_component": hra_component,
80 "parentfield": "earnings",
81 "parenttype": "Salary Structure",
82 },
83 )
84
85
86def get_end_date_for_assignment(assignment_dates, idx, payroll_period):
87 end_date = None
88
89 try:
90 end_date = assignment_dates[idx + 1]
91 end_date = add_days(end_date, -1)
92 except IndexError:
93 pass
94
95 if not end_date:
96 end_date = frappe.db.get_value("Payroll Period", payroll_period, "end_date")
97
98 return end_date
99
100
101def get_component_amt_from_salary_slip(
102 employee, salary_structure, basic_component, hra_component, from_date
103):
104 salary_slip = make_salary_slip(
105 salary_structure,
106 employee=employee,
107 for_preview=1,
108 ignore_permissions=True,
109 posting_date=from_date,
110 )
111
112 basic_amt, hra_amt = 0, 0
113 for earning in salary_slip.earnings:
114 if earning.salary_component == basic_component:
115 basic_amt = earning.amount
116 elif earning.salary_component == hra_component:
117 hra_amt = earning.amount
118 if basic_amt and hra_amt:
119 return basic_amt, hra_amt
120 return basic_amt, hra_amt
121
122
123def calculate_hra_exemption(annual_basic, annual_hra, monthly_house_rent, rented_in_metro_city):
124 # TODO make this configurable
125 exemptions = []
126 # case 1: The actual amount allotted by the employer as the HRA.
127 exemptions.append(annual_hra)
128
129 # case 2: Actual rent paid less 10% of the basic salary.
130 actual_annual_rent = monthly_house_rent * 12
131 exemptions.append(flt(actual_annual_rent) - flt(annual_basic * 0.1))
132
133 # case 3: 50% of the basic salary, if the employee is staying in a metro city (40% for a non-metro city).
134 exemptions.append(annual_basic * 0.5 if rented_in_metro_city else annual_basic * 0.4)
135
136 # return minimum of 3 cases
137 return min(exemptions)
138
139
140def get_component_pay(frequency, amount, from_date, to_date):
141 days = date_diff(to_date, from_date) + 1
142
143 if frequency == "Daily":
144 return amount * days
145 elif frequency == "Weekly":
146 return amount * math.floor(days / 7)
147 elif frequency == "Fortnightly":
148 return amount * math.floor(days / 14)
149 elif frequency == "Monthly":
150 return amount * month_diff(to_date, from_date)
151 elif frequency == "Bimonthly":
152 return amount * (month_diff(to_date, from_date) / 2)
153
154
155def calculate_hra_exemption_for_period(doc):
156 monthly_rent, eligible_hra = 0, 0
157 if doc.house_rent_payment_amount:
158 validate_house_rent_dates(doc)
159 # TODO receive rented months or validate dates are start and end of months?
160 # Calc monthly rent, round to nearest .5
161 factor = flt(date_diff(doc.rented_to_date, doc.rented_from_date) + 1) / 30
162 factor = round(factor * 2) / 2
163 monthly_rent = doc.house_rent_payment_amount / factor
164 # update field used by calculate_annual_eligible_hra_exemption
165 doc.monthly_house_rent = monthly_rent
166 exemptions = calculate_annual_eligible_hra_exemption(doc)
167
168 if exemptions["monthly_exemption"]:
169 # calc total exemption amount
170 eligible_hra = exemptions["monthly_exemption"] * factor
171 exemptions["monthly_house_rent"] = monthly_rent
172 exemptions["total_eligible_hra_exemption"] = eligible_hra
173 return exemptions
174
175
176def validate_house_rent_dates(doc):
177 if not doc.rented_to_date or not doc.rented_from_date:
178 frappe.throw(_("House rented dates required for exemption calculation"))
179
180 if date_diff(doc.rented_to_date, doc.rented_from_date) < 14:
181 frappe.throw(_("House rented dates should be atleast 15 days apart"))
182
183 proofs = frappe.db.sql(
184 """
185 select name
186 from `tabEmployee Tax Exemption Proof Submission`
187 where
188 docstatus=1 and employee=%(employee)s and payroll_period=%(payroll_period)s
189 and (rented_from_date between %(from_date)s and %(to_date)s or rented_to_date between %(from_date)s and %(to_date)s)
190 """,
191 {
192 "employee": doc.employee,
193 "payroll_period": doc.payroll_period,
194 "from_date": doc.rented_from_date,
195 "to_date": doc.rented_to_date,
196 },
197 )
198
199 if proofs:
200 frappe.throw(_("House rent paid days overlapping with {0}").format(proofs[0][0]))