[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>
This commit is contained in:
@ -294,6 +294,9 @@ public class AddBudgetDetailCommandHandler(
|
||||
{
|
||||
var bg = await db.Budgets.FirstOrDefaultAsync(x => x.Id == request.BudgetId, ct)
|
||||
?? throw new NotFoundException("Budget", request.BudgetId);
|
||||
// Lock edit guard (Phase 9 — Migration 16)
|
||||
if (bg.Phase != BudgetPhase.DangSoanThao)
|
||||
throw new ConflictException($"Ngân sách đã trình duyệt (Phase={bg.Phase}), không thể thêm hạng mục. Phải reject để Drafter sửa lại.");
|
||||
var maxOrder = await db.BudgetDetails.Where(d => d.BudgetId == bg.Id)
|
||||
.Select(d => (int?)d.Order).MaxAsync(ct);
|
||||
var entity = new BudgetDetail
|
||||
@ -331,6 +334,11 @@ public class UpdateBudgetDetailCommandHandler(
|
||||
{
|
||||
public async Task Handle(UpdateBudgetDetailCommand request, CancellationToken ct)
|
||||
{
|
||||
var bg = await db.Budgets.FirstOrDefaultAsync(b => b.Id == request.BudgetId, ct)
|
||||
?? throw new NotFoundException("Budget", request.BudgetId);
|
||||
// Lock edit guard (Phase 9 — Migration 16)
|
||||
if (bg.Phase != BudgetPhase.DangSoanThao)
|
||||
throw new ConflictException($"Ngân sách đã trình duyệt (Phase={bg.Phase}), không thể sửa hạng mục. Phải reject để Drafter sửa lại.");
|
||||
var entity = await db.BudgetDetails
|
||||
.FirstOrDefaultAsync(d => d.Id == request.DetailId && d.BudgetId == request.BudgetId, ct)
|
||||
?? throw new NotFoundException("BudgetDetail", request.DetailId);
|
||||
@ -339,10 +347,8 @@ public class UpdateBudgetDetailCommandHandler(
|
||||
entity.KhoiLuong = request.KhoiLuong; entity.DonGia = request.DonGia; entity.ThanhTien = request.ThanhTien;
|
||||
entity.GhiChu = request.GhiChu;
|
||||
|
||||
var bg = await db.Budgets.FirstOrDefaultAsync(b => b.Id == request.BudgetId, ct);
|
||||
if (bg != null)
|
||||
bg.TongNganSach = await db.BudgetDetails.Where(d => d.BudgetId == bg.Id)
|
||||
.SumAsync(d => d.ThanhTien, ct);
|
||||
bg.TongNganSach = await db.BudgetDetails.Where(d => d.BudgetId == bg.Id)
|
||||
.SumAsync(d => d.ThanhTien, ct);
|
||||
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
@ -355,15 +361,18 @@ public class DeleteBudgetDetailCommandHandler(
|
||||
{
|
||||
public async Task Handle(DeleteBudgetDetailCommand request, CancellationToken ct)
|
||||
{
|
||||
var bg = await db.Budgets.FirstOrDefaultAsync(b => b.Id == request.BudgetId, ct)
|
||||
?? throw new NotFoundException("Budget", request.BudgetId);
|
||||
// Lock edit guard (Phase 9 — Migration 16)
|
||||
if (bg.Phase != BudgetPhase.DangSoanThao)
|
||||
throw new ConflictException($"Ngân sách đã trình duyệt (Phase={bg.Phase}), không thể xóa hạng mục. Phải reject để Drafter sửa lại.");
|
||||
var entity = await db.BudgetDetails
|
||||
.FirstOrDefaultAsync(d => d.Id == request.DetailId && d.BudgetId == request.BudgetId, ct)
|
||||
?? throw new NotFoundException("BudgetDetail", request.DetailId);
|
||||
db.BudgetDetails.Remove(entity);
|
||||
|
||||
var bg = await db.Budgets.FirstOrDefaultAsync(b => b.Id == request.BudgetId, ct);
|
||||
if (bg != null)
|
||||
bg.TongNganSach = await db.BudgetDetails.Where(d => d.BudgetId == bg.Id && d.Id != entity.Id)
|
||||
.SumAsync(d => d.ThanhTien, ct);
|
||||
bg.TongNganSach = await db.BudgetDetails.Where(d => d.BudgetId == bg.Id && d.Id != entity.Id)
|
||||
.SumAsync(d => d.ThanhTien, ct);
|
||||
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user