# Phase 5: Check-in / Check-out / Cancellation

## Objective
Implement the guest lifecycle management: check-in flow with room assignment, check-out with folio generation and final payment, cancellation with penalty calculation and refund, and invoice auto-generation.

---

## Tasks

### 5.1 Actions

#### CheckInGuestAction
**File:** `app/Actions/HotelManager/CheckInGuestAction.php`

```php
final class CheckInGuestAction
{
    /**
     * @throws ReservationNotConfirmedException
     * @throws NoRoomAssignedException
     */
    public function __invoke(
        HotelReservation $reservation,
        ?array $roomIds = null,
    ): HotelReservation;
}
```

Logic:
1. Validate reservation status is CONFIRMED
2. If room_ids provided, assign rooms via AssignRoomsAction
3. If no rooms assigned yet, auto-assign available rooms of the correct type
4. Set status to CHECKED_IN, set checked_in_at
5. Update assigned rooms status to OCCUPIED
6. Return reservation

#### CheckOutGuestAction
**File:** `app/Actions/HotelManager/CheckOutGuestAction.php`

```php
final class CheckOutGuestAction
{
    /**
     * @throws ReservationNotCheckedInException
     */
    public function __invoke(HotelReservation $reservation): HotelReservation;
}
```

Logic:
1. Validate reservation status is CHECKED_IN
2. Set status to CHECKED_OUT, set checked_out_at
3. Update assigned rooms status to AVAILABLE
4. Update assigned rooms housekeeping_status to DIRTY
5. Create housekeeping logs (from_status=CLEAN, to_status=DIRTY) for each room
6. Return reservation

#### GenerateFolioAction
**File:** `app/Actions/HotelManager/GenerateFolioAction.php`

```php
final class GenerateFolioAction
{
    /**
     * Generate the final bill (Order) for a reservation.
     * Consolidates: room charges (if not prepaid) + all extras.
     */
    public function __invoke(
        HotelReservation $reservation,
        Employee $employee,
    ): Order;
}
```

Logic:
1. Check if room charges already prepaid (order_item_id not null)
2. Create Order with status WAITING_PAYMENT, linked to hotel + reservation
3. If room charges not prepaid: add OrderItem for HotelReservation (room total)
4. For each HotelReservationExtra not yet billed to an OrderItem:
   - Create OrderItem for HotelExtra with quantity + price
   - Link order_item_id back to HotelReservationExtra
5. Return Order

### 5.2 API — Check-in/out Endpoints

#### CheckoutReservationController
```
POST /v1/places/{place}/hotels/{hotel}/reservations/{reservation}/checkout
```
Calls CheckOutGuestAction + GenerateFolioAction. Returns folio Order.

Note: Check-in is handled via UpdateReservationStatusController (Phase 4) with status=CHECKED_IN, which delegates to CheckInGuestAction internally.

### 5.3 API — Payment Endpoints

All payment controllers follow the same pattern as Shop/Restaurant:

#### WalletPayReservationController
```
POST /v1/places/{place}/hotels/{hotel}/reservations/{reservation}/pay/wallet
```

#### StripePayReservationController
```
POST /v1/places/{place}/hotels/{hotel}/reservations/{reservation}/pay/stripe
```

#### PosPayReservationController
```
POST /v1/places/{place}/hotels/{hotel}/reservations/{reservation}/pay/pos
Body: receipt_photo (image), terminal_id (optional)
```
Delegates to existing ProcessPosPaymentAction.

#### CashPayReservationController
```
POST /v1/places/{place}/hotels/{hotel}/reservations/{reservation}/pay/cash
Body: receipt_photo (image), cash_given, change
```
Delegates to existing ProcessCashPaymentAction.

#### CryptoPayReservationController
```
POST /v1/places/{place}/hotels/{hotel}/reservations/{reservation}/pay/crypto
```

### 5.4 API — Invoice Endpoints

#### DownloadReservationInvoiceController
```
GET /v1/places/{place}/hotels/{hotel}/reservations/{reservation}/invoice
```
Returns invoice PDF for the paid order.

#### SendReservationInvoiceController
```
POST /v1/places/{place}/hotels/{hotel}/reservations/{reservation}/invoice/send
Body: email (optional, defaults to guest email)
```

### 5.5 Cancellation & Refund Logic

#### Cancellation Policy (Hotel-level configuration)

Fields on `Hotel` model (added in Phase 1 migration):
- `free_cancellation_hours` (unsigned int, default 24) — hours before check-in for free cancellation
- `cancellation_penalty_percentage` (unsigned tinyint, default 100) — percentage of first night charged as penalty

#### CancelReservationAction
**File:** `app/Actions/HotelManager/CancelReservationAction.php`

```php
final class CancelReservationAction
{
    /**
     * @throws ReservationNotCancellableException
     */
    public function __invoke(
        HotelReservation $reservation,
        ?string $reason = null,
        bool $waivePenalty = false,
    ): HotelReservation;
}
```

Logic:
1. Validate reservation can be cancelled (status PENDING or CONFIRMED)
2. Calculate penalty:
   - If PENDING (not yet confirmed): **no penalty** — free cancellation
   - If CONFIRMED + cancellation is ≥ `free_cancellation_hours` before check-in: **no penalty**
   - If CONFIRMED + cancellation is < `free_cancellation_hours` before check-in: **penalty = first night rate × `cancellation_penalty_percentage` / 100**
   - If `waivePenalty = true` (manager override): **no penalty**
3. Set status to CANCELLED, set cancelled_at, cancellation_reason
4. If reservation was prepaid (order_item_id not null):
   - **No penalty**: full refund via wallet credit-back (WalletTransaction with type REFUND)
   - **With penalty**: partial refund = prepaid amount − penalty amount
