Dong ca UAT goc (Tra Sol): "lo bam sai cai goi thau, quay lai khong duoc, phai
xoa thi no moi ko co luy ke len". Dot 1 (b1bae77) da dung NEN (co F6 + lenh sua
quy trinh tai cho); dot 2 la phan nguoi dung cham vao.
- App: PeSoftDeleteFeatures.cs MOI
- DELETE /api/purchase-evaluations/{id}/by-approver — 3 rao: phase ChoDuyet
^ dung luot actor ^ matchingLevel.AllowApproverDelete (PER-NGUOI, khong g.Any)
- GET /api/purchase-evaluations/deleted — IgnoreQueryFilters() KEM .Where(IsDeleted)
(IgnoreQueryFilters GO filter chu khong DAO — thieu Where la liet ke ca phieu song)
+ tai lap khoi IDOR cua list hien hanh
- ghi vet ChangelogAction.Delete + ly do
- Api: 2 endpoint moi. 🔴 by-approver CO Y KHONG co [Authorize(Policy)] — do tren DB:
moi vai dang lam nguoi duyet (Procurement/CostControl...) deu CanDelete=0 va khong
seeder nao nang co do => gan policy la 403 voi dung nguoi can dung, ma FE gate cung
quyen nen khong thay nut khong thay loi (gotcha #44). Quyen = 3 rao handler; co F6
la admin tick dich danh tung nguoi => chat hon quyen theo vai.
- Dto: ApprovalWorkflowOptionsDto + AllowApproverDelete. Thieu field nay thi FE gate
luon sai => nut KHONG BAO GIO hien, ma BE lan FE deu build sach (reviewer bat).
- Domain/Infra: 2 menu key Pe_{code}_Approved / _Deleted (KHONG vao MenuKeys.All =>
giu 54/216) + seed tree + labelBackfill "Duyet" -> "Dang duyet" + 2 danh sach permission
- FE x2 app: nut Xoa trong PeWorkflowPanel (md5 mirror khop) + dialog ly do + Layout
regex/route cho 2 muc moi + man "Da xoa" chi-xem
- Tests: +11 (562 tong, 0 fail). T27 khoa CA LOP loi entity-co-co-ma-DTO-khong-co
(fault-inject xac nhan: go field ra => T27 do ngay)
Review: 12 FLAG, chot chan H1 (DTO thieu field) da va. Deploy KHONG co migration
nhung CO seed menu => phai restart API.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nen cho tinh nang "xoa phieu PE o man duyet" (UAT: phieu bam sai goi thau dang
an luy ke). Dot 1 lam NEN, dot 2 moi lam nut xoa.
Vi sao phai lam nen truoc: phieu dang chay pin ApprovalWorkflowId va doc workflow
SONG theo Id do. Moi POST tao workflow moi = Id MOI, nen tick co tren ban moi
KHONG BAO GIO toi duoc phieu dang treo. Phai co lenh sua-tai-cho giu nguyen Id.
- Domain: ApprovalWorkflowLevel + AllowApproverDelete (F6, per-NGUOI) + Mig
20260727033522_AddPeAllowApproverDelete (AddColumn bit default false, no new table)
- Application: UpdateAwDefinitionCommand + Validator + Handler
- 2 phep thu: (T1) khung khong doi - Order tung row bat bien
(T2) luat choi khong doi - cam CeoApprovalThreshold, AllowApproverFinalize,
Code, ApplicableType => 409 bao tao ban moi
- diff theo Level.Id (CAM delete-then-insert: no FK Restrict + mo coi LevelOpinion)
- guard 2 tang chu ky: tang 1 chu ky tren ho so CON SONG; tang 2 chu ky CON LUU
(cha xoa mem hoac opinion xoa mem) - ca hai deu CHAN, khong xoa cung
- purge chi khi ho so cha KHONG CON ROW; boc transaction (ExecuteDelete tu commit)
- KHONG tang Version, KHONG dung IsActive, KHONG doi Id
- Api: PUT /api/approval-workflows-v2/{id} + [Authorize(Policy="Workflows.Create")]
- Infra: message 403 in TEN NGUOI thay GUID tran
- FE: Designer tach "Sua tai cho" (PUT) vs "Tao phien ban moi" (POST) + o tick F6
- Tests: +21 (551 tong, 0 fail) - gom F12 chung minh phieu dang chay nhan thay doi ngay
Review: 14 FLAG, 3 HIGH da va (ExecuteDelete ngoai transaction / purge xoa cung
chu ky phieu xoa mem / endpoint khong co day FE goi toi).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Project Create/Update: LƯU chỉ Trim (giữ mã như người dùng gõ — owner chốt), nhưng kiểm-trùng so trên dạng bỏ-dấu-cách (Code.Replace(' ','')) để chặn tạo/sửa thành bản trùng chỉ khác khoảng trắng ('FLOCK 01' vs 'FLOCK01'). +4 test guard ProjectCodeNormalizationTests. Đây là vá gốc cho vụ dự án trùng vừa gom trên prod (5 phiếu + 5 ngân sách trỏ lại, 3 bản dấu-cách ẩn mềm).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Van de (UAT anh Kiet FDC, S118): user tra.bui (Bui Le Thuy Tra, Procurement TP) dang nhap khong
thay menu quan ly NCC/Suppliers. Nguyen nhan: seeder S92 RevokeTemporarilyHiddenModulesAsync (chay
moi startup) set CanRead=false cho Master/Suppliers/Projects/Departments/Catalog* voi moi role
non-Admin ("chi Admin thay").
Fix (owner chot mo ngoai-le CHI role Procurement, ben qua deploy):
- Them SeedProcurementMasterAccessAsync chay NGAY SAU revoke de THANG (mirror pattern Hrm S65 /
Office S69). Chi dung role Procurement -- S92 "chi Admin thay" GIU NGUYEN cho moi role khac.
- Grant 9 key: Suppliers (R+C+U -- quan ly + cong bo NCC, S113 publish can Suppliers.Update);
Master/Projects/Departments/Catalogs + 4 Catalog leaf (Read-only). KHONG Delete.
- Dashboard/Reports CO Y KHONG cap (reviewer bat: Reports lo tong gia tri HD + top NCC/du an theo
gia tri, nguoc S92; owner chot bo).
- Upgrade-only idempotent: nang co desired len true, khong ha co, khong dung role khac.
Test: +4 regression scope-isolation (ProcurementMasterAccessSeedTests) -- Procurement duoc nang
dung ma tran, role khac (DeptManager rieng) VAN bi revoke. 501 -> 505.
Reviewer PASS (scope cach-ly sach, no over-grant). Grant tam da ap prod (tra.bui verified 20 menu);
deploy nay = ban ben. Follow-up: gate ReportsController thieu policy (task rieng).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hai chỉnh từ buổi UAT anh Kiệt FDC (S117):
1) Fix hiển thị lũy kế "duyệt rồi không bắt" — root cause là DISPLAY-LIE, không phải
lũy kế bug. endsBeforeCeo cho phiếu CHƯA DaDuyet nay POINTER-AWARE: chỉ báo "kết
thúc trước CEO" khi con-trỏ còn ở/trước cấp finalize. Phiếu đã đi QUA cấp finalize
(approver bỏ tick "Cấp này KẾT THÚC" → trình tiếp CEO) → false: sơ đồ hết làm mờ
Bước CEO + hết nói dối "Kết thúc tại Cấp X" khi phiếu đang chờ CEO. 3 site (detail
in-memory + list/inbox EF-subquery). Filter lũy kế GIỮ nguyên (đúng nguyên tắc
"duyệt rồi mới bắc"). BE-only — FE là consumer thuần nên tự render đúng.
2) Feature "duyệt theo KHOẢNG Min–Max": khi PRO có cả Min & Max, người duyệt cấp cuối
chọn CẢ HAI (radio "PRO — cả Min và Max", source=ProMinMax) → Min ở ApprovedPriceAmount
+ Max ở ApprovedPriceMaxAmount (Mig 66 AddColumn nullable, no backfill, phiếu cũ
nguyên vẹn). Bất-biến Max⟺ProMinMax, Min≤Max (guard ở service + validator). HĐ.GiaTri
KHÔNG đọc giá chốt (= SUM báo giá NCC được chọn) → range thuần record-of-decision
hiển thị, 0 tác động HĐ. FE 2-app SHA-mirror (Panel + DetailTabs).
Test 495 → 501 (+1 pointer-aware repro, +5 ProMinMax). Cả 2 điều tra qua /fable-real
engine-đắt (investigator-codebase deep-pass) theo yêu cầu anh Kiệt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Bug: phiếu có cấp AllowApproverFinalize nhưng approver opt-out → lên CEO thật + CEO duyệt, nhưng sơ đồ báo sai "kết thúc tại CCM, không qua CEO" + làm mờ Bước CEO (endsBeforeCeo tính theo CONFIG workflow)
- Fix: +field runtime EndedByLevelFinalize (set true CHỈ ở nhánh level-finalize service) → EndsBeforeCeo cho phiếu DaDuyet đọc runtime thay config; ChoDuyet giữ heads-up config
- BE: entity + EF config + service + 4 projection (detail/List/Inbox/ListApproved) runtime-aware
- Mig 60 AddPeEndedByLevelFinalize + backfill (dò marker "Duyệt KẾT THÚC tại" set phiếu finalize-sớm cũ; ccm-delegation không match → giữ false)
- FE: gate làm-mờ finalizeFlowStep theo endsBeforeCeo (2 app SHA-mirror b7abbc2f)
- Test 440 PASS (repro peFinButWentCeo: config-finalize + EndedByLevelFinalize=false → false) + reviewer PASS 0-issue
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug (anh Kiệt/team, screenshot): drafter=PRO vẫn điền được giá đề xuất Min/Max SAU khi cấp trên (chị Trà) đã duyệt — "duyệt là fix".
Root cause: UpdatePeSuggestedPricePro/CcmCommandHandler cố ý KHÔNG ràng Phase (Mig 54 "chỉnh như tài liệu sống") → sửa được mọi phase kể cả DaDuyet.
Fix:
- BE: +phase-guard — non-admin chỉ sửa giá đề xuất PRO/CCM khi phiếu ở Nháp (DangSoanThao) / Trả lại (TraLai); đã gửi duyệt (ChoDuyet) / đã duyệt (DaDuyet) / từ chối → ConflictException. Admin override (sửa-sai). Role-gate vẫn chạy trước.
- FE (2 app): SuggestedPriceRows gate canEditPro/Ccm thêm && isEditablePhase(ev.phase) → ẩn ô sửa (read-only) khi phiếu đã gửi duyệt. Đồng bộ BE.
- Test: +4 (PRO ChoDuyet->Conflict no-mutate, PRO DaDuyet->Conflict, Admin DaDuyet->override-set, CCM DaDuyet->Conflict); sửa SeedPeAsync default->DangSoanThao (spec change). 436->440 PASS.
Delete phiếu sau duyệt: ĐÃ chặn sẵn (FE nút Xóa chỉ hiện DangSoanThao + BE delete-guard block non-draft) — không cần sửa.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- RevokeTemporarilyHiddenModulesAsync mở rộng predicate: +Contracts/Ct_*
+Master/Suppliers/Projects/Departments/Catalog* (cộng dồn S58 Hrm*/Off*/Personal).
- Tắt 2 re-grant public cũ: SeedAllRolesHrmProfileReadPermissions (S65) +
SeedAllRolesOfficeModulePermissions (S69). Revoke là bước permission CUỐI trong
SeedAdminPermissionsAsync (ungated) → durable, re-ẩn mỗi restart prod (gotcha #75).
- Thu hẹp InReviewScope → chỉ Pe_* (Duyệt NCC giữ nguyên cho user thường;
master-data GET = any-auth nên form PE vẫn chọn được Dự án/NCC/Hạng mục dù menu ẩn).
- 2 method grant giữ private static (reversibility) → 2 seed test cũ vẫn invoke cô-lập, pass.
- +AdminOnlyModulesRevokeTests (3 test-before-merge, security): non-Admin revoked 4 cờ /
Admin untouched / PE preserved. Test 431→434 PASS (45 Domain + 389 Infra).
- Reviewer PASS (5 attack vector: seed-order/under-revoke/over-revoke/test-genuine/prod-impact).
FYI: role CatalogManager (0 user hiện tại) cũng bị thu Danh mục — intended-by-spec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Go-live request anh Kiệt FDC (Zalo "user chưa tự đổi pass được hả em"): trước đây chỉ admin
Reset hộ (POST users/{id}/reset-password), user KHÔNG tự đổi được. Thêm self-service:
- BE: ChangePasswordCommand + POST /api/auth/change-password ([Authorize]). Lấy user qua
ICurrentUser.UserId; UserManager.ChangePasswordAsync verify mật khẩu HIỆN TẠI; sai →
ValidationException field CurrentPassword "Mật khẩu hiện tại không đúng."; rule mật khẩu mới
≥ 12 + khác mật khẩu cũ (validator); sau đổi vô hiệu refresh token (mirror ResetPassword).
Qualify SolutionErp...ValidationException (tránh CS0104 clash với FluentValidation).
- FE 2 app (SHA-mirror): ChangePasswordDialog (3 ô current/new/confirm + validate client:
≥12, khớp, khác cũ) wire vào menu tài khoản TopBar — "Đổi mật khẩu" trên "Đăng xuất".
- Test: +ChangePasswordCommandTests (đúng/sai pass cũ keyed-field + msg, <12 boundary, ==cũ,
chưa-auth/UserId-null/user-not-found, RefreshToken cleared). Full suite 431 PASS (45D + 386I).
Build: BE 0 warn/0 err · fe-user + fe-admin build OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phiếu mà quy trình duyệt có cấp bật AllowApproverFinalize (Mig 58) sẽ kết thúc tại cấp đó,
KHÔNG trình CEO. Hiển thị note cho MỌI NGƯỜI NẮM ở 3 surface (anh Kiệt FDC). KHÔNG migration
(cột AllowApproverFinalize đã có Mig 58, prod ở Mig 59).
BE (3 file, computed DTO):
- PurchaseEvaluationDtos.cs: +bool EndsBeforeCeo +string? FinalizeStepName +string? FinalizeLevelName
trên DetailBundle + ListItem; flow-level +bool AllowApproverFinalize.
- PurchaseEvaluationFeatures.cs: detail compute first-reachable (Step.Order↑→Level.Order↑, dept-name
in-memory); list + inbox projection qua 3 subquery EF-safe (lv.Step!.Name). V1 (workflowId==null) → false.
- CreateContractFromEvaluationFeatures.cs: ListApproved projection cùng DTO → +3 subquery y hệt.
FE 2-app (fe-user + fe-admin SHA-mirror component):
- PeFinalizeChip.tsx (MỚI, chip tím) ở 3 danh sách: PeListPanel + PurchaseEvaluationsListPage (cây) + InboxPage.
- PeWorkflowPanel.tsx: note callout trong phiếu, branch DaDuyet (past-tense) / đang-chạy; copy boundary-aware
"...là KẾT THÚC, không trình CEO. Các cấp trước vẫn duyệt như thường." (tránh cấp giữa hiểu nhầm).
- UserDashboardPage.tsx (chỉ fe-user): dải "Phiếu Duyệt NCC gần đây" + chip (admin dashboard để nguyên).
- types: PeListItem/PeDetailBundle/PeApprovalFlowLevel +field.
Tests: +PeFinalizeProjectionTests (EF-translate smoke 3 subquery + first-reachable + V1-false). Full suite 421→422.
em-main self-gate sau workflow (impl-frontend return rỗng #53 → mirror fe-admin + verify 5 site recover-disk).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Màn hình CEO ở "chỗ thao tác" hiện bản nháp / phiếu chưa xong của NGƯỜI KHÁC (anh Kiệt
FDC). Phiếu nháp (DangSoanThao) lộ qua 2 đường: cond #2 role-eligible (Drafter/DeptManager
→ DangSoanThao, mọi Drafter thấy nháp nhau) + cond #3 V2-approver (workflow pin lúc TẠO
phiếu nên approver như CEO thấy mọi nháp trước khi gửi duyệt = leak chính).
Luật mới: nháp = RIÊNG TƯ người tạo (DrafterUserId) + Admin thấy hết. 4 chỗ:
- GetEligiblePhases: bỏ DangSoanThao khỏi role-eligible (cond #2).
- Inbox (mảng admin): bỏ DangSoanThao — nháp khỏi Inbox hẳn kể cả admin (anh Kiệt chốt).
- List query cond #3: guard != DangSoanThao (approver không thấy nháp).
- Detail authz: guard != DangSoanThao cho V2-approver (không mở nháp người khác bằng link).
Phiếu ĐÃ gửi duyệt giữ nguyên (approver vẫn thấy). Phần admin-screen tinh chỉnh sâu để sau.
+PeDraftVisibilityTests (2 test, test-before security): owner thấy nháp mình · drafter khác
không · CEO/V2-approver không · admin thấy hết · phiếu đã gửi vẫn hiện (no regression).
Full suite 419→421 PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the Mig 58 multi-winner (>=2 cung trung) wiring — the audit's joint-winner
dead-ends, CEO-approved scope ("1 HD per winner"):
- create-contract 1->N: one draft HD per IsWinner supplier (was 409 dead-end for >=2).
Command -> List<Guid>. ContractCodeGenerator self-commits its sequence per call, so
gen all N codes first (contracts built local) then atomic add + single final
SaveChanges (no partial create on failure). GiaTri: single = details ThanhTienNganSach
sum (legacy preserved); >=2 = per-winner quote total (DangSoanThao draft, editable —
anh Kiet UAT the basis). pe.ContractId = first (idempotency). Controller -> {contractIds}.
- list-card winner names: PurchaseEvaluationListItemDto +WinnerSupplierNames (EF
collection subquery over IsWinner suppliers; translates + runs on SQLite — test-verified).
FE PeListPanel shows joined names for >=2 (was blank). Dialog -> contractIds[] + "Da tao N HD".
Tests 413 -> 419 (+6: multi-contract 5 [per-winner GiaTri 140/260 vs single legacy 800,
idempotency, 0-winner, not-DaDuyet] + EF-subquery translation smoke). Full suite GREEN.
FE 2-app SHA-identical. reviewer PASS (codegen-sequencing, positional-record arg-order,
no-partial-create all independently verified).
UAT (anh Kiet): open a >=2-winner phieu -> "Tao HD" creates N draft contracts (1/winner);
confirm per-winner contract GiaTri (quote total) is the wanted basis.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PE stability audit (frozen CEO-approved spec) — D1 behavior-preserving fixes plus
one approved joint-winner financial correction:
- Block B "Gia tri ky nay" + cross-PE peer rollup re-keyed SelectedSupplierId -> IsWinner
(reported 0 / excluded peers for >=2 joint winners; now matches winnerQuoteTotal).
- CEO-notify-on-urgent: +SaveChanges after NotifyManyAsync (Director notify was added
after the only flush -> never persisted; now persists + SignalR push fires).
- HoSoLink: UpdatePeDraft null-safe + clear-via-empty (header edit no longer wipes a
set link; HoSoLinkRow sends "" to clear; header forms omit = preserve).
- #70 stale-echo guard added to PRO budget row3/row8 (|| peFetching).
- Relocate SetPeCcmBudgetPeriodCommand -> PeWorkItemBudgetFeatures (was misplaced in
PeSuggestedPriceFeatures). DTO/comment cleanups.
Tests 402 -> 413 (+11: HoSoLink preserve/clear/set, Block B >=2-winner, CEO-notify
persist x6). Full suite GREEN. FE 2-app SHA-identical. test-specialist + reviewer
returns truncated (#53, over-budget MEMORY) -> em-main self-gate from disk + suite run.
D2 deferred (create-contract 1-HD-per-winner + list-card winner names) — needs focused
care + anh Kiet UAT on per-winner contract value.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CCM (Lưu Trần / anh Kiệt FDC) — làm 3/4 mục (mục 3 "CCM sửa ngân sách" để riêng):
1. Multi-winner: chọn NHIỀU NTP/NCC/ĐVDV cùng trúng → winnerQuoteTotal = SUM
ThanhTien MỌI đơn vị isWinner (vd 3 ông 37.8+9+10 = 56.8tr). Cột mới
PurchaseEvaluationSupplier.IsWinner (Mig 58, backfill từ SelectedSupplierId).
SelectWinner → multi-toggle SupplierIds[]. FE: bảng so sánh tick nhiều ô +
dòng "Tổng chi phí đã chọn"; mọi guard/checklist/computeGiaChaoThau/tạo-HĐ
đổi sang isWinner (selectedSupplierId = null khi liên-danh ≥2).
2. Ngân sách = 0 hợp lệ: nới submit-guard + 3 validator (chỉ chặn khi NULL,
số 0 cố ý đi qua; fallback ProInitial chỉ khi null). Mirror BE + FE.
3. Cấp KẾT THÚC quy trình: ApprovalWorkflowLevel.AllowApproverFinalize (Mig 58)
— admin bật per-cấp trong Workflow Designer; duyệt tới cấp đó TỰ DaDuyet,
bỏ qua CEO (AUTO theo cấu hình quy trình, không ô-tích runtime — anh Kiệt
chốt). FE: banner cảnh báo + bắt chọn giá chốt.
Mig 58 AddPeMultiWinnerAndLevelFinalize (2 AddColumn bool + backfill IsWinner,
additive/reversible). Test 377→395 (+PeMultiWinnerTests +PeApproverFinalizeTests
+budget=0; auto-model gọn bớt 2 test bypass/opt-out). FE 2 app SHA-mirror.
Build sạch BE + 2 FE local.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
D2 — Người KHAI phiếu tự GỠ cờ GẤP của mình (nới luật S77 "GỠ=chỉ TP"). BE handler
restructure SET/UNSET: GẮN giữ role-only (PRO/CCM/Admin); GỠ +drafter (DrafterUserId==actor)
→ drafter/Admin clear CẢ 2 cờ, TP-vai clear cờ vai mình. FE canToggle +isDrafter.
D3 — "Ngân sách kỳ này" (row3) tự nhảy từ "Ngân sách Ban hành lần đầu"
(PeWorkItemBudget.ProInitialAmount) khi chưa nhập tay. Submit-guard BE + FE missingForApproval
dùng effective = budgetPeriodAmount ?? proInitialAmount (mirror 2 tầng, chống bypass gotcha #44).
BE 2 file (Urgent handler + Workflow submit-guard). FE PeDetailTabs ×2 app SHA-identical.
Test +5 PeUrgentToggleAuthzTests (drafter-self-unset authz matrix) → 371 PASS. BE slnx + 2 FE build PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tra Sol + anh Kiet (Zalo chot 14:25): tranh NV khac lo tay go co cua nguoi da gan. Handler SetPurchaseEvaluationUrgent gate BAT DOI XUNG theo IsUrgent: GAN (true) = role chuc nang (Procurement->do / CostControl->xanh / Admin->ca 2); GO (false) = role chuc nang + DeptManager (Truong phong) hoac Admin. FE PeDetailTabs nut toggle gate theo trang thai hien tai (da gap->can quyen GO; chua gap->can quyen GAN) → an nut GO voi NV thuong. Test PeUrgentToggleAuthzTests rewrite asymmetric (354 PASS 0 fail). FE 2 app SHA256-identical. Build slnx 0-err.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Theo Tra Sol + anh Kiet FDC (Zalo): A) o nhap tien VndInlineEdit + BudgetCell nhay phan cach vi-VN (300.000.000) on-keystroke (o dialog da co san). B) them o ghi chu PRO + CCM canh nut Luu trong khoi Gia de xuat (giai thich vi sao chon Min/Max) — Mig 57 AddPeSuggestedPriceNotes (+ProSuggestedPriceNote +CcmSuggestedPriceNote nvarchar(1000) null, additive no-backfill no-table); 2 setter command +Note absolute-set rides role-gate PRO/CCM/Admin; DTO +2 field; controller body +note. C) sua chinh ta 'd. Ban so sanh' -> 'd. Bang so sanh gia' (2 app). GUARD gotcha #70: o gia + ghi chu echo nhau absolute-set -> them peFetching khoa nut Luu toi khi pe-detail refetch land, tranh stale-echo mat du lieu (mirror bang ngan sach S76; em-main review bat impl-frontend sot guard). BE slnx 0-warn 0-err; FE build PASS x2; test 344->351 (+7); PeDetailTabs/PeWorkspaceCreateView 2 app SHA256-identical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
S76 (anh Kiet FDC + chi Tra Sol) — form ngan sach + hien thi quyen nhap NS trong flow:
- Part 1: form ngan sach -> MA TRAN 3 cot, moi phong nhap+dieu chinh cot minh
(PRO canEditPro / CCM canEditCcm / Du an FE hien-thi-only). Mig 56
+ProInitialAmount/ProAdjustmentAmount (additive-nullable + data-migrate
ProEstimate->ProInitial). full moi cot = ban hanh + hieu chinh.
- Part 2: Workflow Designer (fe-admin) +badge "NS PRO/CCM" canh approver
(suy tu role Admin|Procurement / Admin|CostControl, hien-thi-only no-authz).
- Part 3: flow quy trinh fe-user/fe-admin (Duyet NCC) +badge tuong tu.
- Fix race mat-du-lieu Part 1 (useIsFetching khoa Luu khi refetch — dong
cua-so stale-echo, reviewer Part2/3 bat).
- Test 339->344 (+5). 2 workflow review (Part 1 PASS + Part 2/3 PASS).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Theo note anh Kiệt FDC (go-live so-sánh-giá thứ Hai):
- (1) Giá chào thầu thêm giá đề xuất NGOÀI giá NCC: PRO nhập dải Min/Max +
CCM nhập 1 giá (2 lệnh role-gate Procurement/CostControl, fail-closed).
Khi duyệt cấp cuối, người duyệt CHỌN 1 giá chốt (Ncc/ProMin/ProMax/Ccm)
-> luu ApprovedPriceAmount/Source (bind tai moi nhanh DaDuyet, bat buoc
chon; auto-approve he thong mien).
- (3) CCM duyet-done mien CEO: DOI tu AUTO-threshold (S69) sang O-TICH-TAY
(finalizeByCcmDelegation) -- CCM chu dong tich, fail-closed theo nguong
+ role + gia goi. An toan hon (khong vo tinh bo CEO).
- Mig 54 additive-nullable (5 cot PE) - FE 2 app SHA-mirror - test 306->334
(+28: opt-in 6->11, +10 gia chot, +13 setter authz).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Yêu cầu anh Kiệt FDC (sau họp sếp). Mig 53 AddPeUrgentAndCeoApprovalThreshold — 3 AddColumn, no new table (Mig 52→53). Rollout an toàn: cột nullable, ngưỡng null = giữ luồng duyệt cũ 100% cho tới khi admin set.
B — CCM duyệt-final theo NGƯỠNG GIÁ TRỊ ("gói CEO phân quyền theo giá trị"):
- ApprovalWorkflow += CeoApprovalThreshold (decimal?, admin nhập trong Workflow Designer).
- ApproveV2Async: actor role CostControl (CCM) + winnerQuoteTotal (tổng giá NCC được chọn) < ngưỡng → DaDuyet luôn (bỏ CEO); ≥ ngưỡng → đẩy lên CEO như cũ. Ngưỡng null = luồng tuyến tính cũ. Q4 chốt nhận diện theo ROLE người duyệt.
- reviewer PASS 0 blocker: cascade-safe (Off/role không lan), tested load-bearing (CCM dưới ngưỡng → DaDuyet skip CEO).
A — cờ gấp per-vai (visibility-only, Q3 KHÔNG đổi luồng):
- PE += IsUrgentByPro (PRO đỏ) / IsUrgentByCcm (CCM xanh).
- Endpoint PUT /purchase-evaluations/{id}/urgent role-gated (Procurement→ByPro, CostControl→ByCcm, Admin→cả 2, khác→Forbidden) + notify CEO (Director) khi MỚI bật (best-effort).
FE ×2 app: Workflow Designer ô "Ngưỡng giá trị gói CEO" (fe-admin) + PE detail nút bật/tắt cờ gấp đỏ/xanh theo role + badge GẤP + hint "giá trị gói vs ngưỡng → CCM duyệt-final/cần CEO" + PE list badge gấp.
DTO: PE detail += isUrgentByPro/Ccm + winnerQuoteTotal + ceoApprovalThreshold; list += isUrgentByPro/Ccm; workflow V2 += ceoApprovalThreshold.
+14 test (292→306): PeCcmThresholdFinalizeTests 5 (B routing) + PeUrgentToggleAuthzTests 9 (A authz). Build slnx 0/0 · npm build ×2 0 err · dotnet test 306 PASS.
C (sau duyệt xong chuyển phiếu đến dự án) — chờ anh Kiệt làm chi tiết form, CHƯA làm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Anh chốt "public văn phòng số cho all user eoffice". Mở quyền Xem + Tạo (self-service) module Office cho mọi role trên cả 2 app.
- NEW SeedAllRolesOfficeModulePermissionsAsync (DbInitializer): grant CanRead+CanCreate=true cho allow-list 16 key Office, chạy SAU RevokeTemporarilyHiddenModulesAsync để THẮNG revoke (mirror đúng pattern S65 HRM public). Upgrade-only: nâng false→true trên row prod đã có, KHÔNG hạ, KHÔNG đụng CanUpdate/CanDelete. No migration (seed-logic, idempotent).
- ALLOW-LIST 16: Off + Off_Dashboard + Off_DanhBa + Off_PhongHop(View/Book) + Off_DeXuat(List/Create/Inbox) + Off_DonTu(Leave/Ot/Travel) + Off_DatXe + Off_ItTicket.
- GIỮ ẨN (ngoài allow-list → revoke vẫn che non-Admin): Off_PhongHop_Manage (admin CRUD phòng), Off_AttendanceReport (báo cáo chấm công — riêng tư), Off_ChamCong (Cá nhân — golive riêng). HRM (trừ Hồ sơ NS S65) + Personal VẪN ẩn (anh chỉ mở Office).
- reviewer PASS 0 blocker (security): cascade-safe (Off KHÔNG phải inherit-root trong GetMyMenuTree → excluded-3 giữ false, không lan); KHÔNG mở write-path thật (Office controller dùng [Authorize] self-service + [Authorize(Roles=Admin)] cho admin-write — CanCreate chỉ mở menu + nút tạo FE, API authz độc lập menu key; quản lý phòng double-protected).
- +6 test OfficeModulePermissionSeedTests (286→292) lock: allow-list read+create=true · excluded-3 stay hidden (load-bearing) · admin not demoted · no-leak HRM/Personal · upgrade-only preserves admin-raised Update/Delete.
- Build slnx 0/0 · dotnet test 292 PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mig AddDepartmentParentId (0f44d97) thêm ParentId positional thứ 5 vào
CreateDepartmentCommand, nhưng MasterCatalogFilteredUniqueTests.cs:63 còn gọi
4-arg -> CS7036, test-gate FAIL Run #291 (deploy gated, prod nguyên = baseline).
CLAUDE.md §7 spec-change miss (build Api.csproj lẻ thay vì slnx full -> lọt tests).
Full `dotnet build SolutionErp.slnx` PASS sau fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Root cause: o "Gia tri thuc hien du kien con lai" (row 8 bang Tong hop ngan sach) khi gia tri NCC vuot ngan sach -> so du con lai ra AM; BE validator ExpectedRemainingAmount>=0 + FE VndInlineEdit khong bat allowNegative -> chan cung "am ko luu duoc" (testing bao qua anh Kiet)
- BE: AdjustPurchaseEvaluationBudgetCommandValidator GO rule ExpectedRemainingAmount.GreaterThanOrEqualTo(0) -> cho luu so am (mirror tien le LeaveBalance AllowsNegativeRemaining). GIU BudgetPeriodAmount>0 + submit-guard "da nhap NS ky nay" khong doi
- FE x2 app SHA256 identical: (a) allowNegative cho VndInlineEdit row 8; (b) banner amber "Vuot ngan sach - van luu & gui duyet duoc" trong PeBudgetSummaryTable khi cmpPeriod<0 || cmpFull<0. Tang to mau do cu GIU NGUYEN
- Spec change: flip test AdjustBudget_Validator_ExpectedRemainingNegative_FailsValidation -> _PassesValidation (am gio hop le); test BudgetPeriodZero_FailsValidation GIU (budget>0 van enforced)
- Build FE x2 PASS + test 263 PASS (45 Domain + 218 Infra, 0 fail/skip). Reviewer PASS 0 issue (row8 am an toan arithmetic additive-only, submit guard nguyen, mirror byte-identical, no scope creep)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Mig 50 ReplaceBudgetModuleWithPeWorkItemBudgets: bang moi PeWorkItemBudgets (1 record/cap Du an x Hang muc, UNIQUE filtered [IsDeleted]=0) + drop 5 bang Budget cu + PE/Contracts drop BudgetId + backfill BudgetManualAmount->BudgetPeriodAmount TRUOC DropColumn (phieu UAT giu so) + DELETE menu/permission Bg_* IN-list children-first
- BE: PUT {id}/budget/pro (role Procurement) + {id}/budget/ccm (role CostControl, Adjustment cho phep AM) fail-closed Forbidden-truoc-side-effect + EnsureTrackedAsync race-safe (catch unique -> re-fetch winner, loi khac rethrow) + auto-create record khi tao phieu + budgetSummary DTO (luy ke trinh-truoc/chon-thau-truoc/de-xuat-ky-nay + full fallback du-tru-PRO + canEdit flags) + submit-guard (3) doi predicate BudgetPeriodAmount -> "chua nhap Ngan sach ky nay" + PATCH budget-adjust absolute-set 2 field moi + Contract GIU BudgetManual* (HD nhap tay khong doi) + ke thua HD map BudgetPeriodAmount
- FE x2 app SHA256 identical: bang "TONG HOP NGAN SACH TRINH KY" block A (full dam + ban hanh + V0 hieu chinh + du tru PRO + ghi chu, editable theo canEditPro/canEditCcm) + block B 9 dong cong thuc Excel (5=1+3, 6=2+4, 7=full-5, 8 tu nhap default 7, 9=4+8) + to mau vuot ngan sach #C00000 / am do / red-soft row8>row7 + "Chua chon" khi count=0 + banner phieu chua gan Hang muc + o "Ngan sach ky nay" o create/header + XOA pages/components/types budgets + routes + menuKeys + Layout staticMap 4-place
- Tests: +22 PeWorkItemBudgetTests (auto-create x3, ensure/race x2, authz matrix PRO x5 + CCM x3, budgetSummary aggregates x5, adjust x4) - 14 BudgetPolicyTests xoa theo module - 1 test via-BudgetId -> 263 PASS (45 Domain + 218 Infra, 0 fail)
- database-agent advise adopted: khong FK vat ly PE/Contracts->Budgets (DropColumn khong can DropForeignKey) + DropIndex truoc DropColumn (SQL 5074) + IN-list thay LIKE Bg_% (underscore wildcard + miss root) + khong Serializable wrap (nested-tx conflict codegen)
- Reviewer PASS-with-minor 0 blocker (verdict-first survived); 2 minor da sua truoc commit (comment adjustMut absolute-set + dead key budgetId); note: F4 approver-edit-budget UI entry tam drafter-only, BE van cho approver scope - cho UAT anh Kiet
- Scaffold-bug caught: EF tu sinh RenameColumn BudgetManualAmount->ExpectedRemainingAmount (SAI semantics) -> thay bang Add+UPDATE+Drop
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fix drift surfaced by S45 Holiday coverage tests: DB UNIQUE (Year,Date) was unfiltered while handler checks !IsDeleted -> recreating a holiday on a soft-deleted slot threw DbUpdateException(500). Add .HasFilter("[IsDeleted] = 0") matching the 13x project filtered-unique pattern (Catalogs/Contract/PE/Proposal/Budget/WorkflowApps). Soft-deleted slot now reusable per app intent. Flipped Case 7 to assert success-on-reuse. 181 test PASS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>