# CR-008　分期**通路**與**業務型態**雙軸拆分（`channel_type` ＋ `business_type`）

| 項目 | 內容 |
|---|---|
| 編號 | CR-008 |
| 版本 | **1.1（2026-09-20 BEAM PMO 核定：A1 ＋ NULL 允許 ＋ 通路軸擴充為 7 值）** |
| 狀態 | ✅ **已核定**（`CR-002 §6.1` 明載：本項屬**建模層變更**，須另提 CR 並經核定） |
| 提出日期 | 2026-09-20 |
| 核定日期 | 2026-09-20（BEAM PMO：①**A1——DB 與 API 皆改名**；②`business_type` **允許 NULL**，報表須處理空值；③`ACQUIRER`／`NCCC`／`QRCODE` **納入 `channel_type`**，通路軸擴充為 7 值） |
| 提出者 | WorkBuddy AI |
| 影響文件 | `SDD-001 §3.9`、`SCS-009`（§4／§6／§9／§10 #16）、`SDD-007 §5.4.x`（分期 WebUI）、`contracts/openapi/installment-service.yaml`（由 `tools/gen_openapi.py` 產生）、`CR-002 §6.1` |
| 影響程式 | `installment-service`：`InstallmentType`（→`ChannelType`）、新增 `BusinessType`、`InstallmentPlan`、`JdbcInstallmentRepository`、`InstallmentService`、`CreateInstallmentRequest`；種子 `ops/local/init/oracle/10-dev-installment-seed.sql`；前端 `frontend/src/api/installments.ts`、`pages/InstallmentConsole.tsx` |
| 關聯 | `CR-002 §6.1`（本 CR 即其所指「另提之 CR」）、`HANDOVER §3.C #C-15` |
| 嚴重度 | 🟨 **P2（建模缺陷）**——非阻斷（通路軸已可用），但**業務型態無處儲存**，報表維度受限 |

---

## 1. 背景

`CR-002` 修正了 `installmentType` 之**阻斷性缺陷**（契約 6 值實測全數回 `400`）,
處置為「**契約先對齊 DDL**」——使介面可用。但該欄的**建模問題並未解決**：

> `CR-002 §2.2`：「這不是單純的『值域寫錯』，而是**兩個不同分類軸被塞進同一欄**」。
> 「單筆／約定／帳單」講的是**業務型態**（怎麼發起）；
> 「特店／銀行／循環／帳單」講的是**來源通路**（誰提供）。

`CR-002 §6.1` 並已載明處置方向與其限制：

> **現行權宜**：`business_type` 之資訊暫無欄位承載，報表／篩選若需此維度，
> 須自 `original_transaction_id` 或 `product_code` 間接推導。
> **此限制應於 `SCS-009 §10` 明確揭露，不得默認已支援。**

**BEAM PMO 2026-09-20 裁定（#C-15）**：**一定要拆成兩個欄位（`channel_type` ＋ `business_type`）**。

---

## 2. 現況（2026-09-20 實測盤點，非推測）

### 2.1 `business_type` 在程式與 DDL 中**完全不存在**

全庫搜尋 `business_type`／`businessType` 僅命中**文件**（`CR-002`／`HANDOVER`／`SCS-009 §10 #16`），
**程式、DDL、契約、前端皆無此欄位**——證實「無處儲存」確為事實，非搜尋疏漏。

### 2.2 現有唯一一欄 `installment_type`

```sql
-- V1__baseline.sql（installment_products 與 installment_plans 皆同）
installment_type   VARCHAR2(16 CHAR) NOT NULL
                   CHECK (installment_type IN ('MERCHANT','BANK','REVOLVING','BILL')),
```

| 來源 | 分類軸 | 值域 | 權威性 |
|---|---|---|---|
| **BRD-001 §5.6**（需求源頭） | **業務型態** | 單筆消費分期／約定分期／帳單分期 | ★ 需求源頭 |
| 原契約（CR-002 修正前） | 業務型態 | `SINGLE`／`RECURRING`／`STATEMENT`／`ACQUIRER`／`NCCC`／`QRCODE` | ⚠️ 後 3 值語意見 §4.2 |
| SDD-001 §3.9 敘述 | 來源通路 | 商家分期（`MERCHANT`）／銀行分期（`BANK`） | 部分 |
| SDD-001 §3.9 DDL ＋ 實體 DDL ＋ 實作列舉 | **來源通路** | `MERCHANT`／`BANK`／`REVOLVING`／`BILL` | ★ 硬約束（CHECK） |

