# T500 — api-shells-radar-crm-goctl

> Status: `done`（2026-07-31）  
> Milestone: `M0`  
> Kind: `feat`  
> Est. change: `~180 lines`

## Goal

完成後系統應：`radar` 與 `crm` 兩個 group 的路由由 goctl 產出並可打通，所有尚未實作的能力回**明確未就緒錯誤**（禁止 `102000` 空成功）。

## Depends on

- —

## Inputs

- Spec：`../spec.md` §5.1、§5.2、§5.6
- 既有契約檔範例：`apps/backend/generate/api/growth.api`、`scout` 相關段落
- `AGENTS.md`：後端一律 goctl，禁止手寫 handler／routes

## Outputs

### 程式變更（預期路徑）

| 路徑 | 動作 | 說明 |
|------|------|------|
| `apps/backend/generate/api/radar.api` | add | service-profile／watches／today／opportunities／sweeps 全部 route 與 type |
| `apps/backend/generate/api/crm.api` | add | contacts／followups／stats 全部 route 與 type |
| `apps/backend/generate/api/gateway.api` | edit | import 兩個新契約檔 |
| `apps/backend/internal/handler/**`、`routes.go` | generated | `make gen-api` 產出，**不手寫** |
| `apps/backend/internal/logic/radar/**` | add | logic 殼，一律回未就緒錯誤 |
| `apps/backend/internal/logic/crm/**` | add | logic 殼，一律回未就緒錯誤 |

### 行為變更

- 新路由存在且需 Bearer JWT，`owner_uid` 取登入 uid。
- 未實作能力回明確 error code／message（例：`radar capability not ready`），**不是** `102000` + 空 `data`。

### API / 契約

- 依 spec §5.2 全表建立 route；`P1` 的 `/api/v1/radar/import` 先不建。
- 列表型 request 一律 `page`／`pageSize`（`pageSize ≤ 50`），response `pagination` + `list`。
- 時間欄位一律 `int64` unix nanoseconds。

## Out of scope

- 任何業務邏輯與 Mongo 讀寫（T510 起）
- Mongo 索引（T501）
- 前端接線（T502）

## Acceptance

- [x] `make gen-api` 後 `git status` 顯示 handler／routes 為產生物，非手改
- [x] 每個新 route 回未就緒錯誤且**非** `102000`（以 34 個 capability 測試取代 curl，斷言 HTTP 501＋`code≠102000`＋`data=nil`）
- [x] 指令：

```bash
cd apps/backend && make gen-api && make build && go test ./... -count=1
```

## Notes

route 命名避免與既有 `/api/v1/scout` 衝突；`promote` 掛在既有 scout group（T600）。

### 實作結果（2026-07-31）

- 新增 34 個 route：`radar` 20、`crm` 14；`internal/handler/{radar,crm}` 與 `routes.go` 全為 goctl 產出。
- 未就緒語意：`radarDomain.ErrNotReady`／`crmDomain.ErrNotReady` → `internal/response` 映射 **HTTP 501 / code 501010**；各 logic 以 `notReady("<capability>")` 標明是哪個能力。
- 一併定義兩個 module 的 `ErrNotFound`／`ErrForbidden`／`ErrValidation`（`400100`）sentinel 與映射，供 M1 起直接使用，避免每則 task 重複改 `response.go`。
- **超出 spec §5.2 表格的兩個 route**（刻意加入，非漏審）：
  - `GET /api/v1/radar/opportunities/:id/replies` — RP-01「查 replies 只有 public_comment」需要讀取端點。
  - `POST /api/v1/crm/contacts/:id/unmerge` — spec §4.6.3 與 T571 都要求 unmerge。
- 送出回覆端點**未**建立，依原計畫留給 T550。
- `routes.go` 另有一處無害變動：兩個同前綴 `/api/v1/media` 區塊的輸出順序被 goctl 互換，`rest.WithJwt` 仍附在 `generate-image` 區塊，全檔 19 處計數不變。