5. If reservation was NOT prepaid and penalty applies:
   - Create Order + OrderItem for penalty charge (to be collected)
6. Free any assigned rooms (set status back to AVAILABLE)
7. Return reservation

#### RefundReservationAction
**File:** `app/Actions/HotelManager/RefundReservationAction.php`

```php
final class RefundReservationAction
{
    /**
     * Credit back the guest's wallet for a cancelled prepaid reservation.
     * Same pattern as Shop/Restaurant refunds: wallet credit-back, no OrderStatus change.
     */
    public function __invoke(
        HotelReservation $reservation,
        Money $refundAmount,
    ): WalletTransaction;
}
```

#### Cancellation API (Manager)

The manager uses `UpdateReservationStatusController` (Phase 4) with `status=CANCELLED`, which now delegates to `CancelReservationAction` internally. Additional body params: `reason` (optional), `waive_penalty` (boolean, default false).

#### Cancellation Policy Info Endpoint

```
GET /v1/places/{place}/hotels/{hotel}/reservations/{reservation}/cancellation-policy
```
Returns: penalty amount (if any), free cancellation deadline, whether penalty can be waived.

### 5.6 Form Requests

- `PosPaymentRequest` — Reuse from Shop Manager
- `CashPaymentRequest` — Reuse from Shop Manager
- `SendInvoiceRequest` — Reuse from Shop Manager

### 5.7 Exceptions

- `ReservationNotConfirmedException` — Can't check in without CONFIRMED status
- `NoRoomAssignedException` — Can't check in without room assignment (and no available rooms for auto-assign)
- `ReservationNotCheckedInException` — Can't check out without CHECKED_IN status
- `ReservationNotCancellableException` — Can't cancel a reservation that is already checked-in, checked-out, or already cancelled

### 5.8 Tests

**File:** `tests/Feature/Controllers/HotelManager/CheckinCheckoutTest.php`

- [ ] Check-in sets status to CHECKED_IN and rooms to OCCUPIED
- [ ] Check-in auto-assigns room when none assigned
- [ ] Check-in fails when reservation is not CONFIRMED
- [ ] Check-out sets status to CHECKED_OUT and rooms to AVAILABLE + DIRTY
- [ ] Check-out creates housekeeping logs
- [ ] Checkout generates folio Order with correct items
- [ ] Folio excludes room charges when prepaid
- [ ] Folio includes all extras
- [ ] Payment via POS works with receipt photo
- [ ] Payment via Cash works with amounts
- [ ] Payment via Wallet works
- [ ] Payment via Stripe creates payment intent
- [ ] Invoice can be downloaded after payment
- [ ] Invoice can be sent to alternate email
- [ ] Cancellation of PENDING reservation has no penalty
- [ ] Cancellation outside free period applies penalty (first night rate × penalty %)
- [ ] Cancellation within free period has no penalty
- [ ] Manager can waive cancellation penalty
- [ ] Prepaid reservation gets wallet refund on cancellation (full or partial)
- [ ] Non-prepaid reservation with penalty creates penalty Order
- [ ] Cancelled reservation frees assigned rooms
- [ ] Cancellation policy endpoint returns correct penalty info
- [ ] Cannot cancel CHECKED_IN or CHECKED_OUT reservation

---

## Files Created/Modified

| Action | File |
|---|---|
| Create | `app/Actions/HotelManager/CheckInGuestAction.php` |
| Create | `app/Actions/HotelManager/CheckOutGuestAction.php` |
| Create | `app/Actions/HotelManager/GenerateFolioAction.php` |
| Create | `app/Actions/HotelManager/CancelReservationAction.php` |
| Create | `app/Actions/HotelManager/RefundReservationAction.php` |
| Create | `app/Http/Controllers/Api/Employee/HotelManager/CheckoutReservationController.php` |
| Create | `app/Http/Controllers/Api/Employee/HotelManager/WalletPayReservationController.php` |
| Create | `app/Http/Controllers/Api/Employee/HotelManager/StripePayReservationController.php` |
| Create | `app/Http/Controllers/Api/Employee/HotelManager/PosPayReservationController.php` |
| Create | `app/Http/Controllers/Api/Employee/HotelManager/CashPayReservationController.php` |
| Create | `app/Http/Controllers/Api/Employee/HotelManager/CryptoPayReservationController.php` |
| Create | `app/Http/Controllers/Api/Employee/HotelManager/DownloadReservationInvoiceController.php` |
| Create | `app/Http/Controllers/Api/Employee/HotelManager/SendReservationInvoiceController.php` |
| Create | `app/Http/Controllers/Api/Employee/HotelManager/ShowCancellationPolicyController.php` |
| Create | `app/Exceptions/HotelManager/ReservationNotCancellableException.php` |
| Modify | `routes/api.php` — add check-in/out, payment, invoice, cancellation routes |
| Create | `tests/Feature/Controllers/HotelManager/CheckinCheckoutTest.php` |

---

## Acceptance Criteria

- [ ] Check-in flow works end-to-end (with auto-assignment)
- [ ] Check-out flow updates room status + housekeeping
- [ ] Folio generation consolidates all charges correctly
- [ ] All 5 payment methods functional
- [ ] Cancellation penalty calculation correct (free period + penalty %)
- [ ] Prepaid reservation refund via wallet credit-back
- [ ] Manager can waive cancellation penalty
- [ ] Cancellation policy endpoint returns correct info
- [ ] Invoice download and send functional
- [ ] All Swagger annotations present
- [ ] All tests pass
- [ ] Pint + PHPStan pass on new/modified files