---

## 3. 衝突性質

| # | 性質 | 說明 |
|---|---|---|
| 1 | **兩正交軸共用一欄** | 業務型態（怎麼發起）與來源通路（誰提供）為正交維度，真實卡系統**兩者都需要** |
| 2 | **其中一軸完全無處儲存** | 業務型態（`business_type`）目前無任何欄位承載 |
| 3 | **欄名未反映其軸** | 承載「通路」的欄位卻叫 `installment_type`（＝「分期類型」），易被誤讀為業務型態——這正是 `CR-002` 契約值域錯誤之根源 |

---

## 4. 設計

### 4.1 雙軸定義（BEAM PMO 裁定：兩個欄位）

| 欄位 | 軸 | 說明 | 值域 | 權威來源 |
|---|---|---|---|---|
| **`channel_type`** | **來源通路**（誰提供） | 由 `installment_type` **改名**而來，**值域擴充為 7 值**（既有 4 值 ＋ 原契約之 `ACQUIRER`／`NCCC`／`QRCODE`） | `MERCHANT`／`BANK`／`REVOLVING`／`BILL`／**`ACQUIRER`**／**`NCCC`**／**`QRCODE`** | `BRD-001 §5.6` ＋ 原契約 ＋ 既有 DDL（見 §4.2 裁定） |
| **`business_type`** | **業務型態**（怎麼發起） | **新增**，**允許 NULL** | `SINGLE`／`RECURRING`／`STATEMENT` | **BRD-001 §5.6**（需求源頭） |

> ⚠️ `channel_type` 擴充為 7 值屬**放寬**既有 CHECK 約束（4 → 7）——
> 既有資料之 4 種值**仍然合法**，故**不會**因放寬而擋住任何既有資料。

**適用表**：`installment_products`、`installment_plans`（兩表皆需，前者為產品定義、後者為實際案件）。
`installment_periods` **不需**——攤還期隸屬於計畫，維度由父層繼承。

**約束**：
- `channel_type`：`NOT NULL` ＋ `CHECK` 同既有值域（沿用，不改）
- `business_type`：`NOT NULL` ＋ `CHECK (business_type IN ('SINGLE','RECURRING','STATEMENT'))`
  - ⚠️ 是否允許 `NULL` 待定，見 §7 待裁定項 ③

### 4.2 ⚠️ `ACQUIRER`／`NCCC`／`QRCODE` 之歸屬（**待裁定**）

原契約 6 值中，前 3 值（`SINGLE`／`RECURRING`／`STATEMENT`）明確對應 BRD §5.6 之業務型態；
**後 3 值語意偏「通路／交易來源」**，與 `channel_type` 既有 4 值之關係**無法由現有文件判定**：

| 值 | 字面語意 | 疑問 |
|---|---|---|
| `ACQUIRER` | 收單（分期） | 屬「誰提供」→ 應歸 `channel_type`？但既有 4 值無此項 |
| `NCCC` | 財金公司（跨行） | 同上 |
| `QRCODE` | QR Code 掃碼分期 | 偏「交易管道」→ `channel_type`？或視為一種業務型態？ |

**BEAM PMO 2026-09-20 裁定**：三者**納入 `channel_type`**（「誰提供」之語意），
使通路軸由 4 值擴充為 **7 值**：

```
MERCHANT（特店）／BANK（銀行）／REVOLVING（循環）／BILL（帳單）
ACQUIRER（收單）／NCCC（財金）／QRCODE（QR Code）
```

**`business_type` 維持 BRD §5.6 之 3 值**（`SINGLE`／`RECURRING`／`STATEMENT`）——
單筆／約定／帳單，語意為「怎麼發起」，與通路軸正交。

> ⚠️ **誠實揭露**：此歸類係**依字面語意判定**，非來自客戶確認之規格
> （這 3 值既不見於 `BRD-001 §5.6`，亦不見於既有 DDL）。
> 值域一旦寫入即難以回頭，故仍建議**於客戶規格確認時覆核**；
> 本期以 BEAM PMO 裁定為準並記載於此，日後可追溯。

---

## 5. 影響範圍與執行順序

依「SPEC 先行 → DDL → 程式 → 契約 → 前端 → 文件」：

