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.7.2.0',
|
||||
'version': '19.0.7.14.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
|
||||
'depends': [
|
||||
|
||||
Binary file not shown.
@@ -19,6 +19,8 @@ Two changes:
|
||||
|
||||
Lower sequence = appears higher in the Print dropdown.
|
||||
"""
|
||||
import base64
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools import frozendict
|
||||
|
||||
@@ -33,6 +35,20 @@ class IrActionsReport(models.Model):
|
||||
'for both higher and lower priorities.',
|
||||
)
|
||||
|
||||
@api.model
|
||||
def barcode_data_uri(self, barcode_type, value, width=300, height=300):
|
||||
"""Return a data:image/png;base64 URI for a barcode/QR.
|
||||
|
||||
wkhtmltopdf can't always fetch /report/barcode/ over the network
|
||||
during PDF rendering (sandbox/DNS/base-url pitfalls), so reports
|
||||
that embed QR codes on labels inline them as base64 instead.
|
||||
"""
|
||||
png = self.barcode(
|
||||
barcode_type, value,
|
||||
width=width, height=height, humanreadable=0,
|
||||
) or b''
|
||||
return 'data:image/png;base64,' + base64.b64encode(png).decode('ascii')
|
||||
|
||||
|
||||
class IrActionsActions(models.Model):
|
||||
_inherit = 'ir.actions.actions'
|
||||
|
||||
@@ -320,16 +320,21 @@
|
||||
<!-- Prints an ENTECH-style sticker with a QR code that -->
|
||||
<!-- warehouse staff scan to jump straight to the WO form. -->
|
||||
<!-- ============================================================= -->
|
||||
<!-- 102x76mm = 4x3" physical label. Orientation MUST be Portrait: a
|
||||
"custom" wkhtmltopdf page already takes page_width/page_height
|
||||
as literal dimensions, and adding a Landscape orientation flag on
|
||||
top of that swaps the dims AND rotates content, producing a
|
||||
stretched 76x102 portrait page (not what we want). -->
|
||||
<record id="paperformat_fp_wo_sticker" model="report.paperformat">
|
||||
<field name="name">FP WO Sticker (4x3")</field>
|
||||
<field name="name">FP WO Sticker (6x4")</field>
|
||||
<field name="format">custom</field>
|
||||
<field name="page_width">102</field>
|
||||
<field name="page_height">76</field>
|
||||
<field name="orientation">Landscape</field>
|
||||
<field name="margin_top">3</field>
|
||||
<field name="margin_bottom">3</field>
|
||||
<field name="margin_left">3</field>
|
||||
<field name="margin_right">3</field>
|
||||
<field name="page_width">152</field>
|
||||
<field name="page_height">102</field>
|
||||
<field name="orientation">Portrait</field>
|
||||
<field name="margin_top">0</field>
|
||||
<field name="margin_bottom">0</field>
|
||||
<field name="margin_left">0</field>
|
||||
<field name="margin_right">0</field>
|
||||
<field name="header_line" eval="False"/>
|
||||
<field name="header_spacing">0</field>
|
||||
<field name="disable_shrinking" eval="True"/>
|
||||
|
||||
@@ -16,19 +16,12 @@
|
||||
* _coating — fp.coating.config
|
||||
* _process — the resolved fusion.plating.process.node tree
|
||||
* _scan_url — base_url + /fp/wo/<id> (encoded into the QR)
|
||||
|
||||
The sticker works identically whether triggered from the MO form
|
||||
or from any of its child WOs.
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<!-- ========== Shared inner template ========== -->
|
||||
<template id="report_fp_wo_sticker_inner">
|
||||
<t t-set="_base_url" t-value="env['ir.config_parameter'].sudo().get_param('web.base.url', '')"/>
|
||||
<!-- _scan_id is always a numeric database id (mo.id or wo.id)
|
||||
so the scan endpoint can resolve it cleanly. _order_id is
|
||||
what gets printed next to "WO #" and can be a friendly
|
||||
Odoo name like "WH/MO/00067". -->
|
||||
<t t-set="_scan_url" t-value="_base_url + '/fp/wo/' + str(_scan_id)"/>
|
||||
<t t-set="_so" t-value="_mo and env['sale.order'].sudo().search(
|
||||
[('name', '=', _mo.origin)], limit=1) or False"/>
|
||||
@@ -44,191 +37,277 @@
|
||||
<t t-set="_due" t-value="(_mo and (_mo.date_deadline or _mo.date_finished))
|
||||
or (_line and _line.x_fc_part_deadline)
|
||||
or False"/>
|
||||
<!-- Inline the QR as base64 data URI so wkhtmltopdf doesn't need
|
||||
to fetch /report/barcode/ over the network during rendering. -->
|
||||
<t t-set="_qr_src" t-value="env['ir.actions.report'].barcode_data_uri(
|
||||
'QR', _scan_url, width=300, height=300)"/>
|
||||
|
||||
<style>
|
||||
@page { margin: 0; size: 152mm 102mm; }
|
||||
html, body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
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). ------------------- */
|
||||
.fp-sticker {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
color: #111;
|
||||
color: #000;
|
||||
position: absolute;
|
||||
top: 6px; left: 6px; right: 6px; bottom: 6px;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid #000;
|
||||
page-break-after: always;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
/* ---- HEADER band — grew to 40% to fit 2x WO# + logo + bigger QR. */
|
||||
.fp-sticker-head-wrap {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0;
|
||||
height: 40%;
|
||||
border-bottom: 2px solid #000;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
table.fp-sticker-head {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 3mm 4mm;
|
||||
box-sizing: border-box;
|
||||
border: 0.5mm solid #000;
|
||||
border-radius: 1.5mm;
|
||||
}
|
||||
.fp-sticker-header {
|
||||
display: table;
|
||||
width: 100%;
|
||||
border-bottom: 0.3mm solid #000;
|
||||
padding-bottom: 2mm;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
.fp-sticker-header-left {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 66%;
|
||||
padding-right: 2mm;
|
||||
}
|
||||
.fp-sticker-header-right {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 34%;
|
||||
text-align: right;
|
||||
}
|
||||
.fp-sticker-logo {
|
||||
max-height: 11mm;
|
||||
max-width: 100%;
|
||||
}
|
||||
.fp-sticker-wo {
|
||||
font-size: 15pt;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.3mm;
|
||||
margin-top: 1.5mm;
|
||||
}
|
||||
.fp-sticker-qr {
|
||||
width: 22mm;
|
||||
height: 22mm;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
.fp-sticker-qr-caption {
|
||||
font-size: 6pt;
|
||||
color: #666;
|
||||
text-align: right;
|
||||
margin-top: 0.3mm;
|
||||
letter-spacing: 0.1mm;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.fp-sticker-grid {
|
||||
display: table;
|
||||
width: 100%;
|
||||
font-size: 8.5pt;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.fp-sticker-row { display: table-row; }
|
||||
.fp-sticker-row-alt .fp-sticker-label,
|
||||
.fp-sticker-row-alt .fp-sticker-value {
|
||||
background-color: #f4f5f7;
|
||||
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;
|
||||
}
|
||||
.fp-sticker-label {
|
||||
display: table-cell;
|
||||
width: 28%;
|
||||
vertical-align: top;
|
||||
padding: 1mm 2mm 1mm 2mm;
|
||||
td.fp-sticker-head-right {
|
||||
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;
|
||||
}
|
||||
.fp-sticker-logo {
|
||||
/* Logo bumped 40% (116 → 162px height, 520 → 728px width). */
|
||||
max-height: 162px;
|
||||
max-width: 728px;
|
||||
display: block;
|
||||
}
|
||||
.fp-sticker-wo {
|
||||
font-size: 72pt;
|
||||
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. ---------------------- */
|
||||
.fp-sticker-qr-wrap {
|
||||
width: 380px;
|
||||
height: 380px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.fp-sticker-qr {
|
||||
width: 510px;
|
||||
height: 510px;
|
||||
position: absolute;
|
||||
top: -65px;
|
||||
left: -65px;
|
||||
margin: 0;
|
||||
display: block;
|
||||
}
|
||||
/* ---- BODY band (7 rows, each 14.28% of the band) ---- */
|
||||
.fp-sticker-body-wrap {
|
||||
position: absolute;
|
||||
left: 0; right: 0;
|
||||
top: 40%; bottom: 0;
|
||||
}
|
||||
table.fp-sticker-body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
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 td {
|
||||
vertical-align: middle;
|
||||
padding: 0 14px;
|
||||
font-size: 38pt;
|
||||
line-height: 1.1;
|
||||
}
|
||||
td.fp-sticker-label {
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.15mm;
|
||||
text-transform: uppercase;
|
||||
font-size: 7.5pt;
|
||||
color: #333;
|
||||
border-right: 0.3mm solid #000;
|
||||
white-space: nowrap;
|
||||
border-right: 2px solid #000;
|
||||
background-color: #f1f2f4;
|
||||
}
|
||||
.fp-sticker-value {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
padding: 1mm 2mm 1mm 2.5mm;
|
||||
}
|
||||
.fp-sticker-strong {
|
||||
font-weight: 700;
|
||||
font-size: 9.5pt;
|
||||
td.fp-sticker-value {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.fp-sticker-strong { font-weight: 700; }
|
||||
.fp-sticker-muted { color: #555; font-size: 28pt; }
|
||||
</style>
|
||||
|
||||
<div class="fp-sticker">
|
||||
<!-- Header — logo + WO number + QR -->
|
||||
<div class="fp-sticker-header">
|
||||
<div class="fp-sticker-header-left">
|
||||
<img t-if="env.company.logo"
|
||||
class="fp-sticker-logo"
|
||||
t-att-src="image_data_uri(env.company.logo)"/>
|
||||
<div class="fp-sticker-wo">
|
||||
WO #<span t-esc="_order_id"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fp-sticker-header-right">
|
||||
<!-- _scan_url is always base_url + '/fp/wo/<int>' —
|
||||
no characters that need URL encoding, so we can
|
||||
drop the quote() call (which isn't in the QWeb
|
||||
render context on every ir_actions_report path). -->
|
||||
<img class="fp-sticker-qr"
|
||||
t-att-src="'/report/barcode/?barcode_type=QR&value=' + _scan_url + '&width=300&height=300'"/>
|
||||
<div class="fp-sticker-qr-caption">scan to open</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Data grid -->
|
||||
<div class="fp-sticker-grid">
|
||||
<div class="fp-sticker-row">
|
||||
<div class="fp-sticker-label">PO (RO)</div>
|
||||
<div class="fp-sticker-value">
|
||||
<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="(_so and _so.x_fc_po_number) or '—'"/>
|
||||
t-esc="(_so and _so.x_fc_po_number) or '-'"/>
|
||||
<t t-if="_mo">
|
||||
<span style="color:#666;">
|
||||
<span class="fp-sticker-muted">
|
||||
(<span t-esc="_mo.name"/>)
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fp-sticker-row fp-sticker-row-alt">
|
||||
<div class="fp-sticker-label">Customer</div>
|
||||
<div class="fp-sticker-value">
|
||||
<span t-esc="(_so and _so.partner_id.name) or '—'"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fp-sticker-row">
|
||||
<div class="fp-sticker-label">Process</div>
|
||||
<div class="fp-sticker-value">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Customer:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<span t-esc="(_so and _so.partner_id.name) or '-'"/>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fp-sticker-row fp-sticker-row-alt">
|
||||
<div class="fp-sticker-label">Part Number</div>
|
||||
<div class="fp-sticker-value">
|
||||
<t t-else="">-</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Part Number:</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">
|
||||
<span style="color:#666;">
|
||||
<span class="fp-sticker-muted">
|
||||
Rev <span t-esc="_part.revision"/>
|
||||
</span>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">—</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fp-sticker-row">
|
||||
<div class="fp-sticker-label">Due Date</div>
|
||||
<div class="fp-sticker-value">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fp-sticker-row fp-sticker-row-alt">
|
||||
<div class="fp-sticker-label">Qty</div>
|
||||
<div class="fp-sticker-value">
|
||||
<t t-else="">-</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Qty:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<span class="fp-sticker-strong">
|
||||
<t t-set="_qty" t-value="_mo and _mo.product_qty or 0"/>
|
||||
<span t-esc="int(_qty) if _qty == int(_qty) else _qty"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fp-sticker-row">
|
||||
<div class="fp-sticker-label">Notes</div>
|
||||
<div class="fp-sticker-value"
|
||||
style="font-size: 7.5pt; color: #333;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fp-sticker-label">Notes:</td>
|
||||
<td class="fp-sticker-value">
|
||||
<t t-esc="(_so and _so.x_fc_internal_note
|
||||
and _so.x_fc_internal_note.striptags()[:140]) or '—'"/>
|
||||
</div>
|
||||
</div>
|
||||
and _so.x_fc_internal_note.striptags()[:100]) or '-'"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -237,13 +316,10 @@
|
||||
<template id="report_fp_wo_sticker">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="doc">
|
||||
<div class="page"
|
||||
style="padding:0; margin:0; width:100%; height:100%;">
|
||||
<t t-set="_order_id" t-value="doc.id"/>
|
||||
<t t-set="_scan_id" t-value="doc.id"/>
|
||||
<t t-set="_mo" t-value="doc.production_id"/>
|
||||
<t t-call="fusion_plating_reports.report_fp_wo_sticker_inner"/>
|
||||
</div>
|
||||
<t t-set="_order_id" t-value="doc.id"/>
|
||||
<t t-set="_scan_id" t-value="doc.id"/>
|
||||
<t t-set="_mo" t-value="doc.production_id"/>
|
||||
<t t-call="fusion_plating_reports.report_fp_wo_sticker_inner"/>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
@@ -252,18 +328,13 @@
|
||||
<template id="report_fp_mo_sticker">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="doc">
|
||||
<div class="page"
|
||||
style="padding:0; margin:0; width:100%; height:100%;">
|
||||
<!-- Print the MO's friendly name after "WO #" because
|
||||
the shop floor terminology is "WO" for the top-
|
||||
level order, regardless of Odoo's MO/WO split.
|
||||
The QR still encodes the numeric id so scanning
|
||||
resolves cleanly. -->
|
||||
<t t-set="_order_id" t-value="doc.name or doc.id"/>
|
||||
<t t-set="_scan_id" t-value="doc.id"/>
|
||||
<t t-set="_mo" t-value="doc"/>
|
||||
<t t-call="fusion_plating_reports.report_fp_wo_sticker_inner"/>
|
||||
</div>
|
||||
<!-- Shop floor talks in "WO #" regardless of Odoo's MO/WO
|
||||
split. QR always encodes the numeric id so scans
|
||||
resolve cleanly via /fp/wo/<id>. -->
|
||||
<t t-set="_order_id" t-value="doc.name or doc.id"/>
|
||||
<t t-set="_scan_id" t-value="doc.id"/>
|
||||
<t t-set="_mo" t-value="doc"/>
|
||||
<t t-call="fusion_plating_reports.report_fp_wo_sticker_inner"/>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user