# -*- coding: utf-8 -*- from datetime import date, timedelta from dateutil.relativedelta import relativedelta from odoo import models, fields, api from odoo.exceptions import UserError class HrTaxRemittance(models.Model): _name = 'hr.tax.remittance' _description = 'Payroll Tax Remittance' _order = 'period_start desc' _inherit = ['mail.thread', 'mail.activity.mixin'] STATE_SELECTION = [ ('draft', 'Draft'), ('pending', 'Pending'), ('due', 'Due Soon'), ('past_due', 'Past Due'), ('paid', 'Paid'), ] PERIOD_TYPE = [ ('monthly', 'Monthly'), ('quarterly', 'Quarterly'), ] name = fields.Char( string='Reference', required=True, copy=False, default=lambda self: self.env['ir.sequence'].next_by_code('hr.tax.remittance') or 'New', ) company_id = fields.Many2one( 'res.company', string='Company', required=True, default=lambda self: self.env.company, ) currency_id = fields.Many2one( related='company_id.currency_id', string='Currency', ) state = fields.Selection( selection=STATE_SELECTION, string='Status', default='draft', tracking=True, compute='_compute_state', store=True, ) # === Period Information === period_type = fields.Selection( selection=PERIOD_TYPE, string='Period Type', default='monthly', ) period_start = fields.Date( string='Period Start', required=True, ) period_end = fields.Date( string='Period End', required=True, ) due_date = fields.Date( string='Due Date', required=True, help='CRA remittance due date (15th of following month for regular remitters)', ) # === CPP Amounts === cpp_employee = fields.Monetary( string='CPP Employee', currency_field='currency_id', help='Total employee CPP contributions for period', ) cpp_employer = fields.Monetary( string='CPP Employer', currency_field='currency_id', help='Total employer CPP contributions for period', ) cpp2_employee = fields.Monetary( string='CPP2 Employee', currency_field='currency_id', help='Total employee CPP2 (second CPP) contributions for period', ) cpp2_employer = fields.Monetary( string='CPP2 Employer', currency_field='currency_id', help='Total employer CPP2 contributions for period', ) # === EI Amounts === ei_employee = fields.Monetary( string='EI Employee', currency_field='currency_id', help='Total employee EI premiums for period', ) ei_employer = fields.Monetary( string='EI Employer', currency_field='currency_id', help='Total employer EI premiums (1.4x employee) for period', ) # === Tax Amounts === income_tax = fields.Monetary( string='Income Tax', currency_field='currency_id', help='Total federal + provincial income tax withheld for period', ) # === Totals === total = fields.Monetary( string='Total Remittance', currency_field='currency_id', compute='_compute_total', store=True, ) # === Payment Information === payment_date = fields.Date( string='Payment Date', tracking=True, ) payment_reference = fields.Char( string='Payment Reference', help='Bank reference or confirmation number', ) payment_method = fields.Selection([ ('cra_my_business', 'CRA My Business Account'), ('bank_payment', 'Bank Payment'), ('cheque', 'Cheque'), ], string='Payment Method') # === Linked Payslips === payslip_ids = fields.Many2many( 'hr.payslip', string='Related Payslips', help='Payslips included in this remittance period', ) payslip_count = fields.Integer( string='Payslip Count', compute='_compute_payslip_count', ) @api.depends('cpp_employee', 'cpp_employer', 'cpp2_employee', 'cpp2_employer', 'ei_employee', 'ei_employer', 'income_tax') def _compute_total(self): for rec in self: rec.total = ( rec.cpp_employee + rec.cpp_employer + rec.cpp2_employee + rec.cpp2_employer + rec.ei_employee + rec.ei_employer + rec.income_tax ) @api.depends('due_date', 'payment_date') def _compute_state(self): today = date.today() for rec in self: if rec.payment_date: rec.state = 'paid' elif not rec.due_date: rec.state = 'draft' elif rec.due_date < today: rec.state = 'past_due' elif rec.due_date <= today + timedelta(days=7): rec.state = 'due' else: rec.state = 'pending' def _compute_payslip_count(self): for rec in self: rec.payslip_count = len(rec.payslip_ids) def action_calculate_amounts(self): """Calculate remittance amounts from payslips in the period""" self.ensure_one() # Find all confirmed payslips in the period payslips = self.env['hr.payslip'].search([ ('company_id', '=', self.company_id.id), ('state', 'in', ['validated', 'paid']), ('date_from', '>=', self.period_start), ('date_to', '<=', self.period_end), ]) if not payslips: raise UserError('No confirmed payslips found for this period.') # Sum up amounts by rule code cpp_ee = cpp_er = cpp2_ee = cpp2_er = 0 ei_ee = ei_er = 0 income_tax = 0 for payslip in payslips: for line in payslip.line_ids: code = line.code amount = abs(line.total) if code == 'CPP_EE': cpp_ee += amount elif code == 'CPP_ER': cpp_er += amount elif code == 'CPP2_EE': cpp2_ee += amount elif code == 'CPP2_ER': cpp2_er += amount elif code == 'EI_EE': ei_ee += amount elif code == 'EI_ER': ei_er += amount elif code in ('FED_TAX', 'PROV_TAX'): income_tax += amount self.write({ 'cpp_employee': cpp_ee, 'cpp_employer': cpp_er, 'cpp2_employee': cpp2_ee, 'cpp2_employer': cpp2_er, 'ei_employee': ei_ee, 'ei_employer': ei_er, 'income_tax': income_tax, 'payslip_ids': [(6, 0, payslips.ids)], }) return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': 'Amounts Calculated', 'message': f'Calculated from {len(payslips)} payslips. Total: ${self.total:,.2f}', 'type': 'success', } } def action_mark_paid(self): """Mark remittance as paid""" self.ensure_one() if not self.payment_date: self.payment_date = date.today() self.state = 'paid' def action_view_payslips(self): """View related payslips""" self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': 'Payslips', 'res_model': 'hr.payslip', 'view_mode': 'list,form', 'domain': [('id', 'in', self.payslip_ids.ids)], } @api.model def _get_payment_frequency_from_settings(self, company_id=None): """Get payment frequency from payroll settings.""" if not company_id: company_id = self.env.company.id settings = self.env['payroll.config.settings'].get_settings(company_id) return settings.federal_tax_payment_frequency or 'monthly' def create_monthly_remittance(self, year, month, company_id=None): """Create a monthly remittance record""" if not company_id: company_id = self.env.company.id # Get settings for payment frequency and CRA info settings = self.env['payroll.config.settings'].get_settings(company_id) payment_freq = settings.federal_tax_payment_frequency or 'monthly' # Calculate period dates period_start = date(year, month, 1) if month == 12: period_end = date(year + 1, 1, 1) - timedelta(days=1) due_date = date(year + 1, 1, 15) else: period_end = date(year, month + 1, 1) - timedelta(days=1) due_date = date(year, month + 1, 15) # Create the remittance remittance = self.create({ 'company_id': company_id, 'period_type': payment_freq if payment_freq in ['monthly', 'quarterly'] else 'monthly', 'period_start': period_start, 'period_end': period_end, 'due_date': due_date, }) # Calculate amounts remittance.action_calculate_amounts() return remittance class HrTaxRemittanceSequence(models.Model): """Create sequence for tax remittance""" _name = 'hr.tax.remittance.sequence' _description = 'Tax Remittance Sequence Setup' _auto = False def init(self): # This will be handled by ir.sequence data instead pass