From 270f427d7f332fe6eff2a7b104ce4bac6ba1eea4 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Mon, 27 Apr 2026 21:14:30 -0400 Subject: [PATCH] feat(sub12b): Move Rack + Stop Timer OWL dialogs (Task 13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move Rack: rack name in title via getter, tag chips, batches list (read-only), Type + To Node + To Station picker. Atomic Save commits all batches via /fp/tablet/move_rack/commit. Stop Timer: opens with state already at 'stopped' (server flipped on load via /labor_timer/stop), pre-fills billed_* from accrued. Operator edits → Save (state → reconciled). Save & Start New Timer chains into a fresh timer for the same step via the start_new=True flag — mirrors screen 10's right-most button. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../static/src/js/move_rack_dialog.js | 78 ++++++++++++++++ .../static/src/js/stop_timer_dialog.js | 92 +++++++++++++++++++ .../static/src/xml/move_rack_dialog.xml | 76 +++++++++++++++ .../static/src/xml/stop_timer_dialog.xml | 53 +++++++++++ 4 files changed, 299 insertions(+) create mode 100644 fusion_plating/fusion_plating_shopfloor/static/src/js/move_rack_dialog.js create mode 100644 fusion_plating/fusion_plating_shopfloor/static/src/js/stop_timer_dialog.js create mode 100644 fusion_plating/fusion_plating_shopfloor/static/src/xml/move_rack_dialog.xml create mode 100644 fusion_plating/fusion_plating_shopfloor/static/src/xml/stop_timer_dialog.xml diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/js/move_rack_dialog.js b/fusion_plating/fusion_plating_shopfloor/static/src/js/move_rack_dialog.js new file mode 100644 index 00000000..fd653b60 --- /dev/null +++ b/fusion_plating/fusion_plating_shopfloor/static/src/js/move_rack_dialog.js @@ -0,0 +1,78 @@ +/** @odoo-module */ +/* + * Sub 12b — Move Rack dialog (OWL). + * + * Mirrors screens 11, 13, 14. Same shape as Move Parts but no + * transition prompts (rack moves are rack-level). Title carries + * rack name; parts list (read-only) shows all batches on the rack. + */ + +import { Component, onWillStart, useState } from "@odoo/owl"; +import { Dialog } from "@web/core/dialog/dialog"; +import { rpc } from "@web/core/network/rpc"; +import { useService } from "@web/core/utils/hooks"; +import { _t } from "@web/core/l10n/translation"; + + +export class FpMoveRackDialog extends Component { + static template = "fusion_plating_shopfloor.FpMoveRackDialog"; + static components = { Dialog }; + static props = ["rackId", "toStepId", "onCommit?", "close"]; + + setup() { + this.notification = useService("notification"); + this.state = useState({ + loading: true, + rack: { tag_ids: [] }, + batches: [], + toStep: { tank_options: [] }, + transferType: "step", + toTankId: false, + saving: false, + }); + onWillStart(async () => { + const data = await rpc("/fp/tablet/move_rack/preview", { + rack_id: this.props.rackId, + to_step_id: this.props.toStepId, + }); + if (!data.ok) { + this.notification.add(data.error || _t("Preview failed"), + { type: "danger" }); + this.props.close(); + return; + } + this.state.rack = data.rack; + this.state.batches = data.batches; + this.state.toStep = data.to_step; + const opts = data.to_step.tank_options || []; + this.state.toTankId = opts.length ? opts[0].id : false; + this.state.loading = false; + }); + } + + get title() { + return `Move Rack: ${this.state.rack.name || ""}`; + } + + async onSave() { + this.state.saving = true; + const result = await rpc("/fp/tablet/move_rack/commit", { + rack_id: this.props.rackId, + to_step_id: this.props.toStepId, + transfer_type: this.state.transferType, + to_tank_id: this.state.toTankId || false, + }); + if (result.ok) { + this.notification.add( + _t("Moved %s batches", result.count), + { type: "success" }); + if (this.props.onCommit) { + this.props.onCommit(result); + } + this.props.close(); + } else { + this.notification.add(result.error, { type: "danger" }); + this.state.saving = false; + } + } +} diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/js/stop_timer_dialog.js b/fusion_plating/fusion_plating_shopfloor/static/src/js/stop_timer_dialog.js new file mode 100644 index 00000000..d5fab283 --- /dev/null +++ b/fusion_plating/fusion_plating_shopfloor/static/src/js/stop_timer_dialog.js @@ -0,0 +1,92 @@ +/** @odoo-module */ +/* + * Sub 12b — Stop User Labor Timer dialog (OWL). + * + * Mirrors screen 10. Opens with state already at 'stopped' (server-side + * flip on /labor_timer/stop), pre-fills billed_* from accrued. Operator + * edits → Save (state → reconciled). Save & Start New Timer chains + * into a fresh timer for the same step. + */ + +import { Component, onWillStart, useState } from "@odoo/owl"; +import { Dialog } from "@web/core/dialog/dialog"; +import { rpc } from "@web/core/network/rpc"; +import { useService } from "@web/core/utils/hooks"; +import { _t } from "@web/core/l10n/translation"; + + +export class FpStopTimerDialog extends Component { + static template = "fusion_plating_shopfloor.FpStopTimerDialog"; + static components = { Dialog }; + static props = ["timerId", "onReconciled?", "close"]; + + setup() { + this.notification = useService("notification"); + this.state = useState({ + loading: true, + accruedSeconds: 0, + billedHrs: 0, + billedMin: 0, + billedSec: 0, + notes: "", + saving: false, + }); + onWillStart(async () => { + const data = await rpc("/fp/tablet/labor_timer/stop", + { timer_id: this.props.timerId }); + if (data.ok) { + this.state.accruedSeconds = data.accrued_seconds; + this.state.billedHrs = data.billed_hrs; + this.state.billedMin = data.billed_min; + this.state.billedSec = data.billed_sec; + this.state.loading = false; + } else { + this.notification.add(data.error || _t("Stop failed"), + { type: "danger" }); + this.props.close(); + } + }); + } + + get billedPct() { + const total = this.state.billedHrs * 3600 + + this.state.billedMin * 60 + + this.state.billedSec; + if (!this.state.accruedSeconds) return 0; + return Math.round(100 * total / this.state.accruedSeconds); + } + + get accruedDisplay() { + const s = this.state.accruedSeconds; + const h = Math.floor(s / 3600); + const m = Math.floor((s % 3600) / 60); + const sec = s % 60; + return `${h}h ${m}m ${sec}s`; + } + + async commit(startNew) { + this.state.saving = true; + const result = await rpc("/fp/tablet/labor_timer/reconcile", { + timer_id: this.props.timerId, + billed_hrs: this.state.billedHrs, + billed_min: this.state.billedMin, + billed_sec: this.state.billedSec, + notes: this.state.notes, + start_new: startNew, + }); + if (result.ok) { + this.notification.add(_t("Timer reconciled"), + { type: "success" }); + if (this.props.onReconciled) { + this.props.onReconciled(result); + } + this.props.close(); + } else { + this.notification.add(result.error, { type: "danger" }); + this.state.saving = false; + } + } + + onSave() { return this.commit(false); } + onSaveAndStartNew() { return this.commit(true); } +} diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/xml/move_rack_dialog.xml b/fusion_plating/fusion_plating_shopfloor/static/src/xml/move_rack_dialog.xml new file mode 100644 index 00000000..67680e4a --- /dev/null +++ b/fusion_plating/fusion_plating_shopfloor/static/src/xml/move_rack_dialog.xml @@ -0,0 +1,76 @@ + + + + + +
+ +
+ +
+ + + +
+ +
+ +
+ +
    + +
  • + + on WO +
  • +
    +
+ +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+
+ +
Loading…
+ + + + + +
+
+ +
diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/xml/stop_timer_dialog.xml b/fusion_plating/fusion_plating_shopfloor/static/src/xml/stop_timer_dialog.xml new file mode 100644 index 00000000..159a8462 --- /dev/null +++ b/fusion_plating/fusion_plating_shopfloor/static/src/xml/stop_timer_dialog.xml @@ -0,0 +1,53 @@ + + + + + +
+ +
+ Accrued: + · Billed: % +
+ +
+ +
+ hrs + min + sec +
+ +
+ +
+ +