fix(fusion_plating): bug review fixes + progress/net-terms invoicing + formal CoC rebuild

Bug review fixes (found by code review + live QWeb error):
- report_fp_sale.xml: product_uom → product_uom_id (Odoo 19 renamed;
  was raising KeyError during PDF render, blocking all sale-order prints)
- mrp_production.button_mark_done: add idempotency guard on delivery
  auto-create (was duplicating on every re-close)
- fp.certificate._compute_batch_ids: use empty recordset instead of
  False for Many2many computed fields
- fp_notification_template._collect_attachments: collapse attach_quotation
  + attach_sale_order into a single render so email doesn't double-attach
  the same PDF
- fp.operator.certification: SQL unique on computed state was unreliable;
  added explicit `revoked` boolean, made state pure-compute, replaced
  SQL constraint with @api.constrains that checks active-only uniqueness;
  has_active_cert now reads revoked + expires_date directly (no stale
  stored state between nightly recomputes)

Two missing invoice strategies implemented + 1 pre-existing deposit bug fix:
- Progress Billing: new x_fc_progress_initial_percent field on sale.order;
  _create_progress_initial_invoice bills the configured % on SO confirm
  via down-payment wizard, _create_final_balance_invoice bills the
  remainder on delivery
- Net Terms: no invoice on confirm; full invoice auto-created when
  fusion.plating.delivery.action_mark_delivered fires
- Fix for deposit (pre-existing, silent): sale.advance.payment.inv
  reads active_ids at wizard-create time, not on create_invoices();
  context was being set on the wrong call, so every deposit attempt
  raised "Expected singleton" and message-posted to chatter instead
  of actually invoicing
- New fusion_plating_invoicing/models/fp_delivery.py hooks
  action_mark_delivered to dispatch final invoice for progress/net_terms
- fp.direct.order.wizard + SO form surface the progress_initial_percent
  field (conditional on strategy)

Report styling cleanup:
- Hide DISCOUNT column from sale + invoice landscape reports unless at
  least one line has a non-zero discount; colspan auto-adjusts
- Replace hardcoded #0066a1 in all reports with company.primary_color
  driven by doc.company_id → company → user.company_id fallback chain,
  with #1d1f1e as ultimate fallback; new .fp-header-primary class
  exposes the colour for inline section headers (CARGO DESCRIPTION,
  PAYMENT DETAILS, OPERATOR SIGN-OFF, etc.) so they retint with the
  company theme without template edits

Certificate of Conformance — formal ENTECH-style rebuild:
- New res.company fields: x_fc_owner_user_id (default signer, sig from
  hr.employee.signature), x_fc_coc_signature_override (manual upload),
  x_fc_{nadcap,as9100,cgp}_logo + _active toggles for accreditation
  badges
- New res.config.settings section "Fusion Plating" exposing the above
  as configurable blocks; manager-only menu under Configuration →
  Fusion Plating Settings
- New fp.certificate fields: nc_quantity, customer_job_no,
  contact_partner_id (child contact for Name / Email / Phone block)
- New report_coc_en + report_coc_fr templates (primary): custom header
  (company contact | accreditations | company logo), bilingual labels
  per variant, customer info block with customer logo, 3-column cert
  info table, 6-column line-item table (Part # | Process | Customer
  PO | Shipped | NC Qty | Customer Job No.), signature image + bordered
  certification statement, footer "Fusion Plating by Nexa Systems"
- Legacy report_coc + report_coc_portrait kept for existing portal-job
  bindings (no behaviour change)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-17 01:18:22 -04:00
parent 6658544f85
commit a623c6684d
27 changed files with 822 additions and 237 deletions

View File

@@ -38,7 +38,7 @@
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- Certificate of Conformance — Portrait -->
<!-- Certificate of Conformance — Portrait (legacy, Portal Job) -->
<record id="action_report_coc_portrait" model="ir.actions.report">
<field name="name">Certificate of Conformance (Portrait)</field>
<field name="model">fusion.plating.portal.job</field>
@@ -50,6 +50,34 @@
<field name="binding_type">report</field>
</record>
<!-- ============================================================= -->
<!-- Formal Certificate of Conformance — English -->
<!-- ============================================================= -->
<record id="action_report_coc_en" model="ir.actions.report">
<field name="name">Certificate of Conformance (English)</field>
<field name="model">fp.certificate</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_coc_en</field>
<field name="report_file">fusion_plating_reports.report_coc_en</field>
<field name="print_report_name">'CoC EN - %s' % object.name</field>
<field name="binding_model_id" ref="fusion_plating_certificates.model_fp_certificate"/>
<field name="binding_type">report</field>
</record>
<!-- ============================================================= -->
<!-- Formal Certificate of Conformance — French -->
<!-- ============================================================= -->
<record id="action_report_coc_fr" model="ir.actions.report">
<field name="name">Certificat de Conformité (Français)</field>
<field name="model">fp.certificate</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_coc_fr</field>
<field name="report_file">fusion_plating_reports.report_coc_fr</field>
<field name="print_report_name">'CoC FR - %s' % object.name</field>
<field name="binding_model_id" ref="fusion_plating_certificates.model_fp_certificate"/>
<field name="binding_type">report</field>
</record>
<!-- ============================================================= -->
<!-- 2. Non-Conformance Report -->
<!-- ============================================================= -->

