Files
solution-erp/.claude/workflows/runs/2026-07-29-S160-khkk-dryrun-plan/sub-investigator-codebase-2.md
2026-07-29 15:51:33 +07:00

11 KiB
Raw Blame History

sub-investigator-codebase-2 — LANE-2 CODEBASE DRY-RUN MAP (S160, /fable-real)

Vai: investigator-codebase · engine-đắt REAL · ghi-đĩa-trong-lúc-làm (chống #53). Trục: CODE THẬT trên repo. Lane-1 (song song) lo nghiệp vụ từ QT/FORM. Spec KHKK cũ (GĐ2 b.7→12, W1-W5, 7 bảng, Mig 69, type=10) = RATIFIED — chỉ BỔ SUNG GĐ3+GĐ4. Ranh giai đoạn (owner chốt @S159, spec §1.2): GĐ2=b.7→12 · GĐ3=b.13→18 (tận dụng Contract V2) · GĐ4=b.19→21 + ký cứng upload-only.

§0 — Nguồn đã đọc + tiền đề

  • run.md S160 (lệnh anh verbatim + ràng buộc 5-anchor + authz S156 + gotcha #84 no-SQL-tay).
  • Spec S157 497 dòng ĐỌC TRỌN (①②③④⑤): 7 bảng ContractSigningPlan*, Mig 69, ApplicableType=10, W1-W5, NOTE W1 @S160 (Khkk_* skeleton ĐÃ prod, dark-launch ĐẢO — menu public, ComingSoon→trang thật mới là "bật").
  • src-QT-TRINH-KY-HD.txt: bảng 2 = 9 bước ISO (Lựa chọn→Soạn thảo→Góp ý→Đàm phán→In HĐ→CCM→BOD→Đóng dấu→Phát hành/Lưu trữ) ↔ ContractPhase 1-9 khớp 1:1 (xác nhận S157 lens-fidelity).
  • src-RG-001-v02-ma-so-hd.txt (2.907B): format A/B/SOL&C/D + 6 ví dụ HĐ + 2 dạng PO (X/Y/PO Z theo nguyên tắc · A/PO/SOL&C/D không nguyên tắc).

(mục chi tiết append dần bên dưới — mỗi §=1 câu hỏi)


§1 — Q1: MAP TÁI DÙNG b.13→21 → Contract V2 (mọi dòng RE-VERIFY trên đĩa 07-29, KHÔNG chép mù S156 — line đã TRÔI vì arc-2 thêm ListDeleted)

Khung đọc: GĐ3 = b.13→18 (owner @S159) chạy trên state machine 5-trạng-thái ContractPhase.cs:14-28 (sống: DangSoanThao=2 · ChoDuyet=10 · DaPhatHanh=9 · TraLai=98 · TuChoi=99; 7 phase [LEGACY] = chính các trạm 13-21 đã gỡ CỐ Ý Mig 21). GĐ4 = b.19→21. Duyệt V2 = ContractWorkflowService.ApproveV2Async (ContractWorkflowService.cs:217-394) cookie-cutter PE: OR-of-N :259-260 · UPSERT ContractLevelOpinions :292-316 · skipToFinal :320-352 · terminal gen-mã+DaPhatHanh :366-385.

b Việc (theo QT/PDF) Phase/feature hiện có Trạng thái Chứng (file:line, đo 07-29)
13 PMH soạn draft HĐ từ giá chốt KHKK DangSoanThao; 2 đường tạo: bridge PE→HĐ + create thường (create thường ĐÃ nhận pin V2 ContractFeatures.cs:109; gen MaHopDong NGAY tại create :113-118 — RG-001 có mã từ nháp, KHÔNG đợi terminal) PARTIAL Bridge pin V1-only CreateContractFromEvaluationFeatures.cs:68-71,108 + KHÔNG đọc giá KHKK (giaTri=SUM Quote.IsSelected :88-90) → chi tiết §2
14 BCH bổ sung/hoàn thiện draft UpdateContractDraftCommand guard duy nhất Phase==DangSoanThao (ContractFeatures.cs:156-157), KHÔNG check drafter ⇒ BCH SỬA được về cơ chế PARTIAL Nhưng BCH KHÔNG XEM được: view-guard GetEligiblePhases (:334-351) map DangSoanThao chỉ cho Drafter/DeptManager (:342-343); role ProjectManager chỉ được DangGopY legacy. Command cũng KHÔNG có ApprovalWorkflowId (:136-145) ⇒ 0 đường re-pin V2 sau tạo
15 Đàm phán với TP (ngoài hệ thống) Không phase riêng (DangDamPhan=LEGACY đã gỡ) — ghi vết = comment + attachment SẴN (triết lý Mig 21: trạm offline = chứng từ đính kèm) ContractPhase.cs:19 LEGACY; attachment upload KHÔNG phase-guard (ContractAttachmentFeatures.cs 0 hit guard Phase — chỉ changelog :84,:144)
16 TP ký nháy → BCH chuyển PMH (scan) Upload attachment mọi phase GAP nhỏ AttachmentPurpose (ContractAttachment.cs:5-11) = DraftExport/ScannedSigned/SealedCopy/Other — KHÔNG có "ký nháy" (InitialSigned). Enum-extend int-column 0-migration (tiền lệ PE ComparisonTable=4)
17 PMH trình ký; CCM kiểm tra + ký nháy Trình DangSoanThao→ChoDuyet (ContractWorkflowService.cs:70-88, init con-trỏ đôi :81-84); CCM = Step 1 của workflow V2 ApplicableType.Contract=3 — duyệt qua ApproveV2Async PARTIAL 🔴 3 lỗ: (i) guard trình đòi role DrafterDeptManager (:73-78) — PMH mang Procurement bị 403 (lớp "vai đi qua không có"); (ii) approver KHÔNG XEM được HĐ ChoDuyet — GetEligiblePhases 0 entry ChoDuyet; (iii) KHÔNG vào inbox — PhaseActorRoles (:421-430) 100% legacy, 0 entry ChoDuyet
18 CEO ký nếu >5 tỷ Step CEO trong workflow V2 (Level đích danh Director) — chạy được nếu CEO ký MỌI HĐ GAP (phần ngưỡng) CeoApprovalThreshold = cột chung ApprovalWorkflow.cs:43 nhưng grep ContractWorkflowService.cs = 0 hit (đọc trọn 443 dòng 07-29) — admin set vào workflow Contract-type bị NGÓ LƠ im lặng. AllowApproverFinalize cũng PE-only. Đường có sẵn 0-code: 2 workflow (có/không Step CEO) + user chọn lúc tạo
(kết GĐ3) Duyệt cấp cuối → DaPhatHanh terminal + gen mã nếu chưa có (:366-385) SẴN Terminal của V2 = "Đã phát hành" — nghĩa là b.19-21 GĐ4 xảy ra SAU terminal về mặt hệ thống
19 HR đóng dấu 2 lựa chọn: (a) HR = Step cuối workflow V2 (SẴN, chỉ cấu hình — terminal dời về sau HR); (b) tracking riêng trên vật thể "bản cứng" GĐ4 SẴN-config / GAP-nếu-tracking-riêng DangDongDau=8 LEGACY (ContractPhase.cs:23) — KHÔNG hồi sinh. §3 đề xuất
20 CCM scan bản cứng, lưu trữ AttachmentPurpose.SealedCopy=3 upload ở DaPhatHanh SẴN ContractAttachment.cs:9; upload không phase-guard ⇒ up được sau terminal
21 Chuyển phát nhanh bản gốc về TP + lưu Không field nào (mã vận đơn/số bản gốc/vị trí lưu) GAP (nhỏ — comment đủ nếu owner không đòi field) Contract.cs:8-70 đọc trọn: 0 field bản-cứng (không seal-date, không courier, không số-bản-gốc, không vị-trí-lưu-file-cứng)

Kết Q1: cơ chế duyệt (17-18) + terminal + attachment (15,16,20) = TÁI DÙNG được ngay sau khi vá 3 lỗ "đường ống" (view/inbox/vai-trình) — đúng verdict LAI S156, hôm nay re-verify còn nguyên. GĐ4 (19-21) KHÔNG có vật mang trong Contract entity — cần quyết định §3 (field-on-Contract vs entity mới).

Đếm endpoint (đo 07-29): ContractsController.cs = 30 endpoint (8 đọc + 22 ghi), class [Authorize] trần :13; ngoại lệ DUY NHẤT có policy: GET /deleted [Authorize(Policy="Contracts.Read")] :28-29 (arc-2 S159) — khuôn per-action ĐÃ CÓ TIỀN LỆ ngay trong file này.


§2 — Q2: BRIDGE KHKK chen giữa PE và Contract

Hiện trạng bridge (đo 07-29): POST /api/purchase-evaluations/{id}/create-contract (PurchaseEvaluationsController.cs:339-346, body {ContractType, TenHopDong, Bypass} :379-382 — KHÔNG có ApprovalWorkflowId) → handler CreateContractFromEvaluationFeatures.cs: guard Phase==DaDuyet + winners.Count>0 + pe.ContractId==null (:53-60); per-winner giaTri = SUM Quote.IsSelected (:88-90); pin V1-only WorkflowDefinitionId=activeWfId (:68-71,:108); gen mã atomic self-committing codegen (:78-115); idempotency pe.ContractId = contracts[0].Id (:145). Picker: GET /purchase-evaluations/approved-pending-contract (:335-337) → ListApprovedPurchaseEvaluationsQuery filter Phase==DaDuyet && ContractId==null (CreateContractFromEvaluationFeatures.cs:173).

(a) KHKK kế thừa từ PE (đã RATIFIED spec S157 §2.3 — chỉ tóm + anchor sống): header PurchaseEvaluationId loose-Guid + ProjectId denorm + DepartmentId (C5) + snapshot HoSoLink; Lines auto-sinh từ winners pe.Suppliers.Where(IsWinner) với PeReferenceAmount = SUM Quote.IsSelected per-winner snapshot 1 lần — copy đúng phép tính bridge cũ :88-90 + guard :53-60. Khuôn nguyên con nằm sẵn trong file bridge.

(b) Contract tạo từ KHKK kế thừa gì (spec §2.5 RATIFIED, W5): (i) giaTri = line.ApprovedAmount khớp w.SupplierId thay SUM :88-90 khi PE có plan DaDuyet + ContextNote lệch "giá KH x vs SUM-PE y" vào changelog (khuôn :121-140); (ii) +param ApprovalWorkflowId pin V2 — thiếu nó HĐ từ phiếu rơi nhánh V1: nếu 0 active WorkflowDefinition cho type đó → KẸT CỨNG ConflictException ContractWorkflowService.cs:115-116 (re-verify 07-29: đúng dòng def == null || def.Steps.Count == 0); (iii) phần còn lại giữ nguyên từ PE (TenHopDong/PaymentTerms→DraftData/BudgetManual* :101-107); (iv) 🆕 ghi ngược Line.ContractId = contract.Id per-winner (cột C6 spec đã có chỗ) — vòng lặp :82-115 đã per-winner nên chỗ ghi tự nhiên.

(c) Sửa bridge cũ hay thêm bridge mới → SỬA BRIDGE CŨ (1 file), KHÔNG đẻ bridge thứ 2 — đây là hướng spec W5 đã ratify; lý do kỹ thuật đứng vững hôm nay: entry-point PE giữ idempotency pe.ContractId (:59-60,:145) + FE nút tạo HĐ đã tồn tại màn PE; bridge mới từ KHKK = endpoint thứ 2 cùng nghĩa (2 chỗ sửa về sau — lớp lỗi 2-site S155). Bổ sung DUY NHẤT cho dry-run: màn KHKK detail (DaDuyet) thêm nút deep-link gọi CÙNG endpoint cũ — 0 endpoint mới, 0 handler mới. (Phương án B ghi để review chốt: POST /contract-signing-plans/{id}/create-contracts riêng — chỉ đáng nếu owner muốn 1 KHKK chia nhiều đợt HĐ; hiện 0 yêu cầu.)

(d) Cardinality chuỗi PE↔KHKK↔Contract:

Cặp Ràng buộc Cơ chế enforce
PE → KHKK 1 PE — ≤1 plan SỐNG (1-N lịch sử, tạo lại được sau TuChoi) app-level AnyAsync(p.PeId==x && p.Phase!=TuChoi) → 409 (spec §2.3; KHÔNG đặt được UNIQUE-filtered theo Phase động — đúng lựa chọn app-level)
PE → Contract 1 PE — N HĐ (multi-winner Mig 58) pe.ContractId = contracts[0].Id :145 + guard :59-60 "đã tạo HĐ rồi"
KHKK → Contract 1 plan — N Lines (UNIQUE filtered PlanId+SupplierId) — mỗi Line ↔ ≤1 HĐ Line.ContractId Guid? (C6) ghi tại bridge; truy ngược HĐ→plan qua Line
⚠️ đường tắt PE → Contract KHÔNG QUA KHKK vẫn sống (Q11 mặc định = cảnh báo mềm) dry-run phải chấp nhận 2 đường song song cho tới khi owner nâng cứng Q11

Pattern tái dùng cite được: (1) pull-model picker ListApprovedPurchaseEvaluationsQuery:173 — nhân bản đổi vị ngữ && chưa-có-plan-sống cho màn "PE chờ lập KH" (W2 spec cũ); picker tạo-HĐ đổi nguồn thành plan DaDuyet (hoặc giữ PE + banner có/không plan — Q11). (2) Multi-entity create + self-committing codegen atomic :78-82 comment + loop. (3) Auto-Lines từ winners :56-62,:82-91.