Initial commit
This commit is contained in:
306
fusion_payroll/models/hr_tax_remittance.py
Normal file
306
fusion_payroll/models/hr_tax_remittance.py
Normal file
@@ -0,0 +1,306 @@
|
||||
# -*- 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
|
||||
Reference in New Issue
Block a user