Files
solution-erp/.claude/workflows/runs/2026-08-04-S171-khkk-ui-mirror-pe/sub-implementer-backend-s171.md
2026-08-04 19:46:34 +07:00

11 KiB
Raw Blame History

sub-implementer-backend-s171 — nhật-ký lane BE (Nhóm 1, BE-1 → BE-7)

Run: 2026-08-04-S171-khkk-ui-mirror-pe · vai implementer-backend · spec §②-D + §③ Nhóm 1 Luật ghi: sau MỖI file sửa xong ⇒ append ngay 1 dòng <path> — <đã làm gì> — <acceptance nào phủ>.

0. Trạng thái

  • BE-1 DetailDto +5 field
  • BE-1b FinalizeStepName/FinalizeLevelName DẪN XUẤT + 3-ngả (in-memory thay 3 subquery — khai ở §2, và SỬA công thức con-trỏ cho đúng KHKK)
  • BE-2 ContractSigningPlanApprovalDto (đẻ mới) + projection
  • BE-3 tên người duyệt cho Approval (join Users — đường thứ ba, lý do §2)
  • BE-4 WorkflowStepDto +DepartmentName
  • BE-5 changelogs endpoint policy KeHoachKyKet.Read (+ rào hàng EnsureCanViewAsync)
  • BE-6 map Cấp cho Approvals (StepOrder/StepName/LevelOrder/LevelName)
  • BE-7 enum +ApprovalAttachment=3 + endpoint upload riêng (policy Update)
  • Mirror types/khkk.ts ×2 app — SHA256 trùng khít
  • dotnet build sạch (0 warning / 0 error) + tsc -b ×2 app exit 0
  • 🔴 B-3 acceptance RUNTIME — treo, DB dev 0 phiếu (§4.1)

