feat(reports): split customer-facing line header into Part Number + Description columns

User request: on customer-facing PDFs the single "Part" column was
stacking part number + revision + description together. Split into
two distinct columns so customers see Part Number in its own field
and the Description column carries only the prose.

Macro split:
  * customer_line_part_number — strong part number + "(Rev X)"
  * customer_line_description  — line.name + any populated line
    metadata (serial, job#, thickness)
  * customer_line_header (legacy) kept as a thin wrapper that
    t-calls both macros stacked so older reports still render.

Reports updated — each gains a new first "PART NUMBER" column and
the old "PART" column renamed to "DESCRIPTION":
  * report_fp_sale (both portrait variants)
  * report_fp_invoice (both portrait variants)
  * report_fp_packing_slip (both variants — delivery + MO-origin)

Column widths rebalanced per report. Section / note colspans bumped
to account for the extra column.

report_fp_bol left as-is — its "Description of Goods" td is a
freight-convention combined field, intentionally keeps the stacked
layout via the legacy customer_line_header macro.

fusion_plating_reports → 19.0.6.0.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-23 10:17:56 -04:00
parent 8142bd229a
commit eddf803d4c
5 changed files with 84 additions and 28 deletions

View File

@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Plating — Reports',
'version': '19.0.5.1.0',
'version': '19.0.6.0.0',
'category': 'Manufacturing/Plating',
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
'depends': [

View File

@@ -20,13 +20,34 @@
<t t-call="fusion_plating_reports.customer_line_header"/>
-->
<odoo>
<!-- ==========================================================
customer_line_header (legacy — kept for backward compat)
Prints part number + revision + description in ONE td.
Reports written before the 2026-04-23 column split still
call this macro. New reports should use the split macros
below (customer_line_part_number + customer_line_description)
which render into two separate <td> columns.
========================================================== -->
<template id="customer_line_header">
<t t-call="fusion_plating_reports.customer_line_part_number"/>
<br/>
<t t-call="fusion_plating_reports.customer_line_description"/>
</template>
<!-- ==========================================================
customer_line_part_number — just the part number + rev
Renders as a single-line "PN-1234 Rev A" block, intended
for the "Part Number" td in customer-facing tables.
========================================================== -->
<template id="customer_line_part_number">
<t t-if="line.x_fc_part_catalog_id">
<strong>
<span t-esc="line.x_fc_part_catalog_id.part_number"/>
<!-- Sub 5 — prefer the revision snapshot captured on the line
at save time; fall back to the catalog's current revision
if no snapshot was frozen (old lines from before Sub 5). -->
<!-- Prefer the Sub-5 revision snapshot captured on the
line at save time; fall back to the catalog's
current revision for older lines that predate it. -->
<t t-set="_rev" t-value="(
line.x_fc_revision_snapshot
if 'x_fc_revision_snapshot' in line._fields
@@ -36,11 +57,21 @@
<span> (Rev <span t-esc="_rev"/>)</span>
</t>
</strong>
<br/>
</t>
<t t-else="">
<!-- Fee / freight / non-part line: no part number to show -->
<span class="text-muted"></span>
</t>
</template>
<!-- ==========================================================
customer_line_description — customer-facing description
plus any populated line metadata (serial, job#, thickness).
Intended for the "Description" td in customer-facing tables.
========================================================== -->
<template id="customer_line_description">
<t t-if="line.x_fc_part_catalog_id">
<span t-esc="line.name"/>
<!-- Sub 5 — print serial / job# / thickness under the line
description when populated. Each on its own row so blank
fields disappear cleanly. -->
<t t-if="'x_fc_serial_id' in line._fields and line.x_fc_serial_id">
<br/>
<small>Serial: <span t-esc="line.x_fc_serial_id.name"/></small>
@@ -63,4 +94,5 @@
</t>
</t>
</template>
</odoo>

View File

@@ -80,7 +80,8 @@
<table class="bordered">
<thead>
<tr>
<th class="text-start" style="width: 52%;">PART</th>
<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>
@@ -91,15 +92,18 @@
<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="6"><strong t-field="line.name"/></td></tr>
<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="6"><span t-field="line.name"/></td></tr>
<tr class="note-row"><td colspan="7"><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_header"/>
<t t-call="fusion_plating_reports.customer_line_part_number"/>
</td>
<td>
<t t-call="fusion_plating_reports.customer_line_description"/>
</td>
<td class="text-center">
<span t-esc="int(line.quantity) if line.quantity == int(line.quantity) else line.quantity"/>
@@ -262,11 +266,12 @@
<!-- Lines — hide discount column unless at least one line has a discount -->
<t t-set="has_discount" t-value="any(l.discount for l in doc.invoice_line_ids)"/>
<t t-set="col_count" t-value="7 if has_discount else 6"/>
<t t-set="col_count" t-value="8 if has_discount else 7"/>
<table class="bordered">
<thead>
<tr>
<th class="text-start" style="width: 42%;">PART</th>
<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>
@@ -286,7 +291,10 @@
<t t-elif="not line.display_type or line.display_type == 'product'">
<tr>
<td>
<t t-call="fusion_plating_reports.customer_line_header"/>
<t t-call="fusion_plating_reports.customer_line_part_number"/>
</td>
<td>
<t t-call="fusion_plating_reports.customer_line_description"/>
</td>
<td class="text-center">
<span t-esc="int(line.quantity) if line.quantity == int(line.quantity) else line.quantity"/>

View File

@@ -77,7 +77,8 @@
<table class="bordered">
<thead>
<tr>
<th class="text-start" style="width: 56%;">PART</th>
<th class="text-start" style="width: 22%;">PART NUMBER</th>
<th class="text-start" style="width: 34%;">DESCRIPTION</th>
<th style="width: 12%;">QTY</th>
<th style="width: 10%;">UOM</th>
<th style="width: 22%;">LOT / SERIAL</th>
@@ -86,9 +87,12 @@
<tbody>
<t t-foreach="doc.move_ids_without_package" t-as="move">
<tr>
<t t-set="line" t-value="move.sale_line_id or move"/>
<td>
<t t-set="line" t-value="move.sale_line_id or move"/>
<t t-call="fusion_plating_reports.customer_line_header"/>
<t t-call="fusion_plating_reports.customer_line_part_number"/>
</td>
<td>
<t t-call="fusion_plating_reports.customer_line_description"/>
</td>
<td class="text-center">
<span t-esc="int(move.quantity) if move.quantity == int(move.quantity) else move.quantity"/>
@@ -213,7 +217,8 @@
<table class="bordered">
<thead>
<tr>
<th class="text-start" style="width: 44%;">PART</th>
<th class="text-start" style="width: 18%;">PART NUMBER</th>
<th class="text-start" style="width: 26%;">DESCRIPTION</th>
<th style="width: 10%;">ORDERED</th>
<th style="width: 10%;">DONE</th>
<th style="width: 8%;">UOM</th>
@@ -224,9 +229,12 @@
<tbody>
<t t-foreach="doc.move_ids_without_package" t-as="move">
<tr>
<t t-set="line" t-value="move.sale_line_id or move"/>
<td>
<t t-set="line" t-value="move.sale_line_id or move"/>
<t t-call="fusion_plating_reports.customer_line_header"/>
<t t-call="fusion_plating_reports.customer_line_part_number"/>
</td>
<td>
<t t-call="fusion_plating_reports.customer_line_description"/>
</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"/>

View File

@@ -137,7 +137,8 @@
<table class="bordered">
<thead>
<tr>
<th class="text-start" style="width: 50%;">PART</th>
<th class="text-start" style="width: 20%;">PART NUMBER</th>
<th class="text-start" style="width: 30%;">DESCRIPTION</th>
<th style="width: 8%;">QTY</th>
<th style="width: 8%;">UOM</th>
<th style="width: 12%;">UNIT PRICE</th>
@@ -148,15 +149,18 @@
<tbody>
<t t-foreach="doc.order_line" t-as="line">
<t t-if="line.display_type == 'line_section'">
<tr class="section-row"><td colspan="6"><strong t-field="line.name"/></td></tr>
<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="6"><span t-field="line.name"/></td></tr>
<tr class="note-row"><td colspan="7"><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_header"/>
<t t-call="fusion_plating_reports.customer_line_part_number"/>
</td>
<td>
<t t-call="fusion_plating_reports.customer_line_description"/>
</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"/>
@@ -403,11 +407,12 @@
<!-- Order lines — hide discount column unless at least one line has a discount -->
<t t-set="has_discount" t-value="any(l.discount for l in doc.order_line)"/>
<t t-set="col_count" t-value="7 if has_discount else 6"/>
<t t-set="col_count" t-value="8 if has_discount else 7"/>
<table class="bordered">
<thead>
<tr>
<th class="text-start" style="width: 42%;">PART</th>
<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>
@@ -427,7 +432,10 @@
<t t-elif="not line.display_type or line.display_type == 'product'">
<tr>
<td>
<t t-call="fusion_plating_reports.customer_line_header"/>
<t t-call="fusion_plating_reports.customer_line_part_number"/>
</td>
<td>
<t t-call="fusion_plating_reports.customer_line_description"/>
</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"/>