# Litigation: Hearings, Roster & Deadlines — BAMS Legal

> The highest-liability module in the system. Covers court appearances, the
> adjournment chain, court roster with clash detection, and the deadline/
> limitation-date engine. Staff-only.

---

## Table of Contents

1. [Hearing](#hearing)
2. [Witness & Testimony](#witness--testimony)
3. [Hearing Attendance (Roster)](#hearing-attendance-roster)
4. [Matter Deadline](#matter-deadline)
5. [Notes & Guidelines](#notes--guidelines)

---

## Hearing

### Schema

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `ulid` | ULID | Auto | — | Public identifier |
| `firm_id` | FK | Yes | — | |
| `matter_id` | FK | Yes | — | |
| `court_id` | FK | No | `null` | |
| `hearing_date` | Date | Yes | — | |
| `hearing_time` | Time | No | `null` | |
| `type` | Enum | Yes | — | `mention`, `hearing`, `trial`, `ruling`, `judgment`, `adoption`, `cmc`, `motion` |
| `purpose` | String | No | `null` | e.g. "Adoption of written address" |
| `coram` | String | No | `null` | Presiding judge(s) |
| `outcome` | Enum | No | `null` | `held`, `adjourned`, `struck_out`, `ruling`, `judgment`, `settled`, `no_sitting` — null until recorded |
| `proceedings` | Text | No | `null` | What happened in court |
| `remarks` | Text | No | `null` | Lawyer's private observations — **never shown to clients (see M11)** |
| `next_adjourned_date` | Date | No | `null` | |
| `next_purpose` | String | No | `null` | |
| `status` | Enum | Yes | `scheduled` | `scheduled`, `held`, `missed` |

### Admin Routes

**Base:** `/admin/hearings` · **Auth:** session, permission `hearings.view`

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/hearings` | Firm-wide list (filters: date range, court, lawyer, matter, status) |
| `GET` | `/admin/hearings/:id` | Get hearing detail |
| `POST` | `/admin/matters/:matterId/hearings` | Schedule a new hearing |
| `PATCH` | `/admin/hearings/:id` | Update hearing details (before it's held) |
| `POST` | `/admin/hearings/:id/outcome` | Record the outcome — triggers the adjournment chain |
| `DELETE` | `/admin/hearings/:id` | Cancel a scheduled hearing |
| `GET` | `/admin/hearings/calendar` | Calendar JSON feed |

### Request & Response Examples

**Record an outcome** — `POST /admin/hearings/:id/outcome`

```json
{
  "outcome": "adjourned",
  "proceedings": "Matter came up for continuation of trial. Plaintiff's counsel absent.",
  "remarks": "Need to confirm PW2's availability before the next date.",
  "next_adjourned_date": "2026-09-14",
  "next_purpose": "Continuation of trial"
}
```

```json
{
  "success": true,
  "data": {
    "hearing": { "id": 501, "status": "held", "outcome": "adjourned" },
    "next_hearing": { "id": 512, "hearing_date": "2026-09-14", "status": "scheduled" }
  },
  "message": "Outcome recorded. Next hearing scheduled for 14 Sep 2026."
}
```

### Validation Rules

| Field | Rules |
|---|---|
| `hearing_date` | Required on create. |
| `type` | Required. Must be a valid `HearingType`. |
| `outcome` | Required when recording an outcome. Must be a valid `HearingOutcome`. |
| `next_adjourned_date` | Optional, but if present must be after `hearing_date`. |

---

## Witness & Testimony

### Schema (Witness)

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `matter_id` | FK | Yes | — | |
| `name` | String | Yes | — | |
| `side` | String | No | `null` | `plaintiff`, `defence`, `court` |
| `phone` | String | No | `null` | |
| `summary` | Text | No | `null` | What they can testify to |
| `status` | Enum | No | `pending` | `pending`, `testified`, `discharged` |

### Schema (Testimony)

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `witness_id` | FK | Yes | — | |
| `hearing_id` | FK | No | `null` | |
| `testified_on` | Date | Yes | — | |
| `testimony` | Text | Yes | — | |
| `cross_examination` | Text | No | `null` | |
| `recorded_by` | FK (User) | Yes | — | |

### Admin Routes

| Method | Endpoint | Description |
|---|---|---|
| `POST` | `/admin/matters/:id/witnesses` | Add a witness |
| `PATCH` | `/admin/witnesses/:id` | Update witness status |
| `POST` | `/admin/witnesses/:id/testimonies` | Record testimony |

### Notes & Guidelines

- Testimony/proceedings notes are internal working documents — **never**
  surfaced through the M11 client portal (only the hearing's `outcome` and
  dates are client-visible).

---

## Hearing Attendance (Roster)

### Schema

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `hearing_id` | FK | Yes | — | |
| `user_id` | FK | Yes | — | The assigned lawyer |
| `role` | Enum | No | `lead` | `lead`, `supporting`, `holding_brief` |
| `status` | Enum | No | `assigned` | `assigned`, `confirmed`, `attended`, `missed`, `covered` |
| `covered_by` | FK (User) | No | `null` | Set if another lawyer stood in |
| `notes` | Text | No | `null` | |

### Admin Routes

**Base:** `/admin/roster` · **Auth:** session, permission `roster.view` / `roster.manage`

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/roster` | Roster board (`?date=&view=day\|week`) |
| `POST` | `/admin/hearings/:id/assign` | Assign a lawyer — returns a clash warning if applicable |
| `DELETE` | `/admin/hearings/:id/assign/:userId` | Unassign |
| `PATCH` | `/admin/attendances/:id/status` | Update attendance status |
| `GET` | `/admin/roster/print` | Printable daily cause list (queued PDF) |
| `GET` | `/admin/roster/ical/:userUlid` | Signed iCal feed for a lawyer's calendar app |

### Request & Response Examples

**Assign with a clash** — `POST /admin/hearings/:id/assign`

```json
{ "user_id": 12, "role": "lead" }
```

```json
{
  "success": true,
  "warning": {
    "code": "SCHEDULE_CLASH",
    "message": "This lawyer is also assigned to Federal High Court on the same day (MAT/2026/0031). Proceed anyway?"
  },
  "data": { "attendance_id": 901, "status": "assigned" }
}
```

### Validation Rules

| Field | Rules |
|---|---|
| `user_id` | Required. Cannot duplicate an existing assignment on the same hearing. |
| Clash detection | Same user, same `hearing_date`, different `court_id` → warning only, never a hard block. Override is recorded in `notes`. |
| `status = missed` without `covered_by` | Escalates a notification to all `dashboard.executive` holders. |

### Notes & Guidelines

- `roster.ical` feed URLs are **signed and expiring** — never a guessable
  static URL, since they expose a lawyer's schedule.

---

## Matter Deadline

### Schema

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `ulid` | ULID | Auto | — | Public identifier |
| `firm_id` | FK | Yes | — | |
| `matter_id` | FK | Yes | — | |
| `title` | String | Yes | — | |
| `type` | Enum | Yes | — | `limitation`, `filing`, `appeal_window`, `undertaking`, `custom` |
| `due_date` | Date | Yes | — | |
| `reminder_offsets` | JSON | No | `[30,14,7,1]` | Days-before to notify |
| `status` | Enum | No | `open` | `open`, `completed`, `missed`, `waived` |
| `completed_at` | Timestamp | No | `null` | |
| `owner_id` | FK (User) | No | `null` | Falls back to matter team if unset |
| `notes` | Text | No | `null` | |

### Admin Routes

**Base:** `/admin/matters/:matterId/deadlines` · **Auth:** session, permission `deadlines.view` / `deadlines.manage`

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/matters/:id/deadlines` | List a matter's deadlines |
| `POST` | `/admin/matters/:id/deadlines` | Create a deadline |
| `PATCH` | `/admin/deadlines/:id` | Update |
| `PATCH` | `/admin/deadlines/:id/complete` | Mark completed |
| `PATCH` | `/admin/deadlines/:id/waive` | Mark waived (reason required) |
| `GET` | `/admin/deadlines/upcoming` | Firm-wide upcoming deadlines (dashboard feed) |

### Request & Response Examples

**Create a deadline** — `POST /admin/matters/:id/deadlines`

```json
{
  "title": "File Notice of Appeal",
  "type": "appeal_window",
  "due_date": "2026-10-01",
  "owner_id": 12
}
```

```json
{
  "success": true,
  "data": { "id": 77, "status": "open", "due_date": "2026-10-01" },
  "message": "Deadline created. Reminders scheduled at 30/14/7/1 days before."
}
```

### Validation Rules

| Field | Rules |
|---|---|
| `title` | Required. Max 255. |
| `type` | Required. Valid enum value. |
| `due_date` | Required. |
| `waive` reason | Required, min 10 characters — logged permanently. |

### Notes & Guidelines

- **⚠️ This is the single highest-liability model in the system.** A missed
  limitation deadline is malpractice.
- Reminders are sent by a scheduled command with an idempotency guard
  (`deadline_reminders_sent`) — each offset fires **exactly once**, even if the
  command runs more than once in a window.
- Any open deadline with `due_date` within 14 days shows a **red banner** on
  the matter page, independent of whether the reminder job has run — a visual
  safety net that doesn't depend on the notification pipeline succeeding.
- Recording a `judgment` outcome on a hearing offers (never forces) auto-
  creation of an `appeal_window` deadline based on the firm's configured
  appeal-window days (Setting, M01).
