# People (HR-lite) & System Admin — BAMS Legal

> Staff records supporting fee-scale seniority lookups, plus the operational
> admin surface (users, audit, backups, imports) needed to run the system
> day to day. Staff-only.

---

## Table of Contents

1. [Staff](#staff)
2. [Leave Request](#leave-request)
3. [Audit Review](#audit-review)
4. [Backups](#backups)
5. [Data Import](#data-import)
6. [Notes & Guidelines](#notes--guidelines)

---

## Staff

### Schema

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `user_id` | FK | Yes | — | 1:1 with `users` |
| `staff_number` | String | Auto | — | Via NumberingService |
| `seniority` | Enum | No | `null` | `Partner`, `Senior Counsel`, `Counsel`, `Paralegal` — **feeds M07 fee scales** |
| `office_id` | FK | No | `null` | |
| `date_joined` | Date | No | `null` | |
| `phone` | String | No | `null` | |

### Admin Routes

**Base:** `/admin/staff` · **Auth:** session, permission `users.manage`

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/staff` | List staff records |
| `GET` | `/admin/staff/:id` | Get staff detail |
| `POST` | `/admin/staff` | Create a staff record for an existing user |
| `PATCH` | `/admin/staff/:id` | Update (seniority, office, phone) |

### Validation Rules

| Field | Rules |
|---|---|
| `user_id` | Required. Must not already have a staff record. |
| `seniority` | Optional, but required before the user can be assigned appearance-fee claims at a non-default rate (M07). |

### Notes & Guidelines

- `seniority` is the single field M07's `appearance_fee_scales` lookup depends
  on — keep it accurate; a missing value falls back to the null-seniority
  (any) rate.

---

## Leave Request

### Schema

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `staff_id` | FK | Yes | — | |
| `start_date` / `end_date` | Date | Yes | — | |
| `type` | Enum | No | `annual` | `annual`, `sick`, `other` |
| `reason` | Text | No | `null` | |
| `status` | Enum | No | `pending` | `pending`, `approved`, `rejected` |
| `approved_by` | FK (User) | No | `null` | |

### Admin Routes

**Base:** `/admin/leave-requests` · **Auth:** session, own requests + approver permission for others

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/leave-requests` | List (own, or all with approval permission) |
| `POST` | `/admin/leave-requests` | Submit a request |
| `PATCH` | `/admin/leave-requests/:id/status` | Approve/reject |

### Request & Response Examples

**Submit** — `POST /admin/leave-requests`

```json
{ "start_date": "2026-08-10", "end_date": "2026-08-17", "type": "annual", "reason": "Family event" }
```

```json
{ "success": true, "data": { "id": 41, "status": "pending" }, "message": "Leave request submitted." }
```

### Validation Rules

| Field | Rules |
|---|---|
| `end_date` | Must be on or after `start_date`. |
| Approval | Cannot approve your own request. |

### Notes & Guidelines

- Approved leave surfaces as an "unavailable" warning when someone tries to
  assign that lawyer to a hearing on the M04 roster — shared read, not
  duplicated logic.

---

## Audit Review

Read-only view over `activity_log` plus the security events log (login
attempts, permission denials, 2FA changes, portal access grants/revocations,
document confidentiality changes, data exports).

### Admin Routes

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

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/audit` | Searchable log (filters: user, model type, event category, date range) |
| `GET` | `/admin/audit/:id` | Get a single event's before/after diff |

### Request & Response Examples

**Search** — `GET /admin/audit?category=security&from=2026-07-01`

```json
{
  "success": true,
  "data": [
    { "id": 9012, "event": "login_failed", "user": "unknown", "ip": "102.89.x.x", "at": "2026-07-05T08:12:00Z" },
    { "id": 9015, "event": "document_confidentiality_changed", "user": "Aisha Bello", "at": "2026-07-05T09:40:00Z" }
  ]
}
```

### Notes & Guidelines

- This view is read-only by design — audit records are never editable or
  deletable through any route, including by a Firm Admin.

---

## Backups

Thin admin UI wrapper over the scheduled backup system.

### Admin Routes

**Base:** `/admin/backups` · **Auth:** session, permission `backups.manage` (requires 2FA)

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/backups` | Last backup status/size, history list |
| `POST` | `/admin/backups/run` | Trigger a manual backup now |
| `GET` | `/admin/backups/:id/download` | Download a backup file (itself audit-logged) |

### Notes & Guidelines

- An alert banner appears in the admin UI if the last successful backup is
  older than 48 hours — this is checked on every admin page load, not just
  the backups page.

---

## Data Import

### Admin Routes

**Base:** `/admin/import/{matters|clients|hearings}` · **Auth:** session, permission `firm.manage`

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/import/:type/template` | Download the Excel template for this import type |
| `POST` | `/admin/import/:type/preview` | Upload + validate, returns a row-by-row error preview without committing |
| `POST` | `/admin/import/:type/commit` | Commit a previously previewed, validated file |

### Request & Response Examples

**Preview** — `POST /admin/import/clients/preview`

```json
{
  "success": true,
  "data": {
    "total_rows": 340, "valid_rows": 335, "invalid_rows": 5,
    "errors": [{ "row": 12, "field": "email", "message": "Invalid email format" }]
  }
}
```

**Commit** — `POST /admin/import/clients/commit`

```json
{ "success": true, "data": { "created": 335, "skipped": 5 }, "message": "Import complete." }
```

### Validation Rules

| Field | Rules |
|---|---|
| Commit | Only accepted against a file that has already passed `preview` — no direct-to-commit path. |
| Transaction | Commit runs inside a single DB transaction — a partial failure leaves no orphaned rows. |

---

## Notes & Guidelines (module-wide)

- **Payroll is feature-flagged and off by default** (`feature('payroll')`).
  When disabled, its routes 404 and no menu entry appears — most firms this
  size run payroll externally; build it only on confirmed request.
- System health (`GET /admin/system`) surfaces queue health, failed job
  counts, disk usage, and last-run timestamps for scheduled commands
  (deadline reminders, digests, backups) — so a Firm Admin can see at a glance
  whether the automated plumbing from other modules is actually running.
