# Phase 4: Reservation System — DONE

**Completed:** 2026-03-16

## Summary

Full reservation lifecycle for hotel managers (CRUD + status management + room assignment + folio) and customers (browse, book, cancel). Includes 3 actions, 6 exceptions, Filament relation manager, 4 API resources, 3 form requests, 10 controllers, and comprehensive Pest tests.

## Files Created

### Exceptions (6)
- `app/Exceptions/NoRoomAvailableException.php` — No rooms available (422)
- `app/Exceptions/InvalidOccupancyException.php` — Occupancy exceeds limits (422)
- `app/Exceptions/MinStayNotMetException.php` — Stay shorter than minimum (422)
- `app/Exceptions/InvalidStatusTransitionException.php` — Invalid status change (422)
- `app/Exceptions/RoomNotAvailableException.php` — Room not available for dates (422)
- `app/Exceptions/RoomTypeMismatchException.php` — Room doesn't match reservation type (422)

### Actions (3)
- `app/Actions/HotelManager/CreateHotelReservationAction.php` — Validates occupancy, checks availability, validates min/max stay, calculates price via HotelRateService, creates reservation with PENDING status.
- `app/Actions/HotelManager/UpdateReservationStatusAction.php` — Validates state machine transitions (PENDING→CONFIRMED→CHECKED_IN→CHECKED_OUT, PENDING/CONFIRMED→CANCELLED, CONFIRMED→NO_SHOW). Static `isValidTransition()` helper.
- `app/Actions/HotelManager/AssignRoomsAction.php` — Validates room belongs to hotel, matches reservation room type, is available for dates. Syncs pivot table and updates room status (RESERVED or OCCUPIED).

### Filament Admin (1)
- `app/Filament/Resources/HotelResource/RelationManagers/ReservationsRelationManager.php` — Full CRUD with guest selector, room type, dates, status/source badges, confirm/cancel quick actions, filters by status/source/room type.

### API Resources (4)
- `app/Http/Resources/HotelManager/HotelReservationResource.php` — Full detail with guest info, room type, assigned rooms, timestamps.
- `app/Http/Resources/HotelManager/HotelReservationSummaryResource.php` — Compact for list views.
- `app/Http/Resources/HotelManager/HotelFolioResource.php` — Folio with nightly room charges breakdown, extras, room total, extras total, grand total.
- `app/Http/Resources/HotelManager/CustomerHotelReservationResource.php` — Customer-facing with hotel info, can_cancel flag.

### Form Requests (3)
- `app/Http/Requests/HotelManager/CreateReservationRequest.php` — room_type_id, check_in, check_out, adults, children, customer_id, source.
- `app/Http/Requests/HotelManager/UpdateReservationStatusRequest.php` — status (enum), reason.
- `app/Http/Requests/HotelManager/AssignRoomsRequest.php` — room_ids (array of public_ids).

### Manager Controllers (6)
- `ListReservationsController` — GET `/manage/reservations` with filters (status, date range, source, search by guest name/email), paginated.
- `ShowReservationController` — GET `/manage/reservations/{reservation}` with guest, rooms, extras.
- `CreateReservationController` — POST `/manage/reservations` for walk-in/phone, resolves customer_id or uses authenticated user.
- `UpdateReservationStatusController` — PATCH `/manage/reservations/{reservation}/status`.
- `AssignRoomsController` — POST `/manage/reservations/{reservation}/rooms`, resolves public_ids to internal IDs.
- `ShowFolioController` — GET `/manage/reservations/{reservation}/folio` with nightly breakdown.

### Customer Controllers (4)
- `CreateCustomerReservationController` — POST `/hotels/{hotel}/reservations`, source=DIRECT_APP.
- `ListMyHotelReservationsController` — GET `/hotels/reservations`, scoped to authenticated user + place.
- `ShowMyReservationController` — GET `/hotels/{hotel}/reservations/{reservation}`, scoped to user.
- `CancelReservationController` — DELETE `/hotels/{hotel}/reservations/{reservation}`, validates canBeCancelled.

