feat(reports): WO box sticker + QR-scan-to-WO endpoint

Client is migrating from Steelhead and needs to keep the small
parts-box sticker format the warehouse crew already knows. Two
pieces shipped together so scanning is seamless from day one:

1. report_fp_wo_sticker — 4x3" QWeb label bound to mrp.workorder.
   Layout mirrors the Steelhead sticker:
     * ENTECH logo top-left (via env.company.logo)
     * QR code top-right encoding /fp/wo/<id>
     * Grid: PO (RO) / Customer / Process / Part Number / Due
       Date / Qty / Notes
   Dedicated paperformat_fp_wo_sticker at 102x76mm, 300 DPI,
   landscape, 3mm margins — sized for thermal / inkjet label
   printers without shrink-to-fit.
   Binding added so "Print → WO Box Sticker" appears on every
   mrp.workorder record.

2. FpWoScanController — GET /fp/wo/<int:wo_id> redirects the
   scanner straight to the work-order form
   (/odoo/action-mrp.action_mrp_workorder/<id>). auth='user' so
   logged-in scanners land on the WO immediately; others bounce
   through Odoo's login and return to the same URL. No custom
   client work needed — any phone camera, handheld barcode
   scanner, or tablet browser opens the URL on scan.

Process row resolution chain: part.default_process_id →
coating.recipe_id → fallback. So the sticker prints whichever
process is actually going to drive WO generation for this line,
matching the direct-order wizard's Effective Process column.

fusion_plating_reports → 19.0.7.0.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-23 10:39:35 -04:00
parent eddf803d4c
commit be33a76ad2
6 changed files with 229 additions and 1 deletions

View File

