---
title: "FR-150 需求與決策紀錄"
---

# 任務卡到期提醒：PM 手動挑任務寄信

> 白話需求（Phase 0），2026-10-18 決策者口述、首腦整理，同日全部裁定。設計見 [design.md](design.md)。
> 承接 FR-140 的「任務卡到期提醒」（自動排程版），本篇只談**PM 手動挑任務寄提醒**。
> 2026-10-18 決策者已裁：**不做一鍵全發、也不做逐卡按鈕，做一個彈窗讓 PM 勾選未完成任務後一次寄出**。
> 同日再裁 Q2～Q4、Q7（見 §5）：其他 manager 不收、卡上留系統留言、沒期限也能勾、一卡一封。待裁項已清空。

## 1. 想解決什麼

現在到期提醒只有兩個時點會自動寄（剩 3 天、到期當天），而且一個時點只寄一次。
PM 遇到很急的卡（例如客戶明天就要、或指派人一直沒動），想**不等排程、不受兩個時點限制**，
直接在專案管理裡面挑幾張卡，提醒信一次寄出去。逐張開抽屜按會很煩，一鍵全發又會催到不急的人，
所以要的是**中間那種：一個彈窗，勾要催的卡，按一次寄**。

## 2. 現況（手動版要搭在這上面）

| 項目 | 現況 |
|------|------|
| 自動寄的時點 | 剩 3 天（`due_3d`）、到期當天（`due_today`），台北日曆天算 |
| 寄給誰 | 該卡指派人 ＋ 專案 manager |
| 擋重複 | `task_alert_notifications` 唯一索引 `(object_type, object_uid, stage, recipient_email)`，同卡同階段同信箱一次 |
| 寄失敗 | 不補寄，紀錄頁顯示失敗 |
| 哪些卡 | 狀態 TODO／PROCESSING、**有設期限**的卡 |
| 信件內容 | 固定模板：專案／任務／完成期限／連結，沒有自訂文字 |
| 看紀錄 | 專案總覽 →「通知紀錄」頁，每封一列，點列開回那張卡 |
| 程式位置 | `app/task_alert/service/job_due_reminder_service.py`、`infra/readmodel/tasks/job_due_reminder_query.py`、`core/scheduler.py` 的 `task_recurrence_tick` |

## 3. 想要的樣子（2026-10-18 決策者已裁定）

### 3.1 入口與彈窗（決策者已裁）

**入口**：專案總覽頁動作列多一顆「寄送任務提醒」，限專案 manager。放總覽頁是因為 PM 催人的時機是「看整個專案進度時」，不是在單張卡裡。

**彈窗內容**：

- 列出本專案**所有未完成任務**（狀態 TODO／PROCESSING），每列：勾選框／任務名／控制項／指派人／期限／期限標示（逾期紅、即將到期黃、未設定）。
- 預設排序：逾期最前、再來即將到期、再來其他；同級依期限近到遠，沒期限的排最後。急的卡自然浮在上面。
- 頂部兩個快速篩選：「只看逾期」「只看 3 天內到期」，另有任務名／指派人關鍵字搜尋。
- 全選只選「目前篩選後看得到的」，不會把隱藏的也勾進去。
- **沒有指派人的卡顯示但不能勾**（灰掉、hover 提示「尚未指派人員」），寄了也沒人收。
- 底部一個多行留言框（可空、上限 500 字）、一行「將寄給 N 張任務的 M 位指派人」、「寄出」鈕。
- 按「寄出」後同步寄、同步回結果；關窗前 toast 寫「已寄 M 封，失敗 K 封」。

**被否決的方案與原因**（留著免得下次又提）：

| 方案 | 為什麼不做 |
|------|-----------|
| 每張卡抽屜一顆「寄提醒」鈕 | PM 要催五張就要開五次抽屜，決策者原話「一個一個點會崩潰」 |
| 總覽頁一鍵催全部逾期卡 | 不急的人也被催，催信變噪音；也無法對特定卡講為什麼急 |

### 3.2 誰可以按

限**專案 manager**，沿用既有 `assert_project_manager`（改期限那支端點就是這樣守的）。
輪次已結束的專案整顆入口不顯示、端點也擋（跟改期限同一條件）。

### 3.3 哪些卡可以寄

- 彈窗只列狀態 TODO／PROCESSING 的卡；已完成／已取消不出現。
- **沒設期限的卡也列、也能勾**。急的卡不一定有期限，手動版的意義就是「PM 說急就急」。信裡沒期限就不印期限那列。
- 沒有任何指派人的卡**列出但不能勾**，後端收到也略過並在結果裡回「N 張略過：未指派」。
- 後端收到的 uid 逐張驗歸屬（必須屬於 URL 上那個專案），不屬於的整批 400 退回，不要部分寄。

### 3.4 寄給誰

- 每張卡寄**該卡全部指派人**。同一人被勾了三張卡，**收三封**（一封一張卡），不合併。合併成一封要另做模板，而且點連結要開哪張卡會說不清；先不做，§5 Q7 留著。
- 發信的 PM 本人**不寄**，其他 manager 也**不寄**（已裁）。manager 要知道有人催過，看卡上的系統留言（§3.7）。

### 3.5 信長什麼樣

- 主旨：`[系統名] 專案管理者提醒：<任務名>`
- 內文沿用現有模板結構（專案／任務／完成期限／連結），`when` 那句改成「專案管理者 <暱稱> 請您儘速處理」。
- **可選填一段 PM 留言**（彈窗底部的多行文字框，上限 500 字，可空白），**這一批勾的卡共用同一段留言**。信裡多一段「管理者留言：…」。這是手動版跟自動版最大的差別，急的時候 PM 通常要講「為什麼急」。

