Anh chốt "(c) THEO TAY CẦM PHIẾU": Nháp/Trả-lại (tay người soạn) ⇒ chỉ
người soạn up file duyệt; Chờ-duyệt ⇒ chỉ thành viên quy trình đã pin
(membership, không đòi đúng-lượt — chữ anh @S171 không thu hẹp); terminal
DaDuyet/TuChoi ⇒ chặn cả hai (hồ sơ đã chốt).
Guard mới EnsureApprovalAttachmentUploaderAsync (WorkflowGuards) wire vào
UploadContractSigningPlanAttachmentCommandHandler — CHỈ rẽ nhánh
Purpose=ApprovalAttachment; đường căn-cứ dossier giữ nguyên hành vi cũ
(có control-test chặn nới guard ngoài phán quyết).
Đóng lỗ đo prod @S172: drafter qua policy KeHoachKyKet.Update up được
file vào phiếu không thuộc tay mình (#82 hai tầng — policy là quyền
module, không phải quyền theo tay). FE 0-đổi (nút vốn chỉ trong dialog
duyệt — display-layer; guard này là API-layer thật).
Test 660 → 668 (+8: 3 nhánh tay × 2 chủ thể + ngoài-cuộc + terminal-2-vế
+ control đường-soạn). YC-016 vào sổ; YC-013 nhắc=2 (YC-015 verify 4-chân).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Anh gui 2 anh + 1 dong nhan, gom 6 diem:
(1) "bo nut sua voi ve lai form": go TRON dai 2 nut goc phai cua KhkkCreatePage
(Sua + X Ve form tao). Ca hai deu la duong PHU:
- vao che-do sua -> but chi o DANH SACH trai (onEditPlan) = duong DUY NHAT, GIU.
- roi che-do xem -> bam PE bat ky o cay trai, pickPe() tu setViewPlanId(null).
Dinh chinh chu-thich S175 tung dung do ("bo han thi nguoi dung ket, khong co
duong lui"): SAI ngay luc viet — pickPe mang nhan [S174] tuc DA TON TAI truoc
cau canh-bao do. Thu duy nhat mat di la duong quay lai man rong huong-dan.
Go luon import Pencil/X (het cho dung).
(2) "To dam" (ma) + (3) "Bo di" (ten): header muc 2 gio CHI con MA, in dam.
Fallback ve TEN khi thieu ma: peWorkItemCode null khi PE khong gan hang muc
(WorkItemId loose-Guid Mig 49, duoc phep rong) — bo ten VO DIEU KIEN thi ca do
header tro moi chu "Hang muc (du an)", trong nhu loi render.
(4) "Them dau + them o day" + (5) "bo di": thay khoi "Them hang muc cho: [NCC...]"
bang nut + o COT HANH-DONG cua TUNG DONG. Nut o dong nao thi BIET SAN NCC cua
dong do (l.supplierId) nen bo duoc buoc chon NCC. Khoi cu ton tai chi vi no dung
NGOAI moi dong nen buoc phai hoi "cho NCC nao".
Vong doi khoi nay tron 3 nac trong CUNG luot S177 (S175 duoi bang -> doi vao
tbody -> go han) — giu 1 dong vet de khoi ai dung lai.
(6) "Trang thai tra lai cho xoa dc luon nhe": allow-list xoa phieu
{DangSoanThao, TuChoi} -> +TraLai.
🔴 Day la VA LECH 2 TANG chu khong phai noi quyen: FE DA hien nut Xoa cho phieu
TraLai tu truoc (canEdit gom TraLai => readOnly=false => thanh nut day hien),
BE thi nem 409 — bam duoc ma khong xoa duoc (gotcha #82).
+1 test DeleteContractSigningPlan_Allows_TraLai, DA CHUNG CO RANG: stash ban va
-> test DO; tra ban va -> XANH. Test (8) cu khong bat duoc vi ve nghich dung
ChoDuyet (cam ca truoc lan sau) va ve thuan dung TuChoi (cho ca truoc lan sau)
— TraLai la phase DUY NHAT doi verdict va no vang mat khoi moi test.
645/645 test pass (+1) · 2 app build sach · SHA IDENTICAL (da verify identical @HEAD
TRUOC khi copy de) · dist local doi chieu: "Ve form tao" = 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gate PASS 0-blocker (sub-reviewer-gate-k4c.md): WorkflowMatrixViewPage doc ?group= (guard
/^[1-8]$/ chan ca "03") -> loc wf.code === KHKK-N{n} client-side co-chu-dich (payload da
tai tron theo type — khong ro cache, BE khong nhan truc group) + badge nhom + link xem-tat-ca
+ rong-vi-loc tach cau; type≠10 = 0 doi hanh vi (PE matrix regression chung-la-khong).
fe-admin 0-diff (lech co-y 2026-07-27). +1 Fact ListDeleted_FiltersByApprovalGroup (5 ve +
nhanh-Admin-cung-loc, assert Total, RED->GREEN 2 chieu do that — gate-K4b MAJOR-A dong).
Suite 622/0 (45D+577I) do tuoi ca gate lan lead. Ghi mo: 1 test cu
DeleteContractSigningPlan_SoftDeletes flaky 1/12 luot quan sat (gate truy 3 ve khong ra
duong diff — moc cho CI neu tai). No con: gate-K4c 3-MINOR (helper-dispose · link-mat-query
· highlight) + K5-MINOR-3 chu SAP + MAJOR-C da dong o wave nay.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Lo hong (reviewer S118 phat hien khi phan quyen PRO tra.bui): ReportsController chi co
[Authorize] tran (khong policy) -> MOI user dang nhap goi duoc GET /reports/dashboard (tong gia
tri HD dang chay, top NCC/du an theo gia tri, xu huong 12 thang) + GET /reports/contracts/export
(xuat toan bo HD ra Excel). Nguoc quyet dinh S92 "HD chi Admin thay" (menu an nhung API van mo,
go URL/API truc tiep lay duoc).
Fix (owner chot: chi Admin qua Reports.Read policy -- linh hoat mo role khac qua ma tran sau):
- Dashboard() + ExportContracts() -> them [Authorize(Policy = "Reports.Read")] per-method. Hien
chi Admin co Reports.Read -> non-admin 403. Admin bypass moi policy nhu cu.
- MyDashboard() GIU MO (chi [Authorize] class-level) -- scope theo currentUser, moi user chi thay
so cua CHINH MINH (draft/pending/SLA/gia tri draft cua minh), khong lo toan cong ty.
Test: +4 reflection authz regression (AuthorizePolicyRegressionTests, mirror gotcha #44 S45):
dashboard/export co Reports.Read; my-dashboard KHONG co action-policy (guard chong ai khoa nham
dashboard ca nhan). 505 -> 509. Gotcha #82 (menu-flag grant != quyen API that).
Co-Authored-By: Claude Fable 5 <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>
anh Kiệt "chuông không bỏ ra được" — noti "Phiếu cần bạn duyệt" của approver (kể cả CEO) KHÔNG tự xóa khi phiếu đã xong/chuyển bởi người khác (duyệt-thay tại Cấp CEO, co-approver OR-of-N, finalize) → chuông đầy phiếu thừa.
Fix: trong LogTransitionAsync (chạy MỌI transition), TRƯỚC block notify cấp mới → mark-read (ReadAt) tất cả noti Generic + RefId=phiếu còn unread. Phiếu rời cấp (finalize/advance/return/reject) → noti cũ tự sạch; approver cấp MỚI nhận noti fresh (tạo sau mark-read). Best-effort try/catch, V2-only, ExecuteUpdateAsync. KHÔNG đổi target (noti vẫn báo đúng lúc), chỉ thu-hồi khi stale.
Test: +test #7 (finalize → noti CEO cũ mark-read). 435→436 PASS. BE-only, no migration.
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>
Người TẠO phiếu (drafter) không còn nhận chuông "Phiếu cần bạn duyệt" cho
phiếu mình tạo — kể cả khi được cấu hình làm approver ở một Cấp. Chỉ approver
KHÁC người tạo ở Cấp hiện tại mới nhận. Drafter vẫn nhận chuông KẾT-QUẢ
(đã duyệt / trả lại) như cũ (block :1060 giữ nguyên).
- PurchaseEvaluationWorkflowService.LogTransitionAsync: thêm filter
`l.ApproverUserId != evaluation.DrafterUserId` (in-memory LINQ, nullable-safe).
- test-specialist: +3 regression PeApproverNotifyExcludesDrafterTests
(drafter-excluded · co-approver-vẫn-nhận · result-notify-intact; RED-on-old
đã chứng minh). Test 374 -> 377.
- docs/STATUS.md: re-ground canonical (S85-closeout sót: test 356->377,
gotcha 71->73, bundle #341) + dòng S86 +3.
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>