food-market/src/food-market.api/Seed/DevDataSeeder.cs
nurdotnet bcbda1ae5d fix(seeder): bootstrap admin + demo org on stage/prod too, not just Dev
Login on https://food-market.zat.kz failed because DevDataSeeder skipped
in non-Dev envs, so the demo admin account never existed on stage.

Seeder is idempotent — checks-then-creates for every entity. Safe to run
on every startup in any env. Once a real org/admin replaces the seeded
demo, this seeder is a no-op.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:42:54 +05:00

144 lines
5.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using foodmarket.Domain.Catalog;
using foodmarket.Domain.Organizations;
using foodmarket.Infrastructure.Identity;
using foodmarket.Infrastructure.Persistence;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace foodmarket.Api.Seed;
public class DevDataSeeder : IHostedService
{
private readonly IServiceProvider _services;
private readonly IHostEnvironment _env;
public DevDataSeeder(IServiceProvider services, IHostEnvironment env)
{
_services = services;
_env = env;
}
public async Task StartAsync(CancellationToken ct)
{
// Idempotent — runs in all envs to bootstrap a usable admin + demo org.
// Once first real user/org is set up via UI, rename/disable demo.
// (Wired regardless of env so stage/prod first-deploy lands a working
// admin, otherwise nobody can log in.)
using var scope = _services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var userMgr = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
var roleMgr = scope.ServiceProvider.GetRequiredService<RoleManager<Role>>();
foreach (var role in new[] { SystemRoles.SuperAdmin, SystemRoles.Admin, SystemRoles.Manager, SystemRoles.Cashier, SystemRoles.Storekeeper })
{
if (!await roleMgr.RoleExistsAsync(role))
{
await roleMgr.CreateAsync(new Role { Name = role });
}
}
var demoOrg = await db.Organizations.FirstOrDefaultAsync(o => o.Name == "Demo Market", ct);
if (demoOrg is null)
{
demoOrg = new Organization
{
Name = "Demo Market",
CountryCode = "KZ",
Bin = "000000000000",
Address = "Алматы, ул. Пример 1",
Phone = "+7 (777) 000-00-00",
Email = "demo@food-market.local"
};
db.Organizations.Add(demoOrg);
await db.SaveChangesAsync(ct);
}
await SeedTenantReferencesAsync(db, demoOrg.Id, ct);
const string adminEmail = "admin@food-market.local";
var admin = await userMgr.FindByEmailAsync(adminEmail);
if (admin is null)
{
admin = new User
{
UserName = adminEmail,
Email = adminEmail,
EmailConfirmed = true,
FullName = "System Admin",
OrganizationId = demoOrg.Id,
};
var result = await userMgr.CreateAsync(admin, "Admin12345!");
if (result.Succeeded)
{
await userMgr.AddToRoleAsync(admin, SystemRoles.Admin);
}
}
}
private static async Task SeedTenantReferencesAsync(AppDbContext db, Guid orgId, CancellationToken ct)
{
var anyVat = await db.VatRates.IgnoreQueryFilters().AnyAsync(v => v.OrganizationId == orgId, ct);
if (!anyVat)
{
db.VatRates.AddRange(
new VatRate { OrganizationId = orgId, Name = "Без НДС", Percent = 0m, IsDefault = false },
new VatRate { OrganizationId = orgId, Name = "НДС 12%", Percent = 12m, IsIncludedInPrice = true, IsDefault = true }
);
}
var anyUnit = await db.UnitsOfMeasure.IgnoreQueryFilters().AnyAsync(u => u.OrganizationId == orgId, ct);
if (!anyUnit)
{
db.UnitsOfMeasure.AddRange(
new UnitOfMeasure { OrganizationId = orgId, Code = "796", Symbol = "шт", Name = "штука", DecimalPlaces = 0, IsBase = true },
new UnitOfMeasure { OrganizationId = orgId, Code = "166", Symbol = "кг", Name = "килограмм", DecimalPlaces = 3 },
new UnitOfMeasure { OrganizationId = orgId, Code = "112", Symbol = "л", Name = "литр", DecimalPlaces = 3 },
new UnitOfMeasure { OrganizationId = orgId, Code = "006", Symbol = "м", Name = "метр", DecimalPlaces = 3 },
new UnitOfMeasure { OrganizationId = orgId, Code = "625", Symbol = "уп", Name = "упаковка", DecimalPlaces = 0 }
);
}
var anyPriceType = await db.PriceTypes.IgnoreQueryFilters().AnyAsync(p => p.OrganizationId == orgId, ct);
if (!anyPriceType)
{
db.PriceTypes.AddRange(
new PriceType { OrganizationId = orgId, Name = "Розничная", IsDefault = true, IsRetail = true, SortOrder = 1 },
new PriceType { OrganizationId = orgId, Name = "Оптовая", SortOrder = 2 }
);
}
var mainStore = await db.Stores.IgnoreQueryFilters().FirstOrDefaultAsync(s => s.OrganizationId == orgId && s.IsMain, ct);
if (mainStore is null)
{
mainStore = new Store
{
OrganizationId = orgId,
Name = "Основной склад",
Code = "MAIN",
Kind = StoreKind.Warehouse,
IsMain = true,
Address = "Алматы, ул. Пример 1",
};
db.Stores.Add(mainStore);
await db.SaveChangesAsync(ct);
}
var anyRetail = await db.RetailPoints.IgnoreQueryFilters().AnyAsync(r => r.OrganizationId == orgId, ct);
if (!anyRetail)
{
db.RetailPoints.Add(new RetailPoint
{
OrganizationId = orgId,
Name = "Касса 1",
Code = "POS-1",
StoreId = mainStore.Id,
});
}
await db.SaveChangesAsync(ct);
}
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}