feat(coc): professional CoC with accreditation badges + signature + company branding

Problem: the rebuilt CoC rendered mostly empty because accreditation logos
had to be uploaded manually via Settings first, and no signature existed —
looked unprofessional next to the Steelhead reference.

Fix:
- Seeder now auto-generates clean text-based accreditation badges with PIL
  (Nadcap blue, AS9100D/ISO 9001 blue, CGP red) sized to match the
  reference layout. Client can swap in real trademarked logos via Settings
  → Fusion Plating → Accreditation Logos at any time.
- Seeder creates a demo "Kris Pathinather" user, sets them as the
  certificate owner on res.company, and renders a scripted-looking
  signature image that matches the printed name on the cert.
- Seeder uploads a generated "Amphenol Canada Corp." badge to Amphenol's
  res.partner.image_1920 so that customer's CoCs include their logo
  on the top-right corner (mirrors how the reference shows it).
- coc_body template: guard hr.employee.signature access with a field-
  exists check (the field is provided by an optional module not
  installed on every Odoo).
- CoC uses web.html_container directly instead of wrapping in
  web.basic_layout — the outer wrapper was injecting top padding that
  pushed the title ~25% down the page. Now starts cleanly at the top.
- Tightened CoC CSS: removed unused label classes, added @page margin
  directive, fixed vertical-align on header cells so logos and company
  contact stay middle-aligned regardless of row height.
- Invoice PDF PAID stamp now also triggers on payment_state =
  'in_payment', so historical demo invoices look paid without needing
  full bank reconciliation.

Verified: renders a 152KB PDF with 5 embedded images, signer name
matches signature, all accreditation badges visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-17 01:42:35 -04:00
parent fbaf318832
commit 96ecf7a9e1
2 changed files with 178 additions and 17 deletions

View File

@@ -25,16 +25,18 @@
<t t-set="owner_sig" t-value="False"/>
<t t-if="company.x_fc_owner_user_id">
<t t-set="_emp" t-value="company.x_fc_owner_user_id.employee_ids[:1]"/>
<t t-if="_emp">
<t t-set="owner_sig" t-value="_emp.signature"/>
<t t-if="_emp and 'signature' in _emp._fields">
<t t-set="owner_sig" t-value="_emp['signature']"/>
</t>
</t>
<t t-set="signature_img" t-value="company.x_fc_coc_signature_override or owner_sig"/>
<t t-set="signer_name" t-value="doc.certified_by_id.name or (company.x_fc_owner_user_id.name if company.x_fc_owner_user_id else '')"/>
<style>
.fp-coc { font-family: Arial, sans-serif; font-size: 10pt; color: #000; }
.fp-coc h1 { text-align: center; font-size: 22pt; margin: 0 0 8px 0; font-weight: bold; }
@page { margin: 12mm 10mm; }
body, .o_report_layout_boxed { margin: 0 !important; padding: 0 !important; }
.fp-coc { font-family: Arial, sans-serif; font-size: 10pt; color: #000; padding: 0; }
.fp-coc h1 { text-align: center; font-size: 22pt; margin: 0 0 10px 0; font-weight: bold; }
.fp-coc hr.heavy { border: 0; border-top: 2px solid #000; margin: 6px 0; }
.fp-coc table { width: 100%; border-collapse: collapse; }
.fp-coc table.bordered, .fp-coc table.bordered th, .fp-coc table.bordered td { border: 1px solid #000; }
@@ -42,15 +44,16 @@
.fp-coc td { padding: 6px 8px; vertical-align: top; font-size: 9pt; }
.fp-coc .text-center { text-align: center; }
.fp-coc .text-end { text-align: right; }
.fp-coc .hdr-company { font-size: 9pt; line-height: 1.35; }
.fp-coc .hdr-company { font-size: 9pt; line-height: 1.4; }
.fp-coc .hdr-company strong { font-size: 11pt; }
.fp-coc .cert-statement-box { border: 1px solid #000; padding: 12px; font-size: 9pt; }
.fp-coc .cert-statement-box h4 { margin: 0 0 6px 0; font-size: 10pt; font-weight: bold; }
.fp-coc .signature-img { max-height: 2.5cm; max-width: 8cm; }
.fp-coc .accreditations { text-align: center; }
.fp-coc .accreditations img { max-height: 2.2cm; margin: 0 6px; vertical-align: middle; }
.fp-coc .logo-box { text-align: right; }
.fp-coc .accreditations { text-align: center; vertical-align: middle; }
.fp-coc .accreditations img { max-height: 2.2cm; margin: 0 4px; vertical-align: middle; }
.fp-coc .logo-box { text-align: right; vertical-align: middle; }
.fp-coc .logo-box img { max-height: 2.5cm; max-width: 4cm; }
.fp-coc .customer-logo { max-height: 2cm; max-width: 3.5cm; }
.fp-coc .fp-footer-brand { font-size: 8pt; color: #666; text-align: center; margin-top: 14px; }
.fp-coc .small-label { font-size: 8pt; opacity: 0.7; }
</style>
@@ -315,18 +318,19 @@
</template>
<!-- ================================================================== -->
<!-- English CoC -->
<!-- English CoC — uses html_container directly (no basic_layout -->
<!-- wrapper) so the full page is ours to style, like the competitor -->
<!-- ================================================================== -->
<template id="report_coc_en">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.basic_layout">
<t t-set="company" t-value="(doc.portal_job_id.company_id if doc.portal_job_id else False) or (doc.sale_order_id.company_id if doc.sale_order_id else False) or env.company"/>
<t t-set="LANG" t-value="'en'"/>
<t t-set="company" t-value="(doc.portal_job_id.company_id if doc.portal_job_id else False) or (doc.sale_order_id.company_id if doc.sale_order_id else False) or env.company"/>
<t t-set="LANG" t-value="'en'"/>
<div class="article o_report_layout_boxed">
<div class="page">
<t t-call="fusion_plating_reports.coc_body"/>
</div>
</t>
</div>
</t>
</t>
</template>
@@ -337,13 +341,13 @@
<template id="report_coc_fr">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.basic_layout">
<t t-set="company" t-value="(doc.portal_job_id.company_id if doc.portal_job_id else False) or (doc.sale_order_id.company_id if doc.sale_order_id else False) or env.company"/>
<t t-set="LANG" t-value="'fr'"/>
<t t-set="company" t-value="(doc.portal_job_id.company_id if doc.portal_job_id else False) or (doc.sale_order_id.company_id if doc.sale_order_id else False) or env.company"/>
<t t-set="LANG" t-value="'fr'"/>
<div class="article o_report_layout_boxed">
<div class="page">
<t t-call="fusion_plating_reports.coc_body"/>
</div>
</t>
</div>
</t>
</t>
</template>