# WS1 — Spec màn hình tiếp nhận hồ sơ (UI đầu tiên)

> 08/7/2026 · Thuộc [Kế hoạch GĐ1](README.md) · Bản HTML + wireframe: [WS1-man-hinh-tiep-nhan.html](WS1-man-hinh-tiep-nhan.html)
> Đầu vào: [Quy trình hồ sơ](WS1-quy-trinh-ho-so-2026-07-06.md) · [Schema §2](schema-du-lieu-2026-07-06.md) · [Module student](../modules/student-2026-07-07.md) · [Plan data-scope §3b](plan-data-scope-2026-07-06.md)
> Phạm vi: 2 màn hình trong module `student` (feature `hoso`) — **Danh sách hồ sơ** và **Form tiếp nhận**. Dev có thể code ngay, không chờ DB Vĩnh An (khi có DB chỉ chỉnh tên trường/quy tắc mã).

---

## 1. Màn hình A — Danh sách hồ sơ (`/student/hoso`)

### Bố cục
```
[Bộ lọc: Trạng thái ▾ | Hạng ▾ | Khóa ▾ | Loại hồ sơ ▾ | Tìm (tên/CCCD/SĐT/mã ĐK)]   [+ Tiếp nhận hồ sơ]
[Tab đếm nhanh: Tất cả (128) · Tiếp nhận (12) · Chờ bổ sung (5) · Hợp lệ (9) · Đã thu Đ1 (20) · …]
┌──────────────────────────────────────────────────────────────────────────────┐
│ Mã ĐK | Họ tên | CCCD | Hạng | Khóa | Trạng thái (badge) | Thiếu giấy tờ | GV │
│  … mỗi dòng: click mở chi tiết · menu ⋮ theo quyền (sửa/duyệt/bàn giao/hủy)   │
└──────────────────────────────────────────────────────────────────────────────┘
[Phân trang 20/50/100]
```

### Hành vi
| # | Hành vi | Chi tiết |
|---|---|---|
| A1 | Tab đếm theo trạng thái | Đếm realtime theo bộ lọc đang áp; click tab = lọc nhanh |
| A2 | Badge trạng thái | Màu theo vòng đời: xám `tu_van` · xanh dương `tiep_nhan` · vàng `cho_bo_sung` · xanh lá `hop_le`→`dang_hoc` · đỏ `huy` |
| A3 | Cột "Thiếu giấy tờ" | Chỉ hiện với `cho_bo_sung`: chip đỏ từng loại thiếu (đọc checklist) |
| A4 | Data scope | TUYEN_SINH thấy dải tiếp nhận; KE_TOAN từ `hop_le` trở đi; GIAO_VIEN chỉ học viên phụ trách — **server lọc** (scopeWhere), UI ẩn tab không thuộc phạm vi |
| A5 | Nút `+ Tiếp nhận` | Chỉ hiện khi có `student.main.hoso.create` |
| A6 | Menu dòng ⋮ | Theo quyền + trạng thái hiện tại (xem bảng transition mục 3) |
| A7 | Export | `student.main.student.export` — xuất theo bộ lọc đang áp |

## 2. Màn hình B — Form tiếp nhận (`/student/hoso/new`, sửa: `/student/hoso/:id`)

### Bố cục 3 khối + 1 cột phải
```
┌─ 1. THÔNG TIN HỌC VIÊN ─────────────────────┐ ┌─ CỘT PHẢI ────────────┐
│ Họ tên* · Ngày sinh* · Giới tính*           │ │ Ảnh chân dung* (chụp/  │
│ CCCD* · Ngày cấp* · Nơi cấp*                │ │  upload, crop 3x4)     │
│ SĐT* · Email · Thường trú* · Cư trú         │ │────────────────────────│
├─ 2. THÔNG TIN KHÓA HỌC ─────────────────────┤ │ TRẠNG THÁI HỒ SƠ       │
│ Hạng GPLX* ▾ · Loại hồ sơ* (mới/nâng hạng)  │ │ [tiep_nhan] (badge)    │
│ Khóa dự kiến ▾ · Nguồn/CTV ▾                │ │ Nút hành động theo     │
│ Học phí thỏa thuận* (gợi ý từ bảng giá)     │ │ transition + quyền     │
├─ 3. CHECKLIST GIẤY TỜ (động theo hạng+loại)─┤ │────────────────────────│
│ ☑ Đơn ĐK (mẫu)   [scan] [✓ đã đối chiếu]    │ │ LỊCH SỬ (timeline)     │
│ ☑ CCCD           [scan] [✓]                 │ │ 14:02 Tạo — N.V.A      │
│ ☐ Khám SK (hạn __) [scan] [ ]  ← thiếu      │ │ 14:10 → cho_bo_sung    │
│ ☐ GPLX cũ (chỉ hiện khi nâng hạng)          │ │  (thiếu khám SK)       │
└─────────────────────────────────────────────┘ └────────────────────────┘
[Lưu nháp]                    [Lưu & tiếp nhận]  → tự tính trạng thái theo checklist
```

### Validate (client + server đều phải chặn)
| # | Trường | Quy tắc | Thông báo |
|---|---|---|---|
| V1 | CCCD | 12 số; **chống trùng trong khóa** (`ma_dk, ma_kh, ma_csdt`); trùng người khác khóa → cảnh báo mềm + gợi ý **nâng hạng** (liên kết crm) | "CCCD đã có hồ sơ ở khóa K12B — xem hồ sơ / tạo nâng hạng?" |
| V2 | Ngày sinh × Hạng | Tuổi tại **ngày dự kiến sát hạch** ≥ `min_age` của hạng (catalog) | "Chưa đủ N tuổi cho hạng B tại ngày sát hạch dự kiến" |
| V3 | SĐT | 10 số VN; trùng SĐT hồ sơ active → cảnh báo mềm | |
| V4 | Hạng × Loại | Nâng hạng: bắt buộc có GPLX cũ trong checklist; hạng nâng phải hợp lệ theo ma trận nâng hạng (catalog) | |
| V5 | Học phí | > 0; lệch >X% so bảng giá → yêu cầu ghi chú lý do | |
| V6 | Khám SK | Có `ngày hết hạn`; hết hạn trước ngày khai giảng dự kiến → tính là **thiếu** | |
| V7 | Ảnh | Bắt buộc trước khi rời `tiep_nhan`; nén client ≤ 500KB | |