View File

@@ -3,18 +3,30 @@
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
Shared CSS for all Fusion Plating reports (portrait + landscape).
The primary colour is driven by the active company's
res.company.primary_color field (Settings → Company → Report Layout),
falling back to #1d1f1e when the company has no brand colour set.
To keep section-header markup concise in individual report files,
a utility class `.fp-header-primary` is exposed — apply that class
to any `<th>` or `<td>` that should render as a primary-coloured
section banner (e.g. CARGO DESCRIPTION, PAYMENT DETAILS).
-->
<odoo>
<!-- ============================================================= -->
<!-- Portrait Styles -->
<!-- ============================================================= -->
<template id="fp_portrait_styles">
<t t-set="_fp_company" t-value="doc.company_id if doc and 'company_id' in doc._fields else (company if company else user.company_id)"/>
<t t-set="fp_primary" t-value="(_fp_company.primary_color if _fp_company else False) or '#1d1f1e'"/>
<style>
.fp-report { font-family: Arial, sans-serif; font-size: 10pt; color: #000; }
.fp-report table { width: 100%; border-collapse: collapse; margin-bottom: 10px; }
.fp-report table.bordered, .fp-report table.bordered th, .fp-report table.bordered td { border: 1px solid #000; }
.fp-report th { background-color: #0066a1; color: white; padding: 6px 8px; font-weight: bold; text-align: center; font-size: 9pt; }
.fp-report th { background-color: <t t-out="fp_primary"/>; color: white; padding: 6px 8px; font-weight: bold; text-align: center; font-size: 9pt; }
.fp-report td { padding: 6px 8px; vertical-align: top; font-size: 10pt; }
.fp-report .text-center { text-align: center; }
.fp-report .text-end { text-align: right; }
@@ -23,12 +35,13 @@
.fp-report .client-bg { background-color: #fff3e0; }
.fp-report .section-row { background-color: #f0f0f0; font-weight: bold; }
.fp-report .note-row { font-style: italic; color: #555; font-size: 9pt; }
.fp-report h4 { color: #0066a1; margin: 0 0 15px 0; font-size: 16pt; }
.fp-report h4 { color: <t t-out="fp_primary"/>; margin: 0 0 15px 0; font-size: 16pt; }
.fp-report .totals-table { border: 1px solid #000; border-collapse: collapse; }
.fp-report .totals-table td { border: 1px solid #000; padding: 6px 8px; }
.fp-report .info-header { background-color: #f5f5f5; color: #333; }
.fp-report .adp-header { background-color: #e3f2fd; color: #333; }
.fp-report .highlight-box { border: 2px solid #0066a1; background-color: #eaf2f8; padding: 10px; margin: 10px 0; }
.fp-report .highlight-box { border: 2px solid <t t-out="fp_primary"/>; background-color: #eaf2f8; padding: 10px; margin: 10px 0; }
.fp-report .fp-header-primary { background-color: <t t-out="fp_primary"/>; color: white; }
.fp-report .paid-stamp { color: #28a745; font-size: 36pt; font-weight: bold; border: 4px solid #28a745; padding: 10px 20px; transform: rotate(-8deg); display: inline-block; }
.fp-report .status-ok { color: #2e7d32; font-weight: bold; }
.fp-report .status-warning { color: #f57f17; font-weight: bold; }
@@ -43,11 +56,13 @@
<!-- Landscape Styles -->
<!-- ============================================================= -->
<template id="fp_landscape_styles">
<t t-set="_fp_company" t-value="doc.company_id if doc and 'company_id' in doc._fields else (company if company else user.company_id)"/>
<t t-set="fp_primary" t-value="(_fp_company.primary_color if _fp_company else False) or '#1d1f1e'"/>
<style>
.fp-landscape { font-family: Arial, sans-serif; font-size: 11pt; color: #000; }
.fp-landscape table { width: 100%; border-collapse: collapse; margin-bottom: 12px; }
.fp-landscape table.bordered, .fp-landscape table.bordered th, .fp-landscape table.bordered td { border: 1px solid #000; }
.fp-landscape th { background-color: #0066a1; color: white; padding: 8px 10px; font-weight: bold; font-size: 10pt; }
.fp-landscape th { background-color: <t t-out="fp_primary"/>; color: white; padding: 8px 10px; font-weight: bold; font-size: 10pt; }
.fp-landscape td { padding: 6px 8px; vertical-align: top; font-size: 10pt; }
.fp-landscape .text-center { text-align: center; }
.fp-landscape .text-end { text-align: right; }
@@ -56,12 +71,13 @@
.fp-landscape .client-bg { background-color: #fff3e0; }
.fp-landscape .section-row { background-color: #f0f0f0; font-weight: bold; }
.fp-landscape .note-row { font-style: italic; color: #555; }
.fp-landscape h2 { color: #0066a1; margin: 10px 0; font-size: 18pt; }
.fp-landscape h2 { color: <t t-out="fp_primary"/>; margin: 10px 0; font-size: 18pt; }
.fp-landscape .info-table td { padding: 8px 12px; font-size: 11pt; }
.fp-landscape .info-table th { background-color: #f5f5f5; color: #333; font-size: 10pt; padding: 6px 12px; }
.fp-landscape .totals-table { border: 1px solid #000; }
.fp-landscape .totals-table td { border: 1px solid #000; padding: 8px 12px; font-size: 11pt; }
.fp-landscape .highlight-box { border: 2px solid #0066a1; background-color: #eaf2f8; padding: 10px; margin: 10px 0; }
.fp-landscape .highlight-box { border: 2px solid <t t-out="fp_primary"/>; background-color: #eaf2f8; padding: 10px; margin: 10px 0; }
.fp-landscape .fp-header-primary { background-color: <t t-out="fp_primary"/>; color: white; }
.fp-landscape .paid-stamp { color: #28a745; font-size: 42pt; font-weight: bold; border: 4px solid #28a745; padding: 10px 20px; transform: rotate(-8deg); display: inline-block; }
.fp-landscape .status-ok { color: #2e7d32; font-weight: bold; }
.fp-landscape .status-warning { color: #f57f17; font-weight: bold; }

View File

@@ -2,15 +2,355 @@
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Certificate of Conformance — Portal Job (Portrait + Landscape).
The original `report_coc` id is kept as the landscape variant so existing
bindings keep working; a new `report_coc_portrait` variant is added.
Fusion Plating — Certificate of Conformance
Four variants:
- report_coc_en English, portrait, ENTECH-style formal cert (primary)
- report_coc_fr French, portrait, mirror of EN
- report_coc_portrait Legacy portrait (kept for existing bindings)
- report_coc Legacy landscape (kept for existing bindings)
Settings sourced from res.company (Settings → Fusion Plating):
- x_fc_owner_user_id.employee_ids[:1].signature Default signer image
- x_fc_coc_signature_override Optional override image
- x_fc_{nadcap,as9100,cgp}_logo + _active Accreditation badges
-->
<odoo>
<!-- ============================================================= -->
<!-- PORTRAIT -->
<!-- ============================================================= -->
<!-- ================================================================== -->
<!-- Shared CoC body macro (English / French switched via LANG) -->
<!-- ================================================================== -->
<template id="coc_body">
<t t-set="is_fr" t-value="LANG == 'fr'"/>
<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>
<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; }
.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; }
.fp-coc th { background-color: #ededed; font-weight: bold; padding: 6px 8px; font-size: 9pt; text-align: center; }
.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 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 .logo-box img { max-height: 2.5cm; max-width: 4cm; }
.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>
<div class="fp-coc">
<!-- ============================= -->
<!-- HEADER -->
<!-- ============================= -->
<h1 t-if="not is_fr">Certificate of Conformance</h1>
<h1 t-if="is_fr">Certificat de Conformité</h1>
<table>
<tr>
<td style="width: 33%;" class="hdr-company">
<strong t-field="company.name"/><br/>
<t t-if="company.email"><t t-esc="company.email"/><br/></t>
<t t-if="company.phone"><t t-esc="company.phone"/><br/></t>
<t t-if="company.partner_id.street"><t t-esc="company.partner_id.street"/><br/></t>
<t t-if="company.partner_id.street2"><t t-esc="company.partner_id.street2"/><br/></t>
<span t-if="company.partner_id.city"><t t-esc="company.partner_id.city"/></span>
<span t-if="company.partner_id.state_id">, <t t-esc="company.partner_id.state_id.name"/></span>
<span t-if="company.partner_id.zip"> <t t-esc="company.partner_id.zip"/></span><br/>
<t t-if="company.partner_id.country_id"><t t-esc="company.partner_id.country_id.name.upper()"/></t>
</td>
<td style="width: 34%;" class="accreditations">
<t t-if="company.x_fc_nadcap_active and company.x_fc_nadcap_logo">
<img t-att-src="'data:image/png;base64,%s' % company.x_fc_nadcap_logo.decode()"
alt="Nadcap Accredited"/>
</t>
<t t-if="company.x_fc_as9100_active and company.x_fc_as9100_logo">
<img t-att-src="'data:image/png;base64,%s' % company.x_fc_as9100_logo.decode()"
alt="AS9100 / ISO 9001"/>
</t>
<t t-if="company.x_fc_cgp_active and company.x_fc_cgp_logo">
<img t-att-src="'data:image/png;base64,%s' % company.x_fc_cgp_logo.decode()"
alt="Controlled Goods Program"/>
</t>
</td>
<td style="width: 33%;" class="logo-box">
<img t-if="company.logo"
t-att-src="'data:image/png;base64,%s' % company.logo.decode()"
alt=""/>
</td>
</tr>
</table>
<hr class="heavy"/>
<!-- ============================= -->
<!-- CUSTOMER BLOCK -->
<!-- ============================= -->
<table style="margin-top: 6px;">
<tr>
<td style="width: 40%; vertical-align: top;">
<div>
<strong t-if="not is_fr">Customer Name: </strong>
<strong t-if="is_fr">Nom du client : </strong>
<t t-esc="doc.partner_id.name or ''"/>
</div>
<div style="margin-top: 4px;">
<strong t-if="not is_fr">Customer Address:</strong>
<strong t-if="is_fr">Adresse du client :</strong>
<br/>
<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>
<span t-if="doc.partner_id.city"><t t-esc="doc.partner_id.city"/></span>
<span t-if="doc.partner_id.state_id">, <t t-esc="doc.partner_id.state_id.name"/></span>
<span t-if="doc.partner_id.zip"> <t t-esc="doc.partner_id.zip"/></span>
</div>
</td>
<td style="width: 40%; vertical-align: top;">
<t t-set="contact" t-value="doc.contact_partner_id or doc.partner_id"/>
<div>
<strong t-if="not is_fr">Contact Name: </strong>
<strong t-if="is_fr">Nom du contact : </strong>
<t t-esc="contact.name or ''"/>
</div>
<div>
<strong t-if="not is_fr">Customer Email: </strong>
<strong t-if="is_fr">Courriel : </strong>
<t t-esc="contact.email or '-'"/>
</div>
<div>
<strong t-if="not is_fr">Customer Phone: </strong>
<strong t-if="is_fr">Téléphone : </strong>
<t t-esc="contact.phone or '-'"/>
</div>
</td>
<td style="width: 20%; vertical-align: top;" class="logo-box">
<img t-if="doc.partner_id.image_1920"
t-att-src="'data:image/png;base64,%s' % doc.partner_id.image_1920.decode()"
alt=""/>
</td>
</tr>
</table>
<hr class="heavy"/>
<!-- ============================= -->
<!-- CERTIFICATION INFO -->
<!-- ============================= -->
<table class="bordered" style="margin-top: 10px;">
<thead>
<tr>
<th style="width: 33%;">
<span t-if="not is_fr">Date of Certification</span>
<span t-if="is_fr">Date du certificat</span>
</th>
<th style="width: 34%;">
<span t-if="not is_fr">Generated By</span>
<span t-if="is_fr">Créé par</span>
</th>
<th style="width: 33%;">
<span t-if="not is_fr">Work Order #</span>
<span t-if="is_fr">Bon de travail</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">
<span t-field="doc.issue_date" t-options="{'widget': 'date'}"/>
</td>
<td class="text-center">
<t t-esc="(doc.issued_by_id.name if doc.issued_by_id else '') or ''"/>
</td>
<td class="text-center">
<t t-esc="doc.entech_wo_number or (doc.production_id.name if doc.production_id else '') or '-'"/>
</td>
</tr>
</tbody>
</table>
<!-- ============================= -->
<!-- LINE ITEM / QUANTITIES TABLE -->
<!-- ============================= -->
<div class="text-end small-label" style="margin-top: 12px;">
<strong t-if="not is_fr">Quantities</strong>
<strong t-if="is_fr">Quantités</strong>
</div>
<table class="bordered">
<thead>
<tr>
<th style="width: 20%;">
<span t-if="not is_fr">Part Number / Line Item</span>
<span t-if="is_fr">No. de pièce / Ligne</span>
</th>
<th style="width: 32%;">
<span t-if="not is_fr">Process</span>
<span t-if="is_fr">Procédé</span>
</th>
<th style="width: 16%;">
<span t-if="not is_fr">Customer PO</span>
<span t-if="is_fr">Bon de commande</span>
</th>
<th style="width: 10%;">
<span t-if="not is_fr">Shipped</span>
<span t-if="is_fr">Expédié</span>
</th>
<th style="width: 10%;">
<span t-if="not is_fr">NC Qty</span>
<span t-if="is_fr">Qté NC</span>
</th>
<th style="width: 12%;">
<span t-if="not is_fr">Customer Job No.</span>
<span t-if="is_fr">Bon de travail client</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">
<t t-esc="doc.part_number or '-'"/>
</td>
<td>
<t t-esc="doc.process_description or ''"/>
<t t-if="doc.spec_reference">
<br/><em t-esc="doc.spec_reference"/>
</t>
</td>
<td class="text-center">
<t t-esc="doc.po_number or '-'"/>
</td>
<td class="text-center">
<t t-esc="doc.quantity_shipped or 0"/>
</td>
<td class="text-center">
<t t-esc="doc.nc_quantity or 0"/>
</td>
<td class="text-center">
<t t-esc="doc.customer_job_no or '-'"/>
</td>
</tr>
</tbody>
</table>
<!-- ============================= -->
<!-- SIGNATURE + STATEMENT -->
<!-- ============================= -->
<table style="margin-top: 28px;">
<tr>
<td style="width: 50%; vertical-align: top;">
<div>
<strong t-if="not is_fr">Certified By:</strong>
<strong t-if="is_fr">Certifié par :</strong>
</div>
<div style="min-height: 3cm; margin-top: 8px;">
<img t-if="signature_img"
class="signature-img"
t-att-src="'data:image/png;base64,%s' % signature_img.decode()"
alt=""/>
</div>
<div style="margin-top: 6px; border-top: 1px solid #000; padding-top: 4px;">
<strong t-if="not is_fr">Name: </strong>
<strong t-if="is_fr">Nom : </strong>
<t t-esc="signer_name or ''"/>
</div>
</td>
<td style="width: 50%; vertical-align: top; padding-left: 16px;">
<div class="cert-statement-box">
<h4 t-if="not is_fr">Certification Statement</h4>
<h4 t-if="is_fr">Énoncé de conformité</h4>
<p t-if="not is_fr" style="margin: 0;">
This is to certify that the items listed herein have
been processed, inspected and tested in accordance
with your Purchase Order, drawings and specification
requirements. All chemistry used in this order is
Made in Canada. There is no Mercury used in the
processing of this order.
</p>
<p t-if="is_fr" style="margin: 0;">
Ceci est pour certifier que les articles inscrits ont
été procédés, inspectés et mis à l'essai selon votre
bon de commande, dessins et spécifications. Tous les
produits chimiques utilisés dans cette commande sont
fabriqués au Canada. Il n'y a pas de mercure dans les
procédés de fabrication de cette commande.
</p>
</div>
</td>
</tr>
</table>
<!-- ============================= -->
<!-- FOOTER -->
<!-- ============================= -->
<div class="fp-footer-brand">
<t t-if="not is_fr">
Cert Created At:
<span t-esc="context_timestamp(doc.create_date).strftime('%Y-%m-%d %H:%M') if doc.create_date else ''"/>
· Fusion Plating by Nexa Systems
</t>
<t t-if="is_fr">
Certificat créé le :
<span t-esc="context_timestamp(doc.create_date).strftime('%Y-%m-%d %H:%M') if doc.create_date else ''"/>
· Fusion Plating par Nexa Systems
</t>
</div>
</div>
</template>
<!-- ================================================================== -->
<!-- English CoC -->
<!-- ================================================================== -->
<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'"/>
<div class="page">
<t t-call="fusion_plating_reports.coc_body"/>
</div>
</t>
</t>
</t>
</template>
<!-- ================================================================== -->
<!-- French CoC -->
<!-- ================================================================== -->
<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'"/>
<div class="page">
<t t-call="fusion_plating_reports.coc_body"/>
</div>
</t>
</t>
</t>
</template>
<!-- ================================================================== -->
<!-- Legacy portrait (bound to fusion.plating.portal.job) -->
<!-- ================================================================== -->
<template id="report_coc_portrait">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
@@ -18,13 +358,10 @@
<t t-call="fusion_plating_reports.fp_portrait_styles"/>
<div class="fp-report">
<div class="page">
<h4>
Certificate of Conformance —
<span t-field="doc.name"/>
</h4>
<!-- Job info -->
<table class="bordered">
<thead>
<tr>
@@ -43,81 +380,6 @@
</tr>
</tbody>
</table>
<!-- Customer block -->
<table class="bordered">
<thead>
<tr><th colspan="2" style="background-color: #0066a1; color: white;">CUSTOMER</th></tr>
</thead>
<tbody>
<tr>
<td style="width: 30%; background-color: #f5f5f5; font-weight: bold;">Name</td>
<td><span t-field="doc.partner_id.name"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Address</td>
<td>
<span t-field="doc.partner_id" t-options="{'widget': 'contact', 'fields': ['address'], 'no_marker': True}"/>
</td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Tracking Reference</td>
<td><span t-esc="doc.tracking_ref or '-'"/></td>
</tr>
</tbody>
</table>
<!-- Processes -->
<t t-if="doc.process_type_ids">
<table class="bordered">
<thead>
<tr><th style="background-color: #0066a1; color: white;">PROCESSES APPLIED</th></tr>
</thead>
<tbody>
<tr>
<td>
<t t-foreach="doc.process_type_ids" t-as="pt">
<span t-out="pt.name"/>
<t t-if="not pt_last">, </t>
</t>
</td>
</tr>
</tbody>
</table>
</t>
<!-- Certification statement -->
<div class="highlight-box">
This certifies that the above items were processed in accordance with
applicable specifications and meet all requirements as stated in the
purchase order. All work was performed in compliance with the quality
management system.
</div>
<!-- Notes -->
<t t-if="doc.notes">
<table class="bordered">
<tr class="section-row"><td>NOTES</td></tr>
<tr><td><t t-out="doc.notes"/></td></tr>
</table>
</t>
<!-- Signature block -->
<div class="row" style="margin-top: 30px;">
<div class="col-6">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Quality Manager (Signature)</div>
</div>
</div>
<div class="col-6">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Date</div>
</div>
</div>
</div>
</div>
</div>
</t>
@@ -125,9 +387,9 @@
</t>
</template>
<!-- ============================================================= -->
<!-- LANDSCAPE (legacy id `report_coc` kept for existing bindings) -->
<!-- ============================================================= -->
<!-- ================================================================== -->
<!-- Legacy landscape (bound to fusion.plating.portal.job) -->
<!-- ================================================================== -->
<template id="report_coc">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
@@ -139,8 +401,6 @@
Certificate of Conformance
<span t-field="doc.name"/>
</h2>
<!-- Job Info -->
<table class="bordered info-table">
<thead><tr>
<th>JOB REF</th>
@@ -149,7 +409,6 @@
<th>RECEIVED</th>
<th>SHIP DATE</th>
<th>TRACKING REF</th>
<th>STATUS</th>
</tr></thead>
<tbody><tr>
<td class="text-center"><span t-field="doc.name"/></td>
@@ -158,76 +417,8 @@
<td class="text-center"><span t-field="doc.received_date" t-options="{'widget': 'date'}"/></td>
<td class="text-center"><span t-field="doc.actual_ship_date" t-options="{'widget': 'date'}"/></td>
<td class="text-center"><span t-field="doc.tracking_ref"/></td>
<td class="text-center"><span t-field="doc.state"/></td>
</tr></tbody>
</table>
<!-- Customer Address -->
<table class="bordered">
<thead><tr>
<th colspan="2">CUSTOMER DETAILS</th>
</tr></thead>
<tbody>
<tr>
<td style="width:30%; font-weight:bold;">Name</td>
<td><span t-field="doc.partner_id.name"/></td>
</tr>
<tr>
<td style="font-weight:bold;">Address</td>
<td>
<span t-field="doc.partner_id" t-options="{'widget': 'contact', 'fields': ['address'], 'no_marker': True}"/>
</td>
</tr>
</tbody>
</table>
<!-- Processes -->
<table class="bordered" t-if="doc.process_type_ids">
<thead><tr>
<th>PROCESSES APPLIED</th>
</tr></thead>
<tbody><tr>
<td>
<t t-foreach="doc.process_type_ids" t-as="pt">
<span t-out="pt.name"/>
<t t-if="not pt_last">, </t>
</t>
</td>
</tr></tbody>
</table>
<!-- Certification Statement -->
<table class="bordered">
<tr class="section-row"><td>CERTIFICATION</td></tr>
<tr><td style="padding: 16px 12px; font-size: 11pt;">
This certifies that the above items were processed in accordance
with applicable specifications and meet all requirements as stated
in the purchase order. All work was performed in compliance with
the quality management system.
</td></tr>
</table>
<!-- Notes -->
<t t-if="doc.notes">
<table class="bordered">
<tr class="section-row"><td>NOTES</td></tr>
<tr><td><t t-out="doc.notes"/></td></tr>
</table>
</t>
<!-- Signature Block -->
<table class="bordered" style="margin-top: 30px;">
<tbody>
<tr>
<td style="width:50%; height: 60px; vertical-align: bottom; font-weight: bold;">
Quality Manager Signature: ___________________________
</td>
<td style="width:50%; height: 60px; vertical-align: bottom; font-weight: bold;">
Date: ___________________________
</td>
</tr>
</tbody>
</table>
</div>
</div>
</t>

View File

@@ -111,7 +111,7 @@
<table class="bordered">
<thead>
<tr>
<th colspan="4" style="background-color: #0066a1; color: white;">CARGO DESCRIPTION</th>
<th colspan="4" class="fp-header-primary">CARGO DESCRIPTION</th>
</tr>
<tr>
<th style="width: 12%;">PACKAGES</th>
@@ -283,7 +283,7 @@
<table class="bordered">
<thead>
<tr>
<th colspan="6" style="background-color: #0066a1; color: white;">CARGO DESCRIPTION</th>
<th colspan="6" class="fp-header-primary">CARGO DESCRIPTION</th>
</tr>
<tr>
<th style="width: 10%;">PACKAGES</th>

View File

@@ -266,7 +266,9 @@
</tbody>
</table>
<!-- Lines -->
<!-- Lines — hide discount column unless at least one line has a discount -->
<t t-set="has_discount" t-value="any(l.discount for l in doc.invoice_line_ids)"/>
<t t-set="col_count" t-value="8 if has_discount else 7"/>
<table class="bordered">
<thead>
<tr>
@@ -275,7 +277,7 @@
<th style="width: 8%;">QTY</th>
<th style="width: 8%;">UOM</th>
<th style="width: 12%;">UNIT PRICE</th>
<th style="width: 10%;">DISCOUNT</th>
<th t-if="has_discount" style="width: 10%;">DISCOUNT</th>
<th style="width: 10%;">TAXES</th>
<th style="width: 10%;">AMOUNT</th>
</tr>
@@ -283,10 +285,10 @@
<tbody>
<t t-foreach="doc.invoice_line_ids" t-as="line">
<t t-if="line.display_type == 'line_section'">
<tr class="section-row"><td colspan="8"><strong t-field="line.name"/></td></tr>
<tr class="section-row"><td t-att-colspan="col_count"><strong t-field="line.name"/></td></tr>
</t>
<t t-elif="line.display_type == 'line_note'">
<tr class="note-row"><td colspan="8"><span t-field="line.name"/></td></tr>
<tr class="note-row"><td t-att-colspan="col_count"><span t-field="line.name"/></td></tr>
</t>
<t t-elif="not line.display_type">
<tr>
@@ -305,7 +307,7 @@
<td class="text-end">
<span t-field="line.price_unit" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
<td class="text-center">
<td t-if="has_discount" class="text-center">
<t t-if="line.discount"><span t-esc="line.discount"/>%</t>
<t t-else="">-</t>
</td>

View File

@@ -59,7 +59,7 @@
<table class="bordered">
<thead>
<tr>
<th colspan="2" style="background-color: #0066a1; color: white;">PAYMENT DETAILS</th>
<th colspan="2" class="fp-header-primary">PAYMENT DETAILS</th>
</tr>
</thead>
<tbody>
@@ -93,7 +93,7 @@
<table class="bordered">
<thead>
<tr>
<th colspan="3" style="background-color: #0066a1; color: white;">APPLIED TO INVOICES</th>
<th colspan="3" class="fp-header-primary">APPLIED TO INVOICES</th>
</tr>
<tr>
<th style="width: 34%;">INVOICE #</th>
@@ -220,7 +220,7 @@
<table class="bordered">
<thead>
<tr>
<th colspan="5" style="background-color: #0066a1; color: white;">APPLIED TO INVOICES</th>
<th colspan="5" class="fp-header-primary">APPLIED TO INVOICES</th>
</tr>
<tr>
<th style="width: 20%;">INVOICE #</th>

View File

@@ -131,7 +131,7 @@
<td class="text-center">
<span t-esc="int(line.product_uom_qty) if line.product_uom_qty == int(line.product_uom_qty) else line.product_uom_qty"/>
</td>
<td class="text-center"><span t-field="line.product_uom"/></td>
<td class="text-center"><span t-field="line.product_uom_id"/></td>
<td class="text-end">
<span t-field="line.price_unit" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
@@ -327,7 +327,9 @@
</table>
</t>
<!-- Order lines -->
<!-- Order lines — hide discount column unless at least one line has a discount -->
<t t-set="has_discount" t-value="any(l.discount for l in doc.order_line)"/>
<t t-set="col_count" t-value="8 if has_discount else 7"/>
<table class="bordered">
<thead>
<tr>
@@ -336,7 +338,7 @@
<th style="width: 8%;">QTY</th>
<th style="width: 8%;">UOM</th>
<th style="width: 12%;">UNIT PRICE</th>
<th style="width: 10%;">DISCOUNT</th>
<th t-if="has_discount" style="width: 10%;">DISCOUNT</th>
<th style="width: 10%;">TAXES</th>
<th style="width: 10%;">AMOUNT</th>
</tr>
@@ -344,10 +346,10 @@
<tbody>
<t t-foreach="doc.order_line" t-as="line">
<t t-if="line.display_type == 'line_section'">
<tr class="section-row"><td colspan="8"><strong t-field="line.name"/></td></tr>
<tr class="section-row"><td t-att-colspan="col_count"><strong t-field="line.name"/></td></tr>
</t>
<t t-elif="line.display_type == 'line_note'">
<tr class="note-row"><td colspan="8"><span t-field="line.name"/></td></tr>
<tr class="note-row"><td t-att-colspan="col_count"><span t-field="line.name"/></td></tr>
</t>
<t t-elif="not line.display_type">
<tr>
@@ -362,11 +364,11 @@
<td class="text-center">
<span t-esc="int(line.product_uom_qty) if line.product_uom_qty == int(line.product_uom_qty) else line.product_uom_qty"/>
</td>
<td class="text-center"><span t-field="line.product_uom"/></td>
<td class="text-center"><span t-field="line.product_uom_id"/></td>
<td class="text-end">
<span t-field="line.price_unit" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
<td class="text-center">
<td t-if="has_discount" class="text-center">
<t t-if="line.discount"><span t-esc="line.discount"/>%</t>
<t t-else="">-</t>
</td>

View File

@@ -86,7 +86,7 @@
<!-- Process parameters -->
<table class="bordered">
<thead>
<tr><th colspan="2" style="background-color: #0066a1; color: white;">PROCESS PARAMETERS</th></tr>
<tr><th colspan="2" class="fp-header-primary">PROCESS PARAMETERS</th></tr>
</thead>
<tbody>
<tr>
@@ -135,7 +135,7 @@
<t t-if="doc.operation_id and doc.operation_id.note">
<table class="bordered">
<thead>
<tr><th style="background-color: #0066a1; color: white;">OPERATION INSTRUCTIONS</th></tr>
<tr><th class="fp-header-primary">OPERATION INSTRUCTIONS</th></tr>
</thead>
<tbody>
<tr><td><div t-field="doc.operation_id.note"/></td></tr>
@@ -172,7 +172,7 @@
<table class="bordered">
<thead>
<tr>
<th colspan="3" style="background-color: #0066a1; color: white;">OPERATOR SIGN-OFF</th>
<th colspan="3" class="fp-header-primary">OPERATOR SIGN-OFF</th>
</tr>
<tr>
<th style="width: 40%;">OPERATOR</th>
@@ -272,7 +272,7 @@
<div class="col-6">
<table class="bordered">
<thead>
<tr><th colspan="2" style="background-color: #0066a1; color: white;">PROCESS PARAMETERS</th></tr>
<tr><th colspan="2" class="fp-header-primary">PROCESS PARAMETERS</th></tr>
</thead>
<tbody>
<tr>
@@ -314,7 +314,7 @@
<table class="bordered">
<thead>
<tr>
<th colspan="4" style="background-color: #0066a1; color: white;">CHEMISTRY TARGETS</th>
<th colspan="4" class="fp-header-primary">CHEMISTRY TARGETS</th>
</tr>
<tr>
<th>PARAM</th>
@@ -342,7 +342,7 @@
<t t-if="doc.operation_id and doc.operation_id.note">
<table class="bordered">
<thead>
<tr><th style="background-color: #0066a1; color: white;">OPERATION INSTRUCTIONS</th></tr>
<tr><th class="fp-header-primary">OPERATION INSTRUCTIONS</th></tr>
</thead>
<tbody>
<tr><td><div t-field="doc.operation_id.note"/></td></tr>
@@ -354,7 +354,7 @@
<table class="bordered" style="margin-top: 15px;">
<thead>
<tr>
<th colspan="5" style="background-color: #0066a1; color: white;">OPERATOR SIGN-OFF</th>
<th colspan="5" class="fp-header-primary">OPERATOR SIGN-OFF</th>
</tr>
<tr>
<th style="width: 25%;">OPERATOR</th>