Employee it declaration
This commit is contained in:
parent
4db7e5ade2
commit
5460f6c207
|
|
@ -34,7 +34,7 @@
|
|||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base','hr','hr_payroll','hr_employee_extended'],
|
||||
'depends': ['base','hr','hr_payroll','hr_employee_extended','l10n_in_hr_payroll'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
|
|
@ -48,14 +48,15 @@
|
|||
'report/report_action.xml',
|
||||
'report/it_tax_template.xml',
|
||||
'views/it_tax_menu_and_wizard_view.xml',
|
||||
'wizards/hr_tds_calculation.xml',
|
||||
'wizards/children_education_costing.xml',
|
||||
'wizards/employee_life_insurance.xml',
|
||||
'wizards/nsc_declaration.xml',
|
||||
'wizards/self_occupied_property.xml',
|
||||
'wizards/letout_house_property.xml',
|
||||
'wizards/nsc_income_loss.xml',
|
||||
'data/default_investment_types.xml',
|
||||
# 'views/it_investment_type.xml',
|
||||
# 'views/it_investment_costing.xml'
|
||||
],
|
||||
}
|
||||
'wizards/self_occupied_property.xml',
|
||||
'wizards/letout_house_property.xml',
|
||||
'wizards/nsc_income_loss.xml',
|
||||
'data/default_investment_types.xml',
|
||||
# 'views/it_investment_type.xml',
|
||||
# 'views/it_investment_costing.xml'
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ class ITTaxStatementWizard(models.TransientModel):
|
|||
contract_id = fields.Many2one('hr.contract', related='employee_id.contract_id', required=True)
|
||||
currency_id = fields.Many2one('res.currency', related='employee_id.company_id.currency_id')
|
||||
|
||||
period_id = fields.Many2one('payroll.period', required=True)
|
||||
period_line = fields.Many2one('payroll.period.line',
|
||||
domain="[('period_id', '=', period_id), ('to_date', '<', fields.Date.today())]")
|
||||
period_id = fields.Many2one('payroll.period', required=True)
|
||||
period_line = fields.Many2one('payroll.period.line',
|
||||
domain="[('period_id', '=', period_id), ('to_date', '<', fields.Date.today())]")
|
||||
|
||||
# Taxpayer profile
|
||||
taxpayer_name = fields.Char(related='employee_id.name')
|
||||
|
|
@ -96,12 +96,32 @@ class ITTaxStatementWizard(models.TransientModel):
|
|||
('new', 'New Regime')
|
||||
], string="Beneficial Regime", readonly=True)
|
||||
|
||||
def _get_age_category(self, age):
|
||||
if age < 60:
|
||||
return 'below_60'
|
||||
elif age < 80:
|
||||
return '60_to_80'
|
||||
return 'above_80'
|
||||
def _get_age_category(self, age):
|
||||
if age < 60:
|
||||
return 'below_60'
|
||||
elif age < 80:
|
||||
return '60_to_80'
|
||||
return 'above_80'
|
||||
|
||||
def _get_effective_period_start(self):
|
||||
self.ensure_one()
|
||||
period_start = self.period_id.from_date if self.period_id else False
|
||||
if not period_start:
|
||||
return False
|
||||
if self.emp_doj and self.period_id.to_date and self.period_id.from_date <= self.emp_doj <= self.period_id.to_date:
|
||||
return max(period_start, self.emp_doj.replace(day=1))
|
||||
return period_start
|
||||
|
||||
def _get_effective_period_lines(self):
|
||||
self.ensure_one()
|
||||
if not self.period_id:
|
||||
return self.env['payroll.period.line']
|
||||
|
||||
period_lines = self.period_id.period_line_ids.sorted('from_date')
|
||||
effective_start = self._get_effective_period_start()
|
||||
if not effective_start:
|
||||
return period_lines
|
||||
return period_lines.filtered(lambda line: line.to_date and line.to_date >= effective_start)
|
||||
|
||||
def _find_applicable_slab(self, regime, period_id, age, residence_type):
|
||||
"""Find the applicable tax slab without forcing both regimes to exist."""
|
||||
|
|
@ -116,7 +136,7 @@ class ITTaxStatementWizard(models.TransientModel):
|
|||
('residence_type', '=', 'both')
|
||||
], limit=1)
|
||||
|
||||
def _get_applicable_slab(self, regime, period_id, age, residence_type):
|
||||
def _get_applicable_slab(self, regime, period_id, age, residence_type):
|
||||
"""Get the applicable tax slab based on regime, age, and residence type"""
|
||||
age_category = self._get_age_category(age)
|
||||
slab_master = self._find_applicable_slab(regime, period_id, age, residence_type)
|
||||
|
|
@ -125,7 +145,21 @@ class ITTaxStatementWizard(models.TransientModel):
|
|||
"No tax slab found for %s Regime with Age Category: %s and Residence Type: %s"
|
||||
) % (regime.capitalize(), age_category.replace('_', ' ').title(), residence_type))
|
||||
|
||||
return slab_master
|
||||
return slab_master
|
||||
|
||||
@api.onchange('employee_id', 'period_id')
|
||||
def _onchange_employee_id_period_id(self):
|
||||
domain_by_record = {}
|
||||
for rec in self:
|
||||
domain = [('period_id', '=', rec.period_id.id), ('to_date', '<', fields.Date.today())] if rec.period_id else []
|
||||
if rec.emp_doj:
|
||||
domain.append(('to_date', '>=', rec.emp_doj.replace(day=1)))
|
||||
|
||||
if rec.period_line and rec.period_line not in rec._get_effective_period_lines():
|
||||
rec.period_line = False
|
||||
domain_by_record[rec.id] = domain
|
||||
if len(self) == 1:
|
||||
return {'domain': {'period_line': domain_by_record.get(self.id, [])}}
|
||||
|
||||
def _get_standard_deduction(self, regime, slab_master=False):
|
||||
if slab_master:
|
||||
|
|
@ -295,7 +329,7 @@ class ITTaxStatementWizard(models.TransientModel):
|
|||
|
||||
return list(grouped.values())
|
||||
|
||||
def fetch_salary_components(self):
|
||||
def fetch_salary_components(self):
|
||||
"""fetch salary components from payroll data"""
|
||||
for rec in self:
|
||||
data = {
|
||||
|
|
@ -311,10 +345,10 @@ class ITTaxStatementWizard(models.TransientModel):
|
|||
}
|
||||
if not rec.employee_id or not rec.contract_id or not rec.period_id or not rec.period_line:
|
||||
return data
|
||||
period_lines = rec.period_id.period_line_ids
|
||||
|
||||
for line in period_lines:
|
||||
components = rec._get_salary_components_for_period_line(line)
|
||||
period_lines = rec._get_effective_period_lines()
|
||||
|
||||
for line in period_lines:
|
||||
components = rec._get_salary_components_for_period_line(line)
|
||||
if line.from_date and rec.period_line.from_date and line.from_date <= rec.period_line.from_date:
|
||||
data['basic_salary']['actual'].append(components['basic_salary'])
|
||||
data['hra_salary']['actual'].append(components['hra_salary'])
|
||||
|
|
@ -378,7 +412,7 @@ class ITTaxStatementWizard(models.TransientModel):
|
|||
)
|
||||
rec.standard_deduction = rec._get_standard_deduction(rec.tax_regime, slab_master)
|
||||
|
||||
def fetch_deduction_components(self):
|
||||
def fetch_deduction_components(self):
|
||||
for rec in self:
|
||||
data = {
|
||||
'professional_tax': {'actual': [], 'projected': []},
|
||||
|
|
@ -387,12 +421,12 @@ class ITTaxStatementWizard(models.TransientModel):
|
|||
if not rec.employee_id or not rec.contract_id or not rec.period_id or not rec.period_line:
|
||||
return data
|
||||
|
||||
for line in rec.period_id.period_line_ids:
|
||||
rule_amounts = rec._get_rule_amounts_for_period_line(line, ['PT', 'PFE'])
|
||||
bucket = 'actual' if line.from_date <= rec.period_line.from_date else 'projected'
|
||||
data['professional_tax'][bucket].append(rule_amounts['PT'])
|
||||
data['nps_employer_contribution'][bucket].append(rule_amounts['PFE'])
|
||||
return data
|
||||
for line in rec._get_effective_period_lines():
|
||||
rule_amounts = rec._get_rule_amounts_for_period_line(line, ['PT', 'PFE'])
|
||||
bucket = 'actual' if line.from_date <= rec.period_line.from_date else 'projected'
|
||||
data['professional_tax'][bucket].append(rule_amounts['PT'])
|
||||
data['nps_employer_contribution'][bucket].append(rule_amounts['PFE'])
|
||||
return data
|
||||
|
||||
|
||||
@api.onchange('employee_id')
|
||||
|
|
@ -725,25 +759,19 @@ class ITTaxStatementWizard(models.TransientModel):
|
|||
'target': 'current',
|
||||
}
|
||||
|
||||
def _prepare_income_tax_data(self, include_comparison=False):
|
||||
"""Prepare data for the tax statement report"""
|
||||
today = date.today()
|
||||
fy_start = self.period_id.from_date
|
||||
fy_end = self.period_id.to_date
|
||||
total_months = ((fy_end.year - fy_start.year) * 12 +
|
||||
(fy_end.month - fy_start.month) + 1)
|
||||
|
||||
line_start = self.period_line.from_date
|
||||
current_month_index = ((line_start.year - fy_start.year) * 12 +
|
||||
(line_start.month - fy_start.month) + 1)
|
||||
if today.month >= 4:
|
||||
fy_start = date(today.year, 4, 1)
|
||||
fy_end = date(today.year + 1, 3, 31)
|
||||
else:
|
||||
fy_start = date(today.year - 1, 4, 1)
|
||||
fy_end = date(today.year, 3, 31)
|
||||
|
||||
values = self._get_tax_base_values(include_comparison=include_comparison)
|
||||
def _prepare_income_tax_data(self, include_comparison=False):
|
||||
"""Prepare data for the tax statement report"""
|
||||
today = date.today()
|
||||
display_fy_start = self.period_id.from_date
|
||||
fy_end = self.period_id.to_date
|
||||
effective_fy_start = self._get_effective_period_start() or display_fy_start
|
||||
total_months = ((fy_end.year - effective_fy_start.year) * 12 +
|
||||
(fy_end.month - effective_fy_start.month) + 1)
|
||||
|
||||
line_start = self.period_line.from_date
|
||||
current_month_index = ((line_start.year - effective_fy_start.year) * 12 +
|
||||
(line_start.month - effective_fy_start.month) + 1)
|
||||
values = self._get_tax_base_values(include_comparison=include_comparison)
|
||||
salary_components_data = values['salary_components_data']
|
||||
annual_gross_salary = values['annual_gross_salary']
|
||||
gross_salary_actual = values['gross_salary_actual']
|
||||
|
|
@ -778,15 +806,16 @@ class ITTaxStatementWizard(models.TransientModel):
|
|||
|
||||
# Prepare data structure matching screenshot format
|
||||
# Financial year (period_id)
|
||||
fy_start = self.period_id.from_date
|
||||
fy_end = self.period_id.to_date
|
||||
total_months = ((fy_end.year - fy_start.year) * 12 +
|
||||
(fy_end.month - fy_start.month) + 1)
|
||||
|
||||
# Current month (period_line)
|
||||
line_start = self.period_line.from_date
|
||||
current_month_index = ((line_start.year - fy_start.year) * 12 +
|
||||
(line_start.month - fy_start.month) + 1)
|
||||
display_fy_start = self.period_id.from_date
|
||||
fy_end = self.period_id.to_date
|
||||
effective_fy_start = self._get_effective_period_start() or display_fy_start
|
||||
total_months = ((fy_end.year - effective_fy_start.year) * 12 +
|
||||
(fy_end.month - effective_fy_start.month) + 1)
|
||||
|
||||
# Current month (period_line)
|
||||
line_start = self.period_line.from_date
|
||||
current_month_index = ((line_start.year - effective_fy_start.year) * 12 +
|
||||
(line_start.month - effective_fy_start.month) + 1)
|
||||
tax_result['roundoff_taxable_income'] = float(round(tax_result["taxable_income"] / 10) * 10)
|
||||
birthday = self.employee_id.birthday
|
||||
if birthday:
|
||||
|
|
@ -806,7 +835,7 @@ class ITTaxStatementWizard(models.TransientModel):
|
|||
'total': total,
|
||||
})
|
||||
data = {
|
||||
'financial_year': f"{fy_start.year}-{fy_end.year}",
|
||||
'financial_year': f"{display_fy_start.year}-{fy_end.year}",
|
||||
'assessment_year': fy_end.year + 1,
|
||||
'report_time': today.strftime('%d-%m-%Y %H:%M'),
|
||||
'user': 'ESS',
|
||||
|
|
|
|||
|
|
@ -13,17 +13,17 @@ class IncomeTaxSlabMaster(models.Model):
|
|||
)
|
||||
]
|
||||
|
||||
name = fields.Char(string="Slab Name", required=True)
|
||||
period_id = fields.Many2one('payroll.period')
|
||||
name = fields.Char(string="Slab Name", required=True, copy=False, default="AY")
|
||||
period_id = fields.Many2one('payroll.period', copy=False)
|
||||
regime = fields.Selection([
|
||||
('old', 'Old Tax Regime'),
|
||||
('new', 'New Tax Regime')
|
||||
], required=True)
|
||||
], required=True, default='old')
|
||||
age_category = fields.Selection([
|
||||
('below_60', 'Below 60 Years'),
|
||||
('60_to_80', '60-80 Years'),
|
||||
('above_80', 'Above 80 Years')
|
||||
], required=True)
|
||||
], required=True, default='below_60')
|
||||
residence_type = fields.Selection([
|
||||
('resident', 'Resident'),
|
||||
('non_resident', 'Non Resident'),
|
||||
|
|
@ -34,6 +34,37 @@ class IncomeTaxSlabMaster(models.Model):
|
|||
rules = fields.One2many('it.slab.master.rules','slab_id', string="Slab Rules")
|
||||
surcharges = fields.One2many('it.sur.charge.rules','slab_id', string="Surcharges Rules")
|
||||
|
||||
def copy(self, default=None):
|
||||
"""Override copy to duplicate slab rules and surcharge rules"""
|
||||
if default is None:
|
||||
default = {}
|
||||
|
||||
# Check if we should duplicate from context
|
||||
duplicate_rules = self.env.context.get('duplicate_slab_rules', True)
|
||||
|
||||
default.update({
|
||||
'name': _("%s (copy)") % (self.name or 'Slab'),
|
||||
'rules': [],
|
||||
'surcharges': [],
|
||||
})
|
||||
|
||||
new_slab = super(IncomeTaxSlabMaster, self).copy(default)
|
||||
|
||||
# Only duplicate if flag is True
|
||||
if duplicate_rules:
|
||||
if self.rules:
|
||||
for rule in self.rules:
|
||||
rule.copy({
|
||||
'slab_id': new_slab.id,
|
||||
})
|
||||
|
||||
if self.surcharges:
|
||||
for surcharge in self.surcharges:
|
||||
surcharge.copy({
|
||||
'slab_id': new_slab.id,
|
||||
})
|
||||
|
||||
return new_slab
|
||||
class IncomeTaxSlabMasterRules(models.Model):
|
||||
_name = 'it.slab.master.rules'
|
||||
_description = 'Income Tax slab rules'
|
||||
|
|
|
|||
|
|
@ -21,19 +21,19 @@
|
|||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<header>
|
||||
<button name="action_generate_report"
|
||||
string="Generate Tax Statement"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-file-text"/>
|
||||
<button name="action_check_regime_comparison"
|
||||
string="Check Comparison"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-calculator"/>
|
||||
<button name="action_generate_comparison_report"
|
||||
string="Download Comparison"
|
||||
type="object"
|
||||
<button name="action_generate_report"
|
||||
string="Generate Tax Statement"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-file-text"/>
|
||||
<button name="action_check_regime_comparison"
|
||||
string="Check Comparison"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-calculator"/>
|
||||
<button name="action_generate_comparison_report"
|
||||
string="Download Comparison"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-balance-scale"
|
||||
invisible="not comparison_available"/>
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
<field name="period_id" options="{'no_edit': True, 'no_create': True, 'no_open': True}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="period_line" force_save="1" domain="[('period_id', '=', period_id),('to_date','<',datetime.datetime.now())]"/>
|
||||
<field name="period_line" force_save="1" domain="[('period_id', '=', period_id),('to_date','<',(context_today() + datetime.timedelta(days=30)).strftime('%Y-%m-%d')),('to_date','>',emp_doj)]"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
</field>
|
||||
</page>
|
||||
<page string="Surcharge Rules">
|
||||
<field name="rules">
|
||||
<field name="surcharges">
|
||||
<list editable="bottom">
|
||||
<field name="min_income"/>
|
||||
<field name="max_income"/>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from . import hr_tds_calculation
|
||||
from . import children_education_costing
|
||||
from . import employee_life_insurance
|
||||
from . import nsc_declaration
|
||||
|
|
|
|||
|
|
@ -0,0 +1,308 @@
|
|||
from datetime import date
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class HrTdsCalculation(models.TransientModel):
|
||||
_inherit = 'l10n.in.tds.computation.wizard'
|
||||
|
||||
def default_get(self, fields):
|
||||
res = super().default_get(fields)
|
||||
# Remove the standard_deduction if it was set by parent
|
||||
if 'standard_deduction' in res:
|
||||
del res['standard_deduction']
|
||||
return res
|
||||
|
||||
tax_regime = fields.Selection([
|
||||
('new', 'New Regime'),
|
||||
('old', 'Old Regime')
|
||||
], string="Tax Regime", required=True, default='new')
|
||||
|
||||
standard_deduction = fields.Float(
|
||||
string="Standard Deduction",
|
||||
compute="_compute_standard_deduction",
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
def _get_regular_pay_structure(self):
|
||||
self.ensure_one()
|
||||
if self.payslip_id and self.payslip_id.struct_id:
|
||||
return self.payslip_id.struct_id
|
||||
if self.contract_id.structure_type_id.default_struct_id:
|
||||
return self.contract_id.structure_type_id.default_struct_id
|
||||
return self.env['hr.payroll.structure']
|
||||
|
||||
def _get_annualization_factor(self):
|
||||
self.ensure_one()
|
||||
if self.contract_id:
|
||||
return self.contract_id._get_salary_costs_factor()
|
||||
return 12
|
||||
|
||||
def _get_monthly_gross_from_structure(self):
|
||||
self.ensure_one()
|
||||
if not self.contract_id or not self.contract_id.employee_id:
|
||||
return 0.0
|
||||
|
||||
structure = self._get_regular_pay_structure()
|
||||
payslip = self.payslip_id
|
||||
dummy_payslip = self.env['hr.payslip']
|
||||
|
||||
if not payslip:
|
||||
today = fields.Date.today()
|
||||
period_start = today.replace(day=1)
|
||||
period_end = fields.Date.end_of(period_start, 'month')
|
||||
dummy_payslip = self.env['hr.payslip'].sudo().create({
|
||||
'name': 'TDS Gross Preview',
|
||||
'employee_id': self.contract_id.employee_id.id,
|
||||
'contract_id': self.contract_id.id,
|
||||
'struct_id': structure.id or self.contract_id.structure_type_id.default_struct_id.id,
|
||||
'date_from': period_start,
|
||||
'date_to': period_end,
|
||||
})
|
||||
dummy_payslip.sudo().compute_sheet()
|
||||
payslip = dummy_payslip
|
||||
|
||||
try:
|
||||
gross_lines = payslip.line_ids.filtered(lambda line: line.salary_rule_id.code == 'GROSS')
|
||||
if gross_lines:
|
||||
return sum(gross_lines.mapped('total'))
|
||||
return self.contract_id.wage or 0.0
|
||||
finally:
|
||||
if dummy_payslip:
|
||||
dummy_payslip.sudo().action_payslip_cancel()
|
||||
dummy_payslip.sudo().unlink()
|
||||
|
||||
def _get_current_payroll_period(self):
|
||||
today = fields.Date.today()
|
||||
return self.env['payroll.period'].search([
|
||||
('from_date', '<=', today),
|
||||
('to_date', '>=', today),
|
||||
], limit=1)
|
||||
|
||||
def _get_age_category(self, age):
|
||||
if age < 60:
|
||||
return 'below_60'
|
||||
if age < 80:
|
||||
return '60_to_80'
|
||||
return 'above_80'
|
||||
|
||||
def _get_employee_age(self):
|
||||
self.ensure_one()
|
||||
employee = self.contract_id.employee_id
|
||||
if employee and employee.birthday:
|
||||
return relativedelta(date.today(), employee.birthday).years
|
||||
return 30
|
||||
|
||||
def _find_applicable_slab(self, regime):
|
||||
self.ensure_one()
|
||||
period = self._get_current_payroll_period()
|
||||
if not period:
|
||||
return self.env['it.slab.master']
|
||||
|
||||
age_category = self._get_age_category(self._get_employee_age())
|
||||
return self.env['it.slab.master'].search([
|
||||
('period_id', '=', period.id),
|
||||
('regime', '=', regime),
|
||||
('age_category', '=', age_category),
|
||||
'|',
|
||||
('residence_type', '=', 'resident'),
|
||||
('residence_type', '=', 'both'),
|
||||
], limit=1)
|
||||
|
||||
def _get_standard_deduction_amount(self, regime, slab_master=False):
|
||||
if slab_master and slab_master.standard_deduction:
|
||||
return slab_master.standard_deduction
|
||||
return 75000.0 if regime == 'new' else 50000.0
|
||||
|
||||
def _compute_tax_using_slab(self, taxable_income, slab_master):
|
||||
rules = slab_master.rules.sorted(lambda r: (r.sequence, r.max_income or float('inf')))
|
||||
applicable_rule = False
|
||||
previous_rules = self.env['it.slab.master.rules']
|
||||
|
||||
for rule in rules:
|
||||
min_income = rule.min_income or 0.0
|
||||
max_income = rule.max_income or float('inf')
|
||||
if min_income < taxable_income <= max_income:
|
||||
applicable_rule = rule
|
||||
previous_rules = rules.filtered(
|
||||
lambda r: r.sequence < rule.sequence or
|
||||
(r.sequence == rule.sequence and (r.max_income or float('inf')) < max_income)
|
||||
)
|
||||
break
|
||||
|
||||
if not applicable_rule:
|
||||
return 0.0
|
||||
|
||||
previous_max_income = previous_rules[-1].max_income if previous_rules else 0.0
|
||||
current_tax = (taxable_income - previous_max_income) * (applicable_rule.tax_rate / 100.0)
|
||||
previous_fixed_amounts = sum(previous_rules.mapped('fixed_amount'))
|
||||
return current_tax + previous_fixed_amounts
|
||||
|
||||
def _compute_rebate(self, regime, taxable_income, slab_tax):
|
||||
if regime == 'old':
|
||||
if taxable_income <= 500000.0:
|
||||
return min(12500.0, slab_tax)
|
||||
return 0.0
|
||||
|
||||
if taxable_income >= 1200000.0:
|
||||
return max(0.0, slab_tax - (taxable_income - 1200000.0))
|
||||
return min(60000.0, slab_tax)
|
||||
|
||||
def _compute_surcharge(self, slab_master, taxable_income, tax_after_rebate):
|
||||
surcharge_rate = 0.0
|
||||
for rule in slab_master.surcharges.sorted('min_income'):
|
||||
max_income = rule.max_income or float('inf')
|
||||
if rule.min_income < taxable_income <= max_income:
|
||||
surcharge_rate = rule.surcharge_rate
|
||||
return tax_after_rebate * (surcharge_rate / 100.0)
|
||||
|
||||
def _compute_cess(self, slab_master, taxable_income, tax_with_surcharge):
|
||||
cess_rate = 4.0
|
||||
for rule in slab_master.rules.sorted('min_income'):
|
||||
max_income = rule.max_income or float('inf')
|
||||
if rule.min_income < taxable_income <= max_income:
|
||||
cess_rate = rule.cess_rate or 4.0
|
||||
break
|
||||
return tax_with_surcharge * (cess_rate / 100.0)
|
||||
|
||||
def _compute_tax_from_custom_slab(self, total_income, regime, slab_master):
|
||||
taxable_income = max(total_income - self._get_standard_deduction_amount(regime, slab_master), 0.0)
|
||||
slab_tax = self._compute_tax_using_slab(taxable_income, slab_master)
|
||||
rebate = self._compute_rebate(regime, taxable_income, slab_tax)
|
||||
total_tax_on_income = max(0.0, slab_tax - rebate)
|
||||
surcharge = self._compute_surcharge(slab_master, taxable_income, total_tax_on_income)
|
||||
tax_with_surcharge = total_tax_on_income + surcharge
|
||||
cess = self._compute_cess(slab_master, taxable_income, tax_with_surcharge) if tax_with_surcharge else 0.0
|
||||
return {
|
||||
'taxable_income': taxable_income,
|
||||
'tax_on_taxable_income': slab_tax,
|
||||
'rebate': rebate,
|
||||
'total_tax_on_income': total_tax_on_income,
|
||||
'surcharge': surcharge,
|
||||
'cess': cess,
|
||||
'total_tax': tax_with_surcharge + cess,
|
||||
}
|
||||
|
||||
def _compute_tax_from_rule_parameters(self, total_income, standard_deduction):
|
||||
rule_parameter = self.env['hr.rule.parameter']
|
||||
tax_slabs = rule_parameter._get_parameter_from_code('l10n_in_tds_rate_chart')
|
||||
tax_slabs_for_surcharge = rule_parameter._get_parameter_from_code('l10n_in_surcharge_rate')
|
||||
min_income_for_surcharge = rule_parameter._get_parameter_from_code('l10n_in_min_income_surcharge')
|
||||
min_income_for_rebate = rule_parameter._get_parameter_from_code('l10n_in_min_income_tax_rebate')
|
||||
|
||||
taxable_income = max(total_income - standard_deduction, 0.0)
|
||||
tax = 0.0
|
||||
for rate, (lower, upper) in tax_slabs:
|
||||
if taxable_income <= lower:
|
||||
break
|
||||
taxable_amount = min(taxable_income, float(upper)) - lower
|
||||
tax += round(taxable_amount * rate)
|
||||
|
||||
if taxable_income >= min_income_for_rebate:
|
||||
marginal_income = taxable_income - min_income_for_rebate
|
||||
rebate = max(tax - marginal_income, 0.0)
|
||||
else:
|
||||
rebate = tax
|
||||
total_tax_on_income = tax - rebate
|
||||
|
||||
surcharge = 0.0
|
||||
if taxable_income > min_income_for_surcharge:
|
||||
for rate, amount in tax_slabs_for_surcharge:
|
||||
if taxable_income <= float(amount[1]):
|
||||
surcharge = total_tax_on_income * rate
|
||||
break
|
||||
|
||||
max_tax_slabs = rule_parameter._get_parameter_from_code('l10n_in_max_surcharge_tax_rate')
|
||||
max_taxable_income, max_tax, max_surcharge = 0.0, 0.0, 0.0
|
||||
for income, tax_amount, surcharge_rate in max_tax_slabs:
|
||||
if taxable_income <= income:
|
||||
break
|
||||
max_taxable_income, max_tax, max_surcharge = income, tax_amount, surcharge_rate
|
||||
|
||||
excess_income = taxable_income - max_taxable_income
|
||||
max_tax_with_surcharge = max_tax + max_surcharge
|
||||
total_tax_with_surcharge = total_tax_on_income + surcharge
|
||||
excess_tax = total_tax_with_surcharge - max_tax_with_surcharge
|
||||
if excess_tax - excess_income > 0:
|
||||
surcharge = max_tax_with_surcharge + taxable_income - max_taxable_income - total_tax_on_income
|
||||
|
||||
cess = (total_tax_on_income + surcharge) * 0.04
|
||||
return {
|
||||
'taxable_income': taxable_income,
|
||||
'tax_on_taxable_income': tax,
|
||||
'rebate': rebate,
|
||||
'total_tax_on_income': total_tax_on_income,
|
||||
'surcharge': surcharge,
|
||||
'cess': cess,
|
||||
'total_tax': total_tax_on_income + surcharge + cess,
|
||||
}
|
||||
|
||||
@api.depends('contract_id', 'payslip_id', 'contract_id.structure_type_id', 'contract_id.wage')
|
||||
def _compute_total_income(self):
|
||||
for record in self:
|
||||
if not record.contract_id:
|
||||
record.total_income = 0.0
|
||||
continue
|
||||
monthly_gross = record._get_monthly_gross_from_structure()
|
||||
|
||||
rule_parameter = self.env['hr.rule.parameter']
|
||||
pf = 0
|
||||
if record.contract_id:
|
||||
amounts = rule_parameter._get_parameter_from_code('l10n_in_professional_tax')
|
||||
cost = record.contract_id.wage -1800
|
||||
if cost >= 20000:
|
||||
pf = amounts[0]
|
||||
elif cost >= 15001 and cost < 20000:
|
||||
pf = amounts[1]
|
||||
else:
|
||||
pf = 0
|
||||
record.total_income = (monthly_gross * record._get_annualization_factor()) + (pf * 12)
|
||||
|
||||
@api.depends('contract_id', 'payslip_id', 'total_income')
|
||||
def _compute_net_monthly(self):
|
||||
for record in self:
|
||||
factor = record._get_annualization_factor() if record.contract_id else 12
|
||||
if record.payslip_id and record.payslip_id.net_wage:
|
||||
record.net_monthly = record.payslip_id.net_wage
|
||||
elif factor:
|
||||
record.net_monthly = record.total_income / factor
|
||||
else:
|
||||
record.net_monthly = 0.0
|
||||
|
||||
@api.depends('tax_regime', 'contract_id')
|
||||
def _compute_standard_deduction(self):
|
||||
for record in self:
|
||||
slab_master = record._find_applicable_slab(record.tax_regime) if record.tax_regime else False
|
||||
record.standard_deduction = record._get_standard_deduction_amount(record.tax_regime or 'new', slab_master)
|
||||
|
||||
@api.depends('total_income', 'tax_regime', 'standard_deduction', 'contract_id')
|
||||
def _compute_taxable_income(self):
|
||||
for record in self:
|
||||
if not record.total_income:
|
||||
record.taxable_income = 0.0
|
||||
record.tax_on_taxable_income = 0.0
|
||||
record.rebate = 0.0
|
||||
record.total_tax_on_income = 0.0
|
||||
record.surcharge = 0.0
|
||||
record.cess = 0.0
|
||||
record.total_tax = 0.0
|
||||
continue
|
||||
|
||||
slab_master = record._find_applicable_slab(record.tax_regime) if record.tax_regime else False
|
||||
if slab_master:
|
||||
values = record._compute_tax_from_custom_slab(record.total_income, record.tax_regime, slab_master)
|
||||
else:
|
||||
values = record._compute_tax_from_rule_parameters(record.total_income, record.standard_deduction)
|
||||
|
||||
record.taxable_income = values['taxable_income']
|
||||
record.tax_on_taxable_income = values['tax_on_taxable_income']
|
||||
record.rebate = values['rebate']
|
||||
record.total_tax_on_income = values['total_tax_on_income']
|
||||
record.surcharge = values['surcharge']
|
||||
record.cess = values['cess']
|
||||
record.total_tax = values['total_tax']
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="l10n_in_tds_computation_wizard_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">l10n.in.tds.computation.wizard.view.form</field>
|
||||
<field name="model">l10n.in.tds.computation.wizard</field>
|
||||
<field name="inherit_id" ref="l10n_in_hr_payroll.l10n_in_tds_computation_wizard_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//form/group/field[@name='total_income']" position="after">
|
||||
<field name="tax_regime" widget="radio" options="{'horizontal': true}"/>
|
||||
</xpath>
|
||||
<xpath expr="//form/group/field[@name='standard_deduction']" position="attributes">
|
||||
<attribute name="force_save">1</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue