# Walk-Through Check-Ins Backend

## Purpose

`walkthrough_checkins` stores structured classroom-observation ("walk-through") records for
director-level staff. Each record captures the observed teacher, classroom, observing director,
date/time, seven rated categories with optional per-category comments, and overall notes. The
backend owns tenant scope, campus scope, creator ownership, validation, and role-gated access.

## Slice Files (by layer)

- Route: `src/routes/walkthrough_checkins.ts` (thin wiring; `GET /`, `POST /`, `DELETE /:id`).
- Controller: `src/api/controllers/walkthrough_checkins.controller.ts` (custom — not the CRUD
  factory; uses `paramStr` for the id param).
- Service (BLL): `src/services/walkthrough_checkins.ts` (+ `walkthrough_checkins.types.ts` for
  `WalkthroughInput` / `WalkthroughFilter`).
- Repository (DAL): queries run through `db.walkthrough_checkins` inside the service (no separate
  `db/api/walkthrough_checkins.ts`).
- Model: `src/db/models/walkthrough_checkins.ts`.
- Shared used: `services/shared/validate.ts` (`nullableString`); `db/with-transaction.ts`
  (`withTransaction`); `shared/constants/pagination.ts` (`resolvePagination`);
  `services/shared/access.ts` (`getOrganizationIdOrGlobal`, `hasFeaturePermission`,
  `hasGlobalAccess`, `requireUserId`); `shared/errors/*` (`ForbiddenError`,
  `ValidationError`).

## API

All routes require JWT authentication. Base path mounted at `/api/walkthrough_checkins`.

- `GET /api/walkthrough_checkins` -> `200` `{ rows, count }`. Optional query `teacher_name`, plus
  `limit` / `page` (paginated via `resolvePagination`). Returns check-ins visible to the current
  manager, ordered by `check_in_date` desc then `createdAt` desc.
- `POST /api/walkthrough_checkins` -> `201`. Request body wrapped as `{ data: <WalkthroughInput> }`.
  Returns the created check-in DTO.
- `DELETE /api/walkthrough_checkins/:id` -> `200` `{ deletedCount }`. Deletes one check-in visible
  to the current manager.

## Access Rules

- All operations require `MANAGE_WALKTHROUGH`. Role-seeded permissions are only
  the baseline grants. `custom_permissions` can grant it and
  `custom_permissions_filter` can remove it for non-global users.
- Campus visibility: roles in `WALKTHROUGH_TENANT_WIDE_ROLE_NAMES` (`super_admin`,
  `system_admin`, `owner`, `superintendent`) or `globalAccess` see all org records;
  other managers (e.g. `director`) are restricted to their own staff campus on `list` and
  `delete` via `campusScope`.

## Tenant Scope

- Organization is resolved via `getOrganizationIdOrGlobal`: global access users bypass the org
  filter and see check-ins across all organizations; regular users are bound to their org.
- `campusId` is resolved from the current user's direct campus scope via `getCampusId`, else `null`.
- On create, `createdById` is required from the current user (`requireUserId`); `updatedById` from
  the current user.

## Data Contract

- Required mutation fields: `teacher_name`, `classroom`, `director_name`, `check_in_date`,
  `check_in_time` (non-empty strings); and the seven rating fields `attitude_rating`,
  `classroom_management_rating`, `cleanliness_rating`, `vibes_rating`, `team_dynamics_rating`,
  `emergency_exit_rating`, `lesson_plan_rating` (finite numbers). Invalid input raises
  `ValidationError`.
- Optional mutation fields: the matching per-category comments (`attitude_comment`,
  `classroom_management_comment`, `cleanliness_comment`, `vibes_comment`, `team_dynamics_comment`,
  `emergency_exit_comment`, `lesson_plan_comment`) and `overall_notes`. Comments are normalized via
  `nullableString` (trimmed, blank -> `null`).
- DTO fields: `id`, the five required strings, the seven `*_rating` and matching `*_comment` fields,
  `overall_notes`, `organizationId`, `campusId`, `createdById`, `updatedById`, `createdAt`,
  `updatedAt`.
- Model columns: the five required strings are TEXT not null except `check_in_date` (DATEONLY) and
  `check_in_time` (TIME); the seven `*_rating` are INTEGER not null; the seven `*_comment` and
  `overall_notes` are TEXT nullable; `importHash` (unique). Tenant/audit columns: `organizationId`
  (not null), `createdById` (not null), `campusId` (nullable), `updatedById` (nullable). The model
  is `paranoid` (soft delete via `deletedAt`) and uses `freezeTableName`.
- Associations: `belongsTo` organizations (`organization`), campuses (`campus`), users
  (`createdBy`, `updatedBy`). Note: there is no `user` association on this model.

## Behavior / Notes

- `create` runs inside `withTransaction`; required string fields are trimmed before persistence.
- `list` is paginated with shared defaults (`resolvePagination`).
- `remove` is a hard `destroy` scoped by `id` + organization + `campusScope`, returning the number
  of rows deleted (no transaction wrapper).

## Tests

None yet (no `walkthrough_checkins` unit/e2e test in `src/`).

## Related

- Frontend: `frontend/docs/walkthrough-integration.md`.
- Related slices: `frame-entries.md` (similar editor-role-gated, campus-resolved director
  workflow), `safety-quiz-results.md`, `personality-quiz-results.md`.
