[CLAUDE] Infra: SeedDemoContractsAsync — 7 HĐ varied phases để UAT-ready
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m39s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m39s
User feedback: làm hết task pending. Phase 1: seed sample HĐ data để admin login thấy ngay E2E hoạt động full (không cần tự tạo từ đầu). ## Idempotent guard Skip nếu đã có bất kỳ HĐ nào tên bắt đầu "[DEMO]" — admin có thể xóa demo + create thật bình thường, restart không clobber. ## 7 demo HĐ — covering 7 ContractTypes + 4-5 phases khác nhau | # | Type | Final Phase | Tên HĐ | Giá trị | Details | |---|---|---|---|---|---| | 1 | ThauPhu | DangSoanThao | Thi công móng + cột tầng 1 — FLOCK 01 | 850M | 3 hạng mục (đào, đổ BT, lắp thép) | | 2 | GiaoKhoan | DangGopY | Khoán nhân công xây + trát + sơn | 320M | 3 công việc + 2 comments demo | | 3 | NhaCungCap | DangInKy | Cung cấp xi măng + sắt thép Q2/2026 | 1.2B | 3 SP (xi măng, thép D14/D18) | | 4 | DichVu | DangTrinhKy | Thuê cẩu tháp 6 tháng | 540M | 1 DV (cẩu tháp Liebherr) | | 5 | MuaBan | DaPhatHanh | Mua máy phát điện 250kVA | 850M | 2 SP có VAT 10% | | 6 | NguyenTacNCC | DangGopY | HĐ nguyên tắc cung cấp vật tư 2026 | 0 (framework) | 2 SP với khung giá min/max | | 7 | NguyenTacDV | DangSoanThao | HĐ nguyên tắc bảo trì TB 2026 | 0 (framework) | 1 DV với SLA | ## Workflow simulation Loop transition phases tới finalPhase, insert ContractApproval row mỗi phase với ApproverUserId mapping đúng role: - DangKiemTraCCM → ccm.tran (CostControl) - DangTrinhKy + DangDongDau → bod.huynh (Director) - DaPhatHanh → hra.dang (HrAdmin) - Khác → qs.hoang (Drafter — soạn thảo + đàm phán + in ký) SkipCcm policy (DichVu/MuaBan/NguyenTacNcc/NguyenTacDv) bỏ DangKiemTraCCM khỏi flow. Mã HĐ auto gen qua codeGen.GenerateAsync (RG-001 format đầy đủ). ## Demo data gồm - 7 Contracts (Header) - ~14 Details rows (3+3+3+1+2+2+1) - ~30 ContractApprovals (workflow history per phase) - 2 ContractComments (demo conversation CCM↔Drafter) ## Build dotnet build BE pass (0 error) ## Login test sau deploy Admin → /my-contracts hoặc /contracts → thấy 7 [DEMO] HĐ với mã RG-001 + varied phases. Click bất kỳ HĐ → Panel 2 thấy Tổng quan + Chi tiết + Lịch sử duyệt full content. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -44,6 +44,8 @@ public static class DbInitializer
|
||||
var codeGen = sp.GetRequiredService<IContractCodeGenerator>();
|
||||
await BackfillContractCodesAsync(db, codeGen, logger);
|
||||
|
||||
await SeedDemoContractsAsync(db, userManager, codeGen, logger);
|
||||
|
||||
await WarnDefaultAdminPasswordAsync(userManager, logger);
|
||||
}
|
||||
|
||||
@ -268,6 +270,244 @@ public static class DbInitializer
|
||||
}
|
||||
}
|
||||
|
||||
// Seed 7 demo HĐ — 1 per ContractType, varied phases (Đang soạn / Góp ý /
|
||||
// Đã in ký / Đã phát hành), với Details + Approvals + Comments để admin
|
||||
// login thấy ngay sample data hoạt động full E2E. Idempotent: skip nếu đã
|
||||
// có HĐ nào tên bắt đầu "[DEMO]".
|
||||
private static async Task SeedDemoContractsAsync(
|
||||
ApplicationDbContext db, UserManager<User> userManager,
|
||||
IContractCodeGenerator codeGen, ILogger logger)
|
||||
{
|
||||
if (await db.Contracts.AnyAsync(c => c.TenHopDong != null && c.TenHopDong!.StartsWith("[DEMO]")))
|
||||
{
|
||||
logger.LogInformation("SeedDemoContracts: skip — đã có demo HĐ.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Lookup các tham chiếu cần
|
||||
var supplier = await db.Suppliers.FirstOrDefaultAsync();
|
||||
var project = await db.Projects.FirstOrDefaultAsync();
|
||||
if (supplier is null || project is null)
|
||||
{
|
||||
logger.LogWarning("SeedDemoContracts: skip — thiếu Supplier/Project.");
|
||||
return;
|
||||
}
|
||||
|
||||
var qsHoang = await userManager.FindByEmailAsync("qs.hoang@solutionerp.local");
|
||||
var ccmTran = await userManager.FindByEmailAsync("ccm.tran@solutionerp.local");
|
||||
var bodHuynh = await userManager.FindByEmailAsync("bod.huynh@solutionerp.local");
|
||||
var hraDang = await userManager.FindByEmailAsync("hra.dang@solutionerp.local");
|
||||
var qsDeptId = (await db.Departments.FirstOrDefaultAsync(d => d.Code == "QS"))?.Id;
|
||||
var nowUtc = DateTime.UtcNow;
|
||||
|
||||
async Task<Contract> createDemoAsync(
|
||||
ContractType type, ContractPhase finalPhase, string ten, decimal giaTri, string? noiDung)
|
||||
{
|
||||
var activeWfId = await db.WorkflowDefinitions.AsNoTracking()
|
||||
.Where(w => w.ContractType == type && w.IsActive)
|
||||
.Select(w => (Guid?)w.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
var c = new Contract
|
||||
{
|
||||
Type = type,
|
||||
Phase = ContractPhase.DangSoanThao, // Tạo ở phase 2, transition lên sau
|
||||
SupplierId = supplier.Id,
|
||||
ProjectId = project.Id,
|
||||
DepartmentId = qsDeptId,
|
||||
DrafterUserId = qsHoang?.Id,
|
||||
GiaTri = giaTri,
|
||||
TenHopDong = $"[DEMO] {ten}",
|
||||
NoiDung = noiDung,
|
||||
BypassProcurementAndCCM = false,
|
||||
WorkflowDefinitionId = activeWfId,
|
||||
SlaDeadline = nowUtc.AddDays(7),
|
||||
};
|
||||
c.MaHopDong = await codeGen.GenerateAsync(c, project.Code, supplier.Code);
|
||||
db.Contracts.Add(c);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Mock approvals + Phase advance qua manual write (skip workflow service vì
|
||||
// service expect ICurrentUser context). Thực tế chỉ cần ContractApproval
|
||||
// history + cập nhật Phase entity.
|
||||
ContractPhase[] flow = type is ContractType.HopDongDichVu or ContractType.HopDongMuaBan
|
||||
or ContractType.HopDongNguyenTacNCC or ContractType.HopDongNguyenTacDichVu
|
||||
? [ContractPhase.DangGopY, ContractPhase.DangDamPhan, ContractPhase.DangInKy,
|
||||
ContractPhase.DangTrinhKy, ContractPhase.DangDongDau, ContractPhase.DaPhatHanh]
|
||||
: [ContractPhase.DangGopY, ContractPhase.DangDamPhan, ContractPhase.DangInKy,
|
||||
ContractPhase.DangKiemTraCCM, ContractPhase.DangTrinhKy,
|
||||
ContractPhase.DangDongDau, ContractPhase.DaPhatHanh];
|
||||
|
||||
var current = ContractPhase.DangSoanThao;
|
||||
foreach (var next in flow)
|
||||
{
|
||||
if (next > finalPhase) break;
|
||||
Guid? actorId = next switch
|
||||
{
|
||||
ContractPhase.DangKiemTraCCM => ccmTran?.Id,
|
||||
ContractPhase.DangTrinhKy or ContractPhase.DangDongDau => bodHuynh?.Id,
|
||||
ContractPhase.DaPhatHanh => hraDang?.Id,
|
||||
_ => qsHoang?.Id,
|
||||
};
|
||||
db.ContractApprovals.Add(new ContractApproval
|
||||
{
|
||||
ContractId = c.Id,
|
||||
FromPhase = current,
|
||||
ToPhase = next,
|
||||
ApproverUserId = actorId,
|
||||
Decision = ApprovalDecision.Approve,
|
||||
Comment = next == ContractPhase.DangGopY ? "Demo seed — chuyển góp ý" : null,
|
||||
ApprovedAt = nowUtc,
|
||||
});
|
||||
current = next;
|
||||
}
|
||||
c.Phase = current;
|
||||
return c;
|
||||
}
|
||||
|
||||
async Task addThauPhuDetail(Guid cid, int order, string hangMuc, string dvt, decimal kl, decimal dg)
|
||||
{
|
||||
db.ThauPhuDetails.Add(new Domain.Contracts.Details.ThauPhuDetail
|
||||
{
|
||||
ContractId = cid, Order = order, HangMuc = hangMuc, DonViTinh = dvt,
|
||||
KhoiLuong = kl, DonGia = dg, ThanhTien = kl * dg,
|
||||
});
|
||||
}
|
||||
async Task addNccDetail(Guid cid, int order, string ma, string ten, string dvt, decimal sl, decimal dg)
|
||||
{
|
||||
db.NhaCungCapDetails.Add(new Domain.Contracts.Details.NhaCungCapDetail
|
||||
{
|
||||
ContractId = cid, Order = order, MaSP = ma, TenSP = ten, DonViTinh = dvt,
|
||||
SoLuong = sl, DonGia = dg, ThanhTien = sl * dg,
|
||||
});
|
||||
}
|
||||
async Task addMuaBanDetail(Guid cid, int order, string ma, string ten, string dvt, decimal sl, decimal dg, decimal vat)
|
||||
{
|
||||
db.MuaBanDetails.Add(new Domain.Contracts.Details.MuaBanDetail
|
||||
{
|
||||
ContractId = cid, Order = order, MaSP = ma, TenSP = ten, DonViTinh = dvt,
|
||||
SoLuong = sl, DonGia = dg, ThueVAT = vat, ThanhTien = sl * dg * (1 + vat / 100m),
|
||||
});
|
||||
}
|
||||
async Task addDichVuDetail(Guid cid, int order, string ma, string ten, string dvt, decimal tg, decimal dg)
|
||||
{
|
||||
db.DichVuDetails.Add(new Domain.Contracts.Details.DichVuDetail
|
||||
{
|
||||
ContractId = cid, Order = order, MaDichVu = ma, TenDichVu = ten, DonViTinh = dvt,
|
||||
ThoiGian = tg, DonGia = dg, ThanhTien = tg * dg,
|
||||
});
|
||||
}
|
||||
|
||||
// 1. HĐ Thầu phụ — DangSoanThao (đang soạn)
|
||||
var c1 = await createDemoAsync(
|
||||
ContractType.HopDongThauPhu, ContractPhase.DangSoanThao,
|
||||
"Thi công móng + cột tầng 1 — FLOCK 01", 850_000_000m,
|
||||
"Bao gồm đào móng, đổ bê tông móng cột, lắp cốt thép.");
|
||||
await addThauPhuDetail(c1.Id, 1, "Đào móng công trình", "m3", 120, 250_000);
|
||||
await addThauPhuDetail(c1.Id, 2, "Đổ bê tông móng cột", "m3", 80, 1_800_000);
|
||||
await addThauPhuDetail(c1.Id, 3, "Lắp dựng cốt thép", "kg", 5000, 18_000);
|
||||
|
||||
// 2. HĐ Giao khoán — DangGopY (đang góp ý)
|
||||
var c2 = await createDemoAsync(
|
||||
ContractType.HopDongGiaoKhoan, ContractPhase.DangGopY,
|
||||
"Khoán nhân công xây tường + trát + sơn — FLOCK 01", 320_000_000m,
|
||||
"Phần hoàn thiện tầng 2 + 3.");
|
||||
db.GiaoKhoanDetails.Add(new Domain.Contracts.Details.GiaoKhoanDetail
|
||||
{
|
||||
ContractId = c2.Id, Order = 1, MaCongViec = "XAY-TUONG", TenCongViec = "Xây tường gạch 110",
|
||||
DonViTinh = "m2", KhoiLuong = 800, DonGia = 180_000, ThanhTien = 144_000_000m,
|
||||
});
|
||||
db.GiaoKhoanDetails.Add(new Domain.Contracts.Details.GiaoKhoanDetail
|
||||
{
|
||||
ContractId = c2.Id, Order = 2, MaCongViec = "TRAT-TUONG", TenCongViec = "Trát tường 2 mặt",
|
||||
DonViTinh = "m2", KhoiLuong = 1600, DonGia = 80_000, ThanhTien = 128_000_000m,
|
||||
});
|
||||
db.GiaoKhoanDetails.Add(new Domain.Contracts.Details.GiaoKhoanDetail
|
||||
{
|
||||
ContractId = c2.Id, Order = 3, MaCongViec = "SON-NUOC", TenCongViec = "Sơn nước nội thất",
|
||||
DonViTinh = "m2", KhoiLuong = 1600, DonGia = 30_000, ThanhTien = 48_000_000m,
|
||||
});
|
||||
|
||||
// 3. HĐ Nhà cung cấp — DangInKy (đang in ký)
|
||||
var c3 = await createDemoAsync(
|
||||
ContractType.HopDongNhaCungCap, ContractPhase.DangInKy,
|
||||
"Cung cấp xi măng + sắt thép Q2/2026", 1_200_000_000m,
|
||||
"Đợt 1: 200 tấn xi măng + 50 tấn thép.");
|
||||
await addNccDetail(c3.Id, 1, "XM-PCB40", "Xi măng PCB40 50kg", "tan", 200, 1_800_000);
|
||||
await addNccDetail(c3.Id, 2, "THEP-D14", "Thép cây phi 14", "kg", 30000, 18_500);
|
||||
await addNccDetail(c3.Id, 3, "THEP-D18", "Thép cây phi 18", "kg", 20000, 18_800);
|
||||
|
||||
// 4. HĐ Dịch vụ — DangTrinhKy (đang trình ký BOD)
|
||||
var c4 = await createDemoAsync(
|
||||
ContractType.HopDongDichVu, ContractPhase.DangTrinhKy,
|
||||
"Thuê cẩu tháp 6 tháng — FLOCK 01", 540_000_000m,
|
||||
"Cẩu tháp Liebherr — Q2-Q3/2026.");
|
||||
await addDichVuDetail(c4.Id, 1, "VC-CAN-TRUC", "Cẩu tháp Liebherr 320 EC-H", "thang", 6, 90_000_000);
|
||||
|
||||
// 5. HĐ Mua bán — DaPhatHanh (đã phát hành — full flow)
|
||||
var c5 = await createDemoAsync(
|
||||
ContractType.HopDongMuaBan, ContractPhase.DaPhatHanh,
|
||||
"Mua máy phát điện 250kVA", 850_000_000m,
|
||||
"1 máy phát điện chính + 1 dự phòng cho công trường.");
|
||||
await addMuaBanDetail(c5.Id, 1, "MP-250KVA", "Máy phát điện Cummins 250kVA", "cai", 1, 720_000_000, 10);
|
||||
await addMuaBanDetail(c5.Id, 2, "MP-100KVA", "Máy phát điện dự phòng 100kVA", "cai", 1, 50_000_000, 10);
|
||||
|
||||
// 6. HĐ Nguyên tắc NCC — DangGopY (framework agreement)
|
||||
var c6 = await createDemoAsync(
|
||||
ContractType.HopDongNguyenTacNCC, ContractPhase.DangGopY,
|
||||
"HĐ nguyên tắc cung cấp vật tư xây dựng 2026", 0m,
|
||||
"Khung giá tham chiếu — đặt mua qua PO theo từng đợt.");
|
||||
db.NguyenTacNccDetails.Add(new Domain.Contracts.Details.NguyenTacNccDetail
|
||||
{
|
||||
ContractId = c6.Id, Order = 1, NhomSP = "Xi măng",
|
||||
TenSP = "Xi măng PCB40 50kg", DonViTinh = "tan",
|
||||
DonGiaToiThieu = 1_700_000, DonGiaToiDa = 1_900_000,
|
||||
DieuKienThanhToan = "Net 30 — chuyển khoản",
|
||||
});
|
||||
db.NguyenTacNccDetails.Add(new Domain.Contracts.Details.NguyenTacNccDetail
|
||||
{
|
||||
ContractId = c6.Id, Order = 2, NhomSP = "Sắt thép",
|
||||
TenSP = "Thép cây phi 10-32", DonViTinh = "kg",
|
||||
DonGiaToiThieu = 17_500, DonGiaToiDa = 19_500,
|
||||
DieuKienThanhToan = "Net 30 — chuyển khoản",
|
||||
});
|
||||
|
||||
// 7. HĐ Nguyên tắc Dịch vụ — DangSoanThao
|
||||
var c7 = await createDemoAsync(
|
||||
ContractType.HopDongNguyenTacDichVu, ContractPhase.DangSoanThao,
|
||||
"HĐ nguyên tắc bảo trì thiết bị 2026", 0m,
|
||||
"Khung dịch vụ bảo trì máy móc — gọi theo nhu cầu.");
|
||||
db.NguyenTacDvDetails.Add(new Domain.Contracts.Details.NguyenTacDvDetail
|
||||
{
|
||||
ContractId = c7.Id, Order = 1, LoaiDichVu = "Bảo trì",
|
||||
TenDichVu = "Bảo trì máy phát điện", DonViTinh = "lan",
|
||||
DonGiaToiThieu = 5_000_000, DonGiaToiDa = 8_000_000,
|
||||
SLA = "Phản hồi 24h, hoàn thành 72h",
|
||||
});
|
||||
|
||||
// Comments demo cho HĐ #2 (DangGopY)
|
||||
if (ccmTran is not null)
|
||||
{
|
||||
db.ContractComments.Add(new ContractComment
|
||||
{
|
||||
ContractId = c2.Id, UserId = ccmTran.Id, Phase = ContractPhase.DangGopY,
|
||||
Content = "[DEMO] CCM góp ý: Đề nghị bổ sung điều khoản phạt chậm tiến độ + bảo hành 12 tháng.",
|
||||
});
|
||||
}
|
||||
if (qsHoang is not null)
|
||||
{
|
||||
db.ContractComments.Add(new ContractComment
|
||||
{
|
||||
ContractId = c2.Id, UserId = qsHoang.Id, Phase = ContractPhase.DangGopY,
|
||||
Content = "[DEMO] Drafter phản hồi: Đã ghi nhận, sẽ cập nhật bản v2.",
|
||||
});
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Seed 7 demo contracts: {C1} {C2} {C3} {C4} {C5} {C6} {C7}",
|
||||
c1.MaHopDong, c2.MaHopDong, c3.MaHopDong, c4.MaHopDong, c5.MaHopDong, c6.MaHopDong, c7.MaHopDong);
|
||||
}
|
||||
|
||||
// Phase 5.1 security: log warning nếu admin vẫn dùng password mặc định sau deploy production.
|
||||
private static async Task WarnDefaultAdminPasswordAsync(UserManager<User> userManager, ILogger logger)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user