# Session Rollback API — Implementation Spec

## Overview

Admin-scoped endpoint that reverses a closed billing session. Restores lot baselines, deletes archived records, cleans up dependent data, and sends void emails to tenants if bills were already delivered.

## Pending decision
Should the void bill be retailed & soft deleted for audit?
What should happen to common consumption entries?

---

## Endpoint

```
DELETE /api/admin/session/rollback
```

**Auth:** JWT admin (role_id = 2). Scoped to `service_provider_id` from the JWT token.

**Request body:**
```json
{
  "uuid": "058c56e8-4110-4763-83f1-006035af9d68"
}
```

**Validation:**
- `uuid` — required, valid UUID format

---

## Decision Rules (evaluated in order)

### Rule 1 — Session must exist and belong to this service provider
Query `archived_sessions` for the given UUID and the authenticated user's `service_provider_id`. If no rows found, return 404.

### Rule 2 — Must be the latest session for each affected lot (hard block)
For every `lot_id` in the session, check whether a more recent `archived_sessions` row exists (any row with `survey_start_date` greater than the current session's `survey_start_date` for the same lot). If any lot has a newer session, reject with 422 and list the blocking lot numbers. The admin must roll back sessions in reverse chronological order.

Error response:
```json
{
  "status": false,
  "message": "Session cannot be rolled back: newer sessions exist for the following lots.",
  "lots": ["A1", "A2", "B3"]
}
```

### Rule 3 — Delivered mails trigger void email job (not a block)
Check `mail_scheduled_histories` for any row linked to an `archived_session_id` in this UUID where `mail_scheduled_status = 2` (delivered). If found, queue a `SendVoidInvoiceJob` for each affected lot before proceeding. Rollback is not blocked — void emails are fire-and-queued.

---

## Execution Steps (all inside a single DB transaction)

1. Load all `archived_sessions` rows for the UUID. Confirm at least one exists (re-check inside transaction).
2. Delete `mail_scheduled_histories` rows linked to any `archived_session_id` in the UUID set. (Required: hard FK constraint on `archived_session_id` → `archived_sessions.id` will block the next step otherwise.)
3. Delete `common_consumption` rows where `archived_session_uuid = :uuid`.
4. Update `lots.previous_unit`: for each lot in the session, restore `lots.previous_unit = archived_sessions.previous_unit` (the opening reading for that session).
5. Delete `archived_sessions` rows where `uuid = :uuid` and `service_provider_id = :spid`.
6. Commit.

If any step throws, roll back the transaction. Do not dispatch void email jobs until the transaction is confirmed committed.

---

## Void Email Job — `SendVoidInvoiceJob`

**Dispatched after** the DB transaction commits, only when delivered mails existed.

**Payload per job:**
- `lot_id`
- `site_id`
- `service_type_id`
- Billing period (`survey_start_date`, `survey_end_date` — captured before deletion)
- `net_bill_value` — captured before deletion
- Recipient email — resolved from `LotOwner` via `lot_no`

**Job behaviour:**
- Uses the same mail driver as the standard billing mail (ZeptoMail)
- Template: "Invoice Void" — states the invoice for the period is cancelled, no action required
- On failure: logs error, does not re-trigger rollback (rollback already committed)
- Retry policy: standard Laravel queue retries (3 attempts, backoff)

**Template data needed:**
- Lot number
- Site name
- Service type name
- Billing period (formatted date range)
- Original invoice amount
- Void reason: "Administrative correction"

---

## Response

**Success (no delivered mails):**
```json
{
  "status": true,
  "message": "Session rolled back successfully.",
  "lots_restored": 6,
  "void_emails_queued": 0
}
```

**Success (void emails queued):**
```json
{
  "status": true,
  "message": "Session rolled back. Void invoices have been queued for delivery.",
  "lots_restored": 6,
  "void_emails_queued": 6
}
```

**Hard block (non-latest session):**
```json
{
  "status": false,
  "message": "Session cannot be rolled back: newer sessions exist for the following lots.",
  "lots": ["A1", "A2"]
}
```

**Not found:**
```json
{
  "status": false,
  "message": "Session not found."
}
```

---

## Files to Create

| File | Purpose |
|---|---|
| `app/Http/Controllers/V1/Admin/SessionRollback.php` | Controller — validates, calls repo, dispatches job |
| `app/Repository/SessionRollbackRepo.php` | All DB logic — pre-checks, transaction, data capture |
| `app/Jobs/SendVoidInvoiceJob.php` | Queued job — sends void email per lot |
| `resources/views/email/void_invoice.blade.php` | Void email template |

**Routes file:** `routes/api.php` — add inside the admin middleware group:
```php
Route::delete('session/rollback', [SessionRollback::class, 'rollback']);
```

---

## Files to Modify

| File | Change |
|---|---|
| `app/Repository/SessionRepo.php` | No changes needed — rollback is a separate repo |
| `routes/api.php` | Add the DELETE route to the admin group |

---

## Key Data Notes (derived from codebase)

- `archived_sessions.service_provider_id` — always present, use for scoping
- `archived_sessions.previous_unit` — the lot's opening reading for the session; this is what gets restored to `lots.previous_unit`
- `lots.current_unit` — redundant field, intentionally not restored on rollback
- `mail_scheduled_histories.archived_session_id` — hard FK, must delete before archived_sessions
- `mail_scheduled_histories.mail_scheduled_status` — `2` = delivered
- `common_consumption.archived_session_uuid` — no FK, soft reference, must delete manually
- Session close sets `lots.previous_unit = archived_sessions.current_unit` (SessionRepo.php line 748)
- UUID collation: handled at the Laravel/PDO level — no `COLLATE` clauses needed in Eloquent queries

---

## Out of Scope for This Release

- Rolling back non-latest sessions (blocked by Rule 2; requires cascade rollback design decision)
- Audit log table for rollback events (log to Laravel log file only for now)
- Rollback of open (not yet closed) sessions — use the existing `discard` endpoint