| 步驟 | 項目 | 說明 |
|---|---|---|
| 0 | **本 CR 核定** | 前置（CR-002 §6.1 要求） |
| 1 | `SDD-001 §3.9` 升版 | 權威模型先改，否則下次重產基線會再掉一次（A-42 之教訓） |
| 2 | **新增 `V3__installment_channel_business_type.sql`** | ⚠️ **不改 V1 基線**（`SAD-003 §9`；V1 已套用，改檔即 checksum 失配） |
| 3 | 程式：`InstallmentType` → `ChannelType`、新增 `BusinessType` | 領域列舉、聚合、Repository（含 SQL 欄位）、Service、DTO |
| 4 | **契約**：改 `tools/gen_openapi.py` 後 `--write` | ⚠️ **禁手改 yaml**（產生器會覆寫） |
| 5 | 種子 `10-dev-installment-seed.sql` | 補 `business_type` 值 |
| 6 | 前端 `installments.ts`／`InstallmentConsole.tsx` | 標籤對照與表單 |
| 7 | `SCS-009`／`SDD-007 §5.4.x` 升版、回寫 `CR-002 §6.1` | 解除 §10 #16 之限制揭露 |

### 5.1 遷移腳本須注意（Oracle 實測事項）

- `ALTER TABLE … RENAME COLUMN installment_type TO channel_type`：
  ⚠️ **須實測** `CHECK` 約束之搜尋條件是否隨改名自動更新；
  若不更新，須**先刪約束再改名再重建**（`V1` 已套用，不可改）。
- 新增 `business_type` 時，既有資料**必須回填**（不可留 NULL 而無預設）：
  建議先加可為 NULL 之欄 → 回填 → 再加 `NOT NULL` 與 `CHECK`；
  或直接 `DEFAULT 'SINGLE'`（⚠️ 默認值需業務確認，見 §7 ③）。
- 遷移後**必須真實 Oracle 實測**（項目紀律：DDL 變更不得僅以單測驗證）。

---

## 6. 回溯相容性

| 面向 | 影響 |
|---|---|
| **DB** | `installment_type` → `channel_type` 為**改名**，既有查詢／視圖若引用舊名會失效 |
| **API 契約** | ⚠️ 若同步將 `installmentType` 改名為 `channelType`，為 **breaking change**——既有呼叫端全數失效 |
| **程式** | `InstallmentType` enum 改名，所有引用點須同步（已盤點，見表頭「影響程式」） |

---

## 7. 裁定結果（BEAM PMO 2026-09-20）

| # | 項目 | 裁定 | 影響 |
|---|---|---|---|
| ① | 是否改名 `installment_type` → `channel_type` | ✅ **A1：改名**（DB 與 API 皆改） | 徹底消除 `CR-002` 值域錯誤之根源（開發者依欄名字面填了業務型態值，導致契約值 100% 被拒）。API 為 **breaking change**，本期仍在開發階段，代價低於上線後 |
| ② | API 契約是否同步改名 | ✅ **同步改名** | `installmentType` → `channelType`，並新增 `businessType` |
| ③ | `business_type` 是否允許 `NULL` | ✅ **允許 NULL** | 既有資料（含種子、已建立之分期計畫）當初未記錄此維度；**誠實表達「未記錄」勝過編一個值**。報表與篩選須處理空值 |
| ④ | `ACQUIRER`／`NCCC`／`QRCODE` 歸屬 | ✅ **納入 `channel_type`** | 通路軸擴充為 7 值（放寬 CHECK，既有 4 值仍合法）。歸類依字面語意，非客戶確認規格——見 §4.2 之揭露 |

### 7.1 為何採 A1 而非 B（保留舊名）

`CR-002` 之阻斷性缺陷正是「**欄名與語意不符**」所導致：
開發者看到 `installmentType`（字面＝「分期類型」）便填了業務型態值
（`SINGLE`／`RECURRING`／`QRCODE`），結果 100% 被 DDL 的通路值域拒絕。
**保留舊名等於保留缺陷成因**——改名是治本，加欄只是治標。

---

## 8. 執行結果（2026-09-20 完成）

依 §5 之順序全數執行完畢，**並以真實 Oracle 端到端實測**（非僅單測推論）。

