fix(shopfloor,reports): scanner status line + sticker rev cleanup

- Re-detect BarcodeDetector / window.jsQR at every modal open instead
  of only at component setup. Avoids the trap where a stale cached
  bundle reports "no decoder" even after a redeploy.
- Add a one-line status indicator at the top of the scan modal showing
  exactly which decoder is active ("Decoder: native" / "Decoder: jsqr"
  / "Decoder: none — paste URL below"). Lets the operator see at a
  glance whether scanning is even possible without round-tripping
  through Safari Web Inspector.
- Sticker: strip a leading "Rev " (case-insensitive) from
  fp.part.catalog.revision before printing so values like "Rev 1"
  don't render as "Rev Rev 1".

Versions: shopfloor 19.0.18 -> 19.0.19, reports 19.0.7.16 -> 19.0.7.17.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-25 13:30:40 -04:00
parent b93633d728
commit 9fe7855fc3
7 changed files with 55 additions and 11 deletions

View File

@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0) # License OPL-1 (Odoo Proprietary License v1.0)
{ {
'name': 'Fusion Plating — Reports', 'name': 'Fusion Plating — Reports',
'version': '19.0.7.16.0', 'version': '19.0.7.17.0',
'category': 'Manufacturing/Plating', 'category': 'Manufacturing/Plating',
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.', 'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
'depends': [ 'depends': [

View File

@@ -306,8 +306,17 @@
<span class="fp-sticker-strong" <span class="fp-sticker-strong"
t-esc="_part.part_number"/> t-esc="_part.part_number"/>
<t t-if="_part.revision"> <t t-if="_part.revision">
<!-- Some parts store the revision with a
"Rev " prefix already (e.g. "Rev 1"),
others store just the value ("1", "A").
Strip a leading "Rev " (case insensitive)
so we don't print "Rev Rev 1". -->
<t t-set="_rev_clean" t-value="_part.revision.strip()"/>
<t t-if="_rev_clean.lower().startswith('rev ')">
<t t-set="_rev_clean" t-value="_rev_clean[4:].strip()"/>
</t>
<span class="fp-sticker-muted"> <span class="fp-sticker-muted">
Rev <span t-esc="_part.revision"/> Rev <span t-esc="_rev_clean"/>
</span> </span>
</t> </t>
</t> </t>

View File

@@ -5,7 +5,7 @@
{ {
'name': 'Fusion Plating — Shop Floor', 'name': 'Fusion Plating — Shop Floor',
'version': '19.0.18.0.0', 'version': '19.0.19.0.0',
'category': 'Manufacturing/Plating', 'category': 'Manufacturing/Plating',
'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer, ' 'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer, '
'first-piece inspection gates.', 'first-piece inspection gates.',

View File

@@ -44,18 +44,17 @@ export class QrScanner extends Component {
setup() { setup() {
this.notification = useService("notification"); this.notification = useService("notification");
this.videoRef = useRef("video"); this.videoRef = useRef("video");
const hasNative = typeof BarcodeDetector !== "undefined";
const hasJsQR = typeof window !== "undefined" && typeof window.jsQR === "function";
this.state = useState({ this.state = useState({
open: false, open: false,
error: null, error: null,
manualUrl: "", manualUrl: "",
detected: "", // last decoded value (for user feedback) detected: "", // last decoded value (for user feedback)
// True whenever ANY decoder (native or jsQR) is available. // canScan / decoder are recomputed in open() — don't trust
// Drives the template: when true we show the camera <video>; // setup-time values because vendored libs may attach to
// when false we fall through to the manual paste UI only. // window asynchronously after the bundle finishes parsing.
canScan: hasNative || hasJsQR, canScan: false,
decoder: hasNative ? "native" : (hasJsQR ? "jsqr" : "none"), decoder: "none",
statusLine: "", // visible diagnostic shown in modal
}); });
this.stream = null; this.stream = null;
this.decodeLoopActive = false; this.decodeLoopActive = false;
@@ -68,9 +67,33 @@ export class QrScanner extends Component {
onWillUnmount(() => this._stopCamera()); onWillUnmount(() => this._stopCamera());
} }
/**
* Check what decoder is available right now and update state. Run
* at every open() — not just setup() — because a stale bundle in
* the browser cache can flip results between page loads.
*/
_detectCapabilities() {
const hasNative = typeof BarcodeDetector !== "undefined";
const hasJsQR = typeof window !== "undefined" && typeof window.jsQR === "function";
this.state.canScan = hasNative || hasJsQR;
this.state.decoder = hasNative ? "native" : (hasJsQR ? "jsqr" : "none");
// Build a one-line status the user can read in the modal so
// it's obvious whether the decoder loaded. Helps diagnose
// "nothing happens" reports without round-tripping through
// Safari Web Inspector.
this.state.statusLine = (
"Decoder: " + this.state.decoder +
(hasNative ? " (native)" : "") +
(!hasNative && hasJsQR ? " (jsQR)" : "") +
(!this.state.canScan ? " — paste URL below" : "")
);
}
async open() { async open() {
this._detectCapabilities();
this.state.open = true; this.state.open = true;
this.state.error = null; this.state.error = null;
this.state.detected = "";
await this._startCamera(); await this._startCamera();
} }

View File

@@ -66,7 +66,8 @@
.o_fp_qr_error, .o_fp_qr_error,
.o_fp_qr_warn, .o_fp_qr_warn,
.o_fp_qr_detected { .o_fp_qr_detected,
.o_fp_qr_status {
padding: $fp-space-2 $fp-space-3; padding: $fp-space-2 $fp-space-3;
border-radius: $fp-radius-sm; border-radius: $fp-radius-sm;
background: $fp-card-soft; background: $fp-card-soft;
@@ -75,6 +76,13 @@
word-break: break-all; word-break: break-all;
} }
.o_fp_qr_status {
border-left: 3px solid $fp-accent;
color: $fp-ink;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 11px;
}
.o_fp_qr_error { .o_fp_qr_error {
border-left: 3px solid $fp-bad; border-left: 3px solid $fp-bad;
} }

View File

@@ -25,6 +25,10 @@
</button> </button>
</div> </div>
<div class="o_fp_qr_modal_body"> <div class="o_fp_qr_modal_body">
<div t-if="state.statusLine" class="o_fp_qr_status">
<i class="fa fa-info-circle me-1"/>
<span t-esc="state.statusLine"/>
</div>
<div t-if="!state.canScan and !state.error" <div t-if="!state.canScan and !state.error"
class="o_fp_qr_warn"> class="o_fp_qr_warn">
Live decoding isn't supported in this browser. Live decoding isn't supported in this browser.