22 KiB
sub-invest-fable-b2-cum3 — B2 CỤM-3: SPEC CHI TIẾT K7 (cầu KHKK→HĐ + đường ống V2) + K8 (dry-run E2E)
Run
2026-07-31-S164-4gd-khkk-fanout· engine/fable-real· investigator-codebase (read-only spec-writer) Luật đan xen: đọc đủ MỘT section → VIẾT NGAY. Kế thừa S156/S160 — không soi lại vùng đã đo.
K7 Cầu KHKK→HĐ + đường ống V2
K7.0 RE-ĐO 3 nấc (đo lại hôm nay, không tin sổ) + 1 BÁC tiền đề đề bài
| Nấc | Kết quả đo @S164 | Evidence |
|---|---|---|
| (a) cột/index | ✅ CÓ | ContractSigningPlanLine.cs:25 ContractId Guid? + comment "[C6 review-schema] HĐ sinh ra từ dòng này (W5)"; index non-unique ContractSigningPlanLineConfiguration.cs:26 |
| (b) write-site | ✅ = 0 (tự grep, không mượn số lead) | grep ContractId trong ContractSigningPlanFeatures.cs → 3 hit đều READ: :67 DTO field, :574 projection, :1185 comment; ContractSigningPlanWorkflowService.cs → 1 hit :54 = COMMENT FK. 0 assignment |
| (c) test E2E | ✅ = 0 test bridge | tests/ có 4 file SigningPlan (Approval/Crud/Schema/AuthorizePolicy) + CreateContractFromEvaluationMultiWinnerTests.cs (bridge PE cũ) — KHÔNG file nào test cầu KHKK→HĐ |
🔴 BÁC tiền đề K7(b) của đề bài — "SỬA 3 lỗ đường ống (GetEligiblePhases V2-aware + inbox V2 + guard trình)" là kiến thức S156 ĐÃ LỖI THỜI. Cả 3 đã LAND @W6-S161:
- List + Detail V2-aware:
ContractFeatures.cs:306-318— vế (a)myWorkflowIds.Contains(x.c.ApprovalWorkflowId)per-HĐ (helperResolveUserApprovalWorkflowIdsAsync:403-411) + vế (b)HardCopyActorRoles:391-392thấy DaPhatHanh. Comment:381-387CẤM nhét ChoDuyet vàoGetEligiblePhases(hàm dùng chung với ListDeleted:441→ nhét là RÒ HĐ ChoDuyet xóa-mềm sang màn "Đã xóa"). K7 KHÔNG sửaGetEligiblePhases— implementer đọc đề bài cũ mà "làm cho xong" là tạo lỗ. - Inbox V2:
ContractFeatures.cs:532-534v2InboxIds = ResolveV2InboxIdsAsync(...)(bản Contract ĐÃ TỒN TẠI) + where:543|| v2InboxIds.Contains(c.Id)+AdminInboxPhases:503-513đã +ChoDuyet (DR-4). - Guard trình:
ContractWorkflowService.cs:73-92đã nớiDrafter ∨ DeptManager ∨ Procurement ∨ isCreator(vá lỗ PMH-403 đo 07-29).
⇒ K7(b) co lại thành V3: test-khóa 3 vế (regression lock — xem K7.4 T8), KHÔNG build lại. Phần build THẬT của K7 = bridge (a) + FE (c).
K7.1 (a) BRIDGE MỚI — POST /api/contract-signing-plans/{id}/create-contract
Khuôn mẫu: mirror bridge PE→HĐ cũ CreateContractFromEvaluationFeatures.cs (endpoint PurchaseEvaluationsController.cs:340-343) + guard workflow V2 của CreateContractCommand (ContractFeatures.cs:74-81). Luật spec-4gd §①: GIỐNG NHAU — CHỈ KHÁC FORM — ĐỪNG CHẾ THÊM.
Command (file mới Application/ContractSigningPlans/CreateContractFromSigningPlanFeatures.cs):
public record CreateContractFromSigningPlanCommand(
Guid PlanId,
List<Guid> LineIds, // OG-1: schema GỘP — N line cùng NCC → 1 HĐ; UI-1-1 truyền 1 phần tử
ContractType ContractType,
Guid ApprovalWorkflowId, // V2 type-3 — user chọn trong dialog
string? TenHopDong = null) : IRequest<Guid>; // 1 call = 1 HĐ (khác bridge PE trả List)
⚠️ Lệch đề bài có chủ đích: đề ghi input {planId, lineIds[], contractType}; PHẢI thêm ApprovalWorkflowId vì V2 KHÔNG phân theo ContractType — ApprovalWorkflow.cs:57 Contract = 3, // HĐ general (any ContractType) ⇒ "pin theo ContractType" tự động là BẤT KHẢ; chọn quy trình = việc của dialog (mirror ContractCreatePage dropdown type-3).
Guard tuần tự trong handler (mỗi guard 1 message VN rõ):
- plan tồn tại +
Phase == DaDuyet(mirror rào (i)ContractSigningPlanFeatures.cs:330-332). LineIdsnon-empty, distinct, TẤT CẢ ∈ plan.Lines.- TẤT CẢ line cùng
SupplierId— 1 HĐ = 1 NCC (Contract.SupplierIdđơn). Liên-danh N NCC = N lần bấm (UI-1-1 mỗi lần 1 line nên tự thỏa). Message: "Các dòng chọn thuộc nhiều NCC khác nhau — mỗi Hợp đồng chỉ 1 NCC, tạo lần lượt." - TỪNG line
ApprovedAmount != null— null → Conflict message NÊU TÊN hạng mục: "Dòng '{TenHangMuc}' chưa có giá duyệt chốt — phiếu phải qua finalize (K2-freeze) trước khi đưa vào HĐ." KHÔNG fallback ProposedAmount (số chưa chốt). - TỪNG line
ContractId == null— chống double-bridge per-line (thay guardpe.ContractIdsingle của bridge cũ:59-60). Message: "Dòng '{TenHangMuc}' đã có Hợp đồng." ApprovalWorkflowId: tồn tại +ApplicableType == Contract(3)— copy trọn 2 nhánh NotFound/Conflict theo khuônEnsureWorkflowTypeAsync(ContractSigningPlanFeatures.cs:209-220) đổi hằng type.
Build Contract (map field): Type=request.ContractType · Phase=DangSoanThao · SupplierId=line.SupplierId · ProjectId=plan.ProjectId (ContractSigningPlan.cs:25) · DepartmentId=plan.DepartmentId (:26) · DrafterUserId=currentUser · GiaTri = Σ ApprovedAmount(lines chọn) — phạm-vi-gộp = ĐÚNG tập LineIds trong call, không hơn không kém (DoD spec-4gd K7) · TenHopDong = request.TenHopDong ?? "{pe.TenGoiThau} — {TenHangMuc}" · ApprovalWorkflowId = request.ApprovalWorkflowId (pin V2 — khác bridge cũ chỉ pin V1 :68-71,108) · WorkflowDefinitionId = null (KHÔNG pin V1 song song — HĐ mới đi V2 thuần, nhánh dispatch ContractWorkflowService.cs:111 ưu tiên V2) · optional BudgetManualName="NS tham chiếu KHKK {MaKeHoach}" + BudgetManualAmount=Σ PeReferenceAmount(lines) (mirror :106-107, tham khảo).
Mã HĐ: gen NGAY lúc bridge — mirror tiền lệ bridge PE :113 + bài S88 self-committing codegen (:78-81): gen TRƯỚC khi Add/track (codegen tự SaveChanges — comment ContractSigningPlanFeatures.cs:352-357 đã trả giá bài này); terminal V2 có guard if (MaHopDong is null) (ContractWorkflowService.cs:384) nên không double-gen. Trade-off cháy seq RG-001 khi HĐ không phát hành → khai ở K8 guard.
Write-site ContractId (nấc b đóng): sau khi Add contract + SaveChanges chung 1 lần: line.ContractId = contract.Id cho TỪNG line chọn (load lines TRACKED — đây là assignment ĐẦU TIÊN toàn codebase). + 2 changelog mirror :117-141: ContractChangelogs (Insert, "Tạo HĐ {mã} từ KHKK {MaKeHoach}") + ContractSigningPlanChangelogs (Update, ContextNote=contractId). Atomic: 1 SaveChanges cuối.
Nối cây GĐ3 (quyết): pe.ContractId ??= contract.Id (chỉ khi null) — usePipelineStages.ts đi GĐ3 DUY NHẤT qua pe.contractId (skill contract-workflow đã khai); không set = HĐ sinh ra NHƯNG cây 4-folder mù GĐ3. Hệ quả 2 chiều khai rõ: (i) bridge PE→HĐ cũ sẽ 409 cho PE đó (:59-60 — đúng ý, khóa đường cũ tự nhiên); (ii) liên-danh N-HĐ cây vẫn chỉ link HĐ[0] — hố sẵn có của bridge cũ (:145), nâng cấp cây đọc line.contractId = backlog W9, KHÔNG làm đợt này (đụng 2 file SHA-identical ràng buộc K5).
Controller: ContractSigningPlansController — [HttpPost("{id:guid}/create-contract")] [Authorize(Policy = "Contracts.Create")] — 🔴 đúng-key-policy-endpoint-đích (gotcha #85): tài nguyên sinh ra là Contract ⇒ key Contracts.Create, KHÔNG phải KeHoachKyKet.*. Trả 201 { contractId, maHopDong }.
Ai bấm + chọn ContractType (câu III): ⏳ PENDING-OWNER — default spec: "người có quyền tạo HĐ" = ai mang policy Contracts.Create thấy nút + tự chọn ContractType trong dialog (khớp l1-F15 spec-4gd "mặc định người bấm chọn trong dialog"). Đánh dấu chờ anh; đổi answer chỉ đổi điều kiện hiện nút FE, máy BE không đổi.
K7.2 (b) Đường ống — trạng thái sau re-đo
- ĐÃ VÁ (K7.0): list/detail/inbox/guard-trình. Việc K7 = V3 test-khóa + xác nhận runtime bằng curl trong acceptance (A3/A4).
- CÒN THIẾU thật: 0 — HĐ pin V2 sinh từ bridge đi được trọn: trình (
:70-101set ChoDuyet + levelOrder=1 vì AWId≠null:97) → ApproveV2Async (:111-113) → terminal gen mã + DaPhatHanh (:381-392). - ⚠️ Tiền đề vận hành (không phải code): prod phải có ≥1
ApprovalWorkflowApplicableType=3 active + roster thật. Dev hiện chỉ cóQT-HD-V2-001"mẫu UAT" (sqlcmd Dev @S164); prod CHƯA đo được (SSH chết khi load SQL-client — S134/S148) → chuyển thành pre-flight K8 B0.
K7.3 (c) Nút FE ×2 app
- Vị trí:
fe-user/src/pages/khkk/KhkkDetailPage.tsxkhối actions PageHeader:291-318(cạnh nút Xóa/Danh sách) + mirrorfe-admin/.../KhkkDetailPage.tsx(tiền lệ PeWorkflowPanel byte-identical 2 app — S155). - Điều kiện hiện:
phase === KhkkPhase.DaDuyet && can('Contracts','Create')(#85 — đúng key endpoint; KHÔNG ORKhkk_*). - Dialog "Đưa vào Hợp đồng": (1) chọn 1 line (UI-1-1 OG-1 — radio list các line
contractId == null, hiện TenHangMuc + NCC + ApprovedAmount; line đã bridge → badge "Đã có HĐ" + link, disabled); (2) Select ContractType 7 enum; (3) Select Quy trình duyệt HĐ (nguồn = mirror dropdown ContractCreatePage, filter ApplicableType=3); (4) TenHopDong prefill. Submit → POST → toast mã HĐ + navigate/link HĐ; invalidate query detail. - Line có
ApprovedAmount == null→ disabled + tooltip "chưa có giá chốt".
K7.4 (d) Test — 7 case (fixture khuôn ContractSigningPlanApprovalTests.cs sẵn có)
| # | Case | Assert |
|---|---|---|
| T1 | Happy 1-line: plan DaDuyet, line ApprovedAmount=X | 1 HĐ: GiaTri==X · Phase=DangSoanThao · ApprovalWorkflowId pin đúng · WorkflowDefinitionId null · MaHopDong non-null · line.ContractId==hđ.Id · pe.ContractId==hđ.Id · 2 changelog |
| T2 | Gộp N=2 line CÙNG NCC (máy OG-1) | GiaTri==Σ 2 ApprovedAmount · CẢ 2 line cùng ContractId |
| T3 | 1 line ApprovedAmount=null | Conflict, message chứa TenHangMuc; DB 0 HĐ mới + 0 line đổi (atomic) |
| T4 | plan.Phase=ChoDuyet | Conflict "chỉ từ phiếu Đã duyệt" |
| T5 | line đã có ContractId | Conflict (double-bridge per-line) |
| T6 | 2 line KHÁC SupplierId 1 call | Conflict "mỗi HĐ 1 NCC" |
| T7 | ApprovalWorkflowId type-10 (KHKK) | Conflict (guard type-3 — chống forge, mirror :74-81) |
| T8 (khóa V3) | HĐ pin V2 ở ChoDuyet: approver-V2-không-role-legacy | thấy List (vế :315-316) + Inbox (:543); cấp-2 CHƯA thấy inbox khi đang cấp-1 |
K7.5 (e) Acceptance đo được (chạy sau deploy, số cụ thể)
- A1
POST .../create-contract→ 201; sqlcmdSELECT ContractId FROM ContractSigningPlanLines WHERE Id IN (…)= đúng contractId, đúng N dòng chọn. - A2 sqlcmd
Contracts.GiaTri== ΣApprovedAmountcác line chọn — khớp từng đồng. - A3 login user CHỈ có chân V2 (0 role legacy):
GET /api/contracts?phase=10+GET /api/contracts/{id}→ thấy HĐ (trước W6: rỗng). - A4
GET /api/contracts/inbox: approver cấp-hiện-tại CÓ HĐ; user cấp-sau CHƯA. - A5 duyệt hết trạm V2 →
Phase=9 (DaPhatHanh);MaHopDongđúng RG-001 ({ProjectCode}/{abbr}/SOL&{SupCode}/{Seq:D2}) — sinh từ lúc bridge, terminal không double-gen. - A6 POST lần 2 cùng line → 409; FE line badge "Đã có HĐ" + disabled.
- A7
dotnet test≥ baseline+8 (khai k con số); cicd PASS; FE-ship → byte-verify #77 ×2 app.
K7.6 Danh sách việc (n = 8)
V1 BE command+validator+handler bridge (file mới) · V2 BE endpoint controller + policy Contracts.Create · V3 test-khóa đường ống (T8 — KHÔNG sửa GetEligiblePhases) · V4 FE fe-user nút+dialog · V5 FE fe-admin mirror · V6 FE types/hook + invalidate (×2 app) · V7 test T1-T7 · V8 acceptance A1-A7 + khai số test.
K8 Dry-run E2E
K8.0 Tham số chốt + 2 QUYẾT
- Nguồn:
PE/2026/A/049(gói 14 Mat PVC) — theo đề bài. QUYẾT-1 nhóm: ống PVC = vật tư chính ⇒ nhóm B1 · Vật tư → workflowKHKK-N4(transcribedanh-muc-sp002-transcribe.md:52"B1 · Vật tư / Materials (23) → N4"; hàng khớp chọn trong 23 dòng B1, fallbackB1-23 Vật tư chính khác:75). KHÔNG phải A1 (:20= thiết bị/vật tư PHỤ → N1). - QUYẾT-2 loại HĐ:
HopDongNhaCungCap(abbr "NCC" CÓ trong RG-001 v02 — né hố "MB" không gốc quy định, S160 F-06.1/F-21). - Đội hình: cột "Ai" = chép từ bảng danh tính K3-acceptance (vá-11 cụm-1, đo trên bản LIVE) — Dev KHÔNG có
QT-DN-V2-001(sqlcmd Dev @S164 chỉ cóQT-HD-V2-001type-3 UAT) ⇒ KHÔNG điền trước email từ Dev. Anchor đã chốt: trạm CCM = TP.CCM Phan Văn Chương (finalize-gate OG-3/OG-9) · trạm cuối = CEO (anh Trường). Giả định 4 trạm — số trạm THẬT theo seed K3, người chạy bám panel duyệt. - Nhánh chính = A (finalize tại a Chương) — chứng tính năng MỚI OG-9; nhánh B (lên CEO) = phiếu ZZTEST thứ 2 nếu còn giờ.
- Login API prod: parse field
accessToken(KHÔNG phảitoken— đo S160).
K8.1 Bảng bước (B0-B16 = 17 bước; mỗi bước Ai · Hành động · Kỳ vọng · Bằng chứng)
B0 PRE-FLIGHT (chặn cả buổi nếu thiếu): (i) scripts/backup-sql.ps1 backup prod; (ii) verify prod có ApprovalWorkflow ApplicableType=3 ACTIVE (đội hình HĐ thật) + KHKK-N4 active + roster khớp bảng K3 — thiếu → admin tạo bằng designer fe-admin TRƯỚC; (iii) verify PE/2026/A/049: Phase=DaDuyet + có winner IsWinner + chưa có plan nhóm-4 sống (rào ContractSigningPlanFeatures.cs:342-347, sau K2 = per-(PeId,group)); (iv) dặn team (Zalo/email): "hệ thống bắn notification phiếu ZZTEST trong khung giờ X — bỏ qua" — KHÔNG có cơ chế tắt notify per-record, chỉ dặn (DoD l3-F16); (v) hỏi anh câu giờ-G ở K8.2. Bằng chứng: ảnh backup + sqlcmd 3 dòng verify.
| # | Ai | Hành động | Kỳ vọng UI/API | Bằng chứng |
|---|---|---|---|---|
| B1 | BCH drafter [K3-bảng] | fe-user /khkk → Tạo mới: picker chọn PE/2026/A/049, workflow KHKK-N4, GhiChu tiền tố ZZTEST |
201 + MaKeHoach = KHKK/2026/{seq} hiện header |
ss-k8-01 + mã phiếu |
| B2 | BCH drafter | Điền line: gán hạng mục B1-xx (PVC) per line, ProposedAmount per NCC×hạng mục; save | Lines lưu; line thiếu hạng mục bị submit-guard chặn (vá-5 cụm-1) | ss-k8-02 |
| B3 | BCH drafter | Trình duyệt (Nháp→ChoDuyet) | Badge "Chờ duyệt"; phiếu vào inbox trạm-1; notification bắn | ss-k8-03 |
| B4 | Trạm-1 [K3-bảng] | Login → /khkk Đang duyệt → mở phiếu → KhkkWorkflowPanel bấm Duyệt + ý kiến "ZZTEST duyệt trạm 1" |
Advance cấp/bước; LevelOpinion UPSERT hiện Section ý kiến | ss-k8-04 |
| B5 | Trạm-2 [K3-bảng] | Như B4 tại trạm 2 | Advance; user trạm-1 hết thấy phiếu trong inbox | ss-k8-05 |
| B6 | TP.CCM Phan Văn Chương | Trạm CCM: tick "Duyệt kết thúc tại cấp" (AllowApproverFinalize — OG-9 nhánh A) | Phase=DaDuyet KHÔNG qua CEO; EndedByLevelFinalize=true; ApprovedAmount ghi tại choke-point + FREEZE (QĐ9 mirror Mig 67) |
ss-k8-06 + sqlcmd EndedByLevelFinalize,ApprovedAmount |
| B7 | (chỉ nhánh B — phiếu ZZTEST-2) CEO Trường | Duyệt final trạm cuối | Phase=DaDuyet đường chuẩn |
ss-k8-07 |
| B8 | BCH/Admin | Mở lại detail: số khóa 🔒; thử sửa line | ApprovedAmount hiển thị khóa; sửa line bị phase-guard chặn (vá-6 cụm-1); sqlcmd số không đổi | ss-k8-08 + sqlcmd |
| B9 | Người có Contracts.Create [⏳ câu III] |
Bấm "Đưa vào Hợp đồng" (K7): dialog chọn 1 line (UI-1-1) + ContractType=NhaCungCap + workflow HĐ type-3 | 201 → HĐ draft GiaTri == ApprovedAmount line; MaHopDong RG-001 có ngay; line badge "Đã có HĐ" |
ss-k8-09 + mã HĐ |
| B10 | Người bấm B9 | Mở HĐ draft (TenHopDong tiền tố ZZTEST), kiểm NCC/dự án/giá → Trình duyệt | Phase→ChoDuyet (guard trình đã nới :73-92); vào inbox approver V2 HĐ |
ss-k8-10 |
| B11 | Approver HĐ [đội hình type-3] | Duyệt lần lượt hết trạm V2 | Terminal: Phase=DaPhatHanh (mã giữ nguyên — guard :384); notification "đã phát hành" |
ss-k8-11 |
| B12 | HRA/CCM [HardCopyActorRoles] | GĐ4: mở HĐ (leaf Hdc_*) → upload scan bản cứng (purpose SealedCopy) |
Badge hasSealedCopy (EXISTS ContractFeatures.cs:348-355); vai bản-cứng THẤY HĐ DaPhatHanh (:317-318) |
ss-k8-12 |
| B13 | Bất kỳ + Admin | Mở cây 4-folder (usePipelineStages) | Đủ 4 tầng: PE A/049 → KHKK ZZTEST (folder nhóm 4) → HĐ ZZTEST → bản cứng; đường nối khkk.purchaseEvaluationId · pe.contractId · hasSealedCopy |
ss-k8-13 |
| B14 | Lead | Chốt sổ: bảng mã thật (PE/2026/A/049 · KHKK/2026/xxx · mã HĐ) + tick DoD K8 spec-4gd :35 |
Đủ: ZZTEST prefix · toàn trình hết trạm · cây 4 GĐ · screenshot + mã thật | bảng trong session-log |
B15 CLEANUP + danh sách bản-ghi-KHÔNG-xóa-được (sống chung):
- HĐ ZZTEST DaPhatHanh — HỐ-1:
DeleteContractguard numericPhase >= DangInKy(ContractFeatures.cs:632-633, S160) chặn xóa cả 9/10/98/99, admin không thoát qua API. Sống chung: giữ vĩnh viễn với tiền tố ZZTEST, HOẶC sqlcmd soft-delete tay (IsDeleted=1) sau khi anh gật. - KHKK ZZTEST DaDuyet — 🔴 HỐ-1-KHKK (đo mới @S164): delete allow-list
{DangSoanThao, TuChoi}nằm SAU nhánh admin (ContractSigningPlanFeatures.cs:1145-1148) ⇒ phiếu DaDuyet KHÔNG xóa được KỂ CẢ Admin + chiếm slot (A/049, nhóm-4) — PE thật này hết lập phiếu N4 cho tới khi xử. Lối thoát: sqlcmdUPDATE ContractSigningPlans SET IsDeleted=1,…— global filter làm rào:342-344giải phóng slot (soft-deleted không tính "sống"). - Seq đã cháy:
ContractCodeSequences(mã HĐ ăn STT 01 của cặp prefix (dự án thật, NCC thật)) +KHKK/2026seq — không thu hồi. - Changelog/Approval/LevelOpinion các phiếu trên + notification đã bắn — vĩnh viễn by-design.
- PE A/049: KHÔNG đụng số liệu (chỉ bị set
pe.ContractIdbởi bridge B9 — khai với anh; muốn gỡ = sqlcmd NULL lại sau khi soft-delete HĐ).
B16 ROLLBACK: hỏng giữa chừng → DỪNG, không xóa gì, chụp lỗi + ghi bước đứng; phiếu KHKK kẹt ChoDuyet → approver "Từ chối" (TuChoi → xóa được qua UI :1145); HĐ kẹt ChoDuyet → admin Reject→TuChoi; restore DB = last-resort KHÔNG dùng (prod đang sống, restore = mất data người khác — S91).
K8.2 Câu hỏi giờ-G cho anh (1 câu, trước B1)
Vòng-1 chạy thẳng trên PE/2026/A/049 thật (giá: KHKK ZZTEST DaDuyet + HĐ ZZTEST DaPhatHanh mang tên dự án/NCC THẬT nằm vĩnh viễn trong list tới khi anh cho sqlcmd soft-delete + cháy 1 seq mã HĐ của cặp prefix thật) — HAY dựng bộ ZZTEST riêng (ZZTEST Project + Supplier + PE tự tạo từ GĐ1, 0 đụng data thật, thêm ~30 phút setup)? Đề bài chỉ định A/049 ⇒ default = A/049 nếu anh im lặng; "PE test riêng" = vòng-2 (đề bài đã chừa).
Thứ tự + phụ thuộc
K1 ──→ K2 ──→ K3 ──────────────────────┐
│ │ ▼
│ └─ phiếu duyệt được → DaDuyet + ApprovedAmount (port finalize)
└─ TenHangMuc/ApprovalGroup/freeze-răng K7 (V1→V2→V7 BE; V3∥; V4/V5/V6 FE; V8 sau deploy)
cụm-2: K4a ∥ ngay · K4b/K4c sau K2/K3 · K5 ∥ · K6 ∥ │
▼
K8 = CUỐI CÙNG (sau MỌI wave deploy prod + cicd PASS + B0)
- K7 SAU cụm-1, lý do đo được (3): (i) message-chặn T3 cần
TenHangMuc— cột chỉ có sau Mig 71 K2; (ii) guard/rào per-(PeId, ApprovalGroup) + phase-guard line = K2 vá-6, K7 build trên trạng thái đó; (iii) không có K3 thìApprovedAmountNULL 100% (cột "CHỈ ghi tại choke-point finalize"Line.cs:23— nhánh finalize là việc K3.a) ⇒ bridge chặn mọi phiếu, K7 không test nổi happy-path. - K7 ⊥ cụm-2: không cần K4/K5/K6 về máy (điểm chạm cây duy nhất = set
pe.ContractId, không đổi file cây). - Rebase: K7 thêm code vào
ContractSigningPlanFeatures.cs+ FE KhkkDetailPage — cụm-1 vá 2/6/8 chạm CÙNG file ⇒ K7 khởi công SAU khi cụm-1 merge, đọc lại spec-cum1 §② trước khi viết (đừng vá lại:342— vá-2 đã CẤM). - K8 sau TẤT CẢ: dry-run đi qua menu 48-leaf (K4), cây sub-folder (K5), admin thu gọn (K6), designer 8 nhóm (K3), bridge (K7) — thiếu wave nào lộ wave đó; + B0 pre-flight prod.
Checklist tổng cụm-3
- K7-BE: POST bridge 201 ·
Line.ContractIdghi (write-site ĐẦU TIÊN toàn codebase) · pinApprovalWorkflowIdtype-3 ·GiaTri == Σ ApprovedAmounttập line chọn · null → chặn message CÓ TÊN hạng mục · double-bridge per-line 409 · policyContracts.Create(#85 đúng-key-endpoint). - K7-KHÔNG-LÀM (chống làm-thừa-tạo-lỗ):
GetEligiblePhasesGIỮ NGUYÊN (ContractFeatures.cs:381-387cấm nhét ChoDuyet) · ListDeleted GIỮ NGUYÊN (:442-447cố ý không ghép vế V2). - K7-FE ×2 app: nút chỉ
DaDuyet && can('Contracts','Create')· dialog 1-line UI-1-1 · badge "Đã có HĐ" · byte-verify #77 (wave có FE-ship). - K7-test: T1-T8 xanh, suite ≥ baseline+8 (khai k), cicd PASS.
- K7-acceptance: A1-A7 số thật (curl + sqlcmd), dán vào PR.
- K8-pre: B0 đủ 5 mục + câu giờ-G (K8.2) có trả lời.
- K8-run: B1-B14 đủ bằng chứng (ss-k8-01..13 + bảng 3 mã thật PE/KHKK/HĐ).
- K8-hậu: B15 danh sách bản-ghi-vĩnh-viễn ghi vào session-log (gồm HỐ-1-KHKK mới) · B16 không kích hoạt hoặc ghi lý do.
- DoD gốc spec-4gd: K7
:34+ K8:35tick từng dòng — không tick gộp.
END sub-invest-fable-b2-cum3 — TOTAL: K7 8v · K8 17-bước · 8 test