# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # # Real implementations for the state-machine action stubs that # fusion_plating core's fp.job.step shipped as NotImplementedError # placeholders. Per spec §5.2 state machine. from odoo import _, fields, models from odoo.exceptions import UserError class FpJobStep(models.Model): _inherit = 'fp.job.step' def button_start(self): """Override — soft gate when parts haven't been received yet. Doesn't block (parts could be in-transit late, manager wants the shop to start prep regardless), but posts a chatter warning on the job so the audit trail captures premature starts. """ result = super().button_start() for step in self: so = step.job_id.sale_order_id if not so: continue recv = so.x_fc_receiving_status if ( 'x_fc_receiving_status' in so._fields ) else None if recv in (False, None, 'not_received'): step.job_id.message_post(body=_( 'Step "%(step)s" started before parts were received ' '(SO %(so)s — receiving status: %(status)s). ' 'Confirm the parts are physically on the floor before ' 'continuing.' ) % { 'step': step.name, 'so': so.name or '', 'status': recv or 'unknown', }) return result def button_pause(self): """Pause an in-progress step (operator break, end of shift). Closes the open timelog row, sums duration_actual, transitions state to 'paused'. button_start re-opens a fresh timelog when the operator resumes. """ for step in self: if step.state != 'in_progress': raise UserError(_( "Step '%s' is in state '%s' — only in-progress steps can pause." ) % (step.name, step.state)) now = fields.Datetime.now() open_log = step.time_log_ids.filtered(lambda l: not l.date_finished) if open_log: open_log.write({'date_finished': now}) step.state = 'paused' step.duration_actual = sum(step.time_log_ids.mapped('duration_minutes')) return True def button_skip(self): """Skip a pending/ready step (e.g. opt-in step the planner decided not to activate for this job). """ for step in self: if step.state not in ('pending', 'ready'): raise UserError(_( "Step '%s' is in state '%s' — only pending/ready steps can be skipped." ) % (step.name, step.state)) step.state = 'skipped' return True def button_cancel(self): """Cancel a single step. Use fp.job.action_cancel to cancel the whole job. """ for step in self: if step.state == 'done': raise UserError(_( "Step '%s' is done — cannot cancel." ) % step.name) if step.state == 'cancelled': raise UserError(_( "Step '%s' is already cancelled." ) % step.name) step.state = 'cancelled' return True