Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] hr_payroll_tds: Introduced Tax Declaration Sub-menu #650

Draft
wants to merge 1 commit into
base: 18.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,4 @@ dmypy.json

# Pyre type checker
.pyre/
.vscode/shortcuts.json
2 changes: 2 additions & 0 deletions hr_payroll_tds/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizard
20 changes: 20 additions & 0 deletions hr_payroll_tds/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "Payroll/TDS",
"version": "1.0",
"author": "Harsh Siddhpara siha",
"summary": "Add Tax Declaration in contracts menu",
"depends": ["l10n_in_hr_payroll"],
"data": [
"security/security.xml",
"security/ir.model.access.csv",
"wizard/hr_tds_declaration_wizard_view.xml",
"views/hr_tds_declaration_views.xml",
"views/hr_payroll_menu.xml",
"views/hr_tds_declaration_details_views.xml",
"views/hr_tds_report.xml",
"views/report_tds_declaration_template.xml",
"data/hr_rule_parameters_data.xml"
],
"installable": True,
"license": "LGPL-3",
}
33 changes: 33 additions & 0 deletions hr_payroll_tds/data/hr_rule_parameters_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="l10n_in_rule_parameter_tds_tax_slabs" model="hr.rule.parameter">
<field name="name">India: TDS Rate Chart New Regime</field>
<field name="code">l10n_in_tds_rate_chart_new_regime</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="l10n_in_rule_parameter_tds_tax_slabs_value" model="hr.rule.parameter.value">
<field name="parameter_value">[
(0.0, (0, 300000),0),
(0.05, (300001, 700000),0),
(0.1, (700001, 1000000),20000),
(0.15, (1000001, 1200000),50000),
(0.2, (1200001, 1500000),80000),
(0.3, (1500001, float('inf')),140000)
]</field>
<field name="rule_parameter_id" ref="l10n_in_rule_parameter_tds_tax_slabs"/>
<field name="date_from" eval="datetime(2000, 1, 1).date()"/>
</record>

<record id="l10n_in_rule_parameter_standard_deduction_new_regime" model="hr.rule.parameter">
<field name="name">India: Standard Deduction New Regime</field>
<field name="code">l10n_in_standard_deduction_new_regime</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="l10n_in_rule_parameter_standard_deduction_value" model="hr.rule.parameter.value">
<field name="parameter_value">75000</field>
<field name="rule_parameter_id" ref="l10n_in_rule_parameter_standard_deduction_new_regime"/>
<field name="date_from" eval="datetime(2000, 1, 1).date()"/>
</record>

</odoo>
3 changes: 3 additions & 0 deletions hr_payroll_tds/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import hr_tds_declaration
from . import hr_tds_declaration_details
from . import hr_employee
16 changes: 16 additions & 0 deletions hr_payroll_tds/models/hr_employee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from odoo import exceptions, models

class Employee(models.Model):
_inherit = "hr.employee"

def action_open_related_tds_declaration(self):
"""Opens the TDS Declarations associated with the current employee."""

action = self.env["ir.actions.actions"]._for_xml_id("hr_payroll_tds.action_open_declarations")
target_ids = self.env["hr.tds.declaration.details"].search([("employee_id", "=", self.id)])
if not target_ids:
raise exceptions.UserError("No TDS declaration available for current employee.")
action["views"] = [[False, "list"], [False, "form"]]
action["domain"] = [("id", "in", target_ids.ids)]

return action
61 changes: 61 additions & 0 deletions hr_payroll_tds/models/hr_tds_declaration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import datetime
from odoo import api, fields, models

class HrTdsDeclaration(models.Model):
_name = "hr.tds.declaration"
_description = "TDS declaration"

def _get_financial_year_selection(self):
current_year = datetime.date.today().year
previous_year = f"{current_year-1}-{current_year}"
current_financial_year = f"{current_year}-{current_year+1}"
return [(previous_year, previous_year), (current_financial_year, current_financial_year)]

name = fields.Char(string="TDS Declaration", required=True)
tds_declaration_ids = fields.One2many("hr.tds.declaration.details", "tds_declaration_id")
financial_year = fields.Selection(
selection=_get_financial_year_selection,
string="Financial Year",
required=True,
default=lambda self: f"{datetime.date.today().year}-{datetime.date.today().year + 1}",
)
start_date = fields.Date(string="Start Date", compute="_compute_dates",readonly=False)
end_date = fields.Date(string="End Date", compute="_compute_dates",readonly=False)
tds_declaration_count = fields.Integer(compute="_compute_declarations_count")
company_id = fields.Many2one("res.company", default=lambda self: self.env.company)
state = fields.Selection(
selection=[
("new", "New"),
("draft", "Draft"),
("confirmed", "Confirmed"),
("accepted", "Accepted")
],
string="Status",
default = "new"
)

