feat(sticker): restore older 3-cell-header + right-Notes-panel layout
Per user request — the previous (now restored) layout is the one operators were used to: - Header: single row with 3 cells side-by-side (Logo | WO # | QR) instead of the stacked logo+WO# left / QR right. - Body: 3-col layout (Label | Value | Notes-rowspan). Notes is a tall right-side panel spanning all 7 body rows — gives operators ~62mm of vertical space for multi-line notes (vs. the prior single-row Notes that capped at ~8.7mm). - Fields: PO #, SN #, Customer, Part #, Due Date, Thickness, Qty. SN # joins x_fc_serial_ids; Thickness composes from the coating's thickness_min/max + uom. - Header band 32% (was 40%), body band 68% — the header is shorter because there's no stacked logo/WO# anymore; body grows to absorb the freed space, but the 7-row grid already provided ample per-row height. - Body text 17pt main / 13pt muted (down from 38pt). The big-font pass was for a 7-row equal-height grid; with the 3-col layout the per-row height is unchanged but the rows are now narrower (only ~70% of the page width minus the Notes panel), so a more conservative font keeps text from wrapping mid-cell. - QR generation stays at 600x600 (sharper print at scan time). Variable interface unchanged. mrp.production / mrp.workorder / fp.job / sale.order all still call the same inner template; two new variables (_serial_names, _thickness_str) get pre-set by the defaults template and resolved by the inner. Verified: 72-KB PDF renders cleanly on entech for WO-30019. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
{
|
||||
'name': 'Fusion Plating — Reports',
|
||||
'version': '19.0.10.4.1',
|
||||
'version': '19.0.10.5.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
|
||||
'depends': [
|
||||
|
||||
@@ -67,8 +67,26 @@
|
||||
else (_mo and _mo.name) or ''"/>
|
||||
<t t-set="_internal_note" t-value="_internal_note
|
||||
or (_so and _so.x_fc_internal_note
|
||||
and _so.x_fc_internal_note.striptags()[:100])
|
||||
and _so.x_fc_internal_note.striptags()[:400])
|
||||
or '-'"/>
|
||||
<!-- Serial number(s): pre-set by outer (fp.job sets it from the
|
||||
linked SO line), otherwise resolve from _line if present.
|
||||
Joined with commas when multiple serials live on one line. -->
|
||||
<t t-set="_serial_names" t-value="_serial_names
|
||||
or (_line and 'x_fc_serial_ids' in _line._fields
|
||||
and _line.x_fc_serial_ids
|
||||
and ', '.join(_line.x_fc_serial_ids.mapped('name')))
|
||||
or '-'"/>
|
||||
<!-- Thickness range: prefer outer-supplied string; otherwise compose
|
||||
from the coating's min/max + uom. Shows "-" when no coating or
|
||||
both min/max are zero. -->
|
||||
<t t-set="_thickness_str" t-value="_thickness_str or (_coating
|
||||
and (_coating.thickness_min or _coating.thickness_max)
|
||||
and (
|
||||
('%s - %s' % (_coating.thickness_min, _coating.thickness_max))
|
||||
+ (' ' + (_coating.thickness_uom or '') if _coating.thickness_uom else '')
|
||||
)
|
||||
) or '-'"/>
|
||||
<!-- Inline the QR as base64 data URI so wkhtmltopdf doesn't need
|
||||
to fetch /report/barcode/ over the network during rendering.
|
||||
Generated at 600x600 (was 300x300) so it down-scales rather
|
||||
@@ -85,10 +103,10 @@
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
/* Boxy professional layout: thick outer border, horizontal row
|
||||
borders, vertical label/value divider. Absolute positioning +
|
||||
% row heights force the content to fill the full page in
|
||||
wkhtmltopdf (which ignores vh/vw/flex). ------------------- */
|
||||
/* Layout: 3-cell header (Logo | WO# | QR) above a 3-col body
|
||||
(Label | Value | Notes-rowspan). Notes spans the full body
|
||||
so operators have a tall right-side panel for multi-line
|
||||
notes — the original ENTECH sticker layout. */
|
||||
.fp-sticker {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
color: #000;
|
||||
@@ -100,14 +118,13 @@
|
||||
page-break-after: always;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
/* ---- HEADER band — 40% to fit 2x WO# + logo + QR. */
|
||||
/* ---- HEADER band — 32% height, 3 cells side-by-side. */
|
||||
.fp-sticker-head-wrap {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0;
|
||||
height: 40%;
|
||||
height: 32%;
|
||||
border-bottom: 2px solid #000;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
table.fp-sticker-head {
|
||||
width: 100%;
|
||||
@@ -115,82 +132,53 @@
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.fp-sticker-head td { padding: 0; vertical-align: middle; }
|
||||
col.fp-col-head-left { width: 66%; }
|
||||
col.fp-col-head-right { width: 34%; }
|
||||
td.fp-sticker-head-left {
|
||||
overflow: hidden;
|
||||
border-right: 2px solid #000;
|
||||
}
|
||||
td.fp-sticker-head-right {
|
||||
table.fp-sticker-head td {
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Left column nested 2-row table: logo on top, WO# below.
|
||||
Horizontal divider between rows mirrors body row borders. */
|
||||
table.fp-sticker-head-left-stack {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.fp-sticker-head-left-stack tr.fp-row-logo { height: 50%; }
|
||||
table.fp-sticker-head-left-stack tr.fp-row-wo { height: 50%; }
|
||||
table.fp-sticker-head-left-stack td {
|
||||
padding: 0 14px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
/* Logo cell + WO# cell each get explicit vertical-align so the
|
||||
content sits in the middle of its half of the header band. */
|
||||
table.fp-sticker-head-left-stack tr.fp-row-logo td,
|
||||
table.fp-sticker-head-left-stack tr.fp-row-wo td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
table.fp-sticker-head-left-stack tr + tr td {
|
||||
border-top: 1px solid #000;
|
||||
}
|
||||
col.fp-col-head-logo { width: 28%; }
|
||||
col.fp-col-head-wo { width: 44%; }
|
||||
col.fp-col-head-qr { width: 28%; }
|
||||
td.fp-sticker-head-logo { border-right: 2px solid #000; }
|
||||
td.fp-sticker-head-wo { border-right: 2px solid #000; }
|
||||
.fp-sticker-logo {
|
||||
/* Logo bumped 40% (116 → 162px height, 520 → 728px width). */
|
||||
max-height: 162px;
|
||||
max-width: 728px;
|
||||
display: block;
|
||||
max-height: 22mm;
|
||||
max-width: 90%;
|
||||
display: inline-block;
|
||||
}
|
||||
.fp-sticker-wo {
|
||||
font-size: 72pt;
|
||||
font-size: 36pt;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.2mm;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
}
|
||||
/* QR wrapper crops the white quiet-zone around the QR pattern
|
||||
so it doesn't visually float on a white square inside the
|
||||
cell. The PNG from Odoo's barcode generator carries a
|
||||
~12% border (4 modules of quiet-zone) on each side; we
|
||||
render the image larger than the wrapper and offset it so
|
||||
the wrapper clips that border out. ---------------------- */
|
||||
/* QR wrapper crops the ~12% quiet-zone the barcode generator
|
||||
adds around the pattern so the visible QR fills the cell. */
|
||||
.fp-sticker-qr-wrap {
|
||||
width: 380px;
|
||||
height: 380px;
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.fp-sticker-qr {
|
||||
width: 510px;
|
||||
height: 510px;
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
position: absolute;
|
||||
top: -65px;
|
||||
left: -65px;
|
||||
top: -40px;
|
||||
left: -40px;
|
||||
margin: 0;
|
||||
display: block;
|
||||
}
|
||||
/* ---- BODY band (7 rows, each 14.28% of the band) ---- */
|
||||
/* ---- BODY band — 68%, 3 cols (Label | Value | Notes-rowspan). */
|
||||
.fp-sticker-body-wrap {
|
||||
position: absolute;
|
||||
left: 0; right: 0;
|
||||
top: 40%; bottom: 0;
|
||||
top: 32%; bottom: 0;
|
||||
}
|
||||
table.fp-sticker-body {
|
||||
width: 100%;
|
||||
@@ -198,15 +186,22 @@
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
col.fp-col-label { width: 22%; }
|
||||
col.fp-col-value { width: 48%; }
|
||||
col.fp-col-notes { width: 30%; }
|
||||
/* 7 rows of equal height. wkhtmltopdf needs explicit row
|
||||
heights to fill the band; without it rows collapse to
|
||||
text-line height. -------------------------------------- */
|
||||
table.fp-sticker-body tr { height: 14.28%; }
|
||||
table.fp-sticker-body tr + tr td { border-top: 1px solid #000; }
|
||||
col.fp-col-label { width: 32%; }
|
||||
col.fp-col-value { width: 68%; }
|
||||
table.fp-sticker-body tr + tr td.fp-sticker-label,
|
||||
table.fp-sticker-body tr + tr td.fp-sticker-value {
|
||||
border-top: 1px solid #000;
|
||||
}
|
||||
table.fp-sticker-body td {
|
||||
vertical-align: middle;
|
||||
padding: 0 14px;
|
||||
font-size: 38pt;
|
||||
line-height: 1.1;
|
||||
padding: 0 12px;
|
||||
font-size: 17pt;
|
||||
line-height: 1.15;
|
||||
}
|
||||
td.fp-sticker-label {
|
||||
font-weight: 700;
|
||||
@@ -218,138 +213,143 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
border-right: 2px solid #000;
|
||||
}
|
||||
/* Notes spans all 7 body rows — multi-line text wraps freely
|
||||
(no nowrap, top-aligned, modest padding). Big bold "Notes:"
|
||||
heading then the body text. */
|
||||
td.fp-sticker-notes {
|
||||
vertical-align: top;
|
||||
padding: 10px 14px;
|
||||
font-size: 14pt;
|
||||
line-height: 1.3;
|
||||
white-space: pre-line;
|
||||
}
|
||||
.fp-sticker-notes-head {
|
||||
font-weight: 700;
|
||||
font-size: 16pt;
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.fp-sticker-strong { font-weight: 700; }
|
||||
.fp-sticker-muted { color: #555; font-size: 28pt; }
|
||||
.fp-sticker-muted { color: #555; font-size: 13pt; }
|
||||
</style>
|
||||
|
||||
<div class="fp-sticker">
|
||||
<div class="fp-sticker-head-wrap">
|
||||
<table class="fp-sticker-head">
|
||||
<colgroup>
|
||||
<col class="fp-col-head-left"/>
|
||||
<col class="fp-col-head-right"/>
|
||||
</colgroup>
|
||||
<tr>
|
||||
<td class="fp-sticker-head-left">
|
||||
<!-- env.company.logo is often blank while logo_web
|
||||
is populated from the company partner's image.
|
||||
Fall back across both + partner.image_1920. -->
|
||||
<t t-set="_logo" t-value="env.company.logo
|
||||
or env.company.logo_web
|
||||
or env.company.partner_id.image_1920
|
||||
or False"/>
|
||||
<table class="fp-sticker-head-left-stack">
|
||||
<tr class="fp-row-logo">
|
||||
<td>
|
||||
<img t-if="_logo"
|
||||
class="fp-sticker-logo"
|
||||
t-att-src="image_data_uri(_logo)"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="fp-row-wo">
|
||||
<td>
|
||||
<div class="fp-sticker-wo">
|
||||
WO #<span t-esc="_order_id"/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td class="fp-sticker-head-right">
|
||||
<div class="fp-sticker-qr-wrap" t-if="_qr_src">
|
||||
<img class="fp-sticker-qr"
|
||||
t-att-src="_qr_src"/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="fp-sticker-head">
|
||||
<colgroup>
|
||||
<col class="fp-col-head-logo"/>
|
||||
<col class="fp-col-head-wo"/>
|
||||
<col class="fp-col-head-qr"/>
|
||||
</colgroup>
|
||||
<tr>
|
||||
<td class="fp-sticker-head-logo">
|
||||
<!-- env.company.logo is often blank while
|
||||
logo_web is populated. Fall back across
|
||||
both + partner.image_1920. -->
|
||||
<t t-set="_logo" t-value="env.company.logo
|
||||
or env.company.logo_web
|
||||
or env.company.partner_id.image_1920
|
||||
or False"/>
|
||||
<img t-if="_logo"
|
||||
class="fp-sticker-logo"
|
||||
t-att-src="image_data_uri(_logo)"/>
|
||||
</td>
|
||||
<td class="fp-sticker-head-wo">
|
||||
<div class="fp-sticker-wo">
|
||||
WO #<span t-esc="_order_id"/>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="fp-sticker-qr-wrap" t-if="_qr_src">
|
||||
<img class="fp-sticker-qr"
|
||||
t-att-src="_qr_src"/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="fp-sticker-body-wrap">
|
||||
<table class="fp-sticker-body">
|
||||
<colgroup>
|
||||
<col class="fp-col-label"/>
|
||||
<col class="fp-col-value"/>
|
||||
</colgroup>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">PO (RO):</td>
|
||||
<td class="fp-sticker-value">
|
||||
<span class="fp-sticker-strong"
|
||||
t-esc="_po_number"/>
|
||||
<t t-if="_mo_ref">
|
||||
<span class="fp-sticker-muted">
|
||||
(<span t-esc="_mo_ref"/>)
|
||||
</span>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Customer:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<span t-esc="_partner_name"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Process:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<t t-if="_process">
|
||||
<span t-esc="_process.name"/>
|
||||
</t>
|
||||
<t t-elif="_coating">
|
||||
<span t-esc="_coating.name"/>
|
||||
</t>
|
||||
<t t-else="">-</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Part Number:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<t t-if="_part">
|
||||
<table class="fp-sticker-body">
|
||||
<colgroup>
|
||||
<col class="fp-col-label"/>
|
||||
<col class="fp-col-value"/>
|
||||
<col class="fp-col-notes"/>
|
||||
</colgroup>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">PO #:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<span class="fp-sticker-strong"
|
||||
t-esc="_part.part_number"/>
|
||||
<t t-if="_part.revision">
|
||||
<!-- Some parts store the revision with a
|
||||
"Rev " prefix already (e.g. "Rev 1"),
|
||||
others store just the value ("1", "A").
|
||||
Strip a leading "Rev " (case insensitive)
|
||||
so we don't print "Rev Rev 1". -->
|
||||
<t t-set="_rev_clean" t-value="_part.revision.strip()"/>
|
||||
<t t-if="_rev_clean.lower().startswith('rev ')">
|
||||
<t t-set="_rev_clean" t-value="_rev_clean[4:].strip()"/>
|
||||
</t>
|
||||
t-esc="_po_number"/>
|
||||
<t t-if="_mo_ref">
|
||||
<span class="fp-sticker-muted">
|
||||
Rev <span t-esc="_rev_clean"/>
|
||||
(<span t-esc="_mo_ref"/>)
|
||||
</span>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">-</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Due Date:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<t t-if="_due">
|
||||
<span t-esc="_due.strftime('%b %d, %Y')"/>
|
||||
</t>
|
||||
<t t-else="">-</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Qty:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<span class="fp-sticker-strong">
|
||||
<span t-esc="int(_qty) if _qty == int(_qty) else _qty"/>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Notes:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<t t-esc="_internal_note"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<!-- Notes panel spans all 7 body rows. -->
|
||||
<td class="fp-sticker-notes" rowspan="7">
|
||||
<span class="fp-sticker-notes-head">Notes:</span>
|
||||
<t t-esc="_internal_note"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">SN #:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<span t-esc="_serial_names"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Customer:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<span t-esc="_partner_name"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Part #:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<t t-if="_part">
|
||||
<span class="fp-sticker-strong"
|
||||
t-esc="_part.part_number"/>
|
||||
<t t-if="_part.revision">
|
||||
<t t-set="_rev_clean" t-value="_part.revision.strip()"/>
|
||||
<t t-if="_rev_clean.lower().startswith('rev ')">
|
||||
<t t-set="_rev_clean" t-value="_rev_clean[4:].strip()"/>
|
||||
</t>
|
||||
<span class="fp-sticker-muted">
|
||||
Rev <span t-esc="_rev_clean"/>
|
||||
</span>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">-</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Due Date:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<t t-if="_due">
|
||||
<span t-esc="_due.strftime('%b %d, %Y')"/>
|
||||
</t>
|
||||
<t t-else="">-</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Thickness:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<span t-esc="_thickness_str"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Qty:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<span class="fp-sticker-strong">
|
||||
<span t-esc="int(_qty) if _qty == int(_qty) else _qty"/>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -372,6 +372,8 @@
|
||||
<t t-set="_partner_name" t-value="False"/>
|
||||
<t t-set="_mo_ref" t-value="False"/>
|
||||
<t t-set="_internal_note" t-value="False"/>
|
||||
<t t-set="_serial_names" t-value="False"/>
|
||||
<t t-set="_thickness_str" t-value="False"/>
|
||||
<t t-set="_scan_path" t-value="False"/>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user