307 lines
9.4 KiB
Python
307 lines
9.4 KiB
Python
# -*- 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
|