chore(plating): de-dash shipped code + intake-neutral customer emails

Replace em-dashes and en-dashes with hyphens across 789 shipped source
files (py/xml/js/scss) so the delivered module reads as human-written;
em-dashes had become a recognizable AI-generated tell. Internal .md dev
notes are excluded. The WO-sticker mojibake strippers keep their dash
search targets (now written — / –). No logic changes: comments
and display strings only; validated with py_compile + lxml parse.

Rewrite the 7 customer notification emails to be intake-neutral
(ship-in / drop-off / pickup) and repair-aware, and fix the Shipped
email documents line (packing slip vs bill of lading; certificate only
when issued). Subjects use a hyphen separator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-05 00:16:19 -04:00
parent c9eb61ee0c
commit 8c76a16366
789 changed files with 4692 additions and 4692 deletions

View File

@@ -3,7 +3,7 @@
Copyright 2026 Nexa Systems Inc.
License OPL-1
Sub 2 shared QWeb macro for customer-facing line rendering.
Sub 2 - shared QWeb macro for customer-facing line rendering.
Called from report_fp_sale, report_fp_invoice, report_fp_packing_slip,
report_fp_bol. Prints the customer's part number + revision + the
@@ -11,7 +11,7 @@
For non-part lines (rush fees, freight, expedite) where
x_fc_part_catalog_id is blank, falls back to Odoo's standard product
display safe for fee/service lines that shouldn't look like parts.
display - safe for fee/service lines that shouldn't look like parts.
Params expected in the calling context:
line - the sale.order.line / account.move.line / stock.picking line
@@ -22,7 +22,7 @@
<odoo>
<!-- ==========================================================
customer_line_header (legacy kept for backward compat)
customer_line_header (legacy - kept for backward compat)
Prints part number + revision + description in ONE td.
Reports written before the 2026-04-23 column split still
@@ -37,7 +37,7 @@
</template>
<!-- ==========================================================
customer_line_part_number just the part number + rev
customer_line_part_number - just the part number + rev
Renders as a single-line "PN-1234 Rev A" block, intended
for the "Part Number" td in customer-facing tables.
========================================================== -->
@@ -60,17 +60,17 @@
</t>
<t t-else="">
<!-- Fee / freight / non-part line: no part number to show -->
<span class="text-muted"></span>
<span class="text-muted">-</span>
</t>
</template>
<!-- ==========================================================
customer_line_description customer-facing description
customer_line_description - customer-facing description
plus serial + thickness only.
Per client request (2026-04-29): customer-facing reports
show ONLY description, serial, and thickness. Job # was
previously shown here but is internal-only it lives on
previously shown here but is internal-only - it lives on
the traveller / WO sticker / packing slip header, not on
what the customer sees. Process variant, treatment names,
and recipe codes deliberately don't render either.

View File

