# Module `dat` — Thiết bị & dữ liệu DAT

> 07/7/2026 · Giai đoạn: **MVP · Painkiller** · Bản HTML: [dat.html](dat.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** `device` + `log` — cần thêm `session`, `reconcile`, `alert` (lõi giá trị).
> Schema chi tiết: [§2.6](../giai-doan-1-mvp/schema-du-lieu-2026-07-06.md)

## 1. Vai trò
**Painkiller số 1 của MVP**: nhận dữ liệu phiên học từ vendor DAT, **đối soát km/giờ** theo hạng, cảnh báo học viên thiếu chỉ tiêu trước kỳ sát hạch và phát hiện **lệch GV/xe so với đăng ký Sở**. Dữ liệu DAT là căn cứ xét hoàn thành khóa học (NĐ 94/2026).

## 2. Feature / BO / Action

| Feature | BO chính | Action |
|---|---|---|
| `device` | Thiết bị DAT (seri, sim, hạn) | view·create·edit·delete |
| `session` | Phiên học DAT (km/giờ) | view·**import**·**exclude**·export |
| `reconcile` | Kết quả đối soát theo học viên/hạng | view·**reconcile**·report |
| `alert` | Cảnh báo thiếu km/giờ, lệch GV/xe | view·configure·**send** |
| `log` | Nhật ký truyền/kết nối | view |

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

### Thiết bị (`device`) — *đã có*
Seri, số SIM, loại thiết bị, ngày bàn giao, ngày hết hạn (hợp quy/dịch vụ), trạng thái.

### Phiên học (`session`) — *mới, bảng `dat_phien_hoc`*
| Trường | Kiểu | Ghi chú |
|---|---|---|
| Nguồn + ID phiên gốc | text | **UNIQUE (nguồn, id gốc)** — chống trùng import |
| Học viên / khóa / GV / xe | khóa nghiệp vụ | `ma_dk, ma_kh, ma_gv, ma_xe` |
| Bắt đầu / kết thúc | datetime | |
| Quãng đường (km) / thời lượng (phút) | number | |
| **Hợp lệ** | bool | false khi lệch GV/xe/trùng giờ |
| Lý do loại | text | ghi rõ khi không tính |

### Đối soát (`reconcile`) — *kết quả tổng hợp theo học viên*
Tổng km/giờ hợp lệ ↔ chuẩn `required_km/hours` của hạng (catalog) → % hoàn thành, còn thiếu, dự báo đủ trước ngày sát hạch.

### Cảnh báo (`alert`)
Quy tắc ngưỡng (thiếu >X% khi còn Y ngày; phiên lệch GV/xe; thiết bị mất kết nối) → đẩy notify.

## 4. Quy tắc nghiệp vụ
1. **Connector theo vendor** (plug-in): mỗi nhà cung cấp DAT 1 adapter; phiên import phải idempotent (chạy lại không nhân đôi).
2. Phiên **lệch GV/xe so với phân công đã đăng ký** → `hop_le=false` + lý do — không tính vào đối soát, đưa vào danh sách xem xét.
3. Phiên trùng giờ cùng học viên/xe → giữ 1, loại phần trùng.
4. `exclude`/khôi phục phiên là thao tác có quyền riêng + ghi lịch sử (ai loại, lý do).
5. Kết quả đối soát là **đầu vào trực tiếp** cho báo cáo tốt nghiệp (report) và bảng công GV (payroll v1.5).

## 5. Liên kết chéo module
| Module | Khóa | Ghi chú |
|---|---|---|
| student | `ma_dk` | tiến độ km/giờ từng hồ sơ |
| course | `ma_kh` | đối soát theo khóa; đối chiếu phân công |
| teacher / vehicle | `ma_gv` / `ma_xe`, `seri_dat` | phát hiện lệch; quy phiên về xe theo lịch sử gắn thiết bị |
| catalog | hạng → km/giờ chuẩn | chuẩn đối soát |
| notify | cảnh báo | gửi Zalo/app |
| report | kết quả đối soát | căn cứ xét hoàn thành |

## 6. Câu hỏi mở
- Vĩnh An dùng vendor DAT nào, định dạng lấy dữ liệu (API/file) → viết connector đầu tiên.
- Chuẩn "phiên hợp lệ" chi tiết (đêm/ngày, tốc độ tối thiểu?) theo quy định hiện hành.

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

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

```json
[
  {
    "group": "main",
    "feature": "device",
    "label": "Thiết bị DAT",
    "actions": [
      "view",
      "create",
      "edit",
      "delete"
    ]
  },
  {
    "group": "main",
    "feature": "session",
    "label": "Phiên học DAT",
    "actions": [
      "view",
      "import",
      "exclude",
      "export"
    ]
  },
  {
    "group": "main",
    "feature": "reconcile",
    "label": "Đối soát km/giờ",
    "actions": [
      "view",
      "reconcile",
      "report"
    ]
  },
  {
    "group": "main",
    "feature": "alert",
    "label": "Cảnh báo",
    "actions": [
      "view",
      "configure",
      "send"
    ]
  },
  {
    "group": "main",
    "feature": "log",
    "label": "Nhật ký truyền",
    "actions": [
      "view"
    ]
  }
]
```

> ⚠️ Action đặc thù (`exclude`, `reconcile`, `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` | `KY_THUAT` | `QL_DAO_TAO` | `GIAO_VU` | `GIAO_VIEN` | `HOC_VIEN` |
|---|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| `device` | R | R | W | — | — | — | — |
| `session` | R | R | — | R | W | R¹ | — |
| `reconcile` | R | R | — | W | W | R¹ | R² |
| `alert` | R | R | — | W | R | — | — |
| `log` | R | R | R | — | — | — | — |

> **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 đủ: `dat.main.<feature>.<action>`.
> Đặc thù: session +import,exclude (GIAO_VU) · reconcile +reconcile (QL_DAO_TAO, GIAO_VU) · alert +send,configure (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": "session",
    "fields": [
      "ma_gv",
      "ma_dk",
      "ma_kh"
    ],
    "scopeVars": [
      "current_ma_gv",
      "current_ma_dk_list"
    ]
  },
  {
    "group": "main",
    "feature": "reconcile",
    "fields": [
      "ma_dk",
      "ma_gv"
    ],
    "scopeVars": [
      "current_ma_gv",
      "current_ma_dk_list"
    ]
  }
]
```

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

| Vai trò | Feature (action) | Điều kiện record |
|---|---|---|
| `GIAO_VIEN` | session, reconcile (view) | ma_gv = ctx.current_ma_gv |
| `HOC_VIEN` | reconcile (view) | ma_dk ∈ ctx.current_ma_dk_list (các lượt đăng ký của mình) |

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

```json
[
  {
    "kind": "rule",
    "module": "dat",
    "feature": "session",
    "actions": [
      "view"
    ],
    "predicate": {
      "op": "cmp",
      "field": "ma_gv",
      "cmp": "=",
      "value": {
        "ctx": "current_ma_gv"
      }
    }
  },
  {
    "kind": "rule",
    "module": "dat",
    "feature": "reconcile",
    "actions": [
      "view"
    ],
    "predicate": {
      "op": "cmp",
      "field": "ma_gv",
      "cmp": "=",
      "value": {
        "ctx": "current_ma_gv"
      }
    }
  },
  {
    "kind": "rule",
    "module": "dat",
    "feature": "reconcile",
    "actions": [
      "view"
    ],
    "predicate": {
      "op": "in",
      "field": "ma_dk",
      "values": [
        {
          "ctx": "current_ma_dk_list"
        }
      ]
    }
  }
]
```

> 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.
