# Module `notify` — Nhắc hạn & thông báo đa kênh

> 07/7/2026 · Giai đoạn: **MVP** · Bản HTML: [notify.html](notify.html) · Sơ đồ tổng thể: [../so-do-ws-module-feature-2026-07-06.md](../so-do-ws-module-feature-2026-07-06.md)
> Trạng thái code: **chưa có — scaffold mới**.

## 1. Vai trò
Hạ tầng **nhắc hạn + gửi thông báo** dùng chung: quét hạn giấy tờ (GV/xe/học viên), nhận cảnh báo từ module khác (dat, tuition) và gửi qua **Zalo OA / SMS / app / email**. Tách module riêng vì mọi module đều cần và kênh gửi (Zalo OA — việc #1, anh Hưng) cấu hình một chỗ.

## 2. Feature / BO / Action

| Feature | BO chính | Action |
|---|---|---|
| `rule` | Quy tắc nhắc hạn | view·configure |
| `message` | Thông báo đã gửi | view·**send**·export |
| `channel` | Kênh gửi (Zalo OA/SMS/app/email) | view·configure |

## 3. Dữ liệu chính

### Quy tắc nhắc hạn (`rule`)
| Trường | Kiểu | Ghi chú |
|---|---|---|
| Đối tượng | enum | chứng chỉ GV / giấy tờ xe / giấy khám SK học viên / hạn đóng học phí / thiếu km-giờ DAT |
| Nguồn dữ liệu hạn | ref | module.feature.trường hạn |
| Ngưỡng nhắc | list | vd 30/15/7 ngày trước hạn; lặp lại đến khi xử lý |
| Kênh + người nhận | config | vai trò nhận (KY_THUAT nhận hạn xe…) + cá nhân liên quan |
| Trạng thái | bool | |

### Thông báo đã gửi (`message`) — *log, append-only*
| Trường | Kiểu | Ghi chú |
|---|---|---|
| Nguồn | text | rule nào / module nào yêu cầu (dat.alert, tuition…) |
| Người nhận | ref | nhân viên / học viên (SĐT, Zalo id) |
| Kênh | enum | zalo_oa / sms / app_push / email |
| Nội dung | text | theo template |
| Trạng thái gửi | enum | chờ / đã gửi / lỗi (+ retry) |
| Thời gian | datetime | |

### Kênh (`channel`)
Cấu hình Zalo OA (app id, token), SMS brandname, template tin theo kênh.

## 4. Quy tắc nghiệp vụ
1. Job quét hạn **chạy hằng ngày**; mỗi (đối tượng × ngưỡng) chỉ gửi 1 lần — không spam.
2. Module khác **không tự gửi tin** — đẩy yêu cầu qua notify để log tập trung một chỗ.
3. Log gửi là **append-only** (bằng chứng đã thông báo — có giá trị khi tranh chấp học phí/hạn giấy tờ).
4. Kênh lỗi (Zalo hết hạn token…) → cảnh báo QTHT; tin lỗi retry theo cấu hình.
5. Học viên nhận qua Zalo OA/app; nhân viên nhận theo vai trò cấu hình trong rule.

## 5. Liên kết chéo module
| Module | Vai trò |
|---|---|
| teacher / vehicle / student | nguồn trường hạn (chứng chỉ, đăng kiểm, khám SK) |
| dat | cảnh báo thiếu km/giờ, lệch GV/xe |
| tuition | nhắc hạn đóng học phí đợt |
| portal (v1.5) | đẩy thông báo vào app học viên |

## 6. Câu hỏi mở
- Zalo OA đăng ký xong chưa (việc #1 — anh Hưng)? Gói tin ZNS hay tin OA thường?
- Có dùng SMS brandname không (chi phí)?

## 7. Ma trận phân quyền (→ manifest & seed role)

### 7.1 Khai báo `permissions[]` trong `manifest.json`

```json
[
  {
    "group": "main",
    "feature": "rule",
    "label": "Quy tắc nhắc hạn",
    "actions": [
      "view",
      "configure"
    ]
  },
  {
    "group": "main",
    "feature": "message",
    "label": "Thông báo đã gửi",
    "actions": [
      "view",
      "send",
      "export"
    ]
  },
  {
    "group": "main",
    "feature": "channel",
    "label": "Kênh gửi",
    "actions": [
      "view",
      "configure"
    ]
  }
]
```

> ⚠️ Action đặc thù (`send`) cần bổ sung vào validator manifest — xem [plan data-scope P1](../giai-doan-1-mvp/plan-data-scope-2026-07-06.md).

### 7.2 Ma trận vai trò × feature (seed `rt_role`)

| Feature | `QTHT` | `GIAM_DOC` | `QL_DAO_TAO` |
|---|:--:|:--:|:--:|
| `rule` | W | R | W |
| `message` | W | R | R |
| `channel` | W | — | — |

> **R** xem · **W** tạo/sửa · **A** duyệt · **—** không có quyền · ¹ dữ liệu **phân công** · ² **của chính mình** · ³ **theo trạng thái** hồ sơ. Vai trò không liệt kê = không có quyền. Permission key đầy đủ: `notify.main.<feature>.<action>`.
> Đặc thù: message +send (QTHT, QL_DAO_TAO)
