This commit is contained in:
gsinghpal
2026-04-07 20:49:21 -04:00
parent 3cc93b8783
commit 4fde4c7bd1
25 changed files with 1253 additions and 900 deletions

View File

@@ -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 {