# CR-002　分期服務契約值域與 DDL 不一致

| 項目 | 內容 |
|---|---|
| 文件編號 | CR-002 |
| 版本 | 1.1 |
| 狀態 | **已核定並執行完成**（2026-09-19，BEAM PMO 裁定；含 `SDD-005` 1.2 更正） |
| 提出日期 | 2026-09-19 |
| 提出者 | 開發團隊（分期域 WebUI 端到端實測時發現） |
| 影響文件 | SDD-001 §3.9（ER/DDL）、SDD-005 §6（狀態機）、SDD-006 §3.4（錯誤碼）、SCS-009 |
| 影響服務 | `installment-service`（8089）、WebUI `/installments` |
| 優先級 | **高（P1）**——契約列舉值**全部不可用**，照契約開發之呼叫端無法建立分期 |

---

## 1. 摘要

`installment-service` 之 OpenAPI 契約有 **3 處值域**與 ER／實體 DDL／實作不一致。
其中 **`installmentType` 為阻斷性缺陷**：契約列舉之 6 個值**無一可用**，
實測一律回 `400 CC-INS-4000`。亦即**依契約開發之呼叫端 100% 無法建立分期**。

已於 2026-09-19 經 BEAM PMO 裁定處理方向並執行修正（見 §4、§5）。

---

## 2. 現況與實測證據

以下皆為 2026-09-19 於本地 Oracle 26ai（`FREEPDB1`）＋ 服務 8089 實測之結果，非推測。

### 2.1 實測：契約列舉值全部被拒

```
POST /api/v1/installments   （X-Channel: WEB、X-Idempotency-Key: 每次不同）

installmentType=SINGLE      → HTTP 400  CC-INS-4000  未知分期類型：SINGLE
installmentType=RECURRING   → HTTP 400  CC-INS-4000  未知分期類型：RECURRING
installmentType=QRCODE      → HTTP 400  CC-INS-4000  未知分期類型：QRCODE
installmentType=MERCHANT    → HTTP 201
installmentType=BANK        → HTTP 201
```

→ **契約所載之 6 個值全部不可用**；可用值僅 DDL 之 4 值。

### 2.2 三方（四方）來源對照

#### 不一致項 1：`CreateInstallmentRequest.installmentType`

| 來源 | 分類軸 | 值域 |
|---|---|---|
| **BRD-001 §5.6**（需求源頭） | **業務型態** | 單筆消費分期／約定分期／帳單分期 |
| **契約**（修正前） | **業務型態** | `SINGLE`／`RECURRING`／`STATEMENT`／`ACQUIRER`／`NCCC`／`QRCODE` |
| SDD-001 §3.9 敘述 | **來源通路** | 商家分期（`MERCHANT`）／銀行分期（`BANK`） |
| SDD-001 §3.9 DDL ＋ 實體 DDL ＋ 實作列舉 | **來源通路** | `MERCHANT`／`BANK`／`REVOLVING`／`BILL` |

> ⚠️ **這不是單純的「值域寫錯」，而是兩個不同分類軸被塞進同一欄**：
> 「單筆／約定／帳單」講的是**業務型態**（怎麼發起）；
> 「特店／銀行／循環／帳單」講的是**來源通路**（誰提供）。
> 兩者在真實卡系統**都需要**，但目前只有一個欄位。

#### 不一致項 2：`InstallmentView.status`

| 來源 | 值域 |
|---|---|
| 契約（修正前） | `PENDING`／`ACTIVE`／`SETTLED`／`EARLY_SETTLED`／`DEFAULTED`／`REJECTED`／`CANCELLED` |
| SDD-005 §6 狀態機（8 條轉換） | 同上 7 值 |
| SDD-001 §3.9 DDL | `ACTIVE`／`COMPLETED`／`PRE_CLOSED`／`DEFAULTED`／`CANCELLED` |
| 實體 DDL（`V1__baseline.sql`） | 同上 5 值 |
| 實作列舉 `PlanStatus` | 同上 5 值 |

#### 不一致項 3：`InstallmentPeriod.status`

| 來源 | 值域 |
|---|---|
| 契約（修正前） | **未定義列舉**，僅範例誤植為 `PENDING` |
| SDD-001 §3.9 DDL ＋ 實體 DDL ＋ 實作列舉 | `DUE`／`PAID`／`OVERDUE`／`WAIVED`／`REVERSED`（預設 `DUE`） |

---

## 3. 衝突性質分析

三項並非同一類問題，處理方式亦不同：

| # | 項目 | 性質 | 嚴重度 |
|---|---|---|---|
| 1 | `installmentType` | **建模缺陷**——兩個正交分類軸共用一欄 | **阻斷（P1）**：契約值 100% 不可用 |
| 2 | `InstallmentView.status` | **值域不一致**——狀態機值域無法落 ER/DDL | 高：契約宣告不可達之狀態 |
| 3 | `InstallmentPeriod.status` | **契約缺漏**——欄位未定義列舉且範例錯誤 | 中：契約未表達約束 |

### 3.1 為何第 2 項不以 SDD-005 §6 為權威

`SDD-005 §6` 之狀態機含 `PENDING`（申請）與 `REJECTED`（拒絕）二態，看似較完整，
但查證後**不應作為持久狀態之權威**：

1. **BRD 流程無此狀態**：`BRD-001 §5.6` 之分期流程為
   `分期申請 → 資格與額度檢核 → {否：拒絕－額度不足 ／ 是：總約簽署 → 首期入帳}`。
   其中「拒絕」為**同步交易拒絕**，非先落 `PENDING` 再轉 `REJECTED`；
   流程中**不存在**「已申請、待核准」之持久狀態。
2. **ER/DDL 無此狀態**：`SDD-001 §3.9` 之 `INSTALLMENT_PLANS.status` 無 `PENDING`／`REJECTED`，
   且 `DEFAULT 'ACTIVE'`——計畫一建立即為 `ACTIVE`。
3. **實作無此狀態**：`InstallmentService.create` 直接建立 `ACTIVE`，略過核准流程
   （此為既有已知事項，見 `SCS-009 §10.5` #4）。
4. **`view` 只能回傳存得下的值**：`InstallmentView.status` 是**已持久狀態之檢視**，
   宣告一個資料庫存不下的值，只會讓呼叫端寫出永不觸發的分支。

→ 綜合：**4 個來源（BRD＋ER/DDL＋實體 DDL＋實作）不支持 `PENDING`／`REJECTED`，
僅 `SDD-005 §6` 一家支持**。故以 ER/DDL 為權威。

> 名稱對照（語意相同、命名不同）：
> `SETTLED` ↔ `COMPLETED`（按期攤還完畢）；`EARLY_SETTLED` ↔ `PRE_CLOSED`（提前結清）。

---

## 4. 決策（2026-09-19 BEAM PMO 裁定）

| # | 項目 | 裁定 | 理由 |
|---|---|---|---|
| 1 | `installmentType` | **契約先對齊 DDL**（`MERCHANT`／`BANK`／`REVOLVING`／`BILL`） | 契約是介面，介面必須可用；建模層面的雙軸拆分（見 §6）另案處理，不在本 CR 動 DDL |
| 2 | `InstallmentView.status` | **以 ER/DDL 為權威**，契約對齊 5 值 | 見 §3.1：4 源 vs 1 源，且 BRD 流程無持久化申請狀態 |
| 3 | `InstallmentPeriod.status` | 契約**補列舉**並修正範例 | 屬契約缺漏，無設計爭議 |

---

## 5. 已執行之修正

### 5.1 契約（經產生器，未手改 YAML）

修改 `tools/gen_openapi.py` 之 `installment-service` 區塊後重新產生
（**不可手改 `contracts/openapi/*.yaml`**，會被覆蓋）：

| # | 修正 |
|---|---|
| 1 | `installmentType` enum → `["MERCHANT","BANK","REVOLVING","BILL"]`，並加註說明原值域不可用 |
| 2 | `InstallmentView.status` enum → `["ACTIVE","COMPLETED","PRE_CLOSED","DEFAULTED","CANCELLED"]`，並註明與 `SDD-005 §6` 之差異 |
| 3 | `InstallmentPeriod.status` **新增** enum `["DUE","PAID","OVERDUE","WAIVED","REVERSED"]` |
| 4 | 服務描述由「單筆、約定、帳單、自行收單、NCCC、QR Code 分期」改為「特店分期、銀行分期、循環分期、帳單分期」（對齊實際值域） |
| 5 | 修正範例：`installmentType` `SINGLE`→`MERCHANT`；期數 `status` `PENDING`→`DUE`；`totalFee` `6000`→`600`（120000×0.005）；`feeAmount` `500`→`50`（600÷12） |
| 6 | 補欄位說明：金額單位為**最小貨幣單位（分）**；`agreementSigned` 未帶回 `422 CC-INS-1002`；`remainingPrincipal` 末期必為 0 之不變式 |

**產生器驗證結果**：17 服務 / 70 端點 / **0 錯誤**（`✅ OpenAPI 驗證：0 錯誤`）。

### 5.2 文件