### 3.6 擋重複與防手滑

- 唯一索引鎖的是「同卡同階段同信箱」，手動寄每次都是新階段，`stage` 用 `manual_<這一批的 epoch 秒>`（同一批所有卡共用同一個 stage 值，之後查「這一批寄了哪些」才對得起來），所以**可以重複寄**，這是刻意的（急就是要能催第二次）。
- 防手滑：彈窗列上顯示「上次手動提醒：X 分鐘前／X 天前」（讀紀錄表最近一筆 `manual_*`）；勾到的卡裡有 **10 分鐘內**寄過的，按寄出時多一層確認「其中 N 張 10 分鐘內才寄過，確定再寄？」。後端不擋。
- 不做每日上限。真的濫用是管理問題不是系統問題。

### 3.7 紀錄要留什麼

- 一樣寫進 `task_alert_notifications`，`object_type=job_due`、`stage=manual_<epoch>`，每位收件人一列。
- 通知紀錄頁那一列要看得出「這是 PM 手動寄的、誰寄的」：「新出現」欄改掛「管理者提醒 · 王小明」。
- **表上沒有「誰觸發」的欄位**，要加一欄 `triggered_by`（login_name，可空；自動寄的留空）。這是一支 migration，主線 active，只套 DEV，出貨基線待重產。
- 每張被寄的卡**留一則系統留言**（已裁）：「管理者 <暱稱> 已寄出提醒」，PM 有填留言就接在後面。指派人開卡就看到被催過，其他 manager 也看得到有人催過。留言作者用 `system`，跟內稽退回那則同一套。

### 3.8 寄出的回饋

- 同步寄、同步回結果。PM 按下去要立刻知道「寄出去了」還是「郵件伺服器沒開」，不要丟背景。一批最多就幾十封，同步寄撐得住；若實測超過 10 秒再考慮改背景。
- 回 `{jobs: N, sent: M, failed: K, skipped_unassigned: S}`，前端 toast：「已寄 M 封」／「寄送失敗：郵件伺服器未啟用，請管理員到郵件設定啟用」。
- 一封寄失敗不影響其他封（跟自動版一樣）。

## 4. 粗估要動哪裡

| 層 | 動作 |
|----|------|
| migration | `task_alert_notifications` 加 `triggered_by varchar(64)`；DEV only，主線 active，出貨基線待重產 |
| BE 查詢 | 新 readmodel：專案未完成任務清單（任務名／控制項／指派人／期限／最近一次 `manual_*` 時間），給彈窗用。既有任務清單 API 形狀是規劃頁用的，欄位多且沒有「最近手動提醒」，不硬塞 |
| BE 端點 | `GET /project/<project_uid>/job-due-reminder/candidates`（彈窗清單）、`POST /project/<project_uid>/job-due-reminder`（body：`job_uids[]`、`message` 可空）；兩支都 `assert_project_manager` |
| BE service | 新檔 `app/task_alert/service/manual_job_due_reminder_service.py`，不塞進自動版那支；寄信與寫紀錄的 helper 從自動版抽出共用；收件人查詢重用 `JobDueReminderQuery.recipients` 加「排除 manager」與「排除某 login_name」參數 |
| BE i18n | 主旨、`when` 句、管理者留言段三個 key，zh／en 兩份 |
| FE | 專案總覽動作列入口（限 manager）＋選任務彈窗（清單／篩選／搜尋／全選／留言框／寄出）；通知紀錄頁手動列的標籤 |
| 通知紀錄 API | 回傳多 `triggered_by`＋`triggered_by_name`（暱稱 enrich 照審計欄位規範） |
| spec | `project-overview.md` §1.1 動作列、`project-alert-notifications.md` §11、`my-tasks.md` §11.5 各補一段 |

## 5. 決策紀錄（2026-10-18 全部裁定）

| # | 問題 | 裁示 | 理由 |
|---|------|------|------|
| Q1 | 入口先做哪個 | 總覽頁入口開彈窗、勾選未完成任務一次寄 | 逐卡按太煩、一鍵全發太粗 |
| Q2 | 其他 manager 要不要一起收 | **不收**，只寄指派人 | 發信的 PM 知道自己寄了；多 manager 互相收催信是噪音；要知道有人催過看卡上系統留言 |
| Q3 | 卡上要不要留系統留言 | **要** | 指派人開卡就看到「被催了」；也補上 Q2 不寄 manager 的資訊缺口 |
| Q4 | 沒期限的卡能不能勾 | **能** | 手動版的本質是「PM 判斷急」，不該被期限綁住 |
| Q5 | 可不可以重複寄 | 可以，10 分鐘內寄過的要二次確認 | 催兩次是正常需求；二次確認擋手滑就夠 |
| Q6 | 要不要同時進頂欄鈴鐺 | 這次不做 | 鈴鐺目前只接流程進站通知，接進來要改查詢形狀，另案 |
| Q7 | 同一人被勾多張卡，一封還是多封 | **多封，一卡一封** | 合併要另做模板、連結開哪張卡說不清；跟自動版、通知紀錄「一列一封」形狀一致 |
| Q8 | 彈窗要不要也列已完成的卡 | 不列 | 催已完成的卡沒意義 |

## 6. 不在範圍

- 逾期後的自動再提醒（每天催一次之類）。那是自動版的規則改動，另談。
- 補寄自動版寄失敗的那幾封。
- 全專案一鍵催（不勾選直接全發）。
- 逐卡抽屜按鈕。
- 合併同一收件人多張卡成一封信（Q7 若改裁再做）。
- 站內鈴鐺。
- Discord／Telegram 管道。到期提醒目前只有 Email，手動版跟著只做 Email。
