namespace MyOffice.DbContext; using Data.Models.Accounts; using Data.Models.Currencies; using Data.Models.Items; using Data.Models.Users; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; /// /// Fills an empty database with three demo users (UAH/USD/EUR) and sample catalog data. /// public static class DemoDataSeeder { private static readonly PasswordHasher PasswordHasher = new(); private static readonly string[] CurrencyCodes = ["UAH", "USD", "EUR"]; private static readonly (string Code, string Email, string Password, string FirstName)[] Users = [ ("UAH", "user_UAH@user_UAH.userUAH", "user_UAH", "User UAH"), ("USD", "user_USD@user_USD.userUSD", "user_USD", "User USD"), ("EUR", "user_EUR@user_EUR.userEUR", "user_EUR", "User EUR"), ]; /// 1 USD = 42 UAH, 1 EUR = 50 UAH. private const decimal UsdInUah = 42m; private const decimal EurInUah = 50m; public static async Task SeedIfEmptyAsync(AppDbContext db, CancellationToken cancellationToken = default) { if (await db.Users.AnyAsync(cancellationToken)) { return; } // AppDbContext defaults to NoTracking; seeding needs identity values and graph inserts. var previousTracking = db.ChangeTracker.QueryTrackingBehavior; var previousDetect = db.ChangeTracker.AutoDetectChangesEnabled; db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll; db.ChangeTracker.AutoDetectChangesEnabled = true; try { var yearStart = new DateTime(DateTime.UtcNow.Year, 1, 1, 0, 0, 0, DateTimeKind.Utc); var rng = new Random(2026); foreach (var (code, email, password, firstName) in Users) { await SeedUserAsync(db, code, email, password, firstName, yearStart, rng, cancellationToken); } await db.SaveChangesAsync(cancellationToken); } finally { db.ChangeTracker.QueryTrackingBehavior = previousTracking; db.ChangeTracker.AutoDetectChangesEnabled = previousDetect; db.ChangeTracker.Clear(); } } private static async Task SeedUserAsync( AppDbContext db, string homeCurrency, string email, string password, string firstName, DateTime rateDate, Random rng, CancellationToken cancellationToken) { var ua = homeCurrency == "UAH"; var userId = Guid.NewGuid(); var user = new User { Id = userId, UserName = email, Email = email, PasswordHash = PasswordHasher.HashPassword(new object(), password), FirstName = firstName, FullName = firstName, IsEmailConfirmed = true, CurrencyId = homeCurrency, }; db.Users.Add(user); var currencies = new Dictionary(StringComparer.Ordinal); foreach (var code in CurrencyCodes) { var currency = new Currency { Id = Guid.NewGuid(), UserId = userId, CurrencyGlobalId = code, Name = CurrencyDisplayName(code, ua), ShortName = code, IsPrimary = code == homeCurrency, }; db.Currencies.Add(currency); db.CurrencyRates.Add(new CurrencyRate { CurrencyId = currency.Id, DateTime = rateDate, Quantity = 1, Rate = RateInHome(code, homeCurrency), }); currencies[code] = currency; } // UnCategorized convention: category Id == user Id db.ItemCategories.Add(new ItemCategory { Id = userId, UserId = userId, Name = ua ? "Без категорії" : "UnCategorized", Items = [], IsInternal = true, }); var accountCategoryDefs = ua ? new (string Key, string Name)[] { ("CASH", "Готівка"), ("BANK", "Банк"), ("DEPOSIT", "Депозит") } : [("CASH", "CASH"), ("BANK", "BANK"), ("DEPOSIT", "DEPOSIT")]; var accounts = new List(); foreach (var (key, name) in accountCategoryDefs) { var category = new AccountCategory { Id = Guid.NewGuid(), UserId = userId, Name = name, }; db.AccountCategories.Add(category); foreach (var code in CurrencyCodes) { var account = new Account { Id = Guid.NewGuid(), OwnerId = userId, CurrencyGlobalId = code, Name = $"{name} {code}", }; db.Accounts.Add(account); db.AccountAccountCategories.Add(new AccountAccountCategory { AccountId = account.Id, CategoryId = category.Id, }); db.AccountAccesses.Add(new AccountAccess { AccountId = account.Id, UserId = userId, OwnerId = userId, IsAllowRead = true, IsAllowWrite = true, IsAllowManage = true, Type = AccountAccessTypeEnum.balance, }); accounts.Add(account); } } var incomeCategory = new ItemCategory { Id = Guid.NewGuid(), UserId = userId, Name = ua ? "Доходи" : "INCOME", Items = [], }; var outcomeCategory = new ItemCategory { Id = Guid.NewGuid(), UserId = userId, Name = ua ? "Витрати" : "OUTCOME", Items = [], }; db.ItemCategories.AddRange(incomeCategory, outcomeCategory); var incomeNames = ua ? new[] { "Готівка", "Офіс" } : ["CASH", "OFFICE"]; var outcomeNames = ua ? new[] { "Товари", "Бензин", "Steam", "Продукти", "Кафе", "Оренда", "Комунальні", "Інтернет", "Зв'язок", "Розваги", } : [ "Goods", "Petrol", "Steam", "Groceries", "Cafe", "Rent", "Utilities", "Internet", "Mobile", "Entertainment", ]; var incomeItems = await AddItemsAsync(db, incomeCategory.Id, incomeNames, cancellationToken); var outcomeItems = await AddItemsAsync(db, outcomeCategory.Id, outcomeNames, cancellationToken); await db.SaveChangesAsync(cancellationToken); SeedMotions(db, accounts, incomeItems, outcomeItems, rng, count: 100); } private static async Task> AddItemsAsync( AppDbContext db, Guid categoryId, IEnumerable names, CancellationToken cancellationToken) { var items = new List(); foreach (var name in names) { var global = db.ItemGlobals.Local.FirstOrDefault(x => x.Name == name) ?? await db.ItemGlobals.FirstOrDefaultAsync(x => x.Name == name, cancellationToken); if (global == null) { global = new ItemGlobal { Id = Guid.NewGuid(), Name = name }; db.ItemGlobals.Add(global); } var item = new Item { CategoryId = categoryId, ItemGlobalId = global.Id, }; db.Items.Add(item); items.Add(item); } return items; } private static void SeedMotions( AppDbContext db, List accounts, List incomeItems, List outcomeItems, Random rng, int count) { var now = DateTime.UtcNow; var from = now.AddMonths(-3); for (var i = 0; i < count; i++) { var isIncome = rng.Next(2) == 0; var item = isIncome ? incomeItems[rng.Next(incomeItems.Count)] : outcomeItems[rng.Next(outcomeItems.Count)]; var account = accounts[rng.Next(accounts.Count)]; var days = (now - from).TotalDays; var when = from.AddDays(rng.NextDouble() * days); var amount = Math.Round((decimal)(rng.NextDouble() * (isIncome ? 4500 : 700) + (isIncome ? 200 : 20)), 2); db.Motions.Add(new Motion { Id = Guid.NewGuid(), AccountId = account.Id, ItemId = item.Id, DateTime = when, CreatedOn = when, Description = isIncome ? "Demo income" : "Demo expense", AmountPlus = isIncome ? amount : 0, AmountMinus = isIncome ? 0 : amount, }); } } private static decimal RateInHome(string currencyCode, string homeCurrency) { static decimal InUah(string code) => code switch { "UAH" => 1m, "USD" => UsdInUah, "EUR" => EurInUah, _ => 1m, }; return InUah(currencyCode) / InUah(homeCurrency); } private static string CurrencyDisplayName(string code, bool ua) => (code, ua) switch { ("UAH", true) => "Гривня", ("USD", true) => "Долар США", ("EUR", true) => "Євро", ("UAH", _) => "Ukrainian hryvnia", ("USD", _) => "US Dollar", ("EUR", _) => "Euro", _ => code, }; }