feat(sub12b): fp.job.step.move + fp.job.step.move.input.value
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) <noreply@anthropic.com>
This commit is contained in:
@@ -22,5 +22,13 @@
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Sub 12b — Move Parts / Move Rack chain-of-custody log -->
|
||||
<record id="seq_fp_job_step_move" model="ir.sequence">
|
||||
<field name="name">FP — Move Log</field>
|
||||
<field name="code">fp.job.step.move</field>
|
||||
<field name="prefix">FP/MOVE/%(year)s/</field>
|
||||
<field name="padding">5</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -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
|
||||
|
||||
104
fusion_plating/fusion_plating/models/fp_job_step_move.py
Normal file
104
fusion_plating/fusion_plating/models/fp_job_step_move.py
Normal file
@@ -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')
|
||||
@@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user