1. Đọc / khảo sát

  • Spec đọc TRỌN. 3 QĐ owner nhận: (1) panel 1 = cây + 4 mitigation, (2) panel 3 chưa chọn = hộp dashed, (3) file đính kèm khi duyệt = LÀM THẬT.
  • Nhóm 1 chỉ đụng BE ⇒ QĐ-1/QĐ-2 là việc của lane FE; lane này phục vụ bằng dữ-liệu (BE-1..BE-7). QĐ-3 = BE-7, làm thật.
  • Đo được (khác spec, ghi để lane sau không chép sai):
    • POST {id}/attachments ĐÃ TỒN-TẠI, policy KeHoachKyKet.Update, handler UploadContractSigningPlanAttachmentCommandHandler KHÔNG có guard owner/phase ⇒ người duyệt (có Update) upload được. BE-7 chỉ cần enum + 1 đường riêng ép Purpose.
    • ContractSigningPlansController TRƯỚC S171: R = 18 action, ^\s*\[Authorize\(Policy = 19 = 1 + R (đúng số spec nêu). Không có [Authorize] trần.
    • ApprovalDecision: Pending=0 · Approve=1 · Reject=2 · AutoApprove=3 (Domain/Contracts/ApprovalDecision.cs).
    • ApprovalWorkflow.Version (:24) có thật ⇒ WorkflowVersion lấy trực tiếp.
    • 🔴 KHKK ≠ PE ở con-trỏ Bước: CurrentWorkflowStepIndex là INDEX 0-based vào list ĐÃ SORT (ContractSigningPlanWorkflowService.cs:26,422-435), KHÔNG phải Step.Order - 1 như PE. Chép nguyên công thức PE = sai khi Order không tuần-tự.

2. Nhật-ký file sửa

  • src/Backend/SolutionErp.Application/ContractSigningPlans/ContractSigningPlanFeatures.cs — (a) ContractSigningPlanWorkflowStepDto +DepartmentName (append cuối, default null) [BE-4/B-4]; (b) đẻ mới ContractSigningPlanApprovalDto 13 field (kèm StepOrder/StepName/LevelOrder/LevelName dẫn xuất từ ApprovalWorkflowLevelId loose-Guid) [BE-2/BE-6/B-2]; (c) ContractSigningPlanDetailDto +5: WorkflowVersion · EndedByLevelFinalize · FinalizeStepName · FinalizeLevelName · Approvals [BE-1/B-1].
  • …/ContractSigningPlanFeatures.cs (handler GetContractSigningPlanQueryHandler) — (a) levelMeta +LevelName; (b) join Departments 1 lượt cho DepartmentName của Bước (không N+1) [BE-4]; (c) BE-1b DẪN XUẤT finalize step/level + 3-ngả EndedByLevelFinalize in-memory (cây đã nạp) thay 3 subquery của PE — cùng ngữ nghĩa, bớt 3 lượt truy, và SỬA công thức con-trỏ theo INDEX 0-based của KHKK; (d) query ContractSigningPlanApprovals + join Users lấy tên [BE-2/BE-3]; (e) call-site new ContractSigningPlanDetailDto(:784) truyền 5 field mới.
  • …/ContractSigningPlanFeatures.cs (cuối file, Region 8 mới) — ContractSigningPlanChangelogDto + ListContractSigningPlanChangelogsQuery + handler EnsureCanViewAsync (rào hàng, KHÔNG chép lỗ #82 của PE) [BE-5].
  • src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlanAttachment.cs — enum +ApprovalAttachment = 3, KHÔNG đánh số lại Other = 99 [BE-7].
  • src/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs — (a) GET {id}/changelogs policy KeHoachKyKet.Read [BE-5/B-5]; (b) POST {id}/approval-attachments policy KeHoachKyKet.Update, ép Purpose=ApprovalAttachment ở BE (FE không truyền số enum) [BE-7/QĐ-3].

🔴 BE-3 — khai lý do chọn đường THỨ BA (spec cho 2 lựa chọn)

Spec: "lấy tên từ changelog cùng transition hoặc denorm cột tên vào Approval. Chọn 1, khai lý do." Tôi chọn join Users, vì:

  1. Ghép theo changelog KHÔNG CÓ KHOÁ CHÍNH XÁC: Approval.ApprovedAt = now của domain (ContractSigningPlanWorkflowService.cs:532), còn Changelog.CreatedAt do AuditingInterceptor đóng lúc SaveChangeshai giá trị lệch nhau vài ms. Ghép được thì phải dùng "bucket thời gian" — đúng cái hack 5s mà PE đang phải chịu ở FE (PeDetailTabs.tsx:3152-3192), tức nhập thêm nợ chứ không trả nợ.
  2. Denorm cột tên = cần MIGRATION (Mig 72) — ngoài phạm vi đợt UI này, và đợt này không có mục migration nào khác để đi kèm.
  3. Chi phí thật của join: 1 query batch trên tập DISTINCT ApprovedByUserId của đúng 1 phiếu (thực tế ≤ ~6 người). Handler này vốn đã multi-query (pe · project · dept · drafter · suppliers · workflow), không phải projection 1 phát.

3. Tự kiểm B-1 · B-2 · B-4 · B-5 · B-6 (số ĐO được, không phải tuyên bố)

Thước Yêu cầu Đo được Kết
B-1 5 tên mới trong record ContractSigningPlanDetailDto -A 80 WorkflowVersion :216 · EndedByLevelFinalize :217 · FinalizeStepName :218 · FinalizeLevelName :219 · List<ContractSigningPlanApprovalDto> Approvals :220 5/5
B-2 grep -c ContractSigningPlanApprovalDto src/Backend ≥ 2 (hôm nay 0) 3 (1 định-nghĩa + 1 field DetailDto + 1 call-site projection)
B-3 🔴 RUNTIME: phiếu DaDuyetapprovals[] có ≥1 Decision=Reject approvedByFullName ≠ null ∀ phần tử CHƯA CHẠY ĐƯỢC — xem §4 treo
B-4 DepartmentName trong …WorkflowStepDto ≥ 1 1
B-5 grep -n changelogs <controller> = 1 hit + [Authorize(Policy = "KeHoachKyKet.Read")] liền kề 1 hit (:91), policy ở :92 ngay dưới [HttpGet] — đúng convention file (mọi action đều [Http*] rồi [Authorize(Policy…)])
B-6 R = ^\s*\[Http(Get|Post|Put|Delete|Patch) · ^\s*\[Authorize\(Policy = 1+R · ^\s*\[Authorize\]\s*$ = 0 R = 20 (18 cũ + changelogs + approval-attachments) · Policy = 21 = 1+20 · trần = 0
build dotnet build SolutionErp.slnx Build succeeded · 0 Warning · 0 Error (lượt 1 FAIL CS1739 dossierItemId → sửa thành DossierItemId, lượt 2+3 sạch)
tsc fe-admin + fe-user tsc -b exit 0 cả hai (types-only, mọi consumer đọc từ response — không nơi nào dựng object literal)
E-1 mirror types/khkk.ts SHA256 aa4103de6362e67e68d026e125fe65a33efb92af4bd6ab811d6e0d9db3610812 ×2 app trùng khít
E-2 0 hit 4 path cấm + 2 path nhạy (hooks/usePipelineStages.ts, components/pipeline/) diff lane này = đúng 5 file (3 BE + 2 types/khkk.ts), 0 path cấm, KHÔNG chạm hook/pipeline

🧪 Fault-injection — chứng thước B-6 CÓ RĂNG (không chỉ happy-path PASS)

Cắm 1 action [Authorize] trần vào cây tạm (…/scratchpad/injected.cs, KHÔNG đụng repo):

  • gauge 3 ^\s*\[Authorize\]\s*$: 0 → 1 ⇒ FLAG đúng.
  • gauge 2 đồng thời vỡ: R 21 nhưng Authorize(Policy) vẫn 21 ≠ 1+R=22 ⇒ FLAG lần hai. ⇒ Cả 2 vế của thước đều bắt được ca xấu; số xanh ở bảng trên KHÔNG phải do thước mù.

🔸 1 chỗ tôi tự sửa để thước B-5 khỏi bị bơm tử-số

Comment cảnh-báo #82 của tôi ban đầu chứa literal changelogsgrep -c ra 2 trên file 100% đúng — đúng lớp citation-trap mà chính spec vừa phải sửa cho B-6. Đã đổi chữ trong comment (endpoint lịch-sử), giữ nguyên nội dung cảnh-báo. Đo lại = 1.

4. Chưa làm / lý do (khai thẳng — lỗ có khai > báo xanh mà sai)

  1. B-3 (RUNTIME) — CHƯA CHẠY. Lý do đo được, không phải phỏng đoán: DB dev (localdb)\MSSQLLocalDB / SolutionErp_DevContractSigningPlans = 0 hàng, DaDuyet = 0, ContractSigningPlanApprovals = 0 hàng ⇒ không tồn tại phiếu nào để gọi thật. Cần chạy ở UAT (lane E-5/E-6, fe-user:8080). Cận-dưới đã có từ code, KHÔNG thay được B-3: LogSigningPlanTransitionAsync ghi Approvals thẳng trong thân hàm, không if nào bọc (…WorkflowService.cs:534), 6 call-site phủ trọn (:204 :283 :296 :311 :325 :393:393 phủ CẢ Trả lại lẫn Từ chối cùng ApprovalDecision.Reject); bộ lọc của tôi chỉ bỏ Pending ⇒ hàng Reject không thể bị nuốt. approvedByFullName null CHỈ khi ApprovedByUserId null (transition do system/SLA) hoặc user đã bị xoá — không có đường nào khác.
  2. Không đụng api/khkk.ts của FE. Lane FE (nhóm 2-3) sở hữu file đó và đang sửa song song; tôi ghi hợp-đồng endpoint ở §5 thay vì chen vào (tránh đụng độ). Không phải "đã wire" — FE còn phải gọi.
  3. Không viết test. Theo docs/rules.md §7: đây là feature mới ⇒ test-after (UAT ổn rồi viết), không phải bug-fix/critical-algorithm. dotnet test chưa chạy trong lane này (E-4 là thước của lane reviewer, và build agent nền khác đang chạy — bài #68).
  4. BE-3 chọn đường THỨ BA so với 2 lựa chọn spec đưa — lý do đầy đủ ở §2, tóm: ghép-theo-changelog không có khoá chính xác (2 timestamp khác nguồn), denorm cột cần migration ngoài phạm vi.

5. Hợp-đồng cho lane FE (literal — khỏi đoán)

  • GET /api/contract-signing-plans/{id}KhkkDetailDto +5: workflowVersion: number|null · endedByLevelFinalize: boolean · finalizeStepName: string|null · finalizeLevelName: string|null · approvals: KhkkApprovalDto[]; và mỗi workflowSteps[] +departmentName: string|null.
  • GET /api/contract-signing-plans/{id}/changelogs?take=200KhkkChangelogDto[] (mới nhất trước). Policy KeHoachKyKet.Read.
  • POST /api/contract-signing-plans/{id}/approval-attachmentsmultipart, field file (+ note tuỳ chọn). BE tự ép purpose = ApprovalAttachment (3); FE KHÔNG gửi purpose. Policy KeHoachKyKet.Update (cùng policy với /transitions ⇒ người duyệt đã có quyền).
  • Khối "📎 File đính kèm khi duyệt" lọc attachments.filter(a => a.purpose === KhkkAttachmentPurpose.ApprovalAttachment), rỗng ⇒ tự ẩn (D-9 vế 2).
  • approvals[] BE đã lọc decision = Pending ⇒ FE KHÔNG dedupe lại; (N) = approvals.length là cùng nghĩa với (N) của PE (D-11).

END implementer-backend-s171 — 7/7 mục BE xong (B-3 acceptance RUNTIME còn treo, đã khai) · build PASS