# Mục 1.4.1 — Tạo thư mục trong Cấu trúc hồ sơ (UC22)

> **Đọc file này trước khi viết bất kỳ case nào của mục 1.4.1.**
> Đây là nơi lưu **tiền điều kiện chung** do QA gửi + cách script dựng nó, để mọi case trong mục
> dùng chung một cách hiểu (kể cả khi làm ở session khác).
>
> Tài liệu màn hình: `src/screen-instructions/KHO-TAI-LIEU.md` (+ `MODAL-TAO-THU-MUC.md`).
> Quy ước đặt tên & cấu trúc: `CLAUDE.md`. Fixture/TIMEOUT/PW: `tests/README.md`.

---

## 1. Tiền điều kiện chung của mục 1.4.1 — NGUYÊN VĂN QA

> **Điều kiện:**
>
> - Thủ thư / ITAdmin / Người dùng được phân quyền tạo mới hồ sơ: Trạng thái **"Khai báo"** hoặc
>   **"Đang hoạt động"**
> - Người dùng / Nhóm được phân quyền **Owner** của Bộ hồ sơ: Trạng thái **"Đang hoạt động"**
> - Người dùng / Nhóm được phân quyền **Tạo mới** trong Cấu trúc hồ sơ: Trạng thái **"Đang hoạt động"**
> - Người dùng / Nhóm được phân quyền **Tạo mới tại Thư mục** trong Bộ hồ sơ: Trạng thái
>   **"Đang hoạt động"**

### Cách đọc (theo `CLAUDE.md` — "Đọc tiền điều kiện của testcase QA")

- 4 dòng trên là **4 nhóm vai** có thể thao tác được ở mục này, **không phải 4 điều kiện phải thoả
  cùng lúc**. Mỗi case chọn (các) vai mà dòng testcase của nó ghi.
- Vai nào QA ghi là có quyền thì **mặc định làm được** tới bước "Mong muốn" — không tự đổi kỳ vọng
  thành "vai này bị chặn". Chạy thật bị chặn → đó là **kết quả test**.
- **Quyền cấp lớn bao trùm cấp nhỏ**: có quyền ở BHS thì cũng có quyền đó ở TM/TL bên trong.
  Vì vậy "Tạo mới ở BHS" ⊃ "Tạo mới tại Thư mục", còn ngược lại thì không.
- Trạng thái BHS ghi kèm mỗi vai là **điều kiện bắt buộc của vai đó**, không phải tuỳ chọn:
  chỉ **người tạo BHS** (Thủ thư/ITAdmin tự tạo) mới mở được BHS ở trạng thái `Khai báo`;
  các vai còn lại **phải** `Hoạt động` (xem `KHO-TAI-LIEU.PHAN-QUYEN-THEO-VAI.md` mục 1).

---

## 2. Bảng vai → cách script dựng dữ liệu

Tất cả dựng qua `dungDuLieuBoHoSo()` trong [`1.4.1.setup.ts`](./1.4.1.setup.ts).
Mỗi test **tự dựng BHS riêng** (không chia sẻ dữ liệu giữa các test) để chạy song song được.

| Vai QA nêu                       | Fixture       | Cách dựng                                                                                                    | Trạng thái BHS |
| -------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------ | -------------- |
| Thủ thư                          | `librarian`   | Chính nó gọi `dungDuLieuBoHoSo` → tự tạo BHS nên là **Owner**                                                | `Khai báo`     |
| ITAdmin                          | `admin`       | Như trên                                                                                                     | `Khai báo`     |
| Người dùng được **Owner** BHS    | `ecm06`       | `librarian` dựng + `quyenBanDau: { permTestId: DOC_PERM.OWNER, account: "ecm06" }` + `chuyenSangHoatDong`     | `Hoạt động`    |
| Người dùng được **Tạo mới** BHS  | `ecm05`       | như trên với `DOC_PERM.ADD`                                                                                  | `Hoạt động`    |
| **Nhóm** được Owner / Tạo mới    | `ecm06`       | như trên nhưng `account: GROUP.ECM06` (`AUTO_GROUP_ECM06`, thành viên `ecm06`), thao tác bằng `ecm06`         | `Hoạt động`    |
| Chỉ có quyền **tại 1 thư mục**   | `ecm05/ecm06` | `taoThuMucCoQuyen: { account, permTestId: FOLDER_PICKER.ADD \| OWNER, doMat?, trangThaiHienThi? }` — thư mục đó **đặt quyền độc lập** rồi cấp quyền cho vai; thư mục còn lại vẫn kế thừa → là dòng "không có quyền". (Cách khác, nặng hơn: `openPhanQuyenNangCao` → `ngatKeThua(codeTM)` → `themQuyenChoNguoiDung(...)`) | `Hoạt động`    |
| **Không** có quyền tạo mới (nhánh âm) | `ecm05/ecm06` | `quyenBanDau` là quyền **không phải** Owner/ADD: `DOC_PERM.VIEW` hoặc `DOC_PERM.EDIT` | `Hoạt động` |

