# Dev Request Tasks — CRUD & Zalo Notify

Tài liệu mô tả chức năng **Yêu cầu Fix bug / Làm mới** trên LMS: luồng CRUD, trạng thái, cấu hình và cách chạy bot Zalo để gửi tin nhắn nhóm.

- **URL:** `/dev-request-tasks` (vd: https://lmsnew.hocmai.net/dev-request-tasks)
- **Middleware:** `auth` (chỉ cần đăng nhập, không check permission riêng)
- **DB:** bảng tenant (`UsesTenantConnection`)

---

## 1. Mục đích

Quản lý ticket nội bộ:

| Loại (`type`) | Ý nghĩa |
|---|---|
| `fix_bug` | Fix bug |
| `new_request` | Yêu cầu mới / làm mới |

Mỗi task có: tiêu đề, hiện trạng, yêu cầu, ghi chú thêm, ảnh đính kèm, người được giao (`assigned_to`), trạng thái, ghi chú trạng thái.

Khi **tạo / đổi trạng thái / assign**, hệ thống gửi tin Zalo vào nhóm (có @tag người liên quan) **sau khi** API đã trả response (không chặn UI).

---

## 2. Cấu trúc code chính

| Thành phần | Path |
|---|---|
| Controller | `app/Http/Controllers/DevRequestTaskController.php` |
| Model | `app/Models/DevRequestTask.php`, `DevRequestTaskImage.php` |
| View (CRUD UI) | `resources/views/dev-request-tasks/index.blade.php` |
| Routes | `routes/web.php` → prefix `dev-request-tasks` |
| Config Zalo | `config/zalo.php` |
| Service gửi Zalo | `app/Services/ZaloNotifyService.php` |
| Job gửi Zalo sau response | `app/Jobs/SendDevRequestTaskZaloNotifyJob.php` |
| Bot Node (Puppeteer) | `scripts/zalo-bot/` |
| Migrations | `database/migrations/2026_07_23_100000_*`, `100100_*`, `2026_07_24_060000_*` |

### Routes

| Method | URI | Name | Mô tả |
|---|---|---|---|
| GET | `/dev-request-tasks` | `dev-request-tasks.index` | Trang UI |
| GET | `/dev-request-tasks/list` | `dev-request-tasks.getList` | Danh sách + filter (JSON) |
| GET | `/dev-request-tasks/{id}` | `dev-request-tasks.show` | Chi tiết |
| POST | `/dev-request-tasks` | `dev-request-tasks.store` | Tạo |
| POST | `/dev-request-tasks/{id}/update` | `dev-request-tasks.update` | Sửa |
| POST | `/dev-request-tasks/{id}/status` | `dev-request-tasks.updateStatus` | Đổi trạng thái |
| POST | `/dev-request-tasks/{id}/assign` | `dev-request-tasks.assign` | Assign |
| POST | `/dev-request-tasks/{id}/delete` | `dev-request-tasks.destroy` | Xóa |

---

## 3. Luồng CRUD

```mermaid
flowchart TD
  A[User mở /dev-request-tasks] --> B[GET list + filter]
  B --> C{Hành động}
  C -->|Tạo| D[POST store + upload ảnh]
  C -->|Sửa| E[POST update]
  C -->|Đổi status| F[POST status]
  C -->|Assign| G[POST assign]
  C -->|Xóa| H[POST delete]
  D --> I[Lưu DB]
  E --> I
  F --> I
  G --> I
  I --> J[Trả JSON thành công ngay]
  J --> K[UI refresh bảng]
  I --> L[dispatch Zalo job afterResponse]
  L --> M[ZaloNotifyService HTTP → bot :3910]
  M --> N[Bot Puppeteer gửi tin nhóm Zalo]
  H --> O{Email = trantunghn196@gmail.com?}
  O -->|Không| P[403]
  O -->|Có| Q[Soft delete + xóa file ảnh]
```

### 3.1. Create

1. Form: loại, tiêu đề, **Giao cho** (bắt buộc: kien/tung/tu), hiện trạng, yêu cầu, ghi chú thêm, nhiều ảnh.
2. `store` lưu task `status = new`, lưu ảnh disk `public` (`storage/app/public/dev_request_tasks/{id}/`).
3. Lưu `created_by_zalo_name` từ map email → tên Zalo.
4. Response OK → UI đóng modal, reload list.
5. `afterResponse`: job gửi Zalo “task mới”.

### 3.2. Read / List + Filter

Filter trên index:

- Từ khóa (title / content / current_state / requirement)
- Assign (tất cả / chưa assign / kien / tung / tu)
- **Trạng thái: chọn nhiều** (checkbox): `new`, `done`, `ok`, `error`, `not_error`, `closed`
- Không chọn status = xem tất cả

API `getList` nhận `status[]` (hoặc chuỗi CSV) → `whereIn('status', ...)`.

### 3.3. Update

Sửa nội dung + ảnh (có thể xóa ảnh cũ, thêm ảnh mới). Không đổi status trong form update (status đổi riêng).

### 3.4. Update status

- UI: menu trạng thái trên cột bảng (fixed dropdown) hoặc nút trong modal Chi tiết.
- Backend validate transition qua `DevRequestTask::canTransitionTo()`.
- `error` / `not_error` **bắt buộc** `status_note`.
- Lưu `status_changed_by`, `status_changed_by_zalo_name`.
- **Trả response ngay** → UI cập nhật; Zalo gửi sau (`afterResponse`).

### 3.5. Assign

Đổi `assigned_to` trên bảng; nếu có assignee thì gửi Zalo (cùng kiểu notify create).

### 3.6. Delete

- Nút **Xóa** chỉ hiện khi email login = `trantunghn196@gmail.com`.
- API `destroy` cũng check email này; email khác → `403`.
- Soft delete task + xóa file ảnh trên disk.

---

## 4. Trạng thái & chuyển trạng thái

| Key | Label |
|---|---|
| `new` | Tạo mới |
| `done` | Done |
| `ok` | OK |
| `error` | Lỗi |
| `not_error` | Không phải lỗi |
| `closed` | Đóng task |

### Transition cho phép

```
new        → done
done       → ok | error | not_error | closed
error      → done | closed
ok         → done | error | not_error | closed
not_error  → done | error | ok | closed
closed     → (không đổi được — khóa)
```

Chỉ khi **Đóng task** (`closed`) mới không đổi trạng thái nữa.

---

## 5. Luồng gửi Zalo (chi tiết)

```mermaid
sequenceDiagram
  participant UI as Browser UI
  participant LMS as Laravel LMS
  participant Job as afterResponse Job
  participant Svc as ZaloNotifyService
  participant Bot as zalo-bot :3910
  participant Zalo as chat.zalo.me

  UI->>LMS: POST status / store / assign
  LMS->>LMS: Update DB
  LMS-->>UI: JSON success (nhanh)
  UI->>UI: Refresh list
  LMS->>Job: dispatch(...)->afterResponse()
  Note over LMS,Job: Sau khi response đã gửi client
  Job->>Svc: notifyTaskCreated / notifyStatusChanged
  Svc->>Bot: POST /send + X-Api-Key
  Bot->>Zalo: Puppeteer mở nhóm, @tag, gửi tin
  Bot-->>Svc: { ok: true/false }
```

### Ai được @tag?

- Người được **assign** (`ZALO_NAME_KIEN/TUNG/TU`)
- Người **tạo** task (tên Zalo từ `ZALO_USER_MAP` theo email)

### Nội dung tin (multiline)

Ví dụ đổi trạng thái:

```
tt: Task #12 đổi trạng thái Done → OK
Người đổi: Tùng Trần | Ghi chú trạng thái: ...
Loại: Fix bug
Tiêu đề: ...
Assign: Tú
Hiện trạng: ...
Yêu cầu: ...
Ghi chú thêm: ...
Link: https://lmsnew.hocmai.net/dev-request-tasks
```

### Biết gửi Zalo xong chưa?

Xem log Laravel:

- `SendDevRequestTaskZaloNotifyJob finished` với `ok: true/false`
- Hoặc warning nếu fail

UI **không** chờ / không hiện trạng thái gửi Zalo (cố ý để UX nhanh).

---

## 6. Config Laravel (`.env` + `config/zalo.php`)

Thêm / chỉnh trong `.env`:

```env
ZALO_NOTIFY_ENABLED=true
ZALO_BOT_API_URL=http://127.0.0.1:3910
ZALO_BOT_API_KEY=lms-zalo-secret
ZALO_GROUP_NAME=placementtest_lcms
ZALO_TASK_URL=https://lmsnew.hocmai.net/dev-request-tasks

# Tên Zalo đúng như trong nhóm (phải khớp để @mention)
ZALO_NAME_KIEN="Kien dtt"
ZALO_NAME_TUNG="Tran Thanh Tung"
ZALO_NAME_TU="Nguyễn Anh Tú"

# email login LMS = tên Zalo (cách nhau bởi | )
ZALO_USER_MAP="kienpn@ctv.hocmai.vn=Kien dtt|trantunghn196@gmail.com=Tran Thanh Tung|tu@example.com=Nguyễn Anh Tú"

# Optional: map thêm email → assignee (dự phòng)
ZALO_EMAILS_KIEN=
ZALO_EMAILS_TUNG=
ZALO_EMAILS_TU=
```

Sau khi sửa `.env`:

```bash
php artisan config:clear
# hoặc
php artisan config:cache
```

| Key | Ý nghĩa |
|---|---|
| `ZALO_NOTIFY_ENABLED` | Bật/tắt gửi Zalo từ LMS |
| `ZALO_BOT_API_URL` | Base URL bot local |
| `ZALO_BOT_API_KEY` | Header `X-Api-Key` (phải khớp bot) |
| `ZALO_GROUP_NAME` | Tên nhóm Zalo cần gửi |
| `ZALO_TASK_URL` | Link đưa vào tin nhắn |
| `ZALO_NAME_*` | Tên hiển thị trên Zalo của assignee |
| `ZALO_USER_MAP` | Map email → tên Zalo để @tag người tạo |

**Lưu ý:** Tên Zalo phải **trùng** tên trong danh sách mention của nhóm (kể cả khoảng trắng / dấu). Sai tên → không tag được (hoặc chỉ gửi text thường).

---

## 7. Chạy Zalo bot (`scripts/zalo-bot`)

Bot là Express + Puppeteer: login Zalo Web (QR lần đầu), giữ session trong `zalo_user_data/`, expose API:

| Endpoint | Mô tả |
|---|---|
| `GET /health` | Bot sống / đã login chưa |
| `POST /send` | Body `{ message, tags[] }`, header `X-Api-Key` |

### 7.1. Cài đặt lần đầu

```bash
cd /var/www/html/lms_hocmai/scripts/zalo-bot
npm install
npm run install-chrome
```

### 7.2. Login QR (lần đầu / hết session)

Chạy có UI để quét QR:

```bash
npm run start:ui
# hoặc ZALO_HEADLESS=false npm start
```

Mở Chrome do Puppeteer tạo → quét QR Zalo. Session lưu `zalo_user_data/`.

### 7.3. Chạy nền bằng pm2 (khuyến nghị production)

```bash
cd /var/www/html/lms_hocmai/scripts/zalo-bot
npm run pm2:start
# xem log
npm run pm2:logs
# restart / stop
npm run pm2:restart
npm run pm2:stop
```

Config pm2: `scripts/zalo-bot/ecosystem.config.cjs` (port `3910`, API key, group name, headless).

### 7.4. Kiểm tra bot

```bash
curl -s http://127.0.0.1:3910/health

curl -s -X POST http://127.0.0.1:3910/send \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: lms-zalo-secret" \
  -d '{"message":"test từ LMS","tags":["Tran Thanh Tung"]}'
```

---

## 8. Migration DB

Chạy trên **master + các tenant** (theo quy trình migrate tenant của project):

```bash
php artisan migrate --path=database/migrations/2026_07_23_100000_create_dev_request_tasks_tables.php
php artisan migrate --path=database/migrations/2026_07_23_100100_add_current_state_requirement_assigned_to_dev_request_tasks.php
php artisan migrate --path=database/migrations/2026_07_24_060000_add_zalo_notify_fields_to_dev_request_tasks.php
```

(Điều chỉnh lệnh theo helper/tenant migrate sẵn có của LMS nếu cần.)

Bảng chính:

- `dev_request_tasks` — task + status + assign + zalo name fields
- `dev_request_task_images` — ảnh đính kèm

Ảnh public: `php artisan storage:link` nếu chưa link.

---

## 9. Checklist để gửi được Zalo

1. [ ] Migration đã chạy trên DB tenant đang dùng  
2. [ ] `.env` đủ `ZALO_*`, `config:clear`  
3. [ ] `ZALO_GROUP_NAME` đúng tên nhóm  
4. [ ] `ZALO_NAME_*` và `ZALO_USER_MAP` đúng **tên Zalo trong nhóm**  
5. [ ] `ZALO_BOT_API_KEY` Laravel = key bot  
6. [ ] Đã `npm install` + cài Chrome Puppeteer  
7. [ ] Đã login QR, session còn trong `zalo_user_data/`  
8. [ ] pm2 `zalo-bot` đang chạy, `GET /health` OK  
9. [ ] Tạo/đổi status task → UI nhanh; vài giây sau có tin trong nhóm  
10. [ ] Nếu không có tin: xem `storage/logs/laravel.log` (`SendDevRequestTaskZaloNotifyJob` / `ZaloNotifyService`) và `pm2 logs zalo-bot`

---

## 10. Troubleshooting nhanh

| Hiện tượng | Nguyên nhân thường gặp |
|---|---|
| UI chậm khi đổi status | Bot đang gửi sync — đã chuyển `afterResponse`; clear cache view/config |
| Không có tin Zalo | Bot tắt / chưa login / sai API key / `ZALO_NOTIFY_ENABLED=false` |
| Có tin nhưng không @tag | Sai `ZALO_NAME_*` hoặc `ZALO_USER_MAP` (không khớp tên Zalo) |
| Tag sai người | Map email nhầm trong `ZALO_USER_MAP` |
| `/health` fail | pm2 chưa start hoặc port khác 3910 |
| Session hết | Chạy lại `npm run start:ui`, quét QR |

---

## 11. Phân quyền đặc biệt

| Hành động | Điều kiện |
|---|---|
| Xem / tạo / sửa / status / assign | User đã `auth` |
| Nút & API **Xóa** | Email login = `trantunghn196@gmail.com` |
