# Test Cases — Corporate / SACCO Membership Module

Eighteen functional test cases from spec §17. Each block lists **steps**,
**expected result**, and **where to look in the UI / DB**.

> Prerequisite: run `php artisan db:seed --class=CorporateSeeder` to create
> the `MKR` scheme with 5 principals + dependants + 3 months of premium
> history.

---

## TC-01 · Register corporate account MKR

**Steps**
1. Visit `/corporate/accounts/create`.
2. Code = `MKR`, Name = `Mkulima Kweli SACCO`, Status = `Active`.
3. Monthly premium principal = `1500`, grace period = `7` days.
4. Pilot start = today, pilot end = today + 3 months.
5. Save.

**Expected**
- Row in `corporate_accounts` with the entered values.
- Audit entry with `action=scheme_created`.
- Redirect to scheme profile.

---

## TC-02 · Link existing patient as principal

**Steps**
1. From scheme profile, click **Enrol Principal**.
2. Search a patient that exists in `patients` table; pick.
3. Verification 1 = National ID, Value 1 = patient's ID.
4. Verification 2 = Phone, Value 2 = patient's phone.
5. Save.

**Expected**
- Row in `corporate_members` with `member_type=principal`.
- `member_number` follows pattern `MKR-00001`, `MKR-00002`, …
- Audit entry `action=member_enrolled`.
- ❌ The `patients` table is unchanged (no new patient).

---

## TC-03 · Add dependant

**Steps**
1. From scheme profile, click **Add Dependant**.
2. Search a different patient.
3. Pick a principal to link to.
4. Relationship = `Spouse`.
5. Verification 1 = Birth Certificate, Verification 2 = DOB.
6. Save.

**Expected**
- Row in `corporate_members` with `member_type=dependant`,
  `principal_member_id` set.
- Audit entry `action=dependant_enrolled`.

---

## TC-04 · Detect duplicate dependant

**Steps**
1. Try to enrol a patient who is already a member of the scheme.
2. Try to enrol a dependant whose principal already has
   `max_dependants_per_principal` (e.g. 6).
3. Try to enrol two patients with same name + DOB under same principal.

**Expected**
- All three attempts fail with validation errors.
- No new row created.
- Audit not written (validation failure is not a sensitive action).

---

## TC-05 · Post monthly premium

**Steps**
1. Visit `/corporate/premiums?billing_month=2026-05`.
2. Find a row with status = `Due`. Click **Post**.
3. Amount = expected, Method = M-Pesa, Receipt = `MX12345`.
4. Submit.

**Expected**
- Row updated: `status=Paid`, `amount_paid`, `payment_date`, method, receipt
  populated.
- Member's `last_paid_month` cache updated to that billing month.
- `effectiveStatus()` recomputed.
- Audit entry `action=premium_posted`.

---

## TC-06 · Auto-generate due premiums

**Steps**
1. Run `php artisan corporate:generate-premiums --month=2026-06`.
2. Or click **Auto-generate Monthly Premiums** at `/corporate/premiums`.
3. Run it twice.

**Expected**
- One premium row per active principal in the scheme.
- Second run is idempotent: no duplicates (unique index on
  `member_id + billing_month`).
- Dependant premium component computed from `dependant_premium_rule` JSON.

---

## TC-07 · Mark member active after payment

**Steps**
1. Take a member currently in `Premium Due` status.
2. Post the current month's premium.

**Expected**
- Member status auto-updates to `Active`.
- `CorporateEligibilityService::forPatient($p)` now returns `Compliant` /
  `bill_to_pool = true`.

---

## TC-08 · Suspend member after missed payment past grace

**Steps**
1. Pick a member whose current month is unpaid.
2. Set system date past `due_date + grace_period_days` (or use the seeder's
   "missed last month" principal at index 3).
3. Visit `/corporate/eligibility` and check that patient.

**Expected**
- Status returns `Premium Due`, `bill_to_pool = false`.
- Banner shows `MKR SACCO | Premium Due | Bill as Cash`.

---

## TC-09 · Dependant follows principal status

**Steps**
1. Pick the dependant of a suspended principal.
2. Check eligibility.

**Expected**
- Dependant returns the principal's status (`Premium Due` / `Suspended`),
  not the dependant's own.
- `effectiveStatus()` resolution logic handles this in
  `CorporateMember.php`.

---

## TC-10 · Tag OPD bill to corporate pool

**Steps**
1. Patient is a compliant MKR member.
2. Through the existing OPD billing flow, create an OPD invoice.
3. The OPD controller calls `CorporateBillingService::tagInvoice($invoice,
   ['department' => 'OPD'])` after invoice creation.

**Expected**
- `invoices.corporate_account_id`, `corporate_member_id`,
  `corporate_amount`, `corporate_eligibility = Compliant` filled.