@@ -24,7 +24,7 @@
</record>
<!-- ============================================================= -->
<!-- CoC paper format zero header/footer band so the title -->
<!-- CoC paper format - zero header/footer band so the title -->
<!-- starts at the top of the page, not 35mm down. -->
<!-- ============================================================= -->
<record id="paperformat_fp_coc" model="report.paperformat">
@@ -61,7 +61,7 @@
the body clears the header via padding-top on the .fp-sale
wrapper (same way .fp-coc { padding-top: 20mm } clears it
on the CoC). DO NOT set margin_top to "the header height"
that forces the header to live entirely inside the
- that forces the header to live entirely inside the
reserved zone and any header growth = body overlap.
margin_top=8 + padding-on-wrapper is the proven shape. -->
<field name="margin_top">8</field>
@@ -94,7 +94,7 @@
</record>
<!-- ============================================================= -->
<!-- 1. Certificate of Conformance (Portal Job) Landscape -->
<!-- 1. Certificate of Conformance (Portal Job) - Landscape -->
<!-- ============================================================= -->
<record id="action_report_coc" model="ir.actions.report">
<field name="name">Certificate of Conformance (Landscape)</field>
@@ -108,7 +108,7 @@
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- Certificate of Conformance Portrait (legacy, Portal Job) -->
<!-- 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>
@@ -121,7 +121,7 @@
</record>
<!-- ============================================================= -->
<!-- Certificate of Conformance single bilingual cert. -->
<!-- Certificate of Conformance - single bilingual cert. -->
<!-- The body renders English + the French translation together, so -->
<!-- the separate French print action was removed 2026-05-28 (orphan -->
<!-- DB row + report_coc_fr template unlinked on entech). Compact -->
@@ -289,7 +289,7 @@
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- 12. Work Order Margin Report (mrp.production binding) REMOVED.
<!-- 12. Work Order Margin Report (mrp.production binding) - REMOVED.
Replaced by the fp.job-bound version in
fusion_plating_jobs/report/report_fp_job_margin.xml.
The QWeb template (report_wo_margin) remains in templates for
@@ -322,12 +322,12 @@
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- 14. Work Order Traveller (mrp.workorder bindings) REMOVED.
<!-- 14. Work Order Traveller (mrp.workorder bindings) - REMOVED.
Replaced by fp.job-bound traveller in
fusion_plating_jobs/report/report_fp_job_traveller.xml. -->
<!-- ============================================================= -->
<!-- 14b. Box Sticker 4x3" label for parts-box identification -->
<!-- 14b. Box Sticker - 4x3" label for parts-box identification -->
<!-- Prints an ENTECH-style sticker with a QR code that -->
<!-- warehouse staff scan to jump straight to the WO form. -->
<!-- ============================================================= -->
@@ -351,18 +351,18 @@
<field name="disable_shrinking" eval="True"/>
<!-- dpi=300 is the calibrated value for this paperformat; the
sticker inner's px-based geometry is tuned against it. Do
NOT bump (see CLAUDE.md rule 14 600 broke layout). -->
NOT bump (see CLAUDE.md rule 14 - 600 broke layout). -->
<field name="dpi">300</field>
</record>
<!-- WO Box Sticker (mrp.workorder + mrp.production bindings)
<!-- WO Box Sticker (mrp.workorder + mrp.production bindings) -
REMOVED. Replaced by the fp.job-bound version in
fusion_plating_jobs/report/report_fp_job_sticker.xml.
The shared inner templates (report_fp_wo_sticker_inner /
report_fp_wo_sticker_defaults) stay registered because
fp.job + sale.order stickers both t-call them. -->
<!-- Same sticker bound to sale.order prints one sticker per
<!-- Same sticker bound to sale.order - prints one sticker per
order line that carries a part, so estimators / receiving can
hand them to the floor before fp.jobs even exist. Uses the
same paperformat (6x4") so estimators don't need to think
@@ -380,7 +380,7 @@
<field name="paperformat_id" ref="paperformat_fp_wo_sticker"/>
</record>
<!-- SO Internal sticker same layout, prints internal description
<!-- SO Internal sticker - same layout, prints internal description
instead of the customer-facing line.name. Shop-floor variant. -->
<record id="action_report_fp_so_sticker_internal" model="ir.actions.report">
<field name="name">Internal Sticker</field>
@@ -451,7 +451,7 @@
<!-- 17. Invoice (Portrait + Landscape) -->
<!-- ============================================================= -->
<record id="action_report_fp_invoice_portrait" model="ir.actions.report">
<field name="name">Invoice Plating (Portrait)</field>
<field name="name">Invoice - Plating (Portrait)</field>
<field name="model">account.move</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_invoice_portrait</field>
@@ -463,12 +463,12 @@
<!-- Same compact paperformat as the SO confirmation so the
inline custom header sits at the top of the page (not 40mm
down under Odoo's default margin). See CLAUDE.md
"wkhtmltopdf header overlap the CoC pattern". -->
"wkhtmltopdf header overlap - the CoC pattern". -->
<field name="paperformat_id" ref="paperformat_fp_a4_portrait"/>
</record>
<record id="action_report_fp_invoice_landscape" model="ir.actions.report">
<field name="name">Invoice Plating (Landscape)</field>
<field name="name">Invoice - Plating (Landscape)</field>
<field name="model">account.move</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_invoice_landscape</field>
@@ -509,7 +509,7 @@
</record>
<!-- ============================================================= -->
<!-- 19. Job Traveller Sales Order bindings only. -->
<!-- 19. Job Traveller - Sales Order bindings only. -->
<!-- The MO-bound bindings (mrp.production -> traveller) were -->
<!-- removed because fusion_plating_jobs ships the canonical -->
<!-- fp.job-bound traveller. The SO bindings stay; estimators -->

View File

@@ -8,13 +8,13 @@
Section-header band: #c1c1c1 (neutral grey) with #4e4e4e text
Document titles (h2/h4): #4e4e4e
Hardcoded used to follow `res.company.primary_color` but the
Hardcoded - used to follow `res.company.primary_color` but the
client wanted a uniform neutral palette across every FP report
regardless of company branding. `fp_primary` is kept in scope for
any per-report template that still wants the company colour.
To keep section-header markup concise in individual report files,
a utility class `.fp-header-primary` is exposed apply that class
a utility class `.fp-header-primary` is exposed - apply that class
to any `<th>` or `<td>` that should render as a section banner
(e.g. CARGO DESCRIPTION, PAYMENT DETAILS).
-->
@@ -31,7 +31,7 @@
/* Standard collapse + longhand borders + background-clip.
Tried border-collapse:separate with single-side-per-cell
(right+bottom on cell, top+left on table) to fix wkhtmltopdf's
slightly-lighter-verticals quirk but the `separate` model
slightly-lighter-verticals quirk - but the `separate` model
makes column widths drift between tables with different
column counts, so tables stacked on the page no longer
line up at the outer edges. Reverted. The collapse pattern
@@ -112,7 +112,7 @@
<style>
.fp-landscape { font-family: Arial, sans-serif; font-size: 10pt; color: #000; }
.fp-landscape table { width: 100%; border-collapse: collapse; border-spacing: 0; margin-bottom: 6px; }
/* Standard collapse + longhand + background-clip see comment in fp_portrait_styles. */
/* Standard collapse + longhand + background-clip - see comment in fp_portrait_styles. */
.fp-landscape table.bordered { border: 0; border-collapse: collapse; border-spacing: 0; }
.fp-landscape table.bordered th,
.fp-landscape table.bordered td {

View File

@@ -2,10 +2,10 @@
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating Certificate of Conformance
Fusion Plating - Certificate of Conformance
Design note:
Single bilingual CoC English and the French translation render
Single bilingual CoC - English and the French translation render
together. The body wraps in fp_external_layout_clean + a custom
coc_header (logo + Nadcap + title/barcode, mirroring the Sale Order)
instead of web.external_layout; coc_body_router picks the classic vs
@@ -21,7 +21,7 @@
<odoo>
<!-- ================================================================== -->
<!-- Shared CoC header logo + Nadcap + title/barcode. Mirrors the -->
<!-- Shared CoC header - logo + Nadcap + title/barcode. Mirrors the -->
<!-- Sale Order header (report_fp_sale.xml fp-sale-header-row): company -->
<!-- logo + address LEFT, Nadcap accreditation logo CENTRE, document -->
<!-- title + Code128 barcode RIGHT. Rendered once by the EN/FR wrappers -->
@@ -33,9 +33,9 @@
<t t-set="company_fax" t-value="company.partner_id.x_ff_fax_number if 'x_ff_fax_number' in company.partner_id._fields else False"/>
<t t-set="coc_barcode_uri" t-value="doc.env['ir.actions.report'].sudo().barcode_data_uri('Code128', doc.name, 600, 100) if doc.name else False"/>
<style>
/* Float-based 3-column header (avoid HTML tables the global
/* Float-based 3-column header (avoid HTML tables - the global
bordered-table cascade bleeds borders onto nested tables on
entech wkhtmltopdf; see CLAUDE.md). No bottom border
entech wkhtmltopdf; see CLAUDE.md). No bottom border -
matches the Sale Order header; spacing alone separates it
from the body. */
.fp-coc-header-row { overflow: hidden; margin-bottom: 14px;
@@ -106,7 +106,7 @@
</template>
<!-- ================================================================== -->
<!-- Shared CoC body rendered inside fp_external_layout_clean -->
<!-- Shared CoC body - rendered inside fp_external_layout_clean -->
<!-- ================================================================== -->
<template id="coc_body">
<t t-set="is_fr" t-value="LANG == 'fr'"/>
@@ -122,7 +122,7 @@
<t t-set="signer_name" t-value="(signer_user and signer_user.name) or ''"/>
<style>
/* No web.external_layout header band anymore the CoC
/* No web.external_layout header band anymore - the CoC
renders its own header (coc_header) above the body, the
same way the Sale Order does. So no top padding is
needed to clear an Odoo header zone. */
@@ -137,7 +137,7 @@
.fp-coc td { padding: 5px 8px; vertical-align: top; font-size: 8.5pt; }
.fp-coc .text-center { text-align: center; }
.fp-coc .text-end { text-align: right; }
/* Bilingual stacked column titles English (bold) over the
/* Bilingual stacked column titles - English (bold) over the
French translation (italic grey), same pattern as the SO
report's narrow-cell headers. Added 2026-05-28. */
.fp-coc .fp-bl-en-stk { display: block; font-weight: bold; }
@@ -155,7 +155,7 @@
.fp-coc .small-label { font-size: 7.5pt; opacity: 0.7; }
.fp-coc .brand-note { font-size: 7.5pt; color: #888; text-align: center;
margin-top: 10px; font-style: italic; }
/* Thickness block single outer border, internal-only
/* Thickness block - single outer border, internal-only
cell dividers so the title / metadata / image+readings
look like one connected section. No nested .bordered
class on inner tables; each cell explicitly draws the
@@ -195,7 +195,7 @@
per client request 2026-05-28; the Nadcap logo now
lives in the header, mirroring the Sale Order. -->
<!-- Customer block 2 columns: address | contact. The
<!-- Customer block - 2 columns: address | contact. The
customer-logo column was dropped 2026-05-28 (usually
empty; the company logo is now in the header). -->
<table class="bordered">
@@ -263,7 +263,7 @@
</tbody>
</table>
<!-- Line-item table the column headers already speak
<!-- Line-item table - the column headers already speak
for themselves (Shipped / NC Qty / etc.), so the
hovering "Quantities" caption above was just visual
noise. Removed 2026-05-21. Header row + body row
@@ -322,7 +322,7 @@
</tbody>
</table>
<!-- Thickness readings (Fischerscope XRF) full report
<!-- Thickness readings (Fischerscope XRF) - full report
block mirroring the original XDAL 600 export so the
customer/auditor sees the same context the gauge
produced: equipment, operator, calibration, product,
@@ -361,13 +361,13 @@
readings table mid-row on page 2. -->
<div class="fp-thickness-block">
<!-- 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 class="ftk-title">
<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>
</div>
<!-- Equipment metadata 4-column key/value grid.
<!-- Equipment metadata - 4-column key/value grid.
Per-cell border-right + per-row border-bottom
draw the internal grid; the outer perimeter
comes from .fp-thickness-block's border. Last
@@ -379,49 +379,49 @@
<span class="fp-bl-en">Equipment</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Équipement</span>
</td>
<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 class="ftk-label ftk-cell-divider" style="width: 18%;">
<span class="fp-bl-en">Calibration Std.</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Étalon</span>
</td>
<td style="width: 32%;"><t t-esc="calib or ''"/></td>
<td style="width: 32%;"><t t-esc="calib or '-'"/></td>
</tr>
<tr class="ftk-row-divider">
<td class="ftk-label ftk-cell-divider">
<span class="fp-bl-en">Product</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Produit</span>
</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">
<span class="fp-bl-en">Operator</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Opérateur</span>
</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 class="ftk-row-divider">
<td class="ftk-label ftk-cell-divider">
<span class="fp-bl-en">Application</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Application</span>
</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">
<span class="fp-bl-en">Measured</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Mesuré le</span>
</td>
<td>
<t t-if="doc.x_fc_thickness_datetime"
t-esc="doc.x_fc_thickness_datetime.strftime('%Y-%m-%d %H:%M')"/>
<t t-if="not doc.x_fc_thickness_datetime"></t>
<t t-if="not doc.x_fc_thickness_datetime">-</t>
</td>
</tr>
<tr class="ftk-row-divider">
<td class="ftk-label ftk-cell-divider">
<span class="fp-bl-en">Directory</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Répertoire</span>
</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">
<span class="fp-bl-en">Measuring Time</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Durée de mesure</span>
</td>
<td>
<t t-if="doc.x_fc_thickness_measuring_time_sec"
t-esc="'%d sec' % doc.x_fc_thickness_measuring_time_sec"/>
<t t-if="not doc.x_fc_thickness_measuring_time_sec"></t>
<t t-if="not doc.x_fc_thickness_measuring_time_sec">-</t>
</td>
</tr>
</table>
@@ -510,7 +510,7 @@
</tr>
</table>
<!-- Source-file footnote italic + opacity:0.7
<!-- Source-file footnote - italic + opacity:0.7
(from .small-label) renders jagged/washed-out
on entech wkhtmltopdf. Solid #555 grey at
normal weight prints cleanly. -->
@@ -523,7 +523,7 @@
</div>
</t>
<!-- Signature + certification statement never split
<!-- Signature + certification statement - never split
across pages (page-break-inside on the row works on
entech wkhtmltopdf; see CLAUDE.md). Bordered table draws
the outer box + the column divider between Certified By
@@ -581,7 +581,7 @@
</template>
<!-- ================================================================== -->
<!-- English CoC wrapped in web.external_layout -->
<!-- English CoC - wrapped in web.external_layout -->
<!-- ================================================================== -->
<template id="report_coc_en">
<t t-call="web.html_container">
@@ -591,12 +591,12 @@
company band. fp_external_layout_clean provides the
.article wrapper Odoo needs for correct UTF-8 dispatch
plus a minimal page-number footer, with NO auto header
div coc_header renders the visible header instead. -->
div - coc_header renders the visible header instead. -->
<t t-call="fusion_plating_reports.fp_external_layout_clean">
<t t-set="LANG" t-value="'en'"/>
<t t-call="fusion_plating_reports.coc_header"/>
<div class="page">
<!-- Sub 12c router picks chronological vs classic body -->
<!-- Sub 12c - router picks chronological vs classic body -->
<t t-call="fusion_plating_reports.coc_body_router"/>
</div>
</t>
@@ -604,7 +604,7 @@
</t>
</template>
<!-- French CoC template removed 2026-05-28 the single bilingual
<!-- French CoC template removed 2026-05-28 - the single bilingual
report_coc_en renders English + French together. -->
<!-- ================================================================== -->
@@ -618,7 +618,7 @@
<div class="fp-report">
<div class="page">
<h4>
Certificate of Conformance
Certificate of Conformance -
<span t-field="doc.name"/>
</h4>
<table class="bordered">

View File

@@ -4,7 +4,7 @@
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
Sub 12c Chronological CoC body.
Sub 12c - Chronological CoC body.
Walks fp.job.step.move records in time order (chain-of-custody),
rendering each transition as a heading ("Step Name (Tank Code)")
with a "Moved By / Time" meta line + a 5-column measurement
@@ -54,28 +54,28 @@
<tr>
<td>
<t t-if="job and 'part_catalog_id' in job._fields and job.part_catalog_id">
<span t-esc="job.part_catalog_id.part_number or job.product_id.default_code or ''"/>
<span t-esc="job.part_catalog_id.part_number or job.product_id.default_code or '-'"/>
</t>
<t t-else="">
<span t-esc="(job and job.product_id and job.product_id.default_code) or ''"/>
<span t-esc="(job and job.product_id and job.product_id.default_code) or '-'"/>
</t>
</td>
<td>
<t t-if="job and 'part_catalog_id' in job._fields and job.part_catalog_id">
<span t-esc="job.part_catalog_id.name or job.product_id.name or ''"/>
<span t-esc="job.part_catalog_id.name or job.product_id.name or '-'"/>
</t>
<t t-else="">
<span t-esc="(job and job.product_id and job.product_id.name) or ''"/>
<span t-esc="(job and job.product_id and job.product_id.name) or '-'"/>
</t>
</td>
<td class="text-center">
<span t-esc="(job and job.qty) or ''"/>
</td>
<td class="text-center">
<span t-esc="(job and job.name) or ''"/>
<span t-esc="(job and job.name) or '-'"/>
</td>
<td>
<span t-esc="(job and job.sale_order_id and job.sale_order_id.client_order_ref) or ''"/>
<span t-esc="(job and job.sale_order_id and job.sale_order_id.client_order_ref) or '-'"/>
</td>
<td/>
<td>
@@ -86,7 +86,7 @@
<h3 style="margin-top: 6px;">Specification(s):
<span style="font-weight: normal;"
t-esc="(job and job.recipe_id and job.recipe_id.name) or ''"/>
t-esc="(job and job.recipe_id and job.recipe_id.name) or '-'"/>
</h3>
<hr class="heavy"/>
@@ -95,13 +95,13 @@
<t t-foreach="moves" t-as="mv">
<t t-set="dest" t-value="mv.to_step_id"/>
<t t-set="tank_code" t-value="(mv.to_tank_id and mv.to_tank_id.code) or (dest and dest.tank_id and dest.tank_id.code) or ''"/>
<!-- Sub 12d input_ids lives on recipe_node, not job.step.
<!-- Sub 12d - input_ids lives on recipe_node, not job.step.
Walk via recipe_node_id; filter to step_input + collect=True. -->
<t t-set="recipe_node" t-value="(dest and dest.recipe_node_id) or False"/>
<t t-set="captured" t-value="(recipe_node and recipe_node.input_ids.filtered(lambda i: (i.kind or 'step_input') == 'step_input' and (i.collect if 'collect' in i._fields else True)).sorted('sequence')) or []"/>
<h3>
<span t-esc="(dest and dest.name) or ''"/>
<span t-esc="(dest and dest.name) or '-'"/>
<t t-if="tank_code"> (<span t-esc="tank_code"/>)</t>
</h3>
<div class="fp-chrono-meta">
@@ -115,11 +115,11 @@
</t>
</div>
<!-- Sub 12c+ index captured values from the move's transition_input_value_ids
<!-- Sub 12c+ - index captured values from the move's transition_input_value_ids
by node_input_id so we can render Actual alongside Target. -->
<t t-set="captured_values_by_input" t-value="{v.node_input_id.id: v for v in mv.transition_input_value_ids}"/>
<!-- Measurement sub-table show whenever destination has any
<!-- Measurement sub-table - show whenever destination has any
step_input prompts OR the move recorded any captured values. -->
<t t-set="prompts" t-value="captured"/>
<t t-if="not prompts and mv.transition_input_value_ids">
@@ -173,7 +173,7 @@
</td>
<td class="text-center">
<t t-if="'target_min' in inp._fields and inp.target_min and inp.target_max">
<span t-esc="inp.target_min"/><span t-esc="inp.target_max"/>
<span t-esc="inp.target_min"/>-<span t-esc="inp.target_max"/>
<t t-if="'target_unit' in inp._fields and inp.target_unit">
<span> </span><span t-esc="inp.target_unit"/>
</t>
@@ -208,7 +208,7 @@
<hr class="heavy"/>
<!-- Sign-off block unified with WO Detail / CoC (2026-05-17).
<!-- Sign-off block - unified with WO Detail / CoC (2026-05-17).
Signer = cert's certified_by user → falls back to company
owner. Signature image = signer's Plating Signature
(x_fc_signature_image from Preferences → My Profile). -->
@@ -216,7 +216,7 @@
<t t-set="signature_img" t-value="(signer_user and 'x_fc_signature_image' in signer_user._fields and signer_user.x_fc_signature_image) or False"/>
<t t-set="signer_name" t-value="(signer_user and signer_user.name) or ''"/>
<!-- Sub 12c+ cert statement: per-customer override → company default → hardcoded fallback -->
<!-- Sub 12c+ - cert statement: per-customer override → company default → hardcoded fallback -->
<t t-set="_cust_stmt" t-value="('x_fc_cert_statement' in doc.partner_id._fields and doc.partner_id.x_fc_cert_statement) or False"/>
<t t-set="_co_stmt" t-value="('x_fc_default_cert_statement' in company._fields and company.x_fc_default_cert_statement) or False"/>
<t t-set="cert_statement" t-value="_cust_stmt or _co_stmt or 'We certify that the parts listed above have been processed in accordance with the specifications referenced and that all required tests have been performed. Records on file at our facility per AS9100 / ISO 9001 retention policy.'"/>
@@ -245,7 +245,7 @@
</template>
<!-- ============================================================== -->
<!-- Router picks chronological vs classic body -->
<!-- Router - picks chronological vs classic body -->
<!-- Wired into the existing CoC actions in report_coc.xml. -->
<!-- ============================================================== -->
<template id="coc_body_router">

View File

@@ -3,9 +3,9 @@
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Sub 12 Phase E 8D Report (NCR + linked CAPA combined).
Sub 12 Phase E - 8D Report (NCR + linked CAPA combined).
Bound to fusion.plating.ncr. Renders all 8 disciplines in one PDF.
Degraded mode if no CAPA is linked: D4D8 sections show a placeholder
Degraded mode if no CAPA is linked: D4-D8 sections show a placeholder
note that the CAPA hasn't been opened yet.
-->
<odoo>
@@ -25,9 +25,9 @@
</div>
</div>
<!-- D1 Team -->
<!-- D1 - Team -->
<div style="margin-bottom: 14px;">
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D1 Team</h2>
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D1 - Team</h2>
<table style="width: 100%; padding: 6px;">
<tr><td style="width: 130px; color: #666;">Lead</td><td><span t-out="ncr.team_id.lead_user_id.name or ncr.reported_by_id.name"/></td></tr>
<tr><td style="color: #666;">Team</td><td><span t-out="ncr.team_id.name or 'Unassigned'"/></td></tr>
@@ -35,9 +35,9 @@
</table>
</div>
<!-- D2 Problem Description -->
<!-- D2 - Problem Description -->
<div style="margin-bottom: 14px;">
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D2 Problem Description</h2>
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D2 - Problem Description</h2>
<div style="padding: 6px;">
<table style="width: 100%; margin-bottom: 8px;">
<tr><td style="width: 130px; color: #666;">Severity</td><td><span t-field="ncr.severity"/></td></tr>
@@ -52,17 +52,17 @@
</div>
</div>
<!-- D3 Containment -->
<!-- D3 - Containment -->
<div style="margin-bottom: 14px;">
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D3 Containment Action</h2>
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D3 - Containment Action</h2>
<div style="padding: 6px; background: #fafafa; border-left: 3px solid #ddd;">
<t t-out="ncr.containment or 'No containment narrative recorded.'"/>
</div>
</div>
<!-- D4 Root Cause -->
<!-- D4 - Root Cause -->
<div style="margin-bottom: 14px;">
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D4 Root Cause Analysis</h2>
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D4 - Root Cause Analysis</h2>
<div style="padding: 6px;">
<table t-if="capa or ncr.reason_id" style="width: 100%; margin-bottom: 8px;">
<tr t-if="ncr.reason_id"><td style="width: 130px; color: #666;">Classified Reason</td><td><span t-out="ncr.reason_id.name"/> (<span t-field="ncr.reason_id.category"/>)</td></tr>
@@ -76,58 +76,58 @@
</div>
</div>
<!-- D5 Permanent Corrective Action -->
<!-- D5 - Permanent Corrective Action -->
<div style="margin-bottom: 14px;">
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D5 Permanent Corrective Action</h2>
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D5 - Permanent Corrective Action</h2>
<div style="padding: 6px; background: #fafafa; border-left: 3px solid #ddd;">
<t t-if="capa and capa.action_plan"><t t-out="capa.action_plan"/></t>
<t t-else="">No corrective action plan recorded open a CAPA from the NCR to populate this section.</t>
<t t-else="">No corrective action plan recorded - open a CAPA from the NCR to populate this section.</t>
</div>
</div>
<!-- D6 Implement &amp; Verify -->
<!-- D6 - Implement &amp; Verify -->
<div style="margin-bottom: 14px;">
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D6 Implement &amp; Verify</h2>
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D6 - Implement &amp; Verify</h2>
<div style="padding: 6px;">
<table t-if="capa" style="width: 100%; margin-bottom: 8px;">
<tr><td style="width: 130px; color: #666;">CAPA State</td><td><span t-field="capa.state"/></td></tr>
<tr><td style="color: #666;">Owner</td><td><span t-out="capa.owner_id.name or ''"/></td></tr>
<tr><td style="color: #666;">Due</td><td><span t-out="capa.due_date or ''"/></td></tr>
<tr><td style="color: #666;">Owner</td><td><span t-out="capa.owner_id.name or '-'"/></td></tr>
<tr><td style="color: #666;">Due</td><td><span t-out="capa.due_date or '-'"/></td></tr>
<tr><td style="color: #666;">Verification</td><td><span t-out="capa.verification_date or 'Pending'"/></td></tr>
<tr><td style="color: #666;">Effective</td><td><span t-out="'Yes' if capa.is_effective else 'Pending'"/></td></tr>
</table>
<div style="background: #fafafa; padding: 8px; border-left: 3px solid #ddd;" t-if="capa">
<t t-out="capa.effectiveness_notes or 'No effectiveness notes yet.'"/>
</div>
<div t-if="not capa" style="color: #666;">No CAPA opened implementation tracking unavailable.</div>
<div t-if="not capa" style="color: #666;">No CAPA opened - implementation tracking unavailable.</div>
</div>
</div>
<!-- D7 Prevent Recurrence -->
<!-- D7 - Prevent Recurrence -->
<div style="margin-bottom: 14px;">
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D7 Prevent Recurrence</h2>
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D7 - Prevent Recurrence</h2>
<div style="padding: 6px; background: #fafafa; border-left: 3px solid #ddd;">
<t t-if="capa and capa.type == 'preventive' and capa.action_plan">
<t t-out="capa.action_plan"/>
</t>
<t t-elif="capa and capa.action_plan">
<em>Refer to D5 corrective action plan covers preventive measures.</em>
<em>Refer to D5 - corrective action plan covers preventive measures.</em>
</t>
<t t-else="">No preventive actions recorded. Open a Preventive-type CAPA to track recurrence-prevention measures separately.</t>
</div>
</div>
<!-- D8 Recognise the Team -->
<!-- D8 - Recognise the Team -->
<div style="margin-bottom: 14px;">
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D8 Recognise the Team</h2>
<h2 style="font-size: 14px; background: #eee; padding: 6px 10px; margin: 0 0 6px 0;">D8 - Recognise the Team</h2>
<div style="padding: 6px;">
<p t-if="capa and capa.state in ('effective', 'closed')">
Closure verified <span t-out="capa.verification_date"/> by <span t-out="capa.verification_by_id.name or ''"/>.
Closure verified <span t-out="capa.verification_date"/> by <span t-out="capa.verification_by_id.name or '-'"/>.
</p>
<p t-else="">Pending closure.</p>
<table style="width: 100%; margin-top: 8px; font-size: 11px; color: #666;">
<tr><td>NCR Closed:</td><td><span t-out="ncr.closed_date or 'Open'"/></td></tr>
<tr t-if="capa"><td>CAPA Verified:</td><td><span t-out="capa.verification_date or ''"/></td></tr>
<tr t-if="capa"><td>CAPA Verified:</td><td><span t-out="capa.verification_date or '-'"/></td></tr>
</table>
</div>
</div>

View File

@@ -2,7 +2,7 @@
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating Bill of Lading (Portrait + Landscape).
Fusion Plating - Bill of Lading (Portrait + Landscape).
Binds to fusion.plating.delivery. Includes shipper, consignee, carrier,
cargo description, special instructions, and sign-off lines.
-->
@@ -19,7 +19,7 @@
<div class="fp-report">
<div class="page">
<!-- Resolve shipper company defensively fall back to env.company
<!-- Resolve shipper company defensively - fall back to env.company
when delivery.company_id is missing on legacy records. -->
<t t-set="ship_co" t-value="doc.company_id or env.company"/>
@@ -116,9 +116,9 @@
</tbody>
</table>
<!-- Cargo description iterate the linked fp.job's SO lines
<!-- Cargo description - iterate the linked fp.job's SO lines
so each part renders with its customer part number via
the shared macro. Sub 11 replaced mrp.production lookup. -->
the shared macro. Sub 11 - replaced mrp.production lookup. -->
<t t-set="_job" t-value="env['fp.job'].sudo().search([('name', '=', doc.job_ref)], limit=1) if doc.job_ref else env['fp.job']"/>
<t t-set="_so" t-value="_job.sale_order_id if _job else False"/>
<t t-set="_lines" t-value="_so.order_line.filtered(lambda l: l.product_id and l.product_uom_qty &gt; 0) if _so else False"/>
@@ -152,7 +152,7 @@
<td class="text-center fp-cell-mid">
<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 fp-cell-mid"></td>
<td class="text-center fp-cell-mid">-</td>
<td class="text-center fp-cell-mid">
<span t-if="doc.tdg_required">TDG</span>
<span t-else="">NON-HAZ</span>
@@ -164,15 +164,15 @@
<tr>
<td class="text-center fp-cell-mid">1</td>
<td class="fp-cell-mid">
Plated parts Job <span t-esc="doc.job_ref or doc.name"/>
Plated parts - Job <span t-esc="doc.job_ref or doc.name"/>
<t t-if="doc.notes">
<br/><span t-field="doc.notes"/>
</t>
</td>
<td class="text-center fp-cell-mid">
<span t-esc="int(_mo.product_qty) if _mo else ''"/>
<span t-esc="int(_mo.product_qty) if _mo else '-'"/>
</td>
<td class="text-center fp-cell-mid"></td>
<td class="text-center fp-cell-mid">-</td>
<td class="text-center fp-cell-mid">
<span t-if="doc.tdg_required">TDG</span>
<span t-else="">NON-HAZ</span>
@@ -194,11 +194,11 @@
<tr>
<td class="text-center fp-cell-mid">
<span t-if="doc.coc_attachment_id" class="status-ok">✓ Attached</span>
<span t-else=""></span>
<span t-else="">-</span>
</td>
<td class="text-center fp-cell-mid">
<span t-if="doc.packing_list_attachment_id" class="status-ok">✓ Attached</span>
<span t-else=""></span>
<span t-else="">-</span>
</td>
</tr>
</tbody>
@@ -328,8 +328,8 @@
</tbody>
</table>
<!-- Cargo description iterate the linked fp.job's SO lines.
Sub 11 replaced mrp.production lookup. -->
<!-- Cargo description - iterate the linked fp.job's SO lines.
Sub 11 - replaced mrp.production lookup. -->
<t t-set="_job" t-value="env['fp.job'].sudo().search([('name', '=', doc.job_ref)], limit=1) if doc.job_ref else env['fp.job']"/>
<t t-set="_so" t-value="_job.sale_order_id if _job else False"/>
<t t-set="_lines" t-value="_so.order_line.filtered(lambda l: l.product_id and l.product_uom_qty &gt; 0) if _so else False"/>
@@ -364,7 +364,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"></td>
<td class="text-center">-</td>
<td class="text-center">
<span t-if="doc.tdg_required">TDG</span>
<span t-else="">NON-HAZ</span>
@@ -380,13 +380,13 @@
<tr>
<td class="text-center">1</td>
<td>
Plated parts Job <span t-esc="doc.job_ref or doc.name"/>
Plated parts - Job <span t-esc="doc.job_ref or doc.name"/>
<t t-if="doc.notes">
<br/><span t-field="doc.notes"/>
</t>
</td>
<td class="text-center"></td>
<td class="text-center"></td>
<td class="text-center">-</td>
<td class="text-center">-</td>
<td class="text-center">
<span t-if="doc.tdg_required">TDG</span>
<span t-else="">NON-HAZ</span>
@@ -413,11 +413,11 @@
<tr>
<td class="text-center">
<span t-if="doc.coc_attachment_id" class="status-ok">✓ Attached</span>
<span t-else=""></span>
<span t-else="">-</span>
</td>
<td class="text-center">
<span t-if="doc.packing_list_attachment_id" class="status-ok">✓ Attached</span>
<span t-else=""></span>
<span t-else="">-</span>
</td>
<td class="text-center"><span t-field="doc.custody_event_count"/></td>
</tr>

View File

@@ -2,7 +2,7 @@
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating Invoice (Portrait + Landscape).
Fusion Plating - Invoice (Portrait + Landscape).
Binds to account.move. Includes invoice strategy, deposit context,
payment details, and amount due.
-->
@@ -153,8 +153,8 @@
<td class="text-center"><span t-field="doc.invoice_date"/></td>
<td class="text-center"><span t-field="doc.invoice_date_due"/></td>
<td class="text-center"><span t-field="doc.invoice_user_id"/></td>
<td class="text-center"><span t-esc="po_number or ''"/></td>
<td class="text-center"><span t-esc="doc.payment_reference or ''"/></td>
<td class="text-center"><span t-esc="po_number or '-'"/></td>
<td class="text-center"><span t-esc="doc.payment_reference or '-'"/></td>
</tr>
</tbody>
</table>
@@ -174,8 +174,8 @@
</thead>
<tbody>
<tr>
<td class="text-center"><span t-esc="source_so.x_fc_customer_job_number or ''"/></td>
<td class="text-center"><span t-esc="delivery_method_label or ''"/></td>
<td class="text-center"><span t-esc="source_so.x_fc_customer_job_number or '-'"/></td>
<td class="text-center"><span t-esc="delivery_method_label or '-'"/></td>
</tr>
</tbody>
</table>
@@ -231,14 +231,14 @@
<t t-if="line.x_fc_part_catalog_id and line.x_fc_part_catalog_id.name">
<span t-esc="line.x_fc_part_catalog_id.name"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</div>
<div>
<strong>S/N:</strong>
<t t-if="line.x_fc_serial_ids">
<span t-esc="', '.join(line.x_fc_serial_ids.mapped('name'))"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</div>
</td>
<td>
@@ -386,7 +386,7 @@
<t t-set="delivery_method_label" t-value="dict(source_so._fields['x_fc_delivery_method'].selection).get(source_so.x_fc_delivery_method, '') if source_so and source_so.x_fc_delivery_method else ''"/>
<div class="fp-landscape fp-sale">
<!-- 3-column inline header same as portrait -->
<!-- 3-column inline header - same as portrait -->
<div class="fp-sale-header-row">
<div class="fp-sale-header-left">
<t t-if="logo_uri">
@@ -497,8 +497,8 @@
<td class="text-center"><span t-field="doc.invoice_date"/></td>
<td class="text-center"><span t-field="doc.invoice_date_due"/></td>
<td class="text-center"><span t-field="doc.invoice_user_id"/></td>
<td class="text-center"><span t-esc="po_number or ''"/></td>
<td class="text-center"><span t-esc="doc.payment_reference or ''"/></td>
<td class="text-center"><span t-esc="po_number or '-'"/></td>
<td class="text-center"><span t-esc="doc.payment_reference or '-'"/></td>
<td class="text-center"><span t-field="doc.currency_id.name"/></td>
</tr>
</tbody>
@@ -519,8 +519,8 @@
</thead>
<tbody>
<tr>
<td class="text-center"><span t-esc="source_so.x_fc_customer_job_number or ''"/></td>
<td class="text-center"><span t-esc="delivery_method_label or ''"/></td>
<td class="text-center"><span t-esc="source_so.x_fc_customer_job_number or '-'"/></td>
<td class="text-center"><span t-esc="delivery_method_label or '-'"/></td>
</tr>
</tbody>
</table>
@@ -582,14 +582,14 @@
<t t-if="line.x_fc_part_catalog_id and line.x_fc_part_catalog_id.name">
<span t-esc="line.x_fc_part_catalog_id.name"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</div>
<div>
<strong>S/N:</strong>
<t t-if="line.x_fc_serial_ids">
<span t-esc="', '.join(line.x_fc_serial_ids.mapped('name'))"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</div>
</td>
<td>
@@ -605,7 +605,7 @@
</td>
<td t-if="has_discount" class="text-center">
<t t-if="line.discount"><span t-esc="line.discount"/>%</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td class="text-end">
<span t-field="line.price_subtotal" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
@@ -697,8 +697,8 @@
<thead>
<tr>
<th colspan="3" style="background-color: #28a745; color: white;">
<t t-if="doc.payment_state in ('paid', 'in_payment')">✓ PAYMENT DETAILS PAID IN FULL</t>
<t t-elif="doc.payment_state == 'partial'">PAYMENT DETAILS PARTIALLY PAID</t>
<t t-if="doc.payment_state in ('paid', 'in_payment')">✓ PAYMENT DETAILS - PAID IN FULL</t>
<t t-elif="doc.payment_state == 'partial'">PAYMENT DETAILS - PARTIALLY PAID</t>
<t t-else="">PAYMENT DETAILS</t>
</th>
</tr>

View File

@@ -4,7 +4,7 @@
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
Fusion Plating Job Traveller (Shop Router).
Fusion Plating - Job Traveller (Shop Router).
One centralised document that follows a job through the shop. Pulls
together everything about the MO: customer + PO, receiving, recipe,
@@ -22,7 +22,7 @@
<odoo>
<!-- ============================================================= -->
<!-- INNER BODY shared between portrait and landscape -->
<!-- INNER BODY - shared between portrait and landscape -->
<!-- Receives `mo` (mrp.production) in the t-call context. -->
<!-- ============================================================= -->
<template id="report_fp_job_traveller_body">
@@ -40,7 +40,7 @@
<!-- ===== Title bar ===== -->
<div style="display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 10px;">
<h2 style="margin: 0; font-size: 20pt;">
Job Traveller <span t-field="mo.name"/>
Job Traveller - <span t-field="mo.name"/>
</h2>
<div style="text-align: right;">
<t t-if="mo.x_fc_is_rework">
@@ -59,7 +59,7 @@
</div>
</div>
<!-- ===== 1. JOB HEADER Customer / PO / Part / Qty / Dates ===== -->
<!-- ===== 1. JOB HEADER - Customer / PO / Part / Qty / Dates ===== -->
<table class="bordered">
<thead>
<tr><th colspan="6" class="fp-header-primary">JOB HEADER</th></tr>
@@ -73,12 +73,12 @@
<th class="info-header" style="width: 10%;">Sale Order</th>
<td class="text-center" style="width: 15%;">
<t t-if="so"><span t-field="so.name"/></t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<th class="info-header" style="width: 10%;">Customer PO #</th>
<td class="text-center" style="width: 15%;">
<t t-if="so and so.x_fc_po_number"><span t-field="so.x_fc_po_number"/></t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
</tr>
<tr>
@@ -115,7 +115,7 @@
<t t-if="so and so.x_fc_customer_spec_id">
<span t-field="so.x_fc_customer_spec_id"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<th class="info-header">Recipe</th>
<td>
@@ -131,7 +131,7 @@
<t t-if="mo.date_start">
<span t-field="mo.date_start" t-options="{'widget': 'date'}"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<th class="info-header">Target Ship</th>
<td class="text-center">
@@ -141,7 +141,7 @@
<t t-elif="so and so.commitment_date">
<span t-field="so.commitment_date" t-options="{'widget': 'date'}"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<th class="info-header">Current Location</th>
<td class="text-center">
@@ -151,7 +151,7 @@
</tbody>
</table>
<!-- ===== 1b. PART DESCRIPTIONS (Sub 2) internal report keeps the service
<!-- ===== 1b. PART DESCRIPTIONS (Sub 2) - internal report keeps the service
SKU visible while surfacing customer-facing + internal workflow text. -->
<t t-set="_trav_line" t-value="(mo.x_fc_sale_order_line_ids.filtered(lambda l: l.product_id == mo.product_id)[:1] or mo.x_fc_sale_order_line_ids[:1]) if 'x_fc_sale_order_line_ids' in mo._fields else mo.env['sale.order.line']"/>
<t t-if="(not _trav_line) and so">
@@ -165,11 +165,11 @@
<tr>
<th class="info-header" style="width: 20%;">Customer-Facing Description</th>
<td style="width: 40%;">
<t t-if="_trav_line"><span t-esc="_trav_line.name or ''"/></t>
<t t-else=""></t>
<t t-if="_trav_line"><span t-esc="_trav_line.name or '-'"/></t>
<t t-else="">-</t>
</td>
<th class="info-header" style="width: 15%;">Service SKU</th>
<td style="width: 25%;"><span t-esc="mo.product_id.default_code or ''"/></td>
<td style="width: 25%;"><span t-esc="mo.product_id.default_code or '-'"/></td>
</tr>
<tr>
<th class="info-header">Internal Description / Workflow</th>
@@ -177,7 +177,7 @@
<t t-if="_trav_line and 'x_fc_internal_description' in _trav_line._fields and _trav_line.x_fc_internal_description">
<span t-esc="_trav_line.x_fc_internal_description" style="white-space: pre-wrap;"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
</tr>
</tbody>
@@ -203,11 +203,11 @@
<t t-if="rec.received_date">
<span t-field="rec.received_date" t-options="{'widget': 'datetime'}"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td class="text-center">
<t t-if="'quantity_received' in rec._fields"><span t-esc="rec.quantity_received"/></t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td class="text-center"><span t-field="rec.state"/></td>
<td>
@@ -216,7 +216,7 @@
<i class="fa fa-warning"/> <span t-esc="len(rec.damage_ids)"/> damage entries
</span>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
</tr>
</t>
@@ -228,7 +228,7 @@
</tbody>
</table>
<!-- ===== 3. ROUTING TABLE the main event ===== -->
<!-- ===== 3. ROUTING TABLE - the main event ===== -->
<table class="bordered" style="margin-top: 12px;">
<thead>
<tr><th colspan="12" class="fp-header-primary">PROCESS ROUTING</th></tr>
@@ -260,19 +260,19 @@
<span t-esc="wo.x_fc_thickness_target"/>
<span t-esc="dict(wo._fields['x_fc_thickness_uom'].selection).get(wo.x_fc_thickness_uom, '')"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td class="text-center">
<t t-if="wo.x_fc_dwell_time_minutes">
<span t-esc="wo.x_fc_dwell_time_minutes"/>m
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td class="text-center">
<t t-if="wo.duration_expected">
<span t-esc="'%.0f' % wo.duration_expected"/>m
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td class="sig-line"/>
<td class="sig-line"/>
@@ -312,9 +312,9 @@
<span t-field="bath.name"/>
</td>
<td><span t-field="p.parameter_id"/></td>
<td class="text-center"><span t-esc="p.target_min or ''"/></td>
<td class="text-center"><span t-esc="p.target_max or ''"/></td>
<td class="text-center"><span t-esc="p.uom or ''"/></td>
<td class="text-center"><span t-esc="p.target_min or '-'"/></td>
<td class="text-center"><span t-esc="p.target_max or '-'"/></td>
<td class="text-center"><span t-esc="p.uom or '-'"/></td>
</tr>
</t>
</t>
@@ -322,7 +322,7 @@
</table>
</t>
<!-- ===== 5. QUALITY HOLDS only if present ===== -->
<!-- ===== 5. QUALITY HOLDS - only if present ===== -->
<t t-if="holds">
<div class="highlight-box" style="border-color: #c62828; background-color: #ffebee; margin-top: 12px;">
<strong class="status-fail">
@@ -354,7 +354,7 @@
</div>
</t>
<!-- ===== 6. CERTIFICATES + DELIVERY side by side ===== -->
<!-- ===== 6. CERTIFICATES + DELIVERY - side by side ===== -->
<table style="margin-top: 12px; border: none;">
<tr style="border: none;">
<td style="width: 50%; padding-right: 6px; border: none; vertical-align: top;">
@@ -401,13 +401,13 @@
<td class="text-center"><span t-field="dlv.state"/></td>
<td class="text-center">
<t t-if="dlv.assigned_driver_id"><span t-field="dlv.assigned_driver_id"/></t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td class="text-center">
<t t-if="'tracking_ref' in dlv._fields and dlv.tracking_ref">
<span t-field="dlv.tracking_ref"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
</tr>
</t>
@@ -420,7 +420,7 @@
</tr>
</table>
<!-- ===== 7. REWORK REASON only if rework ===== -->
<!-- ===== 7. REWORK REASON - only if rework ===== -->
<t t-if="mo.x_fc_is_rework and mo.x_fc_rework_reason">
<div class="highlight-box" style="margin-top: 12px;">
<strong>Rework Reason:</strong>
@@ -459,12 +459,12 @@
<p class="small-muted" style="margin-top: 12px; text-align: right;">
Generated <span t-esc="mo.env.cr.now()" t-options="{'widget': 'datetime'}"/>
This traveller must remain with the parts through all operations.
- This traveller must remain with the parts through all operations.
</p>
</template>
<!-- ============================================================= -->
<!-- MO-based Traveller LANDSCAPE (default) -->
<!-- MO-based Traveller - LANDSCAPE (default) -->
<!-- ============================================================= -->
<template id="report_fp_job_traveller_mo_landscape">
<t t-call="web.html_container">
@@ -483,7 +483,7 @@
</template>
<!-- ============================================================= -->
<!-- MO-based Traveller PORTRAIT -->
<!-- MO-based Traveller - PORTRAIT -->
<!-- ============================================================= -->
<template id="report_fp_job_traveller_mo_portrait">
<t t-call="web.html_container">
@@ -502,13 +502,13 @@
</template>
<!-- ============================================================= -->
<!-- SO-based Traveller iterates the SO's MOs -->
<!-- SO-based Traveller - iterates the SO's MOs -->
<!-- Landscape default -->
<!-- ============================================================= -->
<template id="report_fp_job_traveller_so_landscape">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="so">
<!-- Sub 11 MRP gone; find fp.jobs for this SO and delegate
<!-- Sub 11 - MRP gone; find fp.jobs for this SO and delegate
to the native fp.job traveller template (jobs module). -->
<t t-set="jobs" t-value="so.env['fp.job'].search([('sale_order_id', '=', so.id)])"/>
<t t-if="not jobs">
@@ -517,7 +517,7 @@
<t t-call="fusion_plating_reports.fp_landscape_styles"/>
<div class="fp-landscape">
<div class="page">
<h2>Job Traveller <span t-field="so.name"/></h2>
<h2>Job Traveller - <span t-field="so.name"/></h2>
<div class="highlight-box">
<strong class="status-warning">
<i class="fa fa-info-circle"/>
@@ -531,7 +531,7 @@
<t t-foreach="jobs" t-as="job">
<t t-call="web.external_layout">
<div class="page">
<h1>Job Traveller <span t-esc="job.name"/></h1>
<h1>Job Traveller - <span t-esc="job.name"/></h1>
<table class="table table-sm" style="margin-top: 1em;">
<tr><th>Customer</th><td><span t-esc="job.partner_id.name"/></td></tr>
<tr><th>SO</th><td><span t-esc="job.sale_order_id.name or '-'"/></td></tr>
@@ -563,7 +563,7 @@
</template>
<!-- ============================================================= -->
<!-- SO-based Traveller PORTRAIT -->
<!-- SO-based Traveller - PORTRAIT -->
<!-- ============================================================= -->
<template id="report_fp_job_traveller_so_portrait">
<t t-call="web.html_container">
@@ -575,7 +575,7 @@
<t t-call="fusion_plating_reports.fp_portrait_styles"/>
<div class="fp-report">
<div class="page">
<h4>Job Traveller <span t-field="so.name"/></h4>
<h4>Job Traveller - <span t-field="so.name"/></h4>
<div class="highlight-box">
<strong class="status-warning">
<i class="fa fa-info-circle"/>
@@ -589,7 +589,7 @@
<t t-foreach="jobs" t-as="job">
<t t-call="web.external_layout">
<div class="page">
<h2>Job Traveller <span t-esc="job.name"/></h2>
<h2>Job Traveller - <span t-esc="job.name"/></h2>
<table class="table table-sm" style="margin-top: 1em;">
<tr><th>Customer</th><td><span t-esc="job.partner_id.name"/></td></tr>
<tr><th>SO</th><td><span t-esc="job.sale_order_id.name or '-'"/></td></tr>

View File

@@ -2,7 +2,7 @@
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating Packing Slip / Shipping Confirmation (Portrait + Landscape).
Fusion Plating - Packing Slip / Shipping Confirmation (Portrait + Landscape).
Binds to stock.picking. Bill-To / Ship-To boxes, bilingual column
headers, Received-By signature block and a QR code for scan-to-sign.
-->
@@ -17,7 +17,7 @@
margin_top=8mm; the body padding-top clears the header
band. Same trick as .fp-coc and .fp-sale. */
.fp-report.fp-ps .page { padding-top: 20mm; }
/* Title bar: float div layout (NO table see CLAUDE.md
/* Title bar: float div layout (NO table - see CLAUDE.md
wkhtmltopdf overlap §2). Stacked bilingual title with
English bold on top and French italic-grey below. */
.fp-ps-titlebar { margin: 0 0 10px 0; padding: 0; overflow: hidden; }
@@ -54,7 +54,7 @@
<!-- Address box content (shared by portrait + landscape).
NOTE: `t-field` in Odoo 19 requires a dotted path
("record.field_name") passing a bare `partner` variable
("record.field_name") - passing a bare `partner` variable
via t-set and then `t-field="partner"` raises
`AssertionError: t-field must have at least a dot`.
The contact-widget pattern (`t-field="x.partner_id"
@@ -204,7 +204,7 @@
<t t-set="has_carrier" t-value="'carrier_id' in doc._fields and doc.carrier_id"/>
<t t-set="ship_via" t-value="(doc.carrier_id.name if has_carrier else (doc.sale_id.x_fc_ship_via if doc.sale_id and 'x_fc_ship_via' in doc.sale_id._fields and doc.sale_id.x_fc_ship_via else 'CUSTOMER PICKUP'))"/>
<t t-set="tracking_ref" t-value="doc.carrier_tracking_ref if 'carrier_tracking_ref' in doc._fields and doc.carrier_tracking_ref else False"/>
<t t-set="tracking_text" t-value="tracking_ref if tracking_ref else ('Ready for pick up' if not has_carrier else '')"/>
<t t-set="tracking_text" t-value="tracking_ref if tracking_ref else ('Ready for pick up' if not has_carrier else '-')"/>
<t t-set="po_number" t-value="(doc.sale_id.client_order_ref if doc.sale_id and doc.sale_id.client_order_ref else '')"/>
<!-- Packing slip number derived from the SO so it
@@ -224,7 +224,7 @@
<!-- Code128 barcode encoding the packing-slip number so
the printed label matches the visible title. Inlined
via barcode_data_uri (no /report/barcode/ HTTP fetch
wkhtmltopdf network calls fail on entech). -->
- wkhtmltopdf network calls fail on entech). -->
<t t-set="ps_barcode_uri" t-value="doc.env['ir.actions.report'].sudo().barcode_data_uri('Code128', ps_number, 600, 100) if ps_number else False"/>
<t t-set="qr_payload" t-value="doc.name or ''"/>
@@ -292,7 +292,7 @@
<t t-if="doc.scheduled_date">
<span t-field="doc.scheduled_date" t-options="{'widget': 'date'}"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td><span t-esc="tracking_text"/></td>
</tr>
@@ -344,7 +344,7 @@
<t t-set="has_carrier" t-value="'carrier_id' in doc._fields and doc.carrier_id"/>
<t t-set="ship_via" t-value="(doc.carrier_id.name if has_carrier else (doc.sale_id.x_fc_ship_via if doc.sale_id and 'x_fc_ship_via' in doc.sale_id._fields and doc.sale_id.x_fc_ship_via else 'CUSTOMER PICKUP'))"/>
<t t-set="tracking_ref" t-value="doc.carrier_tracking_ref if 'carrier_tracking_ref' in doc._fields and doc.carrier_tracking_ref else False"/>
<t t-set="tracking_text" t-value="tracking_ref if tracking_ref else ('Ready for pick up' if not has_carrier else '')"/>
<t t-set="tracking_text" t-value="tracking_ref if tracking_ref else ('Ready for pick up' if not has_carrier else '-')"/>
<t t-set="po_number" t-value="(doc.sale_id.client_order_ref if doc.sale_id and doc.sale_id.client_order_ref else '')"/>
<!-- See portrait template for the numbering logic. -->
@@ -357,7 +357,7 @@
<!-- Code128 barcode encoding the packing-slip number so
the printed label matches the visible title. Inlined
via barcode_data_uri (no /report/barcode/ HTTP fetch
wkhtmltopdf network calls fail on entech). -->
- wkhtmltopdf network calls fail on entech). -->
<t t-set="ps_barcode_uri" t-value="doc.env['ir.actions.report'].sudo().barcode_data_uri('Code128', ps_number, 600, 100) if ps_number else False"/>
<t t-set="qr_payload" t-value="doc.name or ''"/>
@@ -423,7 +423,7 @@
<t t-if="doc.scheduled_date">
<span t-field="doc.scheduled_date" t-options="{'widget': 'date'}"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td><span t-esc="tracking_text"/></td>
</tr>
@@ -575,7 +575,7 @@
<t t-set="bill_partner" t-value="(_so.partner_invoice_id if _so and _so.partner_invoice_id else (ship_partner.commercial_partner_id or ship_partner))"/>
<t t-set="has_carrier" t-value="m == 'fusion.plating.delivery' and 'x_fc_carrier_id' in doc._fields and doc.x_fc_carrier_id"/>
<t t-set="ship_via" t-value="(doc.x_fc_carrier_id.name if has_carrier else (_so.x_fc_ship_via if _so and 'x_fc_ship_via' in _so._fields and _so.x_fc_ship_via else 'CUSTOMER PICKUP'))"/>
<t t-set="tracking_text" t-value="'Ready for pick up' if not has_carrier else ''"/>
<t t-set="tracking_text" t-value="'Ready for pick up' if not has_carrier else '-'"/>
<t t-set="po_number" t-value="(_so.client_order_ref if _so and _so.client_order_ref else '')"/>
<t t-set="_scheduled" t-value="doc.scheduled_date if m == 'fusion.plating.delivery' else (doc.commitment_date if m == 'sale.order' else (_so.commitment_date if _so else False))"/>
<t t-set="_notes" t-value="doc.notes if m == 'fusion.plating.delivery' else (doc.note if m == 'sale.order' else False)"/>
@@ -643,7 +643,7 @@
<t t-if="_scheduled">
<span t-out="_scheduled" t-options="{'widget': 'date'}"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td><span t-esc="tracking_text"/></td>
</tr>

View File

@@ -3,7 +3,7 @@
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Sub 12 Phase E Monthly Quality Summary report.
Sub 12 Phase E - Monthly Quality Summary report.
On-demand from the Quality Dashboard. Counts by record type / severity /
customer. Overdue ageing buckets. CAPA effectiveness rate. Repeat-customer
flag (>2 NCRs same customer in 90 days). Run via menu action
@@ -20,7 +20,7 @@
<h1 style="margin: 0; font-size: 24px;">Monthly Quality Summary</h1>
<div style="font-size: 13px; color: #666; margin-top: 4px;">
<span t-out="company.name"/>
<span t-out="data['period_label']"/>
- <span t-out="data['period_label']"/>
</div>
</div>

View File

@@ -4,7 +4,7 @@
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
Sub 12c+ Rack Travel Ticket.
Sub 12c+ - Rack Travel Ticket.
Closes the gap left by Sub 12b's Rack Parts dialog 'Save + Print'
button. Operator presses Save + Print → tablet opens
/web/report/pdf/fp.rack.travel/<rack_id> in a new tab → this report
@@ -17,7 +17,7 @@
<odoo>
<record id="paperformat_fp_rack_travel" model="report.paperformat">
<field name="name">FP Rack Travel A5 landscape</field>
<field name="name">FP Rack Travel - A5 landscape</field>
<field name="format">A5</field>
<field name="orientation">Landscape</field>
<field name="margin_top">8</field>
@@ -70,9 +70,9 @@
&#160;·&#160;
<strong>State:</strong>
<t t-if="'racking_state' in rack._fields">
<span t-esc="dict(rack._fields['racking_state'].selection).get(rack.racking_state, rack.racking_state) or ''"/>
<span t-esc="dict(rack._fields['racking_state'].selection).get(rack.racking_state, rack.racking_state) or '-'"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</div>
<div style="margin-top: 6px;">
<t t-if="'tag_ids' in rack._fields">
@@ -106,13 +106,13 @@
<td><span t-esc="(b.qty_done or 0) - (b.qty_scrapped or 0)"/></td>
<td>
<t t-if="b.job_id and b.job_id.product_id">
<span t-esc="b.job_id.product_id.default_code or b.job_id.product_id.name or ''"/>
<span t-esc="b.job_id.product_id.default_code or b.job_id.product_id.name or '-'"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td><span t-esc="(b.job_id and b.job_id.name) or ''"/></td>
<td><span t-esc="(b.job_id and b.job_id.partner_id and b.job_id.partner_id.name) or ''"/></td>
<td><span t-esc="b.name or ''"/></td>
<td><span t-esc="(b.job_id and b.job_id.name) or '-'"/></td>
<td><span t-esc="(b.job_id and b.job_id.partner_id and b.job_id.partner_id.name) or '-'"/></td>
<td><span t-esc="b.name or '-'"/></td>
</tr>
</t>
<t t-else="">

View File

@@ -2,7 +2,7 @@
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating Payment Receipt (Portrait + Landscape).
Fusion Plating - Payment Receipt (Portrait + Landscape).
Binds to account.payment. Shows amount paid, method, reference,
applied invoices, and a "PAID" stamp.
-->

View File

@@ -3,7 +3,7 @@
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Sub 12 Phase E RMA Authorisation PDF.
Sub 12 Phase E - RMA Authorisation PDF.
Single-page customer-facing PDF emailed when an RMA is authorised.
Contains: our header, customer info, RMA number, parts table, return
address, QR code linking to /fp/rma/<id>, and carrier instructions.
@@ -19,7 +19,7 @@
<div>
<h1 style="margin: 0; font-size: 26px;">Return Material Authorisation</h1>
<p style="margin: 4px 0 0 0; font-size: 14px; color: #666;">
EN Technologies Plating &amp; Metal Finishing
EN Technologies - Plating &amp; Metal Finishing
</p>
</div>
<div style="text-align: right;">
@@ -95,13 +95,13 @@
<li>Print this authorisation and include it with your shipment.</li>
<li>Pack returned parts in their <strong>original boxes</strong> (we ship back in the same boxes per shop policy).</li>
<li>Mark each box clearly with the RMA number <strong t-out="rma.name"/>.</li>
<li>Ship to the address below pre-paid carrier of your choice.</li>
<li>Ship to the address below - pre-paid carrier of your choice.</li>
<li>Send your tracking number to your account contact so we can monitor the return.</li>
</ol>
<p style="font-size: 12px; margin-top: 12px;"><strong>Return Address:</strong></p>
<p style="font-size: 12px; margin-left: 14px;">
EN Technologies<br/>
Receiving RMA <span t-out="rma.name"/><br/>
Receiving - RMA <span t-out="rma.name"/><br/>
[shop street address]<br/>
[city, province, postal code]
</p>

View File

@@ -2,7 +2,7 @@
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating Quotation / Sales Order (Portrait + Landscape)
Fusion Plating - Quotation / Sales Order (Portrait + Landscape)
Renders the same sale.order with a title that flips between
"Quotation" (draft/sent) and "Sales Order" (confirmed/done).
-->
@@ -20,14 +20,14 @@
margin-top) pushed the title up INTO the wkhtmltopdf header zone
(the company logo band) and clipped the top of the H1 glyphs.
External_layout already places the page body at the bottom of
the reserved margin-top don't fight that. Use a small positive
the reserved margin-top - don't fight that. Use a small positive
gap and shrink the title text instead. -->
<!-- Custom minimal layout same .article wrapper that Odoo's
<!-- Custom minimal layout - same .article wrapper that Odoo's
report pipeline expects (so UTF-8 charset handling works
correctly via the standard processing path), but with NO
auto company .header div. Includes a minimal .footer div
that ONLY carries the wkhtmltopdf page-number placeholders
(`<span class="page"/> / <span class="topage"/>`) those
(`<span class="page"/> / <span class="topage"/>`) - those
only get filled in when the .footer div is extracted into
wkhtmltopdf's footer-html stream. The .footer is otherwise
empty so no boilerplate company info shows. -->
@@ -69,12 +69,12 @@
and wraps to two naturally if the cell is narrow. Apply
this everywhere except super-narrow cells (QTY, UOM)
where the cell is physically too tight even for the
shortest French word those use the stacked variant
shortest French word - those use the stacked variant
below. */
.fp-bl-en { font-weight: bold; }
.fp-bl-sep { color: #999; margin: 0 3px; font-weight: normal; }
.fp-bl-fr { font-weight: normal; font-style: italic; color: #555; }
/* Stacked variant for narrow cells EN on top line, FR
/* Stacked variant for narrow cells - EN on top line, FR
below in italic-grey. */
.fp-bl-en-stk { display: block; font-weight: bold; }
.fp-bl-fr-stk { display: block; font-weight: normal; font-style: italic; color: #555; font-size: 80%; margin-top: 1px; }
@@ -125,12 +125,12 @@
.fp-sale-company-addr div { margin: 0; }
.fp-sale-company-addr a { color: #2e6da4; text-decoration: none; }
/* Inline footer line phone | email | website | tax id.
/* Inline footer line - phone | email | website | tax id.
One-time render at the bottom of page 1 (multi-page SO
reports are rare; if we ever need it, switch to
wkhtmltopdf --footer-html). */
.fp-sale-customfooter { text-align: center; font-size: 8pt; color: #666; margin-top: 24px; padding-top: 8px; border-top: 1px solid #ccc; }
/* Title bar uses float-based div layout, NOT an HTML table
/* Title bar uses float-based div layout, NOT an HTML table -
the global ".fp-report table" rule was applying borders
to every nested table even with "border: 0 !important",
so the only reliable fix is to avoid the table element. */
@@ -178,7 +178,7 @@
<div class="fp-report fp-sale">
<!-- Inline header (drops web.external_layout for this
report see CSS comment for context). Left: logo
report - see CSS comment for context). Left: logo
+ address + tel/fax + URL. Right: bilingual title
+ Code128 barcode of the order number. -->
<div class="fp-sale-header-row">
@@ -228,7 +228,7 @@
<div class="page">
<!-- Billing / Shipping (wide cells inline) -->
<!-- Billing / Shipping (wide cells - inline) -->
<table class="bordered">
<thead>
<tr>
@@ -254,7 +254,7 @@
</tbody>
</table>
<!-- Row 1: 5 narrow cells (20% each) stacked
<!-- Row 1: 5 narrow cells (20% each) - stacked
so the French label doesn't overflow into
the next column. -->
<table class="bordered">
@@ -282,7 +282,7 @@
<tr>
<td class="text-center"><span t-field="doc.date_order" t-options="{'widget': 'date'}"/></td>
<td class="text-center"><span t-field="doc.user_id"/></td>
<td class="text-center"><span t-esc="doc.x_fc_po_number or ''"/></td>
<td class="text-center"><span t-esc="doc.x_fc_po_number or '-'"/></td>
<td class="text-center">
<!-- Lead Time renders from the
computed display string on
@@ -301,7 +301,7 @@
</tbody>
</table>
<!-- Row 2: 2 wide cells (50% each) inline. -->
<!-- Row 2: 2 wide cells (50% each) - inline. -->
<t t-if="doc.x_fc_customer_job_number or delivery_method_label">
<table class="bordered">
<thead>
@@ -316,8 +316,8 @@
</thead>
<tbody>
<tr>
<td class="text-center"><span t-esc="doc.x_fc_customer_job_number or ''"/></td>
<td class="text-center"><span t-esc="delivery_method_label or ''"/></td>
<td class="text-center"><span t-esc="doc.x_fc_customer_job_number or '-'"/></td>
<td class="text-center"><span t-esc="delivery_method_label or '-'"/></td>
</tr>
</tbody>
</table>
@@ -337,7 +337,7 @@
</div>
</t>
<!-- Order lines. Taxes column dropped taxes
<!-- Order lines. Taxes column dropped - taxes
summarized in the totals block below; per-line
tax labels were noise on a single-tax-region
plating order. The part-number cell appends
@@ -397,19 +397,19 @@
<t t-if="line.x_fc_part_catalog_id and line.x_fc_part_catalog_id.name">
<span t-esc="line.x_fc_part_catalog_id.name"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</div>
<div>
<strong>S/N:</strong>
<t t-if="line.x_fc_serial_ids">
<span t-esc="', '.join(line.x_fc_serial_ids.mapped('name'))"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</div>
</td>
<td>
<!-- Rebind `line` with fp_no_serial_in_desc=True so the
shared description macro skips its Serial line we
shared description macro skips its Serial line - we
already render S/N in the part-number cell above. -->
<t t-set="line" t-value="line.with_context(fp_no_serial_in_desc=True)"/>
<t t-call="fusion_plating_reports.customer_line_description"/>
@@ -648,7 +648,7 @@
</div>
</t>
<!-- Order lines hide discount column unless at least one line has a discount -->
<!-- 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">

View File

@@ -3,7 +3,7 @@
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Parts-box identification sticker printed on a 4x3" label.
Parts-box identification sticker - printed on a 4x3" label.
Bound to mrp.production (MO), mrp.workorder (WO), fp.job, and
sale.order. The shop talks in "WO #" terms (Steelhead legacy) but
@@ -13,20 +13,20 @@
resolves them itself from `_mo` when called from an mrp.* context.
Variables an outer template MAY pre-set (otherwise falls back to
`_mo`-based resolution):
* _order_id number to print as "WO #"
* _scan_id id encoded into the QR URL
* _scan_path '/fp/job/' or '/fp/wo/' prefix (default '/fp/wo/')
* _mo the mrp.production record (or False)
* _so, _line the originating sale order / line
* _part fp.part.catalog
* _spec fusion.plating.customer.spec (audit-tracked spec)
* _process the resolved fusion.plating.process.node tree
* _due datetime/date for "Due Date" row
* _qty float for "Qty" row
* _po_number overrides _so.x_fc_po_number
* _partner_name overrides _so.partner_id.name
* _mo_ref string shown muted in "(WH/MO/...)" '' to hide
* _internal_note free text for "Notes" row
* _order_id - number to print as "WO #"
* _scan_id - id encoded into the QR URL
* _scan_path - '/fp/job/' or '/fp/wo/' prefix (default '/fp/wo/')
* _mo - the mrp.production record (or False)
* _so, _line - the originating sale order / line
* _part - fp.part.catalog
* _spec - fusion.plating.customer.spec (audit-tracked spec)
* _process - the resolved fusion.plating.process.node tree
* _due - datetime/date for "Due Date" row
* _qty - float for "Qty" row
* _po_number - overrides _so.x_fc_po_number
* _partner_name - overrides _so.partner_id.name
* _mo_ref - string shown muted in "(WH/MO/...)" - '' to hide
* _internal_note- free text for "Notes" row
-->
<odoo>
@@ -59,7 +59,7 @@
else (_mo and _mo.product_qty) or 0"/>
<t t-set="_po_number" t-value="_po_number or (_so and _so.x_fc_po_number) or '-'"/>
<t t-set="_partner_name" t-value="_partner_name or (_so and _so.partner_id.name) or '-'"/>
<!-- Customer short-code for shop-floor "secrecy cover" operators
<!-- Customer short-code for shop-floor "secrecy cover" - operators
see "ABC-MANU" instead of "ABC Manufacturing Inc", so visiting
customers / unauthorised passers-by can't immediately tell whose
parts are on which rack. Rule: first 3 chars of word[0] + "-"
@@ -84,36 +84,36 @@
or (_so and _so.x_fc_internal_note
and _so.x_fc_internal_note.striptags()[:100])
or '-'"/>
<!-- Serial number Sub 5 added x_fc_serial_id (M2O fp.serial) on
<!-- Serial number - Sub 5 added x_fc_serial_id (M2O fp.serial) on
the SO line. The serial record's `name` is the printable label. -->
<t t-set="_serial_number" t-value="(_line and 'x_fc_serial_id' in _line._fields and _line.x_fc_serial_id and _line.x_fc_serial_id.name) or '-'"/>
<!-- Thickness operator-typed Char range, e.g. "0.0005-0.0008 mils".
<!-- Thickness - operator-typed Char range, e.g. "0.0005-0.0008 mils".
Stored as-typed; ASCII-safe by convention. Strip en/em-dash
defensively for the wkhtmltopdf font path on entech. -->
<t t-set="_thickness_raw" t-value="_line and 'x_fc_thickness_range' in _line._fields and _line.x_fc_thickness_range"/>
<t t-set="_thickness" t-value="(_thickness_raw and _thickness_raw.replace(u'', '-').replace(u'', '-')) or '-'"/>
<!-- Notes content outer can pre-set this (e.g. the Internal
<t t-set="_thickness" t-value="(_thickness_raw and _thickness_raw.replace(u'\u2013', '-').replace(u'\u2014', '-')) or '-'"/>
<!-- Notes content - outer can pre-set this (e.g. the Internal
variant passes line.x_fc_internal_description). Otherwise
falls back to line.name (customer-facing description per
Sub 2 Q6), then to part.name. Strip en/em-dash, smart
quotes, and ellipsis defensively for the wkhtmltopdf font
path on entech same treatment as thickness above, which
otherwise turns "" into the "â€"" mojibake. -->
path on entech - same treatment as thickness above, which
otherwise turns "-" into the "â€"" mojibake. -->
<t t-set="_notes_raw" t-value="_notes_content
or (_line and _line.name)
or (_part and _part.name)
or '-'"/>
<t t-set="_notes_content" t-value="_notes_raw
.replace(u'', '-').replace(u'', '-')
.replace(u'\u2014', '-').replace(u'\u2013', '-')
.replace(u'', &quot;'&quot;).replace(u'', &quot;'&quot;)
.replace(u'“', '&quot;').replace(u'”', '&quot;')
.replace(u'…', '...')"/>
<!-- Inline the QR as base64 data URI so wkhtmltopdf doesn't need
to fetch /report/barcode/ over the network during rendering.
1000x1000 source Odoo core caps barcode area at 1.2M pixels
1000x1000 source - Odoo core caps barcode area at 1.2M pixels
(`width * height > 1200000` raises "Barcode too large"), so we
stay under that ceiling. 1000x1000 at the 31mm wrapper gives
~821ppi effective far above the 203dpi thermal printer. -->
~821ppi effective - far above the 203dpi thermal printer. -->
<t t-set="_qr_src" t-value="env['ir.actions.report'].barcode_data_uri(
'QR', _scan_url, width=1000, height=1000)"/>
@@ -127,7 +127,7 @@
}
/* 3-cell header (Logo | WO# | QR) + 2-region body (fields left,
Notes column right). Absolute positioning + % heights/widths
are mandatory wkhtmltopdf ignores vh/vw/flex. ----------- */
are mandatory - wkhtmltopdf ignores vh/vw/flex. ----------- */
.fp-sticker {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
color: #000;
@@ -251,7 +251,7 @@
/* Values used to be bold via .fp-sticker-strong (PO/Part#/Qty).
Per ops, only the field title and the WO# header should be
bold; values stay regular weight. Font sizes unchanged from
the original layout bumping them broke wkhtmltopdf's row
the original layout - bumping them broke wkhtmltopdf's row
packing on entech, so we accept the same visual weight as
before. -->
.fp-sticker-strong { font-weight: 400; }
@@ -415,7 +415,7 @@
</template>
<!-- =====================================================
Reusable defaults block every outer template t-calls
Reusable defaults block - every outer template t-calls
this BEFORE the sticker inner so `_so`, `_line`, etc.
are always defined. The inner's `_so or fallback`
pattern relies on these names existing in scope.
@@ -442,7 +442,7 @@
<t t-set="_multi_line" t-value="False"/>
</template>
<!-- ========== Outer template mrp.workorder entry ========== -->
<!-- ========== Outer template - mrp.workorder entry ========== -->
<template id="report_fp_wo_sticker">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
@@ -455,7 +455,7 @@
</t>
</template>
<!-- ========== Outer template mrp.production entry ========== -->
<!-- ========== Outer template - mrp.production entry ========== -->
<template id="report_fp_mo_sticker">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
@@ -471,16 +471,16 @@
</t>
</template>
<!-- ========== Outer template sale.order entry ==========
<!-- ========== Outer template - sale.order entry ==========
Prints one box sticker per order line that has a part. Lines
without x_fc_part_catalog_id (service lines, freight, etc.) are
skipped they don't go through plating so they don't need a
skipped - they don't go through plating so they don't need a
box sticker.
The "WO#" header shows the SO name (e.g. SO-30019). The body
carries the part-specific fields (Part #, Customer, etc.) which
disambiguate multi-line SOs without needing a sequence suffix.
The QR encodes /fp/so-line/<line.id> the controller can
The QR encodes /fp/so-line/<line.id> - the controller can
decide whether to land on the parent SO, the line, or (later)
the spawned job. -->
<template id="report_fp_so_sticker">
@@ -492,7 +492,7 @@
<!-- Multi-line PO: one consolidated sticker.
Part # prints "Multiple Line Items", Qty is the
sum of all part-line qtys. Per-box loop disabled
(_qty_total=1) the consolidated label is the
(_qty_total=1) - the consolidated label is the
master-skid label, not per-physical-box. -->
<t t-call="fusion_plating_reports.report_fp_wo_sticker_defaults"/>
<t t-set="_order_id" t-value="so.name"/>
@@ -540,7 +540,7 @@
</t>
</template>
<!-- ========== Outer template sale.order Internal variant ==========
<!-- ========== Outer template - sale.order Internal variant ==========
Same layout + iteration as report_fp_so_sticker, but pre-sets
_notes_content from x_fc_internal_description (Sub 2 internal
description field) so the Notes column shows the ops-facing

View File

@@ -2,7 +2,7 @@
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating Work Order traveller (Portrait + Landscape).
Fusion Plating - Work Order traveller (Portrait + Landscape).
Printed shop-floor sheet with step info, bath/tank, chemistry targets,
and sign-off rows.
-->
@@ -92,14 +92,14 @@
</t>
</strong>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Customer-Facing Description</td>
<td>
<t t-if="_line"><span t-esc="_line.name or ''"/></t>
<t t-else=""></t>
<t t-if="_line"><span t-esc="_line.name or '-'"/></t>
<t t-else="">-</t>
</td>
</tr>
<tr>
@@ -108,12 +108,12 @@
<t t-if="_line and 'x_fc_internal_description' in _line._fields and _line.x_fc_internal_description">
<span t-esc="_line.x_fc_internal_description" style="white-space: pre-wrap;"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Service SKU</td>
<td><span t-esc="doc.product_id.default_code or ''"/></td>
<td><span t-esc="doc.product_id.default_code or '-'"/></td>
</tr>
</tbody>
</table>
@@ -348,19 +348,19 @@
</t>
</strong>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td>
<t t-if="_line"><span t-esc="_line.name or ''"/></t>
<t t-else=""></t>
<t t-if="_line"><span t-esc="_line.name or '-'"/></t>
<t t-else="">-</t>
</td>
<td>
<t t-if="_line and 'x_fc_internal_description' in _line._fields and _line.x_fc_internal_description">
<span t-esc="_line.x_fc_internal_description" style="white-space: pre-wrap;"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td class="text-center"><span t-esc="doc.product_id.default_code or ''"/></td>
<td class="text-center"><span t-esc="doc.product_id.default_code or '-'"/></td>
</tr>
</tbody>
</table>

View File

@@ -2,7 +2,7 @@
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Work Order Margin Report Manufacturing Order
Work Order Margin Report - Manufacturing Order
-->
<odoo>
<template id="report_wo_margin">
@@ -30,7 +30,7 @@
<h2>
Work Order Margin Report
<span style="font-size: 14pt; color: #333;">
<t t-out="d['mo'].name"/>
- <t t-out="d['mo'].name"/>
</span>
</h2>
@@ -56,13 +56,13 @@
<t t-if="d['mo'].sale_order_id">
<t t-out="d['mo'].sale_order_id.name"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
<td class="text-center">
<t t-if="d['mo'].date_start">
<t t-out="d['mo'].date_start" t-options="{'widget': 'date'}"/>
</t>
<t t-else=""></t>
<t t-else="">-</t>
</td>
</tr></tbody>
</table>
@@ -218,7 +218,7 @@
<!-- Report Footer -->
<p style="font-size: 8pt; color: #888; margin-top: 20px; text-align: right;">
Generated by Fusion Plating Nexa Systems Inc.
Generated by Fusion Plating - Nexa Systems Inc.
</p>
</div>
</div>