feat(fusion_plating): quote-to-cash infra, notifications, wizards, Tier 1 plating features

Quote-to-cash PDF reports (portrait + landscape variants, 16 new actions):
- Quotation / Sales Order, Work Order Traveller, Packing Slip, Bill of Lading,
  Certificate of Conformance (portrait added), Invoice, Payment Receipt
- Shared fp_portrait_styles + fp_landscape_styles base templates

Workflow gap fixes (fusion_plating_bridge_mrp):
- Auto-assign recipe from SO coating config in MrpProduction.action_confirm
- Auto-create draft CoC (fp.certificate) on MrpProduction.button_mark_done

Notifications overhaul (fusion_plating_notifications v2.0):
- Expanded TRIGGER_EVENTS to 7 (added quote_sent, mo_complete, shipped, payment_received)
- Shared _dispatch method replaces three duplicated send helpers
- Auto-attach PDF reports per template config (quote, SO, CoC, invoice, receipt, BoL)
- Rebuilt 7 email templates with fusion_claims accent-bar design
  (info/success color-coded, theme-safe, 600px max-width)
- New hooks: MrpProduction done, FpDelivery mark_delivered, AccountPayment post,
  SaleOrder action_quotation_send

Wizards (fusion_plating_configurator):
- fp.direct.order.wizard — skip quotation for repeat customers with PO in hand;
  optional new-revision drawing upload bumps fp.part.catalog revision and links
  new rev to the SO; creates + confirms the SO in one step
- fp.part.catalog.import.wizard — 3-step CSV import with dry-run preview,
  tolerant parsing (customer by name/email/xmlid, human-readable selections),
  duplicate detection, create-missing-customers option, single transaction commit
- Partner form stat buttons: Direct Order, Import Parts
- CSV template download button

Tier 1 practical plating features:
- T1.1 Hydrogen bake window enforcement (fp.coating.config.requires_bake_relief,
  auto-create fusion.plating.bake.window on plating WO finish, FpDelivery lockout
  when window is open)
- T1.2 Bath replenishment rules + pending suggestion queue
  (fusion.plating.bath.replenishment.rule + .suggestion, hook on bath log line
  create, operator Apply / Dismiss actions)
- T1.3 Rack/fixture library (fusion.plating.rack with MTO counter, strip
  schedule, lifecycle: active → needs_strip → stripping → retired)
- T1.4 Rework / strip-and-replate MOs (x_fc_is_rework, x_fc_original_production_id,
  Create Rework stat button on completed MOs)
- T1.5 Parts location (x_fc_current_location computed on mrp.production —
  "In progress: Alkaline Clean" / "Queued: Bake Oven" / "Ready to Ship")

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-16 23:41:12 -04:00
parent 7c7ef06057
commit d3dd6376a6
51 changed files with 5231 additions and 197 deletions

View File

@@ -3,21 +3,28 @@
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Plating — Reports',
'version': '19.0.1.0.0',
'version': '19.0.2.0.0',
'category': 'Manufacturing/Plating',
'summary': 'PDF reports for all Fusion Plating models: CoC, NCR, CAPA, bath logs, calibration, and more.',
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
'depends': [
'sale',
'account',
'stock',
'mrp',
'fusion_plating',
'fusion_plating_quality',
'fusion_plating_compliance',
'fusion_plating_safety',
'fusion_plating_portal',
'fusion_plating_configurator',
'fusion_plating_bridge_mrp',
'fusion_plating_logistics',
],
'data': [
'security/ir.model.access.csv',
'report/report_base_styles.xml',
'report/report_actions.xml',
# Quality + compliance reports
'report/report_coc.xml',
'report/report_ncr.xml',
'report/report_capa.xml',
@@ -30,6 +37,13 @@
'report/report_waste_manifest.xml',
'report/report_discharge_sample.xml',
'report/report_wo_margin.xml',
# Quote-to-cash reports (portrait + landscape)
'report/report_fp_sale.xml',
'report/report_fp_work_order.xml',
'report/report_fp_packing_slip.xml',
'report/report_fp_bol.xml',
'report/report_fp_invoice.xml',
'report/report_fp_receipt.xml',
],
'installable': True,
'application': False,

View File

@@ -24,10 +24,10 @@
</record>
<!-- ============================================================= -->
<!-- 1. Certificate of Conformance (Portal Job) -->
<!-- 1. Certificate of Conformance (Portal Job) — Landscape -->
<!-- ============================================================= -->
<record id="action_report_coc" model="ir.actions.report">
<field name="name">Certificate of Conformance</field>
<field name="name">Certificate of Conformance (Landscape)</field>
<field name="model">fusion.plating.portal.job</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_coc</field>
@@ -38,6 +38,18 @@
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- Certificate of Conformance — Portrait -->
<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>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_coc_portrait</field>
<field name="report_file">fusion_plating_reports.report_coc_portrait</field>
<field name="print_report_name">'CoC - %s' % object.name</field>
<field name="binding_model_id" ref="fusion_plating_portal.model_fusion_plating_portal_job"/>
<field name="binding_type">report</field>
</record>
<!-- ============================================================= -->
<!-- 2. Non-Conformance Report -->
<!-- ============================================================= -->
@@ -202,4 +214,160 @@
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- ============================================================= -->
<!-- 13. Quotation / Sales Order (Portrait + Landscape) -->
<!-- ============================================================= -->
<record id="action_report_fp_sale_portrait" model="ir.actions.report">
<field name="name">Quotation / Order (Portrait)</field>
<field name="model">sale.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_sale_portrait</field>
<field name="report_file">fusion_plating_reports.report_fp_sale_portrait</field>
<field name="print_report_name">(object.state in ('draft', 'sent') and 'Quotation - %s' % object.name) or 'Order - %s' % object.name</field>
<field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="binding_type">report</field>
</record>
<record id="action_report_fp_sale_landscape" model="ir.actions.report">
<field name="name">Quotation / Order (Landscape)</field>
<field name="model">sale.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_sale_landscape</field>
<field name="report_file">fusion_plating_reports.report_fp_sale_landscape</field>
<field name="print_report_name">(object.state in ('draft', 'sent') and 'Quotation - %s' % object.name) or 'Order - %s' % object.name</field>
<field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- ============================================================= -->
<!-- 14. Work Order Traveller (Portrait + Landscape) -->
<!-- ============================================================= -->
<record id="action_report_fp_work_order_portrait" model="ir.actions.report">
<field name="name">Work Order Traveller (Portrait)</field>
<field name="model">mrp.workorder</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_work_order_portrait</field>
<field name="report_file">fusion_plating_reports.report_fp_work_order_portrait</field>
<field name="print_report_name">'WO - %s' % object.name</field>
<field name="binding_model_id" ref="mrp.model_mrp_workorder"/>
<field name="binding_type">report</field>
</record>
<record id="action_report_fp_work_order_landscape" model="ir.actions.report">
<field name="name">Work Order Traveller (Landscape)</field>
<field name="model">mrp.workorder</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_work_order_landscape</field>
<field name="report_file">fusion_plating_reports.report_fp_work_order_landscape</field>
<field name="print_report_name">'WO - %s' % object.name</field>
<field name="binding_model_id" ref="mrp.model_mrp_workorder"/>
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- ============================================================= -->
<!-- 15. Packing Slip (Portrait + Landscape) -->
<!-- ============================================================= -->
<record id="action_report_fp_packing_slip_portrait" model="ir.actions.report">
<field name="name">Packing Slip (Portrait)</field>
<field name="model">stock.picking</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_packing_slip_portrait</field>
<field name="report_file">fusion_plating_reports.report_fp_packing_slip_portrait</field>
<field name="print_report_name">'Packing Slip - %s' % object.name</field>
<field name="binding_model_id" ref="stock.model_stock_picking"/>
<field name="binding_type">report</field>
</record>
<record id="action_report_fp_packing_slip_landscape" model="ir.actions.report">
<field name="name">Packing Slip (Landscape)</field>
<field name="model">stock.picking</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_packing_slip_landscape</field>
<field name="report_file">fusion_plating_reports.report_fp_packing_slip_landscape</field>
<field name="print_report_name">'Packing Slip - %s' % object.name</field>
<field name="binding_model_id" ref="stock.model_stock_picking"/>
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- ============================================================= -->
<!-- 16. Bill of Lading (Portrait + Landscape) -->
<!-- ============================================================= -->
<record id="action_report_fp_bol_portrait" model="ir.actions.report">
<field name="name">Bill of Lading (Portrait)</field>
<field name="model">fusion.plating.delivery</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_bol_portrait</field>
<field name="report_file">fusion_plating_reports.report_fp_bol_portrait</field>
<field name="print_report_name">'BoL - %s' % object.name</field>
<field name="binding_model_id" ref="fusion_plating_logistics.model_fusion_plating_delivery"/>
<field name="binding_type">report</field>
</record>
<record id="action_report_fp_bol_landscape" model="ir.actions.report">
<field name="name">Bill of Lading (Landscape)</field>
<field name="model">fusion.plating.delivery</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_bol_landscape</field>
<field name="report_file">fusion_plating_reports.report_fp_bol_landscape</field>
<field name="print_report_name">'BoL - %s' % object.name</field>
<field name="binding_model_id" ref="fusion_plating_logistics.model_fusion_plating_delivery"/>
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- ============================================================= -->
<!-- 17. Invoice (Portrait + Landscape) -->
<!-- ============================================================= -->
<record id="action_report_fp_invoice_portrait" model="ir.actions.report">
<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>
<field name="report_file">fusion_plating_reports.report_fp_invoice_portrait</field>
<field name="print_report_name">'Invoice - %s' % (object.name or '')</field>
<field name="binding_model_id" ref="account.model_account_move"/>
<field name="binding_type">report</field>
</record>
<record id="action_report_fp_invoice_landscape" model="ir.actions.report">
<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>
<field name="report_file">fusion_plating_reports.report_fp_invoice_landscape</field>
<field name="print_report_name">'Invoice - %s' % (object.name or '')</field>
<field name="binding_model_id" ref="account.model_account_move"/>
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- ============================================================= -->
<!-- 18. Payment Receipt (Portrait + Landscape) -->
<!-- ============================================================= -->
<record id="action_report_fp_receipt_portrait" model="ir.actions.report">
<field name="name">Payment Receipt (Portrait)</field>
<field name="model">account.payment</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_receipt_portrait</field>
<field name="report_file">fusion_plating_reports.report_fp_receipt_portrait</field>
<field name="print_report_name">'Receipt - %s' % (object.name or '')</field>
<field name="binding_model_id" ref="account.model_account_payment"/>
<field name="binding_type">report</field>
</record>
<record id="action_report_fp_receipt_landscape" model="ir.actions.report">
<field name="name">Payment Receipt (Landscape)</field>
<field name="model">account.payment</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_receipt_landscape</field>
<field name="report_file">fusion_plating_reports.report_fp_receipt_landscape</field>
<field name="print_report_name">'Receipt - %s' % (object.name or '')</field>
<field name="binding_model_id" ref="account.model_account_payment"/>
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
</odoo>

View File

@@ -3,12 +3,48 @@
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
Shared landscape CSS for all Fusion Plating reports.
Shared CSS for all Fusion Plating reports (portrait + landscape).
-->
<odoo>
<!-- ============================================================= -->
<!-- Portrait Styles -->
<!-- ============================================================= -->
<template id="fp_portrait_styles">
<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 td { padding: 6px 8px; vertical-align: top; font-size: 10pt; }
.fp-report .text-center { text-align: center; }
.fp-report .text-end { text-align: right; }
.fp-report .text-start { text-align: left; }
.fp-report .adp-bg { background-color: #e3f2fd; }
.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 .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 .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; }
.fp-report .status-fail { color: #c62828; font-weight: bold; }
.fp-report .sig-box { border: 1px solid #000; padding: 12px; min-height: 70px; }
.fp-report .sig-line { border-bottom: 1px solid #000; min-height: 28px; }
.fp-report .small-muted { font-size: 8pt; color: #666; }
</style>
</template>
<!-- ============================================================= -->
<!-- Landscape Styles -->
<!-- ============================================================= -->
<template id="fp_landscape_styles">
<style>
.fp-landscape { font-family: Arial, sans-serif; font-size: 11pt; }
.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; }
@@ -19,15 +55,20 @@
.fp-landscape .adp-bg { background-color: #e3f2fd; }
.fp-landscape .client-bg { background-color: #fff3e0; }
.fp-landscape .section-row { background-color: #f0f0f0; font-weight: bold; }
.fp-landscape .note-row { font-style: italic; }
.fp-landscape .note-row { font-style: italic; color: #555; }
.fp-landscape h2 { color: #0066a1; 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 .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; }
.fp-landscape .status-fail { color: #c62828; font-weight: bold; }
.fp-landscape .sig-box { border: 1px solid #000; padding: 12px; min-height: 70px; }
.fp-landscape .sig-line { border-bottom: 1px solid #000; min-height: 28px; }
.fp-landscape .small-muted { font-size: 9pt; color: #666; }
</style>
</template>
</odoo>

View File

@@ -2,9 +2,132 @@
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Certificate of Conformance — Portal Job
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.
-->
<odoo>
<!-- ============================================================= -->
<!-- PORTRAIT -->
<!-- ============================================================= -->
<template id="report_coc_portrait">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<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>
<th class="info-header" style="width: 25%;">JOB REF</th>
<th class="info-header" style="width: 25%;">QTY</th>
<th class="info-header" style="width: 25%;">RECEIVED</th>
<th class="info-header" style="width: 25%;">SHIP DATE</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.name"/></td>
<td class="text-center"><span t-field="doc.quantity"/></td>
<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>
</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>
</t>
</t>
</template>
<!-- ============================================================= -->
<!-- LANDSCAPE (legacy id `report_coc` kept for existing bindings) -->
<!-- ============================================================= -->
<template id="report_coc">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
@@ -111,4 +234,5 @@
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,380 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating — Bill of Lading (Portrait + Landscape).
Binds to fusion.plating.delivery. Includes shipper, consignee, carrier,
cargo description, special instructions, and sign-off lines.
-->
<odoo>
<!-- ============================================================= -->
<!-- PORTRAIT -->
<!-- ============================================================= -->
<template id="report_fp_bol_portrait">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-call="fusion_plating_reports.fp_portrait_styles"/>
<div class="fp-report">
<div class="page">
<h4 class="text-center" style="text-align: center;">
BILL OF LADING
</h4>
<div class="text-center" style="text-align: center; margin-bottom: 10px;">
<strong>BoL #: <span t-field="doc.name"/></strong>
</div>
<!-- Shipper / Consignee -->
<table class="bordered">
<thead>
<tr>
<th style="width: 50%;">SHIPPER</th>
<th style="width: 50%;">CONSIGNEE</th>
</tr>
</thead>
<tbody>
<tr>
<td style="height: 90px;">
<strong><span t-field="doc.company_id.name"/></strong><br/>
<t t-if="doc.source_facility_id">
<em t-field="doc.source_facility_id.name"/><br/>
</t>
<div t-field="doc.company_id.partner_id"
t-options="{'widget': 'contact', 'fields': ['address', 'phone', 'email'], 'no_marker': True}"/>
</td>
<td style="height: 90px;">
<strong><span t-field="doc.partner_id.name"/></strong><br/>
<t t-set="dest" t-value="doc.delivery_address_id or doc.partner_id"/>
<div t-field="dest"
t-options="{'widget': 'contact', 'fields': ['address', 'phone'], 'no_marker': True}"/>
<t t-if="doc.contact_name">
<strong>Attn: </strong><span t-field="doc.contact_name"/><br/>
</t>
<t t-if="doc.contact_phone">
<strong>Phone: </strong><span t-field="doc.contact_phone"/>
</t>
</td>
</tr>
</tbody>
</table>
<!-- Shipment info -->
<table class="bordered">
<thead>
<tr>
<th class="info-header" style="width: 33%;">SHIP DATE</th>
<th class="info-header" style="width: 33%;">DRIVER</th>
<th class="info-header" style="width: 34%;">VEHICLE</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.scheduled_date" t-options="{'widget': 'date'}"/></td>
<td class="text-center">
<t t-if="doc.assigned_driver_id">
<span t-field="doc.assigned_driver_id.name"/>
</t>
<t t-else="">-</t>
</td>
<td class="text-center">
<t t-if="doc.vehicle_id">
<span t-field="doc.vehicle_id"/>
</t>
<t t-else="">-</t>
</td>
</tr>
</tbody>
</table>
<!-- Job ref / TDG -->
<table class="bordered">
<thead>
<tr>
<th class="info-header" style="width: 50%;">JOB REFERENCE</th>
<th class="info-header" style="width: 50%;">DANGEROUS GOODS (TDG)</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-esc="doc.job_ref or '-'"/></td>
<td class="text-center">
<span t-if="doc.tdg_required" class="status-warning">TDG REQUIRED</span>
<span t-else="" class="status-ok">No TDG</span>
</td>
</tr>
</tbody>
</table>
<!-- Cargo description -->
<table class="bordered">
<thead>
<tr>
<th colspan="4" style="background-color: #0066a1; color: white;">CARGO DESCRIPTION</th>
</tr>
<tr>
<th style="width: 12%;">PACKAGES</th>
<th class="text-start" style="width: 58%;">DESCRIPTION OF GOODS</th>
<th style="width: 15%;">WEIGHT</th>
<th style="width: 15%;">CLASS</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">1</td>
<td>
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">
<span t-if="doc.tdg_required">TDG</span>
<span t-else="">NON-HAZ</span>
</td>
</tr>
</tbody>
</table>
<!-- Certificate + Packing list refs -->
<table class="bordered">
<thead>
<tr>
<th class="info-header" style="width: 50%;">CoC ATTACHED</th>
<th class="info-header" style="width: 50%;">PACKING LIST</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">
<span t-if="doc.coc_attachment_id" class="status-ok">✓ Attached</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>
</td>
</tr>
</tbody>
</table>
<!-- Certification statement -->
<div class="highlight-box" style="margin-top: 10px;">
This is to certify that the above-named materials are properly classified,
packaged, marked, and labelled, and are in proper condition for transportation
according to the applicable regulations of the Department of Transportation.
</div>
<!-- Sign off -->
<div class="row" style="margin-top: 20px;">
<div class="col-4">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Shipper (Signature / Date)</div>
</div>
</div>
<div class="col-4">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Carrier / Driver (Signature / Date)</div>
</div>
</div>
<div class="col-4">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Consignee (Signature / Date)</div>
</div>
</div>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
<!-- ============================================================= -->
<!-- LANDSCAPE -->
<!-- ============================================================= -->
<template id="report_fp_bol_landscape">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-call="fusion_plating_reports.fp_landscape_styles"/>
<div class="fp-landscape">
<div class="page">
<h2 style="text-align: center;">BILL OF LADING</h2>
<div class="text-center" style="text-align: center; margin-bottom: 10px;">
<strong>BoL #: <span t-field="doc.name"/></strong>
</div>
<!-- Shipper / Consignee -->
<table class="bordered">
<thead>
<tr>
<th style="width: 50%;">SHIPPER</th>
<th style="width: 50%;">CONSIGNEE</th>
</tr>
</thead>
<tbody>
<tr>
<td style="height: 100px; font-size: 12pt;">
<strong><span t-field="doc.company_id.name"/></strong><br/>
<t t-if="doc.source_facility_id">
<em t-field="doc.source_facility_id.name"/><br/>
</t>
<div t-field="doc.company_id.partner_id"
t-options="{'widget': 'contact', 'fields': ['address', 'phone', 'email'], 'no_marker': True}"/>
</td>
<td style="height: 100px; font-size: 12pt;">
<strong><span t-field="doc.partner_id.name"/></strong><br/>
<t t-set="dest" t-value="doc.delivery_address_id or doc.partner_id"/>
<div t-field="dest"
t-options="{'widget': 'contact', 'fields': ['address', 'phone'], 'no_marker': True}"/>
<t t-if="doc.contact_name">
<strong>Attn: </strong><span t-field="doc.contact_name"/><br/>
</t>
<t t-if="doc.contact_phone">
<strong>Phone: </strong><span t-field="doc.contact_phone"/>
</t>
</td>
</tr>
</tbody>
</table>
<!-- Shipment info (wide) -->
<table class="bordered info-table">
<thead>
<tr>
<th>SHIP DATE</th>
<th>DRIVER</th>
<th>VEHICLE</th>
<th>JOB REFERENCE</th>
<th>TDG</th>
<th>STATUS</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.scheduled_date" t-options="{'widget': 'date'}"/></td>
<td class="text-center">
<t t-if="doc.assigned_driver_id"><span t-field="doc.assigned_driver_id.name"/></t>
<t t-else="">-</t>
</td>
<td class="text-center">
<t t-if="doc.vehicle_id"><span t-field="doc.vehicle_id"/></t>
<t t-else="">-</t>
</td>
<td class="text-center"><span t-esc="doc.job_ref or '-'"/></td>
<td class="text-center">
<span t-if="doc.tdg_required" class="status-warning">REQUIRED</span>
<span t-else="" class="status-ok">None</span>
</td>
<td class="text-center"><span t-field="doc.state"/></td>
</tr>
</tbody>
</table>
<!-- Cargo description -->
<table class="bordered">
<thead>
<tr>
<th colspan="6" style="background-color: #0066a1; color: white;">CARGO DESCRIPTION</th>
</tr>
<tr>
<th style="width: 10%;">PACKAGES</th>
<th class="text-start" style="width: 40%;">DESCRIPTION OF GOODS</th>
<th style="width: 12%;">QTY</th>
<th style="width: 12%;">WEIGHT</th>
<th style="width: 12%;">CLASS</th>
<th style="width: 14%;">SPECIAL HANDLING</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">1</td>
<td>
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">
<span t-if="doc.tdg_required">TDG</span>
<span t-else="">NON-HAZ</span>
</td>
<td class="text-center">
<span t-if="doc.tdg_required" class="status-warning">TDG HANDLING</span>
<span t-else="">Standard</span>
</td>
</tr>
</tbody>
</table>
<!-- Attachment refs -->
<table class="bordered info-table">
<thead>
<tr>
<th>CoC</th>
<th>PACKING LIST</th>
<th>CUSTODY EVENTS</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">
<span t-if="doc.coc_attachment_id" class="status-ok">✓ Attached</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>
</td>
<td class="text-center"><span t-field="doc.custody_event_count"/></td>
</tr>
</tbody>
</table>
<!-- Certification -->
<div class="highlight-box" style="margin-top: 10px;">
This is to certify that the above-named materials are properly classified,
packaged, marked, and labelled, and are in proper condition for transportation
according to the applicable regulations of the Department of Transportation.
</div>
<!-- Sign off -->
<div class="row" style="margin-top: 20px;">
<div class="col-4">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Shipper (Signature / Date)</div>
</div>
</div>
<div class="col-4">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Carrier / Driver (Signature / Date)</div>
</div>
</div>
<div class="col-4">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Consignee (Signature / Date)</div>
</div>
</div>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,420 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating — Invoice (Portrait + Landscape).
Binds to account.move. Includes invoice strategy, deposit context,
payment details, and amount due.
-->
<odoo>
<!-- ============================================================= -->
<!-- PORTRAIT -->
<!-- ============================================================= -->
<template id="report_fp_invoice_portrait">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)"/>
<t t-call="fusion_plating_reports.fp_portrait_styles"/>
<div class="fp-report">
<div class="page">
<h4>
<span t-if="doc.move_type == 'out_invoice' and doc.state == 'posted'">Invoice </span>
<span t-elif="doc.move_type == 'out_invoice' and doc.state == 'draft'">Draft Invoice </span>
<span t-elif="doc.move_type == 'out_refund'">Credit Note </span>
<span t-elif="doc.move_type == 'in_invoice'">Vendor Bill </span>
<span t-field="doc.name"/>
</h4>
<!-- Billing / Shipping -->
<table class="bordered">
<thead>
<tr>
<th style="width: 50%;">BILLING ADDRESS</th>
<th style="width: 50%;">DELIVERY ADDRESS</th>
</tr>
</thead>
<tbody>
<tr>
<td style="height: 70px;">
<div t-field="doc.partner_id"
t-options="{'widget': 'contact', 'fields': ['name', 'address', 'phone', 'email'], 'no_marker': True}"/>
</td>
<td style="height: 70px;">
<t t-if="doc.partner_shipping_id">
<div t-field="doc.partner_shipping_id"
t-options="{'widget': 'contact', 'fields': ['name', 'address', 'phone'], 'no_marker': True}"/>
</t>
<t t-else="">
<div t-field="doc.partner_id"
t-options="{'widget': 'contact', 'fields': ['name', 'address'], 'no_marker': True}"/>
</t>
</td>
</tr>
</tbody>
</table>
<!-- Invoice info -->
<table class="bordered">
<thead>
<tr>
<th class="info-header" style="width: 25%;">INVOICE DATE</th>
<th class="info-header" style="width: 25%;">DUE DATE</th>
<th class="info-header" style="width: 25%;">SOURCE</th>
<th class="info-header" style="width: 25%;">SALES REP</th>
</tr>
</thead>
<tbody>
<tr>
<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_origin"/></td>
<td class="text-center"><span t-field="doc.invoice_user_id"/></td>
</tr>
</tbody>
</table>
<!-- Lines -->
<table class="bordered">
<thead>
<tr>
<th style="width: 12%;">SKU</th>
<th class="text-start" style="width: 40%;">DESCRIPTION</th>
<th style="width: 8%;">QTY</th>
<th style="width: 8%;">UOM</th>
<th style="width: 12%;">UNIT PRICE</th>
<th style="width: 8%;">TAXES</th>
<th style="width: 12%;">AMOUNT</th>
</tr>
</thead>
<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="7"><strong t-field="line.name"/></td></tr>
</t>
<t t-elif="line.display_type == 'line_note'">
<tr class="note-row"><td colspan="7"><span t-field="line.name"/></td></tr>
</t>
<t t-elif="not line.display_type">
<tr>
<td class="text-center"><span t-esc="line.product_id.default_code or ''"/></td>
<td>
<t t-set="clean_name" t-value="line.name"/>
<t t-if="line.name and '] ' in line.name">
<t t-set="clean_name" t-value="line.name.split('] ', 1)[1]"/>
</t>
<span t-esc="clean_name"/>
</td>
<td class="text-center">
<span t-esc="int(line.quantity) if line.quantity == int(line.quantity) else line.quantity"/>
</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">
<t t-esc="', '.join([(tax.invoice_label or tax.name) for tax in line.tax_ids]) or '-'"/>
</td>
<td class="text-end">
<span t-field="line.price_subtotal" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
</t>
</t>
</tbody>
</table>
<!-- Terms + Totals -->
<div class="row" style="margin-top: 15px;">
<div class="col-6">
<t t-if="doc.invoice_payment_term_id.note">
<strong>Payment Terms:</strong><br/>
<span t-field="doc.invoice_payment_term_id.note"/>
</t>
<t t-if="doc.payment_reference">
<div style="margin-top: 10px;">
<strong>Payment Reference:</strong>
<span t-field="doc.payment_reference"/>
</div>
</t>
</div>
<div class="col-6" style="text-align: right;">
<table class="totals-table" style="width: auto; margin-left: auto;">
<tr>
<td style="min-width: 150px;">Subtotal</td>
<td class="text-end" style="min-width: 110px;">
<span t-field="doc.amount_untaxed" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
<tr>
<td>Taxes</td>
<td class="text-end">
<span t-field="doc.amount_tax" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
<tr style="background-color: #eaf2f8;">
<td><strong>Grand Total</strong></td>
<td class="text-end"><strong>
<span t-field="doc.amount_total" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</strong></td>
</tr>
<t t-if="doc.amount_residual and doc.amount_residual != doc.amount_total">
<tr>
<td><strong>Amount Due</strong></td>
<td class="text-end"><strong>
<span t-field="doc.amount_residual" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</strong></td>
</tr>
</t>
</table>
</div>
</div>
<!-- Paid stamp -->
<t t-if="doc.payment_state == 'paid'">
<div style="margin-top: 15px; text-align: center;">
<span class="paid-stamp">PAID</span>
</div>
</t>
<!-- Notes -->
<t t-if="doc.narration">
<div style="margin-top: 15px;">
<strong>Notes:</strong>
<div t-field="doc.narration"/>
</div>
</t>
</div>
</div>
</t>
</t>
</t>
</template>
<!-- ============================================================= -->
<!-- LANDSCAPE -->
<!-- ============================================================= -->
<template id="report_fp_invoice_landscape">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)"/>
<t t-call="fusion_plating_reports.fp_landscape_styles"/>
<div class="fp-landscape">
<div class="page">
<h2 style="text-align: left;">
<span t-if="doc.move_type == 'out_invoice' and doc.state == 'posted'">Invoice </span>
<span t-elif="doc.move_type == 'out_invoice' and doc.state == 'draft'">Draft Invoice </span>
<span t-elif="doc.move_type == 'out_refund'">Credit Note </span>
<span t-elif="doc.move_type == 'in_invoice'">Vendor Bill </span>
<span t-field="doc.name"/>
</h2>
<!-- Billing / Shipping -->
<table class="bordered">
<thead>
<tr>
<th style="width: 50%;">BILLING ADDRESS</th>
<th style="width: 50%;">DELIVERY ADDRESS</th>
</tr>
</thead>
<tbody>
<tr>
<td style="height: 70px; font-size: 12pt;">
<div t-field="doc.partner_id"
t-options="{'widget': 'contact', 'fields': ['name', 'address', 'phone', 'email'], 'no_marker': True}"/>
</td>
<td style="height: 70px; font-size: 12pt;">
<t t-if="doc.partner_shipping_id">
<div t-field="doc.partner_shipping_id"
t-options="{'widget': 'contact', 'fields': ['name', 'address', 'phone'], 'no_marker': True}"/>
</t>
<t t-else="">
<div t-field="doc.partner_id"
t-options="{'widget': 'contact', 'fields': ['name', 'address'], 'no_marker': True}"/>
</t>
</td>
</tr>
</tbody>
</table>
<!-- Invoice info (wide) -->
<table class="bordered info-table">
<thead>
<tr>
<th>INVOICE DATE</th>
<th>DUE DATE</th>
<th>SOURCE</th>
<th>SALES REP</th>
<th>PAYMENT REF</th>
<th>CURRENCY</th>
</tr>
</thead>
<tbody>
<tr>
<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_origin"/></td>
<td class="text-center"><span t-field="doc.invoice_user_id"/></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>
</table>
<!-- Lines -->
<table class="bordered">
<thead>
<tr>
<th style="width: 10%;">SKU</th>
<th class="text-start" style="width: 32%;">DESCRIPTION</th>
<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 style="width: 10%;">TAXES</th>
<th style="width: 10%;">AMOUNT</th>
</tr>
</thead>
<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>
</t>
<t t-elif="line.display_type == 'line_note'">
<tr class="note-row"><td colspan="8"><span t-field="line.name"/></td></tr>
</t>
<t t-elif="not line.display_type">
<tr>
<td class="text-center"><span t-esc="line.product_id.default_code or ''"/></td>
<td>
<t t-set="clean_name" t-value="line.name"/>
<t t-if="line.name and '] ' in line.name">
<t t-set="clean_name" t-value="line.name.split('] ', 1)[1]"/>
</t>
<span t-esc="clean_name"/>
</td>
<td class="text-center">
<span t-esc="int(line.quantity) if line.quantity == int(line.quantity) else line.quantity"/>
</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">
<t t-if="line.discount"><span t-esc="line.discount"/>%</t>
<t t-else="">-</t>
</td>
<td class="text-center">
<t t-esc="', '.join([(tax.invoice_label or tax.name) for tax in line.tax_ids]) or '-'"/>
</td>
<td class="text-end">
<span t-field="line.price_subtotal" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
</t>
</t>
</tbody>
</table>
<!-- Terms + Totals -->
<div class="row" style="margin-top: 15px;">
<div class="col-7">
<t t-if="doc.invoice_payment_term_id.note">
<strong>Payment Terms:</strong><br/>
<span t-field="doc.invoice_payment_term_id.note"/>
</t>
</div>
<div class="col-5" style="text-align: right;">
<table class="totals-table" style="width: auto; margin-left: auto;">
<tr>
<td style="min-width: 200px;">Subtotal</td>
<td class="text-end" style="min-width: 150px;">
<span t-field="doc.amount_untaxed" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
<tr>
<td>Taxes</td>
<td class="text-end">
<span t-field="doc.amount_tax" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
<tr style="background-color: #eaf2f8;">
<td><strong>Grand Total</strong></td>
<td class="text-end"><strong>
<span t-field="doc.amount_total" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</strong></td>
</tr>
<t t-if="doc.amount_residual and doc.amount_residual != doc.amount_total">
<tr>
<td><strong>Amount Due</strong></td>
<td class="text-end"><strong>
<span t-field="doc.amount_residual" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</strong></td>
</tr>
</t>
</table>
</div>
</div>
<!-- Paid stamp -->
<t t-if="doc.payment_state == 'paid'">
<div style="margin-top: 15px; text-align: center;">
<span class="paid-stamp">PAID</span>
</div>
</t>
<!-- Payment history -->
<t t-if="doc.payment_state != 'invoicing_legacy'">
<t t-set="payments_vals" t-value="doc.sudo().invoice_payments_widget and doc.sudo().invoice_payments_widget.get('content') or []"/>
<t t-if="payments_vals">
<table class="bordered" style="margin-top: 15px;">
<thead>
<tr>
<th colspan="3" style="background-color: #28a745; color: white;">
<t t-if="doc.payment_state == 'paid'">✓ 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>
<tr style="background-color: #f5f5f5;">
<th style="width: 30%;">Date</th>
<th style="width: 40%;">Payment Method</th>
<th style="width: 30%;" class="text-end">Amount</th>
</tr>
</thead>
<tbody>
<t t-foreach="payments_vals" t-as="pv">
<tr t-if="not pv.get('is_exchange')">
<td><span t-out="pv.get('date')" t-options='{"widget": "date"}'/></td>
<td><span t-out="pv.get('payment_method_name') or '-'"/></td>
<td class="text-end">
<span t-out="pv.get('amount')" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
</t>
</tbody>
</table>
</t>
</t>
<!-- Notes -->
<t t-if="doc.narration">
<div style="margin-top: 15px;">
<strong>Notes:</strong>
<div t-field="doc.narration"/>
</div>
</t>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,280 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating — Packing Slip / Shipping Confirmation (Portrait + Landscape).
Binds to stock.picking. Shows parts, quantities, lot/serial tracking,
and a receiver sign-off.
-->
<odoo>
<!-- ============================================================= -->
<!-- PORTRAIT -->
<!-- ============================================================= -->
<template id="report_fp_packing_slip_portrait">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-call="fusion_plating_reports.fp_portrait_styles"/>
<div class="fp-report">
<div class="page">
<h4>
Packing Slip —
<span t-field="doc.name"/>
</h4>
<!-- From / To -->
<table class="bordered">
<thead>
<tr>
<th style="width: 50%;">FROM</th>
<th style="width: 50%;">SHIP TO</th>
</tr>
</thead>
<tbody>
<tr>
<td style="height: 80px;">
<strong><span t-field="doc.company_id.name"/></strong><br/>
<div t-field="doc.company_id.partner_id"
t-options="{'widget': 'contact', 'fields': ['address', 'phone', 'email'], 'no_marker': True}"/>
</td>
<td style="height: 80px;">
<strong><span t-field="doc.partner_id.name"/></strong><br/>
<div t-field="doc.partner_id"
t-options="{'widget': 'contact', 'fields': ['address', 'phone'], 'no_marker': True}"/>
</td>
</tr>
</tbody>
</table>
<!-- Shipment info -->
<table class="bordered">
<thead>
<tr>
<th class="info-header" style="width: 25%;">SHIP DATE</th>
<th class="info-header" style="width: 25%;">SOURCE</th>
<th class="info-header" style="width: 25%;">OPERATION</th>
<th class="info-header" style="width: 25%;">CARRIER</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.scheduled_date" t-options="{'widget': 'date'}"/></td>
<td class="text-center"><span t-esc="doc.origin or '-'"/></td>
<td class="text-center"><span t-field="doc.picking_type_id"/></td>
<td class="text-center">
<t t-if="'carrier_id' in doc._fields and doc.carrier_id">
<span t-field="doc.carrier_id"/>
</t>
<t t-else="">-</t>
</td>
</tr>
</tbody>
</table>
<!-- Products -->
<table class="bordered">
<thead>
<tr>
<th style="width: 12%;">SKU</th>
<th class="text-start" style="width: 44%;">PRODUCT</th>
<th style="width: 12%;">QTY</th>
<th style="width: 10%;">UOM</th>
<th style="width: 22%;">LOT / SERIAL</th>
</tr>
</thead>
<tbody>
<t t-foreach="doc.move_ids_without_package" t-as="move">
<tr>
<td class="text-center"><span t-esc="move.product_id.default_code or ''"/></td>
<td><span t-field="move.product_id"/></td>
<td class="text-center">
<span t-esc="int(move.quantity) if move.quantity == int(move.quantity) else move.quantity"/>
</td>
<td class="text-center"><span t-field="move.product_uom"/></td>
<td>
<t t-foreach="move.move_line_ids" t-as="ml">
<t t-if="ml.lot_id">
<span t-field="ml.lot_id.name"/><br/>
</t>
</t>
</td>
</tr>
</t>
</tbody>
</table>
<!-- Notes -->
<t t-if="doc.note">
<div style="margin-top: 10px;">
<strong>Notes:</strong>
<div t-field="doc.note"/>
</div>
</t>
<!-- Sign off -->
<div class="row" style="margin-top: 30px;">
<div class="col-6">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Shipper (Signature / Date)</div>
</div>
</div>
<div class="col-6">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Receiver (Signature / Date)</div>
</div>
</div>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
<!-- ============================================================= -->
<!-- LANDSCAPE -->
<!-- ============================================================= -->
<template id="report_fp_packing_slip_landscape">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-call="fusion_plating_reports.fp_landscape_styles"/>
<div class="fp-landscape">
<div class="page">
<h2 style="text-align: left;">
Packing Slip —
<span t-field="doc.name"/>
</h2>
<!-- From / To -->
<table class="bordered">
<thead>
<tr>
<th style="width: 50%;">FROM</th>
<th style="width: 50%;">SHIP TO</th>
</tr>
</thead>
<tbody>
<tr>
<td style="height: 80px; font-size: 12pt;">
<strong><span t-field="doc.company_id.name"/></strong><br/>
<div t-field="doc.company_id.partner_id"
t-options="{'widget': 'contact', 'fields': ['address', 'phone', 'email'], 'no_marker': True}"/>
</td>
<td style="height: 80px; font-size: 12pt;">
<strong><span t-field="doc.partner_id.name"/></strong><br/>
<div t-field="doc.partner_id"
t-options="{'widget': 'contact', 'fields': ['address', 'phone'], 'no_marker': True}"/>
</td>
</tr>
</tbody>
</table>
<!-- Shipment info -->
<table class="bordered info-table">
<thead>
<tr>
<th>SHIP DATE</th>
<th>SOURCE</th>
<th>OPERATION</th>
<th>CARRIER</th>
<th>TRACKING REF</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.scheduled_date" t-options="{'widget': 'date'}"/></td>
<td class="text-center"><span t-esc="doc.origin or '-'"/></td>
<td class="text-center"><span t-field="doc.picking_type_id"/></td>
<td class="text-center">
<t t-if="'carrier_id' in doc._fields and doc.carrier_id">
<span t-field="doc.carrier_id"/>
</t>
<t t-else="">-</t>
</td>
<td class="text-center">
<t t-if="'carrier_tracking_ref' in doc._fields">
<span t-esc="doc.carrier_tracking_ref or '-'"/>
</t>
<t t-else="">-</t>
</td>
</tr>
</tbody>
</table>
<!-- Products -->
<table class="bordered">
<thead>
<tr>
<th style="width: 10%;">SKU</th>
<th class="text-start" style="width: 34%;">PRODUCT</th>
<th style="width: 10%;">ORDERED</th>
<th style="width: 10%;">DONE</th>
<th style="width: 8%;">UOM</th>
<th style="width: 14%;">LOT / SERIAL</th>
<th style="width: 14%;">NOTES</th>
</tr>
</thead>
<tbody>
<t t-foreach="doc.move_ids_without_package" t-as="move">
<tr>
<td class="text-center"><span t-esc="move.product_id.default_code or ''"/></td>
<td><span t-field="move.product_id"/></td>
<td class="text-center">
<span t-esc="int(move.product_uom_qty) if move.product_uom_qty == int(move.product_uom_qty) else move.product_uom_qty"/>
</td>
<td class="text-center">
<span t-esc="int(move.quantity) if move.quantity == int(move.quantity) else move.quantity"/>
</td>
<td class="text-center"><span t-field="move.product_uom"/></td>
<td>
<t t-foreach="move.move_line_ids" t-as="ml">
<t t-if="ml.lot_id">
<span t-field="ml.lot_id.name"/><br/>
</t>
</t>
</td>
<td/>
</tr>
</t>
</tbody>
</table>
<!-- Notes -->
<t t-if="doc.note">
<div style="margin-top: 10px;">
<strong>Notes:</strong>
<div t-field="doc.note"/>
</div>
</t>
<!-- Sign off -->
<div class="row" style="margin-top: 30px;">
<div class="col-6">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Shipper (Signature / Date)</div>
</div>
</div>
<div class="col-6">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Receiver (Signature / Date)</div>
</div>
</div>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,282 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating — Payment Receipt (Portrait + Landscape).
Binds to account.payment. Shows amount paid, method, reference,
applied invoices, and a "PAID" stamp.
-->
<odoo>
<!-- ============================================================= -->
<!-- PORTRAIT -->
<!-- ============================================================= -->
<template id="report_fp_receipt_portrait">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-call="fusion_plating_reports.fp_portrait_styles"/>
<div class="fp-report">
<div class="page">
<h4 style="text-align: center;">
PAYMENT RECEIPT
</h4>
<div class="text-center" style="text-align: center; margin-bottom: 15px;">
<strong>Receipt #: <span t-field="doc.name"/></strong>
</div>
<!-- Paid stamp -->
<div class="text-center" style="text-align: center; margin: 20px 0;">
<span class="paid-stamp">✓ RECEIVED</span>
</div>
<!-- From / Received by -->
<table class="bordered">
<thead>
<tr>
<th style="width: 50%;">RECEIVED FROM</th>
<th style="width: 50%;">RECEIVED BY</th>
</tr>
</thead>
<tbody>
<tr>
<td style="height: 70px;">
<strong><span t-field="doc.partner_id.name"/></strong><br/>
<div t-field="doc.partner_id"
t-options="{'widget': 'contact', 'fields': ['address', 'phone', 'email'], 'no_marker': True}"/>
</td>
<td style="height: 70px;">
<strong><span t-field="doc.company_id.name"/></strong><br/>
<div t-field="doc.company_id.partner_id"
t-options="{'widget': 'contact', 'fields': ['address', 'phone', 'email'], 'no_marker': True}"/>
</td>
</tr>
</tbody>
</table>
<!-- Payment details -->
<table class="bordered">
<thead>
<tr>
<th colspan="2" style="background-color: #0066a1; color: white;">PAYMENT DETAILS</th>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 40%; background-color: #f5f5f5; font-weight: bold;">Payment Date</td>
<td><span t-field="doc.date" t-options="{'widget': 'date'}"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Payment Method</td>
<td><span t-field="doc.payment_method_line_id"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Journal</td>
<td><span t-field="doc.journal_id"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Reference / Memo</td>
<td><span t-esc="doc.ref or doc.memo or '-'"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Amount</td>
<td style="font-size: 14pt;"><strong>
<span t-field="doc.amount" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</strong></td>
</tr>
</tbody>
</table>
<!-- Applied invoices -->
<t t-if="doc.reconciled_invoice_ids">
<table class="bordered">
<thead>
<tr>
<th colspan="3" style="background-color: #0066a1; color: white;">APPLIED TO INVOICES</th>
</tr>
<tr>
<th style="width: 34%;">INVOICE #</th>
<th style="width: 33%;">DATE</th>
<th style="width: 33%;">AMOUNT</th>
</tr>
</thead>
<tbody>
<t t-foreach="doc.reconciled_invoice_ids" t-as="inv">
<tr>
<td class="text-center"><span t-field="inv.name"/></td>
<td class="text-center"><span t-field="inv.invoice_date"/></td>
<td class="text-end">
<span t-field="inv.amount_total" t-options='{"widget": "monetary", "display_currency": inv.currency_id}'/>
</td>
</tr>
</t>
</tbody>
</table>
</t>
<!-- Thank you -->
<div class="highlight-box" style="margin-top: 20px; text-align: center;">
Thank you for your payment. This receipt confirms that payment has
been received in full for the amount stated above.
</div>
<!-- Sign off -->
<div class="row" style="margin-top: 30px;">
<div class="col-6">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Received by (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>
</t>
</t>
</template>
<!-- ============================================================= -->
<!-- LANDSCAPE -->
<!-- ============================================================= -->
<template id="report_fp_receipt_landscape">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-call="fusion_plating_reports.fp_landscape_styles"/>
<div class="fp-landscape">
<div class="page">
<h2 style="text-align: center;">PAYMENT RECEIPT</h2>
<div class="text-center" style="text-align: center; margin-bottom: 15px;">
<strong>Receipt #: <span t-field="doc.name"/></strong>
</div>
<!-- Paid stamp -->
<div class="text-center" style="text-align: center; margin: 20px 0;">
<span class="paid-stamp">✓ RECEIVED</span>
</div>
<!-- From / Received by -->
<table class="bordered">
<thead>
<tr>
<th style="width: 50%;">RECEIVED FROM</th>
<th style="width: 50%;">RECEIVED BY</th>
</tr>
</thead>
<tbody>
<tr>
<td style="height: 80px; font-size: 12pt;">
<strong><span t-field="doc.partner_id.name"/></strong><br/>
<div t-field="doc.partner_id"
t-options="{'widget': 'contact', 'fields': ['address', 'phone', 'email'], 'no_marker': True}"/>
</td>
<td style="height: 80px; font-size: 12pt;">
<strong><span t-field="doc.company_id.name"/></strong><br/>
<div t-field="doc.company_id.partner_id"
t-options="{'widget': 'contact', 'fields': ['address', 'phone', 'email'], 'no_marker': True}"/>
</td>
</tr>
</tbody>
</table>
<!-- Payment details (wide) -->
<table class="bordered info-table">
<thead>
<tr>
<th>PAYMENT DATE</th>
<th>METHOD</th>
<th>JOURNAL</th>
<th>REFERENCE</th>
<th>CURRENCY</th>
<th>AMOUNT</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.date" t-options="{'widget': 'date'}"/></td>
<td class="text-center"><span t-field="doc.payment_method_line_id"/></td>
<td class="text-center"><span t-field="doc.journal_id"/></td>
<td class="text-center"><span t-esc="doc.ref or doc.memo or '-'"/></td>
<td class="text-center"><span t-field="doc.currency_id.name"/></td>
<td class="text-end" style="font-size: 13pt;"><strong>
<span t-field="doc.amount" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</strong></td>
</tr>
</tbody>
</table>
<!-- Applied invoices -->
<t t-if="doc.reconciled_invoice_ids">
<table class="bordered">
<thead>
<tr>
<th colspan="5" style="background-color: #0066a1; color: white;">APPLIED TO INVOICES</th>
</tr>
<tr>
<th style="width: 20%;">INVOICE #</th>
<th style="width: 20%;">DATE</th>
<th style="width: 20%;">DUE DATE</th>
<th style="width: 20%;">TOTAL</th>
<th style="width: 20%;">PAYMENT STATE</th>
</tr>
</thead>
<tbody>
<t t-foreach="doc.reconciled_invoice_ids" t-as="inv">
<tr>
<td class="text-center"><span t-field="inv.name"/></td>
<td class="text-center"><span t-field="inv.invoice_date"/></td>
<td class="text-center"><span t-field="inv.invoice_date_due"/></td>
<td class="text-end">
<span t-field="inv.amount_total" t-options='{"widget": "monetary", "display_currency": inv.currency_id}'/>
</td>
<td class="text-center">
<span t-if="inv.payment_state == 'paid'" class="status-ok">Paid</span>
<span t-elif="inv.payment_state == 'partial'" class="status-warning">Partial</span>
<span t-else=""><span t-field="inv.payment_state"/></span>
</td>
</tr>
</t>
</tbody>
</table>
</t>
<!-- Thank you -->
<div class="highlight-box" style="margin-top: 20px; text-align: center;">
Thank you for your payment. This receipt confirms that payment has
been received in full for the amount stated above.
</div>
<!-- Sign off -->
<div class="row" style="margin-top: 30px;">
<div class="col-6">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Received by (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>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,464 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
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).
-->
<odoo>
<!-- ============================================================= -->
<!-- PORTRAIT -->
<!-- ============================================================= -->
<template id="report_fp_sale_portrait">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)"/>
<t t-call="fusion_plating_reports.fp_portrait_styles"/>
<div class="fp-report">
<div class="page">
<!-- Title -->
<h4>
<span t-if="doc.state in ['draft','sent']">Quotation </span>
<span t-else="">Sales Order </span>
<span t-field="doc.name"/>
</h4>
<!-- Billing / Shipping -->
<table class="bordered">
<thead>
<tr>
<th style="width: 50%;">BILLING ADDRESS</th>
<th style="width: 50%;">SHIPPING ADDRESS</th>
</tr>
</thead>
<tbody>
<tr>
<td style="height: 70px;">
<div t-field="doc.partner_invoice_id"
t-options="{'widget': 'contact', 'fields': ['name', 'address', 'phone', 'email'], 'no_marker': True}"/>
</td>
<td style="height: 70px;">
<div t-field="doc.partner_shipping_id"
t-options="{'widget': 'contact', 'fields': ['name', 'address', 'phone'], 'no_marker': True}"/>
</td>
</tr>
</tbody>
</table>
<!-- Order info -->
<table class="bordered">
<thead>
<tr>
<th class="info-header" style="width: 20%;">ORDER DATE</th>
<th class="info-header" style="width: 20%;">EXPIRATION</th>
<th class="info-header" style="width: 20%;">SALESPERSON</th>
<th class="info-header" style="width: 20%;">CUSTOMER PO #</th>
<th class="info-header" style="width: 20%;">RUSH</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.date_order" t-options="{'widget': 'date'}"/></td>
<td class="text-center"><span t-field="doc.validity_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-if="doc.x_fc_rush_order" class="status-warning">RUSH</span>
<span t-else="">Standard</span>
</td>
</tr>
</tbody>
</table>
<!-- Plating info -->
<t t-if="doc.x_fc_part_catalog_id or doc.x_fc_coating_config_id or doc.x_fc_delivery_method">
<table class="bordered">
<thead>
<tr>
<th class="info-header" style="width: 34%;">PART</th>
<th class="info-header" style="width: 33%;">COATING CONFIG</th>
<th class="info-header" style="width: 33%;">DELIVERY METHOD</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.x_fc_part_catalog_id"/></td>
<td class="text-center"><span t-field="doc.x_fc_coating_config_id"/></td>
<td class="text-center">
<t t-set="dm" t-value="dict(doc._fields['x_fc_delivery_method'].selection).get(doc.x_fc_delivery_method, '-')"/>
<span t-esc="dm"/>
</td>
</tr>
</tbody>
</table>
</t>
<!-- Order lines -->
<table class="bordered">
<thead>
<tr>
<th style="width: 10%;">SKU</th>
<th class="text-start" style="width: 40%;">DESCRIPTION</th>
<th style="width: 8%;">QTY</th>
<th style="width: 8%;">UOM</th>
<th style="width: 12%;">UNIT PRICE</th>
<th style="width: 10%;">TAXES</th>
<th style="width: 12%;">AMOUNT</th>
</tr>
</thead>
<tbody>
<t t-foreach="doc.order_line" t-as="line">
<t t-if="line.display_type == 'line_section'">
<tr class="section-row"><td colspan="7"><strong t-field="line.name"/></td></tr>
</t>
<t t-elif="line.display_type == 'line_note'">
<tr class="note-row"><td colspan="7"><span t-field="line.name"/></td></tr>
</t>
<t t-elif="not line.display_type">
<tr>
<td class="text-center"><span t-esc="line.product_id.default_code or ''"/></td>
<td>
<t t-set="clean_name" t-value="line.name"/>
<t t-if="line.name and '] ' in line.name">
<t t-set="clean_name" t-value="line.name.split('] ', 1)[1]"/>
</t>
<span t-esc="clean_name"/>
</td>
<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-end">
<span t-field="line.price_unit" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
<td class="text-center">
<t t-esc="', '.join([(tax.invoice_label or tax.name) for tax in line.tax_ids]) or '-'"/>
</td>
<td class="text-end">
<span t-field="line.price_subtotal" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
</t>
</t>
</tbody>
</table>
<!-- Terms + Totals -->
<div class="row" style="margin-top: 15px;">
<div class="col-6">
<t t-if="doc.payment_term_id.note">
<strong>Payment Terms:</strong><br/>
<span t-field="doc.payment_term_id.note"/>
</t>
<t t-if="doc.x_fc_invoice_strategy">
<div style="margin-top: 10px;">
<strong>Invoice Strategy: </strong>
<t t-set="inv_strat" t-value="dict(doc._fields['x_fc_invoice_strategy'].selection).get(doc.x_fc_invoice_strategy, '-')"/>
<span t-esc="inv_strat"/>
<t t-if="doc.x_fc_invoice_strategy == 'deposit' and doc.x_fc_deposit_percent">
(<span t-esc="doc.x_fc_deposit_percent"/>%)
</t>
</div>
</t>
</div>
<div class="col-6" style="text-align: right;">
<table class="totals-table" style="width: auto; margin-left: auto;">
<tr>
<td style="min-width: 150px;">Subtotal</td>
<td class="text-end" style="min-width: 110px;">
<span t-field="doc.amount_untaxed" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
<tr>
<td>Taxes</td>
<td class="text-end">
<span t-field="doc.amount_tax" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
<tr style="background-color: #eaf2f8;">
<td><strong>Grand Total</strong></td>
<td class="text-end"><strong>
<span t-field="doc.amount_total" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</strong></td>
</tr>
</table>
</div>
</div>
<!-- Terms and Conditions -->
<t t-if="doc.note">
<div style="margin-top: 15px;">
<strong>Terms and Conditions:</strong>
<div t-field="doc.note"/>
</div>
</t>
<!-- Signature -->
<div class="row" style="margin-top: 25px;">
<div class="col-6">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Customer Acceptance (Signature / Date)</div>
</div>
</div>
<div class="col-6">
<div class="sig-box">
<t t-if="doc.signature">
<img t-att-src="image_data_uri(doc.signature)" style="max-height: 3cm; max-width: 8cm;"/><br/>
<span t-field="doc.signed_by"/>
</t>
<t t-else="">
<div class="sig-line"/>
</t>
<div class="small-muted">Authorized Representative</div>
</div>
</div>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
<!-- ============================================================= -->
<!-- LANDSCAPE -->
<!-- ============================================================= -->
<template id="report_fp_sale_landscape">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)"/>
<t t-call="fusion_plating_reports.fp_landscape_styles"/>
<div class="fp-landscape">
<div class="page">
<!-- Title -->
<h2 style="text-align: left;">
<span t-if="doc.state in ['draft','sent']">Quotation </span>
<span t-else="">Sales Order </span>
<span t-field="doc.name"/>
</h2>
<!-- Billing / Shipping -->
<table class="bordered">
<thead>
<tr>
<th style="width: 50%;">BILLING ADDRESS</th>
<th style="width: 50%;">SHIPPING ADDRESS</th>
</tr>
</thead>
<tbody>
<tr>
<td style="height: 70px; font-size: 12pt;">
<div t-field="doc.partner_invoice_id"
t-options="{'widget': 'contact', 'fields': ['name', 'address', 'phone', 'email'], 'no_marker': True}"/>
</td>
<td style="height: 70px; font-size: 12pt;">
<div t-field="doc.partner_shipping_id"
t-options="{'widget': 'contact', 'fields': ['name', 'address', 'phone'], 'no_marker': True}"/>
</td>
</tr>
</tbody>
</table>
<!-- Order info (wide) -->
<table class="bordered info-table">
<thead>
<tr>
<th>ORDER DATE</th>
<th>EXPIRATION</th>
<th>SALESPERSON</th>
<th>CUSTOMER PO #</th>
<th>DELIVERY METHOD</th>
<th>RUSH</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.date_order" t-options="{'widget': 'date'}"/></td>
<td class="text-center"><span t-field="doc.validity_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">
<t t-set="dm" t-value="dict(doc._fields['x_fc_delivery_method'].selection).get(doc.x_fc_delivery_method, '-')"/>
<span t-esc="dm"/>
</td>
<td class="text-center">
<span t-if="doc.x_fc_rush_order" class="status-warning">RUSH</span>
<span t-else="">Standard</span>
</td>
</tr>
</tbody>
</table>
<!-- Plating details -->
<t t-if="doc.x_fc_part_catalog_id or doc.x_fc_coating_config_id">
<table class="bordered info-table">
<thead>
<tr>
<th>PART CATALOG</th>
<th>COATING CONFIGURATION</th>
<th>INVOICE STRATEGY</th>
<th>DEPOSIT %</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.x_fc_part_catalog_id"/></td>
<td class="text-center"><span t-field="doc.x_fc_coating_config_id"/></td>
<td class="text-center">
<t t-set="inv_strat" t-value="dict(doc._fields['x_fc_invoice_strategy'].selection).get(doc.x_fc_invoice_strategy, '-')"/>
<span t-esc="inv_strat"/>
</td>
<td class="text-center">
<t t-if="doc.x_fc_deposit_percent">
<span t-esc="doc.x_fc_deposit_percent"/>%
</t>
<t t-else="">-</t>
</td>
</tr>
</tbody>
</table>
</t>
<!-- Order lines -->
<table class="bordered">
<thead>
<tr>
<th style="width: 10%;">SKU</th>
<th class="text-start" style="width: 32%;">DESCRIPTION</th>
<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 style="width: 10%;">TAXES</th>
<th style="width: 10%;">AMOUNT</th>
</tr>
</thead>
<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>
</t>
<t t-elif="line.display_type == 'line_note'">
<tr class="note-row"><td colspan="8"><span t-field="line.name"/></td></tr>
</t>
<t t-elif="not line.display_type">
<tr>
<td class="text-center"><span t-esc="line.product_id.default_code or ''"/></td>
<td>
<t t-set="clean_name" t-value="line.name"/>
<t t-if="line.name and '] ' in line.name">
<t t-set="clean_name" t-value="line.name.split('] ', 1)[1]"/>
</t>
<span t-esc="clean_name"/>
</td>
<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-end">
<span t-field="line.price_unit" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
<td class="text-center">
<t t-if="line.discount"><span t-esc="line.discount"/>%</t>
<t t-else="">-</t>
</td>
<td class="text-center">
<t t-esc="', '.join([(tax.invoice_label or tax.name) for tax in line.tax_ids]) or '-'"/>
</td>
<td class="text-end">
<span t-field="line.price_subtotal" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
</t>
</t>
</tbody>
</table>
<!-- Terms + Totals -->
<div class="row" style="margin-top: 15px;">
<div class="col-7">
<t t-if="doc.payment_term_id.note">
<strong>Payment Terms:</strong><br/>
<span t-field="doc.payment_term_id.note"/>
</t>
<t t-if="doc.x_fc_invoice_strategy">
<div style="margin-top: 10px;">
<strong>Invoice Strategy: </strong>
<t t-set="inv_strat" t-value="dict(doc._fields['x_fc_invoice_strategy'].selection).get(doc.x_fc_invoice_strategy, '-')"/>
<span t-esc="inv_strat"/>
<t t-if="doc.x_fc_invoice_strategy == 'deposit' and doc.x_fc_deposit_percent">
(<span t-esc="doc.x_fc_deposit_percent"/>%)
</t>
</div>
</t>
</div>
<div class="col-5" style="text-align: right;">
<table class="totals-table" style="width: auto; margin-left: auto;">
<tr>
<td style="min-width: 200px;">Subtotal</td>
<td class="text-end" style="min-width: 150px;">
<span t-field="doc.amount_untaxed" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
<tr>
<td>Taxes</td>
<td class="text-end">
<span t-field="doc.amount_tax" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
<tr style="background-color: #eaf2f8;">
<td><strong>Grand Total</strong></td>
<td class="text-end"><strong>
<span t-field="doc.amount_total" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</strong></td>
</tr>
</table>
</div>
</div>
<!-- Terms and Conditions -->
<t t-if="doc.note">
<div style="margin-top: 15px;">
<strong>Terms and Conditions:</strong>
<div t-field="doc.note"/>
</div>
</t>
<!-- Signature block -->
<div class="row" style="margin-top: 25px;">
<div class="col-6">
<div class="sig-box">
<div class="sig-line"/>
<div class="small-muted">Customer Acceptance (Signature / Date)</div>
</div>
</div>
<div class="col-6">
<div class="sig-box">
<t t-if="doc.signature">
<img t-att-src="image_data_uri(doc.signature)" style="max-height: 3cm; max-width: 8cm;"/><br/>
<span t-field="doc.signed_by"/>
</t>
<t t-else="">
<div class="sig-line"/>
</t>
<div class="small-muted">Authorized Representative</div>
</div>
</div>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,380 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Fusion Plating — Work Order traveller (Portrait + Landscape).
Printed shop-floor sheet with step info, bath/tank, chemistry targets,
and sign-off rows.
-->
<odoo>
<!-- ============================================================= -->
<!-- PORTRAIT -->
<!-- ============================================================= -->
<template id="report_fp_work_order_portrait">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-call="fusion_plating_reports.fp_portrait_styles"/>
<div class="fp-report">
<div class="page">
<h4>
Work Order Traveller —
<span t-field="doc.name"/>
</h4>
<!-- Header info -->
<table class="bordered">
<thead>
<tr>
<th class="info-header">MO #</th>
<th class="info-header">STEP</th>
<th class="info-header">WORK CENTRE</th>
<th class="info-header">PRIORITY</th>
<th class="info-header">STATE</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.production_id.name"/></td>
<td class="text-center"><span t-esc="doc.x_fc_step_display or '-'"/></td>
<td class="text-center"><span t-field="doc.workcenter_id"/></td>
<td class="text-center">
<t t-set="prio" t-value="dict(doc._fields['x_fc_priority'].selection).get(doc.x_fc_priority, 'Normal')"/>
<span t-esc="prio"/>
</td>
<td class="text-center"><span t-field="doc.state"/></td>
</tr>
</tbody>
</table>
<!-- Customer / Part -->
<table class="bordered">
<thead>
<tr>
<th class="info-header" style="width: 50%;">CUSTOMER</th>
<th class="info-header" style="width: 50%;">PRODUCT</th>
</tr>
</thead>
<tbody>
<tr>
<td><span t-field="doc.x_fc_customer_id"/></td>
<td><span t-field="doc.product_id"/></td>
</tr>
</tbody>
</table>
<!-- Sale / Portal links -->
<table class="bordered">
<thead>
<tr>
<th class="info-header" style="width: 33%;">SALE ORDER</th>
<th class="info-header" style="width: 34%;">PORTAL JOB</th>
<th class="info-header" style="width: 33%;">QTY TO PRODUCE</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-esc="doc.x_fc_sale_order_name or '-'"/></td>
<td class="text-center"><span t-field="doc.x_fc_portal_job_id"/></td>
<td class="text-center"><span t-field="doc.qty_production"/></td>
</tr>
</tbody>
</table>
<!-- Process parameters -->
<table class="bordered">
<thead>
<tr><th colspan="2" style="background-color: #0066a1; color: white;">PROCESS PARAMETERS</th></tr>
</thead>
<tbody>
<tr>
<td style="width: 35%; background-color: #f5f5f5; font-weight: bold;">Bath</td>
<td><span t-field="doc.x_fc_bath_id"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Tank</td>
<td><span t-field="doc.x_fc_tank_id"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Rack / Fixture Ref</td>
<td><span t-esc="doc.x_fc_rack_ref or '-'"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Target Thickness</td>
<td>
<t t-if="doc.x_fc_thickness_target">
<span t-esc="doc.x_fc_thickness_target"/>
<span t-esc="dict(doc._fields['x_fc_thickness_uom'].selection).get(doc.x_fc_thickness_uom, '')"/>
</t>
<t t-else="">-</t>
</td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Dwell Time</td>
<td>
<t t-if="doc.x_fc_dwell_time_minutes">
<span t-esc="doc.x_fc_dwell_time_minutes"/> min
</t>
<t t-else="">-</t>
</td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Facility</td>
<td><span t-field="doc.x_fc_facility_id"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Expected Duration</td>
<td><span t-esc="doc.duration_expected"/> min</td>
</tr>
</tbody>
</table>
<!-- Operation instructions -->
<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>
</thead>
<tbody>
<tr><td><div t-field="doc.operation_id.note"/></td></tr>
</tbody>
</table>
</t>
<!-- Bath chemistry targets snapshot -->
<t t-if="doc.x_fc_bath_id and doc.x_fc_bath_id.target_line_ids">
<table class="bordered">
<thead>
<tr>
<th>PARAMETER</th>
<th>MIN</th>
<th>MAX</th>
<th>UOM</th>
</tr>
</thead>
<tbody>
<t t-foreach="doc.x_fc_bath_id.target_line_ids" t-as="p">
<tr>
<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>
</tr>
</t>
</tbody>
</table>
</t>
<!-- Sign-off -->
<div style="margin-top: 20px;">
<table class="bordered">
<thead>
<tr>
<th colspan="3" style="background-color: #0066a1; color: white;">OPERATOR SIGN-OFF</th>
</tr>
<tr>
<th style="width: 40%;">OPERATOR</th>
<th style="width: 30%;">DATE / TIME</th>
<th style="width: 30%;">INITIALS</th>
</tr>
</thead>
<tbody>
<tr><td class="sig-line"/><td class="sig-line"/><td class="sig-line"/></tr>
<tr><td class="sig-line"/><td class="sig-line"/><td class="sig-line"/></tr>
</tbody>
</table>
</div>
<!-- Notes -->
<div style="margin-top: 15px;">
<strong>Notes / Observations:</strong>
<div style="border: 1px solid #000; min-height: 80px; margin-top: 5px;"/>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
<!-- ============================================================= -->
<!-- LANDSCAPE -->
<!-- ============================================================= -->
<template id="report_fp_work_order_landscape">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<t t-call="fusion_plating_reports.fp_landscape_styles"/>
<div class="fp-landscape">
<div class="page">
<h2 style="text-align: left;">
Work Order Traveller —
<span t-field="doc.name"/>
</h2>
<!-- Header info (wide) -->
<table class="bordered info-table">
<thead>
<tr>
<th>MO #</th>
<th>STEP</th>
<th>WORK CENTRE</th>
<th>FACILITY</th>
<th>PRIORITY</th>
<th>STATE</th>
<th>EXPECTED DURATION</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center"><span t-field="doc.production_id.name"/></td>
<td class="text-center"><span t-esc="doc.x_fc_step_display or '-'"/></td>
<td class="text-center"><span t-field="doc.workcenter_id"/></td>
<td class="text-center"><span t-field="doc.x_fc_facility_id"/></td>
<td class="text-center">
<t t-set="prio" t-value="dict(doc._fields['x_fc_priority'].selection).get(doc.x_fc_priority, 'Normal')"/>
<span t-esc="prio"/>
</td>
<td class="text-center"><span t-field="doc.state"/></td>
<td class="text-center"><span t-esc="doc.duration_expected"/> min</td>
</tr>
</tbody>
</table>
<!-- Customer / Links -->
<table class="bordered info-table">
<thead>
<tr>
<th>CUSTOMER</th>
<th>PRODUCT</th>
<th>SALE ORDER</th>
<th>PORTAL JOB</th>
<th>QTY</th>
</tr>
</thead>
<tbody>
<tr>
<td><span t-field="doc.x_fc_customer_id"/></td>
<td><span t-field="doc.product_id"/></td>
<td class="text-center"><span t-esc="doc.x_fc_sale_order_name or '-'"/></td>
<td class="text-center"><span t-field="doc.x_fc_portal_job_id"/></td>
<td class="text-center"><span t-field="doc.qty_production"/></td>
</tr>
</tbody>
</table>
<!-- Process Parameters / Chemistry Targets side-by-side -->
<div class="row">
<div class="col-6">
<table class="bordered">
<thead>
<tr><th colspan="2" style="background-color: #0066a1; color: white;">PROCESS PARAMETERS</th></tr>
</thead>
<tbody>
<tr>
<td style="width: 40%; background-color: #f5f5f5; font-weight: bold;">Bath</td>
<td><span t-field="doc.x_fc_bath_id"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Tank</td>
<td><span t-field="doc.x_fc_tank_id"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Rack / Fixture</td>
<td><span t-esc="doc.x_fc_rack_ref or '-'"/></td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Target Thickness</td>
<td>
<t t-if="doc.x_fc_thickness_target">
<span t-esc="doc.x_fc_thickness_target"/>
<span t-esc="dict(doc._fields['x_fc_thickness_uom'].selection).get(doc.x_fc_thickness_uom, '')"/>
</t>
<t t-else="">-</t>
</td>
</tr>
<tr>
<td style="background-color: #f5f5f5; font-weight: bold;">Dwell Time</td>
<td>
<t t-if="doc.x_fc_dwell_time_minutes">
<span t-esc="doc.x_fc_dwell_time_minutes"/> min
</t>
<t t-else="">-</t>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-6">
<t t-if="doc.x_fc_bath_id and doc.x_fc_bath_id.target_line_ids">
<table class="bordered">
<thead>
<tr>
<th colspan="4" style="background-color: #0066a1; color: white;">CHEMISTRY TARGETS</th>
</tr>
<tr>
<th>PARAM</th>
<th>MIN</th>
<th>MAX</th>
<th>UOM</th>
</tr>
</thead>
<tbody>
<t t-foreach="doc.x_fc_bath_id.target_line_ids" t-as="p">
<tr>
<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>
</tr>
</t>
</tbody>
</table>
</t>
</div>
</div>
<!-- Operation instructions -->
<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>
</thead>
<tbody>
<tr><td><div t-field="doc.operation_id.note"/></td></tr>
</tbody>
</table>
</t>
<!-- Sign-off -->
<table class="bordered" style="margin-top: 15px;">
<thead>
<tr>
<th colspan="5" style="background-color: #0066a1; color: white;">OPERATOR SIGN-OFF</th>
</tr>
<tr>
<th style="width: 25%;">OPERATOR</th>
<th style="width: 15%;">DATE</th>
<th style="width: 15%;">TIME IN</th>
<th style="width: 15%;">TIME OUT</th>
<th style="width: 30%;">INITIALS / NOTES</th>
</tr>
</thead>
<tbody>
<tr><td class="sig-line"/><td class="sig-line"/><td class="sig-line"/><td class="sig-line"/><td class="sig-line"/></tr>
<tr><td class="sig-line"/><td class="sig-line"/><td class="sig-line"/><td class="sig-line"/><td class="sig-line"/></tr>
</tbody>
</table>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>