- Row in `corporate_utilizations` with department=OPD.
- Audit entry `action=bill_tagged`.

---

## TC-11 · Tag IPD bill to corporate pool

Same as TC-10 but department = `IPD`. Verify multiple line items roll up
into one utilization row (the row stores totals from the invoice).

---

## TC-12 · Tag maternity bill to corporate pool

Same as TC-10 but department = `Maternity`. Verify dashboard's Utilization
by Department chart shows the new row.

---

## TC-13 · Prevent corporate billing when premium due

**Steps**
1. Patient is a `Premium Due` member.
2. Create an invoice through existing billing. Controller calls
   `tagInvoice()`.

**Expected**
- `tagInvoice()` returns `null`.
- Invoice has `corporate_account_id=NULL` — it stays a cash bill.
- No row in `corporate_utilizations`.
- Banner displayed at billing time tells cashier "Bill as Cash".

---

## TC-14 · Reverse a bill and confirm utilization reduces

**Steps**
1. Take an invoice already tagged to MKR.
2. Cancel it through existing flow; cancellation handler calls
   `CorporateBillingService::reverseInvoice($invoice, $reason)`.

**Expected**
- Matching `corporate_utilizations` row has `status=Reversed`,
  `reversed_at`, `reversed_by`, `reversal_reason` populated.
- Invoice's `corporate_amount` cleared.
- Dashboard utilization total for that scheme/month decreases by the
  reversed amount.
- Audit entry `action=bill_reversed`.

---

## TC-15 · Reverse premium and confirm compliance recalculates

**Steps**
1. Pick a member currently `Compliant` because their May 2026 premium is
   `Paid`.
2. From `/corporate/premiums`, click **Reverse** on the May row.
3. Reason = "Bank reversal".

**Expected**
- Premium row: `status=Due`, `amount_paid=0`, `reversed_at`,
  `reversal_reason` set. Row is NOT deleted.
- Member's `last_paid_month` rolls back to prior paid month.
- `effectiveStatus()` recomputed → moves to `Grace Period` or `Premium Due`
  depending on dates.
- Audit entry `action=premium_reversed` with reason.

---

## TC-16 · Generate dashboard for month

**Steps**
1. Visit `/corporate/dashboard?corporate_account_id=1&billing_month=2026-05`.

**Expected**
- Stat cards: Enrolled, Active, Dependants, Premiums Expected / Collected /
  Due, Pool Balance, Utilization, Claims Ratio.
- Claims Ratio bar with risk band (Green ≤60, Amber 61-80, Red 81-100,
  Critical >100).
- Top 10 utilizers table.
- Utilization by Department chart.
- Overdue Premiums list.

---

## TC-17 · Generate 3-month pilot report

**Steps**
1. Visit `/corporate/reports/pilot-summary?corporate_account_id=1`.
2. Run.

**Expected**
- One row per pilot month between `pilot_start_date` and `pilot_end_date`.
- Columns: Month, Members, Expected, Collected, Collection Rate, Pool,
  Utilization, Pool Balance, Claims Ratio, Risk Band.
- Recommendation derived from the cumulative claims ratio:
  - `≤60` → Continue scheme as-is
  - `61-80` → Continue, monitor closely
  - `81-100` → Revise premium upward
  - `>100` → Revise benefits or suspend scheme.

---

## TC-18 · Export reports

**Steps**
1. From `/corporate/reports/utilization?corporate_account_id=1`:
   - Click **Export CSV** → downloads `.csv`.
   - Click **Export PDF** → downloads `.pdf` from `print.blade.php`.
   - Click **Export Excel** → downloads `.xlsx`.

**Expected**
- All three formats contain the same row set.
- An audit entry `action=report_export` is written with the report key,
  filters used, and user.

---

## Bonus checks (spec §14 safety rules)

| Rule                                                                 | How to verify |
| -------------------------------------------------------------------- | ------------- |
| If scheme is suspended, all members cash-paying                      | Set `corporate_accounts.scheme_status = 'Suspended'`; eligibility check should return `Scheme Closed` regardless of member status. |
| If patient changes corporate account, old linkage closed not deleted | Disable old membership (status=Closed) before enrolling on a new scheme. |
| No hard deletion                                                     | `DELETE` requests soft-delete and set `status=Closed`. Run `SELECT * FROM corporate_members WHERE deleted_at IS NOT NULL` to confirm rows preserved. |
| Manual override requires reason                                      | POST `/corporate/eligibility/override` without reason → 422 validation. |
| Audit trail captures every change                                    | After running TC-01 to TC-18, `SELECT COUNT(*) FROM corporate_audit_logs` should be ≥ the number of state-changing actions performed. |

---

## Pass criteria for go-live

All 18 test cases pass + all 5 bonus checks pass + the dashboard renders
within 2 seconds for a scheme with 100 members and one month of data.
