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:
@@ -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>
|
||||
Reference in New Issue
Block a user