77 lines
2.1 KiB
Python
77 lines
2.1 KiB
Python
|
|
"""Server-side workflow state machine. Every stage change flows through
|
||
|
|
`transition()`, which validates against ALLOWED_TRANSITIONS and records both
|
||
|
|
a StageTransition row (timestamps + acting user, for aging/cycle-time
|
||
|
|
reporting) and an audit entry."""
|
||
|
|
from app.domain import ALLOWED_TRANSITIONS, Stage
|
||
|
|
from app.models import Ncr, StageTransition
|
||
|
|
from app.models.base import utcnow
|
||
|
|
from app.services.audit import audit_event
|
||
|
|
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
|
||
|
|
class InvalidTransitionError(Exception):
|
||
|
|
def __init__(self, from_stage: str, to_stage: str):
|
||
|
|
self.from_stage = from_stage
|
||
|
|
self.to_stage = to_stage
|
||
|
|
super().__init__(f"Invalid stage transition: {from_stage} -> {to_stage}")
|
||
|
|
|
||
|
|
|
||
|
|
def transition(
|
||
|
|
db: AsyncSession,
|
||
|
|
ncr: Ncr,
|
||
|
|
to_stage: Stage,
|
||
|
|
*,
|
||
|
|
action: str,
|
||
|
|
actor_id: int,
|
||
|
|
note: str | None = None,
|
||
|
|
) -> None:
|
||
|
|
from_stage = Stage(ncr.stage)
|
||
|
|
if to_stage not in ALLOWED_TRANSITIONS.get(from_stage, set()):
|
||
|
|
raise InvalidTransitionError(from_stage.value, to_stage.value)
|
||
|
|
|
||
|
|
now = utcnow()
|
||
|
|
ncr.stage = to_stage.value
|
||
|
|
ncr.stage_entered_at = now
|
||
|
|
db.add(
|
||
|
|
StageTransition(
|
||
|
|
ncr_id=ncr.id,
|
||
|
|
from_stage=from_stage.value,
|
||
|
|
to_stage=to_stage.value,
|
||
|
|
action=action,
|
||
|
|
acted_by_id=actor_id,
|
||
|
|
acted_at=now,
|
||
|
|
note=note,
|
||
|
|
)
|
||
|
|
)
|
||
|
|
audit_event(
|
||
|
|
db,
|
||
|
|
ncr_id=ncr.id,
|
||
|
|
user_id=actor_id,
|
||
|
|
action=action,
|
||
|
|
field_name="stage",
|
||
|
|
old_value=from_stage.value,
|
||
|
|
new_value=to_stage.value,
|
||
|
|
detail=note,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def record_creation(db: AsyncSession, ncr: Ncr, actor_id: int) -> None:
|
||
|
|
db.add(
|
||
|
|
StageTransition(
|
||
|
|
ncr_id=ncr.id,
|
||
|
|
from_stage=None,
|
||
|
|
to_stage=Stage.NEW_REQUEST.value,
|
||
|
|
action="create",
|
||
|
|
acted_by_id=actor_id,
|
||
|
|
acted_at=ncr.created_at,
|
||
|
|
)
|
||
|
|
)
|
||
|
|
audit_event(
|
||
|
|
db,
|
||
|
|
ncr_id=ncr.id,
|
||
|
|
user_id=actor_id,
|
||
|
|
action="create",
|
||
|
|
detail=f"NCR {ncr.ncr_number} created",
|
||
|
|
)
|