# Notification Templates API

**Base URL:** `/api/v1/notification-templates`  
**Yêu cầu:** Bearer Token (Admin)

---

## Mục Lục

- [Data Types](#data-types)
- [Endpoints](#endpoints)
  - [GET /notification-templates](#get-notification-templates)
  - [POST /notification-templates](#post-notification-templates)
  - [GET /notification-templates/:id](#get-notification-templatesid)
  - [PUT /notification-templates/:id](#put-notification-templatesid)
  - [DELETE /notification-templates/:id](#delete-notification-templatesid)
- [Cách Dùng Variables](#cách-dùng-variables)
- [Lỗi Thường Gặp](#lỗi-thường-gặp)

---

## Data Types

### NotificationTemplate Object

```ts
{
  id: string            // UUID
  code: string          // Mã định danh, VD: "MTB-01", "WELCOME_NEW_RESIDENT"
  name: string          // Tên hiển thị
  type: TemplateType
  channel: TemplateChannel
  content: string       // Nội dung có biến dạng {variable}
  subject: string | null  // Tiêu đề (dùng cho EMAIL/SMS)
  description: string | null
  variables: string[] | null  // Danh sách tên biến, VD: ["tenant_name", "amount"]
  status: TemplateStatus
  created_by: string | null   // UUID của user tạo
  updated_by: string | null
  created_at: string    // ISO datetime
  updated_at: string
}
```

### Enums

**TemplateType** — Phân loại mẫu thông báo:

| Giá trị | Mô tả |
|---------|-------|
| `MARKETING` | Khuyến mãi, quảng cáo |
| `BILLING` | Hóa đơn, thanh toán |
| `SYSTEM_ALERT` | Cảnh báo hệ thống |
| `TRANSACTIONAL` | Giao dịch |
| `REMINDER` | Nhắc nhở |
| `ANNOUNCEMENT` | Thông báo chung |

**TemplateChannel** — Kênh gửi thông báo:

| Giá trị | Mô tả |
|---------|-------|
| `SMS` | Tin nhắn SMS |
| `EMAIL` | Email |
| `ZALO_ZNS` | Zalo Notification Service |
| `ZALO_OA` | Zalo Official Account |
| `APP_PUSH` | Push notification (mobile app) |
| `IN_APP` | Thông báo trong app |
| `WEB_PUSH` | Web push notification |

**TemplateStatus** — Trạng thái:

| Giá trị | Mô tả |
|---------|-------|
| `ACTIVE` | Đang hoạt động (mặc định) |
| `INACTIVE` | Tạm dừng |
| `DRAFT` | Bản nháp |

---

## Endpoints

---

### GET /notification-templates

Lấy danh sách tất cả mẫu thông báo.

**Request**

```http
GET /api/v1/notification-templates
Authorization: Bearer <token>
```

**Query Parameters**

| Tham số | Kiểu | Mô tả |
|---------|------|-------|
| `page` | number | Trang hiện tại (mặc định: 1) |
| `pageSize` | number | Số bản ghi mỗi trang (mặc định: 20) |
| `search` | string | Tìm kiếm theo tên hoặc code |
| `sort` | string | Sắp xếp, VD: `created_at:desc` |
| `filter` | string | Lọc theo field, VD: `status:ACTIVE`, `type:BILLING` |

**Response 200**

```json
{
  "success": true,
  "data": {
    "count": 8,
    "rows": [
      {
        "id": "uuid-here",
        "code": "MTB-01",
        "name": "Chào mừng cư dân mới",
        "type": "ANNOUNCEMENT",
        "channel": "APP_PUSH",
        "content": "Chào mừng {tenant_name} đến với {building}! Chúng tôi rất vui được chào đón bạn.",
        "subject": "Chào mừng đến với {building}",
        "description": "Thông báo chào mừng cư dân mới",
        "variables": ["tenant_name", "building"],
        "status": "ACTIVE",
        "created_by": "uuid-here",
        "updated_by": null,
        "created_at": "2026-06-02T10:00:00.000Z",
        "updated_at": "2026-06-02T10:00:00.000Z"
      }
    ],
    "page": 1,
    "pageSize": 20
  },
  "trace_id": "..."
}
```

---

### POST /notification-templates

Tạo mẫu thông báo mới.

> **Lưu ý:** Trường `code` là **tuỳ chọn**. Nếu không truyền, hệ thống tự sinh theo dạng `MTB-01`, `MTB-02`, ...

**Request**

```http
POST /api/v1/notification-templates
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Trường | Kiểu | Bắt buộc | Mô tả |
|--------|------|----------|-------|
| `name` | string | ✅ | Tên mẫu (tối đa 255 ký tự) |
| `type` | TemplateType | ✅ | Loại mẫu |
| `channel` | TemplateChannel | ✅ | Kênh gửi |
| `content` | string | ✅ | Nội dung mẫu (có thể chứa biến `{variable}`) |
| `code` | string | ❌ | Mã định danh (3–50 ký tự). Nếu bỏ trống → tự sinh `MTB-XX` |
| `subject` | string | ❌ | Tiêu đề (dùng cho EMAIL, tối đa 500 ký tự) |
| `description` | string | ❌ | Mô tả ngắn về mẫu |
| `variables` | string[] | ❌ | Danh sách tên biến dùng trong content |
| `status` | TemplateStatus | ❌ | Mặc định: `ACTIVE` |

**Ví dụ 1 — Để hệ thống tự sinh code**

```json
{
  "name": "Chào mừng cư dân mới",
  "type": "ANNOUNCEMENT",
  "channel": "APP_PUSH",
  "content": "Chào mừng {tenant_name} đến với {building}! Nếu cần hỗ trợ, vui lòng liên hệ ban quản lý.",
  "subject": "Chào mừng đến với {building}",
  "variables": ["tenant_name", "building"],
  "description": "Gửi khi cư dân mới checkin"
}
```

**Ví dụ 2 — Tự đặt code**

```json
{
  "code": "WELCOME_NEW_RESIDENT",
  "name": "Chào mừng cư dân mới",
  "type": "ANNOUNCEMENT",
  "channel": "APP_PUSH",
  "content": "Chào mừng {tenant_name} đến với {building}!",
  "variables": ["tenant_name", "building"]
}
```

**Ví dụ 3 — Template hóa đơn quá hạn**

```json
{
  "name": "Nhắc quá hạn",
  "type": "BILLING",
  "channel": "APP_PUSH",
  "content": "Hóa đơn tháng {month} của bạn đã quá hạn {days_overdue} ngày. Số tiền: {amount}.",
  "subject": "Hóa đơn tháng {month} đã quá hạn",
  "variables": ["month", "days_overdue", "amount"],
  "description": "Gửi khi hóa đơn quá hạn thanh toán"
}
```

**Response 200**

```json
{
  "success": true,
  "data": {
    "id": "3f8a7b12-...",
    "code": "MTB-01",
    "name": "Chào mừng cư dân mới",
    "type": "ANNOUNCEMENT",
    "channel": "APP_PUSH",
    "content": "Chào mừng {tenant_name} đến với {building}! Nếu cần hỗ trợ, vui lòng liên hệ ban quản lý.",
    "subject": "Chào mừng đến với {building}",
    "description": "Gửi khi cư dân mới checkin",
    "variables": ["tenant_name", "building"],
    "status": "ACTIVE",
    "created_by": "uuid-of-admin",
    "updated_by": null,
    "created_at": "2026-06-02T10:00:00.000Z",
    "updated_at": "2026-06-02T10:00:00.000Z"
  },
  "trace_id": "..."
}
```

**Response 409 — Code trùng (khi tự nhập code)**

```json
{
  "success": false,
  "data": null,
  "error": {
    "code": "CONFLICT",
    "message": "Notification template code already exists"
  }
}
```

---

### GET /notification-templates/:id

Lấy chi tiết 1 mẫu theo UUID.

**Request**

```http
GET /api/v1/notification-templates/3f8a7b12-...
Authorization: Bearer <token>
```

**Response 200**

```json
{
  "success": true,
  "data": {
    "id": "3f8a7b12-...",
    "code": "MTB-01",
    "name": "Chào mừng cư dân mới",
    "type": "ANNOUNCEMENT",
    "channel": "APP_PUSH",
    "content": "Chào mừng {tenant_name} đến với {building}!",
    "subject": "Chào mừng đến với {building}",
    "description": "Gửi khi cư dân mới checkin",
    "variables": ["tenant_name", "building"],
    "status": "ACTIVE",
    "created_by": "uuid-of-admin",
    "updated_by": null,
    "created_at": "2026-06-02T10:00:00.000Z",
    "updated_at": "2026-06-02T10:00:00.000Z"
  },
  "trace_id": "..."
}
```

**Response 404**

```json
{
  "success": false,
  "data": null,
  "error": {
    "code": "NOT_FOUND",
    "message": "NotificationTemplate not found"
  }
}
```

---

### PUT /notification-templates/:id

Cập nhật mẫu thông báo. Chỉ truyền các trường muốn thay đổi.

> **Lưu ý:** Không thể đổi `code` sau khi tạo.

**Request**

```http
PUT /api/v1/notification-templates/3f8a7b12-...
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body** _(tất cả đều tuỳ chọn)_

| Trường | Kiểu | Mô tả |
|--------|------|-------|
| `name` | string | Tên mới |
| `type` | TemplateType | Loại mới |
| `channel` | TemplateChannel | Kênh mới |
| `content` | string | Nội dung mới |
| `subject` | string \| null | Tiêu đề mới |
| `description` | string \| null | Mô tả mới |
| `variables` | string[] \| null | Danh sách biến mới |
| `status` | TemplateStatus | Trạng thái mới |

**Ví dụ — Ẩn template**

```json
{
  "status": "INACTIVE"
}
```

**Ví dụ — Cập nhật nội dung**

```json
{
  "content": "Kính chào {tenant_name}, chúng tôi xin thông báo hóa đơn tháng {month} đã quá hạn {days_overdue} ngày. Vui lòng thanh toán sớm.",
  "variables": ["tenant_name", "month", "days_overdue"]
}
```

**Response 200** — Trả về object đã cập nhật (cấu trúc giống GET by ID)

---

### DELETE /notification-templates/:id

Xóa mẫu thông báo.

**Request**

```http
DELETE /api/v1/notification-templates/3f8a7b12-...
Authorization: Bearer <token>
```

**Response 200**

```json
{
  "success": true,
  "data": {
    "message": "Notification template deleted successfully"
  },
  "trace_id": "..."
}
```

---

## Cách Dùng Variables

### Cú pháp biến

Biến trong `content` và `subject` dùng dấu ngoặc nhọn đơn: `{tên_biến}`

```
Chào {tenant_name}, hóa đơn tháng {month} là {amount}.
```

### Khai báo `variables`

Trường `variables` là mảng tên các biến xuất hiện trong `content`/`subject`. Frontend nên dùng mảng này để biết cần điền dữ liệu gì khi preview hoặc gửi thông báo.

```json
{
  "content": "Hóa đơn {month} của {tenant_name}: {amount}",
  "variables": ["month", "tenant_name", "amount"]
}
```

### Preview template trên FE

Frontend có thể tự render preview bằng cách thay biến:

```typescript
function renderPreview(content: string, data: Record<string, string>): string {
  let result = content;
  for (const [key, value] of Object.entries(data)) {
    result = result.replaceAll(`{${key}}`, value);
  }
  return result;
}

// Ví dụ
const content = "Chào {tenant_name}, hóa đơn tháng {month} là {amount}.";
const preview = renderPreview(content, {
  tenant_name: "Nguyễn Văn A",
  month: "06/2026",
  amount: "3,000,000 VNĐ"
});
// → "Chào Nguyễn Văn A, hóa đơn tháng 06/2026 là 3,000,000 VNĐ."
```

### Highlight biến chưa điền

```typescript
function highlightUnfilled(content: string): string {
  return content.replace(/\{([^}]+)\}/g, '<mark>{$1}</mark>');
}
```

---

## Lỗi Thường Gặp

| HTTP | Code | Nguyên nhân |
|------|------|-------------|
| `400` | `VALIDATION_ERROR` | Body không hợp lệ (thiếu field bắt buộc, sai kiểu dữ liệu) |
| `401` | `UNAUTHORIZED` | Thiếu hoặc sai Bearer token |
| `403` | `FORBIDDEN` | Token hợp lệ nhưng không phải Admin |
| `404` | `NOT_FOUND` | Không tìm thấy template với ID đã cho |
| `409` | `CONFLICT` | `code` tự nhập đã tồn tại trong hệ thống |

---

## Ví Dụ Templates Có Sẵn

Các templates đã được seed trong DB:

| Code | Tên | Type | Channel |
|------|-----|------|---------|
| `WELCOME_NEW_RESIDENT` | Chào mừng cư dân mới | `ANNOUNCEMENT` | `APP_PUSH` |
| `BILL_OVERDUE` | Nhắc quá hạn | `BILLING` | `APP_PUSH` |
| `BILL_MONTHLY` | Hóa đơn tháng | `BILLING` | `ZALO_ZNS` |
| `PAYMENT_SUCCESS` | Thanh toán thành công | `TRANSACTIONAL` | `APP_PUSH` |
| `PAYMENT_FAILED` | Thanh toán thất bại | `TRANSACTIONAL` | `APP_PUSH` |
| `CONTRACT_EXPIRY` | Hợp đồng sắp hết hạn | `REMINDER` | `SMS` |
| `MAINTENANCE_SCHEDULED` | Bảo trì định kỳ | `SYSTEM_ALERT` | `EMAIL` |
| `PROMOTION_FLASH_SALE` | Flash Sale | `MARKETING` | `ZALO_OA` |
