# Addon Request Notification Logic — Full Reference

This document describes **when** notifications are sent, **who** receives them, and **what** triggers each type. All notifications are stored in the `notifications` + `notification_user` tables and can be read via the Notifications API.

---

## 1. Who Are “Recipients”?

For addon requests, recipients are always drawn from **stakeholders** of the request:

| Role | Source |
|------|--------|
| **Requester** | `addon_request.requested_by_user_id` (sales who created the request) |
| **Assignee** | `addon_request.assigned_to_user_id` (department user assigned to handle it) |

- **“Stakeholders”** = requester + assignee (deduplicated).
- The user who **triggered** the action is often **excluded** (e.g. who updated fields is not notified of their own update).

---

## 2. All Notification Types and When They Fire

### 2.1 `addon_request.created`

| When | Trigger |
|------|--------|
| A new addon request is created | `AddonRequestService::createRequest()` after the request is saved |

**Recipients:** Requester is **excluded**. Everyone else in stakeholders (typically only assignee if already set; often no one at creation).

**Message (example):** `"新的加購需求已建立: {title}"`

**Data:** `addon_request_id`, `request_number`, `title`, `message`, `category`, `status`

---

### 2.2 `addon_request.status_changed`

| When | Trigger |
|------|--------|
| Addon request status is changed (e.g. pending → completed, in_progress → confirmed) | `AddonRequestService::updateStatus()` **except** when transition is **pending → in_progress** |

**Skip rule:** **No notification** when transition is **pending → in_progress** (assignment flow; no extra notification).

**Recipients:** All stakeholders (requester + assignee), no exclusion.

**Message:** Depends on new status (e.g. `"加購需求已完成"`, `"加購需求已確認"`).

**Data:** `addon_request_id`, `request_number`, `title`, `message`, `old_status`, `new_status`

---

### 2.3 `addon_request.updated`

| When | Trigger |
|------|--------|
| Addon request **fields** are updated (e.g. estimated_cost, title, description) | `AddonRequestController::update()` when there are dirty fields after save |

**Recipients:** All stakeholders **except** the user who made the update (`excludeUserId` = current user).

**Message (example):** `"加購需求「{title}」已更新: 預估費用、標題"` (field labels from `changed_fields`).

**Data:** `addon_request_id`, `request_number`, `title`, `message`, `changed_fields` (array of field names)

---

### 2.4 `addon_request.approval_required`

| When | Trigger |
|------|--------|
| Request needs approval and approval records are created | `AddonRequestService::createApprovalRequests()` — one notification **per approver** at the next level |

**Recipients:** **Only the approver** (each approver gets one notification).

**Message:** `"需要您的審核: {title}"`

**Data:** `addon_request_id`, `request_number`, `title`, `message`

---

### 2.5 `addon_request.completed`

| When | Trigger |
|------|--------|
| Addon request status becomes **completed** via workflow | `AddonRequestService::processWorkflow()` when status is already COMPLETED (e.g. after `calculateFinalCost`) |

**Recipients:** All stakeholders (requester + assignee).

**Message:** `"加購需求已完成: {title} (總費用: X.XX TWD)"` (if `final_cost` exists).

**Data:** `addon_request_id`, `request_number`, `title`, `message`, `final_cost`, `cost_currency`

---

### 2.6 `addon_request.overdue`

| When | Trigger |
|------|--------|
| Request is overdue (past requested completion date) | **Not called automatically in current code.** Implement in a scheduled job that finds overdue requests and calls `notifyOverdueRequest()`. |

**Recipients:** **Only assignee** (`assigned_to_user_id`). No notification if no assignee.

**Message:** `"加購需求已逾期: {title}"`

**Data:** `addon_request_id`, `request_number`, `title`, `message`

---

### 2.7 `addon_request.communication_added`

| When | Trigger |
|------|--------|
| A new communication (message/quote/response) is added to the addon request | `AddonRequestCommunicationService` after a communication is created (e.g. sales inquiry, department quote) |

