◆ wport | pm_44

封鎖公司(Block Company)Mockup PRD(給後端 API 規格產生器使用)

封鎖公司(Block Company)

來源 doc/feature/pm_44/block-company-prd.md

封鎖公司(Block Company)Mockup PRD(給後端 API 規格產生器使用)

產出 persona:CEO with PM Background(SaaS / HR tech lens)。 對應 Coda:封鎖公司功能


1. 文件資訊

  • 文件類型:Mockup 需求規格書(前後端共用視圖)
  • 適用對象:前端、後端、產品、QA
  • 最後更新:2026/07/15
  • 版本:1.3.0(2026/07/15 Eric 拍板 Q1/Q2/Q4/Q5/Q6:Q1 Shadow block、Q2 v1 對齊、Q4 解封恢復、Q5 v1 誠實揭露、Q6 深連結二次確認;6 題全數裁決完畢);1.2.0(IA 定案:唯一動點=帳號設定「封鎖公司」;廢除公司頁/職缺卡入口)
  • 對照文件(BD 審查用,不取代本 PRD):block-company-audit-report.mdblock-company-pm-decisions.md
  • 對應 Storybookstorybook/src/stories/mockups/user/BlockCompany.stories.ts(已產出)
  • Storybook 連結https://storybook.wport.me/?path=/docs/mockups-user-blockcompany—docs
  • 建立日期:2026/06/23
  • Coda backlog封鎖公司功能

2. 開發進度與設計來源

開發進度

  • 前端 PR #:[TBD] 後端 PR #:[TBD]

設計來源

  • Figma:[待補上]
  • 既有畫面:求職者帳號設定 →「封鎖公司」管理頁(唯一入口)

3. 模擬頁面摘要

3.1 功能概述

  • 功能名稱:封鎖公司(Block Company)
  • 主要使用者角色:Job Seeker
  • 核心目標
    • 讓求職者避開特定雇主(如現任 / 前東家、不想被看到的公司),降低反感與隱私顧慮造成的流失。
    • 雙向隱藏:被封鎖公司的職缺不出現在此用戶的搜尋/推薦;此用戶不出現在該企業的人才搜尋/「誰來看我」。
    • 動機重點:使用者可在不瀏覽該公司頁的前提下完成封鎖(避免留下「誰來看我」足跡)。

3.2 範圍界定

  • 包含範圍
    • 唯一動點:帳號設定 → 封鎖公司(清單管理 + 搜尋新增封鎖)。
    • 封鎖 / 解除封鎖公司。
    • 雙向隱藏邏輯定義(求職者端搜尋/推薦、企業端人才曝光)。
    • 與收藏互斥(封鎖即移除公司收藏及旗下職缺收藏,對齊 pm_43 BR-024)。
    • 偶發深連結開啟已封鎖公司頁時的已封鎖遮罩(SEC-4;非封鎖入口)。
  • 排除範圍
    • 公司頁 / 職缺卡/overflow menu 上的「封鎖此公司」入口——不做、不規格化。
    • 封鎖個別 HR / 個人(僅封鎖公司層級;v2 再議)。
    • 檢舉 / 申訴流程(另案)。
    • 反向(企業封鎖求職者)—— 屬「黑名單與反向評價」另案。

3.3 成功條件

  • 封鎖後,被封鎖公司之職缺即時自此用戶的職缺搜尋/推薦消失;解除後恢復。
  • 此用戶於該企業端人才搜尋 / 誰來看我 / 人才 CRM(pm_45)不出現。
  • 已投歷史(pm_42)保留,不因封鎖被抹除;該公司「再次應徵」入口在封鎖下隱藏(Q2 裁決 2026/07/15:v1 就做,pm_42 v1 提前接 is_blocked_by_me,需與 pm_42 owner 三方對齊窗口期)。
  • 封鎖時自動移除該公司收藏及其旗下職缺收藏;已封鎖公司依 BR-027 不可見,無法觸發收藏。
  • 使用者僅需進入帳號設定「封鎖公司」即可完成新增/解除,無需造訪公司頁

4. 商業規則對齊

4.1 本功能使用到的業務規則

