feat(jobs,shopfloor): smart buttons + QR scanner + NFC tank pages

Three connected operator-workflow features for entech.

A. fp.job smart buttons — count fields and action methods for sale
   order, steps, deliveries, invoices, payments, quality holds,
   certificates, time logs, and portal job. Each is an oe_stat_button
   that drills into the matching records, mirroring the sale.order
   pattern. Cross-module models are runtime-detected so the form
   stays clean when bridge modules are uninstalled.

B. Reusable QR scanner OWL component (`<QrScanner/>`) wired into the
   Manager Desk, Tablet Station, Plant Overview, and Process Tree
   headers. Click → modal with rear-camera stream (getUserMedia) +
   BarcodeDetector live decode → opens the matching fp.job form via
   the action service. Falls back to a manual URL paste box on
   browsers without BarcodeDetector. Works on iOS 17+ Safari and
   Android Chrome. Width uses `min(420px, 92vw)` wrapped in #{} so
   dart-sass passes it through verbatim instead of trying to compute
   incompatible units at compile time.

C. /fp/tank/<id> public-but-auth-required tank status page for NFC
   taps. Renders the tank's current step (in-progress / paused),
   queued ready steps, and most recent bath chemistry log (lines
   table) on a mobile-first page. URL-based so it works on iOS Safari
   without the Web NFC API — the operator taps the NFC tag, the URL
   opens in the default browser, the page auto-renders. New
   web.assets_frontend bundle entry pulls in the design tokens +
   tank_status.scss.

Manifest version bumps: jobs 19.0.5.0.0, shopfloor 19.0.16.0.0.
Tests: 44 pass (3 new smart-button assertions added).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-25 12:39:37 -04:00
parent 18b5918d3d
commit 74db636458
21 changed files with 1116 additions and 8 deletions

View File

@@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2026 Nexa Systems Inc. · License OPL-1
Fusion Plating — Tank Status (NFC tap-to-view) page
Rendered by /fp/tank/<id>. Mobile-first layout with big touch
targets so an operator can read the tank's current state from a
phone after tapping the NFC tag.
-->
<odoo>
<template id="tank_status_page">
<t t-call="web.frontend_layout">
<t t-set="head">
<meta name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"/>
</t>
<div class="o_fp_tank_status">
<header class="o_fp_tank_head">
<h1>
<i class="fa fa-flask"/>
<span t-esc="tank.name"/>
</h1>
<div class="o_fp_tank_meta">
<span t-if="tank.code"><strong>Code:</strong> <span t-esc="tank.code"/></span>
<span t-if="tank.current_bath_id"><strong>Bath:</strong> <span t-esc="tank.current_bath_id.name"/></span>
<span t-if="tank.facility_id"><strong>Facility:</strong> <span t-esc="tank.facility_id.name"/></span>
<span t-if="tank.work_center_id"><strong>Work Centre:</strong> <span t-esc="tank.work_center_id.name"/></span>
</div>
</header>
<section class="o_fp_tank_section o_fp_tank_section_active">
<h2>
<i t-if="active_step" class="fa fa-cog fa-spin"/>
<i t-if="not active_step" class="fa fa-circle-o"/>
Current Job
</h2>
<div t-if="active_step" class="o_fp_tank_card">
<div class="o_fp_tank_card_title">
<strong><span t-esc="active_step.job_id.name"/></strong>
<span class="o_fp_state_badge"
t-att-data-state="active_step.state">
<span t-esc="active_step.state"/>
</span>
</div>
<div class="o_fp_tank_card_meta">
<span>
<strong>Customer:</strong>
<span t-esc="active_step.job_id.partner_id.name"/>
</span>
<span>
<strong>Step:</strong>
<span t-esc="active_step.name"/>
</span>
<span t-if="active_step.assigned_user_id">
<strong>Operator:</strong>
<span t-esc="active_step.assigned_user_id.name"/>
</span>
<span t-if="active_step.duration_expected">
<strong>Expected:</strong>
<span t-esc="int(active_step.duration_expected)"/> min
</span>
<span t-if="active_step.thickness_target">
<strong>Target thickness:</strong>
<span t-esc="active_step.thickness_target"/>
<span t-esc="active_step.thickness_uom or ''"/>
</span>
<span t-if="active_step.date_started">
<strong>Started:</strong>
<span t-esc="active_step.date_started"/>
</span>
</div>
</div>
<div t-if="not active_step" class="o_fp_tank_empty">
Tank is idle.
</div>
</section>
<section class="o_fp_tank_section">
<h2><i class="fa fa-clock-o"/>Up Next</h2>
<div t-if="ready_steps" class="o_fp_tank_list">
<t t-foreach="ready_steps" t-as="step">
<div class="o_fp_tank_card o_fp_tank_card_compact">
<strong><span t-esc="step.job_id.name"/></strong>
<span class="o_fp_tank_card_sub">
<span t-esc="step.job_id.partner_id.name"/>
· <span t-esc="step.name"/>
<t t-if="step.duration_expected">
· <span t-esc="int(step.duration_expected)"/> min
</t>
</span>
</div>
</t>
</div>
<div t-if="not ready_steps" class="o_fp_tank_empty">
No queued work for this tank.
</div>
</section>
<section t-if="bath_log" class="o_fp_tank_section">
<h2><i class="fa fa-tint"/>Bath Chemistry</h2>
<div class="o_fp_tank_card">
<div class="o_fp_tank_card_meta">
<span>
<strong>Status:</strong>
<span class="o_fp_state_badge"
t-att-data-state="bath_log.status"
t-esc="bath_log.status or '—'"/>
</span>
<span t-if="bath_log.log_date">
<strong>Last sampled:</strong>
<span t-esc="bath_log.log_date"/>
</span>
<span t-if="bath_log.operator_id">
<strong>Sampled by:</strong>
<span t-esc="bath_log.operator_id.name"/>
</span>
</div>
<div t-if="bath_log.line_ids"
class="o_fp_tank_chem_grid">
<t t-foreach="bath_log.line_ids" t-as="line">
<div class="o_fp_tank_chem_cell"
t-att-data-status="line.status">
<div class="o_fp_tank_chem_label">
<span t-esc="line.parameter_id.name or line.parameter_code or '—'"/>
</div>
<div class="o_fp_tank_chem_value">
<span t-esc="line.value"/>
<small t-if="line.uom" t-esc="line.uom"/>
</div>
<div class="o_fp_tank_chem_range"
t-if="line.target_min or line.target_max">
target
<t t-if="line.target_min"><span t-esc="line.target_min"/></t>
<t t-if="line.target_min and line.target_max"> </t>
<t t-if="line.target_max"><span t-esc="line.target_max"/></t>
</div>
</div>
</t>
</div>
</div>
</section>
<footer class="o_fp_tank_foot">
<p>Tap the NFC tag again or scan a part-box QR for job details.</p>
</footer>
</div>
</t>
</template>
<template id="tank_status_not_found">
<t t-call="web.frontend_layout">
<div class="o_fp_tank_status">
<header class="o_fp_tank_head">
<h1>
<i class="fa fa-exclamation-triangle"/>
Tank not found
</h1>
</header>
<section class="o_fp_tank_section">
<p>No tank with id <strong t-esc="tank_id"/>.</p>
</section>
</div>
</t>
</template>
</odoo>