---
description: Enum values MUST match CHECK constraints exactly. Single source of truth.
globs: ["app/Domain/**/Enums/**", "database/migrations/**"]
---

# Enums & CHECK Constraints — Character-for-Character Match

## The Rule

The PHP enum case values and the PostgreSQL CHECK constraint values must be IDENTICAL.
When writing an enum, COPY from the migration. When writing a migration, DEFINE the values first.

## Enum Pattern

```php
namespace App\Domain\{Module}\Enums;

enum StatusName: string
{
    case Draft = 'draft';
    case Active = 'active';
    // Values are ALWAYS snake_case strings
}
```

## CHECK Pattern in Migration

```php
DB::statement("ALTER TABLE table_name ADD CONSTRAINT table_name_column_check 
    CHECK (column IN ('draft', 'active'))");
```

## Naming Convention

- Constraint: `{table}_{column}_check`
- Enum class: PascalCase singular noun (`InvoiceStatus`, `PaymentMethod`)
- Enum values: snake_case (`'partially_paid'`, `'in_progress'`)
- Enum cases: PascalCase (`PartiallyPaid`, `InProgress`)

## Complete Enum Registry

### InvoiceStatus
`'draft', 'sent', 'partially_paid', 'paid', 'overdue', 'cancelled', 'refunded'`

### PaymentStatus
`'confirmed', 'pending', 'failed', 'refunded', 'partially_refunded'`

### PaymentMethod
`'cash', 'card', 'bank_transfer', 'wallet', 'cheque', 'other'`

### TransactionType
`'debit', 'credit'`

### AccountType
`'asset', 'liability', 'equity', 'revenue', 'expense'`

### ParticipantStatus
`'registered', 'active', 'frozen', 'suspended', 'inactive', 'graduated', 'transferred', 'withdrawn', 'blacklisted'`

### EnrollmentStatus
`'pending', 'active', 'completed', 'cancelled', 'expired', 'waitlisted'`

### GroupStatus
`'forming', 'active', 'full', 'on_hold', 'completed', 'cancelled'`

### SessionStatus
`'scheduled', 'in_progress', 'completed', 'cancelled', 'rescheduled'`

### AttendanceStatus
`'expected', 'present', 'late', 'excused', 'absent', 'no_show', 'left_early', 'partial', 'cancelled', 'exempt'`

### Gender
`'male', 'female'`

### Classification
`'regular', 'vip', 'scholarship', 'staff_child', 'trial'`

### FacilityStatus
`'active', 'maintenance', 'closed', 'reserved', 'unavailable'`

### LayoutType
`'grid', 'lanes', 'zones', 'custom'`

### ReservationStatus
`'confirmed', 'tentative', 'cancelled'`

### AssignmentScope
`'full', 'partial', 'supervising', 'assisting', 'observing'`

### AssignmentStatus
`'active', 'suspended', 'completed', 'cancelled'`

### MovementDirection
`'in', 'out'`

### PurchaseOrderStatus
`'draft', 'submitted', 'confirmed', 'partially_received', 'received', 'cancelled'`

### WalletStatus
`'active', 'frozen', 'closed'`

### UserStatus
`'active', 'inactive', 'suspended', 'pending'`

### PricingAdjustmentType
`'percentage_discount', 'fixed_discount', 'fixed_price', 'percentage_increase'`

### PricingRuleType
`'age', 'membership_duration', 'family_size', 'sibling_order', 'classification', 'enrollment_timing', 'enrollment_volume', 'seasonal', 'gender', 'branch', 'day_time', 'loyalty', 'custom'`

## Validation Rules (in FormRequests)

```php
'status' => 'required|in:' . implode(',', array_column(StatusEnum::cases(), 'value'))
```

This ensures form validation accepts EXACTLY what the DB accepts.
