# T771 — contact touch product attribution

> Status: `todo`  
> Milestone: `M7` · Phase D  
> Kind: `feat` · Est. change: `~180 lines`

## Goal

ContactTouch 保存接觸當下 Brand／Product，使同一 Contact 可呈現不同產品歷史且不被後續 primary 改寫。

## Depends on

- T762

## Inputs

- CRM ContactTouch domain/repositories/mappers
- Opportunity accept/reply-used flows
- `../spec.md` §4.5、§6

## Outputs

| 路徑 | 動作 | 說明 |
|------|------|------|
| `apps/backend/internal/module/crm/domain/contact.go` | edit | optional brand_id/product_id/opportunity_id |
| `apps/backend/internal/module/crm/{repository,usecase}/**` | edit | 寫入與讀取接觸當下 attribution |
| `apps/backend/internal/logic/crmmap/map.go` | edit | public touch mapping |
| `apps/web/src/pages/CrmBoardPage.tsx` | edit | timeline 顯示 snapshot label／未指定產品 |
| CRM tests | edit/add | multi-product contact、legacy touch、primary change |

## Behavior

- 舊 touch 欄位可空；同 Contact 不設定永久唯一 Product。
- 後續改 primary 不更新既有 touch。
- owner guard 與既有 CRM 階段機不變。

## Out of scope

- OutcomeEvent（T772）
- 產品統計漏斗（P1）

## Acceptance

- [ ] 一個 Contact 的兩次 touch 可顯示不同 Product。

```bash
cd apps/backend && go test ./internal/module/crm/... ./internal/module/radar/... -count=1
cd apps/web && npm run test -- CrmBoardPage
```