### Tests (1)
- `tests/Feature/Controllers/HotelManager/ReservationsTest.php` — 13 tests: list with filters, show detail, create walk-in, status transitions (full lifecycle), invalid transition error, room assignment, unavailable room error, customer create, customer list (scoped), customer cancel, cannot cancel other's, availability decrease after booking, folio charges.

## Files Modified

- `app/Filament/Resources/HotelResource.php` — Added ReservationsRelationManager.
- `routes/api.php` — Added 6 manager routes + 4 customer routes. Moved `reservations` route before `{hotel}` wildcard to avoid route collision.

## API Routes

### Hotel Manager
| Method | URL | Controller |
|--------|-----|-----------|
| GET | `/v1/places/{place}/hotels/{hotel}/manage/reservations` | ListReservationsController |
| POST | `/v1/places/{place}/hotels/{hotel}/manage/reservations` | CreateReservationController |
| GET | `/v1/places/{place}/hotels/{hotel}/manage/reservations/{reservation}` | ShowReservationController |
| PATCH | `/v1/places/{place}/hotels/{hotel}/manage/reservations/{reservation}/status` | UpdateReservationStatusController |
| POST | `/v1/places/{place}/hotels/{hotel}/manage/reservations/{reservation}/rooms` | AssignRoomsController |
| GET | `/v1/places/{place}/hotels/{hotel}/manage/reservations/{reservation}/folio` | ShowFolioController |

### Customer
| Method | URL | Controller |
|--------|-----|-----------|
| GET | `/v1/places/{place}/hotels/reservations` | ListMyHotelReservationsController |
| POST | `/v1/places/{place}/hotels/{hotel}/reservations` | CreateCustomerReservationController |
| GET | `/v1/places/{place}/hotels/{hotel}/reservations/{reservation}` | ShowMyReservationController |
| DELETE | `/v1/places/{place}/hotels/{hotel}/reservations/{reservation}` | CancelReservationController |

## Reservation Status State Machine

```
PENDING → CONFIRMED, CANCELLED
CONFIRMED → CHECKED_IN, CANCELLED, NO_SHOW
CHECKED_IN → CHECKED_OUT
CHECKED_OUT → (terminal)
CANCELLED → (terminal)
NO_SHOW → (terminal)
```

## Quality Checks

- **Pint:** PASS (0 issues after auto-fix)
- **PHPStan level 7:** PASS (0 errors after fixing duplicate badge color key)
- **Pest tests:** 64 passing (18 Phase 1 + 14 Phase 2 + 11 Phase 3 + 8 Phase 7 + 13 Phase 4)

## Design Decisions

- **Route ordering**: Customer `GET /hotels/reservations` (list my reservations) placed before `{hotel}` wildcard to avoid "reservations" being captured as a hotel public_id.
- **Walk-in flow**: Manager can create reservation with `customer_id` (existing customer) or without (uses authenticated user as fallback). Source defaults to `DIRECT_WALKIN` for manager-created reservations.
- **Room assignment updates status**: Assigning rooms to a CONFIRMED reservation sets rooms to RESERVED. For CHECKED_IN reservations, rooms become OCCUPIED.
- **Folio uses HotelRateService**: Nightly breakdown is recalculated from the rate service for display, ensuring consistency with the pricing engine.
- **Customer cancellation**: Scoped by user_id, so trying to cancel another user's reservation returns 404 (not 403), preventing information leakage.
- **Payment (Phase 5)**: Customer pay endpoint deferred to Phase 5 (Check-in/out) as it requires Order creation and payment flow integration.

## Notes

- No commits created — user will commit manually.
- Swagger annotations present on all 10 new controllers.
- All exceptions extend ApiException with proper HTTP status codes and error codes.
