diff --git a/fusion_plating/fusion_plating_shopfloor/__manifest__.py b/fusion_plating/fusion_plating_shopfloor/__manifest__.py index 3fa26c47..36d6eac3 100644 --- a/fusion_plating/fusion_plating_shopfloor/__manifest__.py +++ b/fusion_plating/fusion_plating_shopfloor/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating โ€” Shop Floor', - 'version': '19.0.36.1.0', + 'version': '19.0.36.1.1', 'category': 'Manufacturing/Plating', 'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer.', 'description': """ diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/js/job_workspace.js b/fusion_plating/fusion_plating_shopfloor/static/src/js/job_workspace.js index 56342341..e55595f1 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/js/job_workspace.js +++ b/fusion_plating/fusion_plating_shopfloor/static/src/js/job_workspace.js @@ -50,6 +50,9 @@ export class FpJobWorkspace extends Component { // the active-step timer re-renders without an RPC. The template // reads tickNow and re-runs formatActiveStepElapsed each second. tickNow: Date.now(), + // Shipping panel input buffer (carrier/service/weight). Seeded + // lazily from the payload defaults inside the handlers below. + shipForm: {}, }); onMounted(async () => { @@ -611,6 +614,85 @@ export class FpJobWorkspace extends Component { this.notification.add(err.message, { type: "danger" }); } } + + // ---- Shipping handlers (tablet receiving+shipping 2026-05-29) ---------- + // All coercion is JS-side (CLAUDE.md Rule 20 โ€” templates only expose Math). + onShipInput(field, ev) { + const raw = ev.target.value; + this.state.shipForm[field] = + field === "weight" ? (parseFloat(raw) || 0) : raw; + } + + async onGenerateLabel() { + const sh = (this.state.data && this.state.data.shipping) || {}; + const f = this.state.shipForm || {}; + const weight = f.weight != null ? f.weight : (sh.weight || 0); + const serviceType = f.service_type != null ? f.service_type : (sh.service_type || ""); + const carrierRaw = f.carrier_id != null ? f.carrier_id : (sh.carrier_id || false); + const carrierId = carrierRaw ? parseInt(carrierRaw, 10) : false; + if (!weight || weight <= 0) { + this.notification.add("Enter a non-zero weight before generating the label.", { type: "danger" }); + return; + } + if (!carrierId) { + this.notification.add("Pick an outbound carrier first.", { type: "danger" }); + return; + } + try { + const res = await rpc("/fp/workspace/generate_label", { + job_id: this.state.jobId, + weight: weight, + service_type: serviceType, + carrier_id: carrierId, + }); + if (res && res.ok) { + this.notification.add( + "Label generated โ€” tracking " + (res.tracking_number || "n/a"), + { type: "success" }, + ); + await this.refresh(); + } else { + this.notification.add((res && res.error) || "Label generation failed", { type: "danger" }); + } + } catch (err) { + this.notification.add(err.message || String(err), { type: "danger" }); + } + } + + async onViewLabel() { + const sh = (this.state.data && this.state.data.shipping) || {}; + if (!sh.label_attachment_id) return; + try { + // Route through fusion_pdf_preview (CLAUDE.md PDF-preview rule). + const action = await rpc("/web/dataset/call_kw", { + model: "ir.attachment", + method: "action_fusion_preview", + args: [[sh.label_attachment_id]], + kwargs: { title: "Shipping Label" }, + }); + if (action) await this.action.doAction(action); + } catch (err) { + // Fallback: plain content open if the preview helper is absent. + window.open("/web/content/" + sh.label_attachment_id, "_blank"); + } + } + + async onMarkShipped() { + try { + const res = await rpc("/fp/workspace/mark_shipped", { job_id: this.state.jobId }); + if (res && res.ok) { + this.notification.add( + "Marked shipped: " + ((res.shipped || []).join(", ") || "done"), + { type: "success" }, + ); + await this.refresh(); + } else { + this.notification.add((res && res.error) || "Mark shipped failed", { type: "danger" }); + } + } catch (err) { + this.notification.add(err.message || String(err), { type: "danger" }); + } + } } registry.category("actions").add("fp_job_workspace", FpJobWorkspace); diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/scss/job_workspace.scss b/fusion_plating/fusion_plating_shopfloor/static/src/scss/job_workspace.scss index bc8ad708..7fcad9a5 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/scss/job_workspace.scss +++ b/fusion_plating/fusion_plating_shopfloor/static/src/scss/job_workspace.scss @@ -385,6 +385,69 @@ $_ws-text-hex: #1d1d1f; flex-wrap: wrap; } +// ============================================================================= +// SHIPPING PANEL (tablet receiving+shipping 2026-05-29) +// ============================================================================= +// Reuses the workspace card tokens ($_ws-card-hex/$_ws-border-hex are +// already dark-aware via the @if branch near the top of this file), so the +// surface adapts to dark mode automatically. Only the blue left-accent is a +// raw hex, and it gets its own dark override below (CLAUDE.md dark-mode rule). + +.o_fp_ws_ship { + background: $_ws-card-hex; + border: 1px solid $_ws-border-hex; + border-left: 4px solid #0071e3; + border-radius: 8px; + padding: 0.7rem 0.85rem; + margin-bottom: 0.7rem; + + .o_fp_ws_ship_head { + display: flex; + align-items: center; + gap: 0.5rem; + font-weight: 600; + margin-bottom: 0.5rem; + } + .o_fp_ws_ship_icon { font-size: 1.2rem; } + .o_fp_ws_ship_title { font-size: 1rem; } + + .o_fp_ws_ship_waiting { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.4rem; + font-size: 0.85rem; + color: var(--text-secondary, #777); + } + + .o_fp_ws_ship_fields { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-bottom: 0.6rem; + + label { + display: flex; + flex-direction: column; + font-size: 0.75rem; + font-weight: 500; + min-width: 150px; + flex: 1; + } + .form-select, .form-control { margin-top: 0.2rem; } + } + + .o_fp_ws_ship_actions { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + } +} + +@if $o-webclient-color-scheme == dark { + .o_fp_ws_ship { border-left-color: #6cb6ff; } +} + // ============================================================================= // PRE-RECIPE RECEIVING CARD (Spec C1+C2 2026-05-24) // ============================================================================= diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/xml/job_workspace.xml b/fusion_plating/fusion_plating_shopfloor/static/src/xml/job_workspace.xml index 9bfdf88a..86b7cb99 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/xml/job_workspace.xml +++ b/fusion_plating/fusion_plating_shopfloor/static/src/xml/job_workspace.xml @@ -213,6 +213,83 @@ + +
+
+ ๐Ÿšš + Ship Order + + Label ready ยท + +
+ + +
+ Waiting on: + + + โ€” + + +
+ + + +
+ + + +
+
+ + + +
+
+
+
Recipe not generated for this WO.