Initial commit for l10n_ca_pst_reports

This commit is contained in:
Denis Durepos 2025-03-11 16:49:13 -04:00
parent b605f77e55
commit b83fb948d4
5 changed files with 235 additions and 0 deletions

View file

View file

@ -0,0 +1,33 @@
{
'name': 'Canadian PST Numbers on Reports',
'author': 'Bemade Inc.',
'version': '1.0',
'category': 'Accounting/Localizations/Reports',
'summary': 'Add PST numbers alongside GST/VAT numbers on reports',
'description': '''
This module adds Provincial Sales Tax (PST) numbers to reports where GST/VAT numbers
are already displayed. Supports:
- Invoice reports
- Company documents
- Customer/Vendor forms
''',
'depends': [
'base',
'account',
'l10n_ca',
'web', # for external layouts
],
'data': [
'views/external_layout.xml',
],
'external_dependencies': {
'python': [],
},
'test_external_dependencies': {
'python': ['pdfminer.six'],
},
'installable': True,
'application': False,
'auto_install': False,
'license': 'LGPL-3',
}

View file

@ -0,0 +1 @@
from . import test_report_tax_numbers

View file

@ -0,0 +1,65 @@
from odoo.tests import TransactionCase, tagged
from pdfminer.high_level import extract_text
import base64
import tempfile
import os
@tagged('post_install', '-at_install')
class TestReportTaxNumbers(TransactionCase):
def setUp(self):
super().setUp()
# Create a Canadian company with GST and PST numbers
self.company = self.env['res.company'].create({
'name': 'Test Canadian Company',
'country_id': self.env.ref('base.ca').id,
'vat': '123456789',
'l10n_ca_pst': 'PST12345',
})
self.env['account.chart.template'].try_loading('ca_2023', company=self.company)
# Create a partner
self.partner = self.env['res.partner'].create({
'name': 'Test Customer',
'country_id': self.env.ref('base.ca').id,
})
# Create an invoice to test with
self.invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': self.partner.id,
'company_id': self.company.id,
})
def _get_pdf_content(self, report_name, res_ids):
"""Generate PDF report and extract its text content."""
pdf_content, _ = self.env['ir.actions.report']._render_qweb_html(report_name, [res_ids])
return str(pdf_content)
def test_invoice_tax_numbers(self):
"""Test that GST and PST numbers appear on invoice report."""
# Generate invoice PDF and extract text
text = self._get_pdf_content('account.report_invoice', self.invoice.id)
# Check for GST and PST numbers
self.assertIn('GST: <span>123456789</span>', text, "GST number not found in invoice")
self.assertIn('PST: <span>PST12345</span>', text, "PST number not found in invoice")
def test_different_layouts(self):
"""Test tax numbers appear with different report layouts."""
layouts = ['standard', 'boxed', 'bold', 'striped', 'bubble', 'wave', 'folder']
for layout in layouts:
# Set company layout
layout_id = self.env.ref(f'web.external_layout_{layout}')
self.company.write({'external_report_layout_id': layout_id.id})
# Generate invoice PDF and extract text
text = self._get_pdf_content('account.report_invoice', self.invoice.id)
# Check for GST and PST numbers
self.assertIn('GST: <span>123456789</span>', text,
f"GST number not found in invoice with {layout} layout")
self.assertIn('PST: <span>PST12345</span>', text,
f"PST number not found in invoice with {layout} layout")