| 文件 | 修正 |
|---|---|
| `SCS-009 §10.1` | 更新為「契約已於 CR-002 對齊 DDL」；保留值域三方不一致之歷史敘述供追溯 |
| `SCS-009 §10.5` #15 | 產品維護端點說明不變；補註契約已對齊 |
| `SCS-009 §11` | 修訂記錄新增 1.2 |
| `frontend/src/api/installments.ts` | 移除「契約列舉不同」之過時註解（現已一致） |

### 5.3 前端

前端於開發時即已依 DDL 值域實作（`INSTALLMENT_TYPE_LABEL`／`PLAN_STATUS_LABEL`／
`PERIOD_STATUS_LABEL`），**本次無需改動程式邏輯**，僅更新過時註解。

---

## 6. 未納入本次修正（另案追蹤）

### 6.1 ✅【已完成】分期類型雙軸拆分 → **`CR-008`**

> **2026-09-20 更新**：本項所指「另提之 CR」即 **`CR-008`**（已由 BEAM PMO 核定為 v1.1，
> 並於同日完成程式／DDL／契約／種子／前端與真實 Oracle 端到端實測）。
> 處置與原建議略有差異——**`installment_type` 改名為 `channel_type`**
> （而非「保留為來源通路」），理由見 `CR-008 §7.1`：
> 「欄名與語意不符」正是本 CR 阻斷性缺陷之**成因**，保留舊名等於保留成因。
> 詳細值域、NULL 政策與 `ACQUIRER`／`NCCC`／`QRCODE` 之歸屬見 `CR-008`。

`installmentType` 一欄同時被要求承載兩個正交維度：

| 維度 | 值域 | 來源 |
|---|---|---|
| 來源通路 | `MERCHANT`／`BANK`／`REVOLVING`／`BILL` | SDD-001 §3.9、實體 DDL |
| 業務型態 | 單筆／約定／帳單／收單／NCCC／QR Code | BRD-001 §5.6、原契約 |

**建議**：於 `INSTALLMENT_PLANS` 與 `INSTALLMENT_PRODUCTS` 新增
`business_type VARCHAR2(16)`，值域對齊 BRD-001 §5.6；
`installment_type` 保留為來源通路。此為**建模層變更**，須另提 CR 並經 SA 核定。

**✅ 已完成（`CR-008`，2026-09-20）**：`business_type` 已新增（允許 NULL，
語意為「當初未記錄」）；通路軸改名為 `channel_type` 並擴充為 7 值。
`SCS-009 §10.2` 與 §10.7 #16 已由「限制揭露」改為「已解除」。

**~~現行權宜~~（已解除）**：`business_type` 之資訊暫無欄位承載，報表／篩選若需此維度，
須自 `original_transaction_id` 或 `product_code` 間接推導。
> ⚠️ **殘留**：`business_type` 允許 NULL，**報表與篩選必須處理空值**，
> 不得以 `COALESCE` 默認成某一型態（`CR-008 §7 ③`）。

### 6.2 ✅【已完成】`SDD-005 §6` 狀態機之更正

`SDD-005 §6` 為**已核定文件**，其值域與 ER/DDL 不一致（見 §2.2 項 2）。
本 CR 先使契約對齊 ER/DDL；`SDD-005 §6` 本身**已於 2026-09-19 經 BEAM PMO 核定後更正**，
`SDD-005` 升版為 **1.2**。

**已完成之更正內容**：

| # | 更正 | 說明 |
|---|---|---|
| 1 | **移除 `PENDING`（申請）** | DDL CHECK 無此值（`DEFAULT 'ACTIVE'`），**無法持久化**；`BRD-001 §5.6` 無持久化之申請狀態 |
| 2 | **移除 `REJECTED`（拒絕）** | 同上；BRD 之「拒絕－額度不足」為**同步交易拒絕** |
| 3 | `SETTLED` → **`COMPLETED`** | 語意相同，以 DDL 為權威統一術語 |
| 4 | `EARLY_SETTLED` → **`PRE_CLOSED`** | 同上 |
| 5 | 轉換 #4 來源 `PENDING` → **`ACTIVE`** | Saga 補償發生於首期入帳階段 |
| 6 | 轉換數 8 → **6** | 連動更正 §12.2 矩陣：分期 8／11 → **6／9**；合計 82／105 → **80／103** |
| 7 | 新增通用規則 **SM-08** | **「狀態值域以可持久化之 DDL CHECK 為權威」**——與 1.1 版 §11 Saga `RUNNING`→`STARTED` 同一原則；並明訂「新增狀態須先改 ER/DDL 再改本文件」 |

