From dcd6df71c03198ee8ab197edb59e790aa6bd74bf Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Mon, 27 Apr 2026 21:07:19 -0400 Subject: [PATCH] =?UTF-8?q?feat(sub12b):=20fp.job.step=20+=20fp.job=20?= =?UTF-8?q?=E2=80=94=20rack/move/traveller=20fields?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fp.job.step: + requires_rack_assignment (related from recipe_node_id) + requires_transition_form (related) + move_ids (O2M from_step_id), incoming_move_ids (O2M to_step_id) + is_racked (compute, stored, depends rack_id) — drives tablet rack-vs-parts greyed-button guard + qty_at_step_start, qty_at_step_finish (advanced by move commits) NOTE: existing 'rack_id' field is reused as the 'current rack' pointer (already there on line 95). Adding requires_rack_assignment as a related from recipe_node_id for runtime gate evaluation. fp.job: + qty_received, qty_visual_inspection_rejects, qty_rework + special_requirements (Text — paper traveller header) + active_timer_ids (filtered O2M, depends on Task 7's state field) + move_ids (O2M to fp.job.step.move) All additive. No removed fields. Existing battle tests unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../fusion_plating/models/fp_job.py | 34 ++++++++++++++++++ .../fusion_plating/models/fp_job_step.py | 35 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/fusion_plating/fusion_plating/models/fp_job.py b/fusion_plating/fusion_plating/models/fp_job.py index 8ae156d3..7c9bf5fa 100644 --- a/fusion_plating/fusion_plating/models/fp_job.py +++ b/fusion_plating/fusion_plating/models/fp_job.py @@ -186,6 +186,40 @@ class FpJob(models.Model): 'job_id', string='Steps', ) + + # ===== Sub 12b — traveller header + active timer ======================== + # Header counters mirror the paper traveller's "Qty Rec." / "VIS INSP." + # / "Rework" columns (screens 16-18). Sub 12c's traveller report pulls + # these into the printed header. + qty_received = fields.Integer( + string='Qty Received', + help='Paper traveller "Qty Rec." column.', + ) + qty_visual_inspection_rejects = fields.Integer( + string='Visual Insp Rejects', + help='Paper traveller "VIS INSP." column.', + ) + qty_rework = fields.Integer( + string='Qty Sent to Rework', + help='Paper traveller "Rework" column.', + ) + special_requirements = fields.Text( + string='Special Requirements', + help='Long free-form spec text from customer; printed on the ' + 'traveller header (Sub 12c).', + ) + active_timer_ids = fields.One2many( + 'fp.job.step.timelog', + 'job_id', + string='Active Timers', + domain=[('state', 'in', ('running', 'paused'))], + help='Sub 12b — used by tablet for live timer badges. Filtered ' + 'on state by Task 7\'s state field.', + ) + move_ids = fields.One2many( + 'fp.job.step.move', 'job_id', + string='Move Log', + ) # step_count + step_done_count are stored (drive list views / stat # buttons in Task 1.8). step_progress_pct stays non-stored — it's a # cheap derivative. Odoo flags as inconsistent when stored and diff --git a/fusion_plating/fusion_plating/models/fp_job_step.py b/fusion_plating/fusion_plating/models/fp_job_step.py index d2ae44e1..8a201481 100644 --- a/fusion_plating/fusion_plating/models/fp_job_step.py +++ b/fusion_plating/fusion_plating/models/fp_job_step.py @@ -139,6 +139,41 @@ class FpJobStep(models.Model): 'step in this job is done/skipped/cancelled.', ) + # ===== Sub 12b — chain-of-custody + rack awareness ===================== + # Note: rack_id (line 95 above) already exists — reused as the "current + # rack on this step" pointer. Sub 12b builds the runtime guards on top. + requires_rack_assignment = fields.Boolean( + related='recipe_node_id.requires_rack_assignment', + store=True, + help='If True, the Move Parts dialog requires a rack to be ' + 'assigned to the parts before the move commits. Snapshot ' + 'from the recipe step at job creation.', + ) + requires_transition_form = fields.Boolean( + related='recipe_node_id.requires_transition_form', + store=True, + ) + move_ids = fields.One2many( + 'fp.job.step.move', 'from_step_id', + string='Outgoing Moves', + ) + incoming_move_ids = fields.One2many( + 'fp.job.step.move', 'to_step_id', + string='Incoming Moves', + ) + is_racked = fields.Boolean( + string='Racked', compute='_compute_is_racked', store=True, + help='True when rack_id is set — drives the tablet rack-vs-parts ' + 'button-state guard (Move Parts greys out).', + ) + qty_at_step_start = fields.Integer(string='Qty at Step Start') + qty_at_step_finish = fields.Integer(string='Qty at Step Finish') + + @api.depends('rack_id') + def _compute_is_racked(self): + for rec in self: + rec.is_racked = bool(rec.rack_id) + # ------------------------------------------------------------------ # Cost rollup (Task 1.6) # cost_per_hour comes from fp.work.centre (Task 1.2 added it there).