using Meezi.Core.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace Meezi.Infrastructure.Data; public static class DemoMenuSeeder { /// /// When true, category and item IDs are prefixed with so /// multiple cafés can each have their own copy of the demo menu without a primary-key /// collision. Pass false only for the legacy demo café (cafe_demo_001) whose IDs are /// already in the database without a café prefix. /// public static async Task EnsureMenuAsync( AppDbContext db, string cafeId, string taxId, ILogger logger, bool useScopedIds = false) { // When useScopedIds=true every row gets a deterministic ID that is unique per café: // category → "{cafeId}_{catalogId}" // item → "{cafeId}_{catalogId}" // The catalog item's CategoryId is remapped through the same function. string Scoped(string catalogId) => useScopedIds ? $"{cafeId}_{catalogId}" : catalogId; if (!await db.Taxes.AnyAsync(t => t.Id == taxId && t.CafeId == cafeId)) { db.Taxes.Add(new Tax { Id = taxId, CafeId = cafeId, Name = "مالیات", Rate = 9, IsDefault = true, IsRequired = true, IsCompound = false }); } var existingCategoryIds = await db.MenuCategories .Where(c => c.CafeId == cafeId) .ToDictionaryAsync(c => c.Id, StringComparer.Ordinal); var categoriesAdded = 0; foreach (var cat in DemoMenuCatalog.Categories) { var catId = Scoped(cat.Id); if (existingCategoryIds.TryGetValue(catId, out var row)) { if (string.IsNullOrWhiteSpace(row.Icon) && !string.IsNullOrWhiteSpace(cat.Icon)) row.Icon = cat.Icon; if (string.IsNullOrWhiteSpace(row.IconPresetId) && !string.IsNullOrWhiteSpace(cat.IconPresetId)) row.IconPresetId = cat.IconPresetId; if (string.IsNullOrWhiteSpace(row.IconStyle) && !string.IsNullOrWhiteSpace(cat.IconStyle)) row.IconStyle = cat.IconStyle; if (string.IsNullOrWhiteSpace(row.NameEn) && !string.IsNullOrWhiteSpace(cat.NameEn)) row.NameEn = cat.NameEn; if (string.IsNullOrWhiteSpace(row.NameAr) && cat.NameAr is not null) row.NameAr = cat.NameAr; continue; } db.MenuCategories.Add(new MenuCategory { Id = catId, CafeId = cafeId, Name = cat.Name, NameEn = cat.NameEn, NameAr = cat.NameAr, Icon = cat.Icon, IconPresetId = cat.IconPresetId, IconStyle = cat.IconStyle, SortOrder = cat.SortOrder, TaxId = taxId, IsActive = true }); categoriesAdded++; } var existingItemIds = await db.MenuItems .Where(i => i.CafeId == cafeId) .Select(i => i.Id) .ToListAsync(); var itemsAdded = 0; foreach (var item in DemoMenuCatalog.Items) { var itemId = Scoped(item.Id); if (existingItemIds.Contains(itemId)) continue; db.MenuItems.Add(new MenuItem { Id = itemId, CafeId = cafeId, CategoryId = Scoped(item.CategoryId), // FK must point at scoped category ID Name = item.Name, NameEn = item.NameEn, NameAr = item.NameAr, Description = item.Description, Price = item.PriceToman, DiscountPercent = item.DiscountPercent, ImageUrl = DemoMenuCatalog.ResolveItemImageUrl(item), IsAvailable = true }); itemsAdded++; } if (categoriesAdded > 0 || itemsAdded > 0) await db.SaveChangesAsync(); if (categoriesAdded > 0 || itemsAdded > 0) { logger.LogInformation( "Demo menu seed: +{Categories} categories, +{Items} items (catalog total {Total}) for cafe {CafeId}", categoriesAdded, itemsAdded, DemoMenuCatalog.Items.Count, cafeId); } await EnsureMenuImagesAsync(db, cafeId, logger); await EnsureMenuTranslationsAsync(db, cafeId, logger); } /// Upserts ImageUrl from catalog/manifest/Food-101 fallbacks. public static async Task EnsureMenuImagesAsync(AppDbContext db, string cafeId, ILogger logger) { var catalogById = DemoMenuCatalog.Items.ToDictionary(i => i.Id, StringComparer.Ordinal); var items = await db.MenuItems .Include(i => i.Category) .Where(i => i.CafeId == cafeId) .ToListAsync(); var updated = 0; foreach (var row in items) { var resolved = catalogById.TryGetValue(row.Id, out var seed) ? DemoMenuCatalog.ResolveItemImageUrl(seed) : MenuItemImageDefaults.ResolveImageUrl(row.Id, row.CategoryId, row.Category?.Name); if (string.IsNullOrWhiteSpace(resolved)) continue; var inCatalog = catalogById.ContainsKey(row.Id); var shouldUpdate = MenuItemImageDefaults.NeedsImageRepair(row.ImageUrl) || inCatalog; if (!shouldUpdate || string.Equals(row.ImageUrl, resolved, StringComparison.Ordinal)) continue; row.ImageUrl = resolved; updated++; } if (updated > 0) { await db.SaveChangesAsync(); logger.LogInformation("Menu image upsert: {Count} items updated for cafe {CafeId}", updated, cafeId); } } /// Upserts NameEn/NameAr from catalog for demo menu rows. public static async Task EnsureMenuTranslationsAsync(AppDbContext db, string cafeId, ILogger logger) { var catalogItems = DemoMenuCatalog.Items.ToDictionary(i => i.Id, StringComparer.Ordinal); var catalogCats = DemoMenuCatalog.Categories.ToDictionary(c => c.Id, StringComparer.Ordinal); var items = await db.MenuItems.Where(i => i.CafeId == cafeId && catalogItems.Keys.Contains(i.Id)).ToListAsync(); var itemUpdated = 0; foreach (var row in items) { if (!catalogItems.TryGetValue(row.Id, out var seed)) continue; var changed = false; if (string.IsNullOrWhiteSpace(row.NameEn) && !string.IsNullOrWhiteSpace(seed.NameEn)) { row.NameEn = seed.NameEn; changed = true; } if (string.IsNullOrWhiteSpace(row.NameAr) && seed.NameAr is not null) { row.NameAr = seed.NameAr; changed = true; } if (changed) itemUpdated++; } var categories = await db.MenuCategories.Where(c => c.CafeId == cafeId && catalogCats.Keys.Contains(c.Id)).ToListAsync(); var catUpdated = 0; foreach (var row in categories) { if (!catalogCats.TryGetValue(row.Id, out var seed)) continue; var changed = false; if (string.IsNullOrWhiteSpace(row.NameEn) && !string.IsNullOrWhiteSpace(seed.NameEn)) { row.NameEn = seed.NameEn; changed = true; } if (string.IsNullOrWhiteSpace(row.NameAr) && seed.NameAr is not null) { row.NameAr = seed.NameAr; changed = true; } if (string.IsNullOrWhiteSpace(row.Icon) && !string.IsNullOrWhiteSpace(seed.Icon)) { row.Icon = seed.Icon; changed = true; } if (changed) catUpdated++; } if (itemUpdated > 0 || catUpdated > 0) { await db.SaveChangesAsync(); logger.LogInformation( "Menu translation upsert: {Items} items, {Cats} categories for cafe {CafeId}", itemUpdated, catUpdated, cafeId); } } }