### Hành vi checklist (lõi màn hình)
1. Đổi **Hạng** hoặc **Loại hồ sơ** → nạp lại checklist từ `catalog_hang_giay_to` (giữ file đã up nếu loại giấy vẫn còn trong danh sách mới).
2. Mỗi mục: upload scan (ảnh/PDF, nhiều trang) + toggle **"đã đối chiếu bản gốc"** (quyền `giayto.verify`) + ô hạn nếu `co_han`.
3. **Trạng thái tự tính khi bấm "Lưu & tiếp nhận"**: đủ mục bắt buộc + đã đối chiếu → `hop_le`; thiếu → `cho_bo_sung` + banner liệt kê đúng mục thiếu. Không cho chọn tay 2 trạng thái này.
4. `Lưu nháp` = `tu_van` (chưa tính checklist) — dùng khi khách chưa mang đủ giấy.

### Nút hành động cột phải (theo trạng thái × quyền)
| Trạng thái hiện tại | Nút hiện ra | Quyền |
|---|---|---|
| `tu_van` | Tiếp nhận | hoso.create |
| `tiep_nhan`/`cho_bo_sung` | Lưu; Chuyển hợp lệ (khi checklist đủ — tự động); Hủy | hoso.edit / hoso.cancel |
| `hop_le` | *(chờ tuition)* hiển thị "Chờ thu đợt 1" + link tạo phiếu thu | đọc |
| `da_thu_dot1` | Xếp khóa (chọn khóa + GV + xe) | hoso.edit + course.enrollment |
| `xep_khoa` | *(chờ report)* "Chờ báo Sở" | đọc |
| `da_bao_so` | Bắt đầu học (khai giảng) | hoso.approve |
| mọi trạng thái trước `dang_hoc` | Bàn giao phòng (nếu bật org) · Hủy (bắt buộc lý do) | hoso.transfer / hoso.cancel |

> `da_thu_dot1` và `da_bao_so` **chỉ đổi qua callback** từ tuition/report (P3 service API) — form không có nút đổi tay; hiển thị trạng thái chờ + deep-link sang module tương ứng.

## 3. API cần có (module student, feature hoso)

| Method | Endpoint | Ghi chú |
|---|---|---|
| GET | `/api/student/hoso` | list + filter + phân trang; **scopeWhere** áp server |
| GET | `/api/student/hoso/counts` | đếm theo trạng thái (tab) — cùng bộ lọc |
| GET | `/api/student/hoso/:id` | chi tiết + checklist + lịch sử |
| POST | `/api/student/hoso` | tạo (V1–V7 validate server) |
| PUT | `/api/student/hoso/:id` | sửa (chỉ trạng thái cho phép) |
| POST | `/api/student/hoso/:id/transition` | `{to, ly_do?}` — chạy rule + ghi `student_ho_so_lichsu`; nguồn duy nhất đổi trạng thái (kể cả callback từ tuition/report gọi vào đây với service token) |
| POST | `/api/student/hoso/:id/giayto` | upload scan (multipart) |
| PUT | `/api/student/hoso/:id/giayto/:gid/verify` | đối chiếu bản gốc |
| GET | `/api/student/hoso/check-cccd?cccd=&ma_kh=` | check trùng realtime (V1) |
| GET | `/api/catalog/checklist?hang=&loai=` | checklist động (đọc catalog) |

## 4. Trạng thái rỗng / lỗi / offline
- Danh sách rỗng theo bộ lọc → gợi ý "Tiếp nhận hồ sơ mới" (nếu có quyền).
- Mất mạng khi lưu → giữ form + retry; upload scan có hàng đợi (nguyên tắc chịu mạng kém — mức tối thiểu cho MVP: không mất dữ liệu đã nhập).
- Lỗi validate server trả về **theo trường** — form focus đúng ô.

## 5. DoD màn hình
- [ ] A: list + filter + tab đếm + badge + menu theo quyền/trạng thái + export.
- [ ] B: form 3 khối + cột phải; V1–V7 chặn cả 2 phía; checklist động theo hạng+loại; trạng thái tự tính.
- [ ] Transition duy nhất qua API `/transition`, mọi lần đổi ghi lịch sử (ai/khi nào/lý do).
- [ ] Data scope đúng 4 vai trò chính (TUYEN_SINH/KE_TOAN/GIAO_VIEN/HOC_VIEN app sau).
- [ ] Check trùng CCCD realtime + gợi ý nâng hạng.
- [ ] Test: tạo đủ giấy → `hop_le`; thiếu 1 mục → `cho_bo_sung` nêu đúng mục; nâng hạng hiện thêm 2 mục giấy; đổi hạng giữ file còn hợp lệ.

## 6. Chốt khi có dữ liệu Vĩnh An
1. Quy tắc sinh `ma_dk` (catalog code-rule) — tạm thời `HV-{ma_csdt}-{YYYY}-{seq5}`.
2. Checklist thật theo NĐ 94/2026 + TT 17/2026-BXD → seed catalog.
3. Có bật bàn giao phòng (org) từ đầu không — mặc định MVP: ẩn.
4. Bảng giá học phí thật → gợi ý V5.
</content>
