import { buildPaginatedResult, DEFAULT_PAGE_SIZE, err, ok, type PaginationInput, type ReadonlyDB, } from "@tailor-platform/erp-kit/core"; import type { DB } from "../generated/kysely-tailordb"; import { ShiftNotFoundError } from "../lib/errors.generated"; type ShiftPlacementOrderByField = "assignedAt"; export interface ListShiftPlacementsByShiftInput extends PaginationInput { shiftId: string; /** Include SUPERSEDED / CANCELLED placements (the swap history). Defaults to false. */ includeInactive?: boolean; } /** * Function: listShiftPlacementsByShift * Description: Returns who is placed on a given Shift, paginated — answers * "who is staffing this slot" for team shifts (which may carry more than one * placement), open-shift fills, and 応援/multi-site coverage. */ export async function run(db: ReadonlyDB, input: ListShiftPlacementsByShiftInput) { const shift = await db .selectFrom("Shift") .selectAll() .where("id", "=", input.shiftId) .executeTakeFirst(); if (!shift) { return err(new ShiftNotFoundError(input.shiftId)); } const limit = input.limit ?? DEFAULT_PAGE_SIZE; const offset = input.offset ?? 0; const orderBy = input.orderBy ?? "assignedAt"; const orderDirection = input.orderDirection ?? "asc"; // ACTIVE only by default. Callers that forget this filter double-count a swapped slot — // predicted-vs-actual would report the outgoing worker as a no-show and the incoming one twice // — so the safe reading is the default and history is opt-in. // // Unlike the by-assignment queries this does *not* also exclude withdrawn slots: the caller // named one shift, and the detail view of a withdrawn slot still has to show who had been on // it. "Is this person placed anywhere" is the question that must exclude them. let placements = db.selectFrom("ShiftPlacement").selectAll().where("shiftId", "=", input.shiftId); if (input.includeInactive !== true) { placements = placements.where("status", "=", "ACTIVE"); } const shiftPlacements = await placements .orderBy(orderBy, orderDirection) // Total order: assignedAt is not unique when a batch places several people at once. .orderBy("id", "asc") .limit(limit + 1) .offset(offset) .execute(); return ok(buildPaginatedResult(shiftPlacements, limit)); }