diff --git a/fusion_plating/fusion_plating/models/fp_rack.py b/fusion_plating/fusion_plating/models/fp_rack.py
index 58402ec9..57831149 100644
--- a/fusion_plating/fusion_plating/models/fp_rack.py
+++ b/fusion_plating/fusion_plating/models/fp_rack.py
@@ -115,3 +115,67 @@ class FpRack(models.Model):
"""Add `delta` to the rack's MTO count. Called by the WO finish hook."""
for rec in self:
rec.mto_count = (rec.mto_count or 0.0) + delta
+
+ # ===== Sub 12b — racking lifecycle (orthogonal to wear-tracking state) =
+ racking_state = fields.Selection(
+ [
+ ('empty', 'Empty'),
+ ('loading', 'Loading'),
+ ('loaded', 'Loaded'),
+ ('in_use', 'In Use'),
+ ('awaiting_unrack', 'Awaiting Unrack'),
+ ('out_of_service', 'Out of Service'),
+ ],
+ string='Racking State', default='empty', tracking=True,
+ help='Operational state in the rack→step→tank flow. Distinct '
+ 'from the wear-tracking `state` (active/needs_strip/...).',
+ )
+ tag_ids = fields.Many2many(
+ 'fp.rack.tag',
+ 'fp_rack_tag_rel', 'rack_id', 'tag_id',
+ string='Tags',
+ )
+ capacity_count = fields.Integer(
+ string='Capacity (parts) — soft warn',
+ help='Soft warning threshold — runtime informs operator when '
+ 'rack is loaded beyond this. Not enforced. Distinct from '
+ '`capacity` field (planning capacity).',
+ )
+
+ current_job_step_id = fields.Many2one(
+ 'fp.job.step', string='Current Step',
+ compute='_compute_current_use', store=True,
+ )
+ current_tank_id = fields.Many2one(
+ 'fusion.plating.tank', string='Current Tank',
+ compute='_compute_current_use', store=True,
+ )
+ current_part_count = fields.Integer(
+ string='Parts on Rack',
+ compute='_compute_current_use', store=True,
+ )
+
+ @api.depends('racking_state')
+ def _compute_current_use(self):
+ # Walks the most recent fp.job.step.move row per rack to derive
+ # current step + tank + part count. For racks not currently in
+ # use, all values are blank.
+ Move = self.env['fp.job.step.move']
+ for rack in self:
+ if rack.racking_state in ('empty', 'out_of_service'):
+ rack.current_job_step_id = False
+ rack.current_tank_id = False
+ rack.current_part_count = 0
+ continue
+ recent = Move.search(
+ [('rack_id', '=', rack.id)],
+ order='move_datetime desc',
+ limit=1,
+ )
+ rack.current_job_step_id = recent.to_step_id if recent else False
+ # current_tank_id pulls from the destination step's tank if set
+ rack.current_tank_id = (
+ recent.to_tank_id or
+ (recent.to_step_id.tank_id if recent and recent.to_step_id else False)
+ ) if recent else False
+ rack.current_part_count = recent.qty_moved if recent else 0
diff --git a/fusion_plating/fusion_plating/views/fp_rack_views.xml b/fusion_plating/fusion_plating/views/fp_rack_views.xml
index 12be9775..29d3d3c1 100644
--- a/fusion_plating/fusion_plating/views/fp_rack_views.xml
+++ b/fusion_plating/fusion_plating/views/fp_rack_views.xml
@@ -68,6 +68,20 @@
+
+
+
+
+
+
+
+
+
+
+
+