---
description: State machines for all entities. Only valid transitions allowed.
globs: ["app/Domain/**/Services/**", "app/Domain/**/Enums/**"]
---

# Status Transitions — Hard Enforcement

## Participant Status

Valid transitions (service MUST enforce):
```
registered  → active, withdrawn
active      → frozen, suspended, inactive, graduated, transferred, withdrawn, blacklisted
frozen      → active, withdrawn, inactive
suspended   → active, withdrawn, blacklisted
inactive    → active, withdrawn
graduated   → active
withdrawn   → active
transferred → (terminal)
blacklisted → (terminal, super_admin override only)
```

NEVER: any status → registered, frozen → graduated, suspended → graduated

## Invoice Status

```
draft → sent, cancelled
sent → partially_paid, overdue, cancelled (only if paid_amount==0)
overdue → partially_paid, paid
partially_paid → paid (NEVER cancelled — has payments)
paid → refunded (via refund flow only)
```

## Training Group Status

```
forming → active, cancelled
active → full, on_hold, completed, cancelled
full → active (spot opens)
on_hold → active
```

## Enrollment Status

```
pending → active, cancelled, waitlisted
active → completed, cancelled, expired
waitlisted → pending (spot opens), cancelled
```

## Session Status

```
scheduled → in_progress, cancelled, rescheduled
in_progress → completed
```

## Implementation Pattern

```php
private const VALID_TRANSITIONS = [
    'current_status' => ['allowed_target_1', 'allowed_target_2'],
];

public function changeStatus(Model $entity, string $newStatus, string $reason): void
{
    $allowed = self::VALID_TRANSITIONS[$entity->status] ?? [];
    if (!in_array($newStatus, $allowed)) {
        throw new InvalidStatusTransitionException(
            "Cannot transition from '{$entity->status}' to '{$newStatus}'"
        );
    }
    // proceed...
}
```

Every service that manages a stateful entity MUST have this constant and validation.
