From bcdc00706ccd58934ed8639b7cbdf6caccd440e6 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Thu, 23 Apr 2026 15:54:39 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20Infra:=20M=E1=BB=9F=20r=E1=BB=99ng?= =?UTF-8?q?=20seed=20master=20(15=20NCC=20+=208=20Project)=20+=20backfill?= =?UTF-8?q?=20demo=20H=C4=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User feedback: thêm master data NCC + Project + cập nhật HĐ đang thiếu/ trùng nhau. ## SeedDemoMasterDataAsync — per-code idempotent Trước: skip toàn bộ nếu Suppliers + Projects table đã có row → không add được seed mới khi expand list. Sau: per-Code check — add từng item nếu Code chưa tồn tại, skip nếu có. Cho phép expand seed list theo thời gian mà không clobber data admin đã add manual. ## 15 demo suppliers (5 SupplierType × 3 entities) - NhaCungCap (5): VLXD-ABC, Xi măng Hà Tiên, Thép Hòa Phát, Cadivi Điện, Tiền Phong Cấp thoát nước - NhaThauPhu (4): Thăng Long XD, Cô Công Cơ khí, MEP Hà Nội, Next Stage Nội thất - ToDoi (3): Hoàng Nam (cốp pha), Bắc Ninh (xây trát), Hà Trang (ốp lát) - DonViDichVu (3): Clean Pro, Hồng Phát Vận chuyển, Long An Bảo vệ - ChuDauTu (3): Vingroup, Sun Group, Masterise Homes Đầy đủ TaxCode + Phone + Email + ContactPerson + Address. ## 8 demo projects (đa dạng quy mô + giai đoạn) - FLOCK01 (sẵn) + SKYGARDEN (sẵn) + INDUSTRIAL (sẵn) - VHOMES-OP (Vinhomes Ocean Park 3, 350B) - ECOPARK-VL (Ecopark Văn Lâm, 180B) - BWTOWER (Văn phòng BW Tower 25 tầng, 95B) - WAREHOUSE-LB (Kho vận Long Biên, 32B) - RESORT-PQ (Resort 5* Phú Quốc 250 phòng, 220B) ## Backfill demo HĐ supplier + project Vấn đề trước: 7 demo HĐ tất cả dùng cùng 1 supplier (FirstOrDefault) + cùng 1 project → list nhìn không thực tế. DemoSupplierByType + DemoProjectByType maps mới: | ContractType | Supplier | Project | |---|---|---| | HopDongThauPhu | NTP-XD (thầu phụ) | FLOCK01 | | HopDongGiaoKhoan | TD-NEHOANG (tổ đội) | FLOCK01 | | HopDongNhaCungCap | NCC-XIMANG | VHOMES-OP | | HopDongDichVu | DV-VANCHUYEN | RESORT-PQ | | HopDongMuaBan | NCC-DIEN | BWTOWER | | HopDongNguyenTacNCC | NCC-THEP | ECOPARK-VL | | HopDongNguyenTacDichVu | DV-CLEAN | WAREHOUSE-LB | SeedDemoContractsAsync 2 path: 1. Lần đầu (no [DEMO] HĐ): tạo mới với supplier+project diverse per type 2. Đã có [DEMO] HĐ: gọi BackfillDemoContractsSupplierProjectAsync — loop từng demo HĐ, update supplier_id + project_id nếu mismatch với map (idempotent, log count updated) Match đúng business: HĐ thầu phụ → NTP, HĐ NCC → NCC supplier, HĐ DV → DV supplier, HĐ chủ đầu tư (BypassCCM) → CDT supplier (admin tự gán). ## Build dotnet build BE pass (0 error) ## Note deploy Lần CI/CD deploy `e8XXXXX` lên prod sẽ: - Add 10 supplier mới + 5 project mới (existing 5+3 giữ nguyên) - Backfill 7 demo HĐ → switch sang đúng supplier + project per type - Log: "Seeded 10 demo suppliers", "Seeded 5 demo projects", "Backfill [DEMO] contracts supplier+project: 6 updated." (6 vì FLOCK01 vẫn dùng cho ThauPhu/GiaoKhoan unchanged) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Persistence/DbInitializer.cs | 199 +++++++++++++++--- 1 file changed, 165 insertions(+), 34 deletions(-) diff --git a/src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs b/src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs index 90bc7b5..dd4fc9d 100644 --- a/src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs +++ b/src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs @@ -148,6 +148,53 @@ public static class DbInitializer } } + // Backfill: re-assign supplier + project cho [DEMO] HĐ existing → đa dạng + // theo DemoSupplierByType / DemoProjectByType. Idempotent (chỉ update nếu + // mismatch). Hữu ích khi expand seed list rồi muốn HĐ cũ pick supplier mới. + private static async Task BackfillDemoContractsSupplierProjectAsync( + ApplicationDbContext db, + IReadOnlyDictionary suppliersByCode, + IReadOnlyDictionary projectsByCode, + ILogger logger) + { + var demos = await db.Contracts + .Where(c => c.TenHopDong != null && c.TenHopDong!.StartsWith("[DEMO]")) + .ToListAsync(); + if (demos.Count == 0) return; + + int updated = 0; + foreach (var c in demos) + { + var newSupplierId = c.SupplierId; + var newProjectId = c.ProjectId; + + if (DemoSupplierByType.TryGetValue(c.Type, out var sCode) + && suppliersByCode.TryGetValue(sCode, out var s) + && c.SupplierId != s.Id) + { + newSupplierId = s.Id; + } + if (DemoProjectByType.TryGetValue(c.Type, out var pCode) + && projectsByCode.TryGetValue(pCode, out var p) + && c.ProjectId != p.Id) + { + newProjectId = p.Id; + } + + if (newSupplierId != c.SupplierId || newProjectId != c.ProjectId) + { + c.SupplierId = newSupplierId; + c.ProjectId = newProjectId; + updated++; + } + } + if (updated > 0) + { + await db.SaveChangesAsync(); + logger.LogInformation("Backfill [DEMO] contracts supplier+project: {Count} updated.", updated); + } + } + private static async Task BackfillContractCodesAsync( ApplicationDbContext db, IContractCodeGenerator codeGen, ILogger logger) { @@ -270,29 +317,65 @@ public static class DbInitializer } } + // Map ContractType → preferred supplier code (đa dạng dữ liệu demo theo + // đúng business: ThauPhu/GiaoKhoan đi với NTP/TĐ, NCC/MuaBan/NTNcc đi + // với NCC, DichVu/NTDv đi với DV). + private static readonly Dictionary DemoSupplierByType = new() + { + [ContractType.HopDongThauPhu] = "NTP-XD", + [ContractType.HopDongGiaoKhoan] = "TD-NEHOANG", + [ContractType.HopDongNhaCungCap] = "NCC-XIMANG", + [ContractType.HopDongDichVu] = "DV-VANCHUYEN", + [ContractType.HopDongMuaBan] = "NCC-DIEN", + [ContractType.HopDongNguyenTacNCC] = "NCC-THEP", + [ContractType.HopDongNguyenTacDichVu] = "DV-CLEAN", + }; + + // Map ContractType → preferred project code (đa dạng dự án per HĐ) + private static readonly Dictionary DemoProjectByType = new() + { + [ContractType.HopDongThauPhu] = "FLOCK01", + [ContractType.HopDongGiaoKhoan] = "FLOCK01", + [ContractType.HopDongNhaCungCap] = "VHOMES-OP", + [ContractType.HopDongDichVu] = "RESORT-PQ", + [ContractType.HopDongMuaBan] = "BWTOWER", + [ContractType.HopDongNguyenTacNCC] = "ECOPARK-VL", + [ContractType.HopDongNguyenTacDichVu] = "WAREHOUSE-LB", + }; + // 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]". + // login thấy ngay sample data hoạt động full E2E. + // + // Idempotent 2 paths: + // 1. Lần đầu (no [DEMO] HĐ): create 7 HĐ varied supplier+project per type + // 2. Đã có [DEMO] HĐ: chạy backfill — update supplier+project nếu mismatched + // với DemoSupplierByType/DemoProjectByType (đa dạng dữ liệu). private static async Task SeedDemoContractsAsync( ApplicationDbContext db, UserManager 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) + // Lookup supplier + project map theo Code (cần cả 2 path) + var suppliersByCode = await db.Suppliers.ToDictionaryAsync(s => s.Code, s => s); + var projectsByCode = await db.Projects.ToDictionaryAsync(p => p.Code, p => p); + if (suppliersByCode.Count == 0 || projectsByCode.Count == 0) { logger.LogWarning("SeedDemoContracts: skip — thiếu Supplier/Project."); return; } + // Path 2: backfill nếu đã có [DEMO] HĐ + if (await db.Contracts.AnyAsync(c => c.TenHopDong != null && c.TenHopDong!.StartsWith("[DEMO]"))) + { + await BackfillDemoContractsSupplierProjectAsync(db, suppliersByCode, projectsByCode, logger); + return; + } + + // Path 1: tạo mới (lần đầu) + // Default fallback supplier + project nếu type-specific không có (shouldn't happen với expanded seed) + var fallbackSupplier = suppliersByCode.Values.First(); + var fallbackProject = projectsByCode.Values.First(); + 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"); @@ -308,6 +391,10 @@ public static class DbInitializer .Select(w => (Guid?)w.Id) .FirstOrDefaultAsync(); + // Pick supplier + project theo type (đa dạng dữ liệu demo) + var supplier = DemoSupplierByType.TryGetValue(type, out var sCode) && suppliersByCode.TryGetValue(sCode, out var s) ? s : fallbackSupplier; + var project = DemoProjectByType.TryGetValue(type, out var pCode) && projectsByCode.TryGetValue(pCode, out var p) ? p : fallbackProject; + var c = new Contract { Type = type, @@ -798,36 +885,80 @@ public static class DbInitializer } } - // Sample master data for UAT/demo. Gated by: only seed if tables are empty - // (respects admin's real data once they start adding). + // Sample master data for UAT/demo — per-code idempotent (skip Code đã có, + // add Code mới). Cho phép admin add/sửa supplier/project mà restart không + // clobber, đồng thời cho phép expand seed list theo thời gian. private static async Task SeedDemoMasterDataAsync(ApplicationDbContext db, ILogger logger) { - if (await db.Suppliers.AnyAsync() && await db.Projects.AnyAsync()) return; + var now = DateTime.UtcNow; - if (!await db.Suppliers.AnyAsync()) + // 15 NCC covering 5 SupplierType (3 each) + var suppliersToSeed = new[] { - db.Suppliers.AddRange( - new Supplier { Code = "NCC-VLXD", Name = "Công ty TNHH Vật liệu Xây dựng ABC", Type = SupplierType.NhaCungCap, TaxCode = "0100000001", Phone = "024 3888 1111", Email = "contact@vlxd-abc.vn", ContactPerson = "Nguyễn Văn An", Address = "123 Láng Hạ, Đống Đa, Hà Nội" }, - new Supplier { Code = "NTP-XD", Name = "Công ty CP Xây dựng Thăng Long", Type = SupplierType.NhaThauPhu, TaxCode = "0100000002", Phone = "024 3888 2222", Email = "info@thanglong-xd.vn", ContactPerson = "Trần Thị Bình", Address = "45 Nguyễn Chí Thanh, Đống Đa, Hà Nội" }, - new Supplier { Code = "TD-NEHOANG", Name = "Tổ đội Hoàng Nam", Type = SupplierType.ToDoi, Phone = "098 111 2233", ContactPerson = "Phạm Hoàng Nam", Address = "Cầu Giấy, Hà Nội", Note = "Tổ đội cốp pha 15 người" }, - new Supplier { Code = "DV-CLEAN", Name = "Công ty TNHH Vệ sinh Công nghiệp Clean Pro", Type = SupplierType.DonViDichVu, TaxCode = "0100000004", Phone = "024 3888 4444", Email = "sales@cleanpro.vn", Address = "Trung Hòa, Cầu Giấy, Hà Nội" }, - new Supplier { Code = "CDT-VIN", Name = "Tập đoàn Vingroup", Type = SupplierType.ChuDauTu, TaxCode = "0100109106", Phone = "024 3974 9999", Email = "contact@vingroup.net", Address = "7 Bằng Lăng 1, Vinhomes Riverside, Long Biên, Hà Nội", Note = "Chủ đầu tư - bypass CCM" } - ); - logger.LogInformation("Seeded 5 demo suppliers"); - } + // NhaCungCap (5) — vật tư xây dựng + new Supplier { Code = "NCC-VLXD", Name = "Công ty TNHH Vật liệu Xây dựng ABC", Type = SupplierType.NhaCungCap, TaxCode = "0100000001", Phone = "024 3888 1111", Email = "contact@vlxd-abc.vn", ContactPerson = "Nguyễn Văn An", Address = "123 Láng Hạ, Đống Đa, Hà Nội" }, + new Supplier { Code = "NCC-XIMANG", Name = "Công ty CP Xi măng Hà Tiên", Type = SupplierType.NhaCungCap, TaxCode = "0100000005", Phone = "028 3829 5555", Email = "sales@hatien.vn", ContactPerson = "Lê Minh Tuấn", Address = "82 Lê Lợi, Q1, TP.HCM" }, + new Supplier { Code = "NCC-THEP", Name = "Công ty CP Thép Hòa Phát", Type = SupplierType.NhaCungCap, TaxCode = "0100000006", Phone = "024 6266 4666", Email = "sales@hoaphat.com.vn", ContactPerson = "Trần Quốc Đạt", Address = "643 Phạm Văn Đồng, Bắc Từ Liêm, Hà Nội" }, + new Supplier { Code = "NCC-DIEN", Name = "Công ty TNHH Thiết bị Điện Cadivi", Type = SupplierType.NhaCungCap, TaxCode = "0100000007", Phone = "028 3925 9889", Email = "info@cadivi.com.vn", ContactPerson = "Hoàng Thị Lan", Address = "70 Nguyễn Tri Phương, Q5, TP.HCM" }, + new Supplier { Code = "NCC-NUOC", Name = "Công ty TNHH Vật tư Cấp thoát nước Tiền Phong", Type = SupplierType.NhaCungCap, TaxCode = "0100000008", Phone = "0225 3876 666", Email = "sales@tienphong-pvc.vn", ContactPerson = "Vũ Văn Bình", Address = "KCN Đình Vũ, Hải Phòng" }, - if (!await db.Projects.AnyAsync()) + // NhaThauPhu (4) — thầu phụ thi công + new Supplier { Code = "NTP-XD", Name = "Công ty CP Xây dựng Thăng Long", Type = SupplierType.NhaThauPhu, TaxCode = "0100000002", Phone = "024 3888 2222", Email = "info@thanglong-xd.vn", ContactPerson = "Trần Thị Bình", Address = "45 Nguyễn Chí Thanh, Đống Đa, Hà Nội" }, + new Supplier { Code = "NTP-COCONG", Name = "Công ty CP Xây dựng & Cơ khí Cô Công", Type = SupplierType.NhaThauPhu, TaxCode = "0100000009", Phone = "024 3776 8888", Email = "info@cocong.com.vn", ContactPerson = "Đỗ Mạnh Hùng", Address = "Khu CN Quang Minh, Mê Linh, Hà Nội" }, + new Supplier { Code = "NTP-MEPCO", Name = "Công ty TNHH MEP Hà Nội", Type = SupplierType.NhaThauPhu, TaxCode = "0100000010", Phone = "024 3556 9999", Email = "contact@mep-hn.vn", ContactPerson = "Nguyễn Thị Hồng", Address = "Cầu Giấy, Hà Nội", Note = "Chuyên thầu phụ M&E" }, + new Supplier { Code = "NTP-NEXTAGE", Name = "Công ty CP Hoàn thiện Nội thất Next Stage", Type = SupplierType.NhaThauPhu, TaxCode = "0100000011", Phone = "024 6688 7777", Email = "sales@nextstage.vn", ContactPerson = "Phạm Văn Khoa", Address = "Hà Đông, Hà Nội" }, + + // ToDoi (3) — tổ đội nhân công + new Supplier { Code = "TD-NEHOANG", Name = "Tổ đội Hoàng Nam", Type = SupplierType.ToDoi, Phone = "098 111 2233", ContactPerson = "Phạm Hoàng Nam", Address = "Cầu Giấy, Hà Nội", Note = "Tổ đội cốp pha 15 người" }, + new Supplier { Code = "TD-XAYTRAT", Name = "Tổ đội Xây trát Bắc Ninh", Type = SupplierType.ToDoi, Phone = "098 444 5566", ContactPerson = "Nguyễn Văn Khánh", Address = "Yên Phong, Bắc Ninh", Note = "Tổ đội xây trát 25 người" }, + new Supplier { Code = "TD-OPLAT", Name = "Tổ đội Ốp lát Hà Trang", Type = SupplierType.ToDoi, Phone = "097 222 8899", ContactPerson = "Trần Thị Hà", Address = "Hà Đông, Hà Nội", Note = "Tổ đội ốp lát + sơn bả 12 người" }, + + // DonViDichVu (3) — dịch vụ + new Supplier { Code = "DV-CLEAN", Name = "Công ty TNHH Vệ sinh Công nghiệp Clean Pro", Type = SupplierType.DonViDichVu, TaxCode = "0100000004", Phone = "024 3888 4444", Email = "sales@cleanpro.vn", Address = "Trung Hòa, Cầu Giấy, Hà Nội" }, + new Supplier { Code = "DV-VANCHUYEN", Name = "Công ty CP Vận chuyển Hồng Phát", Type = SupplierType.DonViDichVu, TaxCode = "0100000012", Phone = "024 3776 5555", Email = "info@hongphat-vc.vn", ContactPerson = "Lê Văn Dũng", Address = "Long Biên, Hà Nội", Note = "Vận chuyển vật tư + xe cẩu" }, + new Supplier { Code = "DV-BAOVE", Name = "Công ty Bảo vệ Long An", Type = SupplierType.DonViDichVu, TaxCode = "0100000013", Phone = "024 3556 6666", Email = "service@longan-bv.vn", Address = "Thanh Xuân, Hà Nội" }, + + // ChuDauTu (3) — chủ đầu tư (bypass CCM) + new Supplier { Code = "CDT-VIN", Name = "Tập đoàn Vingroup", Type = SupplierType.ChuDauTu, TaxCode = "0100109106", Phone = "024 3974 9999", Email = "contact@vingroup.net", Address = "7 Bằng Lăng 1, Vinhomes Riverside, Long Biên, Hà Nội", Note = "Chủ đầu tư - bypass CCM" }, + new Supplier { Code = "CDT-SUNGROUP", Name = "Tập đoàn Sun Group", Type = SupplierType.ChuDauTu, TaxCode = "0100000014", Phone = "024 3939 8888", Email = "contact@sungroup.com.vn", Address = "Hoàn Kiếm, Hà Nội", Note = "Chủ đầu tư - bypass CCM" }, + new Supplier { Code = "CDT-MASTERISE", Name = "Masterise Homes", Type = SupplierType.ChuDauTu, TaxCode = "0100000015", Phone = "028 3939 7777", Email = "contact@masterisehomes.com", Address = "Q2, TP.HCM", Note = "Chủ đầu tư - bypass CCM" }, + }; + + var existingSupplierCodes = await db.Suppliers.Select(s => s.Code).ToListAsync(); + int addedSuppliers = 0; + foreach (var s in suppliersToSeed) { - var now = DateTime.UtcNow; - db.Projects.AddRange( - new Project { Code = "FLOCK01", Name = "Dự án FLOCK 01 — Khu đô thị Mỹ Đình", StartDate = now.AddMonths(-3), EndDate = now.AddMonths(18), BudgetTotal = 120_000_000_000m, Note = "Dự án mẫu — demo" }, - new Project { Code = "SKYGARDEN", Name = "Sky Garden Residence", StartDate = now.AddMonths(-6), EndDate = now.AddMonths(12), BudgetTotal = 85_000_000_000m, Note = "Dự án mẫu — demo" }, - new Project { Code = "INDUSTRIAL", Name = "Nhà máy Công nghiệp Yên Phong", StartDate = now.AddMonths(-1), EndDate = now.AddMonths(9), BudgetTotal = 45_000_000_000m, Note = "Dự án mẫu — demo" } - ); - logger.LogInformation("Seeded 3 demo projects"); + if (existingSupplierCodes.Contains(s.Code)) continue; + db.Suppliers.Add(s); + addedSuppliers++; } + if (addedSuppliers > 0) logger.LogInformation("Seeded {Count} demo suppliers", addedSuppliers); - await db.SaveChangesAsync(); + // 8 dự án (đa dạng quy mô + giai đoạn) + var projectsToSeed = new[] + { + new Project { Code = "FLOCK01", Name = "Dự án FLOCK 01 — Khu đô thị Mỹ Đình", StartDate = now.AddMonths(-3), EndDate = now.AddMonths(18), BudgetTotal = 120_000_000_000m, Note = "Dự án mẫu — demo" }, + new Project { Code = "SKYGARDEN", Name = "Sky Garden Residence", StartDate = now.AddMonths(-6), EndDate = now.AddMonths(12), BudgetTotal = 85_000_000_000m, Note = "Dự án mẫu — demo" }, + new Project { Code = "INDUSTRIAL", Name = "Nhà máy Công nghiệp Yên Phong", StartDate = now.AddMonths(-1), EndDate = now.AddMonths(9), BudgetTotal = 45_000_000_000m, Note = "Dự án mẫu — demo" }, + new Project { Code = "VHOMES-OP", Name = "Vinhomes Ocean Park 3 — Khu Sapphire", StartDate = now.AddMonths(-12), EndDate = now.AddMonths(24), BudgetTotal = 350_000_000_000m, Note = "Khu đô thị quy mô lớn" }, + new Project { Code = "ECOPARK-VL", Name = "Ecopark Văn Lâm — Tổ hợp biệt thự", StartDate = now.AddMonths(-4), EndDate = now.AddMonths(20), BudgetTotal = 180_000_000_000m, Note = "Biệt thự + nhà phố" }, + new Project { Code = "BWTOWER", Name = "Tòa nhà Văn phòng BW Tower — Cầu Giấy", StartDate = now.AddMonths(2), EndDate = now.AddMonths(28), BudgetTotal = 95_000_000_000m, Note = "Văn phòng cho thuê 25 tầng" }, + new Project { Code = "WAREHOUSE-LB",Name = "Kho vận Long Biên — Phase 1", StartDate = now.AddMonths(-2), EndDate = now.AddMonths(8), BudgetTotal = 32_000_000_000m, Note = "Kho lạnh + kho thường" }, + new Project { Code = "RESORT-PQ", Name = "Resort 5* Phú Quốc — Bãi Trường", StartDate = now.AddMonths(-8), EndDate = now.AddMonths(16), BudgetTotal = 220_000_000_000m, Note = "Resort 250 phòng + biệt thự" }, + }; + + var existingProjectCodes = await db.Projects.Select(p => p.Code).ToListAsync(); + int addedProjects = 0; + foreach (var p in projectsToSeed) + { + if (existingProjectCodes.Contains(p.Code)) continue; + db.Projects.Add(p); + addedProjects++; + } + if (addedProjects > 0) logger.LogInformation("Seeded {Count} demo projects", addedProjects); + + if (addedSuppliers + addedProjects > 0) + await db.SaveChangesAsync(); } private static async Task SeedContractTemplatesAsync(ApplicationDbContext db, ILogger logger)