### Dữ liệu `dungDuLieuBoHoSo` tạo ra

```
BHS  AT-UC22-<maCase>-<ts>           (Owner = người gọi; + quyenBanDau nếu có)
 ├─ AT-UC22-<maCase>-<ts>-TM         ← thư mục ở gốc BHS, đang KẾ THỪA quyền của BHS
 └─ AT-UC22-<maCase>-<ts>-TM-CQ      ← quyền ĐỘC LẬP + cấp quyền cho 1 vai (khi taoThuMucCoQuyen)
```

- `taoThuMuc: false` nếu case không cần thư mục sẵn có.
- `chuyenSangHoatDong: true` **bắt buộc** cho mọi vai không phải người tạo BHS.
- Cần thư mục **nhiều cấp**: gọi thêm `createThuMuc(..., { parentName })` trong spec, hoặc bổ sung
  option vào `1.4.1.setup.ts` nếu ≥ 2 case dùng.

### 🚨 Vai cần test luôn phải có **ít nhất 1 quyền ở BHS**

Không có quyền gì thì **không mở được màn chi tiết BHS** (toast `"Bạn không có quyền xem!"`), và bảng
Cấu trúc hồ sơ cũng **chỉ hiện những nhánh vai có quyền**
(`KHO-TAI-LIEU.PHAN-QUYEN-THEO-VAI.md` mục 1 & 3). Vì vậy:

- **Nhánh âm (155, 156)**: cấp cho vai 1 quyền **không phải** Owner/Tạo mới (VIEW hoặc EDIT) để vai
  vẫn vào được màn mà kiểm tra nút bị ẩn.
- **Case 154**: cấp thêm **VIEW ở BHS** cho vai, nếu không thì dòng thư mục "không có quyền" sẽ
  **không hiện** và không kiểm chứng được chữ "chỉ" trong mong muốn của QA (giả định này đã ghi rõ
  ở đầu file spec 154 — nếu QA hiểu khác thì đổi cách dựng).

---

## 2b. Bước kiểm tra dùng chung — [`1.4.1.steps.ts`](./1.4.1.steps.ts)

