diff --git a/fusion_plating/fusion_plating_jobs/tests/test_qty_received_propagation.py b/fusion_plating/fusion_plating_jobs/tests/test_qty_received_propagation.py index 4c2ea41e..6f58630f 100644 --- a/fusion_plating/fusion_plating_jobs/tests/test_qty_received_propagation.py +++ b/fusion_plating/fusion_plating_jobs/tests/test_qty_received_propagation.py @@ -57,9 +57,9 @@ class TestQtyReceivedPropagation(TransactionCase): """The bug: WO-30043 had qty_received=0 after receiving closed.""" so, job = self._make_so_with_job() recv = self._make_receiving(so, received_qty=5) - # Walk the state machine to closed. + # Walk the state machine to closed (draft → counted → closed + # after the 2026-05-20 `staged` retirement). recv.action_mark_counted() - recv.action_mark_staged() recv.action_close() # Reload — the hook fires inside _update_so_receiving_status. job.invalidate_recordset(['qty_received']) @@ -98,7 +98,6 @@ class TestQtyReceivedPropagation(TransactionCase): }) # Should NOT raise. recv.action_mark_counted() - recv.action_mark_staged() recv.action_close() def test_multi_part_so_matches_per_part(self): @@ -137,7 +136,6 @@ class TestQtyReceivedPropagation(TransactionCase): 'expected_qty': 7, 'received_qty': 7, }) recv.action_mark_counted() - recv.action_mark_staged() recv.action_close() job_a.invalidate_recordset(['qty_received']) job_b.invalidate_recordset(['qty_received']) diff --git a/fusion_plating/fusion_plating_receiving/__manifest__.py b/fusion_plating/fusion_plating_receiving/__manifest__.py index 032be7ce..7031c340 100644 --- a/fusion_plating/fusion_plating_receiving/__manifest__.py +++ b/fusion_plating/fusion_plating_receiving/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Receiving & Inspection', - 'version': '19.0.3.19.0', + 'version': '19.0.3.20.0', 'category': 'Manufacturing/Plating', 'summary': 'Parts receiving, inspection, damage logging, and manufacturing gate.', 'description': """ diff --git a/fusion_plating/fusion_plating_receiving/migrations/19.0.3.20.0/post-migrate.py b/fusion_plating/fusion_plating_receiving/migrations/19.0.3.20.0/post-migrate.py new file mode 100644 index 00000000..066a28cc --- /dev/null +++ b/fusion_plating/fusion_plating_receiving/migrations/19.0.3.20.0/post-migrate.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +# +# 2026-05-20 — `staged` state retirement. +# +# Drop `staged` from the active receiving state machine. The state had +# zero downstream effect (same SO mapping as counted), no captured +# data, and median dwell of 11 sec — pure ceremony between Counted +# and Closed. Any existing records currently sitting in `staged` get +# promoted to `closed` (they're already past the box-count step; +# closed is the next logical resting place). +# +# `staged` stays in the Selection as a (legacy) value so historical +# records that ever held it can still be read — we just don't write +# to it anymore. The view's statusbar_visible drops it. + +import logging + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + cr.execute(""" + UPDATE fp_receiving + SET state = 'closed' + WHERE state = 'staged' + """) + n = cr.rowcount + if n: + _logger.info( + '`staged` retirement: advanced %d receiving record(s) to ' + 'closed (state was dead ceremony, median dwell 11 sec).', + n, + ) + # Mirror the new state onto the linked sale orders so downstream + # gates (job step start, mark_done qty check, cert creation) + # see the right `received` status without waiting for the next + # state-transition action. + cr.execute(""" + UPDATE sale_order so + SET x_fc_receiving_status = 'received' + FROM fp_receiving r + WHERE r.sale_order_id = so.id + AND r.state = 'closed' + AND so.x_fc_receiving_status != 'received' + """) + if cr.rowcount: + _logger.info( + '`staged` retirement: synced %d sale_order.x_fc_receiving' + '_status to "received".', cr.rowcount, + ) diff --git a/fusion_plating/fusion_plating_receiving/models/fp_receiving.py b/fusion_plating/fusion_plating_receiving/models/fp_receiving.py index c9295f3e..6235add9 100644 --- a/fusion_plating/fusion_plating_receiving/models/fp_receiving.py +++ b/fusion_plating/fusion_plating_receiving/models/fp_receiving.py @@ -54,9 +54,14 @@ class FpReceiving(models.Model): [ ('draft', 'Awaiting Parts'), ('counted', 'Counted'), - ('staged', 'Staged for Racking'), ('closed', 'Closed'), - # Legacy values — kept readable, never written by new code + # Legacy values — kept readable, never written by new code. + # 2026-05-20: `staged` collapsed away. The state had zero + # downstream effect (same SO mapping as counted, no field + # captured, action_mark_staged just flipped a flag) and + # median dwell was 11 sec — pure ceremony. Pre-migrate + # advances any existing 'staged' record to 'closed'. + ('staged', 'Staged (legacy)'), ('inspecting', 'Inspecting (legacy)'), ('accepted', 'Accepted (legacy)'), ('discrepancy', 'Discrepancy (legacy)'), @@ -768,20 +773,33 @@ class FpReceiving(models.Model): ) % {'user': self.env.user.name, 'n': rec.box_count_in}) def action_mark_staged(self): - """Boxes are in the racking area, awaiting the racking crew.""" + """Deprecated 2026-05-20 — `staged` state was dead ceremony + (median dwell 11 sec, no captured data, no downstream effect). + Kept as a thin shim so any legacy button binding still works: + it advances counted records straight to closed. + """ for rec in self: - if rec.state not in ('counted',): - raise UserError(_('Only Counted records can be marked Staged.')) - rec.state = 'staged' - rec._update_so_receiving_status() - rec.message_post(body=_('Boxes staged for racking.')) + if rec.state != 'counted': + raise UserError(_( + 'Only Counted records can be closed. Stage-for-racking ' + 'is no longer a separate step.' + )) + rec.action_close() def action_close(self): - """Close the receiving — all boxes opened, inspection complete.""" + """Close the receiving — all boxes opened, inspection complete. + + 2026-05-20: now reachable directly from `counted` (the `staged` + intermediate was dropped). Legacy values 'staged' / 'accepted' + / 'resolved' still accepted so pre-Sub-8 records can be closed + without manual SQL surgery. + """ for rec in self: - if rec.state not in ('staged', 'accepted', 'resolved'): - raise UserError(_('Only Staged (or legacy Accepted / Resolved) ' - 'records can be closed.')) + if rec.state not in ('counted', 'staged', 'accepted', 'resolved'): + raise UserError(_( + 'Only Counted (or legacy Staged / Accepted / Resolved) ' + 'records can be closed.' + )) rec.state = 'closed' rec._update_so_receiving_status() rec.message_post(body=_('Receiving closed.')) @@ -859,14 +877,16 @@ class FpReceiving(models.Model): def _update_so_receiving_status(self): """Update the linked sale order's receiving status. - Sub 8 maps the new box-count-only states (`counted`, `staged`, - `closed`) onto the SO's `x_fc_receiving_status`: + Sub 8 + 2026-05-20 cleanup map the receiving states onto the + SO's `x_fc_receiving_status`: - draft -> not_received (no rows or just-created) - - counted / staged -> partial (boxes on dock, parts not yet - racked / inspected) - - closed -> received (all boxes opened, racking done) - Legacy states (inspecting / accepted / discrepancy / resolved) keep - their original mapping for back-compat with pre-Sub-8 records. + - counted -> partial (boxes on dock, + parts not yet racked) + - closed -> received (all boxes opened, + racking confirmed) + Legacy values (staged / inspecting / accepted / discrepancy / + resolved) keep their pre-Sub-8 / pre-cleanup mapping so + records that haven't been touched still resolve sanely. """ for rec in self: if not rec.sale_order_id: diff --git a/fusion_plating/fusion_plating_receiving/views/fp_receiving_views.xml b/fusion_plating/fusion_plating_receiving/views/fp_receiving_views.xml index 584b9ea2..c8b21e9a 100644 --- a/fusion_plating/fusion_plating_receiving/views/fp_receiving_views.xml +++ b/fusion_plating/fusion_plating_receiving/views/fp_receiving_views.xml @@ -40,21 +40,21 @@
- +