# Module `teacher` — Hồ sơ nghiệp vụ & tuân thủ giáo viên

> 07/7/2026 · cập nhật **15/7/2026** · Giai đoạn: **MVP** · Bản HTML: [teacher.html](teacher.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: **đã scaffold** bảng giáo viên (port legacy) — cần thêm `employee_id`, `cert`, `assignment`.
>
> ⚠ **Định vị lại 15/7/2026** (đối chiếu thực tế Vĩnh An: GV chính là nhân viên phòng KH–ĐT):
> teacher **KHÔNG phải "bảng người" thứ hai**. Nhân thân (họ tên, CCCD, SĐT, ảnh) thuộc về
> **nhân viên trong HRM** (built-in — 26 người Vĩnh An đã seed); teacher là **hồ sơ nghiệp vụ
> gắn thêm** qua `employee_id`, giữ phần HRM không có: mã GV đăng ký Sở, hạng GPLX được dạy,
> chứng chỉ + hạn, thẻ RFID, phân công. Lý do vẫn tách module: (1) hai vòng đời khác nhau —
> quan hệ **lao động** (HRM) ≠ vai trò **được cơ quan quản lý cấp phép** (teacher: nghỉ dạy vẫn
> có thể là NV, GV thỉnh giảng có thể không phải NV); (2) `ma_gv`/`ma_csdt` là khóa compliance
> chảy vào DAT/BC1/kế hoạch gửi Sở; (3) HRM là core dùng chung mọi tenant — không nhét nghiệp
> vụ ngành lái xe vào core.

## 1. Vai trò
Hồ sơ **nghiệp vụ & tuân thủ** giáo viên (lý thuyết/thực hành): **chứng chỉ + hạn giấy tờ**,
**đăng ký danh sách GV với Sở XD**, **phân công dạy**. Nhân thân đọc từ HRM qua `employee_id` —
màn hình tạo GV = "chọn nhân viên (phòng đào tạo) → bổ sung hồ sơ nghiệp vụ", KHÔNG nhập lại
thông tin người (join pattern P2 — [quan-he-module](../quan-he-module-2026-07-07.md)).

## 2. Feature / BO / Action

| Feature | BO chính | Action |
|---|---|---|
| `teacher` | Giáo viên (hồ sơ nghiệp vụ) | view·create·edit·delete·export |
| `cert` | Chứng chỉ GV + hạn | view·create·edit·configure |
| `assignment` | Phân công dạy | view·**assign**·report |

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

### Giáo viên (`teacher`) — *bảng đã có, sửa theo định vị 15/7*
| Nhóm | Trường | Ghi chú |
|---|---|---|
| **Link nhân thân** | **`employee_id`** (FK mềm → `hrm.employees`, **nullable**) | Nguồn nhân thân duy nhất. Nullable cho **GV thỉnh giảng/khoán** không thuộc biên chế — mặc định khuyến khích link. Nhân thân (họ tên, ngày sinh, CCCD, SĐT, địa chỉ, ảnh) **đọc từ HRM, không nhập lại**; các cột legacy trùng chỉ giữ làm **mirror import FPT**, UI không cho sửa. |
| **Nghiệp vụ** | `ma_gv`, `ma_csdt` | Mã đăng ký với Sở — khóa compliance, UNIQUE `(ma_gv, ma_csdt)` |
| | hạng GPLX được dạy, trình độ học vấn (ref catalog), chuyên ngành | |
| | thẻ RFID (`ma_the_rfid`/`so_the_rfid`), vân tay | danh tính lên xe/điểm danh |
| | trạng thái đăng ký Sở, trạng thái hoạt động | |

**Import FPT (go-live):** port bảng `giao_vien` từ GPLX_csdt → **khớp `employee_id` bằng CCCD**
với 26 nhân viên đã seed; không khớp → GV thỉnh giảng (employee_id NULL) hoặc thiếu hồ sơ HRM (báo).

### Chứng chỉ (`cert`) — *mới*
| Trường | Kiểu | Ghi chú |
|---|---|---|
| Giáo viên | ref | |
| Loại | enum | GPLX / chứng chỉ sư phạm / chứng chỉ tập huấn / khám sức khỏe |
| Số hiệu, nơi cấp | text | |
| Ngày cấp / **ngày hết hạn** | date | nguồn cho notify nhắc hạn |
| File scan | file | |

### Phân công (`assignment`) — *mới, dùng chung dữ liệu enrollment của course*
GV × khóa × xe; loại dạy (lý thuyết/thực hành); trạng thái khớp đăng ký Sở.

## 4. Quy tắc nghiệp vụ
1. **Một con người – một nguồn nhân thân**: nhân thân sống ở HRM; teacher không sửa nhân thân.
   Sửa tên/SĐT → sửa ở HRM, teacher hiển thị theo. Kiêm nhiệm (vừa hành chính vừa dạy) = 1
   employee + 1 teacher profile — khớp thực tế Vĩnh An.
2. GV dạy thực hành phải có **GPLX đúng hạng + chứng chỉ còn hạn** — hết hạn → chặn phân công mới + nhắc hạn (notify).
3. Phân công phải **khớp danh sách GV đã đăng ký với Sở** — lệch → cảnh báo, phiên DAT của GV đó bị đánh dấu xem xét.
4. Lương GV (v1.5 payroll) tính từ **giờ dạy thực tế DAT** — teacher chỉ giữ hồ sơ, không giữ bảng công.
5. GV là user hệ thống (data scope `phan_cong`): chỉ thấy học viên/lịch của mình — resolve qua
   chuỗi có sẵn **user → `users.employee_id` → teacher.employee_id**, không cần mapping riêng.

## 5. Liên kết chéo module
| Module | Khóa | Ghi chú |
|---|---|---|
| **hrm (built-in)** | **`employee_id`** | **nguồn nhân thân**; teacher = profile nghiệp vụ gắn thêm |
| course | `ma_gv` | phân công theo khóa |
| student | `ma_gv` | học viên phụ trách (data scope) |
| dat | `ma_gv` | phiên học gắn GV; đối chiếu lệch |
| notify | hạn chứng chỉ | quy tắc nhắc hạn |
| rfcard | thẻ RFID | điểm danh/lên xe |

## 6. Câu hỏi mở
- Danh mục loại chứng chỉ GV chính xác theo TT 17/2026-BXD.
- ~~Mapping GV ↔ tài khoản đăng nhập~~ → **đã giải** bằng chuỗi `users.employee_id` → `teacher.employee_id` (định vị 15/7).
- GV thỉnh giảng: bắt buộc tạo hồ sơ HRM loại "cộng tác viên" hay cho phép teacher đứng một mình (employee_id NULL)? — tạm chọn **cho phép NULL**, xác nhận với Vĩnh An.

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

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

```json
[
  {
    "group": "main",
    "feature": "teacher",
    "label": "Giáo viên",
    "actions": [
      "view",
      "create",
      "edit",
      "delete",
      "export"
    ]
  },
  {
    "group": "main",
    "feature": "cert",
    "label": "Chứng chỉ GV",
    "actions": [
      "view",
      "create",
      "edit",
      "configure"
    ]
  },
  {
    "group": "main",
    "feature": "assignment",
    "label": "Phân công dạy",
    "actions": [
      "view",
      "assign",
      "report"
    ]
  }
]
```


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

| Feature | `QTHT` | `GIAM_DOC` | `NHAN_SU` | `QL_DAO_TAO` | `GIAO_VU` | `GIAO_VIEN` |
|---|:--:|:--:|:--:|:--:|:--:|:--:|
| `teacher` | R | R | R | **W** | R | R² |
| `cert` | R | R | R | **W** | — | R² |
| `assignment` | R | R | — | W | R | — |

> Đổi 15/7: **W của teacher/cert chuyển NHAN_SU → QL_DAO_TAO** — hồ sơ nghiệp vụ GV thuộc
> phòng đào tạo (nhân thân đã ở HRM do nhân sự quản); role NHAN_SU ở Vĩnh An hiện không gán ai
> ([WS0 §8.2](../giai-doan-1-mvp/WS0-co-cau-to-chuc-va-phan-quyen-2026-07-06.md)).

> **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 đủ: `teacher.main.<feature>.<action>`.
> Đặc thù: assignment +assign,report (QL_DAO_TAO)

### 7.3 Quyền mức record (row-level)

**(a) Manifest — bổ sung `fields` + `scopeVars` vào các feature bị scope** *(mở rộng schema theo [plan data-scope P1](../giai-doan-1-mvp/plan-data-scope-2026-07-06.md) — manifest chỉ khai "cái gì scope được", KHÔNG chứa rule)*:

```json
[
  {
    "group": "main",
    "feature": "teacher",
    "fields": [
      "giao_vien_id",
      "ma_gv"
    ],
    "scopeVars": [
      "current_giao_vien_id"
    ]
  },
  {
    "group": "main",
    "feature": "cert",
    "fields": [
      "giao_vien_id"
    ],
    "scopeVars": [
      "current_giao_vien_id"
    ]
  }
]
```

**(b) Điều kiện record theo vai trò:**

| Vai trò | Feature (action) | Điều kiện record |
|---|---|---|
| `GIAO_VIEN` | teacher, cert (view) | giao_vien_id = ctx.current_giao_vien_id (hồ sơ của mình) |

**(c) Seed `rt_role.grants` (kind `rule` — nhiều rule cùng resource+action = OR):**

```json
[
  {
    "kind": "rule",
    "module": "teacher",
    "feature": "teacher",
    "actions": [
      "view"
    ],
    "predicate": {
      "op": "cmp",
      "field": "giao_vien_id",
      "cmp": "=",
      "value": {
        "ctx": "current_giao_vien_id"
      }
    }
  },
  {
    "kind": "rule",
    "module": "teacher",
    "feature": "cert",
    "actions": [
      "view"
    ],
    "predicate": {
      "op": "cmp",
      "field": "giao_vien_id",
      "cmp": "=",
      "value": {
        "ctx": "current_giao_vien_id"
      }
    }
  }
]
```

> Biến ngữ cảnh: `current_user_id` — engine có sẵn · `current_giao_vien_id`/`current_ma_gv` — module teacher tra từ user đăng nhập (ctxVars, plan P3) · `current_nguoi_lxid`/`current_ma_dk_list` — module student tra (1 người nhiều lượt đăng ký) · `current_assigned_*` — danh sách phụ trách/được phân công.
