changes
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import tax_yearly_rates
|
||||
from . import tax_yearly_rate_line
|
||||
from . import hr_employee
|
||||
from . import hr_contract
|
||||
from . import hr_payslip
|
||||
from . import hr_roe
|
||||
from . import hr_tax_remittance
|
||||
|
||||
@@ -4,6 +4,7 @@ import base64
|
||||
import os
|
||||
import io
|
||||
from datetime import date
|
||||
from lxml import etree
|
||||
from odoo import models, fields, api, tools
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
@@ -15,16 +16,6 @@ class HrT4Summary(models.Model):
|
||||
_order = 'tax_year desc'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
def _get_pdf_text_coordinates(self):
|
||||
"""Get text overlay coordinates for flattened PDF
|
||||
Returns dict mapping field names to (x, y, font_size, font_name) tuples
|
||||
Coordinates are in points (1/72 inch), origin at bottom-left
|
||||
Reads from pdf.field.position model based on template type
|
||||
"""
|
||||
# Query configured positions from database for T4 Summary
|
||||
position_model = self.env['pdf.field.position']
|
||||
return position_model.get_coordinates_dict('T4 Summary')
|
||||
|
||||
STATE_SELECTION = [
|
||||
('draft', 'Draft'),
|
||||
('generated', 'Generated'),
|
||||
@@ -205,6 +196,9 @@ class HrT4Summary(models.Model):
|
||||
xml_filename = fields.Char(
|
||||
string='XML Filename',
|
||||
)
|
||||
transmitter_bn = fields.Char(string='Business Number (BN)')
|
||||
transmitter_name = fields.Char(string='Transmitter Name')
|
||||
contact_email = fields.Char(string='Contact Email')
|
||||
|
||||
# === Box 74: SIN of Proprietor ===
|
||||
proprietor_sin = fields.Char(
|
||||
@@ -270,7 +264,7 @@ class HrT4Summary(models.Model):
|
||||
|
||||
payslips = self.env['hr.payslip'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('state', 'in', ['validated', 'paid']),
|
||||
('state', 'in', ['done', 'paid']),
|
||||
('date_from', '>=', year_start),
|
||||
('date_to', '<=', year_end),
|
||||
])
|
||||
@@ -305,6 +299,8 @@ class HrT4Summary(models.Model):
|
||||
box_40_allowances = 0
|
||||
box_42_commissions = 0
|
||||
box_44_union_dues = 0
|
||||
rrsp = 0
|
||||
union_dues = 0
|
||||
|
||||
for ps in emp_payslips:
|
||||
# Process each payslip line
|
||||
@@ -357,8 +353,12 @@ class HrT4Summary(models.Model):
|
||||
ei_ee += amount
|
||||
elif code == 'EI_ER':
|
||||
ei_er += amount
|
||||
elif code in ('FED_TAX', 'PROV_TAX'):
|
||||
elif code in ('FED_TAX', 'PROV_TAX', 'OHP'):
|
||||
income_tax += amount
|
||||
elif code == 'RRSP':
|
||||
rrsp += amount
|
||||
elif code == 'UNION_DUES':
|
||||
union_dues += amount
|
||||
|
||||
# Add GROSS to employment income (Box 14)
|
||||
# GROSS already includes all taxable income (salary, overtime, bonus, allowances, commissions, etc.)
|
||||
@@ -387,7 +387,8 @@ class HrT4Summary(models.Model):
|
||||
# New boxes
|
||||
'box_40_taxable_benefits': box_40_allowances,
|
||||
'box_42_commissions': box_42_commissions,
|
||||
'box_44_union_dues': box_44_union_dues,
|
||||
'box_44_union_dues': box_44_union_dues + union_dues,
|
||||
'box_20_rpp': rrsp,
|
||||
})
|
||||
|
||||
self.state = 'generated'
|
||||
@@ -410,6 +411,166 @@ class HrT4Summary(models.Model):
|
||||
'filing_date': date.today(),
|
||||
})
|
||||
|
||||
def action_export_xml(self):
|
||||
"""Generate CRA T4 XML file (T619 format) for electronic filing"""
|
||||
self.ensure_one()
|
||||
nsmap = {
|
||||
None: 'http://www.cra-arc.gc.ca/enov/ol/interfaces/efile/partnership/t4'
|
||||
}
|
||||
root = etree.Element('Submission', nsmap=nsmap)
|
||||
|
||||
# T619 header
|
||||
t619 = etree.SubElement(root, 'T619')
|
||||
self._add_xml_element(t619, 'sbmt_ref_id', 'T4-%s-%s' % (self.tax_year, self.id))
|
||||
self._add_xml_element(t619, 'rpt_tcd', 'O')
|
||||
bn = self.transmitter_bn or self.cra_business_number or ''
|
||||
self._add_xml_element(t619, 'trnmtr_nbr', 'MM' + bn[:7].ljust(7, '0') if bn else 'MM0000000')
|
||||
self._add_xml_element(t619, 'trnmtr_tcd', '4')
|
||||
self._add_xml_element(t619, 'summ_cnt', '1')
|
||||
self._add_xml_element(t619, 'lang_cd', 'E')
|
||||
|
||||
trnmtr_nm = etree.SubElement(t619, 'TRNMTR_NM')
|
||||
self._add_xml_element(trnmtr_nm, 'l1_nm', self.transmitter_name or self.company_id.name or '')
|
||||
|
||||
company = self.company_id
|
||||
trnmtr_addr = etree.SubElement(t619, 'TRNMTR_ADDR')
|
||||
if company.street:
|
||||
self._add_xml_element(trnmtr_addr, 'addr_l1_txt', company.street)
|
||||
if company.city:
|
||||
self._add_xml_element(trnmtr_addr, 'cty_nm', company.city)
|
||||
if company.state_id:
|
||||
self._add_xml_element(trnmtr_addr, 'prov_cd', company.state_id.code)
|
||||
if company.zip:
|
||||
self._add_xml_element(trnmtr_addr, 'pstl_cd', company.zip)
|
||||
self._add_xml_element(trnmtr_addr, 'cntry_cd', 'CAN')
|
||||
|
||||
if self.contact_name or self.contact_phone or self.contact_email:
|
||||
cntc = etree.SubElement(t619, 'CNTC')
|
||||
self._add_xml_element(cntc, 'cntc_nm', self.contact_name)
|
||||
if self.contact_phone:
|
||||
phone = ''.join(filter(str.isdigit, self.contact_phone))
|
||||
if len(phone) >= 10:
|
||||
self._add_xml_element(cntc, 'cntc_area_cd', phone[:3])
|
||||
self._add_xml_element(cntc, 'cntc_phn_nbr', phone[3:10])
|
||||
self._add_xml_element(cntc, 'cntc_email_area', self.contact_email)
|
||||
|
||||
# T4Return
|
||||
t4_return = etree.SubElement(root, 'T4Return')
|
||||
|
||||
# T4Summary
|
||||
t4_summary = etree.SubElement(t4_return, 'T4Summary')
|
||||
bn15 = (self.transmitter_bn or self.cra_business_number or '')[:15]
|
||||
self._add_xml_element(t4_summary, 'bn', bn15)
|
||||
self._add_xml_element(t4_summary, 'tx_yr', str(self.tax_year))
|
||||
self._add_xml_element(t4_summary, 'slp_cnt', str(self.slip_count))
|
||||
|
||||
payr_nm = etree.SubElement(t4_summary, 'PAYR_NM')
|
||||
self._add_xml_element(payr_nm, 'l1_nm', company.name or '')
|
||||
|
||||
payr_addr = etree.SubElement(t4_summary, 'PAYR_ADDR')
|
||||
if company.street:
|
||||
self._add_xml_element(payr_addr, 'addr_l1_txt', company.street)
|
||||
if company.city:
|
||||
self._add_xml_element(payr_addr, 'cty_nm', company.city)
|
||||
if company.state_id:
|
||||
self._add_xml_element(payr_addr, 'prov_cd', company.state_id.code)
|
||||
if company.zip:
|
||||
self._add_xml_element(payr_addr, 'pstl_cd', company.zip)
|
||||
self._add_xml_element(payr_addr, 'cntry_cd', 'CAN')
|
||||
|
||||
t4_tamt = etree.SubElement(t4_summary, 'T4_TAMT')
|
||||
self._add_xml_amount(t4_tamt, 'tot_empt_incm_amt', self.total_employment_income)
|
||||
self._add_xml_amount(t4_tamt, 'tot_empe_cpp_amt', self.total_cpp_employee)
|
||||
self._add_xml_amount(t4_tamt, 'tot_empe_eip_amt', self.total_ei_employee)
|
||||
self._add_xml_amount(t4_tamt, 'tot_itx_ddct_amt', self.total_income_tax)
|
||||
self._add_xml_amount(t4_tamt, 'tot_empr_cpp_amt', self.total_cpp_employer)
|
||||
self._add_xml_amount(t4_tamt, 'tot_empr_eip_amt', self.total_ei_employer)
|
||||
|
||||
# T4Slips
|
||||
for slip in self.slip_ids:
|
||||
slip_elem = etree.SubElement(t4_return, 'T4Slip')
|
||||
emp = slip.employee_id
|
||||
|
||||
empe_nm = etree.SubElement(slip_elem, 'EMPE_NM')
|
||||
name_parts = (emp.name or '').split(' ', 1)
|
||||
self._add_xml_element(empe_nm, 'snm', name_parts[-1] if len(name_parts) > 1 else emp.name or '')
|
||||
self._add_xml_element(empe_nm, 'gvn_nm', name_parts[0] if len(name_parts) > 1 else '')
|
||||
|
||||
if hasattr(emp, 'private_street') and (emp.private_street or emp.private_city):
|
||||
empe_addr = etree.SubElement(slip_elem, 'EMPE_ADDR')
|
||||
if emp.private_street:
|
||||
self._add_xml_element(empe_addr, 'addr_l1_txt', emp.private_street)
|
||||
if emp.private_city:
|
||||
self._add_xml_element(empe_addr, 'cty_nm', emp.private_city)
|
||||
if emp.private_state_id:
|
||||
self._add_xml_element(empe_addr, 'prov_cd', emp.private_state_id.code)
|
||||
if emp.private_zip:
|
||||
self._add_xml_element(empe_addr, 'pstl_cd', emp.private_zip)
|
||||
self._add_xml_element(empe_addr, 'cntry_cd', 'CAN')
|
||||
elif emp.home_street or emp.home_city:
|
||||
empe_addr = etree.SubElement(slip_elem, 'EMPE_ADDR')
|
||||
if emp.home_street:
|
||||
self._add_xml_element(empe_addr, 'addr_l1_txt', emp.home_street)
|
||||
if emp.home_city:
|
||||
self._add_xml_element(empe_addr, 'cty_nm', emp.home_city)
|
||||
if emp.home_province:
|
||||
self._add_xml_element(empe_addr, 'prov_cd', emp.home_province)
|
||||
if emp.home_postal_code:
|
||||
self._add_xml_element(empe_addr, 'pstl_cd', emp.home_postal_code)
|
||||
self._add_xml_element(empe_addr, 'cntry_cd', 'CAN')
|
||||
|
||||
self._add_xml_element(slip_elem, 'sin', slip.sin_number or '')
|
||||
self._add_xml_element(slip_elem, 'empe_nbr', str(emp.employee_number or emp.id))
|
||||
province = emp.home_province or ''
|
||||
self._add_xml_element(slip_elem, 'prov_cd', province)
|
||||
|
||||
t4_amt = etree.SubElement(slip_elem, 'T4_AMT')
|
||||
self._add_xml_amount(t4_amt, 'empt_incm_amt', slip.employment_income)
|
||||
self._add_xml_amount(t4_amt, 'cpp_cntrb_amt', slip.cpp_employee)
|
||||
self._add_xml_amount(t4_amt, 'empe_eip_amt', slip.ei_employee)
|
||||
self._add_xml_amount(t4_amt, 'itx_ddct_amt', slip.income_tax)
|
||||
self._add_xml_amount(t4_amt, 'ei_insu_earn_amt', slip.ei_insurable_earnings)
|
||||
self._add_xml_amount(t4_amt, 'cpp_qpp_pnsn_amt', slip.cpp_pensionable_earnings)
|
||||
self._add_xml_amount(t4_amt, 'unn_dues_amt', slip.box_44_union_dues)
|
||||
|
||||
xml_bytes = etree.tostring(root, xml_declaration=True, encoding='UTF-8', pretty_print=True)
|
||||
filename = 'T4_%s_%s.xml' % (self.tax_year, (company.name or 'Company').replace(' ', '_'))
|
||||
|
||||
self.write({
|
||||
'xml_file': base64.b64encode(xml_bytes),
|
||||
'xml_filename': filename,
|
||||
})
|
||||
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': filename,
|
||||
'type': 'binary',
|
||||
'datas': base64.b64encode(xml_bytes),
|
||||
'res_model': self._name,
|
||||
'res_id': self.id,
|
||||
'mimetype': 'application/xml',
|
||||
})
|
||||
|
||||
self.message_post(
|
||||
body='T4 XML generated: <strong>%s</strong>' % filename,
|
||||
attachment_ids=[attachment.id],
|
||||
)
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/web/content/%s?download=true' % attachment.id,
|
||||
'target': 'self',
|
||||
}
|
||||
|
||||
def _add_xml_element(self, parent, tag, value):
|
||||
if value:
|
||||
elem = etree.SubElement(parent, tag)
|
||||
elem.text = str(value)
|
||||
|
||||
def _add_xml_amount(self, parent, tag, amount):
|
||||
if amount:
|
||||
elem = etree.SubElement(parent, tag)
|
||||
elem.text = '%.2f' % amount
|
||||
|
||||
def _get_pdf_text_coordinates(self):
|
||||
"""Get text overlay coordinates for flattened PDF
|
||||
Returns dict mapping field names to (x, y, font_size, font_name) tuples
|
||||
@@ -1005,6 +1166,13 @@ class HrT4Slip(models.Model):
|
||||
currency_field='currency_id',
|
||||
)
|
||||
|
||||
# === Box 20: RPP/RRSP Contributions ===
|
||||
box_20_rpp = fields.Monetary(
|
||||
string='Box 20: RPP/RRSP',
|
||||
currency_field='currency_id',
|
||||
help='Registered Pension Plan or RRSP contributions',
|
||||
)
|
||||
|
||||
# === T4 Dental Benefits Code ===
|
||||
t4_dental_code = fields.Selection(
|
||||
related='employee_id.t4_dental_code',
|
||||
|
||||
@@ -4,6 +4,7 @@ import base64
|
||||
import os
|
||||
import io
|
||||
from datetime import date
|
||||
from lxml import etree
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
from odoo import tools
|
||||
@@ -128,6 +129,15 @@ class HrT4ASummary(models.Model):
|
||||
string='Telephone',
|
||||
)
|
||||
|
||||
# === Transmitter Information ===
|
||||
transmitter_bn = fields.Char(string='Business Number (BN)')
|
||||
transmitter_name = fields.Char(string='Transmitter Name')
|
||||
contact_email = fields.Char(string='Contact Email')
|
||||
|
||||
# === XML Export ===
|
||||
xml_file = fields.Binary(string='XML File', attachment=True)
|
||||
xml_filename = fields.Char(string='XML Filename')
|
||||
|
||||
# === Filing Information ===
|
||||
filing_date = fields.Date(
|
||||
string='Filing Date',
|
||||
@@ -158,6 +168,123 @@ class HrT4ASummary(models.Model):
|
||||
'filing_date': date.today(),
|
||||
})
|
||||
|
||||
def action_export_xml(self):
|
||||
"""Generate CRA T4A XML file (T619 format) for electronic filing"""
|
||||
self.ensure_one()
|
||||
nsmap = {
|
||||
None: 'http://www.cra-arc.gc.ca/enov/ol/interfaces/efile/partnership/t4a'
|
||||
}
|
||||
root = etree.Element('Submission', nsmap=nsmap)
|
||||
|
||||
t619 = etree.SubElement(root, 'T619')
|
||||
self._add_xml_element(t619, 'sbmt_ref_id', 'T4A-%s-%s' % (self.tax_year, self.id))
|
||||
self._add_xml_element(t619, 'rpt_tcd', 'O')
|
||||
bn = self.transmitter_bn or self.cra_business_number or ''
|
||||
self._add_xml_element(t619, 'trnmtr_nbr', 'MM' + bn[:7].ljust(7, '0') if bn else 'MM0000000')
|
||||
self._add_xml_element(t619, 'trnmtr_tcd', '4')
|
||||
self._add_xml_element(t619, 'summ_cnt', '1')
|
||||
self._add_xml_element(t619, 'lang_cd', 'E')
|
||||
|
||||
trnmtr_nm = etree.SubElement(t619, 'TRNMTR_NM')
|
||||
self._add_xml_element(trnmtr_nm, 'l1_nm', self.transmitter_name or self.company_id.name or '')
|
||||
|
||||
company = self.company_id
|
||||
trnmtr_addr = etree.SubElement(t619, 'TRNMTR_ADDR')
|
||||
if company.street:
|
||||
self._add_xml_element(trnmtr_addr, 'addr_l1_txt', company.street)
|
||||
if company.city:
|
||||
self._add_xml_element(trnmtr_addr, 'cty_nm', company.city)
|
||||
if company.state_id:
|
||||
self._add_xml_element(trnmtr_addr, 'prov_cd', company.state_id.code)
|
||||
if company.zip:
|
||||
self._add_xml_element(trnmtr_addr, 'pstl_cd', company.zip)
|
||||
self._add_xml_element(trnmtr_addr, 'cntry_cd', 'CAN')
|
||||
|
||||
if self.contact_name or self.contact_phone or self.contact_email:
|
||||
cntc = etree.SubElement(t619, 'CNTC')
|
||||
self._add_xml_element(cntc, 'cntc_nm', self.contact_name)
|
||||
if self.contact_phone:
|
||||
phone = ''.join(filter(str.isdigit, self.contact_phone))
|
||||
if len(phone) >= 10:
|
||||
self._add_xml_element(cntc, 'cntc_area_cd', phone[:3])
|
||||
self._add_xml_element(cntc, 'cntc_phn_nbr', phone[3:10])
|
||||
self._add_xml_element(cntc, 'cntc_email_area', self.contact_email)
|
||||
|
||||
t4a_return = etree.SubElement(root, 'T4AReturn')
|
||||
|
||||
t4a_summary = etree.SubElement(t4a_return, 'T4ASummary')
|
||||
bn15 = (self.transmitter_bn or self.cra_business_number or '')[:15]
|
||||
self._add_xml_element(t4a_summary, 'bn', bn15)
|
||||
self._add_xml_element(t4a_summary, 'tx_yr', str(self.tax_year))
|
||||
self._add_xml_element(t4a_summary, 'slp_cnt', str(self.slip_count))
|
||||
|
||||
payr_nm = etree.SubElement(t4a_summary, 'PAYR_NM')
|
||||
self._add_xml_element(payr_nm, 'l1_nm', company.name or '')
|
||||
|
||||
payr_addr = etree.SubElement(t4a_summary, 'PAYR_ADDR')
|
||||
if company.street:
|
||||
self._add_xml_element(payr_addr, 'addr_l1_txt', company.street)
|
||||
if company.city:
|
||||
self._add_xml_element(payr_addr, 'cty_nm', company.city)
|
||||
if company.state_id:
|
||||
self._add_xml_element(payr_addr, 'prov_cd', company.state_id.code)
|
||||
if company.zip:
|
||||
self._add_xml_element(payr_addr, 'pstl_cd', company.zip)
|
||||
self._add_xml_element(payr_addr, 'cntry_cd', 'CAN')
|
||||
|
||||
t4a_tamt = etree.SubElement(t4a_summary, 'T4A_TAMT')
|
||||
self._add_xml_amount(t4a_tamt, 'tot_pens_spran_amt', self.total_box_016)
|
||||
self._add_xml_amount(t4a_tamt, 'tot_lsp_amt', self.total_box_018)
|
||||
self._add_xml_amount(t4a_tamt, 'tot_self_empl_cmsn_amt', self.total_box_020)
|
||||
self._add_xml_amount(t4a_tamt, 'tot_annty_amt', self.total_box_024)
|
||||
|
||||
for t4a in self.slip_ids:
|
||||
slip_elem = etree.SubElement(t4a_return, 'T4ASlip')
|
||||
self._add_xml_element(slip_elem, 'rcpnt_nm', t4a.recipient_name or '')
|
||||
self._add_xml_element(slip_elem, 'sin', (t4a.recipient_sin or '').replace('-', '').replace(' ', ''))
|
||||
if t4a.recipient_account_number:
|
||||
self._add_xml_element(slip_elem, 'rcpnt_bn', t4a.recipient_account_number)
|
||||
|
||||
t4a_amt = etree.SubElement(slip_elem, 'T4A_AMT')
|
||||
self._add_xml_amount(t4a_amt, 'pens_spran_amt', t4a.box_016_pension)
|
||||
self._add_xml_amount(t4a_amt, 'lsp_amt', t4a.box_018_lump_sum)
|
||||
self._add_xml_amount(t4a_amt, 'self_empl_cmsn_amt', t4a.box_020_commissions)
|
||||
self._add_xml_amount(t4a_amt, 'annty_amt', t4a.box_024_annuities)
|
||||
self._add_xml_amount(t4a_amt, 'fees_svc_amt', t4a.box_048_fees)
|
||||
|
||||
xml_bytes = etree.tostring(root, xml_declaration=True, encoding='UTF-8', pretty_print=True)
|
||||
filename = 'T4A_%s_%s.xml' % (self.tax_year, (company.name or 'Company').replace(' ', '_'))
|
||||
|
||||
self.write({
|
||||
'xml_file': base64.b64encode(xml_bytes),
|
||||
'xml_filename': filename,
|
||||
})
|
||||
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': filename,
|
||||
'type': 'binary',
|
||||
'datas': base64.b64encode(xml_bytes),
|
||||
'res_model': self._name,
|
||||
'res_id': self.id,
|
||||
'mimetype': 'application/xml',
|
||||
})
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/web/content/%s?download=true' % attachment.id,
|
||||
'target': 'self',
|
||||
}
|
||||
|
||||
def _add_xml_element(self, parent, tag, value):
|
||||
if value:
|
||||
elem = etree.SubElement(parent, tag)
|
||||
elem.text = str(value)
|
||||
|
||||
def _add_xml_amount(self, parent, tag, amount):
|
||||
if amount:
|
||||
elem = etree.SubElement(parent, tag)
|
||||
elem.text = '%.2f' % amount
|
||||
|
||||
|
||||
class HrT4ASlip(models.Model):
|
||||
"""T4A Slip - One per recipient per tax year"""
|
||||
@@ -486,16 +613,17 @@ class HrT4ASlip(models.Model):
|
||||
})
|
||||
|
||||
# Post to chatter
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': filename,
|
||||
'type': 'binary',
|
||||
'datas': pdf_data,
|
||||
'res_model': self._name,
|
||||
'res_id': self.id,
|
||||
'mimetype': 'application/pdf',
|
||||
})
|
||||
self.message_post(
|
||||
body=f'T4A PDF generated: <strong>{filename}</strong>',
|
||||
attachment_ids=[(0, 0, {
|
||||
'name': filename,
|
||||
'type': 'binary',
|
||||
'datas': pdf_data,
|
||||
'res_model': self._name,
|
||||
'res_id': self.id,
|
||||
'mimetype': 'application/pdf',
|
||||
})],
|
||||
attachment_ids=[attachment.id],
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -208,9 +208,16 @@ class HrPayslip(models.Model):
|
||||
('date_to', '<=', payslip.date_to),
|
||||
('state', 'in', ['done', 'paid']),
|
||||
]
|
||||
# Include current payslip if it's in draft/verify state
|
||||
if payslip.state in ['draft', 'verify']:
|
||||
domain = ['|', ('id', '=', payslip.id)] + domain
|
||||
domain = [
|
||||
'|',
|
||||
('id', '=', payslip.id),
|
||||
'&', '&', '&',
|
||||
('employee_id', '=', payslip.employee_id.id),
|
||||
('date_from', '>=', year_start),
|
||||
('date_to', '<=', payslip.date_to),
|
||||
('state', 'in', ['done', 'paid']),
|
||||
]
|
||||
|
||||
ytd_payslips = self.search(domain)
|
||||
|
||||
@@ -229,13 +236,13 @@ class HrPayslip(models.Model):
|
||||
# Sum up specific rule amounts
|
||||
for line in slip.line_ids:
|
||||
code = line.code or ''
|
||||
if code == 'CPP':
|
||||
if code == 'CPP_EE':
|
||||
ytd_cpp += abs(line.total or 0)
|
||||
elif code == 'CPP2':
|
||||
elif code == 'CPP2_EE':
|
||||
ytd_cpp2 += abs(line.total or 0)
|
||||
elif code == 'EI':
|
||||
elif code == 'EI_EE':
|
||||
ytd_ei += abs(line.total or 0)
|
||||
elif code in ['FED_TAX', 'PROV_TAX', 'INCOME_TAX']:
|
||||
elif code in ['FED_TAX', 'PROV_TAX', 'OHP']:
|
||||
ytd_income_tax += abs(line.total or 0)
|
||||
|
||||
payslip.ytd_gross = ytd_gross
|
||||
@@ -278,13 +285,13 @@ class HrPayslip(models.Model):
|
||||
|
||||
for line in payslip.line_ids:
|
||||
code = line.code or ''
|
||||
if code == 'CPP':
|
||||
if code == 'CPP_EE':
|
||||
employee_cpp = abs(line.total or 0)
|
||||
elif code == 'CPP2':
|
||||
elif code == 'CPP2_EE':
|
||||
employee_cpp2 = abs(line.total or 0)
|
||||
elif code == 'EI':
|
||||
elif code == 'EI_EE':
|
||||
employee_ei = abs(line.total or 0)
|
||||
elif code in ['FED_TAX', 'PROV_TAX', 'INCOME_TAX']:
|
||||
elif code in ['FED_TAX', 'PROV_TAX', 'OHP']:
|
||||
employee_income_tax += abs(line.total or 0)
|
||||
|
||||
payslip.employee_cpp = employee_cpp
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class HrSalaryRuleCategory(models.Model):
|
||||
_inherit = 'hr.salary.rule.category'
|
||||
|
||||
# === Canadian Pension Plan (CPP) Configuration ===
|
||||
cpp_deduction_id = fields.Many2one(
|
||||
'tax.yearly.rates',
|
||||
string='Canadian Configuration Values',
|
||||
domain="[('ded_type', '=', 'cpp')]",
|
||||
help='Link to CPP yearly rates configuration',
|
||||
)
|
||||
cpp_date = fields.Date(
|
||||
string='CPP Date',
|
||||
related='cpp_deduction_id.cpp_date',
|
||||
readonly=True,
|
||||
)
|
||||
max_cpp = fields.Float(
|
||||
string='Maximum CPP',
|
||||
related='cpp_deduction_id.max_cpp',
|
||||
readonly=True,
|
||||
)
|
||||
emp_contribution_rate = fields.Float(
|
||||
string='Employee Contribution Rate',
|
||||
related='cpp_deduction_id.emp_contribution_rate',
|
||||
readonly=True,
|
||||
)
|
||||
employer_contribution_rate = fields.Float(
|
||||
string='Employer Contribution Rate',
|
||||
related='cpp_deduction_id.employer_contribution_rate',
|
||||
readonly=True,
|
||||
)
|
||||
exemption = fields.Float(
|
||||
string='Exemption Amount',
|
||||
related='cpp_deduction_id.exemption',
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
# === Employment Insurance (EI) Configuration ===
|
||||
ei_deduction_id = fields.Many2one(
|
||||
'tax.yearly.rates',
|
||||
string='Employment Insurance',
|
||||
domain="[('ded_type', '=', 'ei')]",
|
||||
help='Link to EI yearly rates configuration',
|
||||
)
|
||||
ei_date = fields.Date(
|
||||
string='EI Date',
|
||||
related='ei_deduction_id.ei_date',
|
||||
readonly=True,
|
||||
)
|
||||
ei_rate = fields.Float(
|
||||
string='EI Rate',
|
||||
related='ei_deduction_id.ei_rate',
|
||||
readonly=True,
|
||||
)
|
||||
ei_earnings = fields.Float(
|
||||
string='Maximum EI Earnings',
|
||||
related='ei_deduction_id.ei_earnings',
|
||||
readonly=True,
|
||||
)
|
||||
emp_ei_amount = fields.Float(
|
||||
string='Employee EI Amount',
|
||||
related='ei_deduction_id.emp_ei_amount',
|
||||
readonly=True,
|
||||
)
|
||||
employer_ei_amount = fields.Float(
|
||||
string='Employer EI Amount',
|
||||
related='ei_deduction_id.employer_ei_amount',
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
# === Federal Tax Configuration ===
|
||||
fed_tax_id = fields.Many2one(
|
||||
'tax.yearly.rates',
|
||||
string='Federal Tax',
|
||||
domain="[('tax_type', '=', 'federal')]",
|
||||
help='Link to Federal tax yearly rates configuration',
|
||||
)
|
||||
|
||||
# === Provincial Tax Configuration ===
|
||||
provincial_tax_id = fields.Many2one(
|
||||
'tax.yearly.rates',
|
||||
string='Provincial Tax',
|
||||
domain="[('tax_type', '=', 'provincial')]",
|
||||
help='Link to Provincial tax yearly rates configuration',
|
||||
)
|
||||
@@ -182,7 +182,7 @@ class HrTaxRemittance(models.Model):
|
||||
# Find all confirmed payslips in the period
|
||||
payslips = self.env['hr.payslip'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('state', 'in', ['validated', 'paid']),
|
||||
('state', 'in', ['done', 'paid']),
|
||||
('date_from', '>=', self.period_start),
|
||||
('date_to', '<=', self.period_end),
|
||||
])
|
||||
@@ -212,7 +212,7 @@ class HrTaxRemittance(models.Model):
|
||||
ei_ee += amount
|
||||
elif code == 'EI_ER':
|
||||
ei_er += amount
|
||||
elif code in ('FED_TAX', 'PROV_TAX'):
|
||||
elif code in ('FED_TAX', 'PROV_TAX', 'OHP'):
|
||||
income_tax += amount
|
||||
|
||||
self.write({
|
||||
|
||||
@@ -358,38 +358,40 @@ class PayrollCheque(models.Model):
|
||||
get_ytd_amount('GROSS') or 0)
|
||||
|
||||
# Get vacation pay
|
||||
vacation_pay_current = get_line_amount('VAC') or get_line_amount('VACATION') or 0
|
||||
vacation_pay_ytd = get_ytd_amount('VAC') or get_ytd_amount('VACATION') or 0
|
||||
vacation_pay_current = get_line_amount('VAC_PAY') or 0
|
||||
vacation_pay_ytd = get_ytd_amount('VAC_PAY') or 0
|
||||
|
||||
# Get stat holiday pay
|
||||
stat_pay_current = get_line_amount('STAT') or get_line_amount('STATHOLIDAY') or 0
|
||||
stat_pay_ytd = get_ytd_amount('STAT') or get_ytd_amount('STATHOLIDAY') or 0
|
||||
stat_pay_current = get_line_amount('STAT_PAY') or 0
|
||||
stat_pay_ytd = get_ytd_amount('STAT_PAY') or 0
|
||||
|
||||
# Get taxes - these are negative in payslip, so use abs()
|
||||
# First try to get from payslip lines
|
||||
income_tax_current = abs(get_line_amount('FIT') or get_line_amount('INCOMETAX') or 0)
|
||||
ei_current = abs(get_line_amount('EI_EMP') or get_line_amount('EI') or 0)
|
||||
cpp_current = abs(get_line_amount('CPP_EMP') or get_line_amount('CPP') or 0)
|
||||
cpp2_current = abs(get_line_amount('CPP2_EMP') or get_line_amount('CPP2') or 0)
|
||||
fed_tax_current = abs(get_line_amount('FED_TAX') or 0)
|
||||
prov_tax_current = abs(get_line_amount('PROV_TAX') or 0)
|
||||
ohp_current = abs(get_line_amount('OHP') or 0)
|
||||
income_tax_current = fed_tax_current + prov_tax_current + ohp_current
|
||||
ei_current = abs(get_line_amount('EI_EE') or 0)
|
||||
cpp_current = abs(get_line_amount('CPP_EE') or 0)
|
||||
cpp2_current = abs(get_line_amount('CPP2_EE') or 0)
|
||||
|
||||
# If individual line values are 0, calculate from payslip totals
|
||||
total_taxes_from_lines = income_tax_current + ei_current + cpp_current + cpp2_current
|
||||
if total_taxes_from_lines == 0 and payslip.basic_wage > 0 and payslip.net_wage > 0:
|
||||
# Calculate total taxes as difference between basic and net
|
||||
total_taxes_calculated = payslip.basic_wage - payslip.net_wage
|
||||
if total_taxes_calculated > 0:
|
||||
# Approximate breakdown based on typical Canadian tax rates
|
||||
# CPP ~5.95%, EI ~1.63%, Income Tax = remainder
|
||||
gross = payslip.basic_wage
|
||||
cpp_current = min(gross * 0.0595, 3867.50) # 2025 CPP max
|
||||
ei_current = min(gross * 0.0163, 1049.12) # 2025 EI max
|
||||
cpp_current = min(gross * 0.0595, 4230.45 / 26)
|
||||
ei_current = min(gross * 0.0163, 1123.07 / 26)
|
||||
income_tax_current = max(0, total_taxes_calculated - cpp_current - ei_current)
|
||||
cpp2_current = 0 # Usually 0 unless over threshold
|
||||
cpp2_current = 0
|
||||
|
||||
income_tax_ytd = abs(get_ytd_amount('FIT') or get_ytd_amount('INCOMETAX') or 0)
|
||||
ei_ytd = abs(get_ytd_amount('EI_EMP') or get_ytd_amount('EI') or 0)
|
||||
cpp_ytd = abs(get_ytd_amount('CPP_EMP') or get_ytd_amount('CPP') or 0)
|
||||
cpp2_ytd = abs(get_ytd_amount('CPP2_EMP') or get_ytd_amount('CPP2') or 0)
|
||||
fed_tax_ytd = abs(get_ytd_amount('FED_TAX') or 0)
|
||||
prov_tax_ytd = abs(get_ytd_amount('PROV_TAX') or 0)
|
||||
ohp_ytd = abs(get_ytd_amount('OHP') or 0)
|
||||
income_tax_ytd = fed_tax_ytd + prov_tax_ytd + ohp_ytd
|
||||
ei_ytd = abs(get_ytd_amount('EI_EE') or 0)
|
||||
cpp_ytd = abs(get_ytd_amount('CPP_EE') or 0)
|
||||
cpp2_ytd = abs(get_ytd_amount('CPP2_EE') or 0)
|
||||
|
||||
# Calculate totals
|
||||
total_taxes_current = income_tax_current + ei_current + cpp_current + cpp2_current
|
||||
|
||||
@@ -291,7 +291,8 @@ class PayrollEntry(models.TransientModel):
|
||||
|
||||
@api.depends('gross_pay', 'employee_id')
|
||||
def _compute_taxes(self):
|
||||
"""Calculate employee tax deductions."""
|
||||
"""Calculate employee tax deductions (preview estimate for bi-weekly)."""
|
||||
PAY_PERIODS = 26
|
||||
for entry in self:
|
||||
if entry.gross_pay <= 0:
|
||||
entry.income_tax = 0
|
||||
@@ -300,26 +301,81 @@ class PayrollEntry(models.TransientModel):
|
||||
entry.cpp2 = 0
|
||||
entry.total_employee_tax = 0
|
||||
continue
|
||||
|
||||
# Get tax rates from parameters or use defaults
|
||||
# These are simplified calculations - actual payroll uses full tax rules
|
||||
|
||||
gross = entry.gross_pay
|
||||
|
||||
# Simplified tax calculations (bi-weekly)
|
||||
# Income tax: ~15-20% average for Canadian employees
|
||||
entry.income_tax = round(gross * 0.128, 2) # Approximate federal + provincial
|
||||
|
||||
# EI: 1.64% of gross (2025 rate) up to maximum
|
||||
entry.employment_insurance = round(min(gross * 0.0164, 1049.12 / 26), 2)
|
||||
|
||||
# CPP: 5.95% of pensionable earnings above basic exemption (2025)
|
||||
cpp_exempt = 3500 / 26 # Annual exemption / 26 pay periods
|
||||
pensionable = max(0, gross - cpp_exempt)
|
||||
entry.cpp = round(min(pensionable * 0.0595, 4034.10 / 26), 2)
|
||||
|
||||
# CPP2: 4% on earnings above first ceiling (2025)
|
||||
entry.cpp2 = 0 # Only applies if earnings exceed $71,300/year
|
||||
|
||||
annual = gross * PAY_PERIODS
|
||||
emp = entry.employee_id
|
||||
|
||||
is_cpp_exempt = getattr(emp, 'exempt_cpp', False)
|
||||
is_ei_exempt = getattr(emp, 'exempt_ei', False)
|
||||
is_fed_exempt = getattr(emp, 'exempt_federal_tax', False)
|
||||
|
||||
# Federal tax estimate using 2026 brackets + BPA phase-out
|
||||
fed_brackets = [
|
||||
(58523, 0.14), (117045, 0.205), (181440, 0.26),
|
||||
(258482, 0.29), (float('inf'), 0.33),
|
||||
]
|
||||
bpa_max, bpa_min = 16452, 14829
|
||||
if annual <= 181440:
|
||||
fed_bpa = bpa_max
|
||||
elif annual >= 258482:
|
||||
fed_bpa = bpa_min
|
||||
else:
|
||||
fed_bpa = bpa_max - (bpa_max - bpa_min) * (annual - 181440) / (258482 - 181440)
|
||||
|
||||
fed_tax = 0
|
||||
prev = 0
|
||||
for threshold, rate in fed_brackets:
|
||||
taxable_in = min(annual, threshold) - prev
|
||||
if taxable_in > 0:
|
||||
fed_tax += taxable_in * rate
|
||||
prev = threshold
|
||||
if annual <= threshold:
|
||||
break
|
||||
fed_tax = max(fed_tax - fed_bpa * 0.14 - 1433 * 0.14, 0)
|
||||
|
||||
# Ontario provincial estimate (default province)
|
||||
on_brackets = [
|
||||
(53891, 0.0505), (107785, 0.0915), (150000, 0.1116),
|
||||
(220000, 0.1216), (float('inf'), 0.1316),
|
||||
]
|
||||
prov_tax = 0
|
||||
prev = 0
|
||||
for threshold, rate in on_brackets:
|
||||
taxable_in = min(annual, threshold) - prev
|
||||
if taxable_in > 0:
|
||||
prov_tax += taxable_in * rate
|
||||
prev = threshold
|
||||
if annual <= threshold:
|
||||
break
|
||||
prov_tax = max(prov_tax - 12989 * 0.0505, 0)
|
||||
|
||||
entry.income_tax = 0 if is_fed_exempt else round((fed_tax + prov_tax) / PAY_PERIODS, 2)
|
||||
|
||||
if is_ei_exempt:
|
||||
entry.employment_insurance = 0
|
||||
else:
|
||||
period_max_insurable = 68900 / PAY_PERIODS
|
||||
insurable = min(gross, period_max_insurable)
|
||||
entry.employment_insurance = round(min(insurable * 0.0163, 1123.07 / PAY_PERIODS), 2)
|
||||
|
||||
if is_cpp_exempt:
|
||||
entry.cpp = 0
|
||||
entry.cpp2 = 0
|
||||
else:
|
||||
period_ympe = 74600 / PAY_PERIODS
|
||||
cpp_exempt_amt = 3500 / PAY_PERIODS
|
||||
pensionable = min(gross, period_ympe)
|
||||
pensionable = max(0, pensionable - cpp_exempt_amt)
|
||||
entry.cpp = round(min(pensionable * 0.0595, 4230.45 / PAY_PERIODS), 2)
|
||||
|
||||
if not is_cpp_exempt and gross > 74600 / PAY_PERIODS:
|
||||
period_ceiling = 85000 / PAY_PERIODS
|
||||
cpp2_base = min(gross, period_ceiling) - period_ympe
|
||||
entry.cpp2 = round(min(cpp2_base * 0.04, 416.00 / PAY_PERIODS), 2)
|
||||
else:
|
||||
entry.cpp2 = 0
|
||||
|
||||
entry.total_employee_tax = entry.income_tax + entry.employment_insurance + entry.cpp + entry.cpp2
|
||||
|
||||
@api.depends('employment_insurance', 'cpp', 'cpp2')
|
||||
|
||||
@@ -60,9 +60,9 @@ class PayrollReportTotalPay(models.AbstractModel):
|
||||
if line.category_id.code == 'BASIC':
|
||||
emp_data[emp_key]['regular_pay'] += line.total or 0
|
||||
if hasattr(line, 'code') and line.code:
|
||||
if line.code == 'STAT_HOLIDAY':
|
||||
if line.code == 'STAT_PAY':
|
||||
emp_data[emp_key]['stat_holiday'] += line.total or 0
|
||||
elif line.code == 'VACATION':
|
||||
elif line.code == 'VAC_PAY':
|
||||
emp_data[emp_key]['vacation_pay'] += line.total or 0
|
||||
|
||||
emp_data[emp_key]['total'] += getattr(slip, 'gross_wage', 0) or 0
|
||||
@@ -295,9 +295,9 @@ class PayrollReportDeductions(models.AbstractModel):
|
||||
deduction_data = defaultdict(lambda: {'employee': 0, 'company': 0})
|
||||
|
||||
deduction_codes = {
|
||||
'CPP': {'name': 'Canada Pension Plan', 'type': 'Tax', 'employer_code': 'CPP_ER'},
|
||||
'CPP2': {'name': 'Second Canada Pension Plan', 'type': 'Tax', 'employer_code': 'CPP2_ER'},
|
||||
'EI': {'name': 'Employment Insurance', 'type': 'Tax', 'employer_code': 'EI_ER'},
|
||||
'CPP_EE': {'name': 'Canada Pension Plan', 'type': 'Tax', 'employer_code': 'CPP_ER'},
|
||||
'CPP2_EE': {'name': 'Second Canada Pension Plan', 'type': 'Tax', 'employer_code': 'CPP2_ER'},
|
||||
'EI_EE': {'name': 'Employment Insurance', 'type': 'Tax', 'employer_code': 'EI_ER'},
|
||||
}
|
||||
|
||||
for slip in payslips:
|
||||
@@ -309,9 +309,9 @@ class PayrollReportDeductions(models.AbstractModel):
|
||||
if line.code in deduction_codes:
|
||||
deduction_data[line.code]['employee'] += abs(line.total or 0)
|
||||
elif line.code.endswith('_ER'):
|
||||
base_code = line.code[:-3]
|
||||
if base_code in deduction_codes:
|
||||
deduction_data[base_code]['company'] += abs(line.total or 0)
|
||||
ee_code = line.code.replace('_ER', '_EE')
|
||||
if ee_code in deduction_codes:
|
||||
deduction_data[ee_code]['company'] += abs(line.total or 0)
|
||||
|
||||
lines = []
|
||||
for code, info in deduction_codes.items():
|
||||
@@ -378,8 +378,8 @@ class PayrollReportWorkersComp(models.AbstractModel):
|
||||
continue
|
||||
|
||||
province = 'ON' # Default, would get from employee address
|
||||
if hasattr(slip.employee_id, 'province_of_employment'):
|
||||
province = getattr(slip.employee_id, 'province_of_employment', None) or 'ON'
|
||||
if hasattr(slip.employee_id, 'home_province'):
|
||||
province = getattr(slip.employee_id, 'home_province', None) or 'ON'
|
||||
|
||||
province_data[province]['wages'] += getattr(slip, 'gross_wage', 0) or 0
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@ class PayrollReportPayrollDetails(models.AbstractModel):
|
||||
|
||||
# Tax breakdown
|
||||
tax_lines = payslip.line_ids.filtered(
|
||||
lambda l: hasattr(l, 'code') and l.code in ['CPP', 'CPP2', 'EI', 'FED_TAX', 'PROV_TAX']
|
||||
lambda l: hasattr(l, 'code') and l.code in ['CPP_EE', 'CPP2_EE', 'EI_EE', 'FED_TAX', 'PROV_TAX', 'OHP']
|
||||
)
|
||||
for line in tax_lines:
|
||||
lines.append({
|
||||
|
||||
@@ -49,12 +49,12 @@ class PayrollReportTaxLiability(models.AbstractModel):
|
||||
|
||||
# Calculate totals by tax type
|
||||
tax_totals = {
|
||||
'income_tax': {'name': _('Income Tax'), 'amount': 0, 'codes': ['FED_TAX', 'PROV_TAX']},
|
||||
'ei_employee': {'name': _('Employment Insurance'), 'amount': 0, 'codes': ['EI']},
|
||||
'income_tax': {'name': _('Income Tax'), 'amount': 0, 'codes': ['FED_TAX', 'PROV_TAX', 'OHP']},
|
||||
'ei_employee': {'name': _('Employment Insurance'), 'amount': 0, 'codes': ['EI_EE']},
|
||||
'ei_employer': {'name': _('Employment Insurance Employer'), 'amount': 0, 'codes': ['EI_ER']},
|
||||
'cpp_employee': {'name': _('Canada Pension Plan'), 'amount': 0, 'codes': ['CPP']},
|
||||
'cpp_employee': {'name': _('Canada Pension Plan'), 'amount': 0, 'codes': ['CPP_EE']},
|
||||
'cpp_employer': {'name': _('Canada Pension Plan Employer'), 'amount': 0, 'codes': ['CPP_ER']},
|
||||
'cpp2_employee': {'name': _('Second Canada Pension Plan'), 'amount': 0, 'codes': ['CPP2']},
|
||||
'cpp2_employee': {'name': _('Second Canada Pension Plan'), 'amount': 0, 'codes': ['CPP2_EE']},
|
||||
'cpp2_employer': {'name': _('Second Canada Pension Plan Employer'), 'amount': 0, 'codes': ['CPP2_ER']},
|
||||
}
|
||||
|
||||
@@ -260,15 +260,15 @@ class PayrollReportTaxWageSummary(models.AbstractModel):
|
||||
tax_data = [
|
||||
{
|
||||
'name': _('Income Tax'),
|
||||
'codes': ['FED_TAX', 'PROV_TAX'],
|
||||
'codes': ['FED_TAX', 'PROV_TAX', 'OHP'],
|
||||
'total_wages': total_wages,
|
||||
'excess_wages': 0, # No excess for income tax
|
||||
'excess_wages': 0,
|
||||
},
|
||||
{
|
||||
'name': _('Employment Insurance'),
|
||||
'codes': ['EI'],
|
||||
'codes': ['EI_EE'],
|
||||
'total_wages': total_wages,
|
||||
'excess_wages': 0, # Would need to calculate based on max
|
||||
'excess_wages': 0,
|
||||
},
|
||||
{
|
||||
'name': _('Employment Insurance Employer'),
|
||||
@@ -278,7 +278,7 @@ class PayrollReportTaxWageSummary(models.AbstractModel):
|
||||
},
|
||||
{
|
||||
'name': _('Canada Pension Plan'),
|
||||
'codes': ['CPP'],
|
||||
'codes': ['CPP_EE'],
|
||||
'total_wages': total_wages,
|
||||
'excess_wages': 0,
|
||||
},
|
||||
@@ -290,7 +290,7 @@ class PayrollReportTaxWageSummary(models.AbstractModel):
|
||||
},
|
||||
{
|
||||
'name': _('Second Canada Pension Plan'),
|
||||
'codes': ['CPP2'],
|
||||
'codes': ['CPP2_EE'],
|
||||
'total_wages': total_wages,
|
||||
'excess_wages': 0,
|
||||
},
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class TaxYearlyRateLine(models.Model):
|
||||
_name = 'tax.yearly.rate.line'
|
||||
_description = 'Tax Yearly Rate Line'
|
||||
_order = 'id'
|
||||
|
||||
# === Relational Fields ===
|
||||
tax_id = fields.Many2one(
|
||||
'tax.yearly.rates',
|
||||
string='Tax',
|
||||
ondelete='cascade',
|
||||
)
|
||||
|
||||
# === Tax Bracket Fields ===
|
||||
tax_bracket = fields.Float(string='Tax Bracket')
|
||||
tax_rate = fields.Float(string='Tax Rate')
|
||||
tax_constant = fields.Float(string='Tax Constant')
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class TaxYearlyRates(models.Model):
|
||||
_name = 'tax.yearly.rates'
|
||||
_description = 'Yearly Tax Rates'
|
||||
_order = 'id'
|
||||
|
||||
# === Selection Options ===
|
||||
TAX_TYPE_SELECTION = [
|
||||
('federal', 'Federal Taxes'),
|
||||
('provincial', 'Provincial Taxes'),
|
||||
]
|
||||
|
||||
DEDUCTION_TYPE_SELECTION = [
|
||||
('cpp', 'Canada Pension Plan'),
|
||||
('ei', 'Employment Insurance'),
|
||||
]
|
||||
|
||||
# === Core Fields ===
|
||||
fiscal_year = fields.Many2one(
|
||||
'account.fiscal.year',
|
||||
string='Fiscal Year',
|
||||
)
|
||||
tax_type = fields.Selection(
|
||||
selection=TAX_TYPE_SELECTION,
|
||||
string='Tax Type',
|
||||
)
|
||||
ded_type = fields.Selection(
|
||||
selection=DEDUCTION_TYPE_SELECTION,
|
||||
string='Deduction Type',
|
||||
)
|
||||
|
||||
# === Tax Bracket Lines ===
|
||||
tax_yearly_rate_ids = fields.One2many(
|
||||
'tax.yearly.rate.line',
|
||||
'tax_id',
|
||||
string='Tax Lines',
|
||||
)
|
||||
|
||||
# === Federal/Provincial Tax Fields ===
|
||||
fed_tax_credit = fields.Float(string='Federal Tax Credit')
|
||||
provincial_tax_credit = fields.Float(string='Provincial Tax Credit')
|
||||
canada_emp_amount = fields.Float(string='Canada Employment Amount')
|
||||
exemption = fields.Float(string='Exemption Amount')
|
||||
|
||||
# === CPP (Canada Pension Plan) Fields ===
|
||||
cpp_date = fields.Date(string='CPP Date')
|
||||
max_cpp = fields.Float(string='Maximum CPP')
|
||||
emp_contribution_rate = fields.Float(string='Employee Contribution Rate')
|
||||
employer_contribution_rate = fields.Float(string='Employer Contribution Rate')
|
||||
|
||||
# === EI (Employment Insurance) Fields ===
|
||||
ei_date = fields.Date(string='EI Date')
|
||||
ei_rate = fields.Float(string='EI Rate')
|
||||
ei_earnings = fields.Float(string='Maximum EI Earnings')
|
||||
emp_ei_amount = fields.Float(string='Employee EI Amount')
|
||||
employer_ei_amount = fields.Float(string='Employer EI Amount')
|
||||
Reference in New Issue
Block a user