| Hàm                                            | Dùng ở   | Nội dung                                                                                      |
| ---------------------------------------------- | -------- | --------------------------------------------------------------------------------------------- |
| `moTabCauTrucHoSo(page, duLieu)`               | tất cả   | Mở BHS theo `recordUrl` → vào tab "Cấu trúc hồ sơ"                                            |
| `expectCoNutTaoMoiVoi2MucCon(page)`            | 153      | `btn-tao-moi` hiện + dropdown có `mni-them-thu-muc`, `mni-them-tai-lieu`                       |
| `expectKhongCoNutTaoMoi(page)`                 | 155      | `btn-tao-moi` **không hiển thị** (`toBeHidden` → đạt cả khi element không tồn tại)             |
| `expectCoNutThemThuMucODong(page, tenTM)`      | 153, 154 | Menu mở rộng của dòng thư mục **có** mục "Thêm thư mục"                                        |
| `expectKhongCoNutThemThuMucODong(page, tenTM)` | 154, 156 | Dòng thư mục thấy được nhưng menu **không** có "Thêm thư mục" (không có nút mở menu cũng đạt)  |
| `taoThuMucDayDuThongTin(page, duLieu, ten, opts?)` | 163  | Thao tác 1–3 của QA: Thêm Thư mục → điền đủ trường bắt buộc → Xác nhận (**đã assert toast "Thành công"**) |
| `expectThuMucHienThiDungViTri(page, duLieu, ten, opts?)` | 163 | Thư mục hiển thị đúng cấp (`level-0` ở gốc, `level-1` + **ngay dưới cha** nếu truyền `tenCha`) |
| `expectThuocTinhThuMucDaLuu(page, duLieu, tenTM)` | 163  | Mở lại màn "Cập nhật thư mục" → Tên đúng + trường bắt buộc có giá trị (không so từng chuỗi)                    |
| `expectIndexThuMucTuSinh(page, duLieu, index, opts?)` | 166 | Mở modal "Thêm thư mục" → trường Index (input disabled ở ô "Tên") tự sinh đúng mã + **không sửa được** |
| `moThuMuc3DatQuyenRiengRoiDoiCapCha(page, duLieu177, cha1, cha2)` | 177 | Mở modal ở gốc BHS cho thư mục 3 → **Đặt quyền độc lập** (quyền riêng) → đổi cấp cha = `cha1` → assert Độ mật/Trạng thái/cả 5 khối quyền giống `cha1` → đổi cấp cha = `cha2` → assert lại giống `cha2` |
| `moThuMuc3TimKiemCapCha(page, duLieu178)`                       | 178 | Mở modal ở gốc BHS cho thư mục 3 → mở ô "Thư mục/HS cấp cha" → gõ tìm theo tên thư mục 1 → assert ra đúng option → gõ tìm theo tên thư mục 2 → assert ra đúng option |
| `expectNguoiTaoLaOwnerThuMuc(page, duLieu, tenTM, tenNguoiTao)` | 163 | Modal Phân quyền nâng cao → người tạo có trong cột **Quyền Owner** của thư mục. Tên người tạo truyền vào là **tên hiển thị lấy động** qua `layTenHienThiNguoiDangDangNhap(creatorPage)` |
| `expectDoMatMacDinhRoiDong(page, recordUrl, doMatMongDoi)`     | 182 | Mở modal "Thêm thư mục" ở gốc BHS → assert Độ mật mặc định → đóng modal bằng nút "Hủy" |
| `doiDoMatBoHoSo(page, recordUrl, doMat)`                        | 182 | Mở lại BHS → tab Thông tin → đổi `sel-securityLevel` → Lưu lại |
| `DO_MAT` / `doMatChoPhepTrong(doMatCha)`                        | 182 | Hằng 3 mức Độ mật theo thứ tự tăng dần + các mức **được phép** dùng bên trong 1 cha (con phải ≥ cha) |
| `expectRangBuocDoMatThuMucCon(page, recordUrl, tenCha, doMatCha)` | 182 | Mở modal tạo TM con trong `tenCha` → assert dropdown Độ mật **chỉ có** các mức được phép; mức không được phép mà app vẫn cho chọn → thử tạo và assert **không** tạo được |
| `TRUONG_DO_MAT` / `TRUONG_TRANG_THAI_HIEN_THI`                  | 182/188 | Mô tả 1 **trường kế thừa từ cấp cha** (testId ở modal TM + ở form BHS, các mức theo thứ tự lỏng → chặt) |
| `expectGiaTriMacDinhRoiDong(page, recordUrl, truong, mongDoi)`   | 182/188 | Mở modal "Thêm thư mục" ở gốc BHS → assert giá trị mặc định của `truong` → đóng modal |
| `doiGiaTriBoHoSo(page, recordUrl, truong, giaTri)`               | 182/188 | Mở lại BHS → tab Thông tin → đổi `truong` → Lưu lại |
| `mucChoPhepTrong(truong, mucCha)`                               | 182/188 | Các mức được phép bên trong 1 cha đang ở mức `mucCha` (con phải ≥ cha) |
| `expectRangBuocThuMucCon(page, recordUrl, truong, opts)`        | 182/188 | Bản generic của dòng trên — `opts.tenCha` bỏ trống thì cấp cha là **gốc BHS**. 3 hàm `*DoMat*` của 182 là wrapper của nhóm này |
| `QuyenPhanCong`                                                 | 247/248 | 1 dòng "quyền nào của ai": cột `PQ_COL`, nhãn, account/tên nhóm, **tên hiển thị lấy động** |
| `taoThuMucChaVaCon247(page, duLieu)`                            | 247 | Thao tác của case: tạo 1 thư mục ở gốc BHS + 1 thư mục con bên trong nó (đều giữ mặc định **kế thừa**) |
| `expectKeThuaQuyenTuCapCha247(page, duLieu, quyen[])`           | 247 | Modal Phân quyền nâng cao → BHS giữ đủ 5 quyền; TM cha `Kế thừa` + quyền **giống BHS**; TM con `Kế thừa` + quyền **giống TM cha** (so cả 5 cột bằng `checkQuyenGiongItemCha`) |
| `taoThuMucOGocBoHoSo248(page, duLieu)`                          | 248 | Tạo 1 thư mục ở gốc BHS, giữ mặc định kế thừa |
| `expectThuMucKeThuaQuyenBoHoSo(page, duLieu, quyen[])`          | 248 | BHS giữ đủ 5 quyền + thư mục `Kế thừa` và quyền **giống BHS** (bản 1 cấp của hàm 247) |
| `ngatKeThuaRoiSuaQuyenThuMuc248(page, duLieu, thaoTac)`         | 248 | Màn Cập nhật thư mục → tab Phân quyền → **Đặt quyền độc lập** → **bớt** 1 chip → **thêm** 1 người → Xác nhận (toast "Thành công") |
| `expectQuyenRiengSauKhiLuu248(page, duLieu, quyen[], thaoTac)`  | 248 | Mở lại Phân quyền nâng cao: thư mục = `Quyền riêng tư`, khối vừa sửa **có** người thêm & **hết** người bớt, 4 khối còn lại giữ nguyên |

