"""Decision service: step advancement logic.""" from __future__ import annotations import uuid from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.models.decision import Decision, DecisionStep # Valid status transitions for decisions _DECISION_STATUS_ORDER = [ "draft", "qualification", "review", "voting", "executed", "closed", ] async def advance_decision(decision_id: uuid.UUID, db: AsyncSession) -> Decision: """Move a decision to its next step. Completes the current active step and activates the next pending step. If no more steps remain, the decision status advances to the next phase. Parameters ---------- decision_id: UUID of the Decision to advance. db: Async database session. Returns ------- Decision The updated decision. Raises ------ ValueError If the decision is not found, or no further advancement is possible. """ result = await db.execute( select(Decision) .options(selectinload(Decision.steps)) .where(Decision.id == decision_id) ) decision = result.scalar_one_or_none() if decision is None: raise ValueError(f"Decision introuvable : {decision_id}") if decision.status == "closed": raise ValueError("La decision est deja cloturee") steps: list[DecisionStep] = sorted(decision.steps, key=lambda s: s.step_order) # Find the current active step active_step: DecisionStep | None = None for step in steps: if step.status == "active": active_step = step break if active_step is not None: # Complete the active step active_step.status = "completed" # Activate the next pending step next_step: DecisionStep | None = None for step in steps: if step.step_order > active_step.step_order and step.status == "pending": next_step = step break if next_step is not None: next_step.status = "active" else: # No more steps: advance the decision status _advance_decision_status(decision) else: # No active step: try to activate the first pending step first_pending: DecisionStep | None = None for step in steps: if step.status == "pending": first_pending = step break if first_pending is not None: first_pending.status = "active" # Also advance decision out of draft if needed if decision.status == "draft": decision.status = "qualification" else: # All steps are completed: advance the decision status _advance_decision_status(decision) await db.commit() await db.refresh(decision) return decision def _advance_decision_status(decision: Decision) -> None: """Move a decision to its next status in the lifecycle.""" try: current_index = _DECISION_STATUS_ORDER.index(decision.status) except ValueError: return next_index = current_index + 1 if next_index < len(_DECISION_STATUS_ORDER): decision.status = _DECISION_STATUS_ORDER[next_index]