Newer
Older
client_dwatch / lib / reports / __tests__ / target-customer.test.ts
import { describe, expect, it } from "vitest";

import {
  resolveTargetCustomer,
  TARGET_CUSTOMER_REQUIRED_MESSAGE,
} from "@/lib/reports/target-customer";
import type { Customer } from "@/lib/types";

/**
 * 確定期間KPIの「対象顧客をどう決めるか」(指示書AK ★段階6★ / §2-1 の決定表)。
 *
 * ★★何を守るためのテストか★★
 *  - ★★候補が2つ以上のときに「静かに1社を選ぶ」ことをしない★★(★落とし穴6★。D-133)—
 *    ★静かに選ぶ実装は例外もテスト失敗も出さない★ので、ここで固定しておく以外に検出手段が無い
 *  - ★`customer_id` が明示されたら顧客マスタを見ない(存在確認をしない)★(期待値表 6-9)
 *  - ★`group_id` の候補は「その傘下だけ」★(別ホールディングスの顧客を混ぜない)
 *
 * ★ルート(HTTPステータス・`details`)側は
 *   `app/api/reports/__tests__/route.test.ts` の「段階6 — 対象顧客の決定」が見る★。
 */

/** 顧客マスタ1行(`db.customers()` の形。★`group_id` は API の形=`null` 可★。D-011) */
function customer(id: number, groupId: number | null): Customer {
  return { id, customer_name: `屋号${id}`, group_id: groupId };
}

const NONE = { groupId: null, customerId: null };

describe("resolveTargetCustomer(指示書AK §2-1 の決定表)", () => {
  it("customer_id 指定 → その顧客(★顧客マスタを見ない=存在確認をしない★。6-9)", () => {
    // ★マスタが空でも、存在しないIDでも、そのまま通す★
    expect(resolveTargetCustomer([], { groupId: null, customerId: 999 })).toEqual({
      ok: true,
      customerId: 999,
    });
  });

  // ★表9(指示書BB A17・D15・D-182)★ — customer_id と group_id の両方は AND(★AK §2-1 の決定表1行目「customer_id が勝つ」を上書き★)。
  //   顧客マスタ: 顧客1(group 1)・顧客2(group 2)・顧客3(group 1)・顧客4(group なし)
  describe("customer_id と group_id の同時指定は AND(表9)", () => {
    const customers = [customer(1, 1), customer(2, 2), customer(3, 1), customer(4, null)];

    it("K1: customer_id=2 だけ → 顧客2(存在確認しない)", () => {
      expect(resolveTargetCustomer(customers, { groupId: null, customerId: 2 })).toEqual({ ok: true, customerId: 2 });
    });

    it("K2: customer_id=2・group_id=2(傘下)→ 顧客2", () => {
      expect(resolveTargetCustomer(customers, { groupId: 2, customerId: 2 })).toEqual({ ok: true, customerId: 2 });
    });

    it("★K3: customer_id=2・group_id=1(傘下でない)→ 該当なし(customerId: null → 200・空)★", () => {
      expect(resolveTargetCustomer(customers, { groupId: 1, customerId: 2 })).toEqual({ ok: true, customerId: null });
    });

    it("K4: customer_id=4(グループなし)・group_id=1 → 該当なし", () => {
      expect(resolveTargetCustomer(customers, { groupId: 1, customerId: 4 })).toEqual({ ok: true, customerId: null });
    });

    it("K5: customer_id=999(無い)・group_id=1 → 該当なし", () => {
      expect(resolveTargetCustomer(customers, { groupId: 1, customerId: 999 })).toEqual({ ok: true, customerId: null });
    });

    it("★K7: customer_id=1・group_id=1(傘下2社)→ 顧客1(422 にしない。顧客が名指しされている)★", () => {
      expect(resolveTargetCustomer(customers, { groupId: 1, customerId: 1 })).toEqual({ ok: true, customerId: 1 });
    });

    it("K8: customer_id なし・group_id=1(傘下2社)→ 今どおり決まらない(候補 [1, 3])", () => {
      expect(resolveTargetCustomer(customers, { groupId: 1, customerId: null })).toEqual({
        ok: false,
        candidateCustomerIds: [1, 3],
      });
    });
  });

  it("group_id 指定・傘下1社 → その顧客(6-6)", () => {
    const customers = [customer(1, 1), customer(2, 2), customer(3, null)];
    expect(resolveTargetCustomer(customers, { groupId: 2, customerId: null })).toEqual({
      ok: true,
      customerId: 2,
    });
  });

  it("★★group_id 指定・傘下2社 → 定まらない。候補は傘下だけ★★(6-7)", () => {
    const customers = [customer(1, 1), customer(2, 1), customer(3, 2)];
    expect(resolveTargetCustomer(customers, { groupId: 1, customerId: null })).toEqual({
      ok: false,
      candidateCustomerIds: [1, 2],
    });
  });

  it("★group_id 指定・傘下0社 → 定まらない(候補は空配列)★(★指示書に明記が無く、起草者が決めた★)", () => {
    const customers = [customer(1, 1)];
    expect(resolveTargetCustomer(customers, { groupId: 9, customerId: null })).toEqual({
      ok: false,
      candidateCustomerIds: [],
    });
  });

  it("どちらも未指定・顧客が1社だけ → その顧客(★P1 はここ★。6-3)", () => {
    expect(resolveTargetCustomer([customer(7, null)], NONE)).toEqual({
      ok: true,
      customerId: 7,
    });
  });

  it("★★どちらも未指定・顧客が2社 → 定まらない(★静かに1社を選ばない★)★★(6-4 / 6-5)", () => {
    const customers = [customer(1, 1), customer(2, 2)];
    expect(resolveTargetCustomer(customers, NONE)).toEqual({
      ok: false,
      candidateCustomerIds: [1, 2],
    });
  });

  it("★どちらも未指定・顧客が0社 → 定まらない★(マスタが空のときに何かを選ばない)", () => {
    expect(resolveTargetCustomer([], NONE)).toEqual({
      ok: false,
      candidateCustomerIds: [],
    });
  });

  it("★`group_id = null`(ホールディングスなし)の顧客は group_id 指定に当たらない★(D-011)", () => {
    const customers = [customer(1, null), customer(2, 1)];
    // ★「所属なしの顧客だけを引く」経路はそもそも存在しない★(API は 1 以上しか受け付けない)
    expect(resolveTargetCustomer(customers, { groupId: 1, customerId: null })).toEqual({
      ok: true,
      customerId: 2,
    });
  });

  it("★422 の文言は指示書AK §2-1 の原文★(言い換えない)", () => {
    expect(TARGET_CUSTOMER_REQUIRED_MESSAGE).toBe(
      "顧客またはグループを指定してください。複数の顧客のデータを合算した数値は出せません。"
    );
  });
});