1fc439b978
[CLAUDE] App+Api+FE: Chunk E5 — Budget 2-stage dept approval (mirror PE/Contract)
...
Budget complete the trifecta — đồng bộ pattern 2-stage cho 3 module
(Contract + PE + Budget) cùng UX cho user khi UAT.
BE App:
- TransitionBudgetCommandHandler thêm INotificationService + IDateTime DI
- Mirror logic 2-stage từ ContractWorkflowService:
- actor.DepartmentId != null + KHÔNG admin/system + KHÔNG resume
- DeptManager (TPB) hoặc CanBypassReview → Stage=Confirm
- Else (NV) → Stage=Review only, BLOCK transition
- Upsert BudgetDepartmentApproval (UNIQUE BudgetId+Phase+Dept+Stage)
- Block khi !hasConfirm: insert Approval + Changelog + Notify TPB → return early
- BudgetDepartmentApprovalFeatures.cs (List query mirror PE/Contract)
Api:
- BudgetsController endpoint GET /budgets/{id}/department-approvals
FE (cả fe-admin + fe-user):
- types/budget.ts thêm ApprovalStage const + BudgetDepartmentApproval type
- BudgetWorkflowPanel section "Tiến trình duyệt 2-cấp phòng ban":
- Group by phase × dept, show Review NV + Confirm TPB
- Highlight amber "chờ TPB confirm" + badge fuchsia bypass
Note: low-priority cho Budget (ít user duyệt budget per dept) nhưng giữ
consistent UX 3 module.
Build: BE pass + FE pass cả 2 + 77 test pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-04 13:46:56 +07:00
9747f8cbf5
[CLAUDE] Infra+App: Chunk C — Smart reject + Resume after reject (3 module)
...
Ràng buộc 2 (Phase 9): khi reject, trả về Drafter (DangSoanThao) + lưu phase
nguồn. Drafter sửa lại + trình lại → quay về phase đã reject (skip phase
trung gian).
Logic flow:
1. Reject (Decision=Reject):
- entity.RejectedFromPhase = currentPhase // snapshot phase đang reject
- targetPhase override = DangSoanThao // force về Drafter
- Approval row: FromPhase=X, ToPhase=DangSoanThao, Decision=Reject
- Notification cho Drafter
2. Resume after reject (Decision=Approve, fromPhase=DangSoanThao,
RejectedFromPhase != null):
- targetPhase override = entity.RejectedFromPhase!.Value
- entity.RejectedFromPhase = null // clear field
- Skip policy guard (Drafter có quyền trình lại sau khi sửa)
- Approval row: FromPhase=DangSoanThao, ToPhase=ResumePhase, Decision=Approve
3. Normal transition (chưa reject hoặc đã clear):
- Logic cũ giữ nguyên — policy guard check + transition
Pattern unified cho 3 module:
- ContractWorkflowService.TransitionAsync: 2 case detect + override
- PurchaseEvaluationWorkflowService.TransitionAsync: tương tự
- TransitionBudgetCommandHandler.Handle: tương tự (Budget không có service riêng,
logic ở handler)
Files:
- src/Backend/SolutionErp.Infrastructure/Services/ContractWorkflowService.cs
- src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs
- src/Backend/SolutionErp.Application/Budgets/BudgetFeatures.cs
Verify:
- Build pass (2 warning DocxRenderer cũ, không liên quan)
- 77 unit test pass — Domain policy không đổi, tests giữ nguyên
Note: Approval history giờ track đầy đủ cycle reject→sửa→resume:
Approval 1: DangGopY → DangSoanThao, Decision=Reject (CCM reject)
Approval 2: DangSoanThao → DangGopY, Decision=Approve (Drafter resume)
UI có thể detect "đã từng reject" qua RejectedFromPhase != null hoặc
qua Approval history (Decision=Reject row gần nhất). Hiển thị banner
đỏ "Phiếu đã bị reject từ phase X, lý do: Y" cho Drafter.
Smart reject hoàn tất Ràng buộc 2. Còn Ràng buộc 3 (2-stage dept approval)
ở Chunk D.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-04 12:20:21 +07:00
14f3c9f817
[CLAUDE] App: Chunk B — Lock edit guards (Phase != DangSoanThao) cho 17 handler
...
Ràng buộc 1 (Phase 9): khi đã trình duyệt → KHÔNG sửa được Header + Detail
+ Quote nữa. Phải reject để Drafter sửa lại.
Pattern dùng: extend helper EnsureContractType + tạo helper PurchaseEvaluationDraftGuard
mới cho PE + inline guard cho Budget. Single source of truth cho mỗi module.
Handlers added Phase guard (17 total):
Contract module (15) — qua EnsureContractType helper:
- 7 Add*DetailHandler (ThauPhu/GiaoKhoan/NhaCungCap/DichVu/MuaBan/NguyenTacNcc/NguyenTacDv)
- 7 Update*DetailHandler (cùng 7 type)
- DeleteContractDetailHandler (inline guard)
PE module (5) — qua PurchaseEvaluationDraftGuard helper mới:
- AddPurchaseEvaluationDetail
- UpdatePurchaseEvaluationDetail
- DeletePurchaseEvaluationDetail
- UpsertPurchaseEvaluationQuote
- DeletePurchaseEvaluationQuote
Budget module (3) — inline guard:
- AddBudgetDetail
- UpdateBudgetDetail (refactor: load Budget thay vì FirstOrDefault sau Detail
load → bỏ null check không cần)
- DeleteBudgetDetail (refactor: tương tự)
KHÔNG lock (intentional):
- ContractComment (cần được trong DangGopY phase 3)
- ContractAttachment Upload/Delete (Drafter scan ký ở DangInKy phase 5)
- PE OpinionUpsert (Ý kiến 4 PB là sign-off, có thể nhập sau khi trình)
- PE Attachment (báo giá NCC upload xuyên suốt workflow)
Verify:
- Build pass (2 warning DocxRenderer cũ)
- 77 unit test pass (54 Domain + 23 Infra) — domain policy không đổi
Smart reject (ràng buộc 2) + 2-stage dept approval (ràng buộc 3) làm ở
Chunk C + D. WorkflowService transition guard chưa update.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-04 12:15:07 +07:00
61e5d4d503
[CLAUDE] PE+Contract+Budget integration — link Budget vào PE/HĐ + cột So với ngân sách
...
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m51s
BE wire BudgetId nullable FK qua command + DTO bundle:
- Budgets.Dtos: + BudgetSummaryDto (compact header snapshot, không kèm Details — gọi /budgets/{id} riêng nếu cần đối chiếu chi tiết)
- PurchaseEvaluations.Dtos: + BudgetId? + Budget? BudgetSummaryDto vào PurchaseEvaluationDetailBundleDto
- Contracts.Dtos: + BudgetId? + Budget? BudgetSummaryDto vào ContractDetailDto
- CreatePE + UpdatePEDraft + handlers: + BudgetId? param + validate (cùng Project + Phase=DaDuyet) + persist
- CreateContract + UpdateContractDraft + handlers: + BudgetId? param + validate + persist + log diff
- GetPE + GetContract handlers: load BudgetSummary nếu có link
- CreateContractFromEvaluation: carry forward pe.BudgetId → contract.BudgetId (nếu phiếu PE đã link)
FE PE (cả 2 app):
- types/purchaseEvaluation.ts: + BudgetSummary type + budgetId/budget vào PeDetailBundle
- PurchaseEvaluationCreatePage: thêm Select 'Ngân sách' filter Phase=DaDuyet + Project match (BE-side filter qua /budgets?projectId=&phase=4). Disabled khi chưa pick Project. Edit mode preserve.
- PeDetailTabs InfoTab: hiển thị Budget link với mã + tên + tổng (clickable → /budgets?id=)
- PeDetailTabs ItemsTab: thêm cột 'NS link · Δ' chỉ hiện khi ev.budgetId. Match per-row qua key groupCode|itemCode → fetch /budgets/{id} riêng. Footer aggregate row 'Tổng' + delta indicator (xanh dưới / đỏ vượt / xám khớp). No-match cell hiện '—'.
FE Contract (cả 2 app):
- types/contracts.ts: + ContractBudgetSummary + budgetId/budget vào ContractDetail
- ContractCreatePage HeaderForm: thêm Budget Select sau FormFields, useEffect reset khi đổi project
- ContractCreatePage EditForm: Select khi isDraft / read-only link card khi !isDraft
TS build pass cả 2 app + dotnet build clean. No new migration (BudgetId? nullable FK đã có từ migration 14).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-28 16:41:11 +07:00
a05c57b081
[CLAUDE] Domain+App+Api: Module Ngan sach (Budget) - 4 bang + workflow simple
...
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m11s
User request: 'Them cho tao 4 bang luu ve ngan sach: Header / Chi tiet
/ Quy trinh duyet / Lich su thay doi'.
Domain (5 file + 1 enum):
- Budget (header) — Aggregate root, AuditableEntity. Field: MaNganSach,
TenNganSach, Description, NamNganSach, ProjectId FK, DepartmentId?,
DrafterUserId, Phase (BudgetPhase 5-state), TongNganSach (sum auto
tu Details), SlaDeadline, SlaWarningSent.
- BudgetDetail — flat row pattern (GroupCode/GroupName + Item +
KhoiLuong/DonGia/ThanhTien). 18,4 precision KhoiLuong, 18,2 money.
- BudgetApproval — workflow history (FromPhase/ToPhase/Decision/Comment)
- BudgetChangelog — audit log unified (EntityType: Header/Detail/Workflow)
- BudgetPhase enum 5 state: DangSoanThao(1) → ChoCCM(2) → ChoCEO(3) →
DaDuyet(4) | TuChoi(99)
- BudgetPolicy hardcoded (no versioned WF, simple default per user
confirm 'tam thoi don gian'): Drafter/DeptManager → CCM → CEO/
AuthorizedSigner. Reject path back to DangSoanThao.
Migration 14 AddBudgets:
- 4 bang moi: Budgets + BudgetDetails + BudgetApprovals + BudgetChangelogs
- Index: Phase+IsDeleted, ProjectId, NamNganSach, SlaDeadline,
MaNganSach unique filtered. Cascade delete child.
- +2 cot FK ngoai bang (per user 'lien ket ca 3'):
* Contracts.BudgetId Guid? + index
* PurchaseEvaluations.BudgetId Guid? + index
Cho phep doi chieu chi phi HD/PE vs ngan sach goi thau.
Application CQRS (BudgetFeatures.cs ~340 line):
- CreateBudget + UpdateBudgetDraft + TransitionBudget + ListBudgets
(filter Phase/Project/Year + search + paging) + GetBudget bundle
(Header + Details + Approvals + Workflow summary)
- DeleteBudget (only DangSoanThao/TuChoi)
- AddBudgetDetail + UpdateBudgetDetail + DeleteBudgetDetail (auto
recompute TongNganSach = sum Details.ThanhTien)
- ListBudgetChangelogs
Api: BudgetsController 11 endpoint REST /api/budgets:
- GET / /{id} /{id}/changelogs
- POST / /{id}/transitions /{id}/details
- PUT /{id} /{id}/details/{detailId}
- DELETE /{id} /{id}/details/{detailId}
DbContext + IApplicationDbContext: 4 DbSet new (Budgets/Details/
Approvals/Changelogs).
MenuKeys + DbInitializer: 4 menu key (Budgets root + Bg_List/Create/
Pending leaves) seed dau order=27 'Ngan sach' icon Wallet. Auto-grant
admin permission via SeedAdminPermissionsAsync (MenuKeys.All).
MaNganSach format don gian 'NS-YYYYMM-NNNN' Random.Shared (chua atomic
sequence - user said 'tam thoi chua co').
Workflow chua versioned, hardcode BudgetPolicy.Default. Tuong lai admin
config qua UI: them BudgetWorkflowDefinition tables tuong tu PE.
2026-04-28 11:37:45 +07:00