feat(jobs): Phase 5 — fp.job reports (sticker + traveller)
Two parallel report definitions for the native job model: 1. Job Sticker (6x4 inch custom paperformat) bound to fp.job. Prints WH/JOB/... ID, customer, SO, qty, due date, recipe, step progress. QR encodes /fp/job/<id> for scan-to-job navigation. 2. Job Traveller bound to fp.job, A4 portrait. Job header + all fp.job.step rows in sequence order with operator sign-off column. Coexists with fusion_plating_reports' MO/WO bindings — both print menus stay live during migration. Deferred reports (use existing during migration; rebind at cutover): - BoL, Packing Slip, Invoice (read from SO, no fp.job change needed) - WO Margin (cost rollup; rebuild against fp.job.step.cost_total in phase-end polish) Adds fusion_plating_reports to fusion_plating_jobs depends. Tests deferred to post-Tailscale-restore: 3 new tests verify report actions are registered + sticker template renders without QWeb errors. Module file content verified locally as well-formed XML. Manifest 19.0.1.7.0 → 19.0.1.8.0. Part of: native job model migration (spec 2026-04-25) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
3
fusion_plating/fusion_plating_jobs/report/__init__.py
Normal file
3
fusion_plating/fusion_plating_jobs/report/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2026 Nexa Systems Inc.
|
||||
License OPL-1 (Odoo Proprietary License v1.0)
|
||||
|
||||
Native fp.job sticker — parallel to fusion_plating_reports' WO Box
|
||||
Sticker which binds to mrp.production/mrp.workorder. Coexists during
|
||||
the migration period.
|
||||
|
||||
QR encodes /fp/job/<id> (controller added in Task TBD; for now scan
|
||||
will fall back to /web/login if controller absent).
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<record id="paperformat_fp_job_sticker" model="report.paperformat">
|
||||
<field name="name">FP Job Sticker (6x4")</field>
|
||||
<field name="format">custom</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"/>
|
||||
<field name="dpi">300</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_fp_job_sticker" model="ir.actions.report">
|
||||
<field name="name">Job Sticker</field>
|
||||
<field name="model">fp.job</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">fusion_plating_jobs.report_fp_job_sticker_template</field>
|
||||
<field name="report_file">fusion_plating_jobs.report_fp_job_sticker_template</field>
|
||||
<field name="print_report_name">'Job Sticker - %s' % (object.name or '').replace('/', '-')</field>
|
||||
<field name="binding_model_id" ref="fusion_plating.model_fp_job"/>
|
||||
<field name="binding_type">report</field>
|
||||
<field name="paperformat_id" ref="paperformat_fp_job_sticker"/>
|
||||
</record>
|
||||
|
||||
<template id="report_fp_job_sticker_template">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="job">
|
||||
<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/job/' + str(job.id)"/>
|
||||
<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; }
|
||||
.fp-job-sticker {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
color: #000;
|
||||
position: absolute;
|
||||
top: 4px; left: 4px; right: 4px; bottom: 4px;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid #000;
|
||||
}
|
||||
.fp-job-sticker-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
border-bottom: 1.5px solid #000;
|
||||
padding-bottom: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.fp-job-sticker-id {
|
||||
font-size: 36pt;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
.fp-job-sticker-qr { width: 32mm; height: 32mm; display: block; }
|
||||
.fp-job-sticker-row { padding: 3px 0; font-size: 14pt; }
|
||||
.fp-job-sticker-label { font-weight: 700; display: inline-block; min-width: 30mm; }
|
||||
</style>
|
||||
<div class="fp-job-sticker">
|
||||
<div class="fp-job-sticker-head">
|
||||
<div>
|
||||
<div style="font-size: 10pt; letter-spacing: 0.5mm; text-transform: uppercase;">Plating Job</div>
|
||||
<div class="fp-job-sticker-id">
|
||||
<span t-esc="job.name"/>
|
||||
</div>
|
||||
</div>
|
||||
<img class="fp-job-sticker-qr" t-if="_qr_src" t-att-src="_qr_src"/>
|
||||
</div>
|
||||
<div class="fp-job-sticker-row">
|
||||
<span class="fp-job-sticker-label">Customer:</span>
|
||||
<span t-esc="job.partner_id.name"/>
|
||||
</div>
|
||||
<div class="fp-job-sticker-row" t-if="job.sale_order_id">
|
||||
<span class="fp-job-sticker-label">SO:</span>
|
||||
<span t-esc="job.sale_order_id.name"/>
|
||||
</div>
|
||||
<div class="fp-job-sticker-row">
|
||||
<span class="fp-job-sticker-label">Qty:</span>
|
||||
<strong><span t-esc="int(job.qty) if job.qty == int(job.qty) else job.qty"/></strong>
|
||||
</div>
|
||||
<div class="fp-job-sticker-row" t-if="job.date_deadline">
|
||||
<span class="fp-job-sticker-label">Due:</span>
|
||||
<span t-esc="job.date_deadline.strftime('%b %d, %Y')"/>
|
||||
</div>
|
||||
<div class="fp-job-sticker-row" t-if="job.recipe_id">
|
||||
<span class="fp-job-sticker-label">Recipe:</span>
|
||||
<span t-esc="job.recipe_id.name"/>
|
||||
</div>
|
||||
<div class="fp-job-sticker-row">
|
||||
<span class="fp-job-sticker-label">Steps:</span>
|
||||
<span t-esc="job.step_done_count"/> / <span t-esc="job.step_count"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2026 Nexa Systems Inc.
|
||||
License OPL-1 (Odoo Proprietary License v1.0)
|
||||
|
||||
Native fp.job traveller — minimal portrait A4 listing all steps.
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<record id="action_report_fp_job_traveller" model="ir.actions.report">
|
||||
<field name="name">Job Traveller</field>
|
||||
<field name="model">fp.job</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">fusion_plating_jobs.report_fp_job_traveller_template</field>
|
||||
<field name="report_file">fusion_plating_jobs.report_fp_job_traveller_template</field>
|
||||
<field name="print_report_name">'Traveller - %s' % (object.name or '').replace('/', '-')</field>
|
||||
<field name="binding_model_id" ref="fusion_plating.model_fp_job"/>
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
<template id="report_fp_job_traveller_template">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="job">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<h1>Job Traveller — <span t-esc="job.name"/></h1>
|
||||
<table class="table table-sm" style="margin-top: 1em;">
|
||||
<tr><th>Customer</th><td><span t-esc="job.partner_id.name"/></td></tr>
|
||||
<tr><th>SO</th><td><span t-esc="job.sale_order_id.name or '-'"/></td></tr>
|
||||
<tr><th>Qty</th><td><span t-esc="job.qty"/></td></tr>
|
||||
<tr><th>Recipe</th><td><span t-esc="job.recipe_id.name or '-'"/></td></tr>
|
||||
<tr><th>Deadline</th><td><span t-esc="job.date_deadline and job.date_deadline.strftime('%b %d, %Y') or '-'"/></td></tr>
|
||||
<tr><th>Status</th><td><span t-esc="job.state"/></td></tr>
|
||||
</table>
|
||||
|
||||
<h2 style="margin-top: 2em;">Steps</h2>
|
||||
<table class="table table-sm table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Operation</th>
|
||||
<th>Work Centre</th>
|
||||
<th>Kind</th>
|
||||
<th>Expected (min)</th>
|
||||
<th>Actual (min)</th>
|
||||
<th>State</th>
|
||||
<th>Operator Sign-off</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="job.step_ids.sorted('sequence')" t-as="step">
|
||||
<tr>
|
||||
<td><span t-esc="step.sequence"/></td>
|
||||
<td><span t-esc="step.name"/></td>
|
||||
<td><span t-esc="step.work_centre_id.name or ''"/></td>
|
||||
<td><span t-esc="step.kind"/></td>
|
||||
<td><span t-esc="step.duration_expected"/></td>
|
||||
<td><span t-esc="step.duration_actual"/></td>
|
||||
<td><span t-esc="step.state"/></td>
|
||||
<td style="border-bottom: 1px solid #999; min-width: 100px;"></td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user