# 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 ```