/**
* `GET /groups` / `GET /customers` で使うクエリ処理(分類マスタ。指示書AK 段階1)。
* 仕様の正は `d-watch-api.yaml` の /groups と /customers。
*
* `db.groups()` / `db.customers()` は全件を返すだけなので、絞り込みと並べ替えはここで組み立てる
* (`lib/schedules/query.ts` と同じ分担。`lib/db/` はデータ取得だけを担う)。
*
* ★★ページングしない★★(指示書AK §4-1 #1)—
* セレクトリストの選択肢を返すエンドポイントなので、`page` / `per_page` を受け取らない。
* 既定の `per_page=50` で切ると ★51件目以降の屋号がセレクトから黙って消える★。
* `meta` は返す(`CLAUDE.md` §9.3「一覧APIは必ず meta を返す」)。意味は
* `/reports/facility-performance` と同じ「page を持たない一覧」の書き方に合わせてある:
* `meta.total` = 絞り込み後の全件数 / `meta.page` = 常に 1 /
* `meta.per_page` = そのページに入っている件数(= `total`。1ページに全件返すため)
*
* ★純粋関数にすること★(`CLAUDE.md` §9.4)— DBアクセスやログ出力を混ぜない。
*/
import { parseMasterIdParam } from "@/lib/api/master-id";
import type { QueryParseResult } from "@/lib/api/query-result";
import type { Customer, Group, ListResponse } from "@/lib/types";
export interface CustomerQuery {
/** ホールディングスで絞り込み。★`null` = 絞らない★ */
groupId: number | null;
}
/**
* `/customers` のクエリパラメータをパースする。
*
* ★検証は `parseMasterIdParam()` の1本に任せる★(`lib/api/master-id.ts`)—
* ★`Number.parseInt()` で書き直さないこと★(`1abc` が `1` として通る。D-123)。
*/
export function parseCustomerQuery(
searchParams: URLSearchParams
): QueryParseResult<CustomerQuery> {
const groupId = parseMasterIdParam(searchParams.get("group_id"), "group_id");
if (!groupId.ok) return groupId;
return { ok: true, query: { groupId: groupId.query } };
}
/**
* ホールディングスで絞り込む。
*
* ★★`null`(未指定)と「該当0件」を取り違えないこと★★ —
* 未指定のときだけ全件を返す。実在しない `group_id` を指定されたら ★空配列★ を返す
* (404 にしない。「該当なし」は正常な結果。指示書AK §9-3 の 6-9 と同じ考え方)。
*
* ★`group_id: null`(ホールディングスなし)の屋号だけを引く手段は用意しない★
* (指示書AK §4-1 #3)。0 を API に漏らさないため、
* 「未設定を明示する値」をクエリパラメータに作らない。
*/
export function filterCustomersByGroup(
items: Customer[],
groupId: number | null
): Customer[] {
if (groupId === null) return items;
return items.filter((c) => c.group_id === groupId);
}
/**
* id の昇順(`d-watch-api.yaml` に明記。D-052 / D-055 と同じく契約に書く)。
*
* ★名前順にしないこと★ — 日本語の並び順はDBの照合順序とJSの `localeCompare` で
* 一致しないため、`USE_FIXTURES` の true/false で順序が変わりうる。
* セレクトリストの並びが環境で変わると「どちらが正しいか」を誰も決められない。
*/
export function sortGroups(items: Group[]): Group[] {
return [...items].sort((a, b) => a.id - b.id);
}
/** `sortGroups()` と同じ規則(id 昇順) */
export function sortCustomers(items: Customer[]): Customer[] {
return [...items].sort((a, b) => a.id - b.id);
}
/**
* ページングしない一覧のレスポンスを組み立てる。
*
* ★`items` を切らない★ — セレクトリストは全件が要る(このファイル冒頭)。
* ★`meta` は返す★ — `CLAUDE.md` §9.3。
*/
export function masterListResponse<T>(items: T[]): ListResponse<T> {
return {
items,
meta: { total: items.length, page: 1, per_page: items.length },
};
}