@api.depends("financial_year")
def _compute_dates(self):
if self.financial_year:
start_year = int(self.financial_year.split("-")[0])
end_year = int(self.financial_year.split("-")[1])
self.start_date = fields.Date.to_date(f"{start_year}-04-01")
self.end_date = fields.Date.to_date(f"{end_year}-03-31")

def _compute_declarations_count(self):
self.tds_declaration_count = len(self.tds_declaration_ids)

def action_approved(self):
self.state = "accepted"

def action_set_to_draft(self):
self.state = "draft"

def action_open_declarations(self):
return {
"type": "ir.actions.act_window",
"res_model": "hr.tds.declaration.details",
"views": [[False, "list"], [False, "form"]],
"domain": [["id", "in", self.tds_declaration_ids.ids]],
"name": f"TDS Declaration {self.financial_year}",
}
160 changes: 160 additions & 0 deletions hr_payroll_tds/models/hr_tds_declaration_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
from odoo import api, fields, models

class HrTdsDeclarationDetails(models.Model):
_name = "hr.tds.declaration.details"
_description = "Hr TDS declaration details for genearated employees"

name = fields.Char(string="Tax Declarations")
tds_declaration_id = fields.Many2one("hr.tds.declaration", string="TDS Declaration")
employee_id = fields.Many2one("hr.employee")
contract_id = fields.Many2one("hr.contract", string="Contract")
start_date = fields.Date(string="Start Date")
end_date = fields.Date(string="End Date")
financial_year = fields.Char(string="Financial Year")
state = fields.Selection(
selection= [
("new", "New"),
("verify", "Confirmed"),
("approve", "Approved"),
("cancel", "Cancelled")
],
string="Status",
default="new"
)
tax_regime = fields.Selection(
selection= [
("new_regime", "New Regime"),
("old_regiem", "Old Regime")
],
string="Tax Regime",
default="new_regime"
)
age_category = fields.Selection(
selection= [
("lt60", "Less Than 60"),
("60to80", "60 - 80"),
("gt80", "Above 80")
],
string="Category (Age)",
default="lt60"
)
currency_id = fields.Many2one("res.currency", default=lambda self: self.env.company.currency_id.id)
total_income = fields.Monetary(string="Total Income (yearly)", compute="_compute_total_income", inverse="_inverse_total_income", readonly=False, store=True)
standard_deduction = fields.Monetary(string="Standard Deducation", compute="_compute_standard_deduction", readonly=True)
taxable_amount = fields.Monetary(string="Taxable Amount", compute="_compute_taxable_amount")
tax_on_taxable_amount = fields.Monetary(string="Tax On Taxable Amount", compute="_compute_taxable_amount")
rebate = fields.Monetary(string="Rebate Under Section 87A(a)", compute="_compute_taxable_amount")
total_tax_on_income = fields.Monetary(string="Total Tax On Income", compute="_compute_taxable_amount")
surcharge = fields.Monetary(string="Surcharge", compute="_compute_taxable_amount")
health_education_cess = fields.Monetary(string="Health and Education Cess", compute="_compute_taxable_amount")
total_tax_to_pay = fields.Monetary(string="Total Tax to be Paid", compute="_compute_taxable_amount")
monthly_tds = fields.Monetary(string="Monthly TDS Payable", compute="_compute_monthly_tds")
other_income_source = fields.Monetary(string="Other Source of Income")
other_allowance = fields.Monetary(string="Other Allowance Details")
#Below field is used to store manually added Total Income to ensure that while modifying other_income_source & other_allowance values are reflecting in correct manner into Total Income
manually_added_total_income = fields.Float()

@api.depends("other_income_source", "other_allowance")
def _compute_total_income(self):
for record in self:
if record.manually_added_total_income and record.total_income:
record.total_income = record.manually_added_total_income + record.other_income_source + record.other_allowance
else:
record.total_income = (record.contract_id.wage * 12 if record.contract_id.wage else 0) + record.other_income_source + record.other_allowance

def _inverse_total_income(self):
for record in self:
if record.total_income != (record.contract_id.wage * 12 if record.contract_id.wage else 0) + record.other_income_source + record.other_allowance:
record.manually_added_total_income = record.total_income
record.total_income += record.other_income_source + record.other_allowance


def _compute_standard_deduction(self):
for record in self:
record.standard_deduction = record.env['hr.rule.parameter']._get_parameter_from_code('l10n_in_standard_deduction_new_regime')