@@ -315,6 +315,39 @@
<field name="paperformat_id" ref="paperformat_fp_a4_landscape"/>
</record>
<!-- ============================================================= -->
<!-- 14b. Box Sticker — 4x3" label for parts-box identification -->
<!-- Prints an ENTECH-style sticker with a QR code that -->
<!-- warehouse staff scan to jump straight to the WO form. -->
<!-- ============================================================= -->
<record id="paperformat_fp_wo_sticker" model="report.paperformat">
<field name="name">FP WO Sticker (4x3")</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="header_line" eval="False"/>
<field name="header_spacing">0</field>
<field name="disable_shrinking" eval="True"/>
<field name="dpi">300</field>
</record>
<record id="action_report_fp_wo_sticker" model="ir.actions.report">
<field name="name">WO Box Sticker</field>
<field name="model">mrp.workorder</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_wo_sticker</field>
<field name="report_file">fusion_plating_reports.report_fp_wo_sticker</field>
<field name="print_report_name">'WO Sticker - %s' % object.name</field>
<field name="binding_model_id" ref="mrp.model_mrp_workorder"/>
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_wo_sticker"/>
</record>
<!-- ============================================================= -->
<!-- 15. Packing Slip (Portrait + Landscape) -->
<!-- ============================================================= -->

View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
WO Box Sticker — 4x3" parts-box identification label.
Layout mirrors the Steelhead sticker format the client is
migrating from:
* ENTECH logo top-left.
* QR code top-right — encodes the WO's scan URL
(/fp/wo/<id>) so warehouse staff can scan with any
phone/tablet and land on the WO form instantly.
* Grid of PO / Customer / Process / Part Number / Due Date
/ Qty / Notes rows.
Printed on a dedicated 4x3" paperformat; no header / footer.
-->
<odoo>
<template id="report_fp_wo_sticker">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-set="_mo" t-value="doc.production_id"/>
<t t-set="_so" t-value="_mo and env['sale.order'].sudo().search(
[('name', '=', _mo.origin)], limit=1) or False"/>
<t t-set="_line" t-value="_so and _so.order_line[:1] or False"/>
<t t-set="_part" t-value="
(_line and _line.x_fc_part_catalog_id)
or (doc.production_id and 'x_fc_part_catalog_ids' in _mo._fields
and _mo.x_fc_sale_order_line_ids[:1].x_fc_part_catalog_id)
or False"/>
<t t-set="_coating" t-value="_line and _line.x_fc_coating_config_id or False"/>
<t t-set="_process" t-value="(_part and _part.default_process_id)
or (_coating and _coating.recipe_id)
or False"/>
<t t-set="_base_url" t-value="env['ir.config_parameter'].sudo().get_param('web.base.url', '')"/>
<t t-set="_scan_url" t-value="_base_url + '/fp/wo/' + str(doc.id)"/>
<div class="page" style="font-family: Arial, sans-serif;
color: #000; width: 100%; height: 100%; padding: 4mm;
box-sizing: border-box;">
<style>
.fp-stk-row { display: table-row; }
.fp-stk-lbl {
display: table-cell;
vertical-align: top;
font-weight: bold;
text-decoration: underline;
padding: 1mm 2mm 1mm 0;
white-space: nowrap;
}
.fp-stk-val {
display: table-cell;
vertical-align: top;
padding: 1mm 0 1mm 2mm;
border-left: 0.4mm solid #000;
}
</style>
<!-- Header: logo + WO # + QR -->
<div style="display: table; width: 100%;">
<!-- Logo + WO number -->
<div style="display: table-cell; vertical-align: top;
width: 65%; padding-right: 2mm;">
<img t-if="env.company.logo"
t-att-src="image_data_uri(env.company.logo)"
style="max-height: 14mm; max-width: 100%;"/>
<div style="font-size: 14pt; font-weight: bold;
margin-top: 2mm;">
WO #<span t-esc="doc.id"/>
</div>
</div>
<!-- QR code — linked to the scan redirect endpoint -->
<div style="display: table-cell; vertical-align: top;
width: 35%; text-align: right;">
<img t-att-src="'/report/barcode/?barcode_type=QR&amp;value=%s&amp;width=200&amp;height=200' % quote(_scan_url)"
style="width: 26mm; height: 26mm;"/>
</div>
</div>
<!-- Data grid -->
<div style="display: table; width: 100%;
margin-top: 3mm; font-size: 9pt;">
<div class="fp-stk-row">
<div class="fp-stk-lbl">PO (RO):</div>
<div class="fp-stk-val">
<t t-esc="(_so and _so.x_fc_po_number) or '—'"/>
<t t-if="_mo"> (<span t-esc="_mo.id"/>)</t>
</div>
</div>
<div class="fp-stk-row">
<div class="fp-stk-lbl">Customer:</div>
<div class="fp-stk-val">
<t t-esc="(_so and _so.partner_id.name) or '—'"/>
</div>
</div>
<div class="fp-stk-row">
<div class="fp-stk-lbl">Process:</div>
<div class="fp-stk-val">
<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-stk-row">
<div class="fp-stk-lbl">Part Number:</div>
<div class="fp-stk-val">
<t t-if="_part">
<span t-esc="_part.part_number"/>
<t t-if="_part.revision"> Rev <span t-esc="_part.revision"/></t>
</t>
<t t-else=""></t>
</div>
</div>
<div class="fp-stk-row">
<div class="fp-stk-lbl">Due Date:</div>
<div class="fp-stk-val">
<t t-set="_due" t-value="_mo and (_mo.date_deadline or _mo.date_finished) or False"/>
<t t-if="_due">
<span t-esc="_due.strftime('%m/%d/%Y')"/>
</t>
<t t-else=""></t>
</div>
</div>
<div class="fp-stk-row">
<div class="fp-stk-lbl">Qty:</div>
<div class="fp-stk-val">
<span t-esc="int(doc.qty_production) if doc.qty_production == int(doc.qty_production) else doc.qty_production"/>
</div>
</div>
<div class="fp-stk-row">
<div class="fp-stk-lbl">Notes:</div>
<div class="fp-stk-val">
<t t-esc="(_so and _so.x_fc_internal_note
and _so.x_fc_internal_note.striptags()[:120]) or ''"/>
</div>
</div>
</div>
</div>
</t>
</t>
</template>
</odoo>