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)
{
'name': 'Fusion Plating — Reports',
'version': '19.0.7.16.0',
'version': '19.0.7.17.0',
'category': 'Manufacturing/Plating',
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
'depends': [

View File

@@ -306,8 +306,17 @@
<span class="fp-sticker-strong"
t-esc="_part.part_number"/>
<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">
Rev <span t-esc="_part.revision"/>
Rev <span t-esc="_rev_clean"/>
</span>
</t>
</t>

View File

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

View File

@@ -44,18 +44,17 @@ export class QrScanner extends Component {
setup() {
this.notification = useService("notification");
this.videoRef = useRef("video");
const hasNative = typeof BarcodeDetector !== "undefined";
const hasJsQR = typeof window !== "undefined" && typeof window.jsQR === "function";
this.state = useState({
open: false,
error: null,
manualUrl: "",
detected: "", // last decoded value (for user feedback)
// True whenever ANY decoder (native or jsQR) is available.
// Drives the template: when true we show the camera <video>;
// when false we fall through to the manual paste UI only.
canScan: hasNative || hasJsQR,
decoder: hasNative ? "native" : (hasJsQR ? "jsqr" : "none"),
// canScan / decoder are recomputed in open() — don't trust
// setup-time values because vendored libs may attach to
// window asynchronously after the bundle finishes parsing.
canScan: false,
decoder: "none",
statusLine: "", // visible diagnostic shown in modal
});
this.stream = null;
this.decodeLoopActive = false;
@@ -68,9 +67,33 @@ export class QrScanner extends Component {
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() {
this._detectCapabilities();
this.state.open = true;
this.state.error = null;
this.state.detected = "";
await this._startCamera();
}

View File

@@ -66,7 +66,8 @@
.o_fp_qr_error,
.o_fp_qr_warn,
.o_fp_qr_detected {
.o_fp_qr_detected,
.o_fp_qr_status {
padding: $fp-space-2 $fp-space-3;
border-radius: $fp-radius-sm;
background: $fp-card-soft;
@@ -75,6 +76,13 @@
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 {
border-left: 3px solid $fp-bad;
}

View File

@@ -25,6 +25,10 @@
</button>
</div>
<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"
class="o_fp_qr_warn">
Live decoding isn't supported in this browser.