規則 ID規則摘要是否關鍵備註
BR-015職缺搜尋顯示條件✅ 是需新增「排除被本人封鎖公司之職缺」過濾
BR-014公司搜尋顯示條件✅ 是被封鎖公司不出現在此用戶的公司搜尋
BR-017資料存取控制✅ 是封鎖名單為使用者私有資料
BR-001登入後功能✅ 是封鎖為登入後行為

4.2 補充說明(已裁決,回寫 business-rules.md v1.6.2)

  • BR-027(封鎖雙向隱藏)
    • 求職者 U 封鎖公司 C 後:
      1. C 的職缺不出現在 U 的職缺搜尋與推薦(影響 BR-015)。
      2. C 的公司頁不出現在 U 的公司搜尋;U 直接以 enc_id 開啟 → 顯示「你已封鎖此公司」遮罩(非 404,以區別不存在)。遮罩為狀態呈現,不是封鎖入口。
      3. U 不出現在 C 的人才搜尋、誰來看我、人才 CRM(pm_45)列表與時間軸(影響人才搜尋)。
      4. 訊息通道採 Shadow block(Q1 裁決 2026/07/15):C 對 U 的站內訊息/邀約照常送出成功,但寫入時若收件人已封鎖該公司則標記 suppressed(比照既有 is_deleted 軟刪 pattern),解除封鎖不回放(不詐屍);U 端讀取查詢一律過濾 suppressed,封鎖前的歷史對話 U 端仍保留可見;系統通知信在 orchestrator 單一咽喉點(查偏好前)加封鎖檢查,命中則靜默跳過。企業端呈現為「已送出無回應」,與已讀不回無法區分(天然掩護)。PM 已知情接受兩個不可消除的弱訊號:企業端該 U 訊息永遠顯示未讀、邀約「已查看」同理。落地:BR-027 第 4 點 + W101-TalentSearchHub 約 6 插入點 + pm_32 通知規格同步。(財務前提:現企業發訊息/邀約不耗點數;若未來上點數制需重審此方案。)
    • 不影響:U 對 C 的歷史應徵紀錄(pm_42)保留;C 對既有應徵的處理不變(封鎖不等於撤回應徵 —— 見問題清單 #1)。
  • BR-024(收藏 ⇄ 封鎖互斥)(與 pm_43 同一條):封鎖 C 時隱藏(非硬刪)U 對 C 的公司收藏及旗下所有職缺之收藏;已封鎖公司及其職缺依 BR-027 不出現於可瀏覽入口,無收藏 toggle(預防性隱藏)。解除封鎖後自動恢復原收藏(Q4 裁決 2026/07/15:採可逆語意),Modal 文案不得宣稱「永久移除」,改為「將暫時隱藏相關收藏,解除封鎖後恢復」。用詞回寫 pm_43 BR-024。
  • BR-028(封鎖上限):每帳號封鎖上限 50 家;達上限提示。批次「封鎖全部」不得突破上限(封到滿即停,或整批拒絕——實作採「封到滿即停」)。
  • BR-029(封鎖原因,匿名統計,選填):封鎖時可選原因(前東家 / 不想被看到 / 不感興趣 / 其他),僅匿名統計,不回饋給企業。

5. 資料實體與欄位

5.1 實體列表

實體名稱說明來源頁面/區塊
CompanyBlock使用者對公司的封鎖關係(user↔company + 時間 + 選填原因)SEC-1 封鎖清單
BlockCandidateRef搜尋新增用的公司精簡資訊與當前狀態SEC-2 新增封鎖 Modal

5.2 TypeScript 介面(語意用途)

interface CompanyBlock {
  enc_id: string;
  company: {
    enc_id: string;
    name: string;
    logo_url?: string;
  };
  blocked_at: string;            // ISO8601
  reason_code?: 'former_employer' | 'privacy' | 'not_interested' | 'other'; // BR-029,選填
}

interface BlockCandidateRef {
  company_enc_id: string;
  name: string;
  logo_url?: string;
  is_blocked_by_me: boolean;     // 已在清單則顯示已封鎖/不可再新增
  is_saved_by_me: boolean;       // 公司已收藏或旗下職缺已收藏(BR-024 Modal「將同時移除相關收藏」)
}

6. 畫面區塊與資料需求

6.1 區塊列表

區塊 ID區塊名稱說明
SEC-1封鎖清單頁掛在帳號設定;CompanyBlock[],可解除封鎖
SEC-2新增封鎖 Modal於 SEC-1 內搜尋公司 → 逐筆新增或「封鎖全部」
SEC-3封鎖確認/影響說明說明雙向隱藏 + 將移除相關收藏;可選原因(寫入前必須確認)
SEC-4已封鎖遮罩深連結開啟被封鎖公司頁時的遮罩 + 解除入口(非封鎖動點)
SEC-5空狀態無封鎖時說明文案

6.2 各區塊資料需求

SEC-1 封鎖清單頁(帳號設定 · 唯一動點)

  • 掛載位置:求職者帳號設定 →「封鎖公司」
  • 顯示實體CompanyBlock[],分頁、排序 blocked_at desc
  • 操作:解除封鎖(即時恢復搜尋/推薦曝光);成功/失敗以 toast 回饋,處理中鎖定該列按鈕
  • 清單篩選:提供公司名稱關鍵字篩選(前端過濾即可,上限 50 筆)
  • 公司已關閉/下架:仍顯示該列(名稱、logo 以封鎖當時快照呈現),標註「此公司已關閉」,仍可解除封鎖釋出配額
  • 計數文案:顯示 {n} / 50 家;僅在 n >= 50 時加「已達上限將無法再新增」

SEC-2 新增封鎖 Modal

  • 開啟:SEC-1「新增封鎖」;已達 50 家時禁用開窗並提示上限
  • 行為:關鍵字搜尋公司 → 逐筆新增,或「封鎖全部」目前結果
  • 狀態同步:候選的 is_blocked_by_me 必須與清單即時同步;已封鎖不得再寫入(idempotent,禁止重複列)
  • 批次上限blockAll / blockOne 過程中仍檢核剩餘額度;滿 50 即停並提示
  • 寫入語意:點選新增後須經 SEC-3 確認才寫入(不得「點了立即寫入、無確認」);取消/關窗時未確認者不得留下封鎖

SEC-3 封鎖確認 Modal

  • 內容:雙向隱藏說明、若 is_saved_by_me(公司已收藏或旗下職缺已收藏)顯示「將同時移除相關收藏」、選填原因(BR-029)
  • 歷史應徵告知:若對該公司有歷史應徵(pm_42),顯示「你先前投遞的應徵,該公司仍可查看」——封鎖不溯及已送達對方的資料(對齊 104 / Indeed 慣例)
  • 達上限:若已達 50 家,禁用並提示(BR-028)
  • 互動回饋:確認送出時按鈕鎖定並顯示 loading;成功後 toast「已封鎖 {公司名}」,失敗顯示原因並可重試(timeout 15s)

SEC-4 已封鎖遮罩(非入口)

  • 觸發:直接以 enc_id/深連結開啟已封鎖公司頁
  • 內容:「你已封鎖此公司,內容已隱藏」+ 解除封鎖按鈕(BR-027 #2)
  • 注意:此畫面不是「去封鎖」的路徑;正常封鎖流程只走 SEC-1 → SEC-2 → SEC-3

7. 使用者動作與後端需求

動作 ID動作名稱類型需要後端涉及實體說明
ACT-1封鎖公司寫入CompanyBlock建立封鎖;連動移除公司收藏及旗下職缺收藏(BR-024);檢核上限(BR-028);(user,company) 唯一
ACT-2解除封鎖寫入CompanyBlock移除封鎖;恢復曝光
ACT-3載入封鎖清單查詢CompanyBlock分頁
ACT-4搜尋可封鎖公司查詢BlockCandidateRefSEC-2 搜尋候選(含 is_blocked / is_saved)

雙向隱藏(BR-027)非單一動作,而是搜尋/推薦/人才曝光查詢的過濾條件:職缺搜尋、公司搜尋、企業端人才搜尋與 pm_45 列表皆需 join 封鎖名單做排除。


8. API Hints

8.1 Read

  • HINT-GET-1:封鎖清單(分頁)→ SEC-1
  • HINT-GET-2:搜尋可封鎖公司(keyword + is_blocked_by_me + is_saved_by_me + 我的已封鎖總數)→ SEC-2 / SEC-3

8.2 Write

  • HINT-MUTATION-1:封鎖公司(輸入 company_enc_id, reason_code?)→ 後端同時移除公司收藏及旗下職缺收藏(BR-024)、檢核上限;重複請求 idempotent
  • HINT-MUTATION-2:解除封鎖

8.3 過濾整合(影響既有查詢,非新 endpoint)

  • HINT-FILTER-1:職缺搜尋 / 推薦排除 blocked_company(BR-015 + BR-027)
  • HINT-FILTER-2:公司搜尋排除 blocked_company(BR-014 + BR-027)
  • HINT-FILTER-3:企業端人才搜尋 / 誰來看我 / pm_45 列表排除「已封鎖該企業之求職者」(BR-027 #3)

9. 導航 / Storybook Map

  • Storybook 標題Mockups/求職者/封鎖公司
  • Stories 路徑storybook/src/stories/mockups/user/BlockCompany.stories.ts
  • 情境控制(建議 args)scenario(list-empty / list-has-items / confirm-modal / blocked-overlay / limit-reached / saved-conflict)
  • IA:會員中心 shell,active=setting,導覽至「封鎖公司」
  • 不做:公司頁/職缺卡 BlockToggle isolation story

10. Flowcharts

10.1 User Flow

flowchart TD
    A[帳號設定 > 封鎖公司] --> B[封鎖清單 SEC-1]
    B --> C[新增封鎖]
    C --> D[搜尋公司 SEC-2]
    D --> E{選一筆或封鎖全部}
    E --> F{已達 50 家?}
    F -- 是 --> G[提示上限, 禁用]
    F -- 否 --> H[封鎖確認 Modal SEC-3]
    H --> I{已收藏公司或旗下職缺?}
    I -- 是 --> J[提示: 將同時移除相關收藏]
    H --> K[選填原因]
    J --> L[確認封鎖]
    K --> L
    L --> M[移除相關收藏 + 建立封鎖]
    M --> N[該公司職缺/公司頁自此用戶隱藏]
    B --> O[解除封鎖]
    O --> P[恢復曝光]
    Q[深連結開啟被封鎖公司頁] --> R[顯示已封鎖遮罩 SEC-4 + 解除入口]

10.2 System Flow

sequenceDiagram
    participant User
    participant FE as Frontend
    participant BE as Backend
    User->>FE: 帳號設定 > 封鎖公司 > 新增封鎖
    FE->>BE: GET 搜尋候選(keyword) + 已封鎖數
    BE-->>FE: BlockCandidateRef[]
    User->>FE: 選公司
    FE-->>User: 確認 Modal(影響說明)
    User->>FE: 確認
    FE->>BE: POST 封鎖(company, reason?)
    BE->>BE: 檢核上限(50) + 移除公司/職缺收藏(BR-024) + 建立封鎖
    BE-->>FE: 成功
    FE-->>User: 清單更新, toast
    Note over BE: 後續職缺/公司/人才查詢 join 封鎖名單排除(BR-027)

10.4 Edge Case Coverage

類別情境系統行為UI 回饋可重試資料一致性策略備註
Network & Performance封鎖請求逾時取消逾時 + 重試Yes未建立封鎖
Network & Performance封鎖連點idempotent鎖定按鈕No(user,company) 唯一
Data & Input達 50 上限後端拒絕提示上限N/AN/ABR-028
Data & Input封鎖全部逼近上限封到滿即停提示已達上限N/A不得超過 50
Data & Input搜尋候選已在清單不寫入、顯示已封鎖不可再按新增N/A與清單同步
User Interruption確認 Modal 中離開不建立封鎖Yes無寫入
Auth & LifecycleSession 過期導登入回跳提示Yes
Logical Inconsistency封鎖時該公司或旗下職缺已被收藏連動移除公司收藏及旗下職缺收藏Modal 提示「將同時移除相關收藏」N/A連動清除(BR-024)
Logical Inconsistency已封鎖公司嘗試收藏不適用(BR-027 已隱藏入口)無收藏 toggle 可點N/A預防性隱藏BR-027
Logical Inconsistency封鎖後仍有歷史應徵保留紀錄,隱藏再投入口pm_42 boundaryN/A不撤回既有應徵見問題 #1
Logical Inconsistency直接開啟被封鎖公司頁回遮罩非 404「你已封鎖此公司」N/A區別不存在BR-027 #2;非封鎖入口
Auth & Lifecycle解除封鎖後並行查詢即時恢復曝光列表刷新Yes以封鎖名單即時為準
Network & Performance封鎖/解除請求處理中按鈕鎖定loading;成功/失敗 toastYes以後端結果為準
Auth & Lifecycle排程/彙整通知送達前對象被封鎖寄出前重新過濾封鎖名單通知不含被封鎖公司內容N/A以送達時點的封鎖名單為準pm_32
Auth & Lifecycle帳號刪除CompanyBlock 隨帳號刪除N/A需回寫 BR-012 資料處理表補列 CompanyBlockBR-012
Data & Input被封鎖公司關閉/下架清單保留快照列標註「此公司已關閉」,可解除N/A名稱/logo 封鎖時快照SEC-1
Logical Inconsistency企業端以直連 URL 開啟已封鎖本公司之求職者頁視同列表排除,不可見呈現方式由 pm_45 定N/ABR-027 #3 全入口一致pm_45

11. Mock Data Schema

  • 資料來源:僅 mock。
  • CRUD 行為:封鎖/解除僅元件狀態,重整還原。
  • 情境切換scenario 控制 list-empty / list-has-items / confirm-modal / blocked-overlay / limit-reached / saved-conflict。

12. 實作備註

  • 禁止事項:❌ 不定義最終 API;❌ 不實作公司頁/職缺卡封鎖入口;BR-024/027/028/029 已回寫 business-rules.md v1.6.2。
  • 注意事項
    • C 端 → user CSS profile。
    • 唯一動點是帳號設定「封鎖公司」;前端只負責清單/搜尋新增/確認 Modal;雙向隱藏是後端查詢層過濾。
    • 「已封鎖遮罩」用遮罩而非 404,與「公司不存在/資料不完整(BR-014 的 404)」語意區別;遮罩不是封鎖流程的一部分。
    • 封鎖名單為高敏感個資(等同使用者的現任雇主/防範對象清單):存取管控與稽核 log 比照履歷等級(BR-017 之上明確加嚴);業界曾發生封鎖名單外洩事故(1111,2018)。
    • 封鎖說明文案不得宣稱「完全匿名」;可承諾「對方不會收到通知」。「不可被察覺」需後端逐一驗證企業端計數器(人才搜尋數、誰來看我、CRM 統計)無可推斷變化後方可加註。

12.1 四致性表

欄位對照表

欄位語意SoT版本
CompanyBlock.reason_code封鎖原因(匿名統計)BR-029v1
BlockCandidateRef.is_blocked_by_me搜尋候選是否已封鎖PRDv1.2
BlockCandidateRef.is_saved_by_me公司已收藏或旗下職缺已收藏(Modal 提示)BR-024v1

常數表

常數SoT版本
封鎖上限50 家BR-028v1
封鎖請求 timeout15sPRDv1

事件字典

事件觸發PayloadSoT版本
company_block_confirmed確認封鎖{company_enc_id, reason_code?, removed_related_saves:bool}PRDv1
company_block_removed解除封鎖{company_enc_id}PRDv1
blocked_overlay_viewed開啟被封鎖公司頁{company_enc_id}PRDv1

術語字典

術語定義SoT版本
封鎖使用者對公司的單向隱藏關係(效果雙向隱藏)BR-027v1
雙向隱藏公司對用戶不曝光 + 用戶對該企業不曝光BR-027v1
已封鎖遮罩深連結開啟被封鎖公司頁時的遮罩(非 404;非封鎖入口)PRDv1.2

12.2 版本凍結表

項目v1/v2Blocking?原因決策者日期
封鎖/解除 + 清單(帳號設定唯一入口)v1MVP;公司頁/職缺卡入口廢除Eric2026/07/15
雙向隱藏(人才端排除)v1需與 pm_45 / 人才搜尋同步落地Eric2026/06/23
封鎖原因統計v1選填Eric2026/06/23
封鎖個人 / HRv2先做公司層級Eric2026/06/23
同負責人多企業帳號聯封v2Q5 裁決 2026/07/15:v1 不做法人/關鍵字匹配,文案誠實揭露「封鎖僅對此公司帳號有效」;B/C(統編/關鍵字雙軌)列 v2 評估Eric2026/07/15
深連結應徵已封鎖公司二次確認v1Q6 裁決 2026/07/15:跳二次確認(成本低,防履歷誤送不想給的對象)Eric2026/07/15

12.3 PRD 問題清單

#章節/規則問題建議修正類別影響SoT
1BR-027封鎖是否應「自動撤回對該公司進行中的應徵」?建議不自動撤回(保留歷史、隱藏再投),撤回另由 pm_42 撤回功能處理邏輯PRD
2BR-027 #2被封鎖公司頁回遮罩 vs 404,後端是否能區分?建議遮罩;RD 確認查詢層可帶封鎖旗標技術PRD
3HINT-FILTER-3人才端排除被封鎖求職者,對既有「誰來看我 / 人才搜尋」效能影響?RD 評估 join / 預計算效能PRD
4BR-027封鎖後既有訊息通道行為(能否再發、歷史對話可見性、未讀通知過濾)已裁決(2026/07/15)Q1 → Shadow block:企業照常送出、求職者收不到、解封不回放、歷史對話保留、通知靜默跳過;詳 §4.2 BR-027 #4邏輯PRD v1.3
5成功條件 vs pm_42「再投入口封鎖下隱藏」pm_44 列 v1、pm_42 排 v2已裁決(2026/07/15)Q2 → A:v1 就做,pm_42 v1 提前接 is_blocked_by_me,三方對齊窗口期排程PRD v1.3
6SEC 入口範圍搜尋 vs 公司頁已裁決(2026/07/15):只留帳號設定搜尋入口;公司頁/職缺卡路線廢除(對應 pm-decisions Q3 → C)範圍PRD v1.2
7BR-024封鎖連動移除之收藏,解除封鎖後是否恢復已裁決(2026/07/15)Q4 → B:改「隱藏」可逆語意,解封自動恢復;Modal 文案改「暫時隱藏…解除後恢復」;詳 §4.2 BR-024邏輯BR-024
8BR-022 交互同一負責人多企業帳號可繞過單一 enc_id 封鎖已裁決(2026/07/15)Q5 → A:v1 不做匹配、文案誠實揭露僅對此公司帳號有效;B/C 列 v2(見 §12.2)邏輯PRD v1.3
9應徵入口經深連結主動應徵已封鎖公司時,是否加二次確認已裁決(2026/07/15)Q6 → A:跳二次確認(見 §12.2、§10.4)邏輯PRD v1.3

12.4 Mockup 對齊待辦(非產品決策)

以下為現有 Storybook mockup 與本 PRD v1.2 的落差,屬實作修正(細節見 audit report):

  1. 寫入須改為「確認後才封鎖」;取消/關窗不得保留未確認寫入(含達上限開窗的提示)
  2. blockOne / blockAll 過程持續檢核 50 上限;禁止重複列 ✅ 已完成(0abb390,瀏覽器實測)
  3. 補 BR-029 原因 UI、SEC-3 確認、SEC-4 遮罩、缺 scenario
  4. 上限文案僅在滿額時顯示「已達上限…」;搜尋框需實際過濾 ✅ 已完成(0abb390
  5. 文案禁寫「完全匿名」 ✅ 已完成(0abb390,banner 改「封鎖不會通知對方」)

13. 下一步

  • ✅ Q1/Q2/Q4/Q5/Q6 已於 2026/07/15 拍板並回寫本文件(見 §12.3、§4.2、§12.2)。6 題全數結案。
  • Q1 Shadow block 落地:BR-027 加第 4 點已回寫 business-rules.md;W101-TalentSearchHub 約 6 插入點 + pm_32 通知規格同步(RD)。
  • Q2 窗口期對齊:與 pm_42 owner 三方確認 v1 提前接 is_blocked_by_me
  • Q4 用詞回寫:pm_43 BR-024 由「移除」改「隱藏(可逆)」,同步 Modal 文案。
  • 與 pm_45(企業人才 CRM)+ 人才搜尋對齊 BR-027 #3 的排除邏輯(blocking)。
  • Mockup 對齊 §12.4(寫入確認語意、上限、去重、原因/遮罩)+ Q6 深連結二次確認。
  • Coda:回寫 封鎖公司功能 列 PRD 欄=pm_44(review 後執行)。

文件結束