# Tab "Tài liệu số" — màn chi tiết Tài liệu (upload file mềm)

> Tài liệu con của [`KHO-TAI-LIEU.md`](./KHO-TAI-LIEU.md). Hàm dùng chung ở `KHO-TAI-LIEU.functions.ts`.
>
> **Khảo sát bằng script Playwright chạy thật trên sitdev ngày 2026-08-04 → 2026-08-05**, tài khoản
> `ecm09` (Admin — hiển thị "Đỗ Hà Linh") và `ecm01` ("Nguyễn Minh Hoàng"). Mọi testId/locator dưới
> đây đọc trực tiếp từ DOM thật, trừ những chỗ ghi `⚠️ chưa xác minh DOM`.
>
> 📌 **Phạm vi tài liệu — chỉ mô tả màn hình.** Không ghi logic test / kỳ vọng của case vào đây.

---

## 1. 🚨 Xếp lớp modal — `getRecordModal` KHÔNG dùng được ở màn này

Đo được **3 modal cùng hiển thị** khi mở 1 tài liệu:

| Lớp | `lbl-modal-title`                   | Dấu hiệu nhận biết                        |
| --- | ----------------------------------- | ----------------------------------------- |
| 1   | `"ATT-047-HĐSL Khai báo"`           | modal BHS, có `lbl-tab-cauTrucHoSo`        |
| 2   | `"ATT-047-HĐSL/TL001 Khai báo"`     | **modal chi tiết Tài liệu**, có `tab-digitizedATM` |
| 3   | _(không có)_                        | **modal xem trước file** (PDF viewer): `toolbar`, `zoom__*`, `page-navigation__*`, `full-screen__enter-button`… |

⇒ `.ant-modal-content:visible` `.last()` (tức `getRecordModal`) trỏ vào **lớp xem trước**.
✅ Dùng **`getModalTaiLieu(page)`** — lọc modal **có `tab-digitizedATM`**.

> 🚨 **Mở tài liệu bằng URL `?itemId=<id tài liệu>` trực tiếp KHÔNG ra màn chi tiết tài liệu**
> (đã thử: modal trên cùng là PDF viewer, `tab-digitizedATM` = 0 phần tử). Đường đi duy nhất đã
> kiểm chứng: **BHS → tab "Cấu trúc hồ sơ" → click tên tài liệu** (`openChiTietTaiLieu`).

---

## 2. Ba tab của màn chi tiết Tài liệu

| testId             | Nhãn        | Ghi chú                                              |
| ------------------ | ----------- | ---------------------------------------------------- |
| `tab-digitizedATM` | Tài liệu số | Khối upload + bảng file mềm (mục 3)                  |
| `tab-general`      | Thông tin   | Form thuộc tính tài liệu                             |
| `tab-history`      | _(icon)_    | Lịch sử hoạt động                                    |

**Tab mặc định phụ thuộc trạng thái tài liệu** (đo 2026-08-05):

| Tình huống                                   | Tab đang active sau khi mở |
| -------------------------------------------- | -------------------------- |
| Tài liệu **vừa tạo xong** (modal chuyển từ Tạo mới → chi tiết) | `tab-general` |
| Mở lại tài liệu **đã có file**                | `tab-digitizedATM`         |

> 🚨 3 tab là `input[type="radio"]` **ẩn** → click `label` bọc ngoài
> (`getByTestId("tab-digitizedATM").locator('xpath=ancestor::label[1]')`), và **cần `force: true`**:
> lớp modal xem trước file có thể phủ lên, click thường báo `element is not visible` rồi timeout.
> ✅ Dùng **`openTabTaiLieuSo(page)`** (idempotent — chỉ click khi tab chưa `checked`).

Modal giữ **cả 3 tab-pane trong DOM**; pane đang mở là
`div[role="tabpanel"][aria-hidden="false"]` → ✅ `getPaneTaiLieu(page)`.

---

## 3. Nội dung tab "Tài liệu số"

### 3a. Khối upload + toolbar