View file

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="l10n_ca_pst_external_layout_standard" inherit_id="web.external_layout_standard">
<xpath expr="//li[@t-if='not forced_vat']" position="replace">
<li t-if="not forced_vat and o.company_id.country_id.code == 'CA'">
<div class="d-flex flex-column">
<div t-if="o.company_id.vat">
GST: <span t-field="o.company_id.vat"/>
</div>
<div t-if="o.company_id.l10n_ca_pst">
PST: <span t-field="o.company_id.l10n_ca_pst"/>
</div>
</div>
</li>
<li t-elif="not forced_vat and o.company_id.vat">
<t t-if="o.company_id.country_id.vat_label" t-out="o.company_id.country_id.vat_label" id="inv_tax_id_label"/>
<t t-else="">Tax ID</t>: <span t-field="o.company_id.vat"/>
</li>
</xpath>
</template>
<!-- Also inherit other layout styles to maintain consistency -->
<template id="l10n_ca_pst_external_layout_boxed" inherit_id="web.external_layout_boxed">
<xpath expr="//li[@t-if='not forced_vat']" position="replace">
<li t-if="not forced_vat and o.company_id.country_id.code == 'CA'">
<div class="d-flex flex-column">
<div t-if="o.company_id.vat">
GST: <span t-field="o.company_id.vat"/>
</div>
<div t-if="o.company_id.l10n_ca_pst">
PST: <span t-field="o.company_id.l10n_ca_pst"/>
</div>
</div>
</li>
<li t-elif="not forced_vat and o.company_id.vat">
<t t-if="o.company_id.country_id.vat_label" t-out="o.company_id.country_id.vat_label" id="inv_tax_id_label"/>
<t t-else="">Tax ID</t>: <span t-field="o.company_id.vat"/>
</li>
</xpath>
</template>
<template id="l10n_ca_pst_external_layout_bold" inherit_id="web.external_layout_bold">
<xpath expr="//li[@t-if='not forced_vat']" position="replace">
<li t-if="not forced_vat and o.company_id.country_id.code == 'CA'">
<div class="d-flex flex-column">
<div t-if="o.company_id.vat">
GST: <span t-field="o.company_id.vat"/>
</div>
<div t-if="o.company_id.l10n_ca_pst">
PST: <span t-field="o.company_id.l10n_ca_pst"/>
</div>
</div>
</li>
<li t-elif="not forced_vat and o.company_id.vat">
<t t-if="o.company_id.country_id.vat_label" t-out="o.company_id.country_id.vat_label" id="inv_tax_id_label"/>
<t t-else="">Tax ID</t>: <span t-field="o.company_id.vat"/>
</li>
</xpath>
</template>
<template id="l10n_ca_pst_external_layout_striped" inherit_id="web.external_layout_striped">
<xpath expr="//li[@t-if='not forced_vat']" position="replace">
<li t-if="not forced_vat and o.company_id.country_id.code == 'CA'">
<div class="d-flex flex-column">
<div t-if="o.company_id.vat">
GST: <span t-field="o.company_id.vat"/>
</div>
<div t-if="o.company_id.l10n_ca_pst">
PST: <span t-field="o.company_id.l10n_ca_pst"/>
</div>
</div>
</li>
<li t-elif="not forced_vat and o.company_id.vat">
<t t-if="o.company_id.country_id.vat_label" t-out="o.company_id.country_id.vat_label" id="inv_tax_id_label"/>
<t t-else="">Tax ID</t>: <span t-field="o.company_id.vat"/>
</li>
</xpath>
</template>
<template id="l10n_ca_pst_external_layout_bubble" inherit_id="web.external_layout_bubble">
<xpath expr="//li[@t-if='not forced_vat']" position="replace">
<li t-if="not forced_vat and o.company_id.country_id.code == 'CA'">
<div class="d-flex flex-column">
<div t-if="o.company_id.vat">
GST: <span t-field="o.company_id.vat"/>
</div>
<div t-if="o.company_id.l10n_ca_pst">
PST: <span t-field="o.company_id.l10n_ca_pst"/>
</div>
</div>
</li>
<li t-elif="not forced_vat and o.company_id.vat">
<t t-if="o.company_id.country_id.vat_label" t-out="o.company_id.country_id.vat_label" id="inv_tax_id_label"/>
<t t-else="">Tax ID</t>: <span t-field="o.company_id.vat"/>
</li>
</xpath>
</template>
<template id="l10n_ca_pst_external_layout_wave" inherit_id="web.external_layout_wave">
<xpath expr="//li[@t-if='not forced_vat']" position="replace">
<li t-if="not forced_vat and o.company_id.country_id.code == 'CA'">
<div class="d-flex flex-column">
<div t-if="o.company_id.vat">
GST: <span t-field="o.company_id.vat"/>
</div>
<div t-if="o.company_id.l10n_ca_pst">
PST: <span t-field="o.company_id.l10n_ca_pst"/>
</div>
</div>
</li>
<li t-elif="not forced_vat and o.company_id.vat">
<t t-if="o.company_id.country_id.vat_label" t-out="o.company_id.country_id.vat_label" id="inv_tax_id_label"/>
<t t-else="">Tax ID</t>: <span t-field="o.company_id.vat"/>
</li>
</xpath>
</template>
<template id="l10n_ca_pst_external_layout_folder" inherit_id="web.external_layout_folder">
<xpath expr="//li[@t-if='not forced_vat']" position="replace">
<li t-if="not forced_vat and o.company_id.country_id.code == 'CA'">
<div class="d-flex flex-column">
<div t-if="o.company_id.vat">
GST: <span t-field="o.company_id.vat"/>
</div>
<div t-if="o.company_id.l10n_ca_pst">
PST: <span t-field="o.company_id.l10n_ca_pst"/>
</div>
</div>
</li>
<li t-elif="not forced_vat and o.company_id.vat">
<t t-if="o.company_id.country_id.vat_label" t-out="o.company_id.country_id.vat_label" id="inv_tax_id_label"/>
<t t-else="">Tax ID</t>: <span t-field="o.company_id.vat"/>
</li>
</xpath>
</template>
</odoo>