@api.depends("total_income")
def _compute_taxable_amount(self):
"""Computes the taxable amount, tax, rebate, surcharge, and total tax payable
based on predefined tax slabs and thresholds.

- Calculates `taxable_amount` after standard deductions.
- Determines tax liability using progressive tax slabs.
- Applies rebate if income is below the rebate threshold.
- Computes surcharge for incomes exceeding the surcharge threshold.
- Ensures surcharge does not exceed legal limits.
- Adds health & education cess (4%) to derive total tax payable.
"""
rule_parameter = self.env['hr.rule.parameter']
tax_slabs = rule_parameter._get_parameter_from_code('l10n_in_tds_rate_chart_new_regime')
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')

for record in self:
record.taxable_amount = max(record.total_income - record.standard_deduction, 0)

tax = 0
for rate, (lower, upper), fixed_tax in tax_slabs:
if record.taxable_amount >= lower and record.taxable_amount <= upper:
taxable_amount_temp = record.taxable_amount - lower
tax = fixed_tax + round(taxable_amount_temp * rate)
record.tax_on_taxable_amount = tax

if record.taxable_amount >= min_income_for_rebate:
marginal_income = record.taxable_amount - min_income_for_rebate
record.rebate = max(record.tax_on_taxable_amount - marginal_income, 0)
else:
record.rebate = record.tax_on_taxable_amount
record.total_tax_on_income = record.tax_on_taxable_amount - record.rebate

if record.taxable_amount > min_income_for_surcharge:
surcharge = 0
for rate, amount in tax_slabs_for_surcharge:
if record.taxable_amount <= float(amount[1]):
surcharge = record.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

for income, tax, surcharge_rate in max_tax_slabs:
if record.taxable_amount <= income:
break
else:
max_taxable_income, max_tax, max_surcharge = income, tax, surcharge_rate

excess_income = record.taxable_amount - max_taxable_income
max_tax_with_surcharge = max_tax + max_surcharge
total_tax_with_surcharge = record.total_tax_on_income + surcharge
excess_tax = total_tax_with_surcharge - max_tax_with_surcharge

if excess_tax - excess_income > 0:
record.surcharge = max_tax_with_surcharge + record.taxable_amount - max_taxable_income - record.total_tax_on_income
else:
record.surcharge = surcharge
else:
record.surcharge = 0.0

record.health_education_cess = (record.total_tax_on_income + record.surcharge) * 0.04
record.total_tax_to_pay = record.total_tax_on_income + record.health_education_cess + record.surcharge

@api.depends("total_tax_to_pay")
def _compute_monthly_tds(self):
for record in self:
record.monthly_tds = record.total_tax_to_pay / 12

def action_tds_declaration_confirm(self):
if self.state == "new":
self.state = "verify"

def action_tds_declaration_approve(self):
if self.state in ("new", "verify"):
self.state = "approve"

def action_tds_declaration_cancel(self):
if self.state not in ("cancel"):
self.state = "cancel"

def action_print_tds_declaration(self):
return self.env.ref('hr_payroll_tds.action_report_tds_declaration').report_action(self.id)
5 changes: 5 additions & 0 deletions hr_payroll_tds/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
hr_payroll_tds_declaration,hr_payroll_tds_declaration,model_hr_tds_declaration,hr_payroll_tds.group_hr_tds_user,1,1,1,1
hr_payroll_tds_declaration_wizard,hr_payroll_tds_declaration_wizard,model_hr_tds_declaration_wizard,base.group_user,1,1,1,0
hr_tds_declaration_details,hr_tds_declaration_details,model_hr_tds_declaration_details,hr_payroll_tds.group_hr_tds_user,1,1,1,0
hr_employee_tds,hr_employee_tds,model_hr_employee,hr_payroll.group_hr_payroll_user,1,1,1,0
16 changes: 16 additions & 0 deletions hr_payroll_tds/security/security.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="group_hr_tds_user" model="res.groups">
<field name="name">Officer: Manage all tds</field>
<field name="category_id" ref="base.module_category_human_resources_payroll"/>
</record>

<record id="hr_tds_declaration_rule" model="ir.rule">
<field name="name">Record rule according to current company</field>
<field name="model_id" ref="model_hr_tds_declaration"/>
<field name="domain_force">[('company_id', '=', company_id)]</field>
<field name="groups" eval="[(4, ref('group_hr_tds_user'))]"/>
</record>

</odoo>
1 change: 1 addition & 0 deletions hr_payroll_tds/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_hr_tds_declaration
Loading