feat(plating): fully bilingual CoC — all labels EN/FR

Convert every remaining CoC label from single-language (is_fr branch) to
bilingual EN/FR: document title, customer block (Name / Address / Contact /
Email / Phone), Fischerscope thickness report title + metadata (Equipment /
Product / Application / Directory / Calibration Std. / Operator / Measured /
Measuring Time), reading stats (Mean / Std Dev / Range), Source file,
Certified By, Name, and the Certification Statement heading. The statement
paragraph now prints both English and French. Reuses the SO report's inline
.fp-bl-en/.fp-bl-fr bilingual classes. Bump reports 19.0.11.30.0.

Deployed + render-verified on entech (CoC-30059 with thickness block, 243KB).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-28 22:11:40 -04:00
parent 9d9be17542
commit ec78fc148d
2 changed files with 34 additions and 49 deletions

View File

@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0) # License OPL-1 (Odoo Proprietary License v1.0)
{ {
'name': 'Fusion Plating — Reports', 'name': 'Fusion Plating — Reports',
'version': '19.0.11.29.0', 'version': '19.0.11.30.0',
'category': 'Manufacturing/Plating', 'category': 'Manufacturing/Plating',
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.', 'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
'depends': [ 'depends': [

View File

@@ -51,6 +51,8 @@
.fp-coc-nadcap-logo { max-height: 45px; max-width: 115px; display: inline-block; } .fp-coc-nadcap-logo { max-height: 45px; max-width: 115px; display: inline-block; }
.fp-coc-title { font-size: 18pt; font-weight: bold; color: #2e2e2e; .fp-coc-title { font-size: 18pt; font-weight: bold; color: #2e2e2e;
line-height: 1.1; display: block; } line-height: 1.1; display: block; }
.fp-coc-title-fr { font-size: 12pt; font-style: italic; color: #555;
line-height: 1.1; display: block; margin-top: 1px; }
/* Barcode: inline-block wrap so the cert-number label centres /* Barcode: inline-block wrap so the cert-number label centres
under the bars. Explicit no-border (wkhtmltopdf frames under the bars. Explicit no-border (wkhtmltopdf frames
inline-data imgs on entech). */ inline-data imgs on entech). */
@@ -92,10 +94,8 @@
</t> </t>
</div> </div>
<div class="fp-coc-header-right"> <div class="fp-coc-header-right">
<span class="fp-coc-title"> <span class="fp-coc-title">Certificate of Conformance</span>
<t t-if="not is_fr">Certificate of Conformance</t> <span class="fp-coc-title-fr">Certificat de Conformité</span>
<t t-if="is_fr">Certificat de Conformité</t>
</span>
<t t-if="coc_barcode_uri"> <t t-if="coc_barcode_uri">
<div class="fp-coc-bc-wrap"> <div class="fp-coc-bc-wrap">
<img t-att-src="coc_barcode_uri" alt="Cert Barcode"/> <img t-att-src="coc_barcode_uri" alt="Cert Barcode"/>
@@ -145,6 +145,11 @@
.fp-coc .fp-bl-fr-stk { display: block; font-weight: normal; .fp-coc .fp-bl-fr-stk { display: block; font-weight: normal;
font-style: italic; color: #555; font-style: italic; color: #555;
font-size: 80%; margin-top: 1px; } font-size: 80%; margin-top: 1px; }
/* Inline bilingual (EN bold / FR italic grey) for field labels
+ section headings. */
.fp-coc .fp-bl-en { font-weight: bold; }
.fp-coc .fp-bl-sep { color: #999; margin: 0 3px; font-weight: normal; }
.fp-coc .fp-bl-fr { font-weight: normal; font-style: italic; color: #555; }
.fp-coc .cert-statement-box { padding: 0; font-size: 8.5pt; } .fp-coc .cert-statement-box { padding: 0; font-size: 8.5pt; }
.fp-coc .cert-statement-box h4 { margin: 0 0 6px 0; font-size: 9.5pt; font-weight: bold; } .fp-coc .cert-statement-box h4 { margin: 0 0 6px 0; font-size: 9.5pt; font-weight: bold; }
.fp-coc .signature-img { max-height: 2.2cm; max-width: 7cm; } .fp-coc .signature-img { max-height: 2.2cm; max-width: 7cm; }
@@ -198,13 +203,11 @@
<tr> <tr>
<td style="width: 50%; vertical-align: top;"> <td style="width: 50%; vertical-align: top;">
<div> <div>
<strong t-if="not is_fr">Customer Name: </strong> <span class="fp-bl-en">Customer Name</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Nom du client</span>:
<strong t-if="is_fr">Nom du client : </strong>
<t t-esc="doc.partner_id.name or ''"/> <t t-esc="doc.partner_id.name or ''"/>
</div> </div>
<div style="margin-top: 4px;"> <div style="margin-top: 4px;">
<strong t-if="not is_fr">Customer Address:</strong> <span class="fp-bl-en">Customer Address</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Adresse du client</span>:
<strong t-if="is_fr">Adresse du client :</strong>
<br/> <br/>
<t t-if="doc.partner_id.street"><t t-esc="doc.partner_id.street"/><br/></t> <t t-if="doc.partner_id.street"><t t-esc="doc.partner_id.street"/><br/></t>
<t t-if="doc.partner_id.street2"><t t-esc="doc.partner_id.street2"/><br/></t> <t t-if="doc.partner_id.street2"><t t-esc="doc.partner_id.street2"/><br/></t>
@@ -216,18 +219,15 @@
<td style="width: 50%; vertical-align: top;"> <td style="width: 50%; vertical-align: top;">
<t t-set="contact" t-value="doc.contact_partner_id or doc.partner_id"/> <t t-set="contact" t-value="doc.contact_partner_id or doc.partner_id"/>
<div> <div>
<strong t-if="not is_fr">Contact Name: </strong> <span class="fp-bl-en">Contact Name</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Nom du contact</span>:
<strong t-if="is_fr">Nom du contact : </strong>
<t t-esc="contact.name or ''"/> <t t-esc="contact.name or ''"/>
</div> </div>
<div> <div>
<strong t-if="not is_fr">Customer Email: </strong> <span class="fp-bl-en">Customer Email</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Courriel</span>:
<strong t-if="is_fr">Courriel : </strong>
<t t-esc="contact.email or '-'"/> <t t-esc="contact.email or '-'"/>
</div> </div>
<div> <div>
<strong t-if="not is_fr">Customer Phone: </strong> <span class="fp-bl-en">Customer Phone</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Téléphone</span>:
<strong t-if="is_fr">Téléphone : </strong>
<t t-esc="contact.phone or '-'"/> <t t-esc="contact.phone or '-'"/>
</div> </div>
</td> </td>
@@ -371,8 +371,7 @@
<!-- Section header — full-width bar, drawn by the <!-- Section header — full-width bar, drawn by the
div's bottom-border, no internal table needed. --> div's bottom-border, no internal table needed. -->
<div class="ftk-title"> <div class="ftk-title">
<t t-if="not is_fr">Fischerscope XRF Thickness Report</t> <span class="fp-bl-en">Fischerscope XRF Thickness Report</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Rapport d'épaisseur Fischerscope XRF</span>
<t t-if="is_fr">Rapport d'épaisseur Fischerscope XRF</t>
</div> </div>
<!-- Equipment metadata — 4-column key/value grid. <!-- Equipment metadata — 4-column key/value grid.
@@ -384,39 +383,33 @@
<table> <table>
<tr class="ftk-row-divider"> <tr class="ftk-row-divider">
<td class="ftk-label ftk-cell-divider" style="width: 18%;"> <td class="ftk-label ftk-cell-divider" style="width: 18%;">
<t t-if="not is_fr">Equipment</t> <span class="fp-bl-en">Equipment</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Équipement</span>
<t t-if="is_fr">Équipement</t>
</td> </td>
<td class="ftk-cell-divider" style="width: 32%;"> <td class="ftk-cell-divider" style="width: 32%;">
<t t-esc="doc.x_fc_thickness_equipment or '—'"/> <t t-esc="doc.x_fc_thickness_equipment or '—'"/>
</td> </td>
<td class="ftk-label ftk-cell-divider" style="width: 18%;"> <td class="ftk-label ftk-cell-divider" style="width: 18%;">
<t t-if="not is_fr">Calibration Std.</t> <span class="fp-bl-en">Calibration Std.</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Étalon</span>
<t t-if="is_fr">Étalon</t>
</td> </td>
<td style="width: 32%;"><t t-esc="calib or '—'"/></td> <td style="width: 32%;"><t t-esc="calib or '—'"/></td>
</tr> </tr>
<tr class="ftk-row-divider"> <tr class="ftk-row-divider">
<td class="ftk-label ftk-cell-divider"> <td class="ftk-label ftk-cell-divider">
<t t-if="not is_fr">Product</t> <span class="fp-bl-en">Product</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Produit</span>
<t t-if="is_fr">Produit</t>
</td> </td>
<td class="ftk-cell-divider"><t t-esc="doc.x_fc_thickness_product or '—'"/></td> <td class="ftk-cell-divider"><t t-esc="doc.x_fc_thickness_product or '—'"/></td>
<td class="ftk-label ftk-cell-divider"> <td class="ftk-label ftk-cell-divider">
<t t-if="not is_fr">Operator</t> <span class="fp-bl-en">Operator</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Opérateur</span>
<t t-if="is_fr">Opérateur</t>
</td> </td>
<td><t t-esc="doc.x_fc_thickness_operator or '—'"/></td> <td><t t-esc="doc.x_fc_thickness_operator or '—'"/></td>
</tr> </tr>
<tr class="ftk-row-divider"> <tr class="ftk-row-divider">
<td class="ftk-label ftk-cell-divider"> <td class="ftk-label ftk-cell-divider">
<t t-if="not is_fr">Application</t> <span class="fp-bl-en">Application</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Application</span>
<t t-if="is_fr">Application</t>
</td> </td>
<td class="ftk-cell-divider"><t t-esc="doc.x_fc_thickness_application or '—'"/></td> <td class="ftk-cell-divider"><t t-esc="doc.x_fc_thickness_application or '—'"/></td>
<td class="ftk-label ftk-cell-divider"> <td class="ftk-label ftk-cell-divider">
<t t-if="not is_fr">Measured</t> <span class="fp-bl-en">Measured</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Mesuré le</span>
<t t-if="is_fr">Mesuré le</t>
</td> </td>
<td> <td>
<t t-if="doc.x_fc_thickness_datetime" <t t-if="doc.x_fc_thickness_datetime"
@@ -426,13 +419,11 @@
</tr> </tr>
<tr class="ftk-row-divider"> <tr class="ftk-row-divider">
<td class="ftk-label ftk-cell-divider"> <td class="ftk-label ftk-cell-divider">
<t t-if="not is_fr">Directory</t> <span class="fp-bl-en">Directory</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Répertoire</span>
<t t-if="is_fr">Répertoire</t>
</td> </td>
<td class="ftk-cell-divider"><t t-esc="doc.x_fc_thickness_directory or '—'"/></td> <td class="ftk-cell-divider"><t t-esc="doc.x_fc_thickness_directory or '—'"/></td>
<td class="ftk-label ftk-cell-divider"> <td class="ftk-label ftk-cell-divider">
<t t-if="not is_fr">Measuring Time</t> <span class="fp-bl-en">Measuring Time</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Durée de mesure</span>
<t t-if="is_fr">Durée de mesure</t>
</td> </td>
<td> <td>
<t t-if="doc.x_fc_thickness_measuring_time_sec" <t t-if="doc.x_fc_thickness_measuring_time_sec"
@@ -479,8 +470,7 @@
class="ftk-stat-mean ftk-row-divider" class="ftk-stat-mean ftk-row-divider"
style="page-break-inside: avoid;"> style="page-break-inside: avoid;">
<td class="ftk-cell-divider"> <td class="ftk-cell-divider">
<t t-if="not is_fr">Mean</t> <span class="fp-bl-en">Mean</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Moyenne</span>
<t t-if="is_fr">Moyenne</t>
</td> </td>
<td class="ftk-cell-divider"><t t-esc="'%.4f' % nip_mean"/></td> <td class="ftk-cell-divider"><t t-esc="'%.4f' % nip_mean"/></td>
<td class="ftk-cell-divider"><t t-esc="'%.3f' % ni_mean"/></td> <td class="ftk-cell-divider"><t t-esc="'%.3f' % ni_mean"/></td>
@@ -490,8 +480,7 @@
class="ftk-stat ftk-row-divider" class="ftk-stat ftk-row-divider"
style="page-break-inside: avoid;"> style="page-break-inside: avoid;">
<td class="ftk-cell-divider"> <td class="ftk-cell-divider">
<t t-if="not is_fr">Std Dev</t> <span class="fp-bl-en">Std Dev</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Écart-type</span>
<t t-if="is_fr">Écart-type</t>
</td> </td>
<td class="ftk-cell-divider"><t t-esc="'%.4f' % nip_std"/></td> <td class="ftk-cell-divider"><t t-esc="'%.4f' % nip_std"/></td>
<td class="ftk-cell-divider"><t t-esc="'%.3f' % ni_std"/></td> <td class="ftk-cell-divider"><t t-esc="'%.3f' % ni_std"/></td>
@@ -509,8 +498,7 @@
class="ftk-stat ftk-row-divider" class="ftk-stat ftk-row-divider"
style="page-break-inside: avoid;"> style="page-break-inside: avoid;">
<td class="ftk-cell-divider"> <td class="ftk-cell-divider">
<t t-if="not is_fr">Range</t> <span class="fp-bl-en">Range</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Étendue</span>
<t t-if="is_fr">Étendue</t>
</td> </td>
<td class="ftk-cell-divider"><t t-esc="'%.4f' % nip_range"/></td> <td class="ftk-cell-divider"><t t-esc="'%.4f' % nip_range"/></td>
<td class="ftk-cell-divider"><t t-esc="'%.3f' % ni_range"/></td> <td class="ftk-cell-divider"><t t-esc="'%.3f' % ni_range"/></td>
@@ -535,8 +523,7 @@
normal weight prints cleanly. --> normal weight prints cleanly. -->
<div t-if="doc.x_fc_thickness_source_filename" <div t-if="doc.x_fc_thickness_source_filename"
style="margin-top: 4px; font-size: 8pt; color: #555;"> style="margin-top: 4px; font-size: 8pt; color: #555;">
<strong t-if="not is_fr">Source file:</strong> <span class="fp-bl-en">Source file</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Fichier source</span>:
<strong t-if="is_fr">Fichier source :</strong>
<t t-esc="' ' + (doc.x_fc_thickness_source_filename or '')"/> <t t-esc="' ' + (doc.x_fc_thickness_source_filename or '')"/>
<t t-if="doc.x_fc_local_thickness_evidence_id"> (attached to cert as evidence)</t> <t t-if="doc.x_fc_local_thickness_evidence_id"> (attached to cert as evidence)</t>
</div> </div>
@@ -553,8 +540,7 @@
<tr style="page-break-inside: avoid;"> <tr style="page-break-inside: avoid;">
<td style="width: 50%; vertical-align: top;"> <td style="width: 50%; vertical-align: top;">
<div> <div>
<strong t-if="not is_fr">Certified By:</strong> <span class="fp-bl-en">Certified By</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Certifié par</span>:
<strong t-if="is_fr">Certifié par :</strong>
</div> </div>
<div style="min-height: 2.5cm; margin-top: 6px;"> <div style="min-height: 2.5cm; margin-top: 6px;">
<img t-if="signature_img" <img t-if="signature_img"
@@ -563,16 +549,15 @@
alt=""/> alt=""/>
</div> </div>
<div style="margin-top: 4px; border-top: 1px solid #000; padding-top: 4px;"> <div style="margin-top: 4px; border-top: 1px solid #000; padding-top: 4px;">
<strong t-if="not is_fr">Name: </strong> <span class="fp-bl-en">Name</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Nom</span>:
<strong t-if="is_fr">Nom : </strong>
<t t-esc="signer_name or ''"/> <t t-esc="signer_name or ''"/>
</div> </div>
</td> </td>
<td style="width: 50%; vertical-align: top; padding-left: 14px;"> <td style="width: 50%; vertical-align: top; padding-left: 14px;">
<div class="cert-statement-box"> <div class="cert-statement-box">
<h4 t-if="not is_fr">Certification Statement</h4> <h4><span class="fp-bl-en">Certification Statement</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Énoncé de conformité</span></h4>
<h4 t-if="is_fr">Énoncé de conformité</h4> <p style="margin: 0;">
<p t-if="not is_fr" style="margin: 0;">
This is to certify that the items listed herein have This is to certify that the items listed herein have
been processed, inspected and tested in accordance been processed, inspected and tested in accordance
with your Purchase Order, drawings and specification with your Purchase Order, drawings and specification
@@ -580,7 +565,7 @@
Made in Canada. There is no Mercury used in the Made in Canada. There is no Mercury used in the
processing of this order. processing of this order.
</p> </p>
<p t-if="is_fr" style="margin: 0;"> <p style="margin: 6px 0 0 0; font-style: italic; color: #444;">
Ceci est pour certifier que les articles inscrits ont Ceci est pour certifier que les articles inscrits ont
été procédés, inspectés et mis à l'essai selon votre été procédés, inspectés et mis à l'essai selon votre
bon de commande, dessins et spécifications. Tous les bon de commande, dessins et spécifications. Tous les