**Recipients:** Depends on **sender_type** of the communication:

| Sender | Notified |
|--------|----------|
| **Sales** | Assignee only (department sees sales message) |
| **Department** | Requester only (sales sees department reply) |
| **Customer** or **System** | Requester + assignee (both see it) |

The **sender** is always excluded (no self-notify).

**Message (example):** `"業務在加購需求「{title}」中新增了報價，報價: 1,000 TWD，需要客戶在 2026/03/15 12:00 前回覆"`

**Data:** `addon_request_id`, `request_number`, `title`, `message`, `communication_id`, `sender_type`, `message_type`

---

### 2.8 `addon_request.customer_response_overdue`

| When | Trigger |
|------|--------|
| Customer response deadline has passed | **Not called automatically in current code.** Implement in a scheduled job that finds communications with `requires_customer_response` and past `customer_response_deadline`, then calls `notifyOverdueCustomerResponse()`. |

**Recipients:** **Requester only** (sales who needs to chase customer).

**Message:** `"客戶回覆已逾期: {title}"`

**Data:** `addon_request_id`, `request_number`, `title`, `message`, `communication_id`

---

### 2.9 `addon_request.daily_digest`

| When | Trigger |
|------|--------|
| Daily summary of pending/in_progress requests per category | **Not called automatically in current code.** Implement in a scheduled job (e.g. daily) that calls `AddonRequestNotificationService::sendDailyDigest()`. |

**Recipients:** All users with the **category’s responsible role** (e.g. ticketing, line_control, operations). One notification per category per user (message includes count of pending requests in that category).

**Message (example):** `"您有 5 個待處理的票務加購需求"`

**Data:** `message`, `category`, `count`

---

## 3. Summary Table: What Notifies Today

| Notification type | Triggered by (in code) | Recipients |
|-------------------|------------------------|------------|
| `addon_request.created` | Create addon request | Stakeholders except requester |
| `addon_request.status_changed` | Status change (except **pending → in_progress**) | Requester + assignee |
| `addon_request.updated` | Update addon request fields (PUT) | Stakeholders except editor |
| `addon_request.approval_required` | Approval workflow creates approvers | Each approver |
| `addon_request.completed` | Workflow: status = completed | Requester + assignee |
| `addon_request.overdue` | **(Scheduler)** | Assignee only |
| `addon_request.communication_added` | New communication on request | By sender: sales→assignee, dept→requester, customer/system→both (exclude sender) |
| `addon_request.customer_response_overdue` | **(Scheduler)** | Requester only |
| `addon_request.daily_digest` | **(Scheduler)** | Users with category role |

---

## 4. Scenario Quick Reference

| User action / event | Notification(s) |
|---------------------|----------------|
| Sales creates new addon request | `addon_request.created` (to assignee if any; requester excluded) |
| Status changed (any except pending→in_progress) | `addon_request.status_changed` (requester + assignee) |
| Status changed **pending → in_progress** | **None** |
| Someone updates title/cost/notes etc. | `addon_request.updated` (other stakeholders only) |
| Workflow creates approval tasks | `addon_request.approval_required` (each approver) |
| Request marked completed via workflow | `addon_request.completed` (requester + assignee) |
| Sales adds communication | `addon_request.communication_added` (assignee) |
| Department adds quote/message | `addon_request.communication_added` (requester) |
| Customer or system adds message | `addon_request.communication_added` (requester + assignee, sender excluded) |
| Overdue request (if scheduler runs) | `addon_request.overdue` (assignee) |
| Customer response overdue (if scheduler runs) | `addon_request.customer_response_overdue` (requester) |
| Daily digest (if scheduler runs) | `addon_request.daily_digest` (per category, to role users) |

---

*Document version: 2026-03-11. Matches `AddonRequestNotificationService` and call sites in `AddonRequestService`, `AddonRequestController`, `AddonRequestCommunicationService`.*