| 步驟 | 項目 | 結果 |
|---|---|---|
| 1 | `SDD-001 §3.9` 升版 | ✅ **1.5**（ER 圖與 DDL 兩表同步為 `channel_type` ＋ `business_type`） |
| 2 | `V3__installment_channel_business_type.sql` | ✅ Flyway 於 8089 啟動時套用成功（`now at version v3`）；`CHANNEL_TYPE` NOT NULL、`BUSINESS_TYPE` 可 NULL；四個具名 CHECK 成立；舊值域約束已卸除 |
| 3 | 程式 | ✅ `InstallmentType`→`ChannelType`（7 值）＋ 新增 `BusinessType`（3 值）；`InstallmentPlan`／`JdbcInstallmentRepository`／`InstallmentService`／DTO 同步 |
| 4 | 契約（`gen_openapi.py` → `--write`） | ✅ 重產 17 服務／**72 端點／0 錯誤**；`installmentType`→`channelType`（7 值）、新增 `businessType`（nullable）；⚠️ **未手改 yaml** |
| 5 | 種子 | ✅ 補雙軸值；**刻意保留 0003 為 NULL** 以驗證空值路徑 |
| 6 | 前端 | ✅ `installments.ts`／`InstallmentConsole.tsx` 同步；空值顯示「未記錄」；`npm run typecheck` **0 錯誤** |
| 7 | `SCS-009`／`SDD-007`／回寫 `CR-002 §6.1` | ✅ `SCS-009` **1.4**、`SDD-007` **1.5**、`CR-002 §6.1` 標為已完成 |

### 8.1 單測與判定力

- `installment-service` 單測 **22 → 33 例，0 失敗**
- 新增 `ChannelTypeBusinessTypeTest`（8 例）：通路軸 7 值、業務型態軸 3 值、
  **業務型態值不得被通路軸接受**（`CR-002` 根因回歸）、`fromCodeOrNull` 之 NULL／未知值分流
- **負樣本實測**：刻意把 `SINGLE` 加回 `ChannelType`、並讓 `fromCodeOrNull` 對未知值
  靜默回 `null` → **精準轉紅 3 例**，其餘 30 例不受影響，證明測試有判定力

### 8.2 真實 Oracle 端到端實測（`SCS-009 §9.3` #14–18）

| # | 情境 | 實測 |
|---|---|---|
| 14 | V3 遷移 | ✅ Flyway 套用成功，四個具名約束成立 |
| 15 | `MERCHANT` ＋ `STATEMENT` 建立 | ✅ 201，`INST2026090006` 落庫 `MERCHANT`／`STATEMENT`，12 期 |
| 16 | 省略 `businessType`（`QRCODE`） | ✅ 201，`INST2026090007` 落庫 `QRCODE`／**NULL**，6 期 |
| 17 | 查詢種子 0003（未記錄） | ✅ `channelType=BANK`、`businessType=**null**`（非空字串、非預設值） |
| 18 | `channelType=SINGLE`（誤填業務型態值） | ✅ 400 `CC-INS-4000`「未知分期來源通路：SINGLE」——`CR-002` 根因仍被正確擋下 |

### 8.3 執行補充：`InstallmentView` 新增 2 欄

`InstallmentView` 原**未**回傳分期分類（連舊的 `installmentType` 都沒有）。
為使呼叫端能**讀回**雙軸、並讓上述端到端實測可驗證「落庫值」而非僅查 DB，
本 CR 執行時**加值新增** `channelType` 與 `businessType` 兩欄：

- 性質：**純加值（additive）**，既有欄位不變、不改語意，**非 breaking change**
- `businessType` 於回應中**可為 `null`**——與 DDL 一致，語意為「當初未記錄」
- 此項屬實作細節，不影響 §7 之四項裁定，故**未另提 CR**；記載於此以維持文件與實物一致

---

## 9. 修訂記錄

| 版本 | 日期 | 修訂者 | 說明 |
|---|---|---|---|
| 1.0 | 2026-09-20 | WorkBuddy AI | 初版。依 `CR-002 §6.1` 與 BEAM PMO 2026-09-20 裁定（#C-15：拆分為 `channel_type` ＋ `business_type`）提出；現況經全庫實測盤點（`business_type` 確認不存在於程式／DDL／契約） |
| 1.1 | 2026-09-20 | BEAM PMO 核定 | 裁定三項：①**A1——DB 與 API 皆改名**（`installment_type`→`channel_type`、`installmentType`→`channelType`）②`business_type` **允許 NULL**，報表須處理空值 ③`ACQUIRER`／`NCCC`／`QRCODE` **納入 `channel_type`**，通路軸擴充為 7 值。§4.1／§4.2／§7 隨之更新 |
