diff --git a/addons_extensions/employee_it_declaration/__manifest__.py b/addons_extensions/employee_it_declaration/__manifest__.py index 5a6e3ee94..504b2607a 100644 --- a/addons_extensions/employee_it_declaration/__manifest__.py +++ b/addons_extensions/employee_it_declaration/__manifest__.py @@ -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' + ], +} diff --git a/addons_extensions/employee_it_declaration/models/it_tax_statement_wiz.py b/addons_extensions/employee_it_declaration/models/it_tax_statement_wiz.py index eecf2c1fd..574227e74 100644 --- a/addons_extensions/employee_it_declaration/models/it_tax_statement_wiz.py +++ b/addons_extensions/employee_it_declaration/models/it_tax_statement_wiz.py @@ -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', diff --git a/addons_extensions/employee_it_declaration/models/slab_master.py b/addons_extensions/employee_it_declaration/models/slab_master.py index 79c426bb5..359fc2fe8 100644 --- a/addons_extensions/employee_it_declaration/models/slab_master.py +++ b/addons_extensions/employee_it_declaration/models/slab_master.py @@ -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' diff --git a/addons_extensions/employee_it_declaration/views/it_tax_menu_and_wizard_view.xml b/addons_extensions/employee_it_declaration/views/it_tax_menu_and_wizard_view.xml index 8fbeca178..597beb191 100644 --- a/addons_extensions/employee_it_declaration/views/it_tax_menu_and_wizard_view.xml +++ b/addons_extensions/employee_it_declaration/views/it_tax_menu_and_wizard_view.xml @@ -21,19 +21,19 @@
-