Initial commit

This commit is contained in:
2026-04-23 21:38:24 -04:00
commit 63e12a9636
1762 changed files with 1672620 additions and 0 deletions
@@ -0,0 +1,841 @@
using Microsoft.EntityFrameworkCore;
using PowderCoating.Core.Entities;
namespace PowderCoating.Infrastructure.Services;
public partial class SeedDataService
{
/// <summary>
/// Seeds the 16 standard job status lookup rows for a company, covering the full
/// powder-coating workflow from Pending through Delivered/Cancelled.
/// </summary>
/// <remarks>
/// Job status is a lookup table (<see cref="JobStatusLookup"/>) rather than a C# enum so
/// that operators can customise display names, colours, and ordering without a code deploy.
/// The <c>StatusCode</c> string constants (e.g., "PENDING", "COATING") are referenced
/// throughout the application code and must not be changed after initial seeding.
///
/// Idempotency check: if 16 or more rows already exist for the company, the method
/// returns 0 immediately. This threshold equals the full set size, so a partially-seeded
/// company (e.g., from a previous failed run) will be re-seeded automatically.
///
/// Key flags per status row:
/// - <c>IsTerminalStatus</c> = true for Completed, Delivered, Cancelled — the AI
/// accounting service uses this to distinguish active vs closed jobs.
/// - <c>IsWorkInProgressStatus</c> = true for production stages (In Preparation through
/// Quality Check) — used by dashboard KPIs and scheduling views.
/// - <c>IsSystemDefined</c> = true for statuses that drive automated workflows
/// (Pending, Completed, Cancelled) — operators cannot delete these.
/// - <c>WorkflowCategory</c> groups statuses for reporting (Pre-Production, Production,
/// Post-Production, Other).
/// </remarks>
/// <param name="company">The tenant company to seed statuses for.</param>
/// <returns>The number of status rows created (16), or 0 if already seeded.</returns>
private async Task<int> SeedJobStatusLookupsAsync(Company company)
{
// Check if job statuses already exist for this company
var existingCount = await _context.Set<JobStatusLookup>()
.IgnoreQueryFilters()
.CountAsync(s => s.CompanyId == company.Id && !s.IsDeleted);
if (existingCount >= 16)
{
return 0; // Already seeded
}
var statuses = new List<JobStatusLookup>
{
new JobStatusLookup
{
StatusCode = "PENDING",
DisplayName = "Pending",
DisplayOrder = 1,
ColorClass = "secondary",
IconClass = "bi-clock",
IsActive = true,
IsSystemDefined = true,
IsTerminalStatus = false,
IsWorkInProgressStatus = false,
WorkflowCategory = "Pre-Production",
Description = "Job has been created and is awaiting approval",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "QUOTED",
DisplayName = "Quoted",
DisplayOrder = 2,
ColorClass = "info",
IconClass = "bi-file-text",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = false,
WorkflowCategory = "Pre-Production",
Description = "Quote has been generated for this job",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "APPROVED",
DisplayName = "Approved",
DisplayOrder = 3,
ColorClass = "primary",
IconClass = "bi-check-circle",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = false,
WorkflowCategory = "Pre-Production",
Description = "Job has been approved and is ready to start",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "IN_PREPARATION",
DisplayName = "In Preparation",
DisplayOrder = 4,
ColorClass = "warning",
IconClass = "bi-tools",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = true,
WorkflowCategory = "Production",
Description = "Job is being prepared for processing",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "SANDBLASTING",
DisplayName = "Sandblasting",
DisplayOrder = 5,
ColorClass = "warning",
IconClass = "bi-wind",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = true,
WorkflowCategory = "Production",
Description = "Surface preparation in progress",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "MASKING_TAPING",
DisplayName = "Masking/Taping",
DisplayOrder = 6,
ColorClass = "warning",
IconClass = "bi-scissors",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = true,
WorkflowCategory = "Production",
Description = "Masking areas that should not be coated",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "CLEANING",
DisplayName = "Cleaning",
DisplayOrder = 7,
ColorClass = "warning",
IconClass = "bi-droplet",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = true,
WorkflowCategory = "Production",
Description = "Final cleaning before coating",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "IN_OVEN",
DisplayName = "In Oven",
DisplayOrder = 8,
ColorClass = "warning",
IconClass = "bi-thermometer-half",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = true,
WorkflowCategory = "Production",
Description = "Parts are being pre-heated",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "COATING",
DisplayName = "Coating",
DisplayOrder = 9,
ColorClass = "warning",
IconClass = "bi-paint-bucket",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = true,
WorkflowCategory = "Production",
Description = "Applying powder coating",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "CURING",
DisplayName = "Curing",
DisplayOrder = 10,
ColorClass = "warning",
IconClass = "bi-fire",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = true,
WorkflowCategory = "Production",
Description = "Curing the powder coating in the oven",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "QUALITY_CHECK",
DisplayName = "Quality Check",
DisplayOrder = 11,
ColorClass = "info",
IconClass = "bi-search",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = true,
WorkflowCategory = "Post-Production",
Description = "Quality inspection in progress",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "COMPLETED",
DisplayName = "Completed",
DisplayOrder = 12,
ColorClass = "success",
IconClass = "bi-check2-all",
IsActive = true,
IsSystemDefined = true,
IsTerminalStatus = true,
IsWorkInProgressStatus = false,
WorkflowCategory = "Post-Production",
Description = "Job work is completed",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "READY_FOR_PICKUP",
DisplayName = "Ready for Pickup",
DisplayOrder = 13,
ColorClass = "success",
IconClass = "bi-box-seam",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = false,
WorkflowCategory = "Post-Production",
Description = "Job is ready for customer pickup",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "DELIVERED",
DisplayName = "Delivered",
DisplayOrder = 14,
ColorClass = "success",
IconClass = "bi-truck",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = true,
IsWorkInProgressStatus = false,
WorkflowCategory = "Post-Production",
Description = "Job has been delivered to customer",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "ON_HOLD",
DisplayName = "On Hold",
DisplayOrder = 15,
ColorClass = "dark",
IconClass = "bi-pause-circle",
IsActive = true,
IsSystemDefined = false,
IsTerminalStatus = false,
IsWorkInProgressStatus = false,
WorkflowCategory = "Other",
Description = "Job has been paused",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobStatusLookup
{
StatusCode = "CANCELLED",
DisplayName = "Cancelled",
DisplayOrder = 16,
ColorClass = "danger",
IconClass = "bi-x-circle",
IsActive = true,
IsSystemDefined = true,
IsTerminalStatus = true,
IsWorkInProgressStatus = false,
WorkflowCategory = "Other",
Description = "Job has been cancelled",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
}
};
await _context.Set<JobStatusLookup>().AddRangeAsync(statuses);
await _context.SaveChangesAsync();
return statuses.Count;
}
/// <summary>
/// Seeds the five standard job priority levels (Low, Normal, High, Urgent, Rush) as
/// lookup rows for a company, each with a Bootstrap colour class and Bootstrap Icon.
/// </summary>
/// <remarks>
/// Like job statuses, priorities are a lookup table so display names and colours can be
/// adjusted per tenant without a code change. The <c>PriorityCode</c> strings
/// (e.g., "NORMAL", "RUSH") are referenced by the job creation UI and report filters
/// and must not be renamed after seeding.
///
/// Idempotency check: bails early if 5 or more rows already exist for the company.
///
/// Both URGENT and RUSH use the "danger" colour class intentionally — they signal
/// different urgency levels but both demand immediate visual attention on the shop floor.
/// The distinction matters for rush-charge calculation: a Rush priority triggers the
/// rush-charge percentage defined in <see cref="CompanyOperatingCosts"/>.
/// </remarks>
/// <param name="company">The tenant company to seed priorities for.</param>
/// <returns>The number of priority rows created (5), or 0 if already seeded.</returns>
private async Task<int> SeedJobPriorityLookupsAsync(Company company)
{
// Check if job priorities already exist for this company
var existingCount = await _context.Set<JobPriorityLookup>()
.IgnoreQueryFilters()
.CountAsync(p => p.CompanyId == company.Id && !p.IsDeleted);
if (existingCount >= 5)
{
return 0; // Already seeded
}
var priorities = new List<JobPriorityLookup>
{
new JobPriorityLookup
{
PriorityCode = "LOW",
DisplayName = "Low",
DisplayOrder = 1,
ColorClass = "secondary",
IconClass = "bi-arrow-down",
IsActive = true,
IsSystemDefined = false,
Description = "Low priority - no rush",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobPriorityLookup
{
PriorityCode = "NORMAL",
DisplayName = "Normal",
DisplayOrder = 2,
ColorClass = "info",
IconClass = "bi-dash",
IsActive = true,
IsSystemDefined = false,
Description = "Normal priority - standard turnaround",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobPriorityLookup
{
PriorityCode = "HIGH",
DisplayName = "High",
DisplayOrder = 3,
ColorClass = "warning",
IconClass = "bi-arrow-up",
IsActive = true,
IsSystemDefined = false,
Description = "High priority - expedited processing",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobPriorityLookup
{
PriorityCode = "URGENT",
DisplayName = "Urgent",
DisplayOrder = 4,
ColorClass = "danger",
IconClass = "bi-exclamation-triangle",
IsActive = true,
IsSystemDefined = false,
Description = "Urgent - requires immediate attention",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new JobPriorityLookup
{
PriorityCode = "RUSH",
DisplayName = "Rush",
DisplayOrder = 5,
ColorClass = "danger",
IconClass = "bi-lightning",
IsActive = true,
IsSystemDefined = false,
Description = "Rush order - top priority",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
}
};
await _context.Set<JobPriorityLookup>().AddRangeAsync(priorities);
await _context.SaveChangesAsync();
return priorities.Count;
}
/// <summary>
/// Seeds the seven standard quote status lookup rows for a company
/// (Draft, Sent, Approved, Rejected, Expired, Converted, Revised).
/// </summary>
/// <remarks>
/// Quote status uses a lookup table (like job status) so operators can adjust display
/// names and colours. The <c>StatusCode</c> strings and the boolean semantic flags
/// (<c>IsApprovedStatus</c>, <c>IsConvertedStatus</c>, <c>IsDraftStatus</c>,
/// <c>IsRejectedStatus</c>) are what the application code queries — UI text is cosmetic.
///
/// Idempotency check: if 7+ rows exist the method skips insertion, but it performs
/// a retroactive fix for the REJECTED status: older seeded databases may have
/// <c>IsRejectedStatus = false</c> because the field was added after initial release.
/// The fix is applied on every call when the row already exists and the flag is wrong,
/// ensuring the quote portal correctly blocks re-approval of rejected quotes.
///
/// Key semantic flags:
/// - <c>IsApprovedStatus</c>: only APPROVED — triggers quote-to-job conversion eligibility.
/// - <c>IsConvertedStatus</c>: only CONVERTED — marks quotes that have already become jobs.
/// - <c>IsDraftStatus</c>: only DRAFT — controls edit permissions (draft quotes are fully editable).
/// - <c>IsRejectedStatus</c>: only REJECTED — prevents the customer approval portal from
/// re-activating a quote the customer already declined.
/// </remarks>
/// <param name="company">The tenant company to seed quote statuses for.</param>
/// <returns>The number of status rows created (7), or 0 if already seeded (retroactive fix may still apply).</returns>
private async Task<int> SeedQuoteStatusLookupsAsync(Company company)
{
// Check if quote statuses already exist for this company
var existingCount = await _context.Set<QuoteStatusLookup>()
.IgnoreQueryFilters()
.CountAsync(s => s.CompanyId == company.Id && !s.IsDeleted);
if (existingCount >= 7)
{
// Retroactive fix: ensure REJECTED status has IsRejectedStatus = true
var rejectedStatus = await _context.Set<QuoteStatusLookup>()
.IgnoreQueryFilters()
.FirstOrDefaultAsync(s => s.CompanyId == company.Id && s.StatusCode == "REJECTED" && !s.IsDeleted);
if (rejectedStatus != null && !rejectedStatus.IsRejectedStatus)
{
rejectedStatus.IsRejectedStatus = true;
await _context.SaveChangesAsync();
}
return 0; // Already seeded
}
var statuses = new List<QuoteStatusLookup>
{
new QuoteStatusLookup
{
StatusCode = "DRAFT",
DisplayName = "Draft",
DisplayOrder = 1,
ColorClass = "secondary",
IconClass = "bi-pencil",
IsActive = true,
IsSystemDefined = true,
IsDraftStatus = true,
IsApprovedStatus = false,
IsConvertedStatus = false,
Description = "Quote is being prepared",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new QuoteStatusLookup
{
StatusCode = "SENT",
DisplayName = "Sent",
DisplayOrder = 2,
ColorClass = "info",
IconClass = "bi-send",
IsActive = true,
IsSystemDefined = false,
IsDraftStatus = false,
IsApprovedStatus = false,
IsConvertedStatus = false,
Description = "Quote has been sent to customer",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new QuoteStatusLookup
{
StatusCode = "APPROVED",
DisplayName = "Approved",
DisplayOrder = 3,
ColorClass = "success",
IconClass = "bi-check-circle",
IsActive = true,
IsSystemDefined = true,
IsDraftStatus = false,
IsApprovedStatus = true,
IsConvertedStatus = false,
Description = "Quote has been approved by customer",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new QuoteStatusLookup
{
StatusCode = "REJECTED",
DisplayName = "Rejected",
DisplayOrder = 4,
ColorClass = "danger",
IconClass = "bi-x-circle",
IsActive = true,
IsSystemDefined = false,
IsDraftStatus = false,
IsApprovedStatus = false,
IsRejectedStatus = true,
IsConvertedStatus = false,
Description = "Quote has been rejected by customer",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new QuoteStatusLookup
{
StatusCode = "EXPIRED",
DisplayName = "Expired",
DisplayOrder = 5,
ColorClass = "warning",
IconClass = "bi-clock-history",
IsActive = true,
IsSystemDefined = false,
IsDraftStatus = false,
IsApprovedStatus = false,
IsConvertedStatus = false,
Description = "Quote has passed its expiration date",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new QuoteStatusLookup
{
StatusCode = "CONVERTED",
DisplayName = "Converted",
DisplayOrder = 6,
ColorClass = "primary",
IconClass = "bi-arrow-right-circle",
IsActive = true,
IsSystemDefined = true,
IsDraftStatus = false,
IsApprovedStatus = false,
IsConvertedStatus = true,
Description = "Quote has been converted to a job",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new QuoteStatusLookup
{
StatusCode = "REVISED",
DisplayName = "Revised",
DisplayOrder = 7,
ColorClass = "info",
IconClass = "bi-arrow-repeat",
IsActive = true,
IsSystemDefined = false,
IsDraftStatus = false,
IsApprovedStatus = false,
IsConvertedStatus = false,
Description = "Quote has been revised",
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
}
};
await _context.Set<QuoteStatusLookup>().AddRangeAsync(statuses);
await _context.SaveChangesAsync();
return statuses.Count;
}
/// <summary>
/// Seeds nine inventory category lookup rows for a company: Powder, Primer, Cleaner,
/// Masking Supplies, Abrasive Media, Chemicals, Consumables, Tools, and Other.
/// </summary>
/// <remarks>
/// Inventory categories are a lookup table so operators can rename them or add custom
/// categories. The <c>CategoryCode</c> strings (e.g., "POWDER", "CLEANER") are used by
/// <see cref="SeedInventoryItemsAsync"/> to resolve the correct category FK when creating
/// demo inventory items — they must not be changed without also updating that seeder.
///
/// The <c>IsCoating = true</c> flag on POWDER and PRIMER distinguishes coating materials
/// from consumables; the pricing engine uses this flag to identify which inventory items
/// can be selected as powder coats on a quote/job item.
///
/// Idempotency check: bails early if 9 or more rows exist for the company.
/// </remarks>
/// <param name="company">The tenant company to seed inventory categories for.</param>
/// <returns>The number of category rows created (9), or 0 if already seeded.</returns>
private async Task<int> SeedInventoryCategoryLookupsAsync(Company company)
{
// Check if categories already exist for this company
var existingCount = await _context.Set<InventoryCategoryLookup>()
.IgnoreQueryFilters()
.CountAsync(c => c.CompanyId == company.Id && !c.IsDeleted);
if (existingCount >= 9)
{
return 0; // Already seeded
}
var categories = new List<InventoryCategoryLookup>
{
new InventoryCategoryLookup
{
CategoryCode = "POWDER",
DisplayName = "Powder",
DisplayOrder = 1,
Description = "Powder coating materials",
IsActive = true,
IsSystemDefined = false,
IsCoating = true,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new InventoryCategoryLookup
{
CategoryCode = "PRIMER",
DisplayName = "Primer",
DisplayOrder = 2,
Description = "Primer coatings",
IsActive = true,
IsSystemDefined = false,
IsCoating = true,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new InventoryCategoryLookup
{
CategoryCode = "CLEANER",
DisplayName = "Cleaner",
DisplayOrder = 3,
Description = "Cleaning solutions and materials",
IsActive = true,
IsSystemDefined = false,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new InventoryCategoryLookup
{
CategoryCode = "MASKING",
DisplayName = "Masking Supplies",
DisplayOrder = 4,
Description = "Masking tape, plugs, and supplies",
IsActive = true,
IsSystemDefined = false,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new InventoryCategoryLookup
{
CategoryCode = "ABRASIVE",
DisplayName = "Abrasive Media",
DisplayOrder = 5,
Description = "Sandblasting and abrasive materials",
IsActive = true,
IsSystemDefined = false,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new InventoryCategoryLookup
{
CategoryCode = "CHEMICAL",
DisplayName = "Chemicals",
DisplayOrder = 6,
Description = "Chemical supplies and solutions",
IsActive = true,
IsSystemDefined = false,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new InventoryCategoryLookup
{
CategoryCode = "CONSUMABLE",
DisplayName = "Consumables",
DisplayOrder = 7,
Description = "General consumable supplies",
IsActive = true,
IsSystemDefined = false,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new InventoryCategoryLookup
{
CategoryCode = "TOOL",
DisplayName = "Tools",
DisplayOrder = 8,
Description = "Tools and equipment supplies",
IsActive = true,
IsSystemDefined = false,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new InventoryCategoryLookup
{
CategoryCode = "OTHER",
DisplayName = "Other",
DisplayOrder = 9,
Description = "Other miscellaneous inventory items",
IsActive = true,
IsSystemDefined = false,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
}
};
await _context.Set<InventoryCategoryLookup>().AddRangeAsync(categories);
await _context.SaveChangesAsync();
return categories.Count;
}
/// <summary>
/// Seeds eight standard surface-preparation services for a company: Sandblasting, Chemical
/// Stripping, Hand Sanding, Media Blasting, Iron Phosphate Wash, Degreasing, Masking, and
/// Outgassing.
/// </summary>
/// <remarks>
/// Prep services are selectable line-item additions on quote and job items, each adding
/// labour cost to the pricing calculation. They are stored per-company so operators can
/// rename, deactivate, or add custom services (e.g., "Zinc Phosphate Wash") without
/// affecting other tenants.
///
/// Idempotency check: bails if any rows exist for the company (count > 0), unlike
/// status lookups which use a minimum-count threshold. This is intentional: if a
/// partial seed left only some services, the operator can remove them and re-run to get
/// the full set.
///
/// Outgassing (pre-bake to release trapped gases from castings) is included because it is
/// a mandatory step for cast aluminium and cast iron parts and is a common source of
/// rework if omitted — including it in the default list helps new operators remember to
/// quote it.
/// </remarks>
/// <param name="company">The tenant company to seed prep services for.</param>
/// <returns>The number of prep service rows created (8), or 0 if any already existed.</returns>
private async Task<int> SeedPrepServicesAsync(Company company)
{
var existingCount = await _context.Set<PrepService>()
.IgnoreQueryFilters()
.CountAsync(s => s.CompanyId == company.Id && !s.IsDeleted);
if (existingCount > 0)
return 0; // Already seeded
var services = new List<PrepService>
{
new PrepService
{
ServiceName = "Sandblasting",
Description = "Abrasive blasting to remove rust, old coatings, and surface contaminants",
DisplayOrder = 1,
IsActive = true,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new PrepService
{
ServiceName = "Chemical Stripping",
Description = "Chemical bath or application to strip existing paint or coatings",
DisplayOrder = 2,
IsActive = true,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new PrepService
{
ServiceName = "Hand Sanding",
Description = "Manual sanding to smooth surfaces and improve adhesion",
DisplayOrder = 3,
IsActive = true,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new PrepService
{
ServiceName = "Media Blasting",
Description = "Blasting with glass beads, walnut shells, or other media for delicate surfaces",
DisplayOrder = 4,
IsActive = true,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new PrepService
{
ServiceName = "Iron Phosphate Wash",
Description = "Chemical pre-treatment wash to improve powder adhesion and corrosion resistance",
DisplayOrder = 5,
IsActive = true,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new PrepService
{
ServiceName = "Degreasing",
Description = "Solvent or aqueous cleaning to remove oils, grease, and shop soils",
DisplayOrder = 6,
IsActive = true,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new PrepService
{
ServiceName = "Masking",
Description = "Masking of threads, holes, or areas that must remain uncoated",
DisplayOrder = 7,
IsActive = true,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
},
new PrepService
{
ServiceName = "Outgassing",
Description = "Pre-bake cycle to release trapped gases from castings before coating",
DisplayOrder = 8,
IsActive = true,
CompanyId = company.Id,
CreatedAt = DateTime.UtcNow
}
};
await _context.Set<PrepService>().AddRangeAsync(services);
await _context.SaveChangesAsync();
return services.Count;
}
}