**保留事項（已寫入 `SDD-005` 1.2 §6）**：
- `COMPLETED` 之產生者（每期扣款／攤還排程）**尚未實作**
- `ACTIVE` → `CANCELLED`（Saga 補償）**尚未實作**
- **若日後業務確需「申請—核准」流程**，須**另提 CR** 於 `SDD-001 §3.9` ER/DDL
  補入狀態並附遷移腳本，**不得僅修改文件**——否則將重現「狀態機有、資料庫存不下」之同一缺陷

**連動更新**：`SDD-007` 升版 **1.1**（§5.4.8 值域說明）、`SCS-009` 升版 **1.3**（§10.1 改為「已全部對齊」）、
契約 `InstallmentView.status` 描述、`InstallmentView.java` javadoc。

### 6.3 【提案】`InstallmentView` 欄位不足

`INSTALLMENT_PLANS` 有 `principal_outstanding`／`paid_periods`／`remaining_periods`／
`currency`／`fee_rate`／`start_date`／`first_due_date` 等欄位，
但 `InstallmentView` 未暴露，前端無法顯示「剩餘本金總額／已繳期數」。

**建議**：視 WebUI 需求於契約補欄位（屬功能增補，非不一致，優先級較低）。

---

## 7. 影響範圍與回改清單

| # | 項目 | 狀態 |
|---|---|---|
| 1 | `tools/gen_openapi.py` 分期區塊 | ✅ 已改 |
| 2 | `contracts/openapi/installment-service.yaml` | ✅ 已重生（產生器產物） |
| 3 | `frontend/src/api/installments.ts` 註解 | ✅ 已更新 |
| 4 | `SCS-009 §10`／§11 | ✅ 已更新（**1.3**：§10.1 改為「值域已全部對齊」） |
| 5 | `SDD-005 §6` 更正與升版 | ✅ **已完成（1.2）**：移除 `PENDING`／`REJECTED`、更名 `SETTLED`／`EARLY_SETTLED`、新增 SM-08、§12.2 連動更正 |
| 6 | `SDD-007` §5.4.8 值域說明 | ✅ 已更新（**1.1**） |
| 7 | `SDD-001 §3.9` 新增 `business_type`（並將 `installment_type` 改名為 `channel_type`） | ✅ **已完成（`CR-008`，2026-09-20）**：`SDD-001` 升 **1.5**、`V3__installment_channel_business_type.sql` 已遷移並經真實 Oracle 實測 |
| 8 | `installment_products` 維護端點 | ⏳ 契約未定義，維持揭露（`SCS-009 §10.7` #15） |

---

## 8. 殘留風險

| # | 風險 | 說明 |
|---|---|---|
| 1 | ~~`SDD-005 §6` 未更正~~ | ✅ **已解除**（1.2 完成）。值域四方（BRD／ER-DDL／狀態機／契約）已全部對齊；殘留僅「`COMPLETED` 無產生者」與「`ACTIVE`→`CANCELLED` 未實作」兩項，已於 `SDD-005` 1.2 §6 明列為保留事項 |
| 2 | ~~`business_type` 無欄位承載~~ | ✅ **已解除（`CR-008`）**：欄位已新增並完成端到端實測。**殘留風險**：允許 NULL，報表與篩選**必須處理空值**，不得以 `COALESCE` 默認成某一型態（見 §6.1） |
| 3 | 既有資料 | 修正僅涉及契約與文件，**未動任何已落庫資料**；DDL 未變更，故無資料轉換風險 |

---

## 9. 建議

- 本 CR 之契約修正**已完成並驗證**，可立即解除呼叫端之阻斷
- ~~§6.2（`SDD-005 §6` 更正）建議於本 Sprint 內核定~~ → ✅ **已完成**（`SDD-005` 1.2）
- ~~§6.1（雙軸拆分）建議於下一 Sprint 之建模檢視一併處理~~ → ✅ **已完成**（`CR-008`，2026-09-20）。
  BRD §5.6 之業務型態維度**已可儲存**；殘留要求為「空值須被正確處理」

---

## 10. 修訂記錄

| 版本 | 日期 | 說明 |
|---|---|---|
| 1.0 | 2026-09-19 | 初版：分期域 WebUI 實測時發現契約列舉值 100% 不可用，提出 3 項不一致之分析與修正；契約修正已執行並通過產生器驗證 |
| 1.1 | 2026-09-19 | BEAM PMO 核定 §6.2 後補完：**`SDD-005` 升版 1.2**（§6 移除 `PENDING`／`REJECTED`、更名 `SETTLED`／`EARLY_SETTLED`、新增 SM-08、§12.2 連動更正）、**`SDD-007` 升版 1.1**、**`SCS-009` 升版 1.3**（§10.1 改為「已全部對齊」）；連動更新契約描述與 `InstallmentView` javadoc；§8 風險 #1 解除 |
