◆ 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/30
  • 版本:1.4.5(2026/08/04 ADR-029 U→C 訊息發送方向裁決:封鎖期間求職者不可對被封鎖公司發送訊息(雙向靜音,BE 強制+FE disabled+就地解封);BR-027 #4 補第 5 點;Q6 確認文案補「解除封鎖前收不到對方回覆」警告;聊天室 VM 加 enc_company_idis_blocked_by_me。業界實查後由 YouCheng 裁決);1.4.4(2026/07/31 SEC-2 兩項釐清:① keyword 僅比對公司名稱(不含介紹/最新消息,修 spec 內部矛盾,TSH PR #149);② 結果超過一頁的呈現提案(載入更多+共 N 筆)——尚未定案、FE/PM 討論中);1.4.3(2026/07/30 ADR-028 SEC-2 搜尋分流移至後端:q 為唯一搜尋參數+search_mode 回顯,舊 keywordtax_id 同批移除(一次硬切)+輸入防護(trim 非空、全形數字正規化);待 PM 核可);1.4.2(2026/07/30 ADR-027 SEC-2 統編搜尋放寬為多國 8/10/13 碼,配合 pm_38 多國註冊;待 PM 追認);1.4.1(2026/07/16 SEC-1 清單分頁 page_size=20);1.4.0(2026/07/16 UX 五題裁決:同 bar 統編搜尋、剩餘額度預先禁用、v1 移除封鎖全部、原因降級 v1.5、清單解封 Undo/SEC-4 輕確認);1.3.1(Q4 可逆語意全文對齊、Q6 補 edge case、BR 回寫 business-rules.md v1.7.0);1.3.0(2026/07/15 Eric 拍板 Q1/Q2/Q4/Q5/Q6);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;非封鎖入口)。
    • SEC-2 單一 searchbar 支援公司名稱或統一編號查詢(找公司用;非聯封)。
  • 排除範圍
    • 公司頁 / 職缺卡/overflow menu 上的「封鎖此公司」入口——不做、不規格化。
    • 「封鎖全部目前搜尋結果」(v1 不做;批次改由 v2 關聯帳號建議封鎖承接)。
    • 封鎖原因進主確認流(BR-029 降級 v1.5:成功後選填或延後)。
    • 封鎖個別 HR / 個人(僅封鎖公司層級;v2 再議)。
    • 同統編/集團多帳號聯封(Q5;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 通知規格同步。(財務前提:現企業發訊息/邀約不耗點數;若未來上點數制需重審此方案。)
      5. U 對 C 的訊息發送方向(ADR-029 裁決 2026/08/04):封鎖期間 U 不可對被封鎖 C 發送訊息(雙向靜音)。BE 於發送路徑(REST+socket)檢查發送者已封鎖收件公司即拒絕(4xx+i18n);FE 於該公司聊天室停用輸入框、顯示「已封鎖此公司,解除封鎖後可繼續對話」並就地提供解封入口(Q4 可逆,解封即恢復)。歷史對話可見性不變。此裁決不影響 Q6(主動應徵=單次自願曝光,仍放行+二次確認)——兩者對應業界兩種不同慣例:應徵類同 104/Indeed 的可見性封鎖(主動應徵放行)、聊天類同 LinkedIn/WhatsApp/BOSS直聘的聊天封鎖(一律雙向斷、要互動先解封)。詳 ADR-029。
    • 不影響: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 家。採剩餘額度驅動預先禁用:remaining = 50 - blocked_countremaining == 0 時 SEC-1「新增封鎖」禁用,且若 SEC-2 Modal 仍開著則所有「新增封鎖」立刻禁用(不必等關窗重開)。選定一筆進入 SEC-3 確認期間,Modal 內其餘新增暫時鎖定,避免並行搶額度。
  • BR-029(封鎖原因,匿名統計)v1 不進主確認流(降級 v1.5/非 blocking)。若日後補數據,採封鎖成功後 toast/底部 sheet 選填 chips(前東家 / 不想被看到 / 不感興趣 / 其他),僅匿名統計、不回饋企業、不收集自由文字。SEC-3 不顯示原因 UI。

5. 資料實體與欄位

5.1 實體列表

實體名稱說明來源頁面/區塊
CompanyBlock使用者對公司的封鎖關係(user↔company + 時間;reason 屬 v1.5)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,v1.5 選填;v1 可不寫入
}

interface BlockCandidateRef {
  company_enc_id: string;
  name: string;
  logo_url?: string;
  tax_id_last4?: string;         // 結果列可顯示統編後四碼,避免同名誤封
  is_blocked_by_me: boolean;     // 已在清單則顯示已封鎖/不可再新增
  is_saved_by_me: boolean;       // 公司已收藏或旗下職缺已收藏(BR-024 Modal「將暫時隱藏相關收藏」)
}

6. 畫面區塊與資料需求

6.1 區塊列表

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

6.2 各區塊資料需求

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

  • 掛載位置:求職者帳號設定 →「封鎖公司」
  • 顯示實體CompanyBlock[]分頁 page_size=20、排序 blocked_at desc(滿額 50 家最多 3 頁)
  • 操作(解除封鎖):清單列一點即解,不跳確認 Modal;成功 toast「已解除,對方可能再次看到你」+ Undo 5~8 秒(Undo 期間可復原該筆封鎖與相關收藏隱藏狀態);處理中鎖定該列;失敗 toast 可重試
  • 清單篩選:提供公司名稱關鍵字篩選(前端過濾即可,上限 50 筆);篩選後重算分頁,結果 ≤20 則不顯示分頁列
  • 公司已關閉/下架:仍顯示該列(名稱、logo 以封鎖當時快照呈現),標註「此公司已關閉」,仍可解除封鎖釋出配額
  • 計數文案:顯示 {n} / 50 家;僅在 n >= 50 時加「已達上限將無法再新增」
  • 分頁 UI:僅當目前清單(篩選後)> 20 筆時顯示;文案「第 {p} / {total} 頁 共 {n} 筆」;解封/新增後若當頁變空則自動退回上一頁

SEC-2 新增封鎖 Modal

  • 開啟:SEC-1「新增封鎖」;已達 50 家時禁用開窗並提示上限
  • 搜尋:單一 searchbar,placeholder「搜尋公司名稱或統一編號」(不做名稱/統編分 tab
    • 前端原樣送出輸入字串(q 參數,trim 後非空),分流由後端判定(v1.4.3 起,ADR-028):正規化(全形數字→半形、去除連字號/空白/點)後為 8、10 或 13 位純數字(對應 pm_38 支援國別 tw 8 碼/vn 10、13 碼/th 13 碼)→ 統編精準查(用正規化後純數字比對);否則 → 名稱關鍵字搜尋(用原字串
    • 回應附 search_modetax_idkeyword)供前端顯示「以統編精準搜尋」提示,前端不自行判斷統編格式
    • (沿革:v1.4.1 為前端分流+僅台灣 8 碼;v1.4.2 放寬多國 8/10/13 碼(ADR-027);v1.4.3 分流移至後端(ADR-028),q唯一搜尋參數、舊 keywordtax_id 同批移除(FE 即時切換、不設過渡期))
    • 結果列顯示公司名 + 統編後四碼(或產業),避免同名誤封
    • keyword 比對範圍(v1.4.4 釐清):名稱模式僅比對公司名稱,不含公司介紹/最新消息(封鎖為精準指認場景,介紹文命中徒增誤封風險;與公司搜尋(探索場景)的三欄位行為刻意不同)
    • 語意:統編僅作「找公司」查詢鍵;觸發同統編/集團聯封(Q5 仍為 v2)
  • 結果超過一頁(⚠️ 提案中,尚未定案——FE/PM 討論中,2026/07/31):候選方案為「載入更多」按鈕+顯示「共 N 筆」(讀 totalCount),不用 Paginator/無限捲動;配套:「載入更多」僅在 currentPage < totalPages 時顯示、載入中禁用、append 不重置捲動、新查詢清空累積並重置 currentPage、N 大時文案引導改用統編精準查。定案後更新本條並移除本警語。 無論採哪個方案,BE 分頁契約(currentPage/totalCount/totalPages,預設每頁 10)均已支援、零改動。
  • 行為:搜尋結果 僅逐筆「新增封鎖」→ 進入 SEC-3;無「封鎖全部」
  • 狀態同步:候選的 is_blocked_by_me 必須與清單即時同步;已封鎖不得再寫入(idempotent,禁止重複列)
  • 剩餘額度remaining = 50 - blocked_countremaining == 0 時所有「新增封鎖」立刻禁用;選定一筆進入 SEC-3 時,其餘列暫時鎖定
  • 寫入語意:點選新增後須經 SEC-3 確認才寫入(不得「點了立即寫入、無確認」);取消/關窗時未確認者不得留下封鎖

SEC-3 封鎖確認 Modal

  • 內容:雙向隱藏說明、若 is_saved_by_me(公司已收藏或旗下職缺已收藏)顯示「將暫時隱藏相關收藏,解除封鎖後恢復」(Q4 可逆語意,不得寫「移除」)、誠實揭露「封鎖僅對此公司帳號有效」
  • 不含:封鎖原因 UI(BR-029 不進 v1 主路徑)
  • 歷史應徵告知:若對該公司有歷史應徵(pm_42),顯示「你先前投遞的應徵,該公司仍可查看」——封鎖不溯及已送達對方的資料(對齊 104 / Indeed 慣例)
  • 達上限:若已達 50 家,禁用並提示(BR-028)
  • 互動回饋:確認送出時按鈕鎖定並顯示 loading;成功後 toast「已封鎖 {公司名}」,失敗顯示原因並可重試(timeout 15s)

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

  • 觸發:直接以 enc_id/深連結開啟已封鎖公司頁
  • 內容:「你已封鎖此公司,內容已隱藏」+ 解除封鎖按鈕(BR-027 #2)
  • 解除互動:點解除時先跳輕量確認「解除後可再次看到此公司內容,對方也可能看到你」→ 確認後解封(與 SEC-1 清單 Undo 策略不同:遮罩頁為偶發誤觸情境)
  • 注意:此畫面不是「去封鎖」的路徑;正常封鎖流程只走 SEC-1 → SEC-2 → SEC-3

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

動作 ID動作名稱類型需要後端涉及實體說明
ACT-1封鎖公司寫入CompanyBlock建立封鎖;連動隱藏公司收藏及旗下職缺收藏(BR-024,解除後恢復);檢核上限(BR-028);(user,company) 唯一
ACT-2解除封鎖寫入CompanyBlock移除封鎖;恢復曝光與收藏;SEC-1 支援 Undo 窗口
ACT-3載入封鎖清單查詢CompanyBlock分頁 page_size=20
ACT-4搜尋可封鎖公司查詢BlockCandidateRefSEC-2:單一 q(後端分流統編精準查/名稱搜尋,ADR-028;舊 keywordtax_id 已移除)+ is_blocked / is_saved

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


8. API Hints

8.1 Read

  • HINT-GET-1:封鎖清單(page + page_size=20,預設排序 blocked_at desc)→ SEC-1
  • HINT-GET-2:搜尋可封鎖公司(單一 q,後端分流(ADR-028),回 search_mode + is_blocked_by_me + is_saved_by_me + 我的已封鎖總數 + tax_id_last4?)→ SEC-2 / SEC-3
  • HINT-GET-3:公司頁詳情 API 需帶 is_blocked_by_me 旗標(或以專屬狀態碼區別)→ SEC-4 遮罩判斷;語意須與 404(不存在)明確區隔(問題清單 #2,RD 確認查詢層可帶旗標)

8.2 Write

  • HINT-MUTATION-1:封鎖公司(輸入 company_enc_id;reason_code 僅 v1.5)→ 後端同時隱藏公司收藏及旗下職缺收藏(BR-024 可逆)、檢核上限;重複請求 idempotent
  • HINT-MUTATION-2:解除封鎖 → 恢復曝光+恢復被隱藏之收藏(BR-024);SEC-1 可於 Undo 窗口內再 POST 封鎖還原
  • HINT-MUTATION-3(v1.5):補寫/更新 reason_code(封鎖成功後選填)

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(has-items / empty / add-modal / approaching-limit / limit-reached / blocked-overlay)
  • IA:會員中心 shell,active=setting,導覽至「封鎖公司」;blocked-overlay 為公司頁深連結殼(非帳號設定)
  • 不做:公司頁/職缺卡 BlockToggle isolation story;不做「封鎖全部」按鈕 story;不做 404 冒充已封鎖(SEC-4 必須是遮罩狀態頁)

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[提示: 將暫時隱藏相關收藏, 解除後恢復]
    J --> L[確認封鎖]
    H --> L
    L --> M[隱藏相關收藏 + 建立封鎖]
    M --> N[該公司職缺/公司頁自此用戶隱藏]
    B --> O[解除封鎖]
    O --> P[Toast + Undo 5至8秒]
    P --> Q[恢復曝光與收藏]
    R[深連結開啟被封鎖公司頁] --> S[顯示已封鎖遮罩 SEC-4]
    S --> T[輕確認後解除]

10.2 System Flow

sequenceDiagram
    participant User
    participant FE as Frontend
    participant BE as Backend
    User->>FE: 帳號設定 > 封鎖公司 > 新增封鎖
    FE->>BE: GET 搜尋候選(q 原樣送出) + 已封鎖數
    Note over BE: 分流:8/10/13 碼純數字→統編精準查,否則名稱搜尋(回 search_mode)
    BE-->>FE: BlockCandidateRef[]
    User->>FE: 選公司
    FE-->>User: 確認 Modal(影響說明, 無原因)
    User->>FE: 確認
    FE->>BE: POST 封鎖(company)
    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 remaining==0
Data & InputModal 內封滿第 50 家寫入成功後立刻禁用其餘新增按鈕 disabledN/A不得超過 50
Data & Input以統編搜尋tax_id 精準匹配結果列含後四碼Yes非聯封與 Q5 區隔
Data & Input搜尋候選已在清單不寫入、顯示已封鎖不可再按新增N/A與清單同步
User Interruption確認 Modal 中離開不建立封鎖Yes無寫入
Auth & LifecycleSession 過期導登入回跳提示Yes
Logical Inconsistency封鎖時該公司或旗下職缺已被收藏連動隱藏公司收藏及旗下職缺收藏Modal 提示「將暫時隱藏相關收藏,解除後恢復」N/A連動隱藏,解除封鎖恢復(BR-024 可逆)Q4
Logical Inconsistency經深連結主動應徵已封鎖公司跳二次確認,確認後才進應徵流程「你已封鎖此公司,主動應徵對方即可看到你的履歷,且在解除封鎖前你將收不到對方的任何回覆,確定?」(v1.4.5 補後半警告——C→U 回覆受 shadow block 抑制,應徵後收不到面試安排屬預期行為,須事前告知)N/A確認不解除封鎖,僅放行該次應徵Q6;應徵流程側(pm_42 boundary);警告文案 ADR-029 附帶
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清單解除封鎖即時解封 + Undo 窗口toast + UndoYesUndo 復原該筆SEC-1
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 控制 has-items / empty / add-modal / approaching-limit(49→50)/ limit-reached / blocked-overlay(SEC-4 遮罩非 404)。

12. 實作備註

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

12.1 四致性表

欄位對照表

欄位語意SoT版本
CompanyBlock.reason_code封鎖原因(匿名統計)BR-029v1.5(v1 可不寫)
BlockCandidateRef.tax_id_last4結果列統編後四碼PRDv1.4
BlockCandidateRef.is_blocked_by_me搜尋候選是否已封鎖PRDv1.2
BlockCandidateRef.is_saved_by_me公司已收藏或旗下職缺已收藏(Modal 提示)BR-024v1

常數表

常數SoT版本
封鎖上限50 家BR-028v1
清單每頁筆數20PRDv1.4.1
解封 Undo 窗口5~8sPRDv1.4
封鎖請求 timeout15sPRDv1

事件字典

事件觸發PayloadSoT版本
company_block_confirmed確認封鎖{company_enc_id, hidden_related_saves:bool}PRDv1.4(Q4 可逆;欄位由 removed_related_saves 更名)
company_block_reason_submitted成功後選填原因(v1.5){company_enc_id, reason_code}BR-029v1.5
company_block_removed解除封鎖{company_enc_id}PRDv1
company_block_undo解封 Undo 復原{company_enc_id}PRDv1.4
blocked_overlay_viewed開啟被封鎖公司頁{company_enc_id}PRDv1

術語字典

術語定義SoT版本
封鎖使用者對公司的單向隱藏關係(效果雙向隱藏)BR-027v1
雙向隱藏公司對用戶不曝光 + 用戶對該企業不曝光BR-027v1
已封鎖遮罩深連結開啟被封鎖公司頁時的遮罩(非 404;非封鎖入口)PRDv1.2
剩餘額度50 - blocked_count;驅動預先禁用新增BR-028v1.4

12.2 版本凍結表

項目v1/v2Blocking?原因決策者日期
封鎖/解除 + 清單(帳號設定唯一入口)v1MVP;公司頁/職缺卡入口廢除Eric2026/07/15
雙向隱藏(人才端排除)v1需與 pm_45 / 人才搜尋同步落地Eric2026/06/23
同 bar 統編搜尋(找公司)v1UX 裁決:單一 searchbar,非 tabEric2026/07/16
剩餘額度預先禁用v1UX 裁決:封滿即禁用其餘新增Eric2026/07/16
「封鎖全部」搜尋結果❌ 不做UX 裁決:v1 移除;批次改 v2 關聯建議Eric2026/07/16
封鎖原因統計v1.5UX 裁決:不進主確認流;成功後選填或延後Eric2026/07/16
清單解封 Undo/SEC-4 輕確認v1UX 裁決:非對稱確認Eric2026/07/16
SEC-1 清單分頁 page_size=20v1滿額最多 3 頁;≤20 不顯示分頁列Eric2026/07/16
封鎖個人 / 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
10SEC-2 搜尋統編要用同 bar 還是另開 tab?已裁決(2026/07/16):同 bar;8 碼走 tax_idUXPRD v1.4
11BR-028封滿第 50 家後其餘新增是否禁用?已裁決(2026/07/16):remaining==0 預先/即時禁用UXPRD v1.4
12SEC-2是否保留「封鎖全部」?已裁決(2026/07/16):v1 移除範圍PRD v1.4
13BR-029封鎖原因是否進主流程?已裁決(2026/07/16):不進;降級 v1.5 成功後選填UXPRD v1.4
14ACT-2解除是否要確認 Modal?已裁決(2026/07/16):清單 Undo;SEC-4 輕確認UXPRD v1.4

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

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

  1. 寫入須改為「確認後才封鎖」;取消/關窗不得保留未確認寫入(含達上限開窗的提示)
  2. blockOne 過程持續檢核 50 上限;禁止重複列;移除 blockAll ✅ 上限/去重已完成;blockAll 於 v1.4 產品裁決移除
  3. 補 SEC-3 確認、SEC-4 遮罩+輕確認解封、缺 scenario ✅ SEC-3/SEC-4/approaching-limit 已補;BR-029 原因 UI 補進 v1
  4. 上限文案僅在滿額時顯示「已達上限…」;搜尋框需實際過濾 ✅ 已完成(0abb390
  5. 文案禁寫「完全匿名」 ✅ 已完成(0abb390,banner 改「封鎖不會通知對方」)
  6. 同 bar 統編搜尋 + 剩餘額度即時禁用 + 清單解封 Undo + 拿掉封鎖全部 + SEC-3 確認 ✅ 已對齊 mockup(v1.4)
  7. SEC-1 分頁 page_size=20 ✅ 已對齊 mockup(v1.4.1)

13. 下一步

  • ✅ Q1/Q2/Q4/Q5/Q6 已於 2026/07/15 拍板並回寫本文件(見 §12.3、§4.2、§12.2)。6 題全數結案。
  • ✅ UX 五題(統編 bar/上限禁用/封鎖全部/原因/解封確認)已於 2026/07/16 拍板並回寫本文件 v1.4;分頁 page_size=20 見 v1.4.1。
  • Q1 Shadow block 落地:BR-027 第 4 點已回寫 business-rules.md v1.7.0(2026/07/15,同批回寫 BR-024 可逆語意、BR-012 補封鎖名單);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(確認語意、統編 bar、Undo、拿掉封鎖全部、SEC-4)+ Q6 深連結二次確認。
  • Coda:回寫 封鎖公司功能 列 PRD 欄=pm_44(review 後執行)。

14. ADR(架構決策紀錄)

對齊 wport_skills gen-prd Rule 22;格式參照 pm_38 §12。

ADR-027: SEC-2 統編搜尋放寬為多國 8/10/13 碼(配合 pm_38 多國註冊)

  • Status: Proposed(待 PM 追認;動到 v1.4「同 bar 統編搜尋(8 碼)」裁決的適用範圍)
  • Context:
    • v1.4 裁決時(2026/07/16)平台僅有台灣公司,SEC-2 統編搜尋定為「恰 8 位數字 → tax_id 精準查」。
    • pm_38(公司編輯/註冊 2.0)開放 tw/vn/th 三國註冊:vn MST 為 10 或 13 碼、th MOA 為 13 碼;companies.country_code 已落地。
    • 兩者同時在測試站後發現衝突:vn/th 公司的登記號碼會被「8 碼」判準擋在兩層——FE isTaxIdQuery 判非統編而當名稱關鍵字送 keyword(名稱 LIKE 必然落空);即使送 tax_id,BE DTO TAX_ID_REGEX = /^\d{8}$/ 直接 400。非台灣公司統編搜尋必然失敗
    • 另 vn/th 慣用書寫含連字號(0123456789-0010-1055-...),原契約未定義正規化。
  • Decision:
    1. tax_id 接受值由「恰 8 位數字」放寬為 8/10/13 位純數字(tw 8/vn 10、13/th 13),BE 驗證前自動去除連字號/空白/點(compactTaxIdSeparators)。
    2. 不驗 checksum:搜尋情境打錯 checksum 即精準查 0 筆,行為可預期;checksum 驗證僅存在於註冊流程(pm_38)。
    3. 查詢維持 uniform_number = ? 精準相等、不帶國別參數:三國長度互斥(8/10/13),無歧義;13 碼 vn/th 理論撞號時回列表由使用者以公司名+後四碼辨識。
    4. tax_id_last4 守衛同步放寬:8/10/13 碼合法長度才回後四碼,其餘(髒資料)回 null
    5. 分流責任仍在 FEisTaxIdQuery 判準同步放寬為「去分隔字元後為 8/10/13 位純數字」。
    6. 單一真相:W101-TalentSearchHub src/tools/tax-id.tool.ts TAX_ID_QUERY_REGEX
  • Rejected alternatives:
    • ❌ BE 從單一 keyword 自動分流(廢除 tax_id 參數)— 已交付契約的重寫,且 FE/BE 各留一套分流邏輯,維護成本大於收益。(此條前提於同日被推翻:FE 確認串接中、改動成本低,分流改由後端承擔——見 ADR-028,部分 supersede 本 ADR 的「分流責任仍在 FE」決策;放寬 8/10/13 碼本身仍有效)
    • ❌ 搜尋端引用 pm_38 checksum 驗證工具 — 搜尋不需真實性驗證;且會造成 pm_44 對 pm_38 的建置依賴。
    • tax_idcountry_code 參數 — 長度已互斥、相等比對天然跨國,多一參數無產品價值。
  • Consequences:
    • BE:已實作並上測試站(2026/07/30,分支 feature/pm_44-tax-id-multi-country,staging smoke 通過:10 碼回 200 空列表、不再 400)。
    • FE:isTaxIdQuery 需同步放寬(見前端對接文件 2026-07-30 變更紀錄),未改前使用者輸入 10/13 碼仍會被當名稱關鍵字。
    • PRD:§SEC-2 搜尋分流敘述更新(v1.4.2);「僅作找公司查詢鍵、不觸發聯封」語意不變(Q5 仍 v2)。
    • 完整端到端驗證(staging 以 pm_38 流程註冊 vn 公司 → 以 MST 搜到)待 FE 改完後補。
  • Decision maker: 待 PM 追認 | Date: 2026/07/30(RD 提案)

ADR-028: SEC-2 搜尋分流移至後端(單一 q 參數+search_mode 回顯)

  • Status: Proposed(待 PM 核可;partially supersedes ADR-027 之「分流責任仍在 FE」決策——多國 8/10/13 碼放寬本身不變)
  • Context:
    • ADR-027 維持既有契約形狀:FE 以 isTaxIdQuery 判斷輸入是否為統編,分別送 keywordtax_id 兩個互斥參數。
    • 這使「什麼算統編」的判準同時存在 FE 與 BE 兩處。pm_38 多國化事故已示範代價:規則一變,FE(web+iOS 各一份)與 BE 都要同步改,漏一端即壞;iOS 改版還受 App Store 審核週期約束。
    • ADR-027 當時 reject「BE 分流」的理由是「已交付契約的重寫」;經確認 FE 尚在串接中、改動成本低(刪 isTaxIdQuery、無條件送原字串,程式碼淨減少),該前提不成立。pm_44 尚未上正式站,是修契約形狀的最後低成本窗口。
    • 儲存端已對齊:pm_38 入庫強制 normalizeTaxId()(去除所有分隔符、只留純數字),VN 分公司號官方寫法 xxxxxxxxxx-xxx 存為 13 碼純數字;搜尋端 compact 後精準比對即可命中,無需額外處理。
  • Decision:
    1. API-2 以 q唯一搜尋參數(必填;keywordtax_id 同批移除):FE 原樣送出使用者輸入,分流由 BE 判定——正規化(全形數字→半形、去除連字號/空白/點)後為 8/10/13 位純數字 → 統編精準查;否則 → 名稱關鍵字搜尋(名稱查詢用原字串,統編查詢用正規化後純數字)。
    2. 回應 extra 增加 search_mode'tax_id''keyword'):BE 告知 FE 本次實際採用的模式,FE 據此顯示「以統編精準搜尋」提示,不自行判斷統編格式
    3. 分流判準單一真相:W101-TalentSearchHub src/tools/tax-id.tool.ts(沿用 ADR-027 的 TAX_ID_QUERY_REGEXcompactTaxIdSeparators)。
    4. 一次硬切(無過渡期):BE 直接以 q 取代 keywordtax_id 上 staging,FE 即時跟進切換(PM 2026/07/30 裁決:FE 確認可立即串新契約;僅 staging、無 prod 流量,無並存必要)。
    5. 輸入防護(edge case 盤點納入)
      • q 於 DTO trim 後非空才通過(全空白輸入 400,防止 " " 落入名稱 LIKE 誤匹配大量含空格名稱)。
      • 正規化含全形數字→半形0300825675 視同 0300825675,中文輸入法情境)。
  • Rejected alternatives:
    • ❌ 維持 FE 分流(ADR-027 現狀)— 分流規則兩份真相的長期負債,每次統編規則變動需 web/iOS/BE 三端同步。
    • ❌ BE 以 uniform_number = ? OR name LIKE ? 混合查詢 — 模糊掉「統編精準查」語意,且與「不驗 checksum」決策相斥(打錯統編會混入名稱撞數字的雜訊結果)。
    • ❌ Additive 過渡並存(q 與舊參數三擇一、分階段移除)— FE 已確認可即時切換且僅 staging 無 prod 流量,並存徒增 DTO 驗證複雜度與「兩種用法」的文件負擔(PM 2026/07/30 裁決硬切)。
  • Consequences:
    • FE:刪 isTaxIdQuery 與雙參數組裝邏輯(程式碼淨減少);「以統編精準搜尋」提示改讀 extra.search_mode;型別更新。
    • BE:DTO 改單一 q(trim 非空+長度 1–100)、service 分流(全形正規化+compact+單一真相 util)、extra.search_mode;新分支+PR(base feature/pm_44-block-company)。
    • 未來統編規則變動(新國別等)為 BE 單端改動,FE/iOS 零改版。
    • 死角附帶收斂:純數字公司名稱/打錯統編的 fallback 若日後需要,屬 BE 內部演進,契約不動。
    • PRD §SEC-2/ACT-4/HINT-GET-2/時序圖同步更新(v1.4.3)。
  • Decision maker: 待 PM 核可 | Date: 2026/07/30(RD 提案,FE 已同意改動)

ADR-029: 封鎖後求職者→被封鎖公司的發送方向(U→C 一併靜音)

  • Status: Accepted(YouCheng 2026/08/04 裁決;補 BR-027 #4 的規範空白——Q1 只裁決 C→U 方向,U→C 未定義)
  • Context:
    • BR-027 #4(Q1)定義 C→U shadow block;U→C 能否發送,PRD/spec 皆未著墨。實作現況為能傳且公司看得到(suppress 判定僅在發送者為企業時觸發)。
    • 問題一(破壞不可察覺性):shadow block 的掩護是「已送出無回應 ≈ 已讀不回」。若封鎖者仍可發訊息,「該 U 主動傳訊息、卻永遠不讀我的任何回覆」是遠強於單純永遠未讀的封鎖 oracle——U 自己的行為會暴露封鎖。
    • 問題二(心智模型矛盾):收不到對方訊息卻可單向喊話;U 發出後永遠等不到回應(回覆被 suppress),淪為客訴與「產品壞了」的錯誤歸因。
    • 問題三(業界實查 2026/08/04):聊天封鎖在所有主流產品皆為雙向靜音——LinkedIn(封鎖後雙方皆不可互發、對話串凍結)、WhatsApp(須先解封才能發)、Instagram(對話串消失)、BOSS直聘(屏蔽後須取消屏蔽才能溝通)。無任何平台允許封鎖者單向喊話。
    • 與 Q6 的關係:業界實為兩種不同功能——可見性封鎖(104/Indeed:擋被動曝光,主動應徵=自願單次曝光放行)與聊天封鎖(雙向靜音)。Q6 對齊前者、本 ADR 對齊後者,兩者並存不矛盾。
  • Decision:
    1. 封鎖期間 U 對被封鎖 C 的訊息發送一律禁止(含既有聊天室內所有 U→C 送出)。
    2. 後端強制:REST+socket 發送路徑檢查「發送者已封鎖收件公司」→ 拒絕(4xx+i18n)。不可只靠前端(API 直打即繞過,問題一的 oracle 仍在)。
    3. 前端呈現:已封鎖公司聊天室歷史對話維持可見(BR-027 不變);輸入框 disabled+「已封鎖此公司,解除封鎖後可繼續對話」+就地解封入口(Q4 可逆語意)。
    4. 契約支援:求職者聊天室列表/詳情 VM 新增 enc_company_idis_blocked_by_me(additive),供 FE 判斷封鎖狀態;BE 4xx 為 backstop,正常流程使用者不應看到該錯誤。
    5. 企業端無感:企業無法觀測 U 輸入框狀態,不新增可察覺訊號;已接受弱訊號清單不變。
    6. Q6 維持(放行應徵+二次確認),僅補確認文案後半警告「解除封鎖前你將收不到對方的任何回覆」(見 §10 edge case 表 v1.4.5 修訂)。
  • Rejected alternatives:
    • ❌ 維持可傳(現況)— oracle 洩漏+死巷體驗,且業界無此先例。
    • ❌ 放行+二次確認警示(比照 Q6)— 應徵是單次正式行為、聊天是持續關係,性質不同;業界聊天封鎖無「警示後放行」先例,且每次發送都在累積問題一的 oracle。
    • ❌ U→C 也 shadow suppress(假裝送出)— 對主動方隱瞞「訊息未送達」比封鎖本身更具欺騙性,客訴風險最高。
    • ❌ 僅前端 disable、後端不擋 — API 直打即繞過。
  • Consequences:
    • BE:聊天發送路徑(REST+socket)封鎖檢查+i18n 錯誤 path;求職者聊天室 VM 加 enc_company_idis_blocked_by_me;對應測試(封鎖→發送 4xx;解封→恢復;企業端不受影響)。
    • FE:聊天室封鎖狀態 UI(disabled 輸入框+banner+解封入口);Q6 確認文案更新。
    • spec 插入點清單新增「訊息發送 U→C 檢查」與 VM 欄位(修訂(八))。
  • Decision maker: YouCheng | Date: 2026/08/04(業界實查後定案)

文件結束