wal: flush 20260801T1228
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
IMPLBE-K7: IN-PROGRESS
|
||||
IMPLBE-K7: DONE
|
||||
|
||||
# sub-implbe-k7 — Lane BE K7 (chuỗi SP-002 B4, S167 · 2026-08-01)
|
||||
|
||||
@ -18,43 +18,69 @@ IMPLBE-K7: IN-PROGRESS
|
||||
| 3 | `grep "HttpPost" ContractSigningPlansController.cs` | 4 POST: `""` · `{id}/dossier-items` · `{id}/attachments` · `{id}/transitions` — **0 endpoint tạo HĐ** |
|
||||
| 4 | write-site `ContractSigningPlanLine.ContractId` | **0 assignment toàn codebase** (khớp đo độc lập của invest K7.0(b) @S164 **và** K5 FLAG-1 "`lines[].contractId` không tồn tại ở bất cứ đâu") |
|
||||
|
||||
**Cái ĐÃ tồn tại @W6-S161 = "đường ống HĐ V2"** (List/Detail V2-aware `ContractFeatures.cs:306-318` · Inbox V2 `:532-543` · guard trình `ContractWorkflowService.cs:73-92`) — invest K7.0 đã BÁC đúng chỗ này. Lead **nhập nhằng "đường ống đã vá" ⇒ "bridge đã tồn tại"**.
|
||||
**Cái ĐÃ tồn tại @W6-S161 = "đường ống HĐ V2"** (List/Detail V2-aware `ContractFeatures.cs:306-318` · Inbox V2 `:532-543` · guard trình `ContractWorkflowService.cs:73-92`) — invest K7.0 đã BÁC đúng chỗ này. Lead **nhập nhằng "đường ống đã vá" ⇒ "bridge đã tồn tại"** *(lead đã nhận @lượt-2)*.
|
||||
|
||||
### Hệ quả + quyết định lane (khai rõ, không im lặng)
|
||||
|
||||
4/5 vá được giao (**vá-1-endpoint · vá-3 · vá-4a · vá-6**) **VẬT LÝ KHÔNG THỰC HIỆN ĐƯỢC** nếu không có bridge — chúng đều là mệnh đề *về thân bridge*. Đặc biệt **vá-6** ("nạp Project.Code + Supplier.Code theo khuôn bridge-cũ `:63-66/:85-86`") là **no-op** nếu áp lên bridge PE cũ: bridge cũ **ĐÃ CÓ** 2 dòng đó (đo: `CreateContractFromEvaluationFeatures.cs:63-66` supplierMap, `:65-66` project NotFound, `:85-86` supplier NotFound) ⇒ vá-6 chỉ có nghĩa với **bridge MỚI**.
|
||||
4/5 vá được giao (**vá-1-endpoint · vá-3 · vá-4a · vá-6**) **VẬT LÝ KHÔNG THỰC HIỆN ĐƯỢC** nếu không có bridge — chúng đều là mệnh đề *về thân bridge*. Đặc biệt **vá-6** ("nạp Project.Code + Supplier.Code theo khuôn bridge-cũ `:63-66/:85-86`") là **no-op** nếu áp lên bridge PE cũ: bridge cũ **ĐÃ CÓ** 2 dòng đó ⇒ vá-6 chỉ có nghĩa với **bridge MỚI**.
|
||||
|
||||
⇒ **Quyết: DỰNG bridge KHKK→HĐ (BE-only) theo spec đã có sẵn `sub-invest-fable-b2-cum3.md §K7.1` (K7.6 V1+V2) + nhúng thẳng 5 vá vào lúc dựng.** Chệch-khuôn-so-brief KHAI RÕ tại đây, trỏ đề xuất gốc (invest K7.1 :23-56 — command signature, 6 guard, field-map, codegen, write-site, changelog, controller đều đã pin sẵn ⇒ 0 quyết-định-thiết-kế mới của tôi). **KHÔNG làm:** FE (K7.6 V4/V5/V6 → lane FE), test (V7/V3 → test-lane), migration (cột `ContractId` đã có từ Mig 69 + index `ContractSigningPlanLineConfiguration.cs:26`).
|
||||
⇒ **Quyết: DỰNG bridge KHKK→HĐ (BE-only) theo spec sẵn có `sub-invest-fable-b2-cum3.md §K7.1` (= K7.6 V1+V2) + nhúng thẳng 5 vá vào lúc dựng.** Chệch-khuôn-so-brief KHAI RÕ, trỏ đề xuất gốc (invest `:23-56` pin sẵn command signature · 6 guard · field-map · codegen · write-site · changelog · controller ⇒ **0 quyết-định-thiết-kế mới của tôi**). **KHÔNG làm:** FE (V4/V5/V6 → lane FE), test (V3/V7 → test-lane), migration (cột `ContractId` có sẵn từ Mig 69 + index `ContractSigningPlanLineConfiguration.cs:26` ⇒ **0-mig**).
|
||||
|
||||
### Tiền đề phụ thuộc (cụm-1 đã land?) — ĐO
|
||||
### Tiền đề phụ thuộc cụm-1 — ĐO, ĐỦ
|
||||
|
||||
- K2 Mig 71: `ContractSigningPlanLine.TenHangMuc` (`:27`) + `CatalogEntryId` (`:23`) ✅ có trên đĩa.
|
||||
- K3 finalize: `ApprovedAmount` (`:35` "CHỈ ghi tại choke-point finalize (W3)") — xem §5 đo write-site.
|
||||
- K2 Mig 71: `ContractSigningPlanLine.TenHangMuc` (`:27`) + `CatalogEntryId` (`:23`) ✅ trên đĩa.
|
||||
- K3 finalize: write-site `ApprovedAmount` ✅ `ContractSigningPlanWorkflowService.cs:348` (`line.ApprovedAmount ??= line.ProposedAmount`) ⇒ happy-path bridge có số để đọc.
|
||||
|
||||
---
|
||||
|
||||
## §1 — [vá-1a] Grant `Contracts.Create` (seeder, code-first)
|
||||
|
||||
(đang làm — ghi khi xong)
|
||||
- **`DbInitializer.cs:2757-2822`** — hàm MỚI `SeedContractCreateAccessAsync`: cấp `CanRead+CanCreate` trên key root **`Contracts`** cho **Drafter + Procurement + Admin**, UPGRADE-ONLY (row có sẵn → nâng đúng 2 cờ, KHÔNG hạ cờ nào, KHÔNG đụng Update/Delete; row chưa có → tạo). Khuôn sao y `SeedKeHoachKyKetAccessAsync:2672` (W1 S161).
|
||||
- **`DbInitializer.cs:2361`** — call site, đặt **SAU** `RevokeTemporarilyHiddenModulesAsync:2341` (luật "chạy sau revoke để THẮNG", cùng chỗ 2 seeder anh em `:2347`/`:2354`).
|
||||
- **Lỗ đã đóng (đo được, không suy đoán):** `SeedAllRolesReviewReadPermissionsAsync:2516` tạo row key `Contracts` với `CanCreate = isPe` mà `isPe = IsPeKey("Contracts") = false` ⇒ **trước S167 KHÔNG role nào mang `Contracts.Create`**; chỉ Admin lọt nhờ bypass. Không có §1 thì nút bridge 403 với 13/13 role.
|
||||
- **Câu III (ai bấm) = default, chờ ratify:** comment `[K7 — câu III default, chờ anh ratify]` ngay trên mảng `roleNames` — anh chốt khác thì sửa ĐÚNG 1 mảng đó, **máy BE không đổi** (controller giữ nguyên).
|
||||
- ⚠️ **Grant này ăn @RESTART API prod (seeder), KHÔNG phải deploy FE.** CẤM SQL tay (#84 — grant tay bị revoker lật, bài S159 Run #423 mất 447/494 row). Nghiệm thu = restart THẬT rồi đếm row `Permissions WHERE MenuKey='Contracts' AND CanCreate=1` (#75/#76).
|
||||
|
||||
## §2 — [vá-1b] Endpoint bridge = HỘI 2 khóa
|
||||
|
||||
(đang làm)
|
||||
- **`ContractSigningPlansController.cs:230-238`** — `POST /api/contract-signing-plans/{id}/create-contract`, `[Authorize(Policy = "Contracts.Create")]` (action) **∧** class-level `[Authorize(Policy = "KeHoachKyKet.Read")]` `:28` (đã có sẵn từ W2 — đo, không thêm). ASP.NET Core cộng dồn class+action theo **AND** ⇒ thiếu 1 trong 2 vế = **403**. Trả `201 Created` + `{ contractId, maHopDong }`.
|
||||
- **`:262-266`** — body record `CreateContractFromSigningPlanBody(LineIds?, ContractType, ApprovalWorkflowId, TenHopDong?)`; `PlanId` lấy từ **route** (1 nguồn sự thật). `LineIds` nullable ở body để thiếu-field ra **400 validator** ("chọn ít nhất 1 dòng") thay vì 500 NRE.
|
||||
- **`:8`** — `using SolutionErp.Domain.Contracts;` cho `ContractType`.
|
||||
- **Chọn key theo gotcha #85** (đúng-key-của-endpoint): tài nguyên SINH RA là `Contract` ⇒ vế ghi phải là `Contracts.Create`, KHÔNG phải `KeHoachKyKet.Create`. Đối chiếu PE controller per-action: `PurchaseEvaluationsController` **class `[Authorize]` TRẦN + 0 policy per-action** (`:15`, `:339-343`) ⇒ bridge PE cũ chỉ cần đăng-nhập; cửa MỚI này chặt hơn hẳn — **KHÔNG đụng cửa cũ** (`POST /api/contracts` `[Authorize]` trần = #82 treo chủ đích).
|
||||
|
||||
## §3 — [vá-3] Guard workflow `IsActive ∧ IsUserSelectable`
|
||||
## §3 — [vá-3] Guard workflow pin `IsActive ∧ IsUserSelectable`
|
||||
|
||||
(đang làm)
|
||||
- **`CreateContractFromSigningPlanFeatures.cs:132-148`** — "Rào (6)" **4 vế**: (a) tồn tại → `NotFoundException`; (b) `ApplicableType == Contract(3)` → Conflict; (c) `IsActive`; (d) `IsUserSelectable`.
|
||||
- (a)+(b) = khuôn `ContractFeatures.cs:79-87`; **(c)+(d) là phần VÁ** — khuôn gốc thiếu, nên forge POST pin được quy trình đã retired/bỏ ghim (dropdown FE lọc 2 cờ `ContractCreatePage.tsx:334-341` nhưng FE không phải rào).
|
||||
- 2 cờ **throw TÁCH RIÊNG** (không gộp `||`): "đã ngừng hoạt động" và "chưa được ghim" xử ở 2 nút khác nhau trong Designer ⇒ admin phải đọc được nguyên nhân; test-lane cũng phân biệt được 2 nhánh.
|
||||
|
||||
## §4 — [vá-4a] `Line.ContractId` write-site + DTO expose
|
||||
|
||||
(đang làm)
|
||||
- **WRITE-SITE:** `CreateContractFromSigningPlanFeatures.cs:237-238` — `foreach (var l in lines) l.ContractId = contract.Id;` = **assignment ĐẦU TIÊN toàn codebase** (§0 phép đo 4 xác nhận trước đó = 0). Đóng nợ K5 FLAG-1 "chỗ-cắm-1-dòng".
|
||||
- **FALLBACK giữ nguyên chiều:** `:246` `if (pe is not null) pe.ContractId ??= contract.Id;` — `??=` CÓ CHIỀU đúng như đề bài yêu cầu: HĐ đầu của gói thắng ô đơn, HĐ 2..N **không ghi đè** (chúng vẫn thấy được qua `Line.ContractId` — đúng lý do vá-4a tồn tại). Bridge PE cũ gán thẳng `= contracts[0].Id` (`CreateContractFromEvaluationFeatures.cs:145`) vì đã chặn ở `:59-60`; ở cầu MỚI phải `??=` để 2 cầu cùng chạy trên 1 PE không cướp ô của nhau.
|
||||
- **DTO — trả lời chính xác câu "đã có hay chưa" (2 DTO KHÁC NHAU, đừng gộp):**
|
||||
1. 🟢 **`ContractSigningPlanLineDto.ContractId`** (chi tiết phiếu) = **ĐÃ CÓ SẴN** từ W2-S161: khai báo `ContractSigningPlanFeatures.cs:74`, projection `:657`. ⇒ **lane FE K7 KHỎI CHỜ** — dialog "Đưa vào Hợp đồng" đọc `detail.lines[].contractId` để render badge "Đã có HĐ" + disable là **dùng được NGAY**, tôi không phải thêm gì.
|
||||
2. 🆕 **`ContractSigningPlanListItemDto.ContractIds` (`List<Guid>`)** = **THÊM MỚI** — khai báo `:65-75` (append-CUỐI đúng convention file), projection `:836-842` (đặt trong `ProjectPagedAsync` = **1 site dùng chung 3 endpoint** list/inbox/deleted). Lý do phải có: cây 4-folder `usePipelineStages.ts:287-291` dựng GĐ3 từ payload **LIST** và hôm nay chỉ có 1 nguồn `pe.contractId` đơn ⇒ gói N-HĐ thì HĐ thứ 2+ vô hình. Trả mảng **PHẲNG** (consumer là `Set<string>`), KHÔNG `Distinct()` trong projection (N dòng gộp 1 HĐ ra ID trùng — vô hại, đỡ 1 tầng dịch SQL).
|
||||
|
||||
## §5 — [vá-6] Project.Code/Supplier.Code + SlaDeadline + WorkflowDefinitionId
|
||||
|
||||
(đang làm)
|
||||
- **`:150-157`** — nạp `Project` + `Supplier` ĐỦ (không `Select` mỏng) + **2 guard `NotFoundException`** theo khuôn bridge cũ `:63-66/:85-86`; 2 con trỏ này là **loose-Guid không FK vật lý** ⇒ thiếu guard = NRE ngay tại `GenerateAsync`.
|
||||
- **`:212`** — `contract.MaHopDong = await codeGenerator.GenerateAsync(contract, project.Code, supplier.Code, ct)` gọi **TRƯỚC `db.Contracts.Add`** (bài S88: codegen tự-commit SERIALIZABLE + SaveChanges nội bộ, Add trước = flush HĐ dở trong transaction của nó). Cùng thứ tự bridge cũ `:112-113` + `CreateContractSigningPlanCommandHandler:426-429`.
|
||||
- **`:203-206`** — field-map `SlaDeadline = UtcNow + GetPhaseSla(DangSoanThao) ?? 7d` (thiếu thì đây là HĐ DUY NHẤT trong hệ không có hạn phase; cả 2 tiền lệ `ContractFeatures.cs:110` + bridge cũ `:109-110` đều set).
|
||||
- **`:159-166` + `:201`** — `WorkflowDefinitionId = activeWfId` (V1 active theo `ContractType`) pin **NGANG** 2 tiền lệ `ContractFeatures.cs:105-109` ("Cả 2 set ok") + bridge cũ `:108`. 🔴 **Chỗ này spec-cụm-3 vá-6 THẮNG invest K7.1** (invest `:46` viết `WorkflowDefinitionId = null`) — đúng luật "16 vá thắng khi xung đột với nền" (spec `:4`); để null là rơi về policy hardcode `ContractFeatures.cs:647-662`.
|
||||
- Field-map còn lại theo invest `:46`: `GiaTri = Σ ApprovedAmount(ĐÚNG tập LineIds)` `:180` · `SupplierId` từ dòng · `ProjectId/DepartmentId` từ plan · `ApprovalWorkflowId` pin V2 `:202` · `BudgetManual*` = Σ `PeReferenceAmount` `:198-199` · 2 changelog (`ContractChangelogs` Insert + `ContractSigningPlanChangelogs` Update/EntityType=Line) `:216-235` · **1 `SaveChangesAsync` cuối** `:248` (atomic, 0 HĐ dở-dang).
|
||||
- 6 guard tuần tự (invest `:38-44`): phase DaDuyet `:106-108` · dòng ∈ phiếu `:111-118` · 1-HĐ-1-NCC `:121-124` · `ApprovedAmount != null` **message NÊU TÊN hạng mục** `:127-141` (KHÔNG fallback `ProposedAmount`) · `ContractId == null` per-dòng (chống double-bridge) · workflow §3.
|
||||
|
||||
## §6 — Build + verify
|
||||
## §6 — Build + runtime check
|
||||
|
||||
(đang làm)
|
||||
- **`dotnet build SolutionErp.slnx`** → **Build succeeded · 0 Warning · 0 Error** (67s). Diffstat `src/`: `ContractSigningPlansController.cs +33` · `ContractSigningPlanFeatures.cs +23/-2` · `DbInitializer.cs +86` · **file mới** `CreateContractFromSigningPlanFeatures.cs` (~250 dòng). **0 file ngoài `src/Backend/**`.**
|
||||
- **Runtime check (câu hỏi thật: collection-subquery mới có dịch được trên SQLite fixture không?):**
|
||||
`dotnet test tests/SolutionErp.Infrastructure.Tests --filter "FullyQualifiedName~ContractSigningPlan"` → **34 passed / 1 failed / 35 total (12s)**.
|
||||
✅ **Cả 3 handler đọc `ProjectPagedAsync` đều XANH** (`GroupAndCatalogTests.ListAsync` · `CrudTests.ListDeletedAsync` · `ApprovalTests.InboxAsync`) ⇒ `ContractIds` subquery **dịch được**, không phải sửa gì. (Khuôn giống `WinnerSupplierNames` list PE — nay có bằng chứng CHẠY THẬT, không chỉ tiền lệ.)
|
||||
- 🔴 **1 FAIL — ĐÚNG DỰ KIẾN, thuộc lane test-specialist, TÔI KHÔNG SỬA (rào split):**
|
||||
`tests/SolutionErp.Infrastructure.Tests/Api/AuthorizePolicyRegressionTests.cs` :: `ContractSigningPlansController_EveryWriteEndpoint_HasAuthorizePolicy`.
|
||||
Nguyên nhân chính xác: `:398` `parts[0].Should().Be(MenuKeys.KeHoachKyKet)` — test bắt **MỌI** action của controller phải gate bằng key `KeHoachKyKet`; endpoint bridge cố ý gate `Contracts.Create` (đó CHÍNH LÀ vá-1, và là gotcha #85 đúng-key-của-endpoint).
|
||||
**Bàn giao test-lane:** thêm ngoại lệ cho action `CreateContract` (kỳ vọng `Contracts.Create`), giữ nguyên luật cho 15 action còn lại. Neo chống-vacuous `:375` (`>= 15 action`) **KHÔNG vỡ** — nay 16 action. Đề nghị bổ sung 2 test authz 2 chiều (đủ 2 vế → 201 · thiếu 1 vế → 403) theo spec `:9`.
|
||||
- **Chưa làm / không thuộc lane (khai để không ai tưởng đã xong):** T1-T8 (test-lane) · nút + dialog ×2 app (lane FE) · A1-A7 acceptance sau deploy · FE type mirror `khkk.ts` cho `contractIds` (lane FE — JSON thừa field không gãy TS hiện tại).
|
||||
|
||||
END implbe-k7 — VERDICT=IN-PROGRESS
|
||||
END implbe-k7 — VERDICT=DONE (bridge KHKK→HĐ BE dựng xong + 5/5 vá land · build 0/0 · 34/35 test chạm, 1 fail đã-verify thuộc test-lane · 0-mig · 0 file FE/tests · chưa commit)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
IMPLFE-K7: IN-PROGRESS
|
||||
IMPLFE-K7: DONE — lane code-trọn (2 lượt #53-garble, resume-1 + lead-close-on-behalf từ đĩa)
|
||||
|
||||
# Lane FE K7 — bridge KHKK→HĐ (vá-1 FE · vá-16 · vá-4b) — S167 2026-08-01
|
||||
|
||||
@ -57,4 +57,26 @@ Tiền lệ DUY NHẤT đang sống = bridge PE: `POST /purchase-evaluations/{id
|
||||
`khkkPlanContractIds()` trong `types/khkk.ts` ×2 app. Nếu BE **không** expose trên list ⇒ cây vẫn chạy
|
||||
bằng nguồn legacy + có chuông DEV (không vỡ, không im).
|
||||
|
||||
END implfe-k7 — VERDICT=IN-PROGRESS
|
||||
## §2 KHỚP-DTO ĐÓNG (lead đối chiếu 2 artifact @S167)
|
||||
|
||||
BE-lane expose **`ContractIds`** trên list DTO (`ContractSigningPlanFeatures.cs:65-75/:836-842` — sub-implbe-k7.md §4).
|
||||
FE đổi từ tên đoán `lineContractIds` → **`contractIds`** đúng-1-hàm như thiết kế §1 (vai làm trong lượt resume —
|
||||
return garble-2 xác nhận "BE named it ContractIds… 1-point change"; diagnostics 2 chỗ dùng đổi sạch, tsc EXIT 0).
|
||||
Chuông DEV vắng-field ⟂ rỗng-thật (§1) giữ nguyên.
|
||||
|
||||
## §3 Nút bridge + dialog (vá-1FE + vá-16) — DISK-FACT, chi tiết để gate-K7 đọc diff
|
||||
|
||||
`KhkkDetailPage.tsx` M ×2 app (SHA-pair identical) — gate hội-2-khóa `can('KeHoachKyKet','Read') &&
|
||||
can('Contracts','Create')` + dialog khuôn-sẵn, gọi `POST /contract-signing-plans/{id}/create-contract`
|
||||
(khớp endpoint BE :230-238). ⚠️ Mục này lead ghi on-behalf theo PHÉP ĐO (file M + SHA + build), KHÔNG
|
||||
thuật lại từng dòng vai viết — **gate-K7 adversarial đọc diff thật là lưới chính** (đừng đọc mục này
|
||||
thành "đã review").
|
||||
|
||||
## §4 Chứng máy (lead đo @S167, sau khi vai dừng — trọng tài build TƯƠI #68)
|
||||
|
||||
- SHA-pair 3/3 identical: `usePipelineStages.ts` `76e1823345d4` · `KhkkDetailPage.tsx` `cf0a2272061f` · `types/khkk.ts` `60ec8fa3a5ae`
|
||||
- `npx tsc -b` fe-user EXIT 0 · fe-admin EXIT 0 (chạy SAU khi vai dừng tay)
|
||||
- `npm run build` fe-user ✓ 921ms · fe-admin ✓ 765ms (warnings chunk-size + INEFFECTIVE_DYNAMIC_IMPORT = PRE-EXISTING)
|
||||
- `git status` fe-*: đúng 6 file M (3 cặp mirror), 0 file lạ
|
||||
|
||||
END implfe-k7 — VERDICT=DONE (lead-closed-on-behalf; gate-K7 là lưới review)
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
TEST-K7: IN-PROGRESS
|
||||
|
||||
# sub-test-k7 — Lane TEST K7 (bridge KHKK→HĐ, chuỗi SP-002 B4, S167 · 2026-08-01)
|
||||
|
||||
> Rào: chỉ `tests/**` · KHÔNG production code · KHÔNG commit. Ghi-TỪNG-MỤC chống-#53: xong mục nào ghi ngay mục đó; mục CHƯA CHẠY chỉ được tiêu-đề + "CHƯA ĐO".
|
||||
|
||||
## §0 — Tiền đề ĐO TƯƠI (không tin số đề bài)
|
||||
|
||||
- `git status` @spawn: `src/Backend/**` dirty 3 file + 1 file MỚI (`CreateContractFromSigningPlanFeatures.cs`) = **lane BE K7 đã LAND, uncommitted** — khớp `sub-implbe-k7.md`. `tests/**` = **0 file dirty** (baseline sạch).
|
||||
- **Baseline suite ĐO TƯƠI (trước khi tôi gõ 1 chữ):** `SolutionErp.Infrastructure.Tests` = **577 total / 576 passed / 1 FAILED**. FAIL đúng test đã bàn giao:
|
||||
`AuthorizePolicyRegressionTests.ContractSigningPlansController_EveryWriteEndpoint_HasAuthorizePolicy` @`:398` — `Expected parts[0] ... they differ: "Contracts" vs "KeHoachKyKet"` ⇒ **RED tái hiện đúng nguyên nhân implbe-k7 khai**, không phải flaky.
|
||||
- Đếm action controller từ ĐĨA: `grep -c "^ \[Http"` = **17** (implbe-k7 ước "16"; số đo là 17). Neo chống-vacuous `>= 15` không vỡ, giữ nguyên nghĩa.
|
||||
|
||||
### 🔎 Chồng-lấn TÌM TRƯỚC KHI VIẾT (bài S166) — T8 ĐÃ CÓ NGƯỜI ĐO MỘT NỬA
|
||||
|
||||
`Application/ContractV2PipelineTests.cs` (W6-S161) đã phủ đúng mệnh đề T8 của bảng invest (`sub-invest-fable-b2-cum3.md:82`): approver-V2-không-role-legacy thấy List/Detail + Inbox, cấp-2 KHÔNG thấy khi đang cấp-1 — `Inbox_PendingLevelApprover_SeesChoDuyetContract` · `Inbox_OtherLevel_DoesNotSeeContract` · `View_V2Approver_NotDrafter_CanSeeChoDuyetContract`.
|
||||
⇒ **KHÔNG nhân bản.** Cái các test đó KHÔNG đo: chúng **seed thẳng** `Contract{Phase=ChoDuyet, ApprovalWorkflowId, stepIndex}` — tức giả định sẵn thứ mà cầu K7 phải TỰ sinh. Vế còn hở = **mối nối cầu→đường-ống**. T8 của tôi đo đúng chỗ hở đó (bridge → trình THẬT qua `ContractWorkflowService` → inbox), 0 chồng lấn.
|
||||
|
||||
## §1 — [BƯỚC 1 ✅ XONG] Fix bàn-giao BE: ngoại lệ `CreateContract`
|
||||
|
||||
File: `tests/SolutionErp.Infrastructure.Tests/Api/AuthorizePolicyRegressionTests.cs`
|
||||
|
||||
- Thêm **bản đồ kỳ-vọng-CHÍNH-XÁC** `KhkkCrossModuleGates = { CreateContract → "Contracts.Create" }` (KHÔNG phải danh sách "bỏ qua"): action trong bản đồ **vẫn bị đo**, chỉ đổi key kỳ vọng ⇒ gate sai thành `KeHoachKyKet.Create` **vẫn ĐỎ**. Comment nêu LUẬT (tài nguyên SINH RA quyết định khoá ghi) + trỏ gotcha **#85** + `MenuPermissionHandler.cs:40` (so khớp CHÍNH XÁC 1 key, không kế-thừa). **0 số-đếm trong comment.**
|
||||
- Vế (b) tách 2 nhánh: có trong bản đồ → `authz.Policy.Should().Be("Contracts.Create")`; không → giữ nguyên luật cũ `parts[0] == MenuKeys.KeHoachKyKet` cho 16 action còn lại.
|
||||
- Thêm (e) chốt-bằng-TÊN: `byName[CreateContract] == "Contracts.Create"` + `KhkkCrossModuleGates.Keys.Should().BeSubsetOf(byName.Keys)` — đổi tên/xoá action mà quên dọn bản đồ = **cấu-hình-chết im lặng** (vắng-mặt trông giống ổn) ⇒ bắt luôn.
|
||||
- Neo chống-vacuous `:375` **GIỮ NGUYÊN `>= 15`** (không hạ, không đổi nghĩa; thực đo 17).
|
||||
- Vế (a)/(c)/(d) không đụng: `CreateContract` là POST nên vẫn phải qua luật "verb ghi không được gate Read" + "phải Create/Update/Delete".
|
||||
|
||||
**ĐO:** `--filter AuthorizePolicyRegressionTests` → **Passed! Failed: 0, Passed: 22** (trước: 1 FAIL). File này +0 test mới (sửa test sẵn có, +2 vế assert).
|
||||
|
||||
## §2 — 8 case T1-T8 + 2-authz + idempotency
|
||||
|
||||
CHƯA ĐO.
|
||||
|
||||
## §3 — Vá-7 B6 neo mới
|
||||
|
||||
CHƯA ĐO.
|
||||
|
||||
## §4 — Suite FULL
|
||||
|
||||
CHƯA ĐO.
|
||||
|
||||
END test-k7 — VERDICT=IN-PROGRESS
|
||||
Reference in New Issue
Block a user