Files
solution-erp/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/sub-reviewer-diff-dot5.md
2026-07-29 14:53:58 +07:00

22 KiB
Raw Blame History

sub-reviewer — soi đối kháng diff đợt-5 (S159 L8)

Vật soi: git diff working-tree vs HEAD (bbc39be), 9 file trong scope (BE 4 + FE 5). Chế độ: tĩnh (không live-verify, theo lệnh lead). Ghi từng FLAG NGAY khi chốt (chống #53).


Bản đồ file trong scope (đo từ git diff --stat)

File +/-
src/Backend/SolutionErp.Api/Controllers/ContractsController.cs +12
src/Backend/SolutionErp.Application/Contracts/ContractFeatures.cs +55
src/Backend/SolutionErp.Application/PurchaseEvaluations/PeSoftDeleteFeatures.cs +1/-1 (comment)
src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs +33/-3
fe-user/src/App.tsx +2
fe-user/src/components/Layout.tsx +15/-3
fe-user/src/pages/UserDashboardPage.tsx +18/-339
fe-user/src/pages/contracts/MyContractsPage.tsx +32/-7
fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx +4/-1

Ngoài scope lead khai (KHÔNG soi, chỉ ghi nhận có mặt trong working-tree): .claude/agent-memory/cicd-monitor/MEMORY.md, .claude/commands/session-{start,end}.md, .claude/governance/.session-counter.json, docs/HANDOFF.md, docs/STATUS.md, scripts/governance-detectors.ps1, scripts/session_ctx.py.


FLAG (ghi dần)

FLAG-1 [M] — fe-admin thiếu 2 route ⇒ 2 lá menu mới rơi IM LẶNG (gotcha #50)

File: fe-admin/src/components/Layout.tsx:31-44 (staticMap) Đo: staticMap fe-admin có Khkk_List / Khkk_Create / Khkk_Pending / Khkk_Approved nhưng KHÔNG có Khkk_WfView, Khkk_Deleted — trong khi seeder (DbInitializer.cs:1780-1786) đã thêm 2 key này vào cây menu dùng CHUNG cho cả 2 app. MenuLeaf của fe-admin: resolvePath trả nullreturn null ⇒ lá biến mất, KHÔNG có cảnh báo. Đúng lớp bẫy mà chính comment Layout.tsx:33 cảnh báo ("thiếu route = sidebar drop SILENT"). Hệ quả: nhóm "1. Kế hoạch ký kết HĐ (NCC-TP)" bên fe-admin hiện 4/6 lá; lệch với fe-user 6/6. Vi phạm docs/rules.md §3.9 (mirror 2 app). Không phải lỗi cho Ct_*_WfView/Approved/Deleted: fe-admin ẩn TOÀN BỘ Ct_* (isAdminHidden :176-178 key.startsWith('Ct_')) ⇒ 21 lá HĐ mới không cần route bên admin. Đã kiểm, CLEAN. Tiêu chí nhận: thêm 2 dòng Khkk_WfView + Khkk_Deleted vào staticMap fe-admin (trỏ /coming-soon?stage=2&m=... như fe-user), rồi grep -c "Khkk_" ở 2 app phải BẰNG NHAU (=6).

FLAG-2 [M] — Màn "Đã xóa" HĐ: bấm vào dòng ⇒ 2 panel TRẮNG (không thông báo), khác hẳn khuôn PE

File: fe-user/src/pages/contracts/MyContractsPage.tsx:163-190 (onClick={() => selectContract(c.id)}) + :52-56 (detail query) + :196-222 (Panel 2/3) Đo (tĩnh, 2 vế):

  • Khuôn PE (S155) CHẶN hẳn: fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx:524-527disabled={deletedView} + cursor-default.
  • Bản HĐ đợt-5 không có nhánh showDeleted ở nút dòng ⇒ vẫn selectContract(id) → set ?id=detail query GET /contracts/{id}.
  • Contract CÓ global query filter !IsDeleted (ContractConfiguration.cs:44) và endpoint chi tiết KHÔNG dùng IgnoreQueryFilters ⇒ HĐ đã xóa 404 NotFound.
  • Render: Panel 2 chỉ có 3 nhánh !selectedId / isLoading / detail.data — nhánh isError KHÔNG có ⇒ lỗi 404 rơi vào khoảng trống ⇒ panel trắng trơn, người dùng không biết vì sao. Panel 3 (:221) cũng trắng.
  • Trên mobile (< lg) còn nặng hơn: selectContract (:75-81) navigate('/contracts/{id}')rời khỏi màn sang trang chi tiết 404. Tiêu chí nhận: hoặc disabled={showDeleted} cho nút dòng (rẻ nhất, đúng khuôn PE), hoặc thêm nhánh detail.isError hiển thị "Phiếu đã xóa — không xem được chi tiết". Nếu chọn cách 2 phải chặn thêm nhánh mobile-navigate.

FLAG-3 [M] — Chú thích khai "Hộp thư viết HOA" nhưng mã KHÔNG chạm "Hộp thư"

File: fe-user/src/components/Layout.tsx:354-357 (chú thích + nhánh depth === 0) vs :385-408 (StaticLeaf) Đo: "Hộp thư" KHÔNG render bằng MenuLeaf — nó là USER_FIXED_TOP (:376-378) render qua StaticLeaf (:385), lớp CSS hard-code px-3 py-1.5 text-[12px] font-mediumkhônguppercase. Sửa MenuLeaf không với tới nó. Hệ quả: ngay đầu sidebar sẽ là "Hộp thư" (thường, 12px) nằm cạnh "TỔNG QUAN QUY TRÌNH" (HOA, 11px, semibold) — đúng thứ owner muốn đồng bộ thì lại lệch; chú thích trong mã mô tả sai việc mã làm (bẫy cho người đọc sau). Ghi nhận CLEAN kèm theo: nhánh 3 ngả depth===0 ? … : isDeep ? … : … không phá lá sâu — depth 1 vẫn ăn nhánh cũ, depth ≥2 vẫn isDeep; khối isActive nằm NGOÀI ternary nên style active nguyên vẹn (trục 6 PASS phần này). Tiêu chí nhận: hoặc bê cùng bộ lớp sang StaticLeaf, hoặc sửa chú thích bỏ chữ "Hộp thư" (khai đúng phạm vi).

FLAG-4 [L] — "Đã duyệt" của HĐ lọc phase=9 (Đã phát hành), bỏ sót HĐ đã được duyệt nhưng chưa phát hành

File: fe-user/src/components/Layout.tsx:141 (?type=N&phase=9) + MyContractsPage.tsx:117-121 (chip "Đã phát hành") Đo: ContractPhase 9 = DaPhatHanh. HĐ ở phase 8 DangDongDau đã qua chữ ký BOD (DangTrinhKy → DangDongDau do Director/AuthorizedSigner) ⇒ về nghiệp vụ đã "được duyệt" nhưng KHÔNG lọt vào màn "Đã duyệt". Param phase của BE là so-khớp BẰNG (ContractFeatures.cs:305), không diễn tả được >= 8. Chính FE cũng lệch tên: nhãn menu "Đã duyệt" ↔ chip trên trang "Đã phát hành". Tiêu chí nhận: owner chốt định nghĩa; nếu giữ 9 thì đổi nhãn lá thành "Đã phát hành" cho khớp chip, nếu muốn "đã duyệt" thì cần BE nhận phaseFrom/danh sách phase.

FLAG-5 [L] — Ở ?type=N&phase=9, lá "Danh sách" SÁNG cùng lúc với "Đã duyệt"

File: fe-user/src/components/Layout.tsx:304-329 (TRANSIENT_QUERY_KEYS + queryMatches) Đo (chạy tay luật ở :322-328): URL hiện tại type=1&phase=9, đích "Danh sách" = type=1pinnedByTarget={type}phase là transient & không được đích ghim ⇒ bị bỏ quaaKeys=[type] = bKeys=[type] ⇒ MATCH. Đích "Đã duyệt" = type=1&phase=9 ⇒ cũng MATCH. ⇒ 2 lá cùng sáng. Bản vá S155 chỉ đóng chiều ngược (ở ?type=1 không cho "Đã duyệt" sáng). Là tật kế thừa từ PE, không phải do đợt-5 gây ra — nhưng đợt-5 nhân nó ra 7 nhóm HĐ nên ghi để owner biết. Không dính lá "Đã xóa": deleted không nằm trong TRANSIENT_QUERY_KEYS ⇒ so khớp nghiêm, phân biệt đúng. Đã kiểm. Tiêu chí nhận: (tùy chọn) đối xứng hóa: nếu URL có key mà MỘT đích anh-em ghim thì đích không-ghim phải trượt.

FLAG-6 [L] — Endpoint mới siết Contracts.Read trong khi list sống không siết ⇒ mở đúng hình bẫy #44

File: src/Backend/SolutionErp.Api/Controllers/ContractsController.cs:26-35 vs :16-24 Đo: MenuPermissionHandler.cs:40-49 so khớp ĐÚNG KHÓA MenuKey == "Contracts", không kế thừa cha-con (kế thừa chỉ có ở tầng hiển thị GetMyMenuTreeQuery, gotcha #35). ⇒ vai nào được cấp lẻ Ct_X_Deletedkhông có dòng Contracts CanRead=1 sẽ thấy lá menu nhưng ăn 403 câm (TanStack nuốt lỗi → isError → trang hiện "Không có HĐ"/trắng). Rủi ro hiện thấp vì: (a) Contracts CÓ trong MenuKeys.All (MenuKeys.cs:168) ⇒ policy Contracts.Read tồn tại thật — trục 1 câu hỏi này PASS; (b) SeedAllRolesReviewReadPermissionsAsync (DbInitializer.cs:2157-2214) đã đưa Contracts vào bộ cấp-cho-mọi-vai; (c) PE S155 dùng y hệt khuôn này và đã chạy thật. Nhưng: nhánh cấp cho khóa thường là bỏ-qua-nếu-đã-có (chú thích :2211-2213 tự khai), nên trên prod dòng Contracts CanRead=0 sẽ KHÔNG được nâng bởi seeder. Đây là rủi ro triển khai, không phải lỗi mã. Tiêu chí nhận (rẻ): 1 câu SELECT r.Name, p.CanRead FROM Permissions p JOIN Roles r ON r.Id=p.RoleId WHERE p.MenuKey='Contracts' trên prod — mọi vai đang dùng phải CanRead=1; hoặc curl /api/contracts/deleted bằng nv.test@ kỳ vọng 200.

FLAG-7 [L] — Khóa menu mới viết chuỗi trần, không đi qua MenuKeys factory (lệch chuẩn PE)

File: DbInitializer.cs:1871,1875,1876 + :2163,2167,2168 + :2179-2180 ("Ct_" + code + "_WfView" …) Đo: PE khi thêm đúng 2 mục tương ứng (S155) đã bổ sung factory MenuKeys.PurchaseEvaluationApproved/Deleted + PurchaseEvaluationWorkflowView (MenuKeys.cs:140-156). Bản HĐ không thêm ContractTypeWfView/Approved/Deleted, mà nối chuỗi tay ở 2 chỗ khác nhau (cây menu + danh sách cấp quyền). MenuKeys.cs:3 tự nhận là "nguồn duy nhất". Hệ quả: gõ sai 1 ký tự ở 1 trong 2 chỗ = lệch câm, trình biên dịch không bắt. Tiêu chí nhận: thêm 3 factory vào MenuKeys.cs (KHÔNG đưa vào MenuKeys.All — giữ số canonical Menu keys/Policies không trôi, đúng như ghi chú PE MenuKeys.cs:145-148), rồi thay 8 chỗ nối chuỗi.

FLAG-8 [L] — Danh sách "Đã xóa" sắp theo DeletedAt nhưng không trả về cột nào cho người dùng thấy mốc xóa

File: ContractFeatures.cs:387 (OrderByDescending(x => x.c.DeletedAt)) + :391-397 (DTO) Đo: ContractListItemDto không có deletedAt (trường thời gian duy nhất là CreatedAt). Người dùng thấy thứ tự "mới xóa trước" nhưng trên dòng chỉ có ngày tạo ⇒ trông như sắp lộn xộn. PE gặp đúng vấn đề này và đã xử bằng cách sắp theo UpdatedAt và hiển thị nó (PurchaseEvaluationsListPage.tsx:557-562). DeletedAt chắc chắn có số liệu: DeleteContractCommandHandler (ContractFeatures.cs:632) gọi .Remove()AuditingInterceptor.cs:54-63 đặt IsDeleted/DeletedAt/DeletedBy. Nên đây là vấn đề trình bày, không phải sai dữ liệu. Tiêu chí nhận: hiện deletedAt (thêm trường vào DTO hoặc mượn updatedAt như PE), hoặc đổi sang sắp theo trường đang hiển thị.

FLAG-9 [L] — Chế độ "Đã duyệt": lọc loại HĐ làm ở phía trình duyệt trên 100 dòng đầu ⇒ có thể ra rỗng oan

File: MyContractsPage.tsx:41-49 (chỉ gửi phase, KHÔNG gửi type) + :58-60 (items.filter(c => c.type === typeFilter)) Đo: ở chế độ không-xóa, type cố ý không gửi lên máy chủ (giữ nguyên nếp cũ), nhưng nay cộng thêm phase=9: máy chủ trả 100 HĐ phase 9 của mọi loại, rồi trình duyệt mới lọc loại. Nếu số HĐ đã phát hành > 100 thì lá "Đã duyệt" của một loại có thể hiện 0 dù thực tế có. Chế độ "Đã xóa" thì ngược lại — có gửi type lên máy chủ (:45), nên không dính. Lệch nhau giữa 2 nhánh trong cùng một hàm. Tiêu chí nhận: gửi type lên máy chủ cho cả 2 nhánh (BE ListContractsQuery hiện chưa có tham số type — cần thêm), hoặc chấp nhận và ghi rõ giới hạn 100.


Ghi nhận PASS (đã kiểm, không thành FLAG)

  • Trục 1 — Where(IsDeleted): ContractFeatures.cs:369-370.IgnoreQueryFilters().Where(x => x.IsDeleted) — đúng bài 8a S155 (Ignore GỠ chứ không ĐẢO). CLEAN.
  • Trục 1 — IDOR: ContractFeatures.cs:375-380 sao đúng khuôn list sống :298-303 (DrafterUserId == userId || eligiblePhases.Contains(Phase)), gọi lại chính ListContractsQueryHandler.GetEligiblePhases (internal static, cùng assembly — biên dịch được). Không nới rộng hơn list sống. CLEAN.
  • Trục 1 — policy có thật: ContractsMenuKeys.All (MenuKeys.cs:168) ⇒ Contracts.Read được đăng ký (|All| × |Actions|). Không phải tên policy ma. CLEAN.
  • Trục 1 — Contract CÓ query filter: ContractConfiguration.cs:44 HasQueryFilter(x => !x.IsDeleted) ⇒ list sống không rò HĐ đã xóa; endpoint mới là đường DUY NHẤT thấy chúng. CLEAN.
  • Trục 2 — định dạng khóa khớp lưới FE: seeder sinh Ct_<Code>_{WfView,List,Create,Pending,Approved,Deleted}; lưới FE Layout.tsx:130 /^Ct_([^_]+)_(List|Create|Pending|WfView|Approved|Deleted)$/. 7 mã loại (ThauPhuNguyenTacDv) không chứa dấu gạch dưới[^_]+ bắt trọn. 6/6 nhánh có nhánh trả đường dẫn (:136-142). CLEAN.
  • Trục 2 — order++ chèn giữa không rối nhóm khác: biến order (DbInitializer.cs:1860) chỉ phục vụ nhóm Ct_*; Order chỉ so trong cùng cấp anh em; nhóm loại HĐ nay cách nhau 7 thay vì 4 nhưng thứ tự tương đối giữ nguyên; Hdc_* dùng biến riêng hdcOrder, Wf_* dùng wfOrder=95 (cha khác). Upsert chỉ đặt lại Order (:1933-1937), không đụng Label/ParentKey/Icon. CLEAN.
  • Trục 2 — labelBackfill KHÔNG đè tên admin đặt: backfill ghi item.Label (:1999-2001); tên admin đổi qua Mig 27 nằm ở cột khácDisplayLabel (MenuItem.cs:16), và FE ưu tiên displayLabel (Layout.tsx:202-203 effectiveLabel). ⇒ ép Label mỗi lần khởi động là enforce có chủ đíchkhông giẫm lên tuỳ biến của admin. CLEAN (đúng như lead khai).
  • Trục 3 — Khkk_WfView/Khkk_Deleted bên fe-user: đã có trong staticMap (Layout.tsx:78-80). Bên fe-admin thiếu → xem FLAG-1.
  • Trục 3 — trạng thái sáng của 6 lá Khkk: phân biệt nhau nhờ tham số m (m=wfview|list|create|pending|approved|deleted), m không nằm trong danh sách transient ⇒ so khớp nghiêm. CLEAN. (ComingSoonPage.tsx:27-28 chỉ đọc stage, bỏ qua m — đúng ý đồ, m chỉ để tách trạng-thái-sáng.)
  • Trục 4 — nút "Tạo HĐ mới" đã ẩn ở chế độ xóa: MyContractsPage.tsx:122-127 bọc {!showDeleted && …}. Kèm chip "Đã xóa — chỉ xem" (:112-116) và câu rỗng riêng (:158). CLEAN.
  • Trục 5 — cắt hero không để lại tham chiếu mồ côi (kiểm ĐỘC LẬP, 2 phép):
    1. Đếm từng ký hiệu đã gỡ trong file hiện tại: STAGE_TONE, StageCard, StageArrow, StageStatRow, StageLink, StageSoon, MyDashboard, peApproved, hasError, retryFailed, stats, Workflow, ChevronDown, PurchaseEvaluationPhase = 0/0 hết.
    2. npx tsc -p tsconfig.app.json --noEmitEXIT=0, không một dòng lỗi. tsconfig.app.jsonnoUnusedLocals: true + noUnusedParameters: true ⇒ nếu còn import/biến mồ côi thì trình biên dịch đã đỏ. Đây là chứng mạnh hơn grep. ⚠️ Đo đúng phạm vi: tsconfig này không bật strict/strictNullChecks, nên "tsc xanh" ở đây chỉ chứng "không mồ côi + không sai kiểu thô", KHÔNG chứng an toàn null.
  • Trục 5 — phần danh sách còn nguyên: tab 4 giai đoạn (UserDashboardPage.tsx:1030-1034 STAGES.map + active={stage === st.n}), 2 bảng (:536, :735), cột "Hành trình" + JourneyDots (:635, :682, :847), nạp lười theo tab (:974, :986). Dữ liệu soonDesc vẫn có người đọc (:881), tone vẫn dùng (:311) ⇒ không thành rác. CLEAN.
  • Trục 5 — tiêu đề mới: PageHeader title="Tổng quan quy trình" khớp nhãn menu backfill MenuKeys.Dashboard = "Tổng quan quy trình". CLEAN.
  • ?type=3 của trang ma trận: ApprovalWorkflowApplicableType.Contract = 3 có thật (ApprovalWorkflow.cs:56); endpoint GET /api/approval-workflows-v2 chỉ cần đăng nhập, không gắn policy (ApprovalWorkflowsV2Controller.cs:16-26) ⇒ không dính bẫy #44. Trang có sẵn nhánh rỗng lịch sự nếu admin chưa ghim quy trình nào cho loại 3 (WorkflowMatrixViewPage.tsx:99-103). CLEAN. (Còn 1 điều KHÔNG kiểm được bằng đọc mã: prod đã có ApprovalWorkflow nào ApplicableType=3IsUserSelectable=true chưa — nếu chưa, 7 lá "Luồng duyệt" đều hiện khung rỗng "Liên hệ Admin".)
  • Sửa chú thích D3 ở PE: … đúng 2 chỗ … 🔴 TÔI TỰ ĐÍNH CHÍNH — xem FLAG-10. (Tôi suýt duyệt con số này bằng mắt; chỉ khi chạy grep -rn "IgnoreQueryFilters" src/Backend mới lòi ra. Bài học tự ghi: con số trong chú thích cũng là một khẳng định đo được — phải chạy phép đếm, không đọc lướt.) Phần CÒN LẠI của chú thích vẫn đúng và giữ nguyên giá trị: cả 2 handler list-deleted đều kèm .Where(x => x.IsDeleted), và lệnh cấm gộp thành helper dùng chung vẫn nguyên.

FLAG-10 [M] — Chú thích vừa sửa trong chính diff này khai SAI con số, và nó là loại sai tự lan

File: PeSoftDeleteFeatures.cs:184 (dòng ĐƯỢC SỬA trong diff) + ContractFeatures.cs:356 (dòng MỚI trong diff) Khẳng định trong mã: "NAY CÓ 2 chỗ dùng IgnoreQueryFilters" và "Chỗ THỨ HAI trong src/Backend". Phép phản chứng (1 lệnh, chạy thật): grep -rn "IgnoreQueryFilters" src/Backend --include=*.cs ⇒ ngoài 2 chỗ nói trên còn ApprovalWorkflowV2AdminFeatures.cs dùng ở 15 vị trí (:957, 964, 971, 978, 985, 992, 999 — 7 bảng *LevelOpinions; và :1038, 1042, 1046, 1050, 1054, 1058, 1062 — 7 phép dò "phiếu cha còn sống không"). Tổng thực tế ≈ 3 tệp / 17 vị trí, không phải 2. Vì sao không chỉ là lỗi chính tả: câu này được viết dưới dạng luật ("chỗ DUY NHẤT/THỨ HAI ⇒ CẤM gộp helper"). Người đọc sau tin vào con số để kết luận "mình đang sửa chỗ duy nhất còn lại" trong khi thực tế có cả một cụm 15 chỗ ở module khác. Con số sai từ S155 ("DUY NHẤT") đã có cơ hội được sửa trong đúng lượt này, nhưng bị viết lại thành một con số sai mới. Tiêu chí nhận: bỏ hẳn con số đếm ra khỏi chú thích (nêu luật thay vì số: "mọi nơi dùng IgnoreQueryFilters để liệt kê bản ghi đã xoá BẮT BUỘC kèm .Where(x => x.IsDeleted)"), hoặc giữ số nhưng nói rõ phạm vi đếm ("2 chỗ thuộc lớp list-deleted"; các chỗ trong ApprovalWorkflowV2AdminFeatures thuộc lớp khác — dò mồ côi). Kèm 1 lệnh grep trong chú thích để người sau kiểm lại được.

  • Không có dấu vết giả-lập: diff không chứa // Mock / alert( / TODO.*wire / placeholder. Endpoint mới là truy vấn thật, FE gọi thật.
  • Chứng cứ cho FLAG-2 (đã truy tới đĩa): truy vấn chi tiết HĐ nằm ở ContractFeatures.cs:462 (GetContractQuery) → :471 db.Contracts.AsNoTracking()không IgnoreQueryFilters ⇒ HĐ đã xoá chắc chắn rơi vào NotFoundException (404). Không phải suy đoán.

Phạm vi KHÔNG kiểm (khai thật)

  • Không chạy dotnet build / dotnet test. Phần BE chỉ soi tĩnh. Rủi ro biên dịch thấp (chỉ dùng lại internal static cùng assembly, DTO có sẵn) nhưng tôi chưa có chứng.
  • Không kiểm chứng dữ liệu prod: (a) dòng Permissions khoá Contracts của 13 vai (FLAG-6); (b) có ApprovalWorkflow nào ApplicableType=3, IsUserSelectable=1 chưa. Cả hai cần 1 câu sqlcmd hoặc 1 lần curl — lead chốt soi tĩnh nên tôi dừng đúng ranh.
  • Không kiểm 7 tệp ngoài phạm vi lead khai (docs/*, scripts/*, .claude/*) — chúng có mặt trong cây làm việc và sẽ đi cùng lượt commit nếu dùng commit -a.
  • Không kiểm git status cho Migrations/ — đợt này không đụng migration nào (0 tệp trong Migrations/), nên bẫy "migration chưa được git theo dõi" (S155) không áp dụng.
  • Chưa chạy npm run build cho fe-admin — FLAG-1 là thiếu dữ liệu trong bản đồ tuyến, không phải lỗi kiểu, nên trình biên dịch sẽ không bắt được; chạy build cũng không đổi kết luận.

PHÁN QUYẾT

PASS_WITH_FLAGS — 10 FLAG (0 High / 4 Medium / 6 Low).

Không có lỗi nào đủ nặng để chặn commit: đường vào dữ liệu của endpoint mới đúng (có .Where(IsDeleted), có rào IDOR sao đúng khuôn list sống, policy có thật, không rò dữ liệu). Việc cắt hero sạch, có chứng độc lập (tsc EXIT=0 với noUnusedLocals).

Bốn việc nên xử trước khi bàn giao cho người dùng thử (không nhất thiết trước khi commit):

  1. FLAG-2 — bấm dòng ở màn "Đã xóa" cho ra 2 panel trắng; một dòng disabled={showDeleted} là xong, đúng khuôn PE đã có.
  2. FLAG-1 — thêm 2 dòng vào staticMap của fe-admin, nếu không 2 lá menu biến mất im lặng bên app quản trị.
  3. FLAG-10 — con số trong chú thích sai và nó được viết dưới dạng luật, dễ dẫn người sau đi sai.
  4. FLAG-3 — hoặc làm nốt "Hộp thư", hoặc sửa chú thích cho khớp việc mã thực sự làm.

Sáu FLAG mức thấp còn lại là chuyện định nghĩa nghiệp vụ (FLAG-4), tật kế thừa từ PE (FLAG-5), rủi ro triển khai cần một câu truy vấn để yên tâm (FLAG-6), nợ quy ước (FLAG-7), trình bày (FLAG-8) và giới hạn phân trang (FLAG-9).


END — TOTAL: 10 FLAG (0 High / 4 Medium / 6 Low) · VERDICT: PASS_WITH_FLAGS