---

## 3. Danh sách case của mục 1.4.1

| Mã  | UC   | Mô tả                                 | File                                       | Trạng thái                                                              |
| --- | ---- | ------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------- |
| 153 | UC22 | Hiển thị button Thêm thư mục — vai có quyền ở **BHS** (top menu + menu dòng) | [`1.4.1.153.spec.ts`](./1.4.1.153.spec.ts) | 4 test. Chạy sitdev 2026-07-29: **Thủ thư PASS** (2.8 phút); Admin / Owner `ecm06` / Tạo mới `ecm05` chưa chạy |
| 154 | UC22 | Hiển thị button Thêm thư mục — vai **chỉ có quyền tại 1 thư mục** → chỉ dòng đó có nút | [`1.4.1.154.spec.ts`](./1.4.1.154.spec.ts) | 3 test (Tạo mới `ecm06`, Owner `ecm05`, nhóm `AUTO_GROUP_ECM06`). Chưa chạy |
| 155 | UC22 | **Không** hiển thị — vai không phải TT/admin/Owner/Tạo mới ở BHS → **top menu** ẩn nút Tạo mới | [`1.4.1.155.spec.ts`](./1.4.1.155.spec.ts) | 2 test (VIEW `ecm06`, EDIT `ecm05`). Chưa chạy |
| 156 | UC22 | **Không** hiển thị — vai không có quyền Owner/Tạo mới ở thư mục → **menu dòng** ẩn nút | [`1.4.1.156.spec.ts`](./1.4.1.156.spec.ts) | 2 test (VIEW `ecm06`, EDIT `ecm05`). Chưa chạy |
| 163 | UC22 | Thêm mới thư mục **thành công** khi nhập đủ thông tin bắt buộc (toast + lưu đúng thuộc tính + đúng vị trí + người tạo là Owner) | [`1.4.1.163.spec.ts`](./1.4.1.163.spec.ts) | 4 test (TT, Admin, Owner `ecm06`, Tạo mới `ecm05`). Chưa chạy |

| 166 | UC22 | Giá trị mặc định trường **Index thư mục** (tự sinh `A` → `B` theo thứ tự tạo, không cho sửa) | [`1.4.1.166.spec.ts`](./1.4.1.166.spec.ts) | 2 test (TT, Admin — Index không phụ thuộc vai). Chưa chạy |

| 177 | UC22 | Thay đổi trường **Thư mục/HS cấp cha** (2 thư mục cha mẫu khác Độ mật/Trạng thái/quyền) → Độ mật / Trạng thái / cả 5 khối quyền tự cập nhật theo cha đang chọn | [`1.4.1.177.spec.ts`](./1.4.1.177.spec.ts) | 3 test (TT, Admin, Owner ở BHS `ecm06`). Viết lại 2026-07-30 theo yêu cầu người dùng. Chưa chạy |
| 178 | UC22 | Kiểm tra chức năng **tìm kiếm** trường Thư mục/HS cấp cha — gõ tên thư mục 1 rồi thư mục 2, mỗi lần đều ra đúng kết quả | [`1.4.1.178.spec.ts`](./1.4.1.178.spec.ts) | 2 test (TT, Admin). Viết 2026-07-30. Chưa chạy |
| 182 | UC22 | Giá trị mặc định trường **Độ mật** (modal Thêm thư mục) đổi theo Độ mật của BHS; Độ mật thư mục con bị giới hạn theo thư mục cha (**con ≥ cha**) | [`1.4.1.182.spec.ts`](./1.4.1.182.spec.ts) | 1 test (chỉ Admin — theo yêu cầu người dùng, Độ mật không phụ thuộc vai). Viết 2026-07-30. Chưa chạy |
| 188 | UC22 | Giá trị mặc định trường **Trạng thái hiển thị** đổi theo BHS; trong item `Private` không tạo được item con `Public` (**con ≥ cha**, Public < Private) | [`1.4.1.188.spec.ts`](./1.4.1.188.spec.ts) | 1 test (chỉ Admin). Dùng chung cơ chế với 182 qua `expectRangBuocThuMucCon`. Viết 2026-08-01. Chưa chạy |
| 247 | UC22 | **Kế thừa quyền từ cấp cha**: BHS phân đủ 5 quyền cho 5 account → thư mục ở gốc BHS và thư mục con của nó đều `Kế thừa` và có **đúng 5 quyền giống cấp cha** | [`1.4.1.247.spec.ts`](./1.4.1.247.spec.ts) | 2 test (TT, Admin). Viết 2026-08-01. Chưa chạy |
| 248 | UC22 | **Ngắt kế thừa**: BHS phân đủ 5 quyền (có 1 **nhóm**) → thư mục kế thừa đúng 5 quyền → "Đặt quyền độc lập" → bớt 1 người, thêm 1 người, **lưu thành công** và thay đổi được ghi nhận (`Quyền riêng tư`) | [`1.4.1.248.spec.ts`](./1.4.1.248.spec.ts) | 2 test (TT, Admin). Viết 2026-08-01. Chưa chạy |
| 249 | UC22 | **2 người cùng tạo mới thư mục tại cùng một thời điểm** (tên khác nhau): 2 trình duyệt chuẩn bị đủ dữ liệu rồi bấm Xác nhận **cùng lúc** → tạo được **cả 2** thư mục, không xét chỉ mục | [`1.4.1.249.spec.ts`](./1.4.1.249.spec.ts) | 1 test — Thủ thư + Admin **trong cùng 1 test** (mỗi vai 1 trình duyệt). Toàn bộ bước viết thẳng trong spec. Viết 2026-08-01. Chưa chạy |
| 250 | UC22 | **2 người cùng tạo mới thư mục tại cùng một thời điểm — CÙNG tên**: 2 trình duyệt chuẩn bị đủ dữ liệu (cùng 1 tên) rồi bấm Xác nhận **cùng lúc** → **chỉ tạo được 1** thư mục, phiên còn lại báo `"Tên thư mục đã tồn tại. Vui lòng kiểm tra lại"` | [`1.4.1.250.spec.ts`](./1.4.1.250.spec.ts) | 1 test — Thủ thư + Admin **trong cùng 1 test**. Không giả định phiên nào thắng. Viết 2026-08-01. Chưa chạy |

> Viết case mới → thêm 1 dòng vào bảng này.
> 153 ↔ 155 là cặp đối xứng (top menu); 154 ↔ 156 là cặp đối xứng (menu mở rộng của dòng);
> 249 ↔ 250 là cặp đối xứng (2 người tạo thư mục đồng thời: tên khác nhau ↔ cùng tên).

---

## 4. Điểm cần QA/DEV chốt (phát hiện khi viết case)

1. **Case 153** — QA ghi dropdown "Tạo mới" có **2 button con** (Thư mục, Tài liệu), nhưng khảo sát
   thực tế có **3 mục**: thêm `mni-tao-ho-so-lien-quan` ("Tạo hồ sơ liên quan").
   Script đang assert "có đủ 2 mục QA yêu cầu", **không** assert "đúng 2 mục".
2. **Case 154** — QA không nói vai có quyền gì ở BHS. Script cấp thêm **Quyền Xem ở BHS** để vai
   thấy được cả dòng "không có quyền" (nếu không, bảng chỉ hiện đúng nhánh vai có quyền). Cần QA
   xác nhận cách hiểu này.
3. **Case 155 / 156, test EDIT** — vai **ADD / EDIT / DOWNLOAD cấp BHS chưa được khảo sát**
   (`KHO-TAI-LIEU.PHAN-QUYEN-THEO-VAI.md` mục 5) nên chưa rõ app có ẩn nút hay không. Script assert
   theo đúng mong muốn QA ("không hiển thị"); chạy thật mà nút vẫn hiện → là **kết quả test**.
4. Hành vi **menu mở rộng của dòng** với vai không phải Owner BHS cũng **chưa khảo sát** — case 154
   và 156 chính là phép đo. Chạy xong nên đưa kết quả vào `KHO-TAI-LIEU.PHAN-QUYEN-THEO-VAI.md`.
5. **Case 166** — modal "Thêm thư mục" **không có field riêng tên "Index"**; mã prefix tự sinh nằm
   trong ô **"Tên"** (input `disabled` trong `div#tenTaiLieu`). Script hiểu "trường Index thư mục" =
   input đó. Nếu QA đang nói về field khác thì cần chỉnh locator (`getIndexThuMuc`).
   Chưa phủ index của **thư mục con** (dạng `A.1`) vì định dạng đó chưa khảo sát ở modal này.
6. **Case 177** — trường cấp cha là `tree-sel-thu-muc-cha` (tree-select); đổi cấp cha thì app cập
   nhật **Độ mật** + **Trạng thái hiển thị** + khối phân quyền theo cha (chi tiết ở
   `KHO-TAI-LIEU.MODAL-TAO-THU-MUC.md` mục 3b) — hành vi này **đã khảo sát bằng MCP 2026-07-30**
   khi thư mục còn ở chế độ **Kế thừa**. Bản viết lại 2026-07-30 (theo yêu cầu người dùng) đổi
   sang: thư mục thứ 3 **Đặt quyền độc lập (quyền riêng) TRƯỚC** rồi mới đổi cấp cha, và dùng
   **dữ liệu riêng cho case này** (`dungDuLieu177` trong `1.4.1.setup.ts`, không dùng chung
   `dungDuLieuBoHoSo`/`taoThuMucCoQuyen`) — 2 thư mục cha mẫu, mỗi cái Độ mật/Trạng thái riêng +
   1 account gán vào **tất cả 5 trường quyền**.
   ⚠️ **Chưa khảo sát bằng MCP** tổ hợp "đã Đặt quyền độc lập RỒI mới đổi cấp cha" — nếu chạy thật
   cho kết quả khác với kỳ vọng (quyền độc lập bị cấp cha ghi đè), đó là **kết quả test** cần báo
   QA/DEV, không phải lỗi script.
7. **Case 178** — QA/người dùng chỉ yêu cầu "kết quả tìm kiếm hiển thị đúng", không nói rõ có phải
   **loại bỏ hết** kết quả không khớp hay không. Script hiện chỉ assert "gõ tên thư mục X thì thấy
   đúng option thư mục X trong dropdown" (dùng lại cách xác định `.ant-select-tree-title` đã khảo
   sát ở case 177), **không** assert "chỉ còn đúng 1 kết quả" — vì 2 thư mục mẫu dùng chung tiền
   tố `AT-UC22-178-...` nên gõ tên thư mục 1 có thể vẫn thấy thư mục 2 trong danh sách. Muốn siết
   chặt hơn cần khảo sát thêm bằng MCP xem app lọc kiểu gì (khớp chuỗi con toàn bộ tên, hay theo
   từng từ...). PW đã có sẵn `checkSearchTreeDropdownHasOption(testId, keyword)` theo đúng cách
   hiểu lỏng này nhưng **chưa từng được dùng/kiểm chứng** ở case nào trước đó.
8. **Lấy tên hiển thị của user** (không phải điểm cần chốt, mà là quy ước): dùng
   `layTenHienThiNguoiDangDangNhap(page)` — hover avatar `[data-testid="avatar-container"]` ở màn
   danh sách → đọc dropdown thông tin user. **Không hard-code tên vào code** vì tên user có thể bị
   đổi trong lúc test. Nhờ vậy case 163 không phụ thuộc bảng `TEN_HIEN_THI` và chạy được với mọi
   account (kể cả `ecm09` — account chưa có trong bảng đó).
9. **Case 182** — **chưa khảo sát MCP, chưa chạy**. Quy tắc Độ mật (người dùng chốt 2026-07-30):
   **con phải ≥ cha** (Thường < Mật < Tuyệt mật) — encode ở `DO_MAT` / `doMatChoPhepTrong`
   trong `1.4.1.steps.ts`, sửa quy tắc thì sửa đúng chỗ đó. Điểm cần chốt:
   - Toast khi **sửa** BHS (`btn-save` ở header) chưa khảo sát — tài liệu mới xác nhận toast cho
     luồng **tạo mới** (`KHO-TAI-LIEU.md` mục 9.8). Script giả định dùng chung cơ chế.
   - Ràng buộc Độ mật cha–con chưa khảo sát: chưa rõ app chặn ở đâu, hay chưa chặn. Tiêu chí fail
     là **lúc lưu** (lưu được mức lỏng hơn cha = lỗi); dropdown có lọc bớt hay không không tính.
   - Chỉ 1 test (Admin) thay vì cặp Thủ thư + Admin mặc định — Độ mật không phụ thuộc vai.
10. **Case 188** — **chưa khảo sát MCP, chưa chạy**. Cùng cơ chế với 182, chỉ khác trường
    (`sel-trangThai` ở BHS / `sel-trang-thai-hien-thi` ở modal TM) và thang mức **Public < Private**.
    Ràng buộc "trong `Private` không được có `Public`" chưa khảo sát — tiêu chí fail là **lúc lưu**.
    Bước 4 dùng cấp cha là **gốc BHS** (`opts.tenCha` bỏ trống).
11. **Case 247** — **chưa khảo sát MCP, chưa chạy**. Ghi chú khi viết:
    - `1.4.1.247.spec.ts` trước đó là **bản copy nhầm của case 188** (2 file giống hệt nhau) →
      đã ghi đè bằng nội dung thật của 247.
    - Tên thư mục dùng hậu tố `-TM-CHA` / `-TM-CON`: các hàm tìm dòng theo tên
      (`getCauTrucRow`, `getCodeCuaItemPhanQuyen`) khớp theo **chuỗi con**, nên tên cha **không
      được là tiền tố** của tên con (kiểu `-TM` / `-TM-CON` sẽ khớp nhầm dòng).
    - Cột "Quyền Owner" của BHS có **cả người tạo BHS** lẫn `ecm04`; assert từng account chỉ kiểm
      **có mặt**, còn phép so khớp tuyệt đối là **con với cha** (`checkQuyenGiongItemCha`).
    - Mỗi test mở thêm **5 phiên browser** chỉ để lấy tên hiển thị của `ecm04`…`ecm08` (quy ước
      không hard-code tên — `tests/README.md` mục 2). Nếu chạy quá 15 phút (timeout config) thì
      cắt bớt số vai chạy hoặc chuyển sang bảng `TEN_HIEN_THI`.
12. **Case 248** — **chưa khảo sát MCP, chưa chạy**. Cách hiểu đã chốt khi viết:
    - "Ngắt kế thừa" của thư mục = nút **"Đặt quyền độc lập"** ở tab Phân quyền của màn **Cập nhật
      thư mục**. Không dùng `ngatKeThua` của modal Phân quyền nâng cao vì ở đó chỉ **thêm** được
      quyền, **không bớt** được người (`MODAL-PHAN-QUYEN-NANG-CAO.md` mục 6).
    - Thêm/bớt làm trên **cùng khối "Quyền tải file"** — khối này chỉ có đúng 1 người (`ecm07`) nên
      xoá **chip đầu tiên** chắc chắn đúng người; chip **không mang tên** trong DOM nên không có
      cách xoá theo tên. Hàm assert `số chip = 1` trước khi xoá để hỏng dữ liệu là fail rõ ràng.
    - Điều **chưa khảo sát**: app có cho **xoá chip kế thừa** rồi lưu hay không. Tài liệu mới xác
      nhận chip kế thừa vẫn còn sau khi đặt quyền độc lập (`MODAL-TAO-THU-MUC.md` mục 4b) — chạy
      thật bị chặn thì đó là **kết quả test**, báo QA/DEV.
    - Quyền Cập nhật cấp cho **nhóm** `AUTO_GROUP_ECM06`: nhóm không đăng nhập được nên
      `layTenDoiTuongPhanQuyen` trả về chính tên nhóm — mỗi test chỉ mở thêm **4** phiên browser.
    - Dữ liệu dùng chung với 247 qua `dungDuLieuPhan5Quyen` (đổi tên từ `dungDuLieu247`); 248 chỉ
      dùng `tenThuMucCha`, bỏ `tenThuMucCon`.
13. **Case 249** — **chưa khảo sát MCP, chưa chạy**. Cách hiểu & giả định đã chốt khi viết:
    - Theo yêu cầu người dùng, **không** tách hàm sang `1.4.1.setup.ts` / `1.4.1.steps.ts`: kịch bản
      2 phiên song song là đặc thù của case này, không case nào khác dùng lại.
    - "Cùng một thời điểm" = `Promise.all` trên 2 phiên browser → 2 lệnh click phát đi trong **cùng
      1 tick**, không chờ nhau. Không thể đảm bảo 2 request tới server trùng đúng mili-giây — đây là
      mức đồng thời tối đa Playwright cho phép trong 1 test.
    - **1 test** thay vì cặp Thủ thư + Admin: 2 vai này chính là **2 người tham gia** trong cùng test.
      Muốn thử "nhiều người" hơn thì thêm phần tử vào mảng `nguoiThamGia` của `runTest`.
    - Người thứ hai (Admin `ecm09`) được cấp **Owner ở BHS** lúc tạo + BHS `chuyenHoatDong` — vai
      Admin **chưa được khảo sát** (`KHO-TAI-LIEU.PHAN-QUYEN-THEO-VAI.md` mục 5) nên không dựa vào
      giả định "Admin mặc định thấy mọi BHS".
    - **Không assert chỉ mục** (mã prefix `A`/`B`) của 2 thư mục — người dùng đã chốt là không quan
      trọng. Hệ quả: nếu app sinh **trùng chỉ mục** cho 2 thư mục thì case này vẫn PASS; muốn bắt
      lỗi đó phải bổ sung assert "2 mã prefix khác nhau".
    - Assert "bảng có **đúng 2 dòng**" dựa vào việc BHS tạo bằng loại `AUTO-TEST-TYPE` không có cấu
      trúc thư mục mẫu (`KHO-TAI-LIEU.md` mục 3e). Nếu đổi loại hồ sơ trong `createBoHoSo` thì phải
      xem lại assert này.
14. **Case 250** — **chưa khảo sát MCP, chưa chạy**. Cùng cơ chế đồng thời với 249 (viết thẳng
    trong spec, `Promise.all`), khác ở chỗ 2 phiên dùng **cùng 1 tên thư mục**. Điểm cần chốt:
    - 🚨 **Thông báo `"Tên thư mục đã tồn tại. Vui lòng kiểm tra lại"` chưa có trong tài liệu màn
      hình** — chưa rõ app render ở toast `.ant-message-notice` hay lỗi inline
      `.ant-form-item-explain-error` trong modal. Spec dò bằng `page.getByText(...)` (khớp chuỗi
      con, bắt được cả 2 chỗ). Khảo sát xong bằng `/khao-sat-man-hinh` thì siết lại selector và bổ
      sung thông báo vào `KHO-TAI-LIEU.MODAL-TAO-THU-MUC.md`.
    - **Không giả định phiên nào thắng**: mỗi phiên "đua" 2 tín hiệu (toast "Thành công" ⟷ thông
      báo trùng tên) — quan sát được mở **trước** khi click vì toast chỉ sống vài giây. Assert:
      đúng **1** phiên thành công + các phiên còn lại báo **đúng nguyên văn** thông báo QA yêu cầu.
    - Cả 2 khả năng sai đều fail rõ ràng: app cho tạo **2 thư mục trùng tên** (0 phiên báo lỗi) hay
      app chặn **cả 2** (0 phiên thành công) — thông điệp lỗi in ra kết quả từng phiên.
    - Assert "bảng có **đúng 1 dòng**" dựa vào BHS loại `AUTO-TEST-TYPE` không có cấu trúc thư mục
      mẫu (giống 249).
15. **`moLaiBoHoSo: false`** (thêm 2026-07-30): bỏ bước mở lại BHS giữa các thao tác tạo thư mục
    liên tiếp, tiết kiệm ~37 s/lần. **Chưa chạy kiểm chứng** — lỗi kiểu "không thấy dòng / không mở
    được menu" ở thao tác liên tiếp thì bỏ option này là về hành vi cũ. Các case khác dùng mặc định
    nên không bị ảnh hưởng.
