/**
* ★分類(ホールディングス/屋号)→ 施設ID の解決★に使う施設マスタの読み取り
* (指示書AK 段階3 §6-3 / ★罠7★ / ★落とし穴14★)。
*
* ------------------------------------------------------------------------
* ★★`db.facilities()` を流用しないのは `loadFacilityNameIndex()` と同じ理由★★
* ------------------------------------------------------------------------
* `prismaSource.facilities()` は ★`where: { deleted_at: null }`★ を付けています
* (施設マスタ画面は論理削除された施設を出さない、という別の要件)。
* 一方 `cleanLogs()` の `include: { facility: true }` には ★deleted_at の条件がありません★=
* ★★いまの清掃詳細ログには、論理削除された施設のログも出ています★★。
*
* ★★だからこの読み取りも `deleted_at` で絞りません★★(★罠7★。
* 詳しい理由は `loadFacilityNameIndex()`(`lib/db/facility-name-source.ts`)の docstring)。
* 絞ると ★その施設のログが屋号で絞った瞬間に静かに消えます★
* (★件数が減るだけで例外もテスト失敗も出ない★)。
* ★★施設名の解決(`facilityNameIndex()`)と扱いを揃えること★★ —
* 片方だけ絞ると、★施設名とグループを併用したときだけ結果が変わる★という
* いちばん気づきにくい食い違いになります。
*
* ------------------------------------------------------------------------
* ★★`group_id` は `customers` 側にしかない★★
* ------------------------------------------------------------------------
* `facilities.customer_id` → `customers.group_id` の2段です(`prisma/schema.prisma`)。
* ★リレーションが張られている★(`facilities_customer_id_foreign`)ので、
* ★入れ子の `select` で1回のクエリにまとめられます★。
*
* ★★施設ごとにループでクエリを撃たないこと★★(★落とし穴14★)—
* ★施設マスタは実測12件★なので ★1回読んでメモリで解決する★(指示書AK §6-3)。
*
* ★★`customers` を別クエリで引いて突き合わせる形に**しない**こと★★ —
* そちらにすると「屋号側の `deleted_at` をどうするか」という判断が新たに1つ増え、
* ★論理削除された屋号に属する施設が静かに落ちる★経路ができます。
* 入れ子の `select` なら ★施設の行がある限り屋号の行も必ず付いてくる★(FK 必須列)。
*
* ------------------------------------------------------------------------
* ★★解決そのものはここでやらない★★
* ------------------------------------------------------------------------
* 当たり外れを決めるのは `lib/facilities/resolve-by-master.ts` の純粋関数です
* (`loadFacilityNameIndex()` と `resolve-by-name.ts` の分担と同じ。規則を2箇所に散らさない)。
*/
import { groupIdFromDb } from "@/lib/format/group-id";
import type { FacilityGroupRef } from "@/lib/types";
/**
* `facilities` から読む列(★これ以上増やさないこと★。解決に要るのは id と所属だけ)。
*
* ★`facility_name` を足さないこと★ — 施設名の照合は `facilityNameIndex()` の担当で、
* ★あちらの読み取りとこちらを1本にまとめない★(用途が増えると `where` を足したく
* なったときに片方の要件を壊す)。
*/
export interface FacilityGroupRow {
id: bigint;
customer_id: bigint;
/** ★`customers.group_id` は NOT NULL で、0 = ホールディングスなしのセンチネル★(D-011) */
customer: { group_id: bigint };
}
/**
* この関数が必要とする Prisma の最小限の形(★テストからスタブを渡せるようにするため★)。
* ★`PrismaClient` をそのまま要求しないこと★ — 生成済みクライアントが無い環境でも
* 単体テストが走るようにしてある(`lib/db/facility-name-source.ts` と同じ作法)。
*/
export interface FacilityGroupSourcePrisma {
facility: {
findMany(args: {
select: {
id: true;
customer_id: true;
customer: { select: { group_id: true } };
};
}): Promise<FacilityGroupRow[]>;
};
}
/**
* 分類の解決に使う行だけを、★列を絞って★全件読む。
*
* ★★`where` を足さないこと★★ — 上の「罠7」。
* ★テスト(`lib/db/__tests__/facility-group-source.test.ts`)が
* 「渡した引数のキーが `select` だけであること」を固定しています★。
*
* ★★`group_id` の 0 → `null` はここで済ませる★★(D-011 / 指示書AK §4-4 / ★落とし穴3★)—
* ★変換を知っているのは `groupIdFromDb()` だけ★。★`=== 0` をこのファイルにも
* 解決側にも書かないこと★。`BigInt` のままだと `0n === 0` が `false` で静かに外れるので、
* 先に `Number()` に落としてから渡す(`prismaSource.customers()` と同じ)。
*/
export async function loadFacilityGroupIndex(
prisma: FacilityGroupSourcePrisma
): Promise<FacilityGroupRef[]> {
const rows = await prisma.facility.findMany({
select: { id: true, customer_id: true, customer: { select: { group_id: true } } },
});
// ★BigInt を残すと `NextResponse.json` が例外を投げる(`lib/db/index.ts` と同じ作法)★
return rows.map((row) => ({
id: Number(row.id),
customer_id: Number(row.customer_id),
group_id: groupIdFromDb(Number(row.customer.group_id)),
}));
}