From 4187842d30419e9770091dff914bd5e4c764aeba Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Mon, 27 Apr 2026 21:04:41 -0400 Subject: [PATCH] feat(sub12b): fp.job.step.move + fp.job.step.move.input.value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chain-of-custody log: one row per Move Parts / Move Rack commit. FP/MOVE/YYYY/NNNN sequence (5-digit). Carries from/to step + tank, transfer type, qty, location, photo, rack, operator, datetime. Child model captures recorded transition-input values (Sub 12a's fp.step.template.transition.input snapshots → fp.job.step.move. input.value rows). Each row carries 5 typed value columns; the controller (Task 8) picks the right one based on input_type. Operators get read+write+create — they generate moves at runtime — but no unlink. Manager-only deletes for audit safety. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../fusion_plating/data/fp_sequence_data.xml | 8 ++ .../fusion_plating/models/__init__.py | 1 + .../fusion_plating/models/fp_job_step_move.py | 104 ++++++++++++++++++ .../security/ir.model.access.csv | 6 + 4 files changed, 119 insertions(+) create mode 100644 fusion_plating/fusion_plating/models/fp_job_step_move.py diff --git a/fusion_plating/fusion_plating/data/fp_sequence_data.xml b/fusion_plating/fusion_plating/data/fp_sequence_data.xml index 5d46cac3..6dfe542d 100644 --- a/fusion_plating/fusion_plating/data/fp_sequence_data.xml +++ b/fusion_plating/fusion_plating/data/fp_sequence_data.xml @@ -22,5 +22,13 @@ + + + FP — Move Log + fp.job.step.move + FP/MOVE/%(year)s/ + 5 + + diff --git a/fusion_plating/fusion_plating/models/__init__.py b/fusion_plating/fusion_plating/models/__init__.py index bf11a616..d2ef444f 100644 --- a/fusion_plating/fusion_plating/models/__init__.py +++ b/fusion_plating/fusion_plating/models/__init__.py @@ -40,3 +40,4 @@ from . import fp_step_template_transition_input # Sub 12b — Rack-aware moves + persistent labor reconciliation from . import fp_rack_tag +from . import fp_job_step_move diff --git a/fusion_plating/fusion_plating/models/fp_job_step_move.py b/fusion_plating/fusion_plating/models/fp_job_step_move.py new file mode 100644 index 00000000..d9b41347 --- /dev/null +++ b/fusion_plating/fusion_plating/models/fp_job_step_move.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +# Part of the Fusion Plating product family. + +from odoo import api, fields, models + + +class FpJobStepMove(models.Model): + """Chain-of-custody log — one row per part-batch move. + + Sub 12b: every Move Parts / Move Rack click commits one (or, for + rack moves, one-per-batch atomic) row here. Sub 12c walks these in + chronological order to render the customer CoC PDF. + """ + _name = 'fp.job.step.move' + _description = 'Fusion Plating — Job Step Move (Chain-of-Custody)' + _inherit = ['mail.thread'] + _order = 'move_datetime desc, id desc' + + name = fields.Char( + string='Move Reference', + default=lambda self: self.env['ir.sequence'].next_by_code( + 'fp.job.step.move') or '/', + readonly=True, copy=False, + ) + job_id = fields.Many2one('fp.job', string='Job', + required=True, ondelete='cascade', index=True) + from_step_id = fields.Many2one('fp.job.step', string='From Step', + ondelete='set null', index=True) + to_step_id = fields.Many2one('fp.job.step', string='To Step', + ondelete='set null', index=True, required=True) + from_tank_id = fields.Many2one('fusion.plating.tank', + related='from_step_id.tank_id', store=True) + to_tank_id = fields.Many2one('fusion.plating.tank', string='To Tank', + ondelete='set null') + + transfer_type = fields.Selection([ + ('step', 'Step'), + ('hold', 'Hold'), + ('scrap', 'Scrap'), + ('rework', 'Rework'), + ('split', 'Split'), + ('return', 'Return'), + ], string='Transfer Type', default='step', required=True) + + qty_moved = fields.Integer(string='Qty Moved', required=True) + qty_available_at_move = fields.Integer(string='Qty Available') + + to_location = fields.Selection([ + ('global', 'Global'), + ('quarantine', 'Quarantine'), + ('staging_a', 'Staging A'), + ('staging_b', 'Staging B'), + ('shipping_dock', 'Shipping Dock'), + ('scrap_bin', 'Scrap Bin'), + ], string='To Location', default='global') + + photo_evidence_id = fields.Many2one('ir.attachment', + string='Photo Evidence', ondelete='set null') + customer_wo_count = fields.Integer(string='# Customer WOs') + + rack_id = fields.Many2one('fusion.plating.rack', + string='Rack', ondelete='set null', index=True) + unrack_after_move = fields.Boolean(string='Unrack After Move') + + moved_by_user_id = fields.Many2one('res.users', string='Moved By', + default=lambda self: self.env.user, required=True) + move_datetime = fields.Datetime(string='Move Time', + default=fields.Datetime.now, required=True, index=True) + + transition_input_value_ids = fields.One2many( + 'fp.job.step.move.input.value', 'move_id', + string='Transition Input Values', + ) + + +class FpJobStepMoveInputValue(models.Model): + """Captured value for one transition-input prompt. + + Each row = one author-defined prompt × one move. Snapshot of what + the operator typed at move-time. Used by Sub 12c CoC report. + """ + _name = 'fp.job.step.move.input.value' + _description = 'Fusion Plating — Captured Transition Input Value' + _order = 'move_id, id' + + move_id = fields.Many2one('fp.job.step.move', string='Move', + required=True, ondelete='cascade', index=True) + template_input_id = fields.Many2one( + 'fp.step.template.transition.input', + string='Template Input', ondelete='set null', + help='What was originally asked (template-level reference).') + node_input_id = fields.Many2one( + 'fusion.plating.process.node.input', + string='Node Input', ondelete='set null', + help='Snapshot of the authored prompt at job-creation time.') + + value_text = fields.Char(string='Text Value') + value_number = fields.Float(string='Number Value') + value_boolean = fields.Boolean(string='Yes/No Value') + value_date = fields.Datetime(string='Date Value') + value_attachment_id = fields.Many2one('ir.attachment', + string='Attachment Value', ondelete='set null') diff --git a/fusion_plating/fusion_plating/security/ir.model.access.csv b/fusion_plating/fusion_plating/security/ir.model.access.csv index a94c186d..6b78505e 100644 --- a/fusion_plating/fusion_plating/security/ir.model.access.csv +++ b/fusion_plating/fusion_plating/security/ir.model.access.csv @@ -73,3 +73,9 @@ access_fp_step_template_transition_input_manager,fp.step.template.transition.inp access_fp_rack_tag_operator,fp.rack.tag.operator,model_fp_rack_tag,group_fusion_plating_operator,1,0,0,0 access_fp_rack_tag_supervisor,fp.rack.tag.supervisor,model_fp_rack_tag,group_fusion_plating_supervisor,1,1,1,1 access_fp_rack_tag_manager,fp.rack.tag.manager,model_fp_rack_tag,group_fusion_plating_manager,1,1,1,1 +access_fp_job_step_move_operator,fp.job.step.move.operator,model_fp_job_step_move,group_fusion_plating_operator,1,1,1,0 +access_fp_job_step_move_supervisor,fp.job.step.move.supervisor,model_fp_job_step_move,group_fusion_plating_supervisor,1,1,1,0 +access_fp_job_step_move_manager,fp.job.step.move.manager,model_fp_job_step_move,group_fusion_plating_manager,1,1,1,1 +access_fp_job_step_move_input_value_operator,fp.job.step.move.input.value.operator,model_fp_job_step_move_input_value,group_fusion_plating_operator,1,1,1,0 +access_fp_job_step_move_input_value_supervisor,fp.job.step.move.input.value.supervisor,model_fp_job_step_move_input_value,group_fusion_plating_supervisor,1,1,1,0 +access_fp_job_step_move_input_value_manager,fp.job.step.move.input.value.manager,model_fp_job_step_move_input_value,group_fusion_plating_manager,1,1,1,1