| testId                          | Mô tả                                                                  |
| ------------------------------- | ---------------------------------------------------------------------- |
| `sec-tai-lieu-so`               | Section (+ `sec-tai-lieu-so-title`)                                    |
| `sec-upload-tai-lieu-dinh-kem`  | Vùng upload (kéo thả)                                                  |
| `file-upload-tai-lieu`          | Nút **Tải lên** — là `div`, **không** phải `input[type=file]`           |
| `btn-tai-ve`                    | **Tải về** — chỉ xuất hiện **khi đã có ≥ 1 file**                       |
| `btn-tai-xuong-toan-bo`         | **Tải về toàn bộ** — chỉ xuất hiện khi đã có file                       |
| `tbl-danh-sach-file-dinh-kem`   | Bảng danh sách file                                                    |
| `empty-file-dinh-kem`           | Dòng trạng thái rỗng: _"Chưa có tài liệu, chọn "Tải lên" hoặc "Kéo thả" để tạo tài liệu"_ |
| `loading-atm`, `loading-noi-dung-tai-lieu` | Placeholder loading                                         |

### 3b. Bảng file — 10 cột

`"" | STT | Tên tệp | "" | Dung lượng | Thời gian upload | Người upload | Trạng thái OCR toàn văn | Tài liệu bóc tách | Nhãn Purview`

Dữ liệu thật của 1 dòng (đo trên sitdev):

```
1 | file-sample.pdf | 139.44 KB | 04/08/2026 | HL Đỗ Hà Linh | Đã bóc tách | (checkbox) | Chưa có
```

- **Thời gian upload chỉ có ngày** (`dd/MM/yyyy`), không có giờ.
- **Người upload** = avatar chữ viết tắt + tên hiển thị (`"HL Đỗ Hà Linh"` khi đọc `innerText`).
- **Trạng thái OCR toàn văn** đo được ngay sau upload đã là `"Đã bóc tách"`.

### 3c. testId theo từng dòng file (hậu tố là **uuid của file**)

`btn-xem-truoc-row-<uuid>`, `lbl-ten-tep-row-<uuid>`, `lbl-dung-luong-row-<uuid>`,
`lbl-ngay-upload-row-<uuid>`, `lbl-nguoi-upload-row-<uuid>`, `tag-ocr-status-row-<uuid>`,
`chk-tai-lieu-boc-tach-row-<uuid>`, `tag-purview-label-row-<uuid>`, `btn-menu-row-<uuid>`.

- 🐞 **`btn-mo-menu-thao-tac` lặp ở MỖI dòng** (3 file → 3 phần tử cùng testId), không phải nút
  chung của bảng như tài liệu cũ ghi → luôn scope theo dòng. Đã ghi vào `KHO-TAI-LIEU.TODO-DEV.md`.
- 🚨 `btn-menu-row-<uuid>` là **`div` phủ kín ô cuối dòng** (`class="dropdownMenu … absolute top-0
  left-0 right-0 bottom-0"`) và **chỉ visible khi hover dòng** → phải `row.hover()` trước; hover
  thẳng nút sẽ `element is not visible` → timeout.
  ✅ Dùng **`openMenuDongFile(page, tenTep)`**.

---

## 4. 🚨 Cơ chế upload

| Điểm                        | Kết quả đo                                                                                     |
| --------------------------- | ---------------------------------------------------------------------------------------------- |
| Số `input[type="file"]`     | **2** trong modal chi tiết TL (1 ở pane **ẩn**, 1 ở pane đang active), cả 2 nằm trong `sec-upload-tai-lieu-dinh-kem`. Modal BHS bên dưới còn 1 input `accept=null` nữa |
| `accept`                    | `.docx,.xlsx,.png,.jpg,.txt,.jpeg,.pptx,.rar,.zip,.cad,.mp3,.mp4,.mkv,.mov,.m4a,.pdf` — ⚠️ **không có `.doc`, `.xls`, không còn biến thể chữ hoa** |
| `multiple`                  | `true` — đã upload **2 file trong 1 lượt** thành công                                           |
| Nút "Tải lên"               | Click `file-upload-tai-lieu` **không mở `filechooser`** (đã thử, timeout 15 s) → chỉ dùng `setInputFiles` |
| Upload khi đang ở tab "Thông tin" | **Không có tác dụng gì** (app không chạy, không lỗi) → phải mở tab "Tài liệu số" trước |
| File ngoài `accept` (`.doc`, `.xls`) | App **im lặng bỏ qua**: không toast, không dòng mới, không thông báo lỗi (đo 12–30 s) |

**Log của app khi upload chạy đúng** (bắt qua console, hữu ích để debug):

```
[UPLOAD-TIMING][…][ALL] hash=10ms fileCount=1
[UPLOAD-TIMING][…][ALL] check-duplicate=89ms fileCount=1
[UPLOAD-TIMING][…][ALL] sas-presign=489ms fileCount=1
[UPLOAD-TIMING][…][ALL] azure-put=842ms fileCount=1
[UPLOAD-TIMING][…][ALL] save-metadata=267ms
[UPLOAD-TIMING][…][ALL] total-frontend=1749ms fileCount=1
```

Dừng lại ở `check-duplicate` ⇒ app đang **chờ lựa chọn ở dialog tài liệu trùng** (mục 5).

---

## 5. Dialog "Danh sách tài liệu trùng"

Xuất hiện khi file sắp upload **trùng với file mềm đã có trong hệ thống**.

**DOM đã đọc trực tiếp 2026-08-05:**

| Phần        | Giá trị                                                                                   |
| ----------- | ----------------------------------------------------------------------------------------- |
| Loại modal  | `.ant-modal-content` **thường** — 🚨 **KHÔNG** phải `.ant-modal-confirm`                   |
| Tiêu đề     | `"Danh sách tài liệu trùng. Có tiếp tục upload các file dưới không?"`                      |
| Bảng        | **`tbl-file-trung`** — cột `Chỉ mục` \| `Mã tài liệu` \| `Tên tài liệu chứa file mềm` \| `Tên file mềm` \| `""` |
| Dòng thật   | `A` \| `ATT-047-HĐSL/TL001` \| `KS-TLSO-1785837066583-TL1` \| `file-sample.pdf`            |
| Nút 1       | **`btn-upload-tat-ca`** — "Toàn bộ" (`ant-btn-primary`)                                     |
| Nút 2       | **`btn-upload-khong-trung`** — "Chỉ tài liệu không trùng"                                   |
| Nút 3       | **`btn-huy-upload`** — "Không" (`ant-btn-default`)                                           |

✅ Locator: **`getDialogTaiLieuTrung(page)`** (lọc modal **có `tbl-file-trung`**).
Hằng 3 nút: **`LUA_CHON_TRUNG`** (giá trị chính là testId) + `LUA_CHON_TRUNG_LABELS`.

**Hành vi từng nút (đo được):**

| Nút                                | Kết quả                                                                                     |
| ---------------------------------- | ------------------------------------------------------------------------------------------- |
| `btn-huy-upload` ("Không")         | Dialog đóng, **không toast**, số file **không đổi** (3 → 3)                                  |
| `btn-upload-tat-ca` ("Toàn bộ")    | Dialog còn ~2–4 s rồi đóng; toast `"Tải tài liệu lên thành công."` hiện ở **~t=4 s** và tắt sau ~4 s; app chạy đủ `sas-presign → azure-put → save-metadata`; **số dòng KHÔNG tăng** (file trùng tên bị **thay thế**, thứ tự dòng đổi) |
| `btn-upload-khong-trung`           | ⚠️ chưa thử                                                                                  |

- Bảng liệt kê **tài liệu đang chứa file trùng** (mã TL + tên TL) ⇒ phạm vi kiểm trùng **vượt ra
  ngoài tài liệu hiện tại**, không chỉ trong tài liệu đang mở.
- **Không chọn gì thì upload treo**: không toast, bảng không có dòng mới — đây là lý do khảo sát ban
  đầu tưởng "upload không chạy".

---

## 6. Kết quả khi upload thành công

- Toast **`"Tải tài liệu lên thành công."`** — xuất hiện sau ~**4 giây** và **tự tắt sau ~4 giây**
  (⚠️ đây là **sửa lại** thông tin cũ ở `KHO-TAI-LIEU.MODAL-TAO-TAI-LIEU.md` mục 3a — chỗ đó ghi
  "không có toast", đo trên modal *tạo mới* nên không đúng cho tab này).
  🚨 **Đừng assert cứng theo toast này**: có lần upload thành công (log app đủ `azure-put` +
  `save-metadata`, dòng file đã vào bảng) mà `.ant-message-notice` **không bắt được** — ngay sau khi
  upload xong app còn **tự mở modal xem trước file vừa upload** (`dialog "file-sample.pdf 1/3"`),
  vòng đời toast rất ngắn. Tín hiệu chắc chắn là **dòng file trong bảng**;
  `uploadFileTaiLieu` chờ toast kiểu "mềm" (không fail nếu thiếu) rồi assert dòng file.
- **Bảng tự thêm dòng ngay**, không cần mở lại phiếu (ảnh app 2026-08-05).
  🚨 Riêng file **trùng tên** thì chọn "Toàn bộ" **không tạo dòng mới** — bản cũ bị thay thế.
- 🚨 **Cạm bẫy Playwright**: `setInputFiles` **đúng cùng 1 file vào cùng 1 input lần thứ hai trong 1
  phiên** sẽ không bắn `change` → app không chạy gì (không dialog, không toast). Muốn upload lại
  đúng file đó thì **mở lại phiếu** (`openChiTietTaiLieu`) trước.
- `btn-tai-ve` và `btn-tai-xuong-toan-bo` xuất hiện.
- File **vẫn còn sau khi mở lại phiếu** (đã kiểm chứng: 3 file upload hôm trước hiện đủ khi mở lại).

---

## 7. Dùng hàm

```ts
import {
  openChiTietTaiLieu,
  openTabTaiLieuSo,
  uploadFileTaiLieu,
  sampleFile,
  SAMPLE_FILE,
  LUA_CHON_TRUNG,
  layDanhSachTenTep,
  docThongTinFile,
  openMenuDongFile,
} from "../../src/screen-instructions/KHO-TAI-LIEU.functions";

// Mở màn chi tiết tài liệu rồi upload 1 file mẫu
await openChiTietTaiLieu(page, recordUrl, tenTaiLieu);
await uploadFileTaiLieu(page, sampleFile(SAMPLE_FILE.PDF));

// Nhiều file 1 lượt
await uploadFileTaiLieu(page, [
  sampleFile(SAMPLE_FILE.DOCX),
  sampleFile(SAMPLE_FILE.XLSX),
]);

// Gặp dialog trùng thì bỏ qua file trùng thay vì upload tất cả
await uploadFileTaiLieu(page, sampleFile(SAMPLE_FILE.PDF), {
  luaChonTrung: LUA_CHON_TRUNG.CHI_KHONG_TRUNG,
});

// Đọc lại thông tin dòng file
const tt = await docThongTinFile(page, SAMPLE_FILE.PDF); // { dungLuong, thoiGianUpload, nguoiUpload, ocr }
```

> 📌 File mẫu nằm ở **`src/sample-files/`** (`file-sample.pdf`, `file-sample.docx`,
> `file_example_XLSX.xlsx` dùng được; `file-sample.doc`, `file_example_XLS.xls` **ngoài `accept`**).
> Vì file mẫu dùng lại nhiều lần nên **lần upload thứ hai trở đi sẽ gặp dialog trùng** — `uploadFileTaiLieu`
> mặc định chọn `"Toàn bộ"` nên vẫn upload được.

---

## 8. ⚠️ Chưa khảo sát

- **Các mục trong menu `btn-menu-row-<uuid>`** của dòng file: tài liệu cũ ghi `mni-tai-xuong`,
  `mni-cap-nhat-thuoc-tinh`, `mni-quan-ly-phien-ban`, `mni-xem-noi-dung-boc-tach`, `mni-xoa`
  nhưng **chưa xác minh lại trên build này** (mọi lần thử đều bị chặn ở bước upload trước đó).
- Nút **`btn-upload-khong-trung`** ("Chỉ tài liệu không trùng") của dialog trùng — chưa thử
  (2 nút còn lại đã đo, mục 5).
- `btn-mo-menu-thao-tac` (nút "..." trong từng dòng): hover không mở dropdown trong lần thử trước.
- **Bộ đếm `File đính kèm: n`** ở header tab "Cấu trúc hồ sơ" — không đọc được dòng nào khớp mẫu đó
  khi grep header, cần khảo sát lại xem app còn hiển thị hay không.
- Luồng **OCR toàn văn** (`tag-ocr-status-row-*` đổi trạng thái theo thời gian), `chk-tai-lieu-boc-tach-row-*`.
- Upload bằng **kéo thả** vào `sec-upload-tai-lieu-dinh-kem`.
- Giới hạn kích thước file, upload file rỗng, upload khi tài liệu ở trạng thái khác `Khai báo`.
