"""Mandate 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.mandate import Mandate, MandateStep # Valid status transitions for mandates _MANDATE_STATUS_ORDER = [ "draft", "candidacy", "voting", "active", "reporting", "completed", ] async def advance_mandate(mandate_id: uuid.UUID, db: AsyncSession) -> Mandate: """Move a mandate to its next step. Completes the current active step and activates the next pending step. If no more steps remain, the mandate status advances to the next phase. Parameters ---------- mandate_id: UUID of the Mandate to advance. db: Async database session. Returns ------- Mandate The updated mandate. Raises ------ ValueError If the mandate is not found, already completed/revoked, or no further advancement is possible. """ result = await db.execute( select(Mandate) .options(selectinload(Mandate.steps)) .where(Mandate.id == mandate_id) ) mandate = result.scalar_one_or_none() if mandate is None: raise ValueError(f"Mandat introuvable : {mandate_id}") if mandate.status in ("completed", "revoked"): raise ValueError(f"Le mandat est deja en statut terminal : {mandate.status}") steps: list[MandateStep] = sorted(mandate.steps, key=lambda s: s.step_order) # Find the current active step active_step: MandateStep | 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: MandateStep | 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 mandate status _advance_mandate_status(mandate) else: # No active step: activate the first pending one first_pending: MandateStep | None = None for step in steps: if step.status == "pending": first_pending = step break if first_pending is not None: first_pending.status = "active" # Move out of draft if mandate.status == "draft": mandate.status = "candidacy" else: # All steps completed: advance status _advance_mandate_status(mandate) await db.commit() await db.refresh(mandate) return mandate def _advance_mandate_status(mandate: Mandate) -> None: """Move a mandate to its next status in the lifecycle.""" try: current_index = _MANDATE_STATUS_ORDER.index(mandate.status) except ValueError: return next_index = current_index + 1 if next_index < len(_MANDATE_STATUS_ORDER): mandate.status = _MANDATE_STATUS_ORDER[next_index]