Files
meezi/src/Meezi.API/Services/DemoSeedService.cs
T

180 lines
7.1 KiB
C#
Raw Normal View History

using Meezi.Core.Entities;
using Meezi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Meezi.API.Services;
public record DemoSeedResult(
int CategoriesAdded,
int ItemsAdded,
int TablesAdded,
int IngredientsAdded,
bool TaxCreated);
public interface IDemoSeedService
{
Task<DemoSeedResult> SeedAsync(string cafeId, CancellationToken ct = default);
}
public class DemoSeedService : IDemoSeedService
{
private readonly AppDbContext _db;
private readonly ILogger<DemoSeedService> _logger;
public DemoSeedService(AppDbContext db, ILogger<DemoSeedService> logger)
{
_db = db;
_logger = logger;
}
public async Task<DemoSeedResult> SeedAsync(string cafeId, CancellationToken ct = default)
{
// 1. Ensure 9% default tax
var taxId = $"{cafeId}_demo_tax";
var taxCreated = false;
if (!await _db.Taxes.AnyAsync(t => t.CafeId == cafeId && t.IsDefault, ct))
{
_db.Taxes.Add(new Tax
{
Id = taxId,
CafeId = cafeId,
Name = "مالیات ارزش افزوده",
Rate = 9,
IsDefault = true,
IsRequired = true,
IsCompound = false
});
await _db.SaveChangesAsync(ct);
taxCreated = true;
}
else
{
taxId = await _db.Taxes
.Where(t => t.CafeId == cafeId && t.IsDefault)
.Select(t => t.Id)
.FirstAsync(ct);
}
// 2. Seed menu (categories + items) using café-agnostic seeder.
// useScopedIds=true prefixes all IDs with cafeId so multiple cafés
// can each have their own demo menu without primary-key collisions.
var beforeCats = await _db.MenuCategories.CountAsync(c => c.CafeId == cafeId, ct);
var beforeItems = await _db.MenuItems.CountAsync(i => i.CafeId == cafeId, ct);
await DemoMenuSeeder.EnsureMenuAsync(_db, cafeId, taxId, _logger, useScopedIds: true);
var afterCats = await _db.MenuCategories.CountAsync(c => c.CafeId == cafeId, ct);
var afterItems = await _db.MenuItems.CountAsync(i => i.CafeId == cafeId, ct);
// 3. Seed ingredients if warehouse is empty
var ingredientsAdded = 0;
if (!await _db.Ingredients.AnyAsync(i => i.CafeId == cafeId, ct))
{
var demoIngredients = BuildDemoIngredients(cafeId);
_db.Ingredients.AddRange(demoIngredients);
await _db.SaveChangesAsync(ct);
ingredientsAdded = demoIngredients.Count;
}
// 4. Seed 10 tables if no tables exist for this café's first active branch
var tablesAdded = 0;
if (!await _db.Tables.AnyAsync(t => t.CafeId == cafeId, ct))
{
var branchId = await _db.Branches
.Where(b => b.CafeId == cafeId && b.IsActive && b.DeletedAt == null)
.OrderBy(b => b.Id)
.Select(b => b.Id)
.FirstOrDefaultAsync(ct);
if (branchId is not null)
{
var tables = BuildDemoTables(cafeId, branchId);
_db.Tables.AddRange(tables);
await _db.SaveChangesAsync(ct);
tablesAdded = tables.Count;
}
}
_logger.LogInformation(
"Demo seed complete for cafe {CafeId}: +{Cats} cats, +{Items} items, +{Tables} tables, +{Ing} ingredients, tax={TaxCreated}",
cafeId, afterCats - beforeCats, afterItems - beforeItems, tablesAdded, ingredientsAdded, taxCreated);
return new DemoSeedResult(
CategoriesAdded: afterCats - beforeCats,
ItemsAdded: afterItems - beforeItems,
TablesAdded: tablesAdded,
IngredientsAdded: ingredientsAdded,
TaxCreated: taxCreated);
}
private static List<Ingredient> BuildDemoIngredients(string cafeId) =>
[
Ingredient(cafeId, "قهوه اسپرسو", "گرم", 2000, 500, 80, 2000),
Ingredient(cafeId, "شیر", "میلی‌لیتر", 10000, 2000, 15, 10000),
Ingredient(cafeId, "شکر", "گرم", 5000, 1000, 5, 5000),
Ingredient(cafeId, "وانیل", "میلی‌لیتر", 500, 100, 50, 500),
Ingredient(cafeId, "شکلات تلخ", "گرم", 1000, 200, 120, 1000),
Ingredient(cafeId, "خامه", "میلی‌لیتر", 2000, 500, 30, 2000),
Ingredient(cafeId, "دارچین", "گرم", 300, 50, 40, 300),
Ingredient(cafeId, "چای سیاه", "گرم", 1000, 200, 60, 1000),
Ingredient(cafeId, "آب معدنی", "میلی‌لیتر", 20000, 5000, 3, 20000),
Ingredient(cafeId, "نان تست", "عدد", 100, 20, 8000, 100),
Ingredient(cafeId, "تخم‌مرغ", "عدد", 60, 12, 6000, 60),
Ingredient(cafeId, "کره", "گرم", 500, 100, 80, 500),
Ingredient(cafeId, "پنیر", "گرم", 1000, 200, 90, 1000),
Ingredient(cafeId, "اسپاتولا یخ", "عدد", 200, 50, 2000, 200),
Ingredient(cafeId, "سس کارامل", "میلی‌لیتر", 1000, 200, 60, 1000),
];
private static Ingredient Ingredient(
string cafeId, string name, string unit,
decimal qty, decimal reorder, decimal cost, decimal par) =>
new()
{
// No [..36] truncation: Id is a text column, and truncating to 36 chars
// cuts off the unique guid for real (32-char) café ids → every row gets
// the same id → PK collision → 500. Keep the full unique id.
Id = $"{cafeId}_ing_{Guid.NewGuid():N}",
CafeId = cafeId,
Name = name,
Unit = unit,
QuantityOnHand = qty,
ReorderLevel = reorder,
UnitCost = cost,
ParLevel = par,
LowStockWarningPercent = 20m
};
private static List<Table> BuildDemoTables(string cafeId, string branchId)
{
var tables = new List<Table>();
// Floor 1: tables 1-4
for (var i = 1; i <= 4; i++)
tables.Add(Table(cafeId, branchId, i.ToString(), 4, "طبقه اول", i));
// Floor 2: tables 5-8
for (var i = 5; i <= 8; i++)
tables.Add(Table(cafeId, branchId, i.ToString(), 4, "طبقه دوم", i));
// VIP: tables 9-10
for (var i = 9; i <= 10; i++)
tables.Add(Table(cafeId, branchId, i.ToString(), 6, "VIP", i));
return tables;
}
private static Table Table(
string cafeId, string branchId, string number, int capacity, string floor, int sortOrder) =>
new()
{
// No [..36] truncation (see Ingredient above): truncating cuts the guid
// for real 32-char café ids → identical ids → PK collision → 500.
Id = $"{cafeId}_tbl_{Guid.NewGuid():N}",
CafeId = cafeId,
BranchId = branchId,
Number = number,
Capacity = capacity,
Floor = floor,
SortOrder = sortOrder,
QrCode = Guid.NewGuid().ToString("N"),
IsActive = true,
IsCleaning = false
};
}