# T565 — contact-model-repository-unique-key

> Status: `done`  
> Milestone: `M4`  
> Kind: `feat`  
> Est. change: `~180 lines`

## Goal

完成後系統應：Contact domain 與 repository 就位，唯一鍵（`owner_uid`＋`source_platform`＋`author_handle`）保證同一人只有一筆。

## Depends on

- T501

## Inputs

- Spec：`../spec.md` §3.4、§4.6、§8、§9.4（CR-01、CR-02）

## Outputs

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

| 路徑 | 動作 | 說明 |
|------|------|------|
| `apps/backend/internal/module/crm/domain/contact.go` | add | 七階段枚舉＋`needs_follow_up`／`follow_up_days`＋`opportunity_ids[]`／`merged_from[]` |
| `apps/backend/internal/module/crm/repository/contact_mongo.go` | add | `FindOrCreate`、Get、List（stage／follow_up／band／last_touch 排序）、UpdateStage、SetFollowUp、AttachOpportunity |
| `apps/backend/internal/module/crm/repository/contact_memory.go` | add | 測試用 |
| `apps/backend/internal/module/crm/usecase/contact.go` | add | usecase 殼 |

### 行為變更

- `FindOrCreate` 以唯一鍵原子操作，併發下不產生兩筆。
- `stage` 初值 `new_found`；`needs_follow_up` 預設 `false`，與 stage **並存**不互斥。
- `last_touch_at` 隨接觸更新（由 T566／T568 寫入）。

## Out of scope

- Touch 時間軸（T566）
- 階段轉移規則（T568）
- API（T569 起）

## Acceptance

- [x] unit test：同鍵兩次 `FindOrCreate` 只有一筆；不同平台同 handle 為兩筆
- [x] `needs_follow_up=true` 不影響 `stage` 值
- [x] 指令：

```bash
cd apps/backend && go test ./internal/module/crm/... -count=1
```

## Notes

命名紅線：本 module 不得出現以 `lead` 指稱銷售線索（RG-05）。
