Files
solution-erp/.claude/workflows/runs/2026-07-17-S134-pe-luyke-tam-tinh/sub-reviewer-0.md
2026-07-17 14:27:19 +07:00

10 KiB
Raw Blame History

sub-reviewer-0 — adversarial spec review PE lũy kế tạm-tính (S134)

Ghi-đĩa-trong-lúc-làm (chống #53). Append từng issue khi tìm thấy. Return = digest.

Trạng thái

  • Đọc spec + investigator
  • Đọc code BE (accumulator :29-71 / features :880-1009 / workflow service :280-321, :840-949, :998-1041, :1112, :1128-1155 / seeder :1290-1352 / entity changelog / DTO :331-353)
  • Đọc code FE (PeDetailTabs :1290-1600 / PeWorkflowPanel :95-134, :400-539 + SHA tự đo 6 file)
  • 8 trục verdict — DONE, xem cuối file

VERDICT: GO_WITH_FIXES (0 Critical / 2 Major spec-fix / 9 nits — Major đều sửa bằng chữ trong spec trước khi hmw chạy)

Issues (append dần)

Trục 3 (D4) — kết quả soi từng nghi vấn

  • (a) CLEAR: seeder DbInitializer.cs:1328-1341 set snapshot TRỰC TIẾP (mirror thủ công, KHÔNG gọi helper) → D4 changelog-in-helper KHÔNG gây changelog rác lúc seed/restart. #75-class không xảy ra.
  • (b) CLEAR: 5 call-site đều là finalize thật (Phase → DaDuyet): :311 admin-override chỉ gọi helper khi targetPhase == DaDuyet; :865 level-finalize; :904 CCM-miễn-CEO; :937 all-steps-done; :1112 V1 legacy. Note "Duyệt khi N phiếu trước chưa duyệt" đúng ngữ cảnh cả 5.
  • (c) SaveChanges: mọi path qua helper đều save — V2/V1 qua TransitionAsync:301, admin-override :316. OK.
  • [M1] D4 thiếu shape đầy đủ cho changelog row: helper ApplyBudgetSnapshotOnFinalizeAsync(evaluation, ct) KHÔNG có actorUserId. PurchaseEvaluationChangelog (mirror LogTransitionAsync:1145-1155) cần EntityType/Action/PhaseAtChange/UserId/UserName/Summary/ContextNote. Spec chỉ nói ContextNote. Nếu hmw tự thêm param actor vào helper → ĐỔI 5 call-site → vi phạm chính acceptance A6 "0 đổi 5 call-site". Spec PHẢI chốt: UserId=null + UserName="Hệ thống" (audit actor tái dựng từ row Transition cùng batch) + EntityType/Action/PhaseAtChange value cụ thể — nếu không hmw sẽ tự quyết 2 hướng ngược nhau.
  • [N1] AddAsync sai pattern codebase: LogTransitionAsync:1145 dùng db.PurchaseEvaluationChangelogs.Add(...) (sync). Spec ghi "AddAsync" — hoạt động nhưng lệch pattern; nên ghi .Add(...).
  • (d) Chi phí: ComputePendingAsync ~3 query (list + delta1 + delta2), spec ghi "+1 query" là under-count — vẫn chấp nhận được (finalize tần suất thấp). Note.

Trục 2 — construction site

  • Grep new PeBudgetSummaryDto toàn repo = ĐÚNG 2 site Features.cs:946/:975 (khớp spec A2). Session log S74 xác nhận pattern grep này (gotcha #65). Tests KHÔNG construct DTO (chỉ 1 comment PeBudgetFreezeTests.cs:21) → append-cuối không gãy compile chỗ nào.
  • [M2] Banner PeWorkflowPanel KHÔNG tự ẩn trên phiếu terminal — mâu thuẫn nội tại spec: predicate banner spec item 7 = pendingPriorPes.length > 0 && !budgetFrozen — KHÔNG có gate readOnly/phase. Phiếu TraLai/TuChoi/Nháp đi nhánh live (Features.cs:957) → pending fields FILL → banner "cần duyệt phiếu trước trước phiếu này" hiện trên phiếu ĐÃ từ chối/trả lại = sai ngữ cảnh. Edge-6 spec claim "banner duyệt tự ẩn (không còn action duyệt)" — SAI với predicate đã cho (banner không phụ thuộc action; V2-banner :418 có !readOnly, action buttons :439 có next.length > 0 — banner spec không kế thừa gate nào). Fix: thêm !readOnly && evaluation.phase === ChoDuyet (hoặc tối thiểu !readOnly && next.length > 0) vào predicate banner + sửa câu edge-6 cho khớp. Dialog-block đã tự gate qua isApproveAction — không dính.

Verify PASS (positive — chống trượt kép)

  • SHA tự đo (git hash-object): PeDetailTabs 2-app IDENTICAL 5534addb9a78... khớp spec; PeWorkflowPanel IDENTICAL a83a58e1d8e1... ; types KHÁC 2bf7736ab4a2ec (spec errata lead đúng — số investigator cũ 47c97df1/9d88e671 stale). Measured-label gate PASS.
  • Trục 1 (chính-xác bất động): ComputeAsync untouched ⇒ mọi consumer bất động — live :962, snapshot :1018, backfill Mig 67 one-time, seeder direct-set :1334-1340. ComputePendingAsync chỉ ĐỌC (AsNoTracking window y hệt) + fill field MỚI + AddAsync changelog — không mutate field cũ nào. Không có đường lệch số cũ.
  • Soft-delete peer: PurchaseEvaluationConfiguration.cs:84 HasQueryFilter !IsDeleted tự áp cho ComputePendingAsync (cùng DbSet).
  • FE công thức khớp: row6 = row2 + row4 (:1318), r5 = row1 + r3 per-cột (:1325) → row5_tạm = r5+delta1, row6_tạm = row6+delta2 đúng đại số. Render sites lũy kế toàn FE = CHỈ PeDetailTabs (:1315-1316/:1498/:1503 + r5 qua colCalc) — grep 2 app không còn site nào khác (không print/export/list) → spec liệt đủ, S89 miss-2-sites không tái diễn.
  • Trục 4 predicate: delta1 NOT IN (1,7,10,99) = {98, legacy 2-6} đúng (10/7 đã trong row1 chính-xác — không double-count); delta2 NOT IN (1,7,99) ⊃ {10 có winner} = gap chính đề bài ✓ + {98, legacy} có winner hợp lý; pendingPriorPes NOT IN (7,99) ⊇ cả 2 delta-set + nháp list-only (D1) — 3 tập nhất quán. EF translate: !array.Contains(p.Phase) → NOT IN, enum non-null → không bẫy null-semantics.
  • Trục 5 dialog: block chèn TRÊN bộ chọn giá chỉ RENDER thêm — priceMissing (:514) + footer disabled (:522) không đổi → acceptance B3 "diff vùng disabled = 0" đo được.
  • D4 mọi path SaveChanges: V2/V1 → TransitionAsync:301; admin-override → :316. Changelog entity + DbSet có sẵn (IApplicationDbContext.cs:63).
  • Baseline test 520 khớp (S133 suite 520/0). A5/A6/B1/B2/B3/B4/C1-C9 đều có lệnh đo được.

Trục 6 — acceptance đo được: PASS có 1 gap

  • [N5] A3 live-JSON verify cần DATA chưa tồn tại: Dev DB multi-pair (cùng ProjectId+WorkItemId ≥2 phiếu) = 0 (invest PHẦN 5). A3/A4 curl cần dựng 2 phiếu cùng cặp trước — spec nên thêm 1 dòng "tạo 2 phiếu cùng Dự án × Hạng mục qua UI/API trước khi curl" để hmw không bịa acceptance đã-pass.
  • B2 "phiếu frozen KHÔNG render (gate đầu nhánh)" hơi code-read-feel — chấp nhận được vì C7 (test DTO defaults) + gate biến grep được; có thể thêm "grep điều kiện budgetFrozen xuất hiện trong block tạm-tính".

Trục 7 — edges THÊM ngoài 7 edge spec

  • Tie CreatedAt bằng nhau: strict < → 2 phiếu cùng timestamp không thấy nhau — pre-existing y hệt ComputeAsync (nhất quán, không mới). Không cần fix.
  • [N2] MaPhieu nullable: entity string? MaPhieu (:10, gen at create Features.cs:158 nhưng data cổ/nullable schema). DTO spec PePendingPriorPeDto(Guid Id, string MaPhieu, int Phase) non-null → nên string? + FE fallback "(chưa có mã)".
  • [N9] Danh sách pending KHÔNG render link: approver click link phiếu Nháp → 403 S89 guard (Features.cs:893-898) → toast lỗi khó hiểu. Spec nên chốt "plain text mã + badge, KHÔNG link".
  • Peer có winner nhưng 0 quote IsSelected: delta2 = 0 nhưng count đếm peer → sub-dòng "+0đ từ 1 phiếu" — mirror y semantics PrevSelectedCount hiện tại (chấp nhận, nhất quán).
  • DaDuyet-no-snapshot fallback [N7]: Features.cs:926-928 phiếu DaDuyet mà SnapshotAt null → nhánh live → BudgetFrozen=false → tạm-tính HIỆN trên phiếu DaDuyet dị dạng này. Pre-existing class (nhánh live đã hiện số live cho row1/2 từ S133) — sau backfill Mig 67 + 5 site + seeder về lý thuyết không còn row nào — ghi nhận để không ngạc nhiên UAT, không fix.
  • Performance: ComputePendingAsync ~3 query (list + delta1 + delta2) mỗi GET detail non-frozen + mỗi finalize — bảng PE nhỏ, peers per cặp ít, chấp nhận. Spec ghi "+1 query" là under-count [N4].
  • List dài không cap: thực tế peers/cặp < 10 — không cần cap; nếu muốn phòng thì .Take(20) — optional.

Trục 8 — mâu thuẫn nội tại / thiếu cho hmw

  • M1 (D4 shape) + M2 (banner gate vs edge-6) = 2 mâu thuẫn/thiếu chính — đã ghi trên.
  • [N1] Spec ghi AddAsync — pattern codebase = db.PurchaseEvaluationChangelogs.Add(...) (:1145). Dùng Add cho khớp.
  • [N3] Spec "map inline nhỏ nếu chưa có map dùng chung" — map ĐÃ CÓ: PurchaseEvaluationPhaseLabel (types cả 2 app, PeWorkflowPanel đang dùng :465) → chỉ thẳng dùng nó, khỏi inline.
  • [N6] Cite "mirror pattern LogTransition :1073-vùng" — thực tế LogTransitionAsync:1128-1155. Số dòng lệch (hmw grep ra được nhưng sửa cho sạch).
  • [N8] Sub-dòng "{n} phiếu chưa duyệt": chốt n = count TƯƠNG ỨNG delta (row1/5 → pendingSubmittedCount; row2/6 → pendingSelectedCount), KHÔNG dùng pendingPriorPes.length (list ⊇ delta-set, dùng nhầm → "từ 3 phiếu" khi delta chỉ từ 1).
  • INV-2 [A‖B] song song: DTO field names + JSON shape đã pin cứng trong spec → B không chờ A, race DTO-shape KHÔNG thật. OK.

CHECKLIST-8-TRỤC (verdict per trục)

  1. Chính-xác bất động: PASS — ComputeAsync untouched + 4 consumer enumerated + ComputePendingAsync read-only.
  2. R1-S133 frozen: PASS (2 site construction verified duy nhất; DTO append-cuối an toàn; fallback DaDuyet-no-snapshot = pre-existing note N7).
  3. D4-trong-helper: PASS-WITH-M1 — seeder direct-set (không gọi helper) nên KHÔNG changelog rác; 5 site đều finalize thật + SaveChanges đủ; NHƯNG spec thiếu shape row (actor/EntityType/Action/vị trí trong nhánh wiKey) → M1.
  4. Predicate mới: PASS — 3 tập nhất quán, không double-count, EF translate OK.
  5. FE: PASS-WITH-M2 — công thức + vị trí + priceMissing không đụng; banner thiếu gate readOnly/phase → M2.
  6. Acceptance: PASS — đo được trừ A3 cần seed data (N5).
  7. Edge sót: 5 edge mới soi — không cái nào block; 3 thành nits (N2/N7/N9).
  8. Mâu thuẫn/thiếu: 2 Major + 5 nits — đều sửa được bằng chữ trong spec, không cần đổi thiết kế.