應徵紀錄(My Applications)Mockup PRD(給後端 API 規格產生器使用)
此範本專門給 gen-storybook-mockup-gen 產出的 mockup PRD 使用。 backend-api-spec-generator 會讀取這份文件與 Storybook mockups 來產生後端 API 規格。 產出 persona:CEO with PM Background(SaaS / HR tech lens)。
1. 文件資訊
- 文件類型:Mockup 需求規格書(前後端共用視圖)
- 適用對象:前端開發、後端開發、產品、QA
- 最後更新:2026/07/06
- 版本:1.2.1
- 對應 Storybook:
storybook/src/stories/mockups/user/ApplicationRecords.stories.ts(已產出) - Storybook 連結:https://storybook.wport.me/?path=/docs/mockups-user-applicationrecords—docs
- 建立日期:2026/06/23
- Coda backlog:
應徵紀錄
2. 開發進度與設計來源
開發進度
- 前端 PR #:[TBD]
- 後端 PR #:[TBD]
設計來源
- Figma 連結:[待補上]
- Pixsole / 既有畫面:求職者既有「會員中心」C 端框架(沿用 user app 樣式 profile;見實作備註)
3. 模擬頁面摘要(Mockup Summary)
3.1 功能概述
- 功能名稱:應徵紀錄(My Applications)
- 主要使用者角色:Job Seeker(求職者,含僑外生 / 外籍)
- 核心目標:
- 讓求職者在單一頁面掌握「我投過哪些職缺、能不能再投、下次可投時間」。
- 降低投遞後焦慮與重複投遞無效操作,提升投遞後 7 日回訪與再次應徵品質。
- 與 BR-011 的 72 小時再投冷卻一致地呈現「下次可投時間」。
3.2 範圍界定
- 包含範圍(v1):
- 應徵清單(同職缺多次合併一列;顯示公司、職缺、最近應徵日、
已投 N 次)。 - 「再次應徵」按鈕(冷卻中 disabled 並倒數;見 BR-011)。
- 職缺已關閉 / 已刪除:卡片灰階、職缺連結停用、顯示「此職缺已關閉」boundary(見 BR-009)。
- 應徵清單(同職缺多次合併一列;顯示公司、職缺、最近應徵日、
- 排除範圍(v1 不做,Storybook 可展示完整願景):
- 狀態 badge 與狀態篩選 tab(全部 / 審閱中 / 面試邀約 / 婉拒)→ v2(待 HR 流程)。
- 面試邀約 / 婉拒狀態(HR 端流程尚未實作)→ v2。
- pm_44 封鎖公司互動(再投隱藏 /
is_blocked_by_me)→ v2(pm_44 上線後)。 - 完整多次應徵時間軸(逐次列出)→ v2;v1 僅顯示最近應徵日與
apply_count。 - 站內信 / 與 HR 對話整合(另案)。
- 應徵附加問卷的填寫流程(Coda:投遞附加問卷,另案)。
- 履歷編輯(另案 edit-resume)。
3.3 成功條件
- 求職者能在 1 頁看到全部應徵紀錄,且冷卻中職缺明確顯示「下次可投時間」。
- 已關閉 / 已刪除職缺以灰階呈現,不會破圖或死連結,顯示「此職缺已關閉」。
- 應徵後 7 日回訪率、再次應徵轉換率可被埋點量測。
4. 商業規則對齊(Business Rules Alignment)
4.1 本功能使用到的業務規則
| 規則 ID | 規則摘要 | 是否關鍵 | 備註 |
|---|---|---|---|
| BR-011 | 同一職缺再次應徵須距上次滿 72 小時;冷卻中無法投遞,系統回傳冷卻中與下次可投時間 | ✅ 是 | 冷卻不再是獨立狀態/ tab;改由「再次應徵」按鈕承載:冷卻中按鈕 disabled 並倒數(HH:MM:SS / 天·時),到期自動可投 |
| BR-009 | 職缺狀態(上架/下架/已刪除/已關閉);已刪除、已關閉為終態 | ✅ 是 | 已投職缺若進入終態,詳情需 boundary 顯示,不可再投 |
| BR-015 | 職缺搜尋僅顯示上架職缺 | ⬜ 否 | 紀錄頁不受搜尋顯示限制(已投紀錄仍須可查看歷史),但「再次應徵」入口受 BR-009/BR-011 約束 |
| BR-001 | 只有 email 已驗證才能登入 | ✅ 是 | 應徵紀錄為登入後頁面 |
4.2 補充說明
- 本功能不新增 business rule,僅在 UI 層忠實呈現 BR-011/BR-009。
- 與 pm_44(封鎖公司)互動:v2;v1 不實作封鎖相關 boundary 與
is_blocked_by_me。
5. 資料實體與欄位(Data Entities & Fields)
描述「需要追蹤什麼資料、為什麼」,不規定 API 欄位命名 / 雜湊 / DB schema(由 OpenAPI Spec 決定)。
5.1 實體列表
| 實體名稱 | 說明 | 來源頁面/區塊 |
|---|---|---|
| ApplicationRecord | 一次應徵的快照與狀態,供清單與詳情顯示 | SEC-1 主列表、SEC-3 詳情 |
| JobSnapshot | 應徵當下的職缺顯示資訊(避免職缺被改/下架後顯示空白) | SEC-1、SEC-3 |
| CompanySnapshot | 應徵當下的公司顯示資訊(名稱、logo) | SEC-1、SEC-3 |
| ReapplyEligibility | 是否可再次應徵(含冷卻到期時間) | SEC-3、ACT |
5.2 實體欄位定義(TypeScript 介面,語意用途;命名待與 Storybook 對齊)
// v1 清單所需欄位(語意用途;命名由 OpenAPI Spec 決定)
interface ApplicationRecord {
enc_id: string;
job: JobSnapshot;
company: CompanySnapshot;
applied_at: string; // ISO8601 最近應徵時間
resume_enc_id: string;
apply_count: number; // 同職缺累計有效應徵次數(≥1)
reapply: ReapplyEligibility;
// v2 再納入:
// status_code: ApplicationStatus;
// status_name: string;
}
// v2:HR 處理狀態 + tab 篩選(全部 / 審閱中 / 面試邀約 / 婉拒)
type ApplicationStatus =
| 'submitted'
| 'reviewing'
| 'invited'
| 'rejected';
// 註(Eric 2026/06/24):冷卻不是 status enum,由 reapply.cooldown_until 驅動按鈕倒數。
// 註(Eric 2026/07/03):v1 不顯示狀態 badge / tab;Storybook mock 可保留完整願景供設計參考。
interface JobSnapshot {
enc_id: string;
title: string;
lifecycle_code: 'published' | 'unpublished' | 'closed' | 'deleted'; // 對齊 BR-009
area_name?: string;
}
interface CompanySnapshot {
enc_id: string;
name: string;
logo_url?: string;
// is_blocked_by_me: boolean; // v2 + pm_44
}
interface ReapplyEligibility {
can_reapply: boolean;
cooldown_until?: string; // ISO8601;冷卻中時提供,供按鈕倒數
reason_code?: 'cooldown' | 'job_closed' | 'job_deleted'; // v2 再加 company_blocked
}
6. 畫面區塊與資料需求(UI Sections & Data Needs)
6.1 區塊列表
| 區塊 ID | 區塊名稱 | 說明 |
|---|---|---|
| SEC-1 | 應徵清單 | 卡片/列表,一筆一職缺;公司/職缺/最近應徵日/再投;終態職缺灰階 |
| SEC-2 | 狀態篩選列 | v2(Storybook 可展示 tab:全部 / 審閱中 / 面試邀約 / 婉拒) |
| SEC-3 | 單筆詳情 | 點開抽屜或導職缺頁;v1 顯示最近應徵日 + 再投/冷卻倒數 |
| SEC-4 | 空狀態 | 無應徵時導向「找工作」CTA |
6.2 各區塊資料需求
SEC-1 應徵清單
- 顯示實體:
ApplicationRecord[] - 資料需求:分頁(page, page_size)、排序(預設 applied_at desc)
- 顯示規則:同職缺多次應徵合併為一列(
已投 N 次),時間顯示最近應徵日 - 職缺終態(closed / deleted):整卡灰階(
opacity降低或灰階濾鏡);職缺標題連結停用;顯示 boundary「此職缺已關閉」;隱藏「再次應徵」 - 空狀態:顯示「你還沒有應徵任何職缺」+「去找工作」CTA
SEC-2 狀態篩選列(v2)
- v2 實作 tab + 狀態 badge(全部 / 審閱中 / 面試邀約 / 婉拒)。
- Storybook mock 可保留完整 tab 作為設計願景;production v1 不實作。
SEC-3 單筆詳情
- 內容(v1):職缺標題(非終態可連結職缺頁)、公司、最近應徵日、
apply_count、再次應徵按鈕 - 內容(v2):狀態 badge、完整多次應徵時間軸、pm_44 封鎖 boundary
- 職缺終態:同 SEC-1 灰階 +「此職缺已關閉」、停用連結、隱藏再投
- 「再次應徵」按鈕的三態:
- 可投:enabled,文案「再次應徵」→ 導投遞流程。
- 冷卻中(BR-011):disabled,按鈕本身顯示倒數計時(剩 <1 天用
HH:MM:SS,≥1 天用「N 天 H 時後可再投」);hover/title 顯示「下次可投:{cooldown_until}」;到期自動恢復可投。 - 終態(BR-009):隱藏按鈕,灰階 + boundary「此職缺已關閉」。
- 倒數來源:前端依後端
reapply.cooldown_until換算並每秒 tick,避免時鐘漂移(§12)。
7. 使用者動作與後端需求(User Actions & Backend Needs)
| 動作 ID | 動作名稱 | 類型 | 需要後端 | 涉及實體 | 說明 |
|---|---|---|---|---|---|
| ACT-1 | 載入應徵清單 | 查詢(Read) | ✅ 是 | ApplicationRecord | 含分頁、排序;由後端依本 PRD 產 API spec |
| ACT-2 | 切換狀態 tab | 查詢(Read) | ❌ v2 | ApplicationRecord | v2 |
| ACT-3 | 開啟詳情 | 查詢(UI) | ❌ v1 可選 | ApplicationRecord | v1 清單欄位足夠;v2 再評估明細 API |
| ACT-4 | 再次應徵 | 寫入(Write) | ✅ 是 | ReapplyEligibility | 沿用既有 apply 流程;受 BR-011/BR-009 檢核 |
| ACT-5 | 撤回應徵(nice) | 寫入(Write) | ✅ 是 | ApplicationRecord | MVP 可不做,列入 v2 |
8. API Hints(提示用,非最終 API 設計)
8.1 資料讀取需求(Read)
- HINT-GET-1:取得我的應徵清單(分頁 + 排序)→ SEC-1;語意見 §5.2、§12.4 欄位映射(實作由
backend-api-spec-generator產出)
8.2 資料寫入需求(Write)
- HINT-MUTATION-1:再次應徵 → 沿用既有 apply 端點語意(
job_enc_id,resume_enc_id);須能讓 UI 呈現 BR-011 冷卻(清單/詳情之reapply.cooldown_until)。
9. 導航 / Storybook Map
- Storybook 標題:
Mockups/求職者/應徵紀錄 - Stories 路徑:
storybook/src/stories/mockups/user/ApplicationRecords.stories.ts - Storybook 連結:https://storybook.wport.me/?path=/docs/mockups-user-applicationrecords—docs
- 情境控制(實際 args):
scenario(empty/mixed/has-cooldown/job-closed)——封鎖情境併入mixed,無獨立company-blocked(對齊實際 Storybook) - Storybook 與 v1 關係:Storybook 為完整願景 mock(含 tab、四種狀態);production v1 範圍以 §3.2、§12.2 為準,兩者並存不視為 PRD 不一致。
- 對應 production 路由:求職者會員中心下(僅參考,不在此新增路由)
10. Flowcharts
10.1 User Flow
flowchart TD
U1[登入後進入會員中心] --> U2[開啟應徵紀錄]
U2 --> U3{有應徵紀錄?}
U3 -- 否 --> U4[空狀態 + 去找工作 CTA]
U3 -- 是 --> U5[顯示清單 + 最近應徵日]
U5 --> U7[開啟單筆詳情]
U7 --> U8{職缺狀態 / 冷卻?}
U8 -- 可再投 --> U9[再次應徵 -> 投遞流程]
U8 -- 冷卻中 --> U10[再次應徵按鈕 disabled + 倒數計時]
U8 -- 已關閉/已刪除 --> U11[灰階 + 此職缺已關閉, 停用連結]
10.2 System Flow
sequenceDiagram
participant User
participant FE as Frontend
participant BE as Backend
User->>FE: 開啟應徵紀錄
FE->>BE: GET 應徵清單(page)
BE-->>FE: ApplicationRecord[] (含 reapply/cooldown)
FE-->>User: 渲染清單
User->>FE: 點「再次應徵」
FE->>BE: POST 再次應徵(job,resume)
BE->>BE: 檢核 BR-011 冷卻 + BR-009 狀態
alt 通過
BE-->>FE: 成功
FE-->>User: 導投遞流程/成功提示
else 冷卻中或終態
BE-->>FE: 業務錯誤或 reapply 欄位
FE-->>User: 顯示原因與下次可投時間
end
10.4 Edge Case Coverage
| 類別 | 情境 | 系統行為 | UI 回饋 | 可重試 | 資料一致性策略 | 備註 |
|---|---|---|---|---|---|---|
| Network & Performance | 清單載入逾時 > 15s | 取消請求 | 顯示逾時 + 重試 | Yes | 不寫入 | |
| Network & Performance | 「再次應徵」連點 | 後端去重;前端按鈕 loading 鎖定 | 防連點 | No | idempotency by (user,job,window) | |
| Data & Input | 狀態 enum 未知值 | 前端 fallback 顯示「處理中」 | 不破版 | N/A | 以後端 status 為準 | v2(v1 無 status enum) |
| User Interruption | 載入清單中離開頁面 | 取消未完成請求 | 無 | Yes | 無寫入 | |
| Auth & Lifecycle | Session 過期 | 導登入並保留回跳 | 提示登入過期 | Yes | 無 | |
| Logical Inconsistency | 職缺已關閉/已刪除但紀錄存在 | 清單/詳情灰階,再投停用 | 「此職缺已關閉」;連結停用 | No | 以 job lifecycle 為準 | BR-009 |
| Logical Inconsistency | 冷卻中嘗試再投 | 後端拒絕回 cooldown_until | 顯示下次可投時間 | 到期後 Yes | 不建立重複應徵 | BR-011 |
11. Mock Data Schema
- 資料來源:僅 mock,不呼叫真實 API。
- 願景 vs v1:mock 可含 tab 與四種狀態(完整願景);v1 production 範圍見 §3.2。
12. 實作備註(Implementation Notes)
- 禁止事項:❌ 不在此定義最終 API path/method;❌ 不直接改 business-rules.md。
- 注意事項:
- 欄位命名與 Storybook/TS interface 一致。
- C 端頁面 → 走
userCSS profile(見 gen-storybook-mockup-gen 的 Dual CSS Profile Contract)。 - 冷卻倒數以後端
cooldown_until為準,前端僅做顯示換算,避免時鐘漂移。
12.1 四致性表(Stage 6)
欄位對照表
| 欄位 | 語意 | SoT | 版本 |
|---|---|---|---|
| status_code | HR 處理狀態 enum | PRD §5.2 | v2 |
| apply_count | 同職缺有效應徵次數 | PRD §5.2 | v1 |
| reapply.cooldown_until | 下次可投時間 | BR-011 + PRD | v1 |
| job.lifecycle_code | 職缺生命週期 | BR-009 | v1 |
常數表
| 常數 | 值 | SoT | 版本 |
|---|---|---|---|
| 再投冷卻 | 72 小時 | BR-011 | v1 |
| 清單分頁 page_size | 20 | PRD | v1 |
| 清單載入 timeout | 15s | PRD | v1 |
事件字典
| 事件 | 觸發 | Payload 摘要 | SoT | 版本 |
|---|---|---|---|---|
| application_list_viewed | 開啟頁面 | {} | PRD | v1 |
| application_reapply_clicked | 點再次應徵 | {job_enc_id, result} | PRD | v1 |
術語字典
| 術語 | 定義 | SoT | 版本 |
|---|---|---|---|
| 有效應徵 | 通過 BR-011 冷卻後成立的一次投遞 | BR-011 | v1 |
| 冷卻中 | 距上次應徵未滿 72h 的狀態 | BR-011 | v1 |
12.2 版本凍結表
| 項目 | v1/v2 | Blocking? | 原因 | 決策者 | 日期 |
|---|---|---|---|---|---|
| 撤回應徵 | v2 | 否 | MVP 不含 | Eric | 2026/06/23 |
| 狀態 badge + tab(全部/審閱中/面試邀約/婉拒) | v2 | 否 | 待 HR 流程 | Eric | 2026/07/03 |
| pm_44 封鎖公司互動 | v2 | 否 | pm_44 未上線 | Eric | 2026/07/03 |
| 完整多次應徵時間軸 | v2 | 否 | v1 僅最近應徵日 + apply_count | Eric | 2026/07/03 |
| 站內信整合 | out | 否 | 另案 | Eric | 2026/06/23 |
12.3 PRD 問題清單
| # | 章節/規則 | 問題 | 建議修正 | 類別 | 影響 | SoT |
|---|---|---|---|---|---|---|
| 1 | §5.2 status | cooldown 是否獨立狀態?reapply.cooldown_until 驅動「再次應徵」按鈕倒數 | 已定案 | — | PRD | |
| 2 | §5.2 status | 已定案 | — | PRD | ||
| 3 | §6.1 | job_applications 合併一列 | 已定案 | — | BE | |
| 4 | §6 SEC-3 | 已定案 | — | PRD | ||
| 5 | §6 SEC-1 | 職缺終態 UI | 已定案(Eric 2026/07/03):灰階 +「此職缺已關閉」 | 已定案 | — | PRD |
| 6 | §14.1 A | 已定案 | — | PRD | ||
| 7 | §14.1 C2 | apply_count);完整多次應徵時間軸 v2;詳情抽屜 v1 可選、不 blocking(對齊 ACT-3) | 已定案 | — | PRD | |
| 8 | §14.1 B1 | 已定案 | — | PRD | ||
| 9 | §14.1 D1 | 已定案 | — | PRD | ||
| 10 | §14.1 B3 | 已定案 | — | PRD |
12.4 後端參考備忘(W101-TalentSearchHub)
性質:供 RD /
backend-api-spec-generator參考,非 PRD 缺陷清單。PRD 已標「需要後端」之處,由後端依本節產 API。
SoT repo:W101-TalentSearchHub(C 端應徵);W101-AMS非本功能實作主體。
稽核日期:2026/07/03。
可沿用
| PRD 需求 | 後端現況 | 路徑 |
|---|---|---|
| BR-011 72h 冷卻 | REAPPLICATION_COOLDOWN_HOURS = 72 | applications.service.ts |
| 同職缺合併 | job_applications unique (user_id, job_id, status) | JobApplications.ts |
| 再次應徵 | POST /applications/apply | applications.controller.ts |
| BR-009 終態阻擋再投 | validateJob() 僅 PUBLISHED | applications.service.ts |
RD 實作 backlog(不計入 PRD P0)
| 項目 | 說明 |
|---|---|
| 求職者清單 API | 依 HINT-GET-1 + 下方欄位映射新建 |
清單含 reapply | 含 cooldown_until 供 UI 倒數 |
| 錯誤契約 / enum 映射 | 由 OpenAPI spec 決定,PRD 不規定 |
pm_44 is_blocked_by_me | v2 + pm_44 上線後 |
Legacy applyRecord 遷移 | 工程計畫,不寫入 PRD |
欄位映射草案(清單 API)
| PRD 欄位 | 後端來源 |
|---|---|
enc_id | job_applications.id 加密 |
applied_at | last_applied_at |
resume_enc_id | resume_id 加密 |
apply_count | reapplication_count + 1 |
reapply.can_reapply | checkCanReapply(...).allowed ∧ job 可投 |
reapply.cooldown_until | checkCanReapply(...).nextAvailableAt |
job.lifecycle_code | JobStatus → §5.2 語意 |
13. 下一步(Next Steps)
- 送 RD:
backend-api-spec-generator依本 PRD + §12.4 + §14 裁決產清單 API spec(無需再等 PM)。 - 前端 v1:清單 + 灰階終態 + 再投/冷卻(不實作 tab/badge)。
- v2 backlog:狀態 tab、HR 狀態機 PRD、pm_44、完整時間軸、狀態變更通知。
- 補 i18n key(冷卻提示、「此職缺已關閉」)。
- Coda:回寫
應徵紀錄列 PRD 欄=pm_42。
14. 待補與決策事項(RD 規格前審查,2026/07/06)
RD 在產出後端 API 規格前,對本 PRD 做了一輪完整性審查。§14.1 PM 裁決已於 2026/07/06 回覆;§14.2 為 RD 自行決定僅知會。Storybook 仍可展示完整願景(§9),production v1 以 §3.2 為準。
14.1 PM 裁決(已定案,Eric 2026/07/06)
以下回覆 RD 審查時提出的五項;多數與 §3.2、§12.2、§12.3 既有裁決一致,此處正式收斂以免重複提問。
A. v1 應徵狀態 / tab(原:狀態流轉未定義)
- 裁決:v1 不實作應徵狀態機、清單 API 不暴露
status_code/status_name、不做 tab / badge。 - 頁面語意:全部應徵紀錄(flat list);求職者看到的就是「我投過哪些職缺」。
- v2:
reviewing/invited/rejected連同狀態流轉定義 → 另開 HR 流程 PRD,不在本案 v1 scope。 - 依據:§3.2 L49-51、§12.2 L320、§12.3 #2。
- Storybook 提醒:
mixed情境的狀態卡為靜態願景 mock,≠ production v1 行為。
C2. SEC-3 單筆詳情 / 多次應徵時間軸
- 裁決:完整多次應徵時間軸 → v2;v1 清單列已含最近應徵日 +
apply_count(§6.2 SEC-3、§3.2 L53)。 - 詳情抽屜:v1 可選、不 blocking(ACT-3:清單欄位已足夠);不強制補 mockup。
- RD 審查備註:§14.1 原引用「§6.1 SEC-3 描述時間軸」與現行正文不符——§6.2 已明確分列 v1 / v2 內容,時間軸從未列入 v1。
B1. 狀態變更後要不要通知求職者?
- 裁決:v1 無狀態變化 → 不做狀態變更通知(信件 / 推播皆不含)。
- v2:待 HR 流程與狀態上線後,若需通知 → 另案(不擴本案 scope)。
- 依據:§3.2 L54(站內信已排除);狀態單純是紀錄,沒有狀態變化就不需要通知。
D1. 要不要有「已投遞」篩選 tab?
- 裁決:不需要。
- v1:無 tab,不適用。
- v2:若未來做 tab,不需獨立「已投遞」tab——頁面即應徵紀錄,有紀錄即代表已投遞,「全部」已涵蓋。
B3. pm_44(封鎖公司)與本案的上線先後?
- 裁決:pm_42 先上線;pm_44 封鎖互動 v2 接上。
- 技術:RD graceful degrade 維持 §14.2 既定(
is_blocked_by_me當 false、不顯示封鎖 boundary)。 - 依據:§3.2 L52、§12.2 L321。
14.2 RD 自行決定(知會,不需 PM 回覆)
以下屬顯示規則 / 技術契約 / 文件對齊,RD 於 spec 階段自行定案並記錄於此,PM 無需處理:
- B2 合併列顯示哪一次狀態(v2):v1 清單不含 status,本項不適用;待 v2 狀態上線時 RD 定:合併列顯示最新一次應徵的狀態,與時間一致。
- D2「再次應徵」端點語意(資格檢查 vs 完整投遞):§8.2 與 §10 流程圖描述不一。RD 定:於 spec 階段對齊既有投遞流程實作決定,不另擴新語意。
- C1 Storybook 情境與命名不一致:§9 / §11 列 5 情境(含獨立 company-blocked),實際只有 4 個(
mixed/has-cooldown/job-closed/empty)且命名為mixed(非mixed-status)。RD 定:以實際 Storybook 為準,回頭對齊 §9 / §11 文字與命名。 - D1-技術面 status 參數合法值:RD 定:v1 清單 API 不含 status 參數;v2 若實作 tab 篩選,status query 收 §5.2 四個 enum 值,不需獨立「已投遞」tab(§14.1 D1 已定)。
- B3-技術面 pm_44 fallback:RD 定:pm_44 未上線時 graceful degrade(
is_blocked_by_me當 false、不顯示封鎖 boundary)。
一句話總結:v1 可開工——清單 API + 冷卻(BR-011)+ 終態(BR-009);狀態/tab/通知/封鎖/時間軸均不 blocking。§14.1 五項 PM 裁決已定案;§14.2 僅知會。
文件結束