diff --git a/.gitignore b/.gitignore index b6e47617de1..2ab793ba540 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,4 @@ dmypy.json # Pyre type checker .pyre/ +.vscode diff --git a/l10n_in_hr_payroll_extension/__init__.py b/l10n_in_hr_payroll_extension/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/l10n_in_hr_payroll_extension/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_in_hr_payroll_extension/__manifest__.py b/l10n_in_hr_payroll_extension/__manifest__.py new file mode 100644 index 00000000000..1e4175e4ae9 --- /dev/null +++ b/l10n_in_hr_payroll_extension/__manifest__.py @@ -0,0 +1,10 @@ +{ + 'name': "Indian Payroll Extension", + 'depends': ['l10n_in_hr_payroll'], + 'installable': True, + 'license': 'LGPL-3', + 'data': [ + 'views/res_config_settings_views.xml', + 'views/hr_contract_views.xml', + ], +} diff --git a/l10n_in_hr_payroll_extension/models/__init__.py b/l10n_in_hr_payroll_extension/models/__init__.py new file mode 100644 index 00000000000..880e048d29b --- /dev/null +++ b/l10n_in_hr_payroll_extension/models/__init__.py @@ -0,0 +1,3 @@ +from . import res_company +from . import res_config_settings +from . import hr_contract diff --git a/l10n_in_hr_payroll_extension/models/hr_contract.py b/l10n_in_hr_payroll_extension/models/hr_contract.py new file mode 100644 index 00000000000..9eebf2c1419 --- /dev/null +++ b/l10n_in_hr_payroll_extension/models/hr_contract.py @@ -0,0 +1,118 @@ +from odoo import api, fields, models + + +class HrContract(models.Model): + _inherit = 'hr.contract' + + l10n_in_basic_salary = fields.Monetary(string="Basic Salary", help="Basic salary calculated from the wage", compute="_compute_l10n_in_basic_salary", inverse="_inverse_l10n_in_basic_salary", currency_field="currency_id") + l10n_in_house_rent_allowance = fields.Monetary(string="House Rent Allowance", compute="_compute_l10n_in_house_rent_allowance", inverse="_inverse_l10n_in_house_rent_allowance", currency_field="currency_id") + l10n_in_standard_allowance = fields.Monetary(string="Standard Allowance", default=lambda self: self.env.company.l10n_in_standard_allowance, currency_field="currency_id") + l10n_in_performance_bonus = fields.Monetary(string="Performance Bonus", compute="_compute_l10n_in_performance_bonus", inverse="_inverse_l10n_in_performance_bonus", currency_field="currency_id") + l10n_in_leave_travel_allowance = fields.Monetary(string="Leave Travel Allowance", compute="_compute_l10n_in_leave_travel_allowance", inverse="_inverse_l10n_in_leave_travel_allowance", currency_field="currency_id") + l10n_in_leave_allowance = fields.Monetary(string="Leave Allowance", compute="_compute_leave_allowance", inverse="_inverse_leave_allowance", currency_field="currency_id") + l10n_in_leave_days = fields.Float(string="Leave Days", default=lambda self: self.env.company.l10n_in_leave_days) + l10n_in_gratuity = fields.Monetary(string="Gratuity", currency_field="currency_id", default=lambda self: self.env.company.l10n_in_gratuity) + l10n_in_supplementary_allowance = fields.Monetary(string="Supplementary Allowance", compute="_compute_l10n_in_supplementary_allowance", inverse="_inverse_l10n_in_supplementary_allowance", currency_field="currency_id", default=lambda self: self.env.company.l10n_in_supplementary_allowance) + + l10n_in_basic_salary_percent = fields.Float(string="Basic Salary Percentage", help="basic salary percentage of wage", default=lambda self: self.env.company.l10n_in_basic_salary_percent) + l10n_in_house_rent_allowance_percent = fields.Float(string="House Rent Allowance Percentage", help="this is the percentage of basic salary", default=lambda self: self.env.company.l10n_in_house_rent_allowance_percent) + l10n_in_standard_allowance_percent = fields.Float(string="Standard Allowance Percentage", compute="_compute_l10n_in_standard_allowance_percent", inverse="_inverse_l10n_in_standard_allowance_percent") + l10n_in_performance_bonus_percent = fields.Float(string="Performance Bonus Percentage", default=lambda self: self.env.company.l10n_in_performance_bonus_percent) + l10n_in_leave_travel_allowance_percent = fields.Float(string="Leave Travel Allowance Percentage", default=lambda self: self.env.company.l10n_in_leave_travel_allowance_percent) + l10n_in_leave_allowance_per_day_percent = fields.Float(string="Leave allowance per day percentage") + l10n_in_leave_allowance_percent = fields.Float(string="Leave Allowance Percentage") + l10n_in_gratuity_percent = fields.Float(string="Gratuity Percentage", compute="_compute_l10n_in_gratuity_percent", inverse="_inverse_l10n_in_gratuity_percent") + l10n_in_supplementary_allowance_percent = fields.Float(string="Supplementary Allowance Percentage") + + l10n_in_pf_employee_contribution = fields.Float(string="Employee Contribution", default=12) + l10n_in_pf_employer_contribution = fields.Float(string="Employer Contribution", default=12) + l10n_in_professional_tax = fields.Monetary(string="Professional Tax", default=200) + l10n_in_esic_employee_contribution = fields.Float(string="Employee Contribution", default=0.75) + l10n_in_esic_employer_contribution = fields.Float(string="Employer Contribution", default=3.25) + l10n_in_lwf_employee_contribution = fields.Monetary(string="Employee Contribution", currency_field="currency_id", default=6) + l10n_in_lwf_employer_contribution = fields.Monetary(string="Employer Contribution", currency_field="currency_id", default=12) + l10n_in_other_deduction = fields.Monetary(string="Other Deduction", currency_field="currency_id") + + @api.depends("l10n_in_basic_salary_percent", "wage") + def _compute_l10n_in_basic_salary(self): + for contract in self: + contract.l10n_in_basic_salary = contract.wage * contract.l10n_in_basic_salary_percent + + def _inverse_l10n_in_basic_salary(self): + for contract in self: + contract.l10n_in_basic_salary_percent = contract.l10n_in_basic_salary / contract.wage if contract.wage else 0 + + @api.depends("l10n_in_basic_salary", "l10n_in_house_rent_allowance_percent") + def _compute_l10n_in_house_rent_allowance(self): + for contract in self: + contract.l10n_in_house_rent_allowance = contract.l10n_in_basic_salary * contract.l10n_in_house_rent_allowance_percent + + def _inverse_l10n_in_house_rent_allowance(self): + for contract in self: + contract.l10n_in_house_rent_allowance_percent = contract.l10n_in_house_rent_allowance / contract.l10n_in_basic_salary if contract.l10n_in_basic_salary else 0 + + @api.depends("l10n_in_standard_allowance", "wage") + def _compute_l10n_in_standard_allowance_percent(self): + for contract in self: + contract.l10n_in_standard_allowance_percent = contract.l10n_in_standard_allowance / contract.wage if contract.wage else 0 + + def _inverse_l10n_in_standard_allowance_percent(self): + for contract in self: + contract.l10n_in_standard_allowance = contract.l10n_in_standard_allowance_percent * contract.wage + + @api.depends("l10n_in_performance_bonus_percent", "l10n_in_basic_salary") + def _compute_l10n_in_performance_bonus(self): + for contract in self: + contract.l10n_in_performance_bonus = contract.l10n_in_basic_salary * contract.l10n_in_performance_bonus_percent + + def _inverse_l10n_in_performance_bonus(self): + for contract in self: + contract.l10n_in_performance_bonus_percent = contract.l10n_in_performance_bonus / contract.l10n_in_basic_salary if contract.l10n_in_basic_salary else 0 + + @api.depends("l10n_in_leave_travel_allowance_percent", "l10n_in_basic_salary") + def _compute_l10n_in_leave_travel_allowance(self): + for contract in self: + contract.l10n_in_leave_travel_allowance = contract.l10n_in_basic_salary * contract.l10n_in_leave_travel_allowance_percent + + def _inverse_l10n_in_leave_travel_allowance(self): + for contract in self: + contract.l10n_in_leave_travel_allowance_percent = contract.l10n_in_leave_travel_allowance / contract.l10n_in_basic_salary if contract.l10n_in_basic_salary else 0 + + @api.depends('wage', 'l10n_in_leave_allowance_per_day_percent', 'l10n_in_leave_days') + def _compute_leave_allowance(self): + for contract in self: + contract.l10n_in_leave_allowance = contract.wage * contract.l10n_in_leave_allowance_per_day_percent * contract.l10n_in_leave_days + + def _inverse_leave_allowance(self): + for contract in self: + if contract.l10n_in_basic_salary and contract.l10n_in_leave_days: + contract.l10n_in_leave_allowance_percent = contract.l10n_in_leave_allowance / contract.wage if contract.wage else 0 + contract.l10n_in_leave_allowance_per_day_percent = contract.l10n_in_leave_allowance / (contract.wage * contract.l10n_in_leave_days) if contract.wage else 0 + + @api.depends("l10n_in_gratuity", "l10n_in_basic_salary") + def _compute_l10n_in_gratuity_percent(self): + for contract in self: + contract.l10n_in_gratuity_percent = contract.l10n_in_gratuity / contract.l10n_in_basic_salary if contract.l10n_in_basic_salary else 0 + + def _inverse_l10n_in_gratuity_percent(self): + for contract in self: + contract.l10n_in_gratuity = contract.l10n_in_basic_salary * contract.l10n_in_gratuity_percent + + @api.depends("wage", "l10n_in_basic_salary", "l10n_in_house_rent_allowance", "l10n_in_standard_allowance", "l10n_in_performance_bonus", "l10n_in_leave_travel_allowance", "l10n_in_leave_allowance", "l10n_in_gratuity") + def _compute_l10n_in_supplementary_allowance(self): + for contract in self: + total_allowance = sum([ + contract.l10n_in_basic_salary, + contract.l10n_in_house_rent_allowance, + contract.l10n_in_standard_allowance, + contract.l10n_in_performance_bonus, + contract.l10n_in_leave_travel_allowance, + contract.l10n_in_leave_allowance, + contract.l10n_in_gratuity + ]) + if contract.wage: + contract.l10n_in_supplementary_allowance = contract.wage - total_allowance + + def _inverse_l10n_in_supplementary_allowance(self): + for contract in self: + contract.l10n_in_supplementary_allowance_percent = contract.l10n_in_supplementary_allowance / contract.wage if contract.wage else 0 diff --git a/l10n_in_hr_payroll_extension/models/res_company.py b/l10n_in_hr_payroll_extension/models/res_company.py new file mode 100644 index 00000000000..20a1185d9cc --- /dev/null +++ b/l10n_in_hr_payroll_extension/models/res_company.py @@ -0,0 +1,33 @@ +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = 'res.company' + + l10n_in_org_tax_details = fields.Boolean(string="Organisation Tax Details") + l10n_in_org_pan_number = fields.Char(string="PAN Number") + l10n_in_org_tan_number = fields.Char(string="TAN Number") + l10n_in_org_tds_circle = fields.Char(string="TDS Circle/AO Code") + + l10n_in_pf_employer_identification = fields.Char(string="Employer Identification") + l10n_in_pf_employee_contribution = fields.Float(string="Employee Contribution", default=12) + l10n_in_pf_employer_contribution = fields.Float(string="Employer Contribution", default=12) + + l10n_in_professional_tax_number = fields.Char(string="Professional Tax Number") + + l10n_in_esic_ip = fields.Char(string="ESIC IP") + l10n_in_esic_employee_contribution = fields.Float(string="Employee Contribution", default=0.75) + l10n_in_esic_employer_contribution = fields.Float(string="Employer Contribution", default=3.25) + + l10n_in_lwf_employee_contribution = fields.Monetary(string="Employee Contribution", currency_field="currency_id", default=6) + l10n_in_lwf_employer_contribution = fields.Monetary(string="Employer Contribution", currency_field="currency_id", default=12) + + # Salary Structure + l10n_in_basic_salary_percent = fields.Float(string="Basic Salary Percentage", default=0.50) + l10n_in_house_rent_allowance_percent = fields.Float(string="House Rent Allowance", help="You can define 50% for metro city and 40% for non-metro city.", default=0.50) + l10n_in_standard_allowance = fields.Float(string="Standard Allowance", default=4167) + l10n_in_performance_bonus_percent = fields.Float(string="Performance Bonus", default=0.20) + l10n_in_leave_travel_allowance_percent = fields.Float(string="Leave Travel Allowance", default=0.20) + l10n_in_leave_days = fields.Float(string="Leave Days", default=1) + l10n_in_gratuity = fields.Float(string="Gratuity") + l10n_in_supplementary_allowance = fields.Float(string="Supplementary Allowance") diff --git a/l10n_in_hr_payroll_extension/models/res_config_settings.py b/l10n_in_hr_payroll_extension/models/res_config_settings.py new file mode 100644 index 00000000000..a90def5f03e --- /dev/null +++ b/l10n_in_hr_payroll_extension/models/res_config_settings.py @@ -0,0 +1,38 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + currency_id = fields.Many2one("res.currency", default=lambda self: self.env.company.currency_id) + + l10n_in_org_tax_details = fields.Boolean(related='company_id.l10n_in_org_tax_details', readonly=False) + l10n_in_org_pan_number = fields.Char(related='company_id.l10n_in_org_pan_number', readonly=False) + l10n_in_org_tan_number = fields.Char(related='company_id.l10n_in_org_tan_number', readonly=False) + l10n_in_org_tds_circle = fields.Char(related='company_id.l10n_in_org_tds_circle', readonly=False) + + l10n_in_is_provident_fund = fields.Boolean(string="Employee's Provident Fund", default=True) + l10n_in_pf_employer_identification = fields.Char(related='company_id.l10n_in_pf_employer_identification', readonly=False) + l10n_in_pf_employee_contribution = fields.Float(related="company_id.l10n_in_pf_employee_contribution", readonly=False) + l10n_in_pf_employer_contribution = fields.Float(related="company_id.l10n_in_pf_employer_contribution", readonly=False) + + l10n_in_is_professional_tax = fields.Boolean(string="Professional Tax", default=True) + l10n_in_professional_tax_number = fields.Char(related='company_id.l10n_in_professional_tax_number', readonly=False) + + l10n_in_is_esic = fields.Boolean(string="Employee's State Insurance Corporation", default=True) + l10n_in_esic_ip = fields.Char(string="ESIC IP", related='company_id.l10n_in_esic_ip', readonly=False) + l10n_in_esic_employee_contribution = fields.Float(related="company_id.l10n_in_esic_employee_contribution", readonly=False) + l10n_in_esic_employer_contribution = fields.Float(related="company_id.l10n_in_esic_employer_contribution", readonly=False) + + l10n_in_is_lwf = fields.Boolean(string="Labour Welfare Fund", default=True) + l10n_in_lwf_employee_contribution = fields.Monetary(related="company_id.l10n_in_lwf_employee_contribution", readonly=False) + l10n_in_lwf_employer_contribution = fields.Monetary(related="company_id.l10n_in_lwf_employer_contribution", readonly=False) + + l10n_in_basic_salary_percent = fields.Float(related="company_id.l10n_in_basic_salary_percent", readonly=False) + l10n_in_house_rent_allowance_percent = fields.Float(related="company_id.l10n_in_house_rent_allowance_percent", readonly=False) + l10n_in_standard_allowance = fields.Float(related="company_id.l10n_in_standard_allowance", readonly=False) + l10n_in_performance_bonus_percent = fields.Float(related="company_id.l10n_in_performance_bonus_percent", readonly=False) + l10n_in_leave_travel_allowance_percent = fields.Float(related="company_id.l10n_in_leave_travel_allowance_percent", readonly=False) + l10n_in_leave_days = fields.Float(related="company_id.l10n_in_leave_days", readonly=False) + l10n_in_gratuity = fields.Float(related="company_id.l10n_in_gratuity", readonly=False) + l10n_in_supplementary_allowance = fields.Float(related="company_id.l10n_in_supplementary_allowance", readonly=False) diff --git a/l10n_in_hr_payroll_extension/tests/__init__.py b/l10n_in_hr_payroll_extension/tests/__init__.py new file mode 100644 index 00000000000..0894c057839 --- /dev/null +++ b/l10n_in_hr_payroll_extension/tests/__init__.py @@ -0,0 +1 @@ +from . import test_hr_contract diff --git a/l10n_in_hr_payroll_extension/tests/test_hr_contract.py b/l10n_in_hr_payroll_extension/tests/test_hr_contract.py new file mode 100644 index 00000000000..6c756030053 --- /dev/null +++ b/l10n_in_hr_payroll_extension/tests/test_hr_contract.py @@ -0,0 +1,29 @@ +from odoo.tests.common import TransactionCase +from odoo.tests import tagged + + +@tagged('post_install', '-at_install') +class TestHrContract(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.contract = cls.env['hr.contract'].create({ + 'name': "Test Contract", + 'wage': 100000, + 'l10n_in_basic_salary_percent': 50, + 'l10n_in_house_rent_allowance_percent': 50, + 'l10n_in_standard_allowance': 4167, + 'l10n_in_performance_bonus_percent': 20, + 'l10n_in_leave_travel_allowance_percent': 20, + 'l10n_in_gratuity': 250, + }) + + def test_salary_component_amounts(self): + self.assertEqual(self.contract.l10n_in_basic_salary, 50000, "50% of 100000 should be 50000") + self.assertEqual(self.contract.l10n_in_house_rent_allowance, 25000, "50% of 50000(Basic Salary) should be 25000") + self.assertEqual(self.contract.l10n_in_standard_allowance_percent, 4.167, "4167 is a 4.17% of 100000(Wage)") + self.assertEqual(self.contract.l10n_in_performance_bonus, 10000, "20% of 50000(Basic Salary) should be 10000") + self.assertEqual(self.contract.l10n_in_leave_travel_allowance, 10000, "20% of 50000(Basic Salary) should be 10000") + self.assertEqual(self.contract.l10n_in_gratuity_percent, 0.50, "250 is a 0.50% of 50000(Basic Salary)") + self.assertEqual(self.contract.l10n_in_supplementary_allowance, 583, "supplementary should be remaining amount from wage") diff --git a/l10n_in_hr_payroll_extension/views/hr_contract_views.xml b/l10n_in_hr_payroll_extension/views/hr_contract_views.xml new file mode 100644 index 00000000000..32b877eb05e --- /dev/null +++ b/l10n_in_hr_payroll_extension/views/hr_contract_views.xml @@ -0,0 +1,138 @@ + + + hr.contract.form.in.inherit + hr.contract + + + + 1 + + + 1 + + + + +
+ + + + +
+
+
+
+
diff --git a/l10n_in_hr_payroll_extension/views/res_config_settings_views.xml b/l10n_in_hr_payroll_extension/views/res_config_settings_views.xml new file mode 100644 index 00000000000..c36f6bbdb24 --- /dev/null +++ b/l10n_in_hr_payroll_extension/views/res_config_settings_views.xml @@ -0,0 +1,156 @@ + + + res.config.settings.view.form.inherited.l10n_in_hr_payroll + res.config.settings + + + + + + + + + + + + + +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+ + +
+
+
+
+
+