changes
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
{
|
||||
'name': 'Fusion Plating — Reports',
|
||||
'version': '19.0.11.26.16',
|
||||
'version': '19.0.11.26.30',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
|
||||
'depends': [
|
||||
|
||||
@@ -90,7 +90,12 @@
|
||||
<t t-set="_desc"
|
||||
t-value="line.fp_customer_description() if _has_helper else line.name"/>
|
||||
<span t-esc="_desc" style="white-space: pre-line;"/>
|
||||
<t t-if="'x_fc_serial_id' in line._fields and line.x_fc_serial_id">
|
||||
<!-- Serial line is suppressed when the calling template sets
|
||||
`fp_no_serial_in_desc=True` in `line.env.context`. SO
|
||||
portrait does this because it shows the serial in its own
|
||||
column in the part-number cell — otherwise it'd be
|
||||
duplicated. Invoice / packing slip still display it here. -->
|
||||
<t t-if="'x_fc_serial_id' in line._fields and line.x_fc_serial_id and not line.env.context.get('fp_no_serial_in_desc')">
|
||||
<br/>
|
||||
<small>Serial: <span t-esc="line.x_fc_serial_id.name"/></small>
|
||||
</t>
|
||||
|
||||
@@ -73,6 +73,26 @@
|
||||
<field name="dpi">90</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Compact A4 Landscape for customer-facing landscape reports. -->
|
||||
<!-- Same shape as `paperformat_fp_a4_portrait` (margin_top=8, -->
|
||||
<!-- header_spacing=0) just rotated. Bind alongside the portrait -->
|
||||
<!-- compact for any report that has a landscape variant. -->
|
||||
<!-- ============================================================= -->
|
||||
<record id="paperformat_fp_a4_landscape_compact" model="report.paperformat">
|
||||
<field name="name">Fusion Plating A4 Landscape (Compact)</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="format">A4</field>
|
||||
<field name="orientation">Landscape</field>
|
||||
<field name="margin_top">8</field>
|
||||
<field name="margin_bottom">15</field>
|
||||
<field name="margin_left">10</field>
|
||||
<field name="margin_right">10</field>
|
||||
<field name="header_line" eval="False"/>
|
||||
<field name="header_spacing">0</field>
|
||||
<field name="dpi">90</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- 1. Certificate of Conformance (Portal Job) — Landscape -->
|
||||
<!-- ============================================================= -->
|
||||
@@ -451,6 +471,11 @@
|
||||
<field name="binding_model_id" ref="account.model_account_move"/>
|
||||
<field name="binding_type">report</field>
|
||||
<field name="is_invoice_report" eval="True"/>
|
||||
<!-- 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". -->
|
||||
<field name="paperformat_id" ref="paperformat_fp_a4_portrait"/>
|
||||
</record>
|
||||
|
||||
<record id="action_report_fp_invoice_landscape" model="ir.actions.report">
|
||||
@@ -462,7 +487,10 @@
|
||||
<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"/>
|
||||
<!-- Compact landscape paperformat (same shape as the portrait
|
||||
compact, just rotated) so the inline header lands at the
|
||||
top with no auto-margin gap. -->
|
||||
<field name="paperformat_id" ref="paperformat_fp_a4_landscape_compact"/>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================= -->
|
||||
|
||||
@@ -14,26 +14,92 @@
|
||||
<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">
|
||||
<t t-set="form_code" t-value="'FRM-007'"/>
|
||||
<t t-call="fusion_plating_reports.fp_external_layout_clean">
|
||||
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)"/>
|
||||
<t t-set="company" t-value="doc.company_id or env.company"/>
|
||||
<t t-call="fusion_plating_reports.fp_portrait_styles"/>
|
||||
<t t-call="fusion_plating_reports.fp_sale_bilingual_styles"/>
|
||||
|
||||
<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>
|
||||
<!-- Compute helpers -->
|
||||
<t t-set="title_en" t-value="
|
||||
'Credit Note' if doc.move_type == 'out_refund'
|
||||
else 'Vendor Bill' if doc.move_type == 'in_invoice'
|
||||
else 'Draft Invoice' if (doc.move_type == 'out_invoice' and doc.state == 'draft')
|
||||
else 'Invoice'"/>
|
||||
<t t-set="title_fr" t-value="
|
||||
'Note de crédit' if doc.move_type == 'out_refund'
|
||||
else 'Facture fournisseur' if doc.move_type == 'in_invoice'
|
||||
else 'Facture brouillon' if (doc.move_type == 'out_invoice' and doc.state == 'draft')
|
||||
else 'Facture'"/>
|
||||
<t t-set="barcode_uri" t-value="doc.env['ir.actions.report'].sudo().barcode_data_uri('Code128', doc.name, 600, 100) if doc.name and doc.name != '/' else False"/>
|
||||
<!-- Pull FP-specific fields from the source SO when invoice_origin
|
||||
names one. Falls back to an empty recordset on manual
|
||||
invoices so the t-if guards in the markup stay clean. -->
|
||||
<t t-set="source_so" t-value="doc.env['sale.order'].search([('name', '=', doc.invoice_origin)], limit=1) if doc.invoice_origin else doc.env['sale.order'].browse()"/>
|
||||
<t t-set="logo_uri" t-value="('data:image/png;base64,%s' % company.logo.decode()) if company.logo else False"/>
|
||||
<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="po_number" t-value="(source_so.x_fc_po_number if source_so else False) or doc.invoice_origin or ''"/>
|
||||
<t t-set="spec_label" t-value="(source_so.x_fc_customer_spec_id.display_name or source_so.x_fc_customer_spec_id.name) if source_so and source_so.x_fc_customer_spec_id else ''"/>
|
||||
<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-report fp-sale">
|
||||
<!-- Inline 3-column header: logo+address LEFT,
|
||||
NADCAP MIDDLE, title+barcode RIGHT.
|
||||
Mirrors SO confirmation exactly. -->
|
||||
<div class="fp-sale-header-row">
|
||||
<div class="fp-sale-header-left">
|
||||
<t t-if="logo_uri">
|
||||
<img t-att-src="logo_uri" class="fp-sale-logo" alt="Logo"/>
|
||||
</t>
|
||||
<div class="fp-sale-company-addr">
|
||||
<div>
|
||||
<t t-if="company.partner_id.street"><span t-esc="company.partner_id.street"/></t>
|
||||
<t t-if="company.partner_id.city"> | <span t-esc="company.partner_id.city"/></t>
|
||||
<t t-if="company.partner_id.state_id"> | <span t-esc="company.partner_id.state_id.code or company.partner_id.state_id.name"/></t>
|
||||
<t t-if="company.partner_id.zip"> | <span t-esc="company.partner_id.zip"/></t>
|
||||
</div>
|
||||
<div t-if="company.phone or company_fax">
|
||||
<t t-if="company.phone">Tel: <span t-esc="company.phone"/></t>
|
||||
<t t-if="company.phone and company_fax">   </t>
|
||||
<t t-if="company_fax">Fax: <span t-esc="company_fax"/></t>
|
||||
</div>
|
||||
<div t-if="company.partner_id.website">
|
||||
<a t-att-href="company.partner_id.website"><span t-esc="company.partner_id.website"/></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fp-sale-header-mid">
|
||||
<t t-if="company.x_fc_nadcap_active and company.x_fc_nadcap_logo">
|
||||
<img class="fp-nadcap-logo"
|
||||
t-att-src="'data:image/png;base64,%s' % company.x_fc_nadcap_logo.decode()"
|
||||
alt="Nadcap Accredited"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="fp-sale-header-right">
|
||||
<span class="fp-sale-title-en"><t t-esc="title_en"/></span>
|
||||
<span class="fp-sale-title-fr"><t t-esc="title_fr"/></span>
|
||||
<t t-if="barcode_uri">
|
||||
<div class="fp-bc-wrap" style="margin-top: 4px;">
|
||||
<img t-att-src="barcode_uri" alt="Invoice Barcode"/>
|
||||
<div class="fp-bc-label"><span t-field="doc.name"/></div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<!-- Billing / Shipping -->
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 50%;">BILLING ADDRESS</th>
|
||||
<th style="width: 50%;">DELIVERY ADDRESS</th>
|
||||
<th style="width: 50%;">
|
||||
<span class="fp-bl-en">Billing Address</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Adresse de facturation</span>
|
||||
</th>
|
||||
<th style="width: 50%;">
|
||||
<span class="fp-bl-en">Delivery Address</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Adresse de livraison</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -56,53 +122,131 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Invoice info -->
|
||||
<!-- Row 1: Invoice Date | Due Date | Sales Rep | Customer PO # | Payment Ref -->
|
||||
<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>
|
||||
<th class="info-header" style="width: 20%;">
|
||||
<span class="fp-bl-en-stk">Invoice Date</span>
|
||||
<span class="fp-bl-fr-stk">Date de facture</span>
|
||||
</th>
|
||||
<th class="info-header" style="width: 20%;">
|
||||
<span class="fp-bl-en-stk">Due Date</span>
|
||||
<span class="fp-bl-fr-stk">Date d'échéance</span>
|
||||
</th>
|
||||
<th class="info-header" style="width: 20%;">
|
||||
<span class="fp-bl-en-stk">Sales Rep</span>
|
||||
<span class="fp-bl-fr-stk">Vendeur</span>
|
||||
</th>
|
||||
<th class="info-header" style="width: 20%;">
|
||||
<span class="fp-bl-en-stk">Customer PO #</span>
|
||||
<span class="fp-bl-fr-stk">N° de B/C client</span>
|
||||
</th>
|
||||
<th class="info-header" style="width: 20%;">
|
||||
<span class="fp-bl-en-stk">Payment Ref</span>
|
||||
<span class="fp-bl-fr-stk">Réf. paiement</span>
|
||||
</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="po_number or '—'"/></td>
|
||||
<td class="text-center"><span t-esc="doc.payment_reference or '—'"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Lines -->
|
||||
<!-- Row 2: Customer Job # | Specification | Delivery Method (pulled from source SO; hidden on manual invoices). -->
|
||||
<t t-if="source_so and (source_so.x_fc_customer_job_number or spec_label or delivery_method_label)">
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="info-header" style="width: 34%;">
|
||||
<span class="fp-bl-en">Customer Job #</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">N° de travail client</span>
|
||||
</th>
|
||||
<th class="info-header" style="width: 33%;">
|
||||
<span class="fp-bl-en">Specification</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Spécification</span>
|
||||
</th>
|
||||
<th class="info-header" style="width: 33%;">
|
||||
<span class="fp-bl-en">Delivery Method</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Méthode de livraison</span>
|
||||
</th>
|
||||
</tr>
|
||||
</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="spec_label or '—'"/></td>
|
||||
<td class="text-center"><span t-esc="delivery_method_label or '—'"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</t>
|
||||
|
||||
<!-- Lines (taxes column dropped; summarized in totals) -->
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-start" style="width: 20%;">PART NUMBER</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: 8%;">TAXES</th>
|
||||
<th style="width: 12%;">AMOUNT</th>
|
||||
<th class="text-start" style="width: 24%;">
|
||||
<span class="fp-bl-en">Part Number</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">N° de pièce</span>
|
||||
</th>
|
||||
<th class="text-start" style="width: 38%;">
|
||||
<span class="fp-bl-en">Description</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Description</span>
|
||||
</th>
|
||||
<th style="width: 8%;">
|
||||
<span class="fp-bl-en-stk">Qty</span>
|
||||
<span class="fp-bl-fr-stk">Qté</span>
|
||||
</th>
|
||||
<th style="width: 8%;">
|
||||
<span class="fp-bl-en-stk">UOM</span>
|
||||
<span class="fp-bl-fr-stk">UDM</span>
|
||||
</th>
|
||||
<th style="width: 11%;">
|
||||
<span class="fp-bl-en-stk">Unit Price</span>
|
||||
<span class="fp-bl-fr-stk">Prix unitaire</span>
|
||||
</th>
|
||||
<th style="width: 11%;">
|
||||
<span class="fp-bl-en-stk">Amount</span>
|
||||
<span class="fp-bl-fr-stk">Montant</span>
|
||||
</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>
|
||||
<tr class="section-row"><td colspan="6"><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>
|
||||
<tr class="note-row"><td colspan="6"><span t-field="line.name"/></td></tr>
|
||||
</t>
|
||||
<t t-elif="not line.display_type or line.display_type == 'product'">
|
||||
<tr>
|
||||
<td>
|
||||
<t t-call="fusion_plating_reports.customer_line_part_number"/>
|
||||
<!-- Three stacked lines: Part #, Name, S/N -->
|
||||
<div>
|
||||
<strong>Part #:</strong>
|
||||
<t t-call="fusion_plating_reports.customer_line_part_number"/>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Name:</strong>
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
<strong>S/N:</strong>
|
||||
<t t-if="'x_fc_serial_id' in line._fields and line.x_fc_serial_id">
|
||||
<span t-esc="line.x_fc_serial_id.name"/>
|
||||
</t>
|
||||
<t t-else="">—</t>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Suppress duplicate serial in the description column -->
|
||||
<t t-set="line" t-value="line.with_context(fp_no_serial_in_desc=True)"/>
|
||||
<t t-call="fusion_plating_reports.customer_line_description"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@@ -112,9 +256,6 @@
|
||||
<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>
|
||||
@@ -127,40 +268,47 @@
|
||||
<!-- 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 t-if="doc.invoice_payment_term_id">
|
||||
<strong>Payment Terms / Modalités de paiement:</strong><br/>
|
||||
<t t-if="doc.invoice_payment_term_id.note">
|
||||
<span t-field="doc.invoice_payment_term_id.note"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span t-field="doc.invoice_payment_term_id.name"/>
|
||||
</t>
|
||||
</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 style="min-width: 150px;">
|
||||
<span class="fp-bl-en">Subtotal</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Sous-total</span>
|
||||
</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>
|
||||
<span class="fp-bl-en">Taxes</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Taxes</span>
|
||||
</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: #c1c1c1;">
|
||||
<td><strong>Grand Total</strong></td>
|
||||
<td>
|
||||
<span class="fp-bl-en">Grand Total</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Total général</span>
|
||||
</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>
|
||||
<strong><span class="fp-bl-en">Amount Due</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Montant dû</span></strong>
|
||||
</td>
|
||||
<td class="text-end"><strong>
|
||||
<span t-field="doc.amount_residual" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
|
||||
</strong></td>
|
||||
@@ -173,14 +321,14 @@
|
||||
<!-- Paid stamp -->
|
||||
<t t-if="doc.payment_state in ('paid', 'in_payment')">
|
||||
<div style="margin-top: 15px; text-align: center;">
|
||||
<span class="paid-stamp">PAID</span>
|
||||
<span class="paid-stamp">PAID / PAYÉ</span>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Notes -->
|
||||
<t t-if="doc.narration">
|
||||
<div style="margin-top: 15px;">
|
||||
<strong>Notes:</strong>
|
||||
<strong>Notes / Remarques:</strong>
|
||||
<div t-field="doc.narration"/>
|
||||
</div>
|
||||
</t>
|
||||
@@ -198,26 +346,87 @@
|
||||
<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">
|
||||
<t t-set="form_code" t-value="'FRM-007'"/>
|
||||
<t t-call="fusion_plating_reports.fp_external_layout_clean">
|
||||
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)"/>
|
||||
<t t-set="company" t-value="doc.company_id or env.company"/>
|
||||
<t t-call="fusion_plating_reports.fp_landscape_styles"/>
|
||||
<t t-call="fusion_plating_reports.fp_sale_bilingual_styles"/>
|
||||
|
||||
<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>
|
||||
<!-- Same compute helpers as portrait -->
|
||||
<t t-set="title_en" t-value="
|
||||
'Credit Note' if doc.move_type == 'out_refund'
|
||||
else 'Vendor Bill' if doc.move_type == 'in_invoice'
|
||||
else 'Draft Invoice' if (doc.move_type == 'out_invoice' and doc.state == 'draft')
|
||||
else 'Invoice'"/>
|
||||
<t t-set="title_fr" t-value="
|
||||
'Note de crédit' if doc.move_type == 'out_refund'
|
||||
else 'Facture fournisseur' if doc.move_type == 'in_invoice'
|
||||
else 'Facture brouillon' if (doc.move_type == 'out_invoice' and doc.state == 'draft')
|
||||
else 'Facture'"/>
|
||||
<t t-set="barcode_uri" t-value="doc.env['ir.actions.report'].sudo().barcode_data_uri('Code128', doc.name, 600, 100) if doc.name and doc.name != '/' else False"/>
|
||||
<t t-set="source_so" t-value="doc.env['sale.order'].search([('name', '=', doc.invoice_origin)], limit=1) if doc.invoice_origin else doc.env['sale.order'].browse()"/>
|
||||
<t t-set="logo_uri" t-value="('data:image/png;base64,%s' % company.logo.decode()) if company.logo else False"/>
|
||||
<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="po_number" t-value="(source_so.x_fc_po_number if source_so else False) or doc.invoice_origin or ''"/>
|
||||
<t t-set="spec_label" t-value="(source_so.x_fc_customer_spec_id.display_name or source_so.x_fc_customer_spec_id.name) if source_so and source_so.x_fc_customer_spec_id else ''"/>
|
||||
<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 -->
|
||||
<div class="fp-sale-header-row">
|
||||
<div class="fp-sale-header-left">
|
||||
<t t-if="logo_uri">
|
||||
<img t-att-src="logo_uri" class="fp-sale-logo" alt="Logo"/>
|
||||
</t>
|
||||
<div class="fp-sale-company-addr">
|
||||
<div>
|
||||
<t t-if="company.partner_id.street"><span t-esc="company.partner_id.street"/></t>
|
||||
<t t-if="company.partner_id.city"> | <span t-esc="company.partner_id.city"/></t>
|
||||
<t t-if="company.partner_id.state_id"> | <span t-esc="company.partner_id.state_id.code or company.partner_id.state_id.name"/></t>
|
||||
<t t-if="company.partner_id.zip"> | <span t-esc="company.partner_id.zip"/></t>
|
||||
</div>
|
||||
<div t-if="company.phone or company_fax">
|
||||
<t t-if="company.phone">Tel: <span t-esc="company.phone"/></t>
|
||||
<t t-if="company.phone and company_fax">   </t>
|
||||
<t t-if="company_fax">Fax: <span t-esc="company_fax"/></t>
|
||||
</div>
|
||||
<div t-if="company.partner_id.website">
|
||||
<a t-att-href="company.partner_id.website"><span t-esc="company.partner_id.website"/></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fp-sale-header-mid">
|
||||
<t t-if="company.x_fc_nadcap_active and company.x_fc_nadcap_logo">
|
||||
<img class="fp-nadcap-logo"
|
||||
t-att-src="'data:image/png;base64,%s' % company.x_fc_nadcap_logo.decode()"
|
||||
alt="Nadcap Accredited"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="fp-sale-header-right">
|
||||
<span class="fp-sale-title-en"><t t-esc="title_en"/></span>
|
||||
<span class="fp-sale-title-fr"><t t-esc="title_fr"/></span>
|
||||
<t t-if="barcode_uri">
|
||||
<div class="fp-bc-wrap" style="margin-top: 4px;">
|
||||
<img t-att-src="barcode_uri" alt="Invoice Barcode"/>
|
||||
<div class="fp-bc-label"><span t-field="doc.name"/></div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<!-- Billing / Shipping -->
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 50%;">BILLING ADDRESS</th>
|
||||
<th style="width: 50%;">DELIVERY ADDRESS</th>
|
||||
<th style="width: 50%;">
|
||||
<span class="fp-bl-en">Billing Address</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Adresse de facturation</span>
|
||||
</th>
|
||||
<th style="width: 50%;">
|
||||
<span class="fp-bl-en">Delivery Address</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Adresse de livraison</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -240,44 +449,106 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Invoice info (wide) -->
|
||||
<!-- Invoice info row (wide, 6 cols on landscape) -->
|
||||
<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>
|
||||
<th>
|
||||
<span class="fp-bl-en-stk">Invoice Date</span>
|
||||
<span class="fp-bl-fr-stk">Date de facture</span>
|
||||
</th>
|
||||
<th>
|
||||
<span class="fp-bl-en-stk">Due Date</span>
|
||||
<span class="fp-bl-fr-stk">Date d'échéance</span>
|
||||
</th>
|
||||
<th>
|
||||
<span class="fp-bl-en-stk">Sales Rep</span>
|
||||
<span class="fp-bl-fr-stk">Vendeur</span>
|
||||
</th>
|
||||
<th>
|
||||
<span class="fp-bl-en-stk">Customer PO #</span>
|
||||
<span class="fp-bl-fr-stk">N° de B/C client</span>
|
||||
</th>
|
||||
<th>
|
||||
<span class="fp-bl-en-stk">Payment Ref</span>
|
||||
<span class="fp-bl-fr-stk">Réf. paiement</span>
|
||||
</th>
|
||||
<th>
|
||||
<span class="fp-bl-en-stk">Currency</span>
|
||||
<span class="fp-bl-fr-stk">Devise</span>
|
||||
</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-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>
|
||||
</table>
|
||||
|
||||
<!-- Lines — hide discount column unless at least one line has a discount -->
|
||||
<!-- Source SO row (wider, 3 cols, inline). Hidden on manual invoices. -->
|
||||
<t t-if="source_so and (source_so.x_fc_customer_job_number or spec_label or delivery_method_label)">
|
||||
<table class="bordered info-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="fp-bl-en">Customer Job #</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">N° de travail client</span>
|
||||
</th>
|
||||
<th>
|
||||
<span class="fp-bl-en">Specification</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Spécification</span>
|
||||
</th>
|
||||
<th>
|
||||
<span class="fp-bl-en">Delivery Method</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Méthode de livraison</span>
|
||||
</th>
|
||||
</tr>
|
||||
</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="spec_label or '—'"/></td>
|
||||
<td class="text-center"><span t-esc="delivery_method_label or '—'"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</t>
|
||||
|
||||
<!-- Lines (taxes column dropped; discount column conditional) -->
|
||||
<t t-set="has_discount" t-value="any(l.discount for l in doc.invoice_line_ids)"/>
|
||||
<t t-set="col_count" t-value="8 if has_discount else 7"/>
|
||||
<t t-set="col_count" t-value="7 if has_discount else 6"/>
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-start" style="width: 18%;">PART NUMBER</th>
|
||||
<th class="text-start" style="width: 24%;">DESCRIPTION</th>
|
||||
<th style="width: 8%;">QTY</th>
|
||||
<th style="width: 8%;">UOM</th>
|
||||
<th style="width: 12%;">UNIT PRICE</th>
|
||||
<th t-if="has_discount" style="width: 10%;">DISCOUNT</th>
|
||||
<th style="width: 10%;">TAXES</th>
|
||||
<th style="width: 10%;">AMOUNT</th>
|
||||
<th class="text-start" style="width: 22%;">
|
||||
<span class="fp-bl-en">Part Number</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">N° de pièce</span>
|
||||
</th>
|
||||
<th class="text-start" style="width: 30%;">
|
||||
<span class="fp-bl-en">Description</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Description</span>
|
||||
</th>
|
||||
<th style="width: 7%;">
|
||||
<span class="fp-bl-en-stk">Qty</span>
|
||||
<span class="fp-bl-fr-stk">Qté</span>
|
||||
</th>
|
||||
<th style="width: 7%;">
|
||||
<span class="fp-bl-en-stk">UOM</span>
|
||||
<span class="fp-bl-fr-stk">UDM</span>
|
||||
</th>
|
||||
<th style="width: 12%;">
|
||||
<span class="fp-bl-en-stk">Unit Price</span>
|
||||
<span class="fp-bl-fr-stk">Prix unitaire</span>
|
||||
</th>
|
||||
<th t-if="has_discount" style="width: 10%;">
|
||||
<span class="fp-bl-en-stk">Discount</span>
|
||||
<span class="fp-bl-fr-stk">Remise</span>
|
||||
</th>
|
||||
<th style="width: 12%;">
|
||||
<span class="fp-bl-en-stk">Amount</span>
|
||||
<span class="fp-bl-fr-stk">Montant</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -291,9 +562,28 @@
|
||||
<t t-elif="not line.display_type or line.display_type == 'product'">
|
||||
<tr>
|
||||
<td>
|
||||
<t t-call="fusion_plating_reports.customer_line_part_number"/>
|
||||
<!-- Three stacked lines: Part #, Name, S/N -->
|
||||
<div>
|
||||
<strong>Part #:</strong>
|
||||
<t t-call="fusion_plating_reports.customer_line_part_number"/>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Name:</strong>
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
<strong>S/N:</strong>
|
||||
<t t-if="'x_fc_serial_id' in line._fields and line.x_fc_serial_id">
|
||||
<span t-esc="line.x_fc_serial_id.name"/>
|
||||
</t>
|
||||
<t t-else="">—</t>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<t t-set="line" t-value="line.with_context(fp_no_serial_in_desc=True)"/>
|
||||
<t t-call="fusion_plating_reports.customer_line_description"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@@ -305,10 +595,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>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<t t-esc="', '.join([(tax.invoice_label or tax.name) for tax in line.tax_ids]) or '-'"/>
|
||||
<t t-else="">—</t>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-field="line.price_subtotal" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
|
||||
@@ -322,34 +609,47 @@
|
||||
<!-- 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 t-if="doc.invoice_payment_term_id">
|
||||
<strong>Payment Terms / Modalités de paiement:</strong><br/>
|
||||
<t t-if="doc.invoice_payment_term_id.note">
|
||||
<span t-field="doc.invoice_payment_term_id.note"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span t-field="doc.invoice_payment_term_id.name"/>
|
||||
</t>
|
||||
</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 style="min-width: 200px;">
|
||||
<span class="fp-bl-en">Subtotal</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Sous-total</span>
|
||||
</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>
|
||||
<span class="fp-bl-en">Taxes</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Taxes</span>
|
||||
</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: #c1c1c1;">
|
||||
<td><strong>Grand Total</strong></td>
|
||||
<td>
|
||||
<span class="fp-bl-en">Grand Total</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Total général</span>
|
||||
</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>
|
||||
<strong><span class="fp-bl-en">Amount Due</span><span class="fp-bl-sep">/</span><span class="fp-bl-fr">Montant dû</span></strong>
|
||||
</td>
|
||||
<td class="text-end"><strong>
|
||||
<span t-field="doc.amount_residual" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
|
||||
</strong></td>
|
||||
@@ -362,7 +662,7 @@
|
||||
<!-- Paid stamp -->
|
||||
<t t-if="doc.payment_state in ('paid', 'in_payment')">
|
||||
<div style="margin-top: 15px; text-align: center;">
|
||||
<span class="paid-stamp">PAID</span>
|
||||
<span class="paid-stamp">PAID / PAYÉ</span>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -22,6 +22,46 @@
|
||||
External_layout already places the page body at the bottom of
|
||||
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
|
||||
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
|
||||
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. -->
|
||||
<template id="fp_external_layout_clean">
|
||||
<t t-if="not o" t-set="o" t-value="doc"/>
|
||||
<t t-if="not company">
|
||||
<t t-if="company_id">
|
||||
<t t-set="company" t-value="company_id"/>
|
||||
</t>
|
||||
<t t-elif="o and 'company_id' in o and o.company_id.sudo()">
|
||||
<t t-set="company" t-value="o.company_id.sudo()"/>
|
||||
</t>
|
||||
<t t-else="else">
|
||||
<t t-set="company" t-value="res_company"/>
|
||||
</t>
|
||||
</t>
|
||||
<div class="article o_report_layout_standard"
|
||||
t-att-data-oe-model="o and o._name"
|
||||
t-att-data-oe-id="o and o.id"
|
||||
t-att-data-oe-lang="o and o.env.context.get('lang')">
|
||||
<t t-out="0"/>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div style="font-size: 9pt; color: #666; overflow: hidden;">
|
||||
<!-- Internal form code (e.g. "FRM-006") on the left.
|
||||
Each report sets `form_code` via t-set BEFORE the
|
||||
t-call to this layout; reports that don't set it
|
||||
leave the left side blank. -->
|
||||
<span style="float: left;" t-if="form_code"><t t-esc="form_code"/></span>
|
||||
<span style="float: right; white-space: nowrap; padding-left: 10px;" t-if="report_type == 'pdf'">Page <span class="page"/> / <span class="topage"/></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="fp_sale_bilingual_styles">
|
||||
<style>
|
||||
/* Inline bilingual: English bold, then a faint slash, then
|
||||
@@ -38,13 +78,58 @@
|
||||
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; }
|
||||
/* Match the CoC pattern exactly: tiny paperformat margin_top
|
||||
(8mm) lets the header HTML overflow into the body area,
|
||||
and this 20mm padding-top on the wrapper clears it. See
|
||||
CLAUDE.md "wkhtmltopdf header overlap" — the alternative
|
||||
"size margin_top to the header height" approach has zero
|
||||
slack and breaks any time the header HTML grows. */
|
||||
.fp-report.fp-sale { padding-top: 20mm; }
|
||||
/* This template uses fp_external_layout_clean (sibling
|
||||
template in this file) instead of web.external_layout.
|
||||
That gives us the `.article` wrapper Odoo's report
|
||||
renderer needs for proper UTF-8 dispatch, WITHOUT the
|
||||
`.header` / `.footer` divs that wkhtmltopdf would
|
||||
extract into page-margin streams. So the body owns the
|
||||
entire visible header (logo + address LEFT, title +
|
||||
barcode RIGHT) and no auto company band shows up. See
|
||||
CLAUDE.md "Custom-header reports need .article wrapper". */
|
||||
.fp-report.fp-sale { padding-top: 0; }
|
||||
|
||||
/* Custom inline header: 2-column flex-via-floats. Left has
|
||||
the company logo + address + phone + URL; right has the
|
||||
document title (bilingual stack) + Code128 barcode. */
|
||||
.fp-sale-header-row { overflow: hidden; margin-bottom: 14px; padding-bottom: 6px; }
|
||||
.fp-sale-header-left { float: left; width: 38%; }
|
||||
/* Middle column: NADCAP accreditation logo (pulled from
|
||||
company settings) above a small "25 Years in Business"
|
||||
medallion. Conditional on company.x_fc_nadcap_active so
|
||||
non-Nadcap shops only see the badge. */
|
||||
.fp-sale-header-mid { float: left; width: 24%; text-align: center; padding-top: 4px; }
|
||||
.fp-nadcap-logo { max-height: 45px; max-width: 115px; display: inline-block; }
|
||||
/* 25-Years-in-Business medallion: tasteful gold-on-cream
|
||||
bordered badge. Picks a dark muted gold (#8a6d2c) so it
|
||||
reads as "anniversary keepsake" rather than gaudy. */
|
||||
.fp-25-badge {
|
||||
display: inline-block;
|
||||
border: 1.5px solid #c8a55b;
|
||||
background-color: #faf5e8;
|
||||
padding: 5px 12px;
|
||||
border-radius: 4px;
|
||||
margin-top: 6px;
|
||||
text-align: center;
|
||||
line-height: 1.05;
|
||||
}
|
||||
.fp-25-badge .fp-25-num { font-size: 22pt; font-weight: bold; color: #8a6d2c; }
|
||||
.fp-25-badge .fp-25-lbl { font-size: 7pt; font-weight: bold; color: #8a6d2c; letter-spacing: 1.2px; margin-top: 1px; }
|
||||
.fp-25-badge .fp-25-sub { font-size: 6.5pt; color: #8a6d2c; font-style: italic; margin-top: 1px; }
|
||||
/* Right column: title (English bold + French italic) + barcode
|
||||
+ SO number all centered as a stacked block. Width reduced
|
||||
to 38% to share row with the new middle NADCAP+25Years cell. */
|
||||
.fp-sale-header-right { float: right; width: 38%; text-align: center; }
|
||||
.fp-sale-logo { max-height: 50px; max-width: 280px; display: block; margin-bottom: 4px; }
|
||||
.fp-sale-company-addr { font-size: 8.5pt; color: #222; line-height: 1.35; }
|
||||
.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.
|
||||
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 —
|
||||
the global ".fp-report table" rule was applying borders
|
||||
to every nested table even with "border: 0 !important",
|
||||
@@ -65,48 +150,83 @@
|
||||
.fp-sale-barcode { float: right; margin-left: 12px; }
|
||||
.fp-bc-wrap { display: inline-block; text-align: center; }
|
||||
.fp-bc-wrap img { height: 48px; max-width: 240px; border: 0 !important; padding: 0; display: block; }
|
||||
.fp-bc-wrap .fp-bc-label { font-size: 10pt; color: #333; margin-top: 6px; letter-spacing: 0.5px; }
|
||||
.fp-bc-wrap .fp-bc-label { font-size: 16pt; font-weight: bold; color: #000; margin-top: 6px; letter-spacing: 1.5px; }
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<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"/>
|
||||
<t t-call="fusion_plating_reports.fp_sale_bilingual_styles"/>
|
||||
<!-- Internal form code rendered on the footer left side
|
||||
by fp_external_layout_clean (see that template). -->
|
||||
<t t-set="form_code" t-value="'FRM-006'"/>
|
||||
<t t-call="fusion_plating_reports.fp_external_layout_clean">
|
||||
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)"/>
|
||||
<t t-set="company" t-value="doc.company_id or env.company"/>
|
||||
<t t-call="fusion_plating_reports.fp_portrait_styles"/>
|
||||
<t t-call="fusion_plating_reports.fp_sale_bilingual_styles"/>
|
||||
|
||||
<!-- Compute helpers -->
|
||||
<t t-set="is_quote" t-value="doc.state in ('draft', 'sent')"/>
|
||||
<t t-set="title_en" t-value="'Quotation' if is_quote else 'Order Confirmation'"/>
|
||||
<t t-set="title_fr" t-value="'Devis' if is_quote else 'Confirmation de commande'"/>
|
||||
<t t-set="barcode_uri" t-value="doc.env['ir.actions.report'].sudo().barcode_data_uri('Code128', doc.name, 600, 100) if doc.name else False"/>
|
||||
<t t-set="spec_label" t-value="(doc.x_fc_customer_spec_id.display_name or doc.x_fc_customer_spec_id.name) if doc.x_fc_customer_spec_id else ''"/>
|
||||
<t t-set="delivery_method_label" t-value="dict(doc._fields['x_fc_delivery_method'].selection).get(doc.x_fc_delivery_method, '') if 'x_fc_delivery_method' in doc._fields and doc.x_fc_delivery_method else ''"/>
|
||||
<!-- Compute helpers -->
|
||||
<t t-set="is_quote" t-value="doc.state in ('draft', 'sent')"/>
|
||||
<t t-set="title_en" t-value="'Quotation' if is_quote else 'Order Confirmation'"/>
|
||||
<t t-set="title_fr" t-value="'Devis' if is_quote else 'Confirmation de commande'"/>
|
||||
<t t-set="barcode_uri" t-value="doc.env['ir.actions.report'].sudo().barcode_data_uri('Code128', doc.name, 600, 100) if doc.name else False"/>
|
||||
<t t-set="spec_label" t-value="(doc.x_fc_customer_spec_id.display_name or doc.x_fc_customer_spec_id.name) if doc.x_fc_customer_spec_id else ''"/>
|
||||
<t t-set="delivery_method_label" t-value="dict(doc._fields['x_fc_delivery_method'].selection).get(doc.x_fc_delivery_method, '') if 'x_fc_delivery_method' in doc._fields and doc.x_fc_delivery_method else ''"/>
|
||||
<t t-set="logo_uri" t-value="('data:image/png;base64,%s' % company.logo.decode()) if company.logo else False"/>
|
||||
<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"/>
|
||||
|
||||
<div class="fp-report fp-sale">
|
||||
<div class="page">
|
||||
|
||||
<!-- Title bar: stacked English/French title
|
||||
on the left, Code128 barcode floated
|
||||
right. NO HTML table — see CLAUDE.md
|
||||
"wkhtmltopdf header overlap" §2 for why
|
||||
a table here leaks borders. -->
|
||||
<div class="fp-sale-titlebar">
|
||||
<t t-if="barcode_uri">
|
||||
<div class="fp-sale-barcode">
|
||||
<div class="fp-bc-wrap">
|
||||
<img t-att-src="barcode_uri" alt="Order Barcode"/>
|
||||
<div class="fp-bc-label"><span t-field="doc.name"/></div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<span class="fp-sale-title-en">
|
||||
<t t-esc="title_en"/><span class="fp-sale-title-num"># <span t-field="doc.name"/></span>
|
||||
</span>
|
||||
<span class="fp-sale-title-fr"><t t-esc="title_fr"/></span>
|
||||
<div class="fp-report fp-sale">
|
||||
<!-- Inline header (drops web.external_layout for this
|
||||
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">
|
||||
<div class="fp-sale-header-left">
|
||||
<t t-if="logo_uri">
|
||||
<img t-att-src="logo_uri" class="fp-sale-logo" alt="Logo"/>
|
||||
</t>
|
||||
<div class="fp-sale-company-addr">
|
||||
<div>
|
||||
<t t-if="company.partner_id.street"><span t-esc="company.partner_id.street"/></t>
|
||||
<t t-if="company.partner_id.city"> | <span t-esc="company.partner_id.city"/></t>
|
||||
<t t-if="company.partner_id.state_id"> | <span t-esc="company.partner_id.state_id.code or company.partner_id.state_id.name"/></t>
|
||||
<t t-if="company.partner_id.zip"> | <span t-esc="company.partner_id.zip"/></t>
|
||||
</div>
|
||||
<div t-if="company.phone or company_fax">
|
||||
<t t-if="company.phone">Tel: <span t-esc="company.phone"/></t>
|
||||
<t t-if="company.phone and company_fax">   </t>
|
||||
<t t-if="company_fax">Fax: <span t-esc="company_fax"/></t>
|
||||
</div>
|
||||
<div t-if="company.partner_id.website">
|
||||
<a t-att-href="company.partner_id.website"><span t-esc="company.partner_id.website"/></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Middle column: NADCAP logo only. Base64-inlined
|
||||
from `company.x_fc_nadcap_logo` so wkhtmltopdf
|
||||
doesn't have to fetch over HTTP (network calls
|
||||
fail on entech). -->
|
||||
<div class="fp-sale-header-mid">
|
||||
<t t-if="company.x_fc_nadcap_active and company.x_fc_nadcap_logo">
|
||||
<img class="fp-nadcap-logo"
|
||||
t-att-src="'data:image/png;base64,%s' % company.x_fc_nadcap_logo.decode()"
|
||||
alt="Nadcap Accredited"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="fp-sale-header-right">
|
||||
<span class="fp-sale-title-en"><t t-esc="title_en"/></span>
|
||||
<span class="fp-sale-title-fr"><t t-esc="title_fr"/></span>
|
||||
<t t-if="barcode_uri">
|
||||
<div class="fp-bc-wrap" style="margin-top: 4px;">
|
||||
<img t-att-src="barcode_uri" alt="Order Barcode"/>
|
||||
<div class="fp-bc-label"><span t-field="doc.name"/></div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<!-- Billing / Shipping (wide cells — inline) -->
|
||||
<table class="bordered">
|
||||
@@ -174,10 +294,18 @@
|
||||
<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-if="doc.x_fc_rush_order">
|
||||
<span class="status-warning">Rush / Urgent</span>
|
||||
<!-- Lead Time renders from the
|
||||
computed display string on
|
||||
sale.order. Rush stays
|
||||
highlighted; everything
|
||||
else (range / single value
|
||||
/ Standard) is plain text. -->
|
||||
<t t-if="doc.x_fc_lead_time_display == 'Rush'">
|
||||
<span class="status-warning">Rush</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span t-esc="doc.x_fc_lead_time_display"/>
|
||||
</t>
|
||||
<t t-else="">Standard</t>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -267,13 +395,34 @@
|
||||
<t t-elif="not line.display_type or line.display_type == 'product'">
|
||||
<tr>
|
||||
<td>
|
||||
<t t-call="fusion_plating_reports.customer_line_part_number"/>
|
||||
<t t-if="line.x_fc_part_catalog_id and line.x_fc_part_catalog_id.name">
|
||||
<span> - </span>
|
||||
<span t-esc="line.x_fc_part_catalog_id.name"/>
|
||||
</t>
|
||||
<!-- Three stacked lines:
|
||||
1. Part Number (catalog part_number + revision)
|
||||
2. Name (catalog name, falls back to em-dash)
|
||||
3. Serial Number (m2m serials joined, or em-dash) -->
|
||||
<div>
|
||||
<strong>Part #:</strong>
|
||||
<t t-call="fusion_plating_reports.customer_line_part_number"/>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Name:</strong>
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Rebind `line` with fp_no_serial_in_desc=True so the
|
||||
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"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
|
||||
Reference in New Issue
Block a user