/**
* GET /customers — 屋号一覧(分類マスタ。指示書AK 段階1。読み取り専用)
*
* 形は `app/api/schedules/route.ts` と同じ。
* 仕様の正は `d-watch-api.yaml` の /customers。
*
* ★GET だけ★(理由は `app/api/groups/route.ts` の冒頭)。
* ★認証はここに書かない★ — `proxy.ts` が横断して判定する。
*
* ★`group_id` で絞れる★ — 画面(段階7)はホールディングスを選んだときに
* `GET /customers?group_id=X` を投げ、屋号の選択肢をその傘下だけに絞る。
* ★実在しない `group_id` でも 404 にしない★(空配列 + `meta.total = 0`)。
* 「該当なし」は正常な結果で、404 は「エンドポイントが無い」と区別できなくなる。
*/
import { NextResponse } from "next/server";
import { db } from "@/lib/db";
import {
filterCustomersByGroup,
masterListResponse,
parseCustomerQuery,
sortCustomers,
} from "@/lib/masters/query";
import { withRequestId } from "@/lib/logger";
import { logApiError } from "@/lib/logger/api";
import type { ApiError, Customer, ListResponse } from "@/lib/types";
const ROUTE = "/api/customers";
function errorResponse(error: ApiError, status: number) {
return NextResponse.json(error, { status });
}
export async function GET(request: Request) {
return withRequestId(request, async () => {
const { searchParams } = new URL(request.url);
// ★`group_id=0` は 422★ — 0 は「ホールディングスなし」のセンチネルであって、
// 有効なIDではない(D-011)。★`1abc` も 422★(D-123 で踏んだ罠)。
const parsed = parseCustomerQuery(searchParams);
if (!parsed.ok) {
return errorResponse(parsed.error, parsed.status);
}
let rows: Customer[];
try {
rows = await db.customers();
} catch (e) {
const body: ApiError = {
error: {
code: "internal_error",
message: "データの取得に失敗しました",
details: null,
},
};
logApiError({ route: ROUTE, method: "GET", body, status: 500, err: e });
return errorResponse(body, 500);
}
// ここから下は純粋な絞り込み・並べ替え。すべて try の外
// (`app/api/schedules/route.ts` と同じ方針)。
const filtered = filterCustomersByGroup(rows, parsed.query.groupId);
const body: ListResponse<Customer> = masterListResponse(
sortCustomers(filtered)
);
return NextResponse.json(body);
});
}