293 lines
7.7 KiB
C#
293 lines
7.7 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Fills an empty database with three demo users (UAH/USD/EUR) and sample catalog data.
|
|
/// </summary>
|
|
public static class DemoDataSeeder
|
|
{
|
|
private static readonly PasswordHasher<object> 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"),
|
|
];
|
|
|
|
/// <summary>1 USD = 42 UAH, 1 EUR = 50 UAH.</summary>
|
|
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<string, Currency>(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<Account>();
|
|
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<List<Item>> AddItemsAsync(
|
|
AppDbContext db,
|
|
Guid categoryId,
|
|
IEnumerable<string> names,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var items = new List<Item>();
|
|
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<Account> accounts,
|
|
List<Item> incomeItems,
|
|
List<Item> 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,
|
|
};
|
|
}
|