feat(api): .NET 10 multi-tenant REST API
Full backend implementation: - Multi-tenant cafe/restaurant management (menus, orders, tables, staff) - POS order flow with ZarinPal and Snappfood payment integration - OTP authentication via Kavenegar SMS - QR digital menu with public discover/finder endpoints - Customer loyalty, coupons, CRM - PostgreSQL via EF Core, Redis for caching/sessions - Background jobs, webhook handlers - Full migration history Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,605 @@
|
||||
using System.Text.Json;
|
||||
using Meezi.Core.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
public class AppDbContext : DbContext
|
||||
{
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<Cafe> Cafes => Set<Cafe>();
|
||||
public DbSet<Branch> Branches => Set<Branch>();
|
||||
public DbSet<Table> Tables => Set<Table>();
|
||||
public DbSet<TableSection> TableSections => Set<TableSection>();
|
||||
public DbSet<Employee> Employees => Set<Employee>();
|
||||
public DbSet<MenuCategory> MenuCategories => Set<MenuCategory>();
|
||||
public DbSet<MenuItem> MenuItems => Set<MenuItem>();
|
||||
public DbSet<BranchMenuItemOverride> BranchMenuItemOverrides => Set<BranchMenuItemOverride>();
|
||||
public DbSet<Order> Orders => Set<Order>();
|
||||
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
|
||||
public DbSet<Payment> Payments => Set<Payment>();
|
||||
public DbSet<Customer> Customers => Set<Customer>();
|
||||
public DbSet<Coupon> Coupons => Set<Coupon>();
|
||||
public DbSet<Tax> Taxes => Set<Tax>();
|
||||
public DbSet<EmployeeSalary> EmployeeSalaries => Set<EmployeeSalary>();
|
||||
public DbSet<Attendance> Attendances => Set<Attendance>();
|
||||
public DbSet<EmployeeSchedule> EmployeeSchedules => Set<EmployeeSchedule>();
|
||||
public DbSet<Shift> RegisterShifts => Set<Shift>();
|
||||
public DbSet<CashTransaction> CashTransactions => Set<CashTransaction>();
|
||||
public DbSet<LeaveRequest> LeaveRequests => Set<LeaveRequest>();
|
||||
public DbSet<TableReservation> TableReservations => Set<TableReservation>();
|
||||
public DbSet<CafeReview> CafeReviews => Set<CafeReview>();
|
||||
public DbSet<CafeReviewPhoto> CafeReviewPhotos => Set<CafeReviewPhoto>();
|
||||
public DbSet<ConsumerAccount> ConsumerAccounts => Set<ConsumerAccount>();
|
||||
public DbSet<KitchenStation> KitchenStations => Set<KitchenStation>();
|
||||
public DbSet<SubscriptionPayment> SubscriptionPayments => Set<SubscriptionPayment>();
|
||||
public DbSet<Ingredient> Ingredients => Set<Ingredient>();
|
||||
public DbSet<MenuItemIngredient> MenuItemIngredients => Set<MenuItemIngredient>();
|
||||
public DbSet<StockMovement> StockMovements => Set<StockMovement>();
|
||||
public DbSet<QueueTicket> QueueTickets => Set<QueueTicket>();
|
||||
public DbSet<DailyReport> DailyReports => Set<DailyReport>();
|
||||
public DbSet<Expense> Expenses => Set<Expense>();
|
||||
public DbSet<WebhookLog> WebhookLogs => Set<WebhookLog>();
|
||||
public DbSet<DeliveryCommissionRate> DeliveryCommissionRates => Set<DeliveryCommissionRate>();
|
||||
public DbSet<SystemAdmin> SystemAdmins => Set<SystemAdmin>();
|
||||
public DbSet<PlatformPlanDefinition> PlatformPlanDefinitions => Set<PlatformPlanDefinition>();
|
||||
public DbSet<PlatformSetting> PlatformSettings => Set<PlatformSetting>();
|
||||
public DbSet<PlatformFeature> PlatformFeatures => Set<PlatformFeature>();
|
||||
public DbSet<CafeFeatureOverride> CafeFeatureOverrides => Set<CafeFeatureOverride>();
|
||||
public DbSet<SupportTicket> SupportTickets => Set<SupportTicket>();
|
||||
public DbSet<SupportTicketMessage> SupportTicketMessages => Set<SupportTicketMessage>();
|
||||
public DbSet<CafeNotification> CafeNotifications => Set<CafeNotification>();
|
||||
|
||||
// Website CMS
|
||||
public DbSet<WebsiteBlogPost> WebsiteBlogPosts => Set<WebsiteBlogPost>();
|
||||
public DbSet<WebsiteComment> WebsiteComments => Set<WebsiteComment>();
|
||||
public DbSet<DemoRequest> DemoRequests => Set<DemoRequest>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<Cafe>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasIndex(x => x.Slug).IsUnique();
|
||||
e.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
||||
e.Property(x => x.Slug).HasMaxLength(100).IsRequired();
|
||||
e.Property(x => x.SnappfoodVendorId).HasMaxLength(100);
|
||||
e.Property(x => x.Tap30VendorId).HasMaxLength(100);
|
||||
e.Property(x => x.DigikalaVendorId).HasMaxLength(100);
|
||||
e.Property(x => x.ThemeJson).HasMaxLength(8000);
|
||||
e.Property(x => x.DiscoverProfileJson).HasMaxLength(8000);
|
||||
e.Property(x => x.DiscoverBadgesJson).HasMaxLength(2000);
|
||||
e.Property(x => x.DefaultTaxRate).HasPrecision(5, 2);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Branch>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
||||
e.Property(x => x.Address).HasMaxLength(500);
|
||||
e.Property(x => x.City).HasMaxLength(100);
|
||||
e.Property(x => x.Phone).HasMaxLength(20);
|
||||
e.Property(x => x.ReceiptPrinterIp).HasMaxLength(45);
|
||||
e.Property(x => x.KitchenPrinterIp).HasMaxLength(45);
|
||||
e.Property(x => x.PosDeviceIp).HasMaxLength(45);
|
||||
e.Property(x => x.ReceiptHeader).HasMaxLength(500);
|
||||
e.Property(x => x.ReceiptFooter).HasMaxLength(500);
|
||||
e.Property(x => x.WifiPassword).HasMaxLength(100);
|
||||
e.HasIndex(x => new { x.CafeId, x.IsActive });
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.Branches).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<TableSection>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Name).HasMaxLength(100).IsRequired();
|
||||
e.HasIndex(x => new { x.BranchId, x.Name });
|
||||
e.HasIndex(x => x.CafeId);
|
||||
e.HasOne(x => x.Branch).WithMany(b => b.Sections).HasForeignKey(x => x.BranchId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Table>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Number).HasMaxLength(50).IsRequired();
|
||||
e.Property(x => x.BranchId).IsRequired();
|
||||
e.Property(x => x.SortOrder).HasDefaultValue(0);
|
||||
e.HasIndex(x => x.QrCode).IsUnique();
|
||||
e.HasIndex(x => new { x.BranchId, x.SectionId, x.SortOrder });
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.Tables).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Branch).WithMany(b => b.Tables).HasForeignKey(x => x.BranchId).OnDelete(DeleteBehavior.Restrict);
|
||||
e.HasOne(x => x.Section).WithMany(s => s.Tables).HasForeignKey(x => x.SectionId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Employee>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasIndex(x => new { x.CafeId, x.Phone })
|
||||
.IsUnique()
|
||||
.HasFilter("\"DeletedAt\" IS NULL");
|
||||
e.HasIndex(x => x.BranchId);
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.Employees).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Branch).WithMany(b => b.Staff).HasForeignKey(x => x.BranchId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<MenuCategory>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Icon).HasMaxLength(32);
|
||||
e.Property(x => x.IconPresetId).HasMaxLength(48);
|
||||
e.Property(x => x.IconStyle).HasMaxLength(16);
|
||||
e.Property(x => x.ImageUrl).HasMaxLength(500);
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.MenuCategories).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Tax).WithMany(t => t.MenuCategories).HasForeignKey(x => x.TaxId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasOne(x => x.KitchenStation).WithMany(s => s.Categories).HasForeignKey(x => x.KitchenStationId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<MenuItem>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Model3dUrl).HasMaxLength(500);
|
||||
e.Property(x => x.Price).HasPrecision(18, 2);
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.MenuItems).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Category).WithMany(c => c.MenuItems).HasForeignKey(x => x.CategoryId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<BranchMenuItemOverride>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.PriceOverride).HasPrecision(18, 2);
|
||||
e.HasIndex(x => new { x.BranchId, x.MenuItemId }).IsUnique();
|
||||
e.HasIndex(x => x.CafeId);
|
||||
e.HasOne(x => x.Branch).WithMany().HasForeignKey(x => x.BranchId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.MenuItem).WithMany(m => m.BranchOverrides).HasForeignKey(x => x.MenuItemId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Order>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Subtotal).HasPrecision(18, 2);
|
||||
e.Property(x => x.TaxTotal).HasPrecision(18, 2);
|
||||
e.Property(x => x.Total).HasPrecision(18, 2);
|
||||
e.Property(x => x.DiscountAmount).HasPrecision(18, 2);
|
||||
e.Property(x => x.PlatformCommission).HasPrecision(18, 2);
|
||||
e.Property(x => x.ExternalOrderId).HasMaxLength(120);
|
||||
e.Property(x => x.DeliveryMetaJson).HasMaxLength(4000);
|
||||
e.Property(x => x.GuestTrackingToken).HasMaxLength(64);
|
||||
e.HasIndex(x => x.GuestTrackingToken);
|
||||
e.HasIndex(x => new { x.CafeId, x.DisplayNumber }).IsUnique();
|
||||
e.HasIndex(x => new { x.CafeId, x.DeliveryPlatform, x.ExternalOrderId });
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.Orders).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Branch).WithMany(b => b.Orders).HasForeignKey(x => x.BranchId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasOne(x => x.Table).WithMany(t => t.Orders).HasForeignKey(x => x.TableId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasOne(x => x.Reservation).WithMany().HasForeignKey(x => x.ReservationId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasOne(x => x.Customer).WithMany(c => c.Orders).HasForeignKey(x => x.CustomerId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasOne(x => x.Employee).WithMany(emp => emp.Orders).HasForeignKey(x => x.EmployeeId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasOne(x => x.Coupon).WithMany(c => c.Orders).HasForeignKey(x => x.CouponId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<OrderItem>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.UnitPrice).HasPrecision(18, 2);
|
||||
e.HasOne(x => x.Order).WithMany(o => o.Items).HasForeignKey(x => x.OrderId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.MenuItem).WithMany(m => m.OrderItems).HasForeignKey(x => x.MenuItemId).OnDelete(DeleteBehavior.Restrict);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Payment>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Amount).HasPrecision(18, 2);
|
||||
e.HasOne(x => x.Order).WithMany(o => o.Payments).HasForeignKey(x => x.OrderId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Customer>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasIndex(x => new { x.CafeId, x.Phone });
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.Customers).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Coupon>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasIndex(x => new { x.CafeId, x.Code }).IsUnique();
|
||||
e.Property(x => x.Value).HasPrecision(18, 2);
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.Coupons).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Tax>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Rate).HasPrecision(5, 2);
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.Taxes).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<EmployeeSalary>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasIndex(x => new { x.EmployeeId, x.MonthYear }).IsUnique();
|
||||
e.HasOne(x => x.Employee).WithMany(e => e.Salaries).HasForeignKey(x => x.EmployeeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Attendance>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasIndex(x => new { x.EmployeeId, x.Date }).IsUnique();
|
||||
e.HasOne(x => x.Employee).WithMany(e => e.Attendances).HasForeignKey(x => x.EmployeeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<EmployeeSchedule>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.ToTable("EmployeeSchedules");
|
||||
e.HasIndex(x => new { x.EmployeeId, x.DayOfWeek }).IsUnique();
|
||||
e.HasOne(x => x.Employee).WithMany(e => e.Schedules).HasForeignKey(x => x.EmployeeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Shift>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.ToTable("RegisterShifts");
|
||||
e.Property(x => x.OpeningCash).HasPrecision(18, 2);
|
||||
e.Property(x => x.ClosingCash).HasPrecision(18, 2);
|
||||
e.Property(x => x.ExpectedCash).HasPrecision(18, 2);
|
||||
e.Property(x => x.Discrepancy).HasPrecision(18, 2);
|
||||
e.HasIndex(x => new { x.BranchId, x.Status });
|
||||
e.HasOne(x => x.Cafe).WithMany().HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Branch).WithMany().HasForeignKey(x => x.BranchId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.OpenedBy).WithMany().HasForeignKey(x => x.OpenedByUserId).OnDelete(DeleteBehavior.Restrict);
|
||||
e.HasOne(x => x.ClosedBy).WithMany().HasForeignKey(x => x.ClosedByUserId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<CashTransaction>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Amount).HasPrecision(18, 2);
|
||||
e.HasIndex(x => x.ShiftId);
|
||||
e.HasIndex(x => new { x.CafeId, x.BranchId });
|
||||
e.HasOne(x => x.Shift).WithMany(s => s.Transactions).HasForeignKey(x => x.ShiftId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Branch).WithMany().HasForeignKey(x => x.BranchId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<LeaveRequest>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasOne(x => x.Employee).WithMany(e => e.LeaveRequests).HasForeignKey(x => x.EmployeeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<TableReservation>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasIndex(x => new { x.CafeId, x.Date, x.Time });
|
||||
e.HasOne(x => x.Cafe).WithMany().HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Table).WithMany().HasForeignKey(x => x.TableId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasOne(x => x.Customer).WithMany().HasForeignKey(x => x.CustomerId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<CafeReview>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasIndex(x => new { x.CafeId, x.CreatedAt });
|
||||
e.Property(x => x.AuthorName).HasMaxLength(200).IsRequired();
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.Reviews).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<CafeReviewPhoto>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Url).HasMaxLength(500).IsRequired();
|
||||
e.HasIndex(x => x.ReviewId);
|
||||
e.HasOne(x => x.Review).WithMany(r => r.Photos).HasForeignKey(x => x.ReviewId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<ConsumerAccount>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Phone).HasMaxLength(20).IsRequired();
|
||||
e.HasIndex(x => x.Phone).IsUnique();
|
||||
e.Property(x => x.Name).HasMaxLength(200);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<KitchenStation>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Name).HasMaxLength(100).IsRequired();
|
||||
e.Property(x => x.PrinterIp).HasMaxLength(45);
|
||||
e.HasIndex(x => new { x.CafeId, x.SortOrder });
|
||||
e.HasOne(x => x.Cafe).WithMany().HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Branch).WithMany().HasForeignKey(x => x.BranchId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<SubscriptionPayment>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasIndex(x => x.Authority);
|
||||
e.Property(x => x.AmountToman).HasPrecision(18, 2);
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.SubscriptionPayments).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Ingredient>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
||||
e.Property(x => x.Unit).HasMaxLength(50).IsRequired();
|
||||
e.Property(x => x.QuantityOnHand).HasPrecision(18, 3);
|
||||
e.Property(x => x.ReorderLevel).HasPrecision(18, 3);
|
||||
e.Property(x => x.UnitCost).HasPrecision(18, 2);
|
||||
e.Property(x => x.ParLevel).HasPrecision(18, 3);
|
||||
e.Property(x => x.LowStockWarningPercent).HasPrecision(5, 2);
|
||||
e.HasOne(x => x.Cafe).WithMany(c => c.Ingredients).HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<MenuItemIngredient>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.QuantityPerUnit).HasPrecision(18, 3);
|
||||
e.HasIndex(x => new { x.CafeId, x.MenuItemId, x.IngredientId }).IsUnique();
|
||||
e.HasOne(x => x.MenuItem).WithMany(m => m.RecipeIngredients).HasForeignKey(x => x.MenuItemId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Ingredient).WithMany(i => i.MenuItemRecipes).HasForeignKey(x => x.IngredientId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<StockMovement>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Delta).HasPrecision(18, 3);
|
||||
e.Property(x => x.TotalCostToman).HasPrecision(18, 2);
|
||||
e.Property(x => x.ExpenseId).HasMaxLength(64);
|
||||
e.Property(x => x.BranchId).HasMaxLength(64);
|
||||
e.Property(x => x.Kind).HasConversion<string>().HasMaxLength(30);
|
||||
e.Property(x => x.OrderId).HasMaxLength(64);
|
||||
e.HasIndex(x => new { x.CafeId, x.OrderId });
|
||||
e.HasOne(x => x.Cafe).WithMany().HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Ingredient).WithMany(i => i.Movements).HasForeignKey(x => x.IngredientId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<QueueTicket>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasIndex(x => new { x.CafeId, x.BranchId, x.ServiceDate, x.Number }).IsUnique();
|
||||
e.Property(x => x.CustomerLabel).HasMaxLength(200);
|
||||
e.HasOne(x => x.Cafe).WithMany().HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Branch).WithMany().HasForeignKey(x => x.BranchId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasOne(x => x.Order).WithMany().HasForeignKey(x => x.OrderId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Expense>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Amount).HasPrecision(18, 2);
|
||||
e.Property(x => x.Note).HasMaxLength(500);
|
||||
e.Property(x => x.ReceiptImageUrl).HasMaxLength(500);
|
||||
e.HasIndex(x => new { x.CafeId, x.BranchId, x.CreatedAt });
|
||||
e.HasOne(x => x.Branch).WithMany().HasForeignKey(x => x.BranchId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Shift).WithMany().HasForeignKey(x => x.ShiftId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<DailyReport>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.HasIndex(x => new { x.CafeId, x.BranchId, x.Date }).IsUnique();
|
||||
e.Property(x => x.TotalRevenue).HasPrecision(18, 2);
|
||||
e.Property(x => x.CashRevenue).HasPrecision(18, 2);
|
||||
e.Property(x => x.CardRevenue).HasPrecision(18, 2);
|
||||
e.Property(x => x.CreditRevenue).HasPrecision(18, 2);
|
||||
e.Property(x => x.AvgOrderValue).HasPrecision(18, 2);
|
||||
e.Property(x => x.VoidAmount).HasPrecision(18, 2);
|
||||
e.Property(x => x.TotalExpenses).HasPrecision(18, 2);
|
||||
e.Property(x => x.NetIncome).HasPrecision(18, 2);
|
||||
var topProductsConverter = new ValueConverter<List<TopProductEntry>, string>(
|
||||
v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default),
|
||||
v => JsonSerializer.Deserialize<List<TopProductEntry>>(v, JsonSerializerOptions.Default)
|
||||
?? new List<TopProductEntry>());
|
||||
var topProductsComparer = new ValueComparer<List<TopProductEntry>>(
|
||||
(a, b) => JsonSerializer.Serialize(a, JsonSerializerOptions.Default)
|
||||
== JsonSerializer.Serialize(b, JsonSerializerOptions.Default),
|
||||
v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default).GetHashCode(),
|
||||
v => JsonSerializer.Deserialize<List<TopProductEntry>>(
|
||||
JsonSerializer.Serialize(v, JsonSerializerOptions.Default),
|
||||
JsonSerializerOptions.Default)!);
|
||||
|
||||
e.Property(x => x.TopProducts)
|
||||
.HasConversion(topProductsConverter, topProductsComparer)
|
||||
.HasColumnType("jsonb");
|
||||
e.HasOne(x => x.Branch).WithMany().HasForeignKey(x => x.BranchId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<WebhookLog>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.RawBody).IsRequired();
|
||||
e.Property(x => x.SignatureHeader).HasMaxLength(256);
|
||||
e.Property(x => x.ErrorMessage).HasMaxLength(2000);
|
||||
e.Property(x => x.ExternalOrderId).HasMaxLength(120);
|
||||
e.Property(x => x.MeeziOrderId).HasMaxLength(50);
|
||||
e.HasIndex(x => new { x.Platform, x.CreatedAt });
|
||||
e.HasIndex(x => x.CafeId);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<DeliveryCommissionRate>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.RatePercent).HasPrecision(5, 2);
|
||||
e.HasIndex(x => new { x.CafeId, x.Platform }).IsUnique();
|
||||
e.HasOne(x => x.Cafe).WithMany().HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<SystemAdmin>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
||||
e.Property(x => x.Phone).HasMaxLength(20).IsRequired();
|
||||
e.HasIndex(x => x.Phone).IsUnique();
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PlatformPlanDefinition>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.DisplayNameFa).HasMaxLength(200).IsRequired();
|
||||
e.Property(x => x.DisplayNameEn).HasMaxLength(200);
|
||||
e.Property(x => x.MonthlyPriceToman).HasPrecision(18, 0);
|
||||
e.Property(x => x.LimitsJson).HasMaxLength(4000).IsRequired();
|
||||
e.Property(x => x.FeaturesJson).HasMaxLength(4000);
|
||||
e.HasIndex(x => x.Tier).IsUnique();
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PlatformSetting>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Key).HasMaxLength(120).IsRequired();
|
||||
e.Property(x => x.Value).HasMaxLength(8000).IsRequired();
|
||||
e.Property(x => x.Category).HasMaxLength(60).IsRequired();
|
||||
e.Property(x => x.DescriptionFa).HasMaxLength(500);
|
||||
e.HasIndex(x => x.Key).IsUnique();
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PlatformFeature>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Key).HasMaxLength(80).IsRequired();
|
||||
e.Property(x => x.DisplayNameFa).HasMaxLength(200).IsRequired();
|
||||
e.Property(x => x.DisplayNameEn).HasMaxLength(200);
|
||||
e.Property(x => x.ModuleGroup).HasMaxLength(60).IsRequired();
|
||||
e.HasIndex(x => x.Key).IsUnique();
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<CafeFeatureOverride>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.FeatureKey).HasMaxLength(80).IsRequired();
|
||||
e.HasIndex(x => new { x.CafeId, x.FeatureKey }).IsUnique();
|
||||
e.HasOne<Cafe>().WithMany().HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<SupportTicket>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Subject).HasMaxLength(300).IsRequired();
|
||||
e.HasIndex(x => new { x.CafeId, x.Status, x.UpdatedAt });
|
||||
e.HasOne(x => x.Cafe).WithMany().HasForeignKey(x => x.CafeId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.CreatedByEmployee).WithMany().HasForeignKey(x => x.CreatedByEmployeeId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<SupportTicketMessage>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Body).HasMaxLength(8000).IsRequired();
|
||||
e.HasIndex(x => new { x.TicketId, x.CreatedAt });
|
||||
e.HasOne(x => x.Ticket).WithMany(t => t.Messages).HasForeignKey(x => x.TicketId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<CafeNotification>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Type).HasMaxLength(60).IsRequired();
|
||||
e.Property(x => x.Title).HasMaxLength(300).IsRequired();
|
||||
e.Property(x => x.Body).HasMaxLength(1000);
|
||||
e.Property(x => x.ReferenceId).HasMaxLength(64);
|
||||
e.Property(x => x.TableNumber).HasMaxLength(40);
|
||||
e.HasIndex(x => new { x.CafeId, x.IsRead, x.CreatedAt });
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
// ── Website CMS ──────────────────────────────────────────────────────
|
||||
modelBuilder.Entity<WebsiteBlogPost>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Slug).HasMaxLength(200).IsRequired();
|
||||
e.Property(x => x.TitleFa).HasMaxLength(400).IsRequired();
|
||||
e.Property(x => x.TitleEn).HasMaxLength(400);
|
||||
e.Property(x => x.ExcerptFa).HasMaxLength(1000);
|
||||
e.Property(x => x.ExcerptEn).HasMaxLength(1000);
|
||||
e.Property(x => x.ContentFa).HasColumnType("text");
|
||||
e.Property(x => x.ContentEn).HasColumnType("text");
|
||||
e.Property(x => x.Author).HasMaxLength(200);
|
||||
e.Property(x => x.CategoryFa).HasMaxLength(100);
|
||||
e.Property(x => x.CategoryEn).HasMaxLength(100);
|
||||
e.Property(x => x.TagsJson).HasMaxLength(2000).HasDefaultValue("[]");
|
||||
e.Property(x => x.CoverImage).HasMaxLength(500);
|
||||
e.HasIndex(x => x.Slug).IsUnique();
|
||||
e.HasIndex(x => new { x.IsPublished, x.PublishedAt });
|
||||
e.HasMany(x => x.Comments).WithOne(c => c.Post).HasForeignKey(c => c.PostSlug)
|
||||
.HasPrincipalKey(p => p.Slug).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<WebsiteComment>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.PostSlug).HasMaxLength(200).IsRequired();
|
||||
e.Property(x => x.AuthorName).HasMaxLength(100).IsRequired();
|
||||
e.Property(x => x.AuthorEmail).HasMaxLength(200);
|
||||
e.Property(x => x.Content).HasMaxLength(3000).IsRequired();
|
||||
e.Property(x => x.IpAddress).HasMaxLength(50);
|
||||
e.HasIndex(x => new { x.PostSlug, x.IsApproved, x.CreatedAt });
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<DemoRequest>(e =>
|
||||
{
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.ContactName).HasMaxLength(200).IsRequired();
|
||||
e.Property(x => x.BusinessName).HasMaxLength(300).IsRequired();
|
||||
e.Property(x => x.Phone).HasMaxLength(20).IsRequired();
|
||||
e.Property(x => x.Email).HasMaxLength(200);
|
||||
e.Property(x => x.BranchCount).HasMaxLength(20);
|
||||
e.Property(x => x.Notes).HasMaxLength(2000);
|
||||
e.Property(x => x.Source).HasMaxLength(50).HasDefaultValue("website");
|
||||
e.Property(x => x.AdminNotes).HasMaxLength(2000);
|
||||
e.HasIndex(x => new { x.Status, x.CreatedAt });
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Meezi.Core.Interfaces;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
public class BranchContext : IBranchContext
|
||||
{
|
||||
public string? CafeId { get; set; }
|
||||
public string? BranchId { get; set; }
|
||||
public bool HasBranch => !string.IsNullOrEmpty(BranchId);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
/// <summary>Idempotent SQL fixes for databases where EF history and schema diverged.</summary>
|
||||
public static class DatabaseSchemaPatches
|
||||
{
|
||||
public static async Task ApplyAsync(AppDbContext db, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await db.Database.ExecuteSqlRawAsync(
|
||||
"""
|
||||
ALTER TABLE "OrderItems" ADD COLUMN IF NOT EXISTS "IsVoided" boolean NOT NULL DEFAULT false;
|
||||
ALTER TABLE "OrderItems" ADD COLUMN IF NOT EXISTS "VoidedAt" timestamp with time zone;
|
||||
ALTER TABLE "OrderItems" ADD COLUMN IF NOT EXISTS "VoidedByUserId" text;
|
||||
""",
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Meezi.Core.Entities;
|
||||
using Meezi.Core.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
public static class DemoCouponSeeder
|
||||
{
|
||||
public static async Task EnsureCouponsAsync(AppDbContext db, string cafeId, ILogger logger)
|
||||
{
|
||||
if (await db.Coupons.AnyAsync(c => c.CafeId == cafeId && c.Code == "WELCOME10"))
|
||||
return;
|
||||
|
||||
db.Coupons.AddRange(
|
||||
new Coupon
|
||||
{
|
||||
Id = "coupon_demo_welcome10",
|
||||
CafeId = cafeId,
|
||||
Code = "WELCOME10",
|
||||
Type = CouponType.Percentage,
|
||||
Value = 10,
|
||||
MaxDiscount = 50_000,
|
||||
MinOrderAmount = 100_000,
|
||||
UsageLimit = 100,
|
||||
IsActive = true
|
||||
},
|
||||
new Coupon
|
||||
{
|
||||
Id = "coupon_demo_save20k",
|
||||
CafeId = cafeId,
|
||||
Code = "SAVE20",
|
||||
Type = CouponType.FixedAmount,
|
||||
Value = 20_000,
|
||||
MinOrderAmount = 150_000,
|
||||
UsageLimit = 50,
|
||||
IsActive = true
|
||||
});
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Demo coupons seeded: WELCOME10, SAVE20 for cafe {CafeId}", cafeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
/// <summary>Demo staff for development OTP login (see data/demo-credentials.json).</summary>
|
||||
public static class DemoEmployeesCatalog
|
||||
{
|
||||
public const string DefaultBranchId = "branch_demo_main";
|
||||
|
||||
public sealed record EmployeeSeed(
|
||||
string Id,
|
||||
string Name,
|
||||
string Phone,
|
||||
EmployeeRole Role,
|
||||
string BranchId = DefaultBranchId,
|
||||
decimal BaseSalary = 0);
|
||||
|
||||
public static IReadOnlyList<EmployeeSeed> Employees { get; } =
|
||||
[
|
||||
new("emp_demo_owner", "مدیر دمو", "09121234567", EmployeeRole.Owner),
|
||||
new("emp_demo_manager", "مدیر شعبه", "09121111111", EmployeeRole.Manager),
|
||||
new("emp_demo_cashier", "صندوقدار", "09122222222", EmployeeRole.Cashier),
|
||||
new("emp_demo_waiter", "گارسون", "09123333333", EmployeeRole.Waiter),
|
||||
new("emp_demo_waiter2", "گارسون ۲", "09124444444", EmployeeRole.Waiter),
|
||||
new("emp_demo_chef", "آشپز", "09125555555", EmployeeRole.Chef),
|
||||
new("emp_demo_delivery", "پیک", "09126666666", EmployeeRole.Delivery),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Meezi.Core.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
public static class DemoEmployeesSeeder
|
||||
{
|
||||
public static async Task EnsureEmployeesAsync(AppDbContext db, string cafeId, ILogger logger)
|
||||
{
|
||||
var existingPhones = await db.Employees
|
||||
.Where(e => e.CafeId == cafeId && e.DeletedAt == null)
|
||||
.Select(e => e.Phone)
|
||||
.ToListAsync();
|
||||
|
||||
var added = 0;
|
||||
foreach (var seed in DemoEmployeesCatalog.Employees)
|
||||
{
|
||||
if (existingPhones.Contains(seed.Phone))
|
||||
continue;
|
||||
|
||||
if (await db.Employees.AnyAsync(e => e.Id == seed.Id, cancellationToken: default))
|
||||
continue;
|
||||
|
||||
db.Employees.Add(new Employee
|
||||
{
|
||||
Id = seed.Id,
|
||||
CafeId = cafeId,
|
||||
BranchId = seed.BranchId,
|
||||
Name = seed.Name,
|
||||
Phone = seed.Phone,
|
||||
Role = seed.Role,
|
||||
BaseSalary = seed.BaseSalary
|
||||
});
|
||||
added++;
|
||||
}
|
||||
|
||||
if (added > 0)
|
||||
{
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Demo employees seed: {Count} added for cafe {CafeId}", added, cafeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Café demo menu aligned with Kaggle Food-101 class names (see Food101ImageFallbacks + data/menu-image-manifest.json).
|
||||
/// </summary>
|
||||
public static class DemoMenuCatalog
|
||||
{
|
||||
public sealed record CategorySeed(
|
||||
string Id,
|
||||
string Name,
|
||||
string NameEn,
|
||||
string? NameAr,
|
||||
int SortOrder,
|
||||
string? Icon = null,
|
||||
string? IconPresetId = null,
|
||||
string? IconStyle = "flat");
|
||||
|
||||
public sealed record ItemSeed(
|
||||
string Id,
|
||||
string CategoryId,
|
||||
string Name,
|
||||
string NameEn,
|
||||
string? NameAr,
|
||||
string? Description,
|
||||
decimal PriceToman,
|
||||
decimal DiscountPercent,
|
||||
string Food101Class);
|
||||
|
||||
private static string Img(string itemId, string food101Class)
|
||||
{
|
||||
var kind = DemoMenuCategoryKinds.KindFor(itemId, food101Class);
|
||||
var local = MenuImageManifest.GetLocalImageOverride(itemId);
|
||||
if (!string.IsNullOrWhiteSpace(local))
|
||||
return local;
|
||||
return Food101ImageFallbacks.Resolve(food101Class, kind);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<CategorySeed> Categories { get; } =
|
||||
[
|
||||
new("cat_demo_drinks", "نوشیدنی گرم", "Hot drinks", "مشروبات ساخنة", 1, IconPresetId: "drinks-hot"),
|
||||
new("cat_demo_cold", "نوشیدنی سرد", "Cold drinks", "مشروبات باردة", 2, IconPresetId: "drinks-cold"),
|
||||
new("cat_demo_breakfast", "صبحانه", "Breakfast", "فطور", 3, IconPresetId: "breakfast"),
|
||||
new("cat_demo_food", "غذا و پیشغذا", "Food & snacks", "طعام", 4, IconPresetId: "food-mains"),
|
||||
new("cat_demo_pasta", "پاستا و پیتزا", "Pasta & pizza", "معكرونة وبيتزا", 5, IconPresetId: "pasta-pizza"),
|
||||
new("cat_demo_dessert", "دسر", "Desserts", "حلويات", 6, IconPresetId: "dessert"),
|
||||
];
|
||||
|
||||
public static IReadOnlyList<ItemSeed> Items { get; } =
|
||||
[
|
||||
// Hot drinks
|
||||
new("item_demo_espresso", "cat_demo_drinks", "اسپرسو", "Espresso", "إسبريسو", "دوبل یا سینگل", 65_000, 0, "espresso"),
|
||||
new("item_demo_americano", "cat_demo_drinks", "آمریکانو", "Americano", "أمريكانو", null, 75_000, 0, "cappuccino"),
|
||||
new("item_demo_latte", "cat_demo_drinks", "لاته", "Latte", "لاتيه", "شیر بخار گرفته", 120_000, 0, "latte"),
|
||||
new("item_demo_cappuccino", "cat_demo_drinks", "کاپوچینو", "Cappuccino", "كابتشينو", null, 110_000, 10, "cappuccino"),
|
||||
new("item_demo_mocha", "cat_demo_drinks", "موکا", "Mocha", "موكا", "شکلات و قهوه", 135_000, 0, "mocha"),
|
||||
new("item_demo_tea", "cat_demo_drinks", "چای ماسالا", "Masala tea", "شاي ماسالا", null, 85_000, 0, "miso_soup"),
|
||||
|
||||
// Cold drinks
|
||||
new("item_demo_iced_latte", "cat_demo_cold", "آیس لاته", "Iced latte", "آيس لاتيه", null, 130_000, 0, "iced_coffee"),
|
||||
new("item_demo_cold_brew", "cat_demo_cold", "کولد برو", "Cold brew", "كولد برو", null, 140_000, 0, "iced_coffee"),
|
||||
new("item_demo_lemonade", "cat_demo_cold", "لیموناد", "Lemonade", "ليمونادة", "تازه", 95_000, 0, "lemonade"),
|
||||
new("item_demo_smoothie", "cat_demo_cold", "اسموتی توت", "Berry smoothie", "سموذي", null, 150_000, 15, "smoothie"),
|
||||
|
||||
// Breakfast
|
||||
new("item_demo_croissant", "cat_demo_breakfast", "کروسان", "Croissant", "كرواسان", "کرهای", 75_000, 0, "croque_madame"),
|
||||
new("item_demo_omelette", "cat_demo_breakfast", "املت", "Omelette", "أومليت", "نان سنگک", 145_000, 0, "omelette"),
|
||||
new("item_demo_avocado", "cat_demo_breakfast", "توست آووکادو", "Avocado toast", "توست أفوكادو", null, 185_000, 0, "avocado_toast"),
|
||||
new("item_demo_pancakes", "cat_demo_breakfast", "پنکیک", "Pancakes", "فطائر", "عسل و کره", 165_000, 0, "pancakes"),
|
||||
new("item_demo_waffles", "cat_demo_breakfast", "وافل", "Waffles", "وافل", null, 175_000, 0, "waffles"),
|
||||
new("item_demo_french_toast", "cat_demo_breakfast", "فرنچ تست", "French toast", "توست فرنسي", null, 155_000, 0, "french_toast"),
|
||||
new("item_demo_eggs_benedict", "cat_demo_breakfast", "اگ بندیکت", "Eggs Benedict", "بيض بنديكت", null, 195_000, 0, "eggs_benedict"),
|
||||
|
||||
// Food & snacks
|
||||
new("item_demo_sandwich", "cat_demo_food", "ساندویچ مرغ", "Chicken sandwich", "ساندويتش دجاج", null, 195_000, 0, "club_sandwich"),
|
||||
new("item_demo_salad", "cat_demo_food", "سالاد سزار", "Caesar salad", "سلطة سيزر", null, 175_000, 0, "caesar_salad"),
|
||||
new("item_demo_greek_salad", "cat_demo_food", "سالاد یونانی", "Greek salad", "سلطة يونانية", null, 168_000, 0, "greek_salad"),
|
||||
new("item_demo_burger", "cat_demo_food", "همبرگر", "Burger", "برجر", "۱۵۰ گرم گوشت", 245_000, 0, "hamburger"),
|
||||
new("item_demo_steak", "cat_demo_food", "استیک", "Steak", "ستيك", "medium", 385_000, 0, "steak"),
|
||||
new("item_demo_salmon", "cat_demo_food", "سالمون گریل", "Grilled salmon", "سلمون مشوي", null, 320_000, 0, "grilled_salmon"),
|
||||
new("item_demo_tacos", "cat_demo_food", "تاکو", "Tacos", "تاكو", "سه عدد", 210_000, 0, "tacos"),
|
||||
new("item_demo_shawarma", "cat_demo_food", "شاورما", "Shawarma", "شاورما", null, 185_000, 0, "shawarma"),
|
||||
new("item_demo_falafel", "cat_demo_food", "فلافل", "Falafel", "فلافل", "۶ عدد", 125_000, 0, "falafel"),
|
||||
new("item_demo_hummus", "cat_demo_food", "حمص", "Hummus", "حمص", "نان پیتا", 95_000, 0, "hummus"),
|
||||
new("item_demo_fries", "cat_demo_food", "سیبزمینی سرخکرده", "French fries", "بطاطس مقلية", null, 85_000, 0, "french_fries"),
|
||||
new("item_demo_spring_rolls", "cat_demo_food", "اسپرینگ رول", "Spring rolls", "سبرينغ رول", null, 115_000, 0, "spring_rolls"),
|
||||
new("item_demo_ramen", "cat_demo_food", "رامن", "Ramen", "رامن", null, 235_000, 0, "ramen"),
|
||||
new("item_demo_pho", "cat_demo_food", "فو", "Pho", "فو", null, 225_000, 0, "pho"),
|
||||
new("item_demo_sushi", "cat_demo_food", "سوشی", "Sushi", "سوشي", "۸ تکه", 290_000, 0, "sushi"),
|
||||
|
||||
// Pasta & pizza
|
||||
new("item_demo_pasta", "cat_demo_pasta", "پاستا آلفردو", "Alfredo pasta", "باستا", null, 220_000, 0, "pasta_carbonara"),
|
||||
new("item_demo_carbonara", "cat_demo_pasta", "کاربونارا", "Carbonara", "كاربونارا", null, 228_000, 0, "spaghetti_carbonara"),
|
||||
new("item_demo_bolognese", "cat_demo_pasta", "بولونز", "Bolognese", "بولونيز", null, 215_000, 0, "spaghetti_bolognese"),
|
||||
new("item_demo_lasagna", "cat_demo_pasta", "لازانیا", "Lasagna", "لازانيا", null, 240_000, 0, "lasagna"),
|
||||
new("item_demo_gnocchi", "cat_demo_pasta", "نیوکی", "Gnocchi", "جنوكي", null, 232_000, 0, "gnocchi"),
|
||||
new("item_demo_pizza", "cat_demo_pasta", "پیتزا مارگاریتا", "Margherita pizza", "بيتزا", null, 265_000, 10, "pizza"),
|
||||
new("item_demo_risotto", "cat_demo_pasta", "ریزوتو قارچ", "Mushroom risotto", "ريزوتو", null, 238_000, 0, "mushroom_risotto"),
|
||||
|
||||
// Desserts
|
||||
new("item_demo_cake", "cat_demo_dessert", "کیک شکلاتی", "Chocolate cake", "كيك شوكولاتة", "برشی", 95_000, 15, "chocolate_cake"),
|
||||
new("item_demo_cheesecake", "cat_demo_dessert", "چیزکیک", "Cheesecake", "تشيز كيك", null, 115_000, 0, "cheesecake"),
|
||||
new("item_demo_brownie", "cat_demo_dessert", "براونی", "Brownie", "براوني", "بستنی وانیلی", 105_000, 0, "brownie"),
|
||||
new("item_demo_icecream", "cat_demo_dessert", "بستنی", "Ice cream", "آيس كريم", "دو اسکوپ", 88_000, 0, "ice_cream"),
|
||||
new("item_demo_tiramisu", "cat_demo_dessert", "تیرامیسو", "Tiramisu", "تيراميسو", null, 125_000, 0, "tiramisu"),
|
||||
new("item_demo_donuts", "cat_demo_dessert", "دونات", "Donuts", "دونات", null, 78_000, 0, "donuts"),
|
||||
new("item_demo_churros", "cat_demo_dessert", "چوروس", "Churros", "تشورو", "شکلات", 92_000, 0, "churros"),
|
||||
new("item_demo_baklava", "cat_demo_dessert", "باقلوا", "Baklava", "بقلاوة", null, 98_000, 0, "baklava"),
|
||||
new("item_demo_creme_brulee", "cat_demo_dessert", "کرم بروله", "Crème brûlée", "كريم بروليه", null, 118_000, 0, "creme_brulee"),
|
||||
];
|
||||
|
||||
/// <summary>Resolved image URL for catalog seed (manifest → Food-101 fallback → category default).</summary>
|
||||
public static string ResolveItemImageUrl(ItemSeed item) =>
|
||||
Img(item.Id, item.Food101Class);
|
||||
}
|
||||
|
||||
/// <summary>Maps item/category to drink vs food default images.</summary>
|
||||
file static class DemoMenuCategoryKinds
|
||||
{
|
||||
private static readonly HashSet<string> DrinkCategoryIds = new(StringComparer.Ordinal)
|
||||
{
|
||||
"cat_demo_drinks",
|
||||
"cat_demo_cold"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> DrinkFood101Classes = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"espresso", "latte", "cappuccino", "mocha", "iced_coffee", "lemonade", "smoothie", "miso_soup"
|
||||
};
|
||||
|
||||
public static MenuItemVisualKind KindFor(string itemId, string food101Class)
|
||||
{
|
||||
if (itemId.Contains("demo_iced", StringComparison.Ordinal)
|
||||
|| itemId.Contains("demo_cold", StringComparison.Ordinal)
|
||||
|| itemId.Contains("demo_lemonade", StringComparison.Ordinal)
|
||||
|| itemId.Contains("demo_smoothie", StringComparison.Ordinal)
|
||||
|| itemId.Contains("demo_espresso", StringComparison.Ordinal)
|
||||
|| itemId.Contains("demo_latte", StringComparison.Ordinal)
|
||||
|| itemId.Contains("demo_cappuccino", StringComparison.Ordinal)
|
||||
|| itemId.Contains("demo_mocha", StringComparison.Ordinal)
|
||||
|| itemId.Contains("demo_americano", StringComparison.Ordinal)
|
||||
|| itemId.Contains("demo_tea", StringComparison.Ordinal))
|
||||
return MenuItemVisualKind.Drink;
|
||||
|
||||
if (DrinkFood101Classes.Contains(food101Class))
|
||||
return MenuItemVisualKind.Drink;
|
||||
|
||||
return MenuItemVisualKind.Food;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using Meezi.Core.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
public static class DemoMenuSeeder
|
||||
{
|
||||
public static async Task EnsureMenuAsync(AppDbContext db, string cafeId, string taxId, ILogger logger)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (existingCategoryIds.TryGetValue(cat.Id, 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 = cat.Id,
|
||||
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)
|
||||
{
|
||||
if (existingItemIds.Contains(item.Id))
|
||||
continue;
|
||||
|
||||
db.MenuItems.Add(new MenuItem
|
||||
{
|
||||
Id = item.Id,
|
||||
CafeId = cafeId,
|
||||
CategoryId = item.CategoryId,
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>Upserts ImageUrl from catalog/manifest/Food-101 fallbacks.</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Upserts NameEn/NameAr from catalog for demo menu rows.</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
using Meezi.Core.Branding;
|
||||
using Meezi.Core.Discover;
|
||||
using Meezi.Core.Entities;
|
||||
using Meezi.Core.Enums;
|
||||
using Meezi.Infrastructure.Branding;
|
||||
using Meezi.Infrastructure.Discover;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
public static class DevelopmentDataSeeder
|
||||
{
|
||||
public static async Task SeedAsync(IServiceProvider services)
|
||||
{
|
||||
var env = services.GetRequiredService<IHostEnvironment>();
|
||||
if (!env.IsDevelopment())
|
||||
return;
|
||||
|
||||
var logger = services.GetRequiredService<ILoggerFactory>().CreateLogger("DevelopmentDataSeeder");
|
||||
await using var scope = services.CreateAsyncScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
var cafe = await db.Cafes.FirstOrDefaultAsync(c => c.Slug == "demo-cafe");
|
||||
if (cafe is null)
|
||||
{
|
||||
cafe = new Cafe
|
||||
{
|
||||
Id = "cafe_demo_001",
|
||||
Name = "کافه دمو",
|
||||
NameEn = "Demo Cafe",
|
||||
Slug = "demo-cafe",
|
||||
City = "تهران",
|
||||
Address = "تهران، خیابان ولیعصر",
|
||||
Description = "کافه دمو میزی — مناسب کار، میهمانی و قهوه تخصصی.",
|
||||
PlanTier = PlanTier.Pro,
|
||||
PreferredLanguage = "fa",
|
||||
IsVerified = true,
|
||||
SnappfoodVendorId = "demo_vendor"
|
||||
};
|
||||
|
||||
var owner = new Employee
|
||||
{
|
||||
Id = "emp_demo_owner",
|
||||
CafeId = cafe.Id,
|
||||
BranchId = "branch_demo_main",
|
||||
Name = "مدیر دمو",
|
||||
Phone = "09121234567",
|
||||
Role = EmployeeRole.Owner,
|
||||
BaseSalary = 0
|
||||
};
|
||||
|
||||
db.Cafes.Add(cafe);
|
||||
db.Employees.Add(owner);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
logger.LogInformation("Development seed: cafe slug={Slug}, owner phone={Phone}", cafe.Slug, owner.Phone);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(cafe.SnappfoodVendorId))
|
||||
{
|
||||
cafe.SnappfoodVendorId = "demo_vendor";
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(cafe.ThemeJson))
|
||||
{
|
||||
cafe.ThemeJson = CafeThemeSerializer.Serialize(new CafeTheme
|
||||
{
|
||||
PaletteId = CafeThemeDefaults.PaletteMeeziGreen,
|
||||
PanelStyle = CafeThemeDefaults.PanelModern,
|
||||
MenuStyle = CafeThemeDefaults.MenuCards,
|
||||
Density = CafeThemeDefaults.DensityComfortable,
|
||||
Radius = CafeThemeDefaults.RadiusMd
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(cafe.DiscoverProfileJson))
|
||||
{
|
||||
cafe.DiscoverProfileJson = CafeDiscoverProfileSerializer.Serialize(new CafeDiscoverProfile
|
||||
{
|
||||
Themes = ["modern", "plants_heavy"],
|
||||
Size = "cozy",
|
||||
Vibes = ["quiet", "cozy"],
|
||||
Occasions = ["date", "friends", "study_work"],
|
||||
SpaceFeatures = ["indoor", "wifi", "plants"],
|
||||
NoiseLevel = "quiet",
|
||||
PriceTier = "mid"
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await SeedDemoReviewsAsync(db, cafe.Id, logger);
|
||||
await SeedDemoBranchAsync(db, cafe.Id, logger);
|
||||
await SeedDemoOpenShiftsAsync(db, cafe.Id, logger);
|
||||
|
||||
var ownerEmp = await db.Employees.FirstOrDefaultAsync(e => e.Id == "emp_demo_owner");
|
||||
if (ownerEmp is not null && ownerEmp.BranchId is null)
|
||||
{
|
||||
ownerEmp.BranchId = "branch_demo_main";
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await DemoEmployeesSeeder.EnsureEmployeesAsync(db, cafe.Id, logger);
|
||||
|
||||
const string taxId = "tax_demo_vat";
|
||||
await DemoMenuSeeder.EnsureMenuAsync(db, cafe.Id, taxId, logger);
|
||||
await SeedDemoInventoryAsync(db, cafe.Id, logger);
|
||||
await DemoCouponSeeder.EnsureCouponsAsync(db, cafe.Id, logger);
|
||||
await SeedDemoTablesAsync(db, cafe.Id, logger);
|
||||
|
||||
if (!await db.EmployeeSchedules.AnyAsync(s => s.EmployeeId == "emp_demo_owner"))
|
||||
{
|
||||
for (var day = 0; day <= 6; day++)
|
||||
{
|
||||
db.EmployeeSchedules.Add(new EmployeeSchedule
|
||||
{
|
||||
EmployeeId = "emp_demo_owner",
|
||||
DayOfWeek = day,
|
||||
ShiftType = day is 5 ? ShiftType.DayOff : ShiftType.Morning
|
||||
});
|
||||
}
|
||||
|
||||
var monthYear = DateTime.UtcNow.ToString("yyyy-MM");
|
||||
db.EmployeeSalaries.Add(new EmployeeSalary
|
||||
{
|
||||
EmployeeId = "emp_demo_owner",
|
||||
MonthYear = monthYear,
|
||||
BaseSalary = 25_000_000,
|
||||
OvertimePay = 0,
|
||||
Deductions = 0,
|
||||
NetSalary = 25_000_000,
|
||||
IsPaid = false
|
||||
});
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Development HR seed for cafe {CafeId}", cafe.Id);
|
||||
}
|
||||
|
||||
await SeedDemoOrdersAsync(db, cafe.Id, logger);
|
||||
await DiscoverShowcaseSeeder.SeedAsync(db, logger);
|
||||
}
|
||||
|
||||
private static async Task SeedDemoBranchAsync(AppDbContext db, string cafeId, ILogger logger)
|
||||
{
|
||||
const string branchId = "branch_demo_main";
|
||||
if (!await db.Branches.AnyAsync(b => b.Id == branchId))
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
db.Branches.Add(new Branch
|
||||
{
|
||||
Id = branchId,
|
||||
CafeId = cafeId,
|
||||
Name = "شعبه اصلی",
|
||||
City = "تهران",
|
||||
Address = "تهران، خیابان ولیعصر",
|
||||
Phone = "02112345678",
|
||||
IsActive = true,
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Development branch seed: {BranchId}", branchId);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SeedDemoOpenShiftsAsync(AppDbContext db, string cafeId, ILogger logger)
|
||||
{
|
||||
const string ownerId = "emp_demo_owner";
|
||||
var branchIds = await db.Branches
|
||||
.Where(b => b.CafeId == cafeId && b.IsActive)
|
||||
.Select(b => b.Id)
|
||||
.ToListAsync();
|
||||
|
||||
var added = false;
|
||||
foreach (var branchId in branchIds)
|
||||
{
|
||||
var hasOpen = await db.RegisterShifts.AnyAsync(
|
||||
s => s.CafeId == cafeId && s.BranchId == branchId && s.Status == ShiftStatus.Open);
|
||||
if (hasOpen)
|
||||
continue;
|
||||
|
||||
db.RegisterShifts.Add(new Shift
|
||||
{
|
||||
Id = $"shift_open_{branchId}",
|
||||
CafeId = cafeId,
|
||||
BranchId = branchId,
|
||||
OpenedByUserId = ownerId,
|
||||
OpenedAt = DateTime.UtcNow,
|
||||
OpeningCash = 0,
|
||||
ExpectedCash = 0,
|
||||
Status = ShiftStatus.Open
|
||||
});
|
||||
added = true;
|
||||
}
|
||||
|
||||
if (added)
|
||||
{
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Development open-shift seed for cafe {CafeId}", cafeId);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SeedDemoInventoryAsync(AppDbContext db, string cafeId, ILogger logger)
|
||||
{
|
||||
if (await db.Ingredients.AnyAsync(i => i.CafeId == cafeId))
|
||||
return;
|
||||
|
||||
db.Ingredients.AddRange(
|
||||
new Ingredient
|
||||
{
|
||||
Id = "ing_demo_milk",
|
||||
CafeId = cafeId,
|
||||
Name = "شیر",
|
||||
Unit = "میلیلیتر",
|
||||
QuantityOnHand = 12000,
|
||||
ReorderLevel = 2000,
|
||||
ParLevel = 12000,
|
||||
UnitCost = 80,
|
||||
LowStockWarningPercent = 20
|
||||
},
|
||||
new Ingredient
|
||||
{
|
||||
Id = "ing_demo_coffee",
|
||||
CafeId = cafeId,
|
||||
Name = "پودر قهوه",
|
||||
Unit = "گرم",
|
||||
QuantityOnHand = 500,
|
||||
ReorderLevel = 100,
|
||||
ParLevel = 500,
|
||||
UnitCost = 12,
|
||||
LowStockWarningPercent = 20
|
||||
},
|
||||
new Ingredient
|
||||
{
|
||||
Id = "ing_demo_cups",
|
||||
CafeId = cafeId,
|
||||
Name = "لیوان یکبارمصرف",
|
||||
Unit = "عدد",
|
||||
QuantityOnHand = 80,
|
||||
ReorderLevel = 20,
|
||||
ParLevel = 100,
|
||||
UnitCost = 500,
|
||||
LowStockWarningPercent = 20
|
||||
});
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var espresso = await db.MenuItems
|
||||
.FirstOrDefaultAsync(m => m.CafeId == cafeId && m.Name.Contains("اسپرسو"));
|
||||
if (espresso is not null)
|
||||
{
|
||||
db.MenuItemIngredients.AddRange(
|
||||
new MenuItemIngredient
|
||||
{
|
||||
Id = "mii_demo_espresso_coffee",
|
||||
CafeId = cafeId,
|
||||
MenuItemId = espresso.Id,
|
||||
IngredientId = "ing_demo_coffee",
|
||||
QuantityPerUnit = 10
|
||||
},
|
||||
new MenuItemIngredient
|
||||
{
|
||||
Id = "mii_demo_espresso_cup",
|
||||
CafeId = cafeId,
|
||||
MenuItemId = espresso.Id,
|
||||
IngredientId = "ing_demo_cups",
|
||||
QuantityPerUnit = 1
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
logger.LogInformation("Development inventory seed for cafe {CafeId}", cafeId);
|
||||
}
|
||||
|
||||
private static async Task SeedDemoTablesAsync(AppDbContext db, string cafeId, ILogger logger)
|
||||
{
|
||||
var specs = new (string Id, string Number, int Capacity, string? Floor, string? QrCode)[]
|
||||
{
|
||||
("table_demo_1", "1", 4, "همکف", "demo_table_01"),
|
||||
("table_demo_2", "2", 2, "همکف", null),
|
||||
("table_demo_3", "3", 4, "همکف", null),
|
||||
("table_demo_4", "4", 6, "بالکن", null),
|
||||
("table_demo_5", "5", 4, "بالکن", null),
|
||||
("table_demo_6", "6", 2, "بالکن", null),
|
||||
("table_demo_7", "7", 8, "سالن VIP", null),
|
||||
("table_demo_8", "8", 4, "سالن VIP", null),
|
||||
};
|
||||
|
||||
foreach (var s in specs)
|
||||
{
|
||||
if (await db.Tables.AnyAsync(t => t.Id == s.Id))
|
||||
continue;
|
||||
|
||||
db.Tables.Add(new Table
|
||||
{
|
||||
Id = s.Id,
|
||||
CafeId = cafeId,
|
||||
BranchId = "branch_demo_main",
|
||||
Number = s.Number,
|
||||
Capacity = s.Capacity,
|
||||
Floor = s.Floor,
|
||||
QrCode = s.QrCode ?? Guid.NewGuid().ToString("N"),
|
||||
IsActive = true
|
||||
});
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Development tables seed (8 tables, QR: demo_table_01) for cafe {CafeId}", cafeId);
|
||||
}
|
||||
|
||||
private static async Task SeedDemoReviewsAsync(AppDbContext db, string cafeId, ILogger logger)
|
||||
{
|
||||
if (await db.CafeReviews.AnyAsync(r => r.CafeId == cafeId))
|
||||
return;
|
||||
|
||||
db.CafeReviews.AddRange(
|
||||
new CafeReview
|
||||
{
|
||||
CafeId = cafeId,
|
||||
AuthorName = "سارا",
|
||||
Rating = 5,
|
||||
Comment = "قهوه و فضا عالی بود.",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-3)
|
||||
},
|
||||
new CafeReview
|
||||
{
|
||||
CafeId = cafeId,
|
||||
AuthorName = "علی",
|
||||
Rating = 4,
|
||||
Comment = "سرویس سریع، کیک خوشمزه.",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-1)
|
||||
});
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Development reviews seed for cafe {CafeId}", cafeId);
|
||||
}
|
||||
|
||||
private static async Task SeedDemoOrdersAsync(AppDbContext db, string cafeId, ILogger logger)
|
||||
{
|
||||
if (await db.Orders.AnyAsync(o => o.CafeId == cafeId))
|
||||
return;
|
||||
|
||||
var latte = await db.MenuItems.FirstOrDefaultAsync(m => m.Id == "item_demo_latte");
|
||||
var cake = await db.MenuItems.FirstOrDefaultAsync(m => m.Id == "item_demo_cake");
|
||||
if (latte is null || cake is null)
|
||||
return;
|
||||
|
||||
var customer = new Customer
|
||||
{
|
||||
Id = "cust_demo_reports",
|
||||
CafeId = cafeId,
|
||||
Name = "مشتری گزارش",
|
||||
Phone = "09120000001",
|
||||
Group = CustomerGroup.Regular,
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-10)
|
||||
};
|
||||
db.Customers.Add(customer);
|
||||
|
||||
for (var i = 0; i < 7; i++)
|
||||
{
|
||||
var createdAt = DateTime.UtcNow.AddDays(-i).AddHours(-2);
|
||||
var subtotal = 215_000m;
|
||||
var tax = Math.Round(subtotal * 0.09m, 0);
|
||||
var order = new Order
|
||||
{
|
||||
Id = $"order_demo_{i}",
|
||||
CafeId = cafeId,
|
||||
CustomerId = i % 2 == 0 ? customer.Id : null,
|
||||
OrderType = OrderType.DineIn,
|
||||
Status = OrderStatus.Delivered,
|
||||
DisplayNumber = i + 1,
|
||||
Subtotal = subtotal,
|
||||
TaxTotal = tax,
|
||||
DiscountAmount = i == 0 ? 15_000m : 0,
|
||||
Total = subtotal + tax - (i == 0 ? 15_000m : 0),
|
||||
CreatedAt = createdAt
|
||||
};
|
||||
db.Orders.Add(order);
|
||||
db.OrderItems.AddRange(
|
||||
new OrderItem
|
||||
{
|
||||
OrderId = order.Id,
|
||||
MenuItemId = latte.Id,
|
||||
Quantity = 1,
|
||||
UnitPrice = latte.Price
|
||||
},
|
||||
new OrderItem
|
||||
{
|
||||
OrderId = order.Id,
|
||||
MenuItemId = cake.Id,
|
||||
Quantity = 1,
|
||||
UnitPrice = cake.Price
|
||||
});
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Development reports seed: 7 demo orders for cafe {CafeId}", cafeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
using Meezi.Core.Discover;
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
public static class DiscoverShowcaseCatalog
|
||||
{
|
||||
public sealed record ShowcaseCafe(
|
||||
string Id,
|
||||
string Slug,
|
||||
string Name,
|
||||
string City,
|
||||
string Address,
|
||||
string Description,
|
||||
CafeDiscoverProfile Profile,
|
||||
PlanTier PlanTier,
|
||||
int MenuTemplateIndex,
|
||||
string OwnerPhone,
|
||||
IReadOnlyList<string>? Badges = null);
|
||||
|
||||
public static IReadOnlyList<ShowcaseCafe> Cafes { get; } =
|
||||
[
|
||||
Cafe("cafe_sc_01", "cafe-mokhteh-tehran", "کافه مخته", "تهران", "تهران، ولیعصر، نرسیده به پارک ساعی",
|
||||
"رستری تخصصی قهوه و دسر؛ فضای آرام برای کار و قرار دو نفره. اسپرسو، لاته، چیزکیک.",
|
||||
Profile(["roastery", "modern"], "cozy", ["quiet", "cozy"], ["study_work", "date"], ["indoor", "wifi"], "quiet", "mid"), 0, "09131000001"),
|
||||
Cafe("cafe_sc_02", "cafe-sibo-karaj", "کافه سیب", "کرج", "کرج، گوهردشت، بلوار موذننیا",
|
||||
"کافه دنج کرج با قهوه دمی و بیکری؛ مناسب خانواده و صبحانه آخر هفته.",
|
||||
Profile(["brunch", "plants_heavy"], "medium", ["casual", "cozy"], ["family", "friends"], ["indoor", "kids_friendly"], "moderate", "budget"), 1, "09131000002"),
|
||||
Cafe("cafe_sc_03", "cafe-ava-valiasr", "کافه آوا", "تهران", "تهران، ولیعصر، بالاتر از میدان ونک",
|
||||
"کافه مدرن با نور طبیعی؛ لاته آرت و نوشیدنیهای فصل. جستجو: قهوه تخصصی ولیعصر.",
|
||||
Profile(["modern", "instagrammable"], "medium", ["trendy", "lively"], ["friends", "quick_coffee"], ["indoor", "terrace"], "moderate", "mid"), 0, "09131000003"),
|
||||
Cafe("cafe_sc_04", "restaurant-sang-tehran", "رستوران سنگ", "تهران", "تهران، جردن، خیابان ناهید شرقی",
|
||||
"غذای ایرانی و بینالمللی؛ فضای لوکس برای شام و جشن. استیک، پاستا، سالاد.",
|
||||
Profile(["luxury", "heritage"], "large", ["luxury", "romantic"], ["celebration", "business_meeting"], ["indoor", "private_room"], "quiet", "premium"), 2, "09131000004", PlanTier.Business),
|
||||
Cafe("cafe_sc_05", "cafe-ketab-niavaran", "کافه کتاب نیاوران", "تهران", "تهران، نیاوران، خیابان باهنر",
|
||||
"بوفه کتاب با قهوه و چای؛ فضای ساکت برای درس و کار. دمنوش، کیک، ساندویچ.",
|
||||
Profile(["book_cafe", "quiet"], "cozy", ["quiet", "study_friendly"], ["study_work", "solo"], ["indoor", "wifi"], "quiet", "mid"), 0, "09131000005"),
|
||||
Cafe("cafe_sc_06", "cafe-rooz-karaj", "کافه روز", "کرج", "کرج، عظیمیه، میدان جهاد",
|
||||
"کافه روز کرج؛ اسموتی، قهوه سرد، صبحانه کامل. مناسب جوانان و دورهمی.",
|
||||
Profile(["modern", "brunch"], "medium", ["lively", "casual"], ["friends", "breakfast"], ["outdoor", "wifi"], "moderate", "budget"), 1, "09131000006"),
|
||||
Cafe("cafe_sc_07", "cafe-gol-reza", "کافه گل رضا", "تهران", "تهران، سعادتآباد، میدان کاج",
|
||||
"کافه گلدار و دکور سنتی مدرن؛ عالی برای عکاسی و قرار عاشقانه.",
|
||||
Profile(["persian_traditional", "instagrammable"], "cozy", ["romantic", "cozy"], ["date"], ["indoor", "plants"], "quiet", "mid"), 4, "09131000007"),
|
||||
Cafe("cafe_sc_08", "cafe-shab-enghelab", "کافه شب", "تهران", "تهران، انقلاب، خیابان ۱۲ فروردین",
|
||||
"کافه دیروقت؛ قهوه و دسر تا نیمهشب. موسیقی ملایم، فضای شبگاه.",
|
||||
Profile(["late_night", "industrial"], "medium", ["lively", "trendy"], ["friends", "finding_someone"], ["indoor", "live_music"], "lively", "mid"), 0, "09131000008"),
|
||||
Cafe("cafe_sc_09", "cafe-darya-karaj", "کافه دریا", "کرج", "کرج، مارلیک، بلوار ارم",
|
||||
"تراس باز با نوشیدنی سرد؛ مناسب تابستان و دورهمی دوستانه در کرج.",
|
||||
Profile(["modern"], "spacious", ["casual", "lively"], ["friends", "family"], ["outdoor", "terrace"], "moderate", "mid"), 0, "09131000009"),
|
||||
Cafe("cafe_sc_10", "cafe-nan-o-nam", "نان و نمک", "تهران", "تهران، پاسداران، خیابان گلستان هفتم",
|
||||
"بیکری-کافه با نان تازه و صبحانه؛ کره محلی، مربا، قهوه فیلتر.",
|
||||
Profile(["brunch", "heritage"], "cozy", ["cozy", "traditional"], ["breakfast", "family"], ["indoor"], "moderate", "budget"), 1, "09131000010"),
|
||||
Cafe("cafe_sc_11", "cafe-terrace-farmanieh", "تراس فرمانیه", "تهران", "تهران، فرمانیه، خیابان کلاهدوز",
|
||||
"کافه تراس با ویوی شهر؛ قهوه ویژه و دسر فصل. مناسب قرار و عکاسی.",
|
||||
Profile(["luxury", "instagrammable"], "large", ["romantic", "luxury"], ["date", "celebration"], ["terrace", "outdoor"], "quiet", "premium"), 3, "09131000011", PlanTier.Enterprise, ["award_winner", "roastery"]),
|
||||
Cafe("cafe_sc_12", "cafe-minimal-karaj", "مینیمال کرج", "کرج", "کرج، گلشهر، بلوار امیرکبیر",
|
||||
"کافه مینیمال کرج؛ اسپرسو دقیق و فضای سفید آرام.",
|
||||
Profile(["minimal", "scandi"], "tiny", ["quiet", "study_friendly"], ["solo", "study_work"], ["indoor", "wifi"], "quiet", "mid"), 0, "09131000012"),
|
||||
Cafe("cafe_sc_13", "cafe-chocolate-tehran", "خانه شکلات", "تهران", "تهران، زعفرانیه، خیابان مقدس اردبیلی",
|
||||
"دسرخانه شکلات و قهوه؛ تارت، شکلات دستساز، هات چاکلت.",
|
||||
Profile(["dessert_focus", "luxury"], "cozy", ["romantic", "cozy"], ["date"], ["indoor"], "quiet", "premium"), 3, "09131000013"),
|
||||
Cafe("cafe_sc_14", "cafe-vintage-tajrish", "کافه وینتیج تجریش", "تهران", "تهران، تجریش، میدان قدس",
|
||||
"دکور قدیمی تهران؛ قهوه ترک و کیک خانگی. فضای نوستالژیک.",
|
||||
Profile(["vintage", "heritage"], "cozy", ["traditional", "cozy"], ["friends", "solo"], ["indoor"], "moderate", "mid"), 0, "09131000014"),
|
||||
Cafe("cafe_sc_15", "cafe-work-hub-karaj", "هاب کار کرج", "کرج", "کرج، جهانشهر، بلوار شهید مطهری",
|
||||
"فضای کار اشتراکی با کافه؛ وایفای قوی، قهوه نامحدود، ساندویچ.",
|
||||
Profile(["modern", "book_cafe"], "medium", ["study_friendly", "quiet"], ["study_work", "business_meeting"], ["indoor", "wifi"], "quiet", "budget"), 0, "09131000015"),
|
||||
Cafe("cafe_sc_16", "cafe-pet-pardis", "کافه پت پاردیس", "تهران", "تهران، پردیس، فاز ۱",
|
||||
"کافه پتفرندلی؛ نوشیدنی گیاهی و فضای باز برای صاحبان سگ.",
|
||||
Profile(["plants_heavy", "modern"], "spacious", ["casual", "lively"], ["friends", "family"], ["outdoor", "pet_friendly"], "moderate", "mid"), 0, "09131000016", badges: ["pet_friendly"]),
|
||||
Cafe("cafe_sc_17", "restaurant-sabz-karaj", "رستوران سبز", "کرج", "کرج، مشکیندشت، بلوار آزادی",
|
||||
"رستوران گیاهی و سالم کرج؛ بول، سالاد، آبمیوه تازه.",
|
||||
Profile(["modern", "plants_heavy"], "medium", ["casual"], ["family", "friends"], ["indoor", "outdoor"], "moderate", "mid"), 2, "09131000017"),
|
||||
Cafe("cafe_sc_18", "cafe-roastery-darabad", "رستری دارآباد", "تهران", "تهران، دارآباد، خیابان اصلی",
|
||||
"رستری قهوه تخصصی با دمآوری دستی؛ تست پروفایلهای مختلف.",
|
||||
Profile(["roastery", "industrial"], "medium", ["trendy"], ["quick_coffee", "friends"], ["indoor"], "moderate", "mid"), 0, "09131000018"),
|
||||
Cafe("cafe_sc_19", "cafe-family-mehri", "کافه مهری", "تهران", "تهران، تهرانپارس، فلکه اول",
|
||||
"کافه خانوادگی با فضای بازی کودک؛ صبحانه و ناهار سبک.",
|
||||
Profile(["brunch"], "large", ["casual", "cozy"], ["family"], ["indoor", "kids_friendly"], "moderate", "budget"), 1, "09131000019"),
|
||||
Cafe("cafe_sc_20", "cafe-laleh-karaj", "کافه لاله", "کرج", "کرج، باغستان، خیابان شهید بهشتی",
|
||||
"کافه گل لاله کرج؛ دمنوش گل و قهوه ترک. فضای زنانه دوستانه.",
|
||||
Profile(["plants_heavy", "persian_traditional"], "cozy", ["cozy", "quiet"], ["friends", "solo"], ["indoor", "plants"], "quiet", "budget"), 4, "09131000020"),
|
||||
Cafe("cafe_sc_21", "cafe-business-iran", "کافه ایرانزمین", "تهران", "تهران، آفریقا، برج میلاد نزدیک",
|
||||
"مناسب جلسه کاری؛ قهوه سریع، صبحانه executive، سالاد.",
|
||||
Profile(["modern", "luxury"], "medium", ["luxury", "quiet"], ["business_meeting"], ["indoor", "wifi", "private_room"], "quiet", "premium"), 2, "09131000021"),
|
||||
Cafe("cafe_sc_22", "cafe-sunset-karaj", "غروب کرج", "کرج", "کرج، حصارک، جاده چالوس",
|
||||
"ویوی کوه و غروب؛ قهوه دمی و کیک هویج. مناسب آخر هفته.",
|
||||
Profile(["instagrammable"], "cozy", ["romantic", "casual"], ["date", "friends"], ["outdoor", "terrace"], "quiet", "mid"), 0, "09131000022"),
|
||||
Cafe("cafe_sc_23", "cafe-gaming-tehran", "کافه گیم", "تهران", "تهران، یوسفآباد، خیابان جهانآرا",
|
||||
"کافه گیمینگ؛ نوشیدنی انرژیزا و اسنک؛ فضای پرانرژی جوانان.",
|
||||
Profile(["modern", "late_night"], "medium", ["lively", "trendy"], ["friends"], ["indoor"], "lively", "budget"), 0, "09131000023"),
|
||||
Cafe("cafe_sc_24", "cafe-honey-karaj", "عسل کرج", "کرج", "کرج، طالقانی، میدان شهدا",
|
||||
"کافه با عسل محلی و چای؛ کیک عسل، دمنوش گیاهی کرج.",
|
||||
Profile(["heritage", "persian_traditional"], "cozy", ["traditional", "cozy"], ["family"], ["indoor"], "quiet", "budget"), 4, "09131000024"),
|
||||
Cafe("cafe_sc_25", "cafe-art-tehran", "کافه هنر", "تهران", "تهران، ایرانشهر، خیابان نواب",
|
||||
"گالری-کافه؛ قهوه و نمایشگاه موقت. فضای هنری و خلاق.",
|
||||
Profile(["artistic", "vintage"], "medium", ["artistic", "quiet"], ["solo", "friends"], ["indoor"], "quiet", "mid"), 0, "09131000025"),
|
||||
Cafe("cafe_sc_26", "cafe-quick-karaj", "ایستگاه قهوه کرج", "کرج", "کرج، کرج مرکزی، میدان آزادگان",
|
||||
"قهوه سریع کرج؛ مناسب مسیر کار و دانشجو. ساندویچ و اسپرسو.",
|
||||
Profile(["modern"], "tiny", ["casual"], ["quick_coffee", "study_work"], ["indoor"], "moderate", "budget"), 0, "09131000026"),
|
||||
Cafe("cafe_sc_27", "restaurant-caspian", "رستوران کاسپین", "تهران", "تهران، الهیه، خیابان فرشته",
|
||||
"غذای دریایی و فیش؛ پاستا دریایی، سالاد، mocktail.",
|
||||
Profile(["luxury", "modern"], "large", ["luxury", "romantic"], ["celebration", "date"], ["indoor"], "quiet", "premium"), 2, "09131000027", PlanTier.Enterprise, ["verified_partner"]),
|
||||
Cafe("cafe_sc_28", "cafe-mountain-karaj", "کافه کوهستان", "کرج", "کرج، محمدشهر، انتهای بلوار ارم",
|
||||
"فضای کوهستانی کرج؛ چای کوهی و سوخاری. هوای خنک تراس.",
|
||||
Profile(["modern"], "spacious", ["casual", "cozy"], ["family", "friends"], ["outdoor", "terrace"], "moderate", "mid"), 2, "09131000028"),
|
||||
Cafe("cafe_sc_29", "cafe-vegan-tehran", "کافه وگان", "تهران", "تهران، جمالزاده، خیابان مخبر",
|
||||
"منوی وگان و گیاهی؛ لاته جو، شیر بادام، دسر وگان.",
|
||||
Profile(["modern", "plants_heavy"], "cozy", ["trendy", "casual"], ["friends", "solo"], ["indoor", "wifi"], "quiet", "mid"), 0, "09131000029", badges: ["eco_friendly"]),
|
||||
Cafe("cafe_sc_30", "cafe-royal-karaj", "کافه رویال کرج", "کرج", "کرج، عظیمیه، برج بلور",
|
||||
"کافه لوکس کرج؛ دسر فرانسوی و قهوه اسپشیالتی. مناسب جشن.",
|
||||
Profile(["luxury", "dessert_focus"], "large", ["luxury", "romantic"], ["celebration", "date"], ["indoor", "private_room"], "quiet", "premium"), 3, "09131000030", PlanTier.Enterprise, ["award_winner", "verified_partner"]),
|
||||
];
|
||||
|
||||
private static ShowcaseCafe Cafe(
|
||||
string id,
|
||||
string slug,
|
||||
string name,
|
||||
string city,
|
||||
string address,
|
||||
string description,
|
||||
CafeDiscoverProfile profile,
|
||||
int menuTemplate,
|
||||
string phone,
|
||||
PlanTier plan = PlanTier.Pro,
|
||||
IReadOnlyList<string>? badges = null) =>
|
||||
new(id, slug, name, city, address, description, profile, plan, menuTemplate, phone, badges);
|
||||
|
||||
private static CafeDiscoverProfile Profile(
|
||||
string[] themes,
|
||||
string size,
|
||||
string[] vibes,
|
||||
string[] occasions,
|
||||
string[] space,
|
||||
string noise,
|
||||
string price) => new()
|
||||
{
|
||||
Themes = themes.ToList(),
|
||||
Size = size,
|
||||
Vibes = vibes.ToList(),
|
||||
Occasions = occasions.ToList(),
|
||||
SpaceFeatures = space.ToList(),
|
||||
NoiseLevel = noise,
|
||||
PriceTier = price,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
/// <summary>Rotating menu templates for discover showcase cafés.</summary>
|
||||
public static class DiscoverShowcaseMenus
|
||||
{
|
||||
public sealed record MenuTemplate(
|
||||
IReadOnlyList<DemoMenuCatalog.CategorySeed> Categories,
|
||||
IReadOnlyList<DemoMenuCatalog.ItemSeed> Items);
|
||||
|
||||
public static IReadOnlyList<MenuTemplate> Templates { get; } =
|
||||
[
|
||||
CoffeeShopTemplate(),
|
||||
BrunchTemplate(),
|
||||
RestaurantTemplate(),
|
||||
DessertTemplate(),
|
||||
TraditionalTemplate(),
|
||||
];
|
||||
|
||||
private static MenuTemplate CoffeeShopTemplate() => new(
|
||||
[
|
||||
new("cat_sc_hot", "قهوه تخصصی", "Specialty coffee", null, 1, IconPresetId: "drinks-hot", IconStyle: "gradient"),
|
||||
new("cat_sc_cold", "نوشیدنی سرد", "Cold drinks", null, 2, IconPresetId: "drinks-cold", IconStyle: "modern"),
|
||||
new("cat_sc_snack", "میانوعده", "Snacks", null, 3, IconPresetId: "food-mains", IconStyle: "flat"),
|
||||
],
|
||||
[
|
||||
new("item_sc_espresso", "cat_sc_hot", "اسپرسو", "Espresso", null, "دوبل سینگل", 70_000, 0, "espresso"),
|
||||
new("item_sc_latte", "cat_sc_hot", "لاته", "Latte", null, "شیر بخار", 125_000, 0, "latte"),
|
||||
new("item_sc_mocha", "cat_sc_hot", "موکا", "Mocha", null, null, 140_000, 0, "mocha"),
|
||||
new("item_sc_iced", "cat_sc_cold", "آیس آمریکانو", "Iced americano", null, null, 115_000, 0, "iced_coffee"),
|
||||
new("item_sc_cake", "cat_sc_snack", "چیزکیک", "Cheesecake", null, null, 165_000, 10, "cheesecake"),
|
||||
]);
|
||||
|
||||
private static MenuTemplate BrunchTemplate() => new(
|
||||
[
|
||||
new("cat_sc_br", "صبحانه", "Breakfast", null, 1, IconPresetId: "breakfast", IconStyle: "pastel"),
|
||||
new("cat_sc_br_d", "نوشیدنی", "Drinks", null, 2, IconPresetId: "drinks-hot"),
|
||||
],
|
||||
[
|
||||
new("item_sc_omelette", "cat_sc_br", "املت", "Omelette", null, "نان سنگک", 155_000, 0, "omelette"),
|
||||
new("item_sc_avocado", "cat_sc_br", "توست آووکادو", "Avocado toast", null, null, 195_000, 0, "avocado_toast"),
|
||||
new("item_sc_tea", "cat_sc_br_d", "چای", "Tea", null, null, 65_000, 0, "miso_soup"),
|
||||
]);
|
||||
|
||||
private static MenuTemplate RestaurantTemplate() => new(
|
||||
[
|
||||
new("cat_sc_main", "غذای اصلی", "Mains", null, 1, IconPresetId: "food-mains", IconStyle: "bold"),
|
||||
new("cat_sc_salad", "سالاد", "Salads", null, 2, IconPresetId: "food-mains"),
|
||||
new("cat_sc_drink", "نوشیدنی", "Drinks", null, 3, IconPresetId: "drinks-cold"),
|
||||
],
|
||||
[
|
||||
new("item_sc_burger", "cat_sc_main", "همبرگر", "Burger", null, null, 265_000, 0, "hamburger"),
|
||||
new("item_sc_pasta", "cat_sc_main", "پاستا", "Pasta", null, null, 245_000, 0, "spaghetti_bolognese"),
|
||||
new("item_sc_salad", "cat_sc_salad", "سالاد سزار", "Caesar salad", null, null, 185_000, 0, "caesar_salad"),
|
||||
new("item_sc_lemon", "cat_sc_drink", "لیموناد", "Lemonade", null, null, 95_000, 0, "lemonade"),
|
||||
]);
|
||||
|
||||
private static MenuTemplate DessertTemplate() => new(
|
||||
[
|
||||
new("cat_sc_des", "دسر", "Desserts", null, 1, IconPresetId: "dessert", IconStyle: "soft"),
|
||||
new("cat_sc_des_d", "قهوه", "Coffee", null, 2, IconPresetId: "drinks-hot"),
|
||||
],
|
||||
[
|
||||
new("item_sc_tiramisu", "cat_sc_des", "تیرامیسو", "Tiramisu", null, null, 175_000, 0, "tiramisu"),
|
||||
new("item_sc_macaron", "cat_sc_des", "ماکارون", "Macaron", null, null, 95_000, 0, "macarons"),
|
||||
new("item_sc_capp", "cat_sc_des_d", "کاپوچینو", "Cappuccino", null, null, 110_000, 0, "cappuccino"),
|
||||
]);
|
||||
|
||||
private static MenuTemplate TraditionalTemplate() => new(
|
||||
[
|
||||
new("cat_sc_tr", "نوشیدنی سنتی", "Traditional drinks", null, 1, IconPresetId: "drinks-hot", IconStyle: "duotone"),
|
||||
new("cat_sc_tr_f", "غذا", "Food", null, 2, IconPresetId: "food-mains"),
|
||||
],
|
||||
[
|
||||
new("item_sc_tea_tr", "cat_sc_tr", "چای ایرانی", "Persian tea", null, null, 55_000, 0, "miso_soup"),
|
||||
new("item_sc_kebab", "cat_sc_tr_f", "چلوکباب", "Kebab plate", null, null, 320_000, 0, "steak"),
|
||||
new("item_sc_soup", "cat_sc_tr_f", "سوپ", "Soup", null, null, 120_000, 0, "miso_soup"),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
using Meezi.Core.Branding;
|
||||
using Meezi.Core.Entities;
|
||||
using Meezi.Core.Enums;
|
||||
using Meezi.Infrastructure.Branding;
|
||||
using Meezi.Infrastructure.Discover;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
/// <summary>Seeds 30 Persian showcase cafés for public discover (development only).</summary>
|
||||
public static class DiscoverShowcaseSeeder
|
||||
{
|
||||
private static readonly string[] ReviewAuthors = ["سارا", "علی", "مینا", "رضا", "نازنین"];
|
||||
private static readonly string[] ReviewComments =
|
||||
[
|
||||
"فضا و نوشیدنی عالی بود.",
|
||||
"سرویس سریع، پیشنهاد میکنم.",
|
||||
"مناسب قرار و کار.",
|
||||
"قیمت مناسب برای کیفیت.",
|
||||
"دسر و قهوه خوشمزه بود.",
|
||||
];
|
||||
|
||||
public static async Task SeedAsync(AppDbContext db, ILogger logger)
|
||||
{
|
||||
var addedCafes = 0;
|
||||
foreach (var spec in DiscoverShowcaseCatalog.Cafes)
|
||||
{
|
||||
var cafe = await db.Cafes.FirstOrDefaultAsync(c => c.Id == spec.Id);
|
||||
if (cafe is null)
|
||||
{
|
||||
cafe = new Cafe
|
||||
{
|
||||
Id = spec.Id,
|
||||
Name = spec.Name,
|
||||
NameEn = spec.Slug.Replace('-', ' '),
|
||||
Slug = spec.Slug,
|
||||
City = spec.City,
|
||||
Address = spec.Address,
|
||||
Description = spec.Description,
|
||||
PlanTier = spec.PlanTier,
|
||||
PreferredLanguage = "fa",
|
||||
IsVerified = true,
|
||||
DiscoverProfileJson = CafeDiscoverProfileSerializer.Serialize(spec.Profile),
|
||||
DiscoverBadgesJson = DiscoverBadgesSerializer.Serialize(spec.Badges),
|
||||
ThemeJson = CafeThemeSerializer.Serialize(new CafeTheme
|
||||
{
|
||||
PaletteId = CafeThemeDefaults.PaletteMeeziGreen,
|
||||
PanelStyle = CafeThemeDefaults.PanelModern,
|
||||
MenuStyle = CafeThemeDefaults.MenuCards,
|
||||
Density = CafeThemeDefaults.DensityComfortable,
|
||||
Radius = CafeThemeDefaults.RadiusMd
|
||||
})
|
||||
};
|
||||
db.Cafes.Add(cafe);
|
||||
|
||||
var branchId = BranchId(spec.Id);
|
||||
db.Branches.Add(new Branch
|
||||
{
|
||||
Id = branchId,
|
||||
CafeId = spec.Id,
|
||||
Name = "شعبه اصلی",
|
||||
City = spec.City,
|
||||
Address = spec.Address,
|
||||
Phone = "02100000000",
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
db.Employees.Add(new Employee
|
||||
{
|
||||
Id = OwnerId(spec.Id),
|
||||
CafeId = spec.Id,
|
||||
BranchId = branchId,
|
||||
Name = $"مدیر {spec.Name}",
|
||||
Phone = spec.OwnerPhone,
|
||||
Role = EmployeeRole.Owner,
|
||||
BaseSalary = 0
|
||||
});
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
addedCafes++;
|
||||
}
|
||||
else
|
||||
{
|
||||
var changed = false;
|
||||
if (string.IsNullOrEmpty(cafe.DiscoverProfileJson))
|
||||
{
|
||||
cafe.DiscoverProfileJson = CafeDiscoverProfileSerializer.Serialize(spec.Profile);
|
||||
changed = true;
|
||||
}
|
||||
if (string.IsNullOrEmpty(cafe.DiscoverBadgesJson) && spec.Badges is { Count: > 0 })
|
||||
{
|
||||
cafe.DiscoverBadgesJson = DiscoverBadgesSerializer.Serialize(spec.Badges);
|
||||
changed = true;
|
||||
}
|
||||
if (!cafe.IsVerified)
|
||||
{
|
||||
cafe.IsVerified = true;
|
||||
changed = true;
|
||||
}
|
||||
if (changed)
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await EnsureShowcaseMenuAsync(db, spec, logger);
|
||||
await EnsureShowcaseReviewsAsync(db, spec.Id, logger);
|
||||
}
|
||||
|
||||
if (addedCafes > 0)
|
||||
logger.LogInformation("Discover showcase seed: {Count} new cafés", addedCafes);
|
||||
}
|
||||
|
||||
private static string BranchId(string cafeId) => $"branch_{cafeId}_main";
|
||||
private static string OwnerId(string cafeId) => $"emp_{cafeId}_owner";
|
||||
|
||||
private static async Task EnsureShowcaseMenuAsync(
|
||||
AppDbContext db,
|
||||
DiscoverShowcaseCatalog.ShowcaseCafe spec,
|
||||
ILogger logger)
|
||||
{
|
||||
var taxId = $"tax_{spec.Id}";
|
||||
if (!await db.Taxes.AnyAsync(t => t.Id == taxId))
|
||||
{
|
||||
db.Taxes.Add(new Tax
|
||||
{
|
||||
Id = taxId,
|
||||
CafeId = spec.Id,
|
||||
Name = "مالیات",
|
||||
Rate = 9,
|
||||
IsDefault = true,
|
||||
IsRequired = true,
|
||||
IsCompound = false
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var templateIndex = spec.MenuTemplateIndex % DiscoverShowcaseMenus.Templates.Count;
|
||||
var template = DiscoverShowcaseMenus.Templates[templateIndex];
|
||||
|
||||
var existingCats = await db.MenuCategories
|
||||
.Where(c => c.CafeId == spec.Id)
|
||||
.Select(c => c.Id)
|
||||
.ToListAsync();
|
||||
|
||||
var catsAdded = 0;
|
||||
foreach (var cat in template.Categories)
|
||||
{
|
||||
var catId = Prefixed(spec.Id, cat.Id);
|
||||
if (existingCats.Contains(catId))
|
||||
continue;
|
||||
|
||||
db.MenuCategories.Add(new MenuCategory
|
||||
{
|
||||
Id = catId,
|
||||
CafeId = spec.Id,
|
||||
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
|
||||
});
|
||||
catsAdded++;
|
||||
}
|
||||
|
||||
if (catsAdded > 0)
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var existingItems = await db.MenuItems
|
||||
.Where(i => i.CafeId == spec.Id)
|
||||
.Select(i => i.Id)
|
||||
.ToListAsync();
|
||||
|
||||
var itemsAdded = 0;
|
||||
foreach (var item in template.Items)
|
||||
{
|
||||
var itemId = Prefixed(spec.Id, item.Id);
|
||||
if (existingItems.Contains(itemId))
|
||||
continue;
|
||||
|
||||
var catId = Prefixed(spec.Id, item.CategoryId);
|
||||
db.MenuItems.Add(new MenuItem
|
||||
{
|
||||
Id = itemId,
|
||||
CafeId = spec.Id,
|
||||
CategoryId = catId,
|
||||
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 (itemsAdded > 0)
|
||||
{
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation(
|
||||
"Showcase menu: cafe {Slug} +{Cats} cats +{Items} items",
|
||||
spec.Slug,
|
||||
catsAdded,
|
||||
itemsAdded);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task EnsureShowcaseReviewsAsync(AppDbContext db, string cafeId, ILogger logger)
|
||||
{
|
||||
if (await db.CafeReviews.CountAsync(r => r.CafeId == cafeId) >= 2)
|
||||
return;
|
||||
|
||||
var rng = new Random(cafeId.GetHashCode(StringComparison.Ordinal));
|
||||
var count = 2 - await db.CafeReviews.CountAsync(r => r.CafeId == cafeId);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
db.CafeReviews.Add(new CafeReview
|
||||
{
|
||||
CafeId = cafeId,
|
||||
AuthorName = ReviewAuthors[rng.Next(ReviewAuthors.Length)],
|
||||
Rating = rng.Next(4, 6),
|
||||
Comment = ReviewComments[rng.Next(ReviewComments.Length)],
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-rng.Next(1, 30))
|
||||
});
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogDebug("Showcase reviews seeded for {CafeId}", cafeId);
|
||||
}
|
||||
|
||||
private static string Prefixed(string cafeId, string seedId) => $"{cafeId}_{seedId}";
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Unsplash fallbacks per Food-101 class folder name (Kaggle dataset layout).
|
||||
/// Used when menu-image-manifest.json has no entry or local JPEG was not imported.
|
||||
/// </summary>
|
||||
public static class Food101ImageFallbacks
|
||||
{
|
||||
private static readonly Dictionary<string, string> ClassUrls = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["apple_pie"] = "https://images.unsplash.com/photo-1535920527002-b43e668c3097?w=600",
|
||||
["avocado_toast"] = "https://images.unsplash.com/photo-1541519221064-49632fb3380e?w=600",
|
||||
["baklava"] = "https://images.unsplash.com/photo-1598110756419-84b30681f41f?w=600",
|
||||
["beef_carpaccio"] = "https://images.unsplash.com/photo-1600891963295-d66a269b9202?w=600",
|
||||
["beef_tartare"] = "https://images.unsplash.com/photo-1600891963295-d66a269b9202?w=600",
|
||||
["beet_salad"] = "https://images.unsplash.com/photo-1512621776951-a57141f2eefd?w=600",
|
||||
["bruschetta"] = "https://images.unsplash.com/photo-1572695157366-8486c5880882?w=600",
|
||||
["brownie"] = "https://images.unsplash.com/photo-1606313564200-e75d5e30476e?w=600",
|
||||
["caesar_salad"] = "https://images.unsplash.com/photo-1546793665-c74683f339c1?w=600",
|
||||
["caprese_salad"] = "https://images.unsplash.com/photo-1592417817098-8fd3d9eb14a5?w=600",
|
||||
["cappuccino"] = "https://images.unsplash.com/photo-1572442388796-11668a67e3d0?w=600",
|
||||
["carrot_cake"] = "https://images.unsplash.com/photo-1621303837534-610466b814da?w=600",
|
||||
["cheesecake"] = "https://images.unsplash.com/photo-1524351199678-941a58cfcc36?w=600",
|
||||
["chocolate_cake"] = "https://images.unsplash.com/photo-1578985545062-69928b1d9587?w=600",
|
||||
["churros"] = "https://images.unsplash.com/photo-1627482299165-0883a056a48f?w=600",
|
||||
["club_sandwich"] = "https://images.unsplash.com/photo-1528735602780-2552fd46c7af?w=600",
|
||||
["creme_brulee"] = "https://images.unsplash.com/photo-1470309864661-683be0ef7eaf?w=600",
|
||||
["croque_madame"] = "https://images.unsplash.com/photo-1555507036342-9231d37c10f3?w=600",
|
||||
["cup_cakes"] = "https://images.unsplash.com/photo-1614707267537-b85a1e38f271?w=600",
|
||||
["donuts"] = "https://images.unsplash.com/photo-1551024506-0bccd28d3071?w=600",
|
||||
["dumplings"] = "https://images.unsplash.com/photo-1496116218417-1a781a1c08c2?w=600",
|
||||
["edamame"] = "https://images.unsplash.com/photo-1459411621453-7b03977d4a4d?w=600",
|
||||
["eggs_benedict"] = "https://images.unsplash.com/photo-1608039819502-3d5a2a2e0b0e?w=600",
|
||||
["espresso"] = "https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=600&auto=format&fit=crop",
|
||||
["falafel"] = "https://images.unsplash.com/photo-1601050690597-df5748fb5cee?w=600",
|
||||
["french_fries"] = "https://images.unsplash.com/photo-1573080496219-76b9e6909700?w=600",
|
||||
["french_toast"] = "https://images.unsplash.com/photo-1484723091739-30a329e1f0c4?w=600",
|
||||
["fried_calamari"] = "https://images.unsplash.com/photo-1599487488170-d11ec9c172f0?w=600",
|
||||
["garlic_bread"] = "https://images.unsplash.com/photo-1619535852122-9f0362cc8b0e?w=600",
|
||||
["gnocchi"] = "https://images.unsplash.com/photo-1551183053-bf33a48c970a?w=600",
|
||||
["greek_salad"] = "https://images.unsplash.com/photo-1540189549336-e6e99c3679fe?w=600",
|
||||
["grilled_cheese_sandwich"] = "https://images.unsplash.com/photo-1528735602780-2552fd46c7af?w=600",
|
||||
["grilled_salmon"] = "https://images.unsplash.com/photo-1467003909585-2f8a72700288?w=600",
|
||||
["guacamole"] = "https://images.unsplash.com/photo-1529084963126-48f862a7038c?w=600",
|
||||
["hamburger"] = "https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=600",
|
||||
["hot_and_sour_soup"] = "https://images.unsplash.com/photo-1547592160-23ac45744acd?w=600",
|
||||
["hot_dog"] = "https://images.unsplash.com/photo-1612392062631-94de6c5a533f?w=600",
|
||||
["hummus"] = "https://images.unsplash.com/photo-1626208082043-e6319abfeec2?w=600",
|
||||
["ice_cream"] = "https://images.unsplash.com/photo-1563805042-7684c019e1cb?w=600",
|
||||
["iced_coffee"] = "https://images.unsplash.com/photo-1517487881594-2787aeee8f58?w=600&auto=format&fit=crop",
|
||||
["lasagna"] = "https://images.unsplash.com/photo-1574894709920-11b28e7367e3?w=600",
|
||||
["latte"] = "https://images.unsplash.com/photo-1461023058943-07fcbe16d735?w=600",
|
||||
["lemonade"] = "https://images.unsplash.com/photo-1523672990561-64c16245f769?w=600",
|
||||
["lobster_bisque"] = "https://images.unsplash.com/photo-1547592160-23ac45744acd?w=600",
|
||||
["macaroni_and_cheese"] = "https://images.unsplash.com/photo-1543339493-18da843d4cb4?w=600",
|
||||
["miso_soup"] = "https://images.unsplash.com/photo-1556679343-c7306c1976bc?w=600",
|
||||
["mocha"] = "https://images.unsplash.com/photo-1577887233537-a81b387b125e?w=600",
|
||||
["mushroom_risotto"] = "https://images.unsplash.com/photo-1476124362071-b9f2c96ef2db?w=600",
|
||||
["nachos"] = "https://images.unsplash.com/photo-1513459032971-41fd1e48ff3c?w=600",
|
||||
["omelette"] = "https://images.unsplash.com/photo-1525351484343-752d43d363f1?w=600",
|
||||
["onion_rings"] = "https://images.unsplash.com/photo-1630431341973-02b95b531aa4?w=600",
|
||||
["oysters"] = "https://images.unsplash.com/photo-1514392043407-4f468d2f5c9e?w=600",
|
||||
["paella"] = "https://images.unsplash.com/photo-1534084650011-4c4d81e8ca4b?w=600",
|
||||
["pancakes"] = "https://images.unsplash.com/photo-1567620905732-2d1ec7ab7440?w=600",
|
||||
["panna_cotta"] = "https://images.unsplash.com/photo-1488477181946-6428a029177a?w=600",
|
||||
["pasta_carbonara"] = "https://images.unsplash.com/photo-1621996346565-e3dbc646d9a9?w=600",
|
||||
["pho"] = "https://images.unsplash.com/photo-1591814468924-caf36d123dd6?w=600",
|
||||
["pizza"] = "https://images.unsplash.com/photo-1513104890138-7c749659a591?w=600",
|
||||
["pork_chop"] = "https://images.unsplash.com/photo-1432139558640-143f293648c5?w=600",
|
||||
["prime_rib"] = "https://images.unsplash.com/photo-1546833999-b9f581a1996d?w=600",
|
||||
["ramen"] = "https://images.unsplash.com/photo-1569718212165-3a8278d5f624?w=600",
|
||||
["risotto"] = "https://images.unsplash.com/photo-1476124362071-b9f2c96ef2db?w=600",
|
||||
["samosa"] = "https://images.unsplash.com/photo-1601050690597-df5748fb5cee?w=600",
|
||||
["sashimi"] = "https://images.unsplash.com/photo-1579584425555-c3ce17fd4351?w=600",
|
||||
["scallops"] = "https://images.unsplash.com/photo-1599021452684-36e82be5777f?w=600",
|
||||
["shawarma"] = "https://images.unsplash.com/photo-1529006557810-274adbcb39d8?w=600",
|
||||
["smoothie"] = "https://images.unsplash.com/photo-1505252585463-0433371f7f6b?w=600",
|
||||
["spaghetti_bolognese"] = "https://images.unsplash.com/photo-1622973536968-77544a8a4e0e?w=600",
|
||||
["spaghetti_carbonara"] = "https://images.unsplash.com/photo-1612874741227-866d1aeeecd1?w=600",
|
||||
["spring_rolls"] = "https://images.unsplash.com/photo-1526318896985-4d29c0903299?w=600",
|
||||
["steak"] = "https://images.unsplash.com/photo-1600891963295-d66a269b9202?w=600",
|
||||
["strawberry_shortcake"] = "https://images.unsplash.com/photo-1464349095436-e59b7d591c4f?w=600",
|
||||
["sushi"] = "https://images.unsplash.com/photo-1579584425555-c3ce17fd4351?w=600",
|
||||
["tacos"] = "https://images.unsplash.com/photo-1565299585323-38174c4aab1e?w=600",
|
||||
["tiramisu"] = "https://images.unsplash.com/photo-1571877227200-a0d98ea607e9?w=600",
|
||||
["tuna_tartare"] = "https://images.unsplash.com/photo-1544025162-d766942659778?w=600",
|
||||
["waffles"] = "https://images.unsplash.com/photo-1567818735240-7acbb4b7e34c?w=600",
|
||||
};
|
||||
|
||||
public static bool TryGetUrl(string food101Class, out string url)
|
||||
{
|
||||
if (ClassUrls.TryGetValue(food101Class, out var found) && !string.IsNullOrWhiteSpace(found))
|
||||
{
|
||||
url = found;
|
||||
return true;
|
||||
}
|
||||
|
||||
url = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string Resolve(string? food101Class, MenuItemVisualKind kind)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(food101Class) && TryGetUrl(food101Class, out var url))
|
||||
return url;
|
||||
|
||||
return MenuItemImageDefaults.GetDefaultImageUrl(kind);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
public static class MenuImageManifest
|
||||
{
|
||||
private static IReadOnlyDictionary<string, string>? _urls;
|
||||
private static string? _defaultDrinkUrl;
|
||||
private static string? _defaultFoodUrl;
|
||||
|
||||
private const string FallbackDrinkUrl =
|
||||
"https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=600&auto=format&fit=crop";
|
||||
|
||||
private const string FallbackFoodUrl =
|
||||
"https://images.unsplash.com/photo-1546793665-c74683f339c1?w=600";
|
||||
|
||||
public static string? GetImageUrl(string itemId)
|
||||
{
|
||||
EnsureLoaded();
|
||||
return _urls!.TryGetValue(itemId, out var url) ? url : null;
|
||||
}
|
||||
|
||||
public static string GetDefaultDrinkImageUrl()
|
||||
{
|
||||
EnsureLoaded();
|
||||
return _defaultDrinkUrl ?? FallbackDrinkUrl;
|
||||
}
|
||||
|
||||
public static string GetDefaultFoodImageUrl()
|
||||
{
|
||||
EnsureLoaded();
|
||||
return _defaultFoodUrl ?? FallbackFoodUrl;
|
||||
}
|
||||
|
||||
public static string ResolveImageUrl(string itemId, string fallback)
|
||||
=> GetLocalImageOverride(itemId) ?? fallback;
|
||||
|
||||
/// <summary>Only imported Kaggle/upload paths override Food-101 fallbacks (not stale CDN URLs in manifest).</summary>
|
||||
public static string? GetLocalImageOverride(string itemId)
|
||||
{
|
||||
var url = GetImageUrl(itemId);
|
||||
if (string.IsNullOrWhiteSpace(url)) return null;
|
||||
if (url.StartsWith("/uploads/", StringComparison.OrdinalIgnoreCase)
|
||||
|| url.StartsWith("uploads/", StringComparison.OrdinalIgnoreCase))
|
||||
return url.StartsWith('/') ? url : $"/{url}";
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void EnsureLoaded()
|
||||
{
|
||||
if (_urls is not null) return;
|
||||
LoadFromDisk();
|
||||
}
|
||||
|
||||
private static void LoadFromDisk()
|
||||
{
|
||||
var paths = new[]
|
||||
{
|
||||
Path.Combine(AppContext.BaseDirectory, "data", "menu-image-manifest.json"),
|
||||
Path.Combine(Directory.GetCurrentDirectory(), "data", "menu-image-manifest.json"),
|
||||
Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "data", "menu-image-manifest.json")),
|
||||
Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "data", "menu-image-manifest.json")),
|
||||
};
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (!File.Exists(path)) continue;
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(path);
|
||||
var doc = JsonDocument.Parse(json);
|
||||
if (doc.RootElement.TryGetProperty("defaults", out var defaults))
|
||||
{
|
||||
if (defaults.TryGetProperty("drink", out var drink))
|
||||
_defaultDrinkUrl = drink.GetString();
|
||||
if (defaults.TryGetProperty("food", out var food))
|
||||
_defaultFoodUrl = food.GetString();
|
||||
}
|
||||
|
||||
var map = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
if (doc.RootElement.TryGetProperty("items", out var items))
|
||||
{
|
||||
foreach (var prop in items.EnumerateObject())
|
||||
{
|
||||
if (prop.Value.TryGetProperty("imageUrl", out var url))
|
||||
map[prop.Name] = url.GetString() ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
_urls = map;
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// try next path
|
||||
}
|
||||
}
|
||||
|
||||
_urls = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using Meezi.Core.Entities;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
public enum MenuItemVisualKind
|
||||
{
|
||||
Food,
|
||||
Drink
|
||||
}
|
||||
|
||||
public static class MenuItemImageDefaults
|
||||
{
|
||||
private static readonly HashSet<string> DrinkCategoryIds = new(StringComparer.Ordinal)
|
||||
{
|
||||
"cat_demo_drinks",
|
||||
"cat_demo_cold"
|
||||
};
|
||||
|
||||
private static readonly string[] DrinkCategoryHints =
|
||||
[
|
||||
"drink", "cold", "coffee", "tea", "juice", "smoothie", "beverage", "bar",
|
||||
"نوشیدنی", "سرد", "گرم", "قهوه", "چای", "آبمیوه", "اسموتی", "مشروب", "بار"
|
||||
];
|
||||
|
||||
public static MenuItemVisualKind InferKind(string categoryId, string? categoryName = null)
|
||||
{
|
||||
if (DrinkCategoryIds.Contains(categoryId))
|
||||
return MenuItemVisualKind.Drink;
|
||||
|
||||
var haystack = $"{categoryId} {categoryName}".ToLowerInvariant();
|
||||
if (DrinkCategoryHints.Any(h => haystack.Contains(h, StringComparison.Ordinal)))
|
||||
return MenuItemVisualKind.Drink;
|
||||
|
||||
return MenuItemVisualKind.Food;
|
||||
}
|
||||
|
||||
public static string GetDefaultImageUrl(MenuItemVisualKind kind)
|
||||
=> kind == MenuItemVisualKind.Drink
|
||||
? MenuImageManifest.GetDefaultDrinkImageUrl()
|
||||
: MenuImageManifest.GetDefaultFoodImageUrl();
|
||||
|
||||
/// <summary>Remote https URL suitable for <img src> (excludes missing local uploads).</summary>
|
||||
public static bool IsUsableImageUrl(string? imageUrl) =>
|
||||
!string.IsNullOrWhiteSpace(imageUrl)
|
||||
&& (imageUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase)
|
||||
|| imageUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
public static bool NeedsImageRepair(string? imageUrl) => !IsUsableImageUrl(imageUrl);
|
||||
|
||||
public static string ResolveImageUrl(string itemId, string categoryId, string? categoryName)
|
||||
{
|
||||
var localOverride = MenuImageManifest.GetLocalImageOverride(itemId);
|
||||
if (!string.IsNullOrWhiteSpace(localOverride))
|
||||
return localOverride;
|
||||
|
||||
var catalog = DemoMenuCatalog.Items.FirstOrDefault(i => i.Id == itemId);
|
||||
if (catalog is not null)
|
||||
return DemoMenuCatalog.ResolveItemImageUrl(catalog);
|
||||
|
||||
return GetDefaultImageUrl(InferKind(categoryId, categoryName));
|
||||
}
|
||||
|
||||
public static string ResolveDisplayImageUrl(MenuItem item) =>
|
||||
IsUsableImageUrl(item.ImageUrl)
|
||||
? item.ImageUrl!
|
||||
: ResolveImageUrl(item.Id, item.CategoryId, item.Category?.Name);
|
||||
}
|
||||
+1045
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,672 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Cafes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
NameAr = table.Column<string>(type: "text", nullable: true),
|
||||
NameEn = table.Column<string>(type: "text", nullable: true),
|
||||
Slug = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
Phone = table.Column<string>(type: "text", nullable: true),
|
||||
Address = table.Column<string>(type: "text", nullable: true),
|
||||
City = table.Column<string>(type: "text", nullable: true),
|
||||
LogoUrl = table.Column<string>(type: "text", nullable: true),
|
||||
PlanTier = table.Column<int>(type: "integer", nullable: false),
|
||||
PlanExpiresAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
IsVerified = table.Column<bool>(type: "boolean", nullable: false),
|
||||
PreferredLanguage = table.Column<string>(type: "text", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Cafes", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Branches",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Address = table.Column<string>(type: "text", nullable: true),
|
||||
City = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Branches", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Branches_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Coupons",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Code = table.Column<string>(type: "text", nullable: false),
|
||||
Type = table.Column<int>(type: "integer", nullable: false),
|
||||
Value = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
MinOrderAmount = table.Column<decimal>(type: "numeric", nullable: true),
|
||||
MaxDiscount = table.Column<decimal>(type: "numeric", nullable: true),
|
||||
UsageLimit = table.Column<int>(type: "integer", nullable: true),
|
||||
UsedCount = table.Column<int>(type: "integer", nullable: false),
|
||||
TargetGroup = table.Column<int>(type: "integer", nullable: true),
|
||||
StartsAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
ExpiresAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
IsActive = table.Column<bool>(type: "boolean", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Coupons", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Coupons_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Customers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Phone = table.Column<string>(type: "text", nullable: false),
|
||||
NationalId = table.Column<string>(type: "text", nullable: true),
|
||||
BirthDateJalali = table.Column<string>(type: "text", nullable: true),
|
||||
Group = table.Column<int>(type: "integer", nullable: false),
|
||||
LoyaltyPoints = table.Column<int>(type: "integer", nullable: false),
|
||||
ReferredBy = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Customers", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Customers_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Employees",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Phone = table.Column<string>(type: "text", nullable: false),
|
||||
NationalId = table.Column<string>(type: "text", nullable: true),
|
||||
Role = table.Column<int>(type: "integer", nullable: false),
|
||||
BaseSalary = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
PinCode = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Employees", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Employees_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Taxes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Rate = table.Column<decimal>(type: "numeric(5,2)", precision: 5, scale: 2, nullable: false),
|
||||
IsDefault = table.Column<bool>(type: "boolean", nullable: false),
|
||||
IsRequired = table.Column<bool>(type: "boolean", nullable: false),
|
||||
IsCompound = table.Column<bool>(type: "boolean", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Taxes", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Taxes_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Tables",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
BranchId = table.Column<string>(type: "text", nullable: true),
|
||||
Number = table.Column<int>(type: "integer", nullable: false),
|
||||
Capacity = table.Column<int>(type: "integer", nullable: false),
|
||||
Floor = table.Column<string>(type: "text", nullable: true),
|
||||
QrCode = table.Column<string>(type: "text", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "boolean", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Tables", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Tables_Branches_BranchId",
|
||||
column: x => x.BranchId,
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_Tables_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Attendances",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
EmployeeId = table.Column<string>(type: "text", nullable: false),
|
||||
Date = table.Column<DateOnly>(type: "date", nullable: false),
|
||||
ClockIn = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
ClockOut = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
Notes = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Attendances", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Attendances_Employees_EmployeeId",
|
||||
column: x => x.EmployeeId,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "EmployeeSalaries",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
EmployeeId = table.Column<string>(type: "text", nullable: false),
|
||||
MonthYear = table.Column<string>(type: "text", nullable: false),
|
||||
BaseSalary = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
OvertimePay = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
Deductions = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
NetSalary = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
IsPaid = table.Column<bool>(type: "boolean", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_EmployeeSalaries", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_EmployeeSalaries_Employees_EmployeeId",
|
||||
column: x => x.EmployeeId,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "LeaveRequests",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
EmployeeId = table.Column<string>(type: "text", nullable: false),
|
||||
StartDate = table.Column<DateOnly>(type: "date", nullable: false),
|
||||
EndDate = table.Column<DateOnly>(type: "date", nullable: false),
|
||||
Reason = table.Column<string>(type: "text", nullable: true),
|
||||
Status = table.Column<int>(type: "integer", nullable: false),
|
||||
ReviewedBy = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_LeaveRequests", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_LeaveRequests_Employees_EmployeeId",
|
||||
column: x => x.EmployeeId,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Shifts",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
EmployeeId = table.Column<string>(type: "text", nullable: false),
|
||||
DayOfWeek = table.Column<int>(type: "integer", nullable: false),
|
||||
ShiftType = table.Column<int>(type: "integer", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Shifts", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Shifts_Employees_EmployeeId",
|
||||
column: x => x.EmployeeId,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MenuCategories",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
NameAr = table.Column<string>(type: "text", nullable: true),
|
||||
NameEn = table.Column<string>(type: "text", nullable: true),
|
||||
SortOrder = table.Column<int>(type: "integer", nullable: false),
|
||||
TaxId = table.Column<string>(type: "text", nullable: true),
|
||||
DiscountPercent = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "boolean", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MenuCategories", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_MenuCategories_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_MenuCategories_Taxes_TaxId",
|
||||
column: x => x.TaxId,
|
||||
principalTable: "Taxes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Orders",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
BranchId = table.Column<string>(type: "text", nullable: true),
|
||||
TableId = table.Column<string>(type: "text", nullable: true),
|
||||
CustomerId = table.Column<string>(type: "text", nullable: true),
|
||||
EmployeeId = table.Column<string>(type: "text", nullable: true),
|
||||
OrderType = table.Column<int>(type: "integer", nullable: false),
|
||||
Status = table.Column<int>(type: "integer", nullable: false),
|
||||
CouponId = table.Column<string>(type: "text", nullable: true),
|
||||
DiscountAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Subtotal = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
TaxTotal = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Total = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
SnappfoodOrderId = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Orders", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Orders_Branches_BranchId",
|
||||
column: x => x.BranchId,
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_Orders_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Orders_Coupons_CouponId",
|
||||
column: x => x.CouponId,
|
||||
principalTable: "Coupons",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_Orders_Customers_CustomerId",
|
||||
column: x => x.CustomerId,
|
||||
principalTable: "Customers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_Orders_Employees_EmployeeId",
|
||||
column: x => x.EmployeeId,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_Orders_Tables_TableId",
|
||||
column: x => x.TableId,
|
||||
principalTable: "Tables",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MenuItems",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
CategoryId = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
NameAr = table.Column<string>(type: "text", nullable: true),
|
||||
NameEn = table.Column<string>(type: "text", nullable: true),
|
||||
Description = table.Column<string>(type: "text", nullable: true),
|
||||
Price = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
ImageUrl = table.Column<string>(type: "text", nullable: true),
|
||||
IsAvailable = table.Column<bool>(type: "boolean", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MenuItems", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_MenuItems_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_MenuItems_MenuCategories_CategoryId",
|
||||
column: x => x.CategoryId,
|
||||
principalTable: "MenuCategories",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Payments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
OrderId = table.Column<string>(type: "text", nullable: false),
|
||||
Method = table.Column<int>(type: "integer", nullable: false),
|
||||
Amount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Status = table.Column<int>(type: "integer", nullable: false),
|
||||
Reference = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Payments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Payments_Orders_OrderId",
|
||||
column: x => x.OrderId,
|
||||
principalTable: "Orders",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OrderItems",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
OrderId = table.Column<string>(type: "text", nullable: false),
|
||||
MenuItemId = table.Column<string>(type: "text", nullable: false),
|
||||
Quantity = table.Column<int>(type: "integer", nullable: false),
|
||||
UnitPrice = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Notes = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OrderItems", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OrderItems_MenuItems_MenuItemId",
|
||||
column: x => x.MenuItemId,
|
||||
principalTable: "MenuItems",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_OrderItems_Orders_OrderId",
|
||||
column: x => x.OrderId,
|
||||
principalTable: "Orders",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Attendances_EmployeeId_Date",
|
||||
table: "Attendances",
|
||||
columns: new[] { "EmployeeId", "Date" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Branches_CafeId",
|
||||
table: "Branches",
|
||||
column: "CafeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Cafes_Slug",
|
||||
table: "Cafes",
|
||||
column: "Slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Coupons_CafeId_Code",
|
||||
table: "Coupons",
|
||||
columns: new[] { "CafeId", "Code" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Customers_CafeId_Phone",
|
||||
table: "Customers",
|
||||
columns: new[] { "CafeId", "Phone" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Employees_CafeId_Phone",
|
||||
table: "Employees",
|
||||
columns: new[] { "CafeId", "Phone" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EmployeeSalaries_EmployeeId_MonthYear",
|
||||
table: "EmployeeSalaries",
|
||||
columns: new[] { "EmployeeId", "MonthYear" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LeaveRequests_EmployeeId",
|
||||
table: "LeaveRequests",
|
||||
column: "EmployeeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MenuCategories_CafeId",
|
||||
table: "MenuCategories",
|
||||
column: "CafeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MenuCategories_TaxId",
|
||||
table: "MenuCategories",
|
||||
column: "TaxId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MenuItems_CafeId",
|
||||
table: "MenuItems",
|
||||
column: "CafeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MenuItems_CategoryId",
|
||||
table: "MenuItems",
|
||||
column: "CategoryId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrderItems_MenuItemId",
|
||||
table: "OrderItems",
|
||||
column: "MenuItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrderItems_OrderId",
|
||||
table: "OrderItems",
|
||||
column: "OrderId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_BranchId",
|
||||
table: "Orders",
|
||||
column: "BranchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_CafeId",
|
||||
table: "Orders",
|
||||
column: "CafeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_CouponId",
|
||||
table: "Orders",
|
||||
column: "CouponId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_CustomerId",
|
||||
table: "Orders",
|
||||
column: "CustomerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_EmployeeId",
|
||||
table: "Orders",
|
||||
column: "EmployeeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_TableId",
|
||||
table: "Orders",
|
||||
column: "TableId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Payments_OrderId",
|
||||
table: "Payments",
|
||||
column: "OrderId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Shifts_EmployeeId_DayOfWeek",
|
||||
table: "Shifts",
|
||||
columns: new[] { "EmployeeId", "DayOfWeek" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tables_BranchId",
|
||||
table: "Tables",
|
||||
column: "BranchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tables_CafeId",
|
||||
table: "Tables",
|
||||
column: "CafeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tables_QrCode",
|
||||
table: "Tables",
|
||||
column: "QrCode",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Taxes_CafeId",
|
||||
table: "Taxes",
|
||||
column: "CafeId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Attendances");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "EmployeeSalaries");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "LeaveRequests");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OrderItems");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Payments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Shifts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MenuItems");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Orders");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MenuCategories");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Coupons");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Customers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Employees");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Tables");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Taxes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Branches");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Cafes");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1113
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddTableReservations : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TableReservations",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false),
|
||||
CustomerId = table.Column<string>(type: "text", nullable: true),
|
||||
GuestName = table.Column<string>(type: "text", nullable: false),
|
||||
GuestPhone = table.Column<string>(type: "text", nullable: false),
|
||||
Date = table.Column<DateOnly>(type: "date", nullable: false),
|
||||
Time = table.Column<TimeOnly>(type: "time without time zone", nullable: false),
|
||||
PartySize = table.Column<int>(type: "integer", nullable: false),
|
||||
Status = table.Column<int>(type: "integer", nullable: false),
|
||||
Notes = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TableReservations", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_TableReservations_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_TableReservations_Customers_CustomerId",
|
||||
column: x => x.CustomerId,
|
||||
principalTable: "Customers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TableReservations_CafeId_Date_Time",
|
||||
table: "TableReservations",
|
||||
columns: new[] { "CafeId", "Date", "Time" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TableReservations_CustomerId",
|
||||
table: "TableReservations",
|
||||
column: "CustomerId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "TableReservations");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1174
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCafeReviews : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "CoverImageUrl",
|
||||
table: "Cafes",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Description",
|
||||
table: "Cafes",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CafeReviews",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false),
|
||||
AuthorName = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
AuthorPhone = table.Column<string>(type: "text", nullable: true),
|
||||
Rating = table.Column<int>(type: "integer", nullable: false),
|
||||
Comment = table.Column<string>(type: "text", nullable: true),
|
||||
OwnerReply = table.Column<string>(type: "text", nullable: true),
|
||||
OwnerRepliedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CafeReviews", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CafeReviews_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CafeReviews_CafeId_CreatedAt",
|
||||
table: "CafeReviews",
|
||||
columns: new[] { "CafeId", "CreatedAt" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CafeReviews");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CoverImageUrl",
|
||||
table: "Cafes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Description",
|
||||
table: "Cafes");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1237
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddSubscriptionBilling : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "SnappfoodVendorId",
|
||||
table: "Cafes",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SubscriptionPayments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
PlanTier = table.Column<int>(type: "integer", nullable: false),
|
||||
Months = table.Column<int>(type: "integer", nullable: false),
|
||||
AmountToman = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
AmountRials = table.Column<long>(type: "bigint", nullable: false),
|
||||
Authority = table.Column<string>(type: "text", nullable: true),
|
||||
RefId = table.Column<string>(type: "text", nullable: true),
|
||||
Status = table.Column<int>(type: "integer", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SubscriptionPayments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SubscriptionPayments_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SubscriptionPayments_Authority",
|
||||
table: "SubscriptionPayments",
|
||||
column: "Authority");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SubscriptionPayments_CafeId",
|
||||
table: "SubscriptionPayments",
|
||||
column: "CafeId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "SubscriptionPayments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SnappfoodVendorId",
|
||||
table: "Cafes");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1251
File diff suppressed because it is too large
Load Diff
+67
@@ -0,0 +1,67 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class TableManagementEnhancements : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Number",
|
||||
table: "Tables",
|
||||
type: "character varying(50)",
|
||||
maxLength: 50,
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "integer");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "TableId",
|
||||
table: "TableReservations",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TableReservations_TableId",
|
||||
table: "TableReservations",
|
||||
column: "TableId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_TableReservations_Tables_TableId",
|
||||
table: "TableReservations",
|
||||
column: "TableId",
|
||||
principalTable: "Tables",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_TableReservations_Tables_TableId",
|
||||
table: "TableReservations");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_TableReservations_TableId",
|
||||
table: "TableReservations");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TableId",
|
||||
table: "TableReservations");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Number",
|
||||
table: "Tables",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(50)",
|
||||
oldMaxLength: 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1254
File diff suppressed because it is too large
Load Diff
+29
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddMenuItemDiscountAndMedia : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "DiscountPercent",
|
||||
table: "MenuItems",
|
||||
type: "numeric",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DiscountPercent",
|
||||
table: "MenuItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1366
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddInventoryEntities : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Ingredients",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Unit = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
QuantityOnHand = table.Column<decimal>(type: "numeric(18,3)", precision: 18, scale: 3, nullable: false),
|
||||
ReorderLevel = table.Column<decimal>(type: "numeric(18,3)", precision: 18, scale: 3, nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Ingredients", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Ingredients_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StockMovements",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
IngredientId = table.Column<string>(type: "text", nullable: false),
|
||||
Delta = table.Column<decimal>(type: "numeric(18,3)", precision: 18, scale: 3, nullable: false),
|
||||
Note = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_StockMovements", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_StockMovements_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_StockMovements_Ingredients_IngredientId",
|
||||
column: x => x.IngredientId,
|
||||
principalTable: "Ingredients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Ingredients_CafeId",
|
||||
table: "Ingredients",
|
||||
column: "CafeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StockMovements_CafeId",
|
||||
table: "StockMovements",
|
||||
column: "CafeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StockMovements_IngredientId",
|
||||
table: "StockMovements",
|
||||
column: "IngredientId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "StockMovements");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Ingredients");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1375
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddMenuAndTableMedia : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ImageUrl",
|
||||
table: "Tables",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "VideoUrl",
|
||||
table: "Tables",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "VideoUrl",
|
||||
table: "MenuItems",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ImageUrl",
|
||||
table: "Tables");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "VideoUrl",
|
||||
table: "Tables");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "VideoUrl",
|
||||
table: "MenuItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1387
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddOrderReservationLink : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ReservationId",
|
||||
table: "Orders",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_ReservationId",
|
||||
table: "Orders",
|
||||
column: "ReservationId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Orders_TableReservations_ReservationId",
|
||||
table: "Orders",
|
||||
column: "ReservationId",
|
||||
principalTable: "TableReservations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Orders_TableReservations_ReservationId",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Orders_ReservationId",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ReservationId",
|
||||
table: "Orders");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1390
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddOrderGuestName : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "GuestName",
|
||||
table: "Orders",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GuestName",
|
||||
table: "Orders");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1396
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class PosTableSessionFields : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsCleaning",
|
||||
table: "Tables",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "GuestPhone",
|
||||
table: "Orders",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsCleaning",
|
||||
table: "Tables");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GuestPhone",
|
||||
table: "Orders");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1405
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class VoidOrderLine : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsVoided",
|
||||
table: "OrderItems",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "VoidedAt",
|
||||
table: "OrderItems",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "VoidedByUserId",
|
||||
table: "OrderItems",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(name: "VoidedByUserId", table: "OrderItems");
|
||||
migrationBuilder.DropColumn(name: "VoidedAt", table: "OrderItems");
|
||||
migrationBuilder.DropColumn(name: "IsVoided", table: "OrderItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1415
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class EmployeeBranchId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "BranchId",
|
||||
table: "Employees",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Employees_BranchId",
|
||||
table: "Employees",
|
||||
column: "BranchId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Employees_Branches_BranchId",
|
||||
table: "Employees",
|
||||
column: "BranchId",
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Employees_Branches_BranchId",
|
||||
table: "Employees");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Employees_BranchId",
|
||||
table: "Employees");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BranchId",
|
||||
table: "Employees");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1492
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class DailyQueueTickets : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "QueueTickets",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
BranchId = table.Column<string>(type: "text", nullable: true),
|
||||
ServiceDate = table.Column<DateOnly>(type: "date", nullable: false),
|
||||
Number = table.Column<int>(type: "integer", nullable: false),
|
||||
CustomerLabel = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
IssuedByUserId = table.Column<string>(type: "text", nullable: true),
|
||||
Status = table.Column<int>(type: "integer", nullable: false),
|
||||
OrderId = table.Column<string>(type: "text", nullable: true),
|
||||
IssuedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_QueueTickets", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_QueueTickets_Branches_BranchId",
|
||||
column: x => x.BranchId,
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_QueueTickets_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_QueueTickets_Orders_OrderId",
|
||||
column: x => x.OrderId,
|
||||
principalTable: "Orders",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_QueueTickets_BranchId",
|
||||
table: "QueueTickets",
|
||||
column: "BranchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_QueueTickets_CafeId_BranchId_ServiceDate_Number",
|
||||
table: "QueueTickets",
|
||||
columns: new[] { "CafeId", "BranchId", "ServiceDate", "Number" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_QueueTickets_OrderId",
|
||||
table: "QueueTickets",
|
||||
column: "OrderId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "QueueTickets");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1509
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddBranchEntity : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Branches_CafeId",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
table: "Branches",
|
||||
type: "character varying(200)",
|
||||
maxLength: 200,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "City",
|
||||
table: "Branches",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Address",
|
||||
table: "Branches",
|
||||
type: "character varying(500)",
|
||||
maxLength: 500,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsActive",
|
||||
table: "Branches",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Phone",
|
||||
table: "Branches",
|
||||
type: "character varying(20)",
|
||||
maxLength: 20,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "UpdatedAt",
|
||||
table: "Branches",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
defaultValueSql: "NOW() AT TIME ZONE 'UTC'");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Branches_CafeId_IsActive",
|
||||
table: "Branches",
|
||||
columns: new[] { "CafeId", "IsActive" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Branches_CafeId_IsActive",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsActive",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Phone",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UpdatedAt",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
table: "Branches",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(200)",
|
||||
oldMaxLength: 200);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "City",
|
||||
table: "Branches",
|
||||
type: "text",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(100)",
|
||||
oldMaxLength: 100,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Address",
|
||||
table: "Branches",
|
||||
type: "text",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(500)",
|
||||
oldMaxLength: 500,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Branches_CafeId",
|
||||
table: "Branches",
|
||||
column: "CafeId");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1683
File diff suppressed because it is too large
Load Diff
+184
@@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddShiftAndCashTransaction : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Shifts_Employees_EmployeeId",
|
||||
table: "Shifts");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "Shifts",
|
||||
newName: "EmployeeSchedules");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_Shifts_EmployeeId_DayOfWeek",
|
||||
table: "EmployeeSchedules",
|
||||
newName: "IX_EmployeeSchedules_EmployeeId_DayOfWeek");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_EmployeeSchedules_Employees_EmployeeId",
|
||||
table: "EmployeeSchedules",
|
||||
column: "EmployeeId",
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RegisterShifts",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false),
|
||||
BranchId = table.Column<string>(type: "text", nullable: false),
|
||||
OpenedByUserId = table.Column<string>(type: "text", nullable: false),
|
||||
ClosedByUserId = table.Column<string>(type: "text", nullable: true),
|
||||
OpenedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
ClosedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
OpeningCash = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
ClosingCash = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true),
|
||||
ExpectedCash = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Discrepancy = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true),
|
||||
Status = table.Column<int>(type: "integer", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RegisterShifts", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RegisterShifts_Branches_BranchId",
|
||||
column: x => x.BranchId,
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RegisterShifts_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RegisterShifts_Employees_ClosedByUserId",
|
||||
column: x => x.ClosedByUserId,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_RegisterShifts_Employees_OpenedByUserId",
|
||||
column: x => x.OpenedByUserId,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CashTransactions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
ShiftId = table.Column<string>(type: "text", nullable: false),
|
||||
BranchId = table.Column<string>(type: "text", nullable: true),
|
||||
Type = table.Column<int>(type: "integer", nullable: false),
|
||||
Method = table.Column<int>(type: "integer", nullable: false),
|
||||
Amount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
ReferenceId = table.Column<string>(type: "text", nullable: true),
|
||||
Note = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedByUserId = table.Column<string>(type: "text", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CashTransactions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CashTransactions_Branches_BranchId",
|
||||
column: x => x.BranchId,
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_CashTransactions_RegisterShifts_ShiftId",
|
||||
column: x => x.ShiftId,
|
||||
principalTable: "RegisterShifts",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RegisterShifts_BranchId_Status",
|
||||
table: "RegisterShifts",
|
||||
columns: new[] { "BranchId", "Status" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RegisterShifts_CafeId",
|
||||
table: "RegisterShifts",
|
||||
column: "CafeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RegisterShifts_ClosedByUserId",
|
||||
table: "RegisterShifts",
|
||||
column: "ClosedByUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RegisterShifts_OpenedByUserId",
|
||||
table: "RegisterShifts",
|
||||
column: "OpenedByUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CashTransactions_BranchId",
|
||||
table: "CashTransactions",
|
||||
column: "BranchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CashTransactions_CafeId_BranchId",
|
||||
table: "CashTransactions",
|
||||
columns: new[] { "CafeId", "BranchId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CashTransactions_ShiftId",
|
||||
table: "CashTransactions",
|
||||
column: "ShiftId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CashTransactions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RegisterShifts");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_EmployeeSchedules_Employees_EmployeeId",
|
||||
table: "EmployeeSchedules");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "EmployeeSchedules",
|
||||
newName: "Shifts");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_EmployeeSchedules_EmployeeId_DayOfWeek",
|
||||
table: "Shifts",
|
||||
newName: "IX_Shifts_EmployeeId_DayOfWeek");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Shifts_Employees_EmployeeId",
|
||||
table: "Shifts",
|
||||
column: "EmployeeId",
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1773
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Meezi.Core.Entities;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDailyReport : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DailyReports",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
BranchId = table.Column<string>(type: "text", nullable: false),
|
||||
Date = table.Column<DateOnly>(type: "date", nullable: false),
|
||||
TotalRevenue = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
CashRevenue = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
CardRevenue = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
CreditRevenue = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
TotalOrders = table.Column<int>(type: "integer", nullable: false),
|
||||
AvgOrderValue = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
TotalVoids = table.Column<int>(type: "integer", nullable: false),
|
||||
VoidAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
TotalExpenses = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
NetIncome = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
TopProducts = table.Column<List<TopProductEntry>>(type: "jsonb", nullable: false),
|
||||
GeneratedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DailyReports", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_DailyReports_Branches_BranchId",
|
||||
column: x => x.BranchId,
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DailyReports_BranchId",
|
||||
table: "DailyReports",
|
||||
column: "BranchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DailyReports_CafeId_BranchId_Date",
|
||||
table: "DailyReports",
|
||||
columns: new[] { "CafeId", "BranchId", "Date" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DailyReports");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1841
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddExpense : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Expenses",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
BranchId = table.Column<string>(type: "text", nullable: false),
|
||||
ShiftId = table.Column<string>(type: "text", nullable: true),
|
||||
Category = table.Column<int>(type: "integer", nullable: false),
|
||||
Amount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Note = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
|
||||
ReceiptImageUrl = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
|
||||
CreatedByUserId = table.Column<string>(type: "text", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Expenses", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Expenses_Branches_BranchId",
|
||||
column: x => x.BranchId,
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Expenses_RegisterShifts_ShiftId",
|
||||
column: x => x.ShiftId,
|
||||
principalTable: "RegisterShifts",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Expenses_BranchId",
|
||||
table: "Expenses",
|
||||
column: "BranchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Expenses_CafeId_BranchId_CreatedAt",
|
||||
table: "Expenses",
|
||||
columns: new[] { "CafeId", "BranchId", "CreatedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Expenses_ShiftId",
|
||||
table: "Expenses",
|
||||
column: "ShiftId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Expenses");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1913
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class BranchMenuItemOverride : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BranchMenuItemOverrides",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
BranchId = table.Column<string>(type: "text", nullable: false),
|
||||
MenuItemId = table.Column<string>(type: "text", nullable: false),
|
||||
IsAvailable = table.Column<bool>(type: "boolean", nullable: false),
|
||||
PriceOverride = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true),
|
||||
SortOrderOverride = table.Column<int>(type: "integer", nullable: true),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedByUserId = table.Column<string>(type: "text", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BranchMenuItemOverrides", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_BranchMenuItemOverrides_Branches_BranchId",
|
||||
column: x => x.BranchId,
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_BranchMenuItemOverrides_MenuItems_MenuItemId",
|
||||
column: x => x.MenuItemId,
|
||||
principalTable: "MenuItems",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BranchMenuItemOverrides_BranchId_MenuItemId",
|
||||
table: "BranchMenuItemOverrides",
|
||||
columns: new[] { "BranchId", "MenuItemId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BranchMenuItemOverrides_CafeId",
|
||||
table: "BranchMenuItemOverrides",
|
||||
column: "CafeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BranchMenuItemOverrides_MenuItemId",
|
||||
table: "BranchMenuItemOverrides",
|
||||
column: "MenuItemId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "BranchMenuItemOverrides");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1945
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,115 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPrinterSettings : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "AutoCutEnabled",
|
||||
table: "Branches",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "KitchenPrinterIp",
|
||||
table: "Branches",
|
||||
type: "character varying(45)",
|
||||
maxLength: 45,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "KitchenPrinterPort",
|
||||
table: "Branches",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PaperWidthMm",
|
||||
table: "Branches",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ReceiptFooter",
|
||||
table: "Branches",
|
||||
type: "character varying(500)",
|
||||
maxLength: 500,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ReceiptHeader",
|
||||
table: "Branches",
|
||||
type: "character varying(500)",
|
||||
maxLength: 500,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ReceiptPrinterIp",
|
||||
table: "Branches",
|
||||
type: "character varying(45)",
|
||||
maxLength: 45,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "ReceiptPrinterPort",
|
||||
table: "Branches",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WifiPassword",
|
||||
table: "Branches",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AutoCutEnabled",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "KitchenPrinterIp",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "KitchenPrinterPort",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PaperWidthMm",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ReceiptFooter",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ReceiptHeader",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ReceiptPrinterIp",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ReceiptPrinterPort",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WifiPassword",
|
||||
table: "Branches");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2021
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class BranchTableOwnership : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "SectionId",
|
||||
table: "Tables",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SortOrder",
|
||||
table: "Tables",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
UPDATE "Tables" t
|
||||
SET "BranchId" = (
|
||||
SELECT b."Id"
|
||||
FROM "Branches" b
|
||||
WHERE b."CafeId" = t."CafeId"
|
||||
ORDER BY b."CreatedAt"
|
||||
LIMIT 1
|
||||
)
|
||||
WHERE t."BranchId" IS NULL OR t."BranchId" = '';
|
||||
""");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Tables_Branches_BranchId",
|
||||
table: "Tables");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Tables_BranchId",
|
||||
table: "Tables");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "BranchId",
|
||||
table: "Tables",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TableSections",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
BranchId = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
SortOrder = table.Column<int>(type: "integer", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "boolean", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TableSections", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_TableSections_Branches_BranchId",
|
||||
column: x => x.BranchId,
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tables_BranchId_SectionId_SortOrder",
|
||||
table: "Tables",
|
||||
columns: new[] { "BranchId", "SectionId", "SortOrder" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tables_SectionId",
|
||||
table: "Tables",
|
||||
column: "SectionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TableSections_BranchId_Name",
|
||||
table: "TableSections",
|
||||
columns: new[] { "BranchId", "Name" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TableSections_CafeId",
|
||||
table: "TableSections",
|
||||
column: "CafeId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Tables_Branches_BranchId",
|
||||
table: "Tables",
|
||||
column: "BranchId",
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Tables_TableSections_SectionId",
|
||||
table: "Tables",
|
||||
column: "SectionId",
|
||||
principalTable: "TableSections",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Tables_Branches_BranchId",
|
||||
table: "Tables");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Tables_TableSections_SectionId",
|
||||
table: "Tables");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TableSections");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Tables_BranchId_SectionId_SortOrder",
|
||||
table: "Tables");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Tables_SectionId",
|
||||
table: "Tables");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SectionId",
|
||||
table: "Tables");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SortOrder",
|
||||
table: "Tables");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "BranchId",
|
||||
table: "Tables",
|
||||
type: "text",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tables_BranchId",
|
||||
table: "Tables",
|
||||
column: "BranchId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Tables_Branches_BranchId",
|
||||
table: "Tables",
|
||||
column: "BranchId",
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2028
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPosDeviceSettings : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "PosDeviceIp",
|
||||
table: "Branches",
|
||||
type: "character varying(45)",
|
||||
maxLength: 45,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PosDevicePort",
|
||||
table: "Branches",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PosDeviceIp",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PosDevicePort",
|
||||
table: "Branches");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2028
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixOrderItemVoidColumns : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// VoidOrderLine (20260521134834) was recorded but its Up() was empty on some databases.
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
ALTER TABLE "OrderItems" ADD COLUMN IF NOT EXISTS "IsVoided" boolean NOT NULL DEFAULT false;
|
||||
ALTER TABLE "OrderItems" ADD COLUMN IF NOT EXISTS "VoidedAt" timestamp with time zone;
|
||||
ALTER TABLE "OrderItems" ADD COLUMN IF NOT EXISTS "VoidedByUserId" text;
|
||||
""");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
ALTER TABLE "OrderItems" DROP COLUMN IF EXISTS "VoidedByUserId";
|
||||
ALTER TABLE "OrderItems" DROP COLUMN IF EXISTS "VoidedAt";
|
||||
ALTER TABLE "OrderItems" DROP COLUMN IF EXISTS "IsVoided";
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2036
File diff suppressed because it is too large
Load Diff
+40
@@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddMenuCategoryIconAndImage : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Icon",
|
||||
table: "MenuCategories",
|
||||
type: "character varying(32)",
|
||||
maxLength: 32,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ImageUrl",
|
||||
table: "MenuCategories",
|
||||
type: "character varying(500)",
|
||||
maxLength: 500,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Icon",
|
||||
table: "MenuCategories");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ImageUrl",
|
||||
table: "MenuCategories");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2044
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddMenuCategoryIconPreset : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "IconPresetId",
|
||||
table: "MenuCategories",
|
||||
type: "character varying(48)",
|
||||
maxLength: 48,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "IconStyle",
|
||||
table: "MenuCategories",
|
||||
type: "character varying(16)",
|
||||
maxLength: 16,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IconPresetId",
|
||||
table: "MenuCategories");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IconStyle",
|
||||
table: "MenuCategories");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2048
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCafeThemeJson : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ThemeJson",
|
||||
table: "Cafes",
|
||||
type: "character varying(8000)",
|
||||
maxLength: 8000,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ThemeJson",
|
||||
table: "Cafes");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2069
File diff suppressed because it is too large
Load Diff
+91
@@ -0,0 +1,91 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class QrGuestMenuAndBranchIdentity : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Source",
|
||||
table: "Orders",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "AllowBranchTaxOverride",
|
||||
table: "Cafes",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "DefaultTaxRate",
|
||||
table: "Cafes",
|
||||
type: "numeric",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "AccentColor",
|
||||
table: "Branches",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "LogoUrl",
|
||||
table: "Branches",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "TaxRate",
|
||||
table: "Branches",
|
||||
type: "numeric",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WelcomeText",
|
||||
table: "Branches",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Source",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AllowBranchTaxOverride",
|
||||
table: "Cafes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DefaultTaxRate",
|
||||
table: "Cafes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AccentColor",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LogoUrl",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TaxRate",
|
||||
table: "Branches");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WelcomeText",
|
||||
table: "Branches");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2210
File diff suppressed because it is too large
Load Diff
+200
@@ -0,0 +1,200 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class DeliveryPlatformIntegration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Orders_CafeId",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DeliveryMetaJson",
|
||||
table: "Orders",
|
||||
type: "character varying(4000)",
|
||||
maxLength: 4000,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "DeliveryPlatform",
|
||||
table: "Orders",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ExternalOrderId",
|
||||
table: "Orders",
|
||||
type: "character varying(120)",
|
||||
maxLength: 120,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "PlatformCommission",
|
||||
table: "Orders",
|
||||
type: "numeric(18,2)",
|
||||
precision: 18,
|
||||
scale: 2,
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "DefaultTaxRate",
|
||||
table: "Cafes",
|
||||
type: "numeric(5,2)",
|
||||
precision: 5,
|
||||
scale: 2,
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "numeric");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DigikalaVendorId",
|
||||
table: "Cafes",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Tap30VendorId",
|
||||
table: "Cafes",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DeliveryCommissionRates",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Platform = table.Column<int>(type: "integer", nullable: false),
|
||||
RatePercent = table.Column<decimal>(type: "numeric(5,2)", precision: 5, scale: 2, nullable: false),
|
||||
IsActive = table.Column<bool>(type: "boolean", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DeliveryCommissionRates", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_DeliveryCommissionRates_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WebhookLogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
CafeId = table.Column<string>(type: "text", nullable: true),
|
||||
Platform = table.Column<int>(type: "integer", nullable: false),
|
||||
RawBody = table.Column<string>(type: "text", nullable: false),
|
||||
SignatureHeader = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
SignatureValid = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Processed = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Success = table.Column<bool>(type: "boolean", nullable: false),
|
||||
ErrorMessage = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
|
||||
AttemptCount = table.Column<int>(type: "integer", nullable: false),
|
||||
ExternalOrderId = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: true),
|
||||
MeeziOrderId = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
|
||||
ProcessedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WebhookLogs", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_WebhookLogs_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_CafeId_DeliveryPlatform_ExternalOrderId",
|
||||
table: "Orders",
|
||||
columns: new[] { "CafeId", "DeliveryPlatform", "ExternalOrderId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeliveryCommissionRates_CafeId_Platform",
|
||||
table: "DeliveryCommissionRates",
|
||||
columns: new[] { "CafeId", "Platform" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WebhookLogs_CafeId",
|
||||
table: "WebhookLogs",
|
||||
column: "CafeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WebhookLogs_Platform_CreatedAt",
|
||||
table: "WebhookLogs",
|
||||
columns: new[] { "Platform", "CreatedAt" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DeliveryCommissionRates");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "WebhookLogs");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Orders_CafeId_DeliveryPlatform_ExternalOrderId",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DeliveryMetaJson",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DeliveryPlatform",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ExternalOrderId",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PlatformCommission",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DigikalaVendorId",
|
||||
table: "Cafes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Tap30VendorId",
|
||||
table: "Cafes");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "DefaultTaxRate",
|
||||
table: "Cafes",
|
||||
type: "numeric",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "numeric(5,2)",
|
||||
oldPrecision: 5,
|
||||
oldScale: 2);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_CafeId",
|
||||
table: "Orders",
|
||||
column: "CafeId");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2540
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SystemAdminPlatform : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsSuspended",
|
||||
table: "Cafes",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CafeFeatureOverrides",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
FeatureKey = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
|
||||
IsEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CafeFeatureOverrides", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CafeFeatureOverrides_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PlatformFeatures",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Key = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
|
||||
DisplayNameFa = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
DisplayNameEn = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
ModuleGroup = table.Column<string>(type: "character varying(60)", maxLength: 60, nullable: false),
|
||||
IsEnabledGlobally = table.Column<bool>(type: "boolean", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PlatformFeatures", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PlatformPlanDefinitions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Tier = table.Column<int>(type: "integer", nullable: false),
|
||||
DisplayNameFa = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
DisplayNameEn = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
MonthlyPriceToman = table.Column<decimal>(type: "numeric(18,0)", precision: 18, scale: 0, nullable: false),
|
||||
IsBillableOnline = table.Column<bool>(type: "boolean", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "boolean", nullable: false),
|
||||
SortOrder = table.Column<int>(type: "integer", nullable: false),
|
||||
LimitsJson = table.Column<string>(type: "character varying(4000)", maxLength: 4000, nullable: false),
|
||||
FeaturesJson = table.Column<string>(type: "character varying(4000)", maxLength: 4000, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PlatformPlanDefinitions", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PlatformSettings",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Key = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
|
||||
Value = table.Column<string>(type: "character varying(8000)", maxLength: 8000, nullable: false),
|
||||
Category = table.Column<string>(type: "character varying(60)", maxLength: 60, nullable: false),
|
||||
DescriptionFa = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PlatformSettings", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SupportTickets",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Subject = table.Column<string>(type: "character varying(300)", maxLength: 300, nullable: false),
|
||||
Status = table.Column<int>(type: "integer", nullable: false),
|
||||
Priority = table.Column<int>(type: "integer", nullable: false),
|
||||
CreatedByEmployeeId = table.Column<string>(type: "text", nullable: false),
|
||||
AssignedAdminId = table.Column<string>(type: "text", nullable: true),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
ClosedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SupportTickets", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SupportTickets_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_SupportTickets_Employees_CreatedByEmployeeId",
|
||||
column: x => x.CreatedByEmployeeId,
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SystemAdmins",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Phone = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
IsActive = table.Column<bool>(type: "boolean", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SystemAdmins", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SupportTicketMessages",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
TicketId = table.Column<string>(type: "text", nullable: false),
|
||||
SenderKind = table.Column<int>(type: "integer", nullable: false),
|
||||
SenderId = table.Column<string>(type: "text", nullable: false),
|
||||
Body = table.Column<string>(type: "character varying(8000)", maxLength: 8000, nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SupportTicketMessages", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SupportTicketMessages_SupportTickets_TicketId",
|
||||
column: x => x.TicketId,
|
||||
principalTable: "SupportTickets",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CafeFeatureOverrides_CafeId_FeatureKey",
|
||||
table: "CafeFeatureOverrides",
|
||||
columns: new[] { "CafeId", "FeatureKey" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlatformFeatures_Key",
|
||||
table: "PlatformFeatures",
|
||||
column: "Key",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlatformPlanDefinitions_Tier",
|
||||
table: "PlatformPlanDefinitions",
|
||||
column: "Tier",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PlatformSettings_Key",
|
||||
table: "PlatformSettings",
|
||||
column: "Key",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SupportTicketMessages_TicketId_CreatedAt",
|
||||
table: "SupportTicketMessages",
|
||||
columns: new[] { "TicketId", "CreatedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SupportTickets_CafeId_Status_UpdatedAt",
|
||||
table: "SupportTickets",
|
||||
columns: new[] { "CafeId", "Status", "UpdatedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SupportTickets_CreatedByEmployeeId",
|
||||
table: "SupportTickets",
|
||||
column: "CreatedByEmployeeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SystemAdmins_Phone",
|
||||
table: "SystemAdmins",
|
||||
column: "Phone",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CafeFeatureOverrides");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PlatformFeatures");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PlatformPlanDefinitions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PlatformSettings");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SupportTicketMessages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SystemAdmins");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SupportTickets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsSuspended",
|
||||
table: "Cafes");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2599
File diff suppressed because it is too large
Load Diff
+79
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class OrderNotificationsAndGuestTracking : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "GuestTrackingToken",
|
||||
table: "Orders",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "StatusUpdatedAt",
|
||||
table: "Orders",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CafeNotifications",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Type = table.Column<string>(type: "character varying(60)", maxLength: 60, nullable: false),
|
||||
Title = table.Column<string>(type: "character varying(300)", maxLength: 300, nullable: false),
|
||||
Body = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
ReferenceId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||
TableNumber = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: true),
|
||||
IsRead = table.Column<bool>(type: "boolean", nullable: false),
|
||||
ReadAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CafeNotifications", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_GuestTrackingToken",
|
||||
table: "Orders",
|
||||
column: "GuestTrackingToken");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CafeNotifications_CafeId_IsRead_CreatedAt",
|
||||
table: "CafeNotifications",
|
||||
columns: new[] { "CafeId", "IsRead", "CreatedAt" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CafeNotifications");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Orders_GuestTrackingToken",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GuestTrackingToken",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StatusUpdatedAt",
|
||||
table: "Orders");
|
||||
}
|
||||
}
|
||||
}
|
||||
src/Meezi.Infrastructure/Data/Migrations/20260522174742_MenuItemRecipesAndIngredientCost.Designer.cs
Generated
+2690
File diff suppressed because it is too large
Load Diff
+153
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MenuItemRecipesAndIngredientCost : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_StockMovements_CafeId",
|
||||
table: "StockMovements");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Kind",
|
||||
table: "StockMovements",
|
||||
type: "character varying(30)",
|
||||
maxLength: 30,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "OrderId",
|
||||
table: "StockMovements",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "LowStockWarningPercent",
|
||||
table: "Ingredients",
|
||||
type: "numeric(5,2)",
|
||||
precision: 5,
|
||||
scale: 2,
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "ParLevel",
|
||||
table: "Ingredients",
|
||||
type: "numeric(18,3)",
|
||||
precision: 18,
|
||||
scale: 3,
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "UnitCost",
|
||||
table: "Ingredients",
|
||||
type: "numeric(18,2)",
|
||||
precision: 18,
|
||||
scale: 2,
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MenuItemIngredients",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
MenuItemId = table.Column<string>(type: "text", nullable: false),
|
||||
IngredientId = table.Column<string>(type: "text", nullable: false),
|
||||
QuantityPerUnit = table.Column<decimal>(type: "numeric(18,3)", precision: 18, scale: 3, nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MenuItemIngredients", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_MenuItemIngredients_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_MenuItemIngredients_Ingredients_IngredientId",
|
||||
column: x => x.IngredientId,
|
||||
principalTable: "Ingredients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_MenuItemIngredients_MenuItems_MenuItemId",
|
||||
column: x => x.MenuItemId,
|
||||
principalTable: "MenuItems",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StockMovements_CafeId_OrderId",
|
||||
table: "StockMovements",
|
||||
columns: new[] { "CafeId", "OrderId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MenuItemIngredients_CafeId_MenuItemId_IngredientId",
|
||||
table: "MenuItemIngredients",
|
||||
columns: new[] { "CafeId", "MenuItemId", "IngredientId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MenuItemIngredients_IngredientId",
|
||||
table: "MenuItemIngredients",
|
||||
column: "IngredientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MenuItemIngredients_MenuItemId",
|
||||
table: "MenuItemIngredients",
|
||||
column: "MenuItemId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "MenuItemIngredients");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_StockMovements_CafeId_OrderId",
|
||||
table: "StockMovements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Kind",
|
||||
table: "StockMovements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "OrderId",
|
||||
table: "StockMovements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LowStockWarningPercent",
|
||||
table: "Ingredients");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ParLevel",
|
||||
table: "Ingredients");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UnitCost",
|
||||
table: "Ingredients");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StockMovements_CafeId",
|
||||
table: "StockMovements",
|
||||
column: "CafeId");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2684
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class EmployeePhoneUniquePerCafe : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_MenuItemIngredients_Cafes_CafeId",
|
||||
table: "MenuItemIngredients");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Employees_CafeId_Phone",
|
||||
table: "Employees");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Employees_CafeId_Phone",
|
||||
table: "Employees",
|
||||
columns: new[] { "CafeId", "Phone" },
|
||||
unique: true,
|
||||
filter: "\"DeletedAt\" IS NULL");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Employees_CafeId_Phone",
|
||||
table: "Employees");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Employees_CafeId_Phone",
|
||||
table: "Employees",
|
||||
columns: new[] { "CafeId", "Phone" });
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_MenuItemIngredients_Cafes_CafeId",
|
||||
table: "MenuItemIngredients",
|
||||
column: "CafeId",
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2688
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddMenuItemModel3d : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Model3dUrl",
|
||||
table: "MenuItems",
|
||||
type: "character varying(500)",
|
||||
maxLength: 500,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Model3dUrl",
|
||||
table: "MenuItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2691
File diff suppressed because it is too large
Load Diff
+29
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddSubscriptionPaymentProvider : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Provider",
|
||||
table: "SubscriptionPayments",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Provider",
|
||||
table: "SubscriptionPayments");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2695
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCafeDiscoverProfileJson : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DiscoverProfileJson",
|
||||
table: "Cafes",
|
||||
type: "character varying(8000)",
|
||||
maxLength: 8000,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DiscoverProfileJson",
|
||||
table: "Cafes");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2701
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class OrderDisplayNumber : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "DisplayNumber",
|
||||
table: "Orders",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
WITH numbered AS (
|
||||
SELECT "Id",
|
||||
ROW_NUMBER() OVER (PARTITION BY "CafeId" ORDER BY "CreatedAt", "Id") AS rn
|
||||
FROM "Orders"
|
||||
)
|
||||
UPDATE "Orders" o
|
||||
SET "DisplayNumber" = n.rn
|
||||
FROM numbered n
|
||||
WHERE o."Id" = n."Id";
|
||||
""");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_CafeId_DisplayNumber",
|
||||
table: "Orders",
|
||||
columns: new[] { "CafeId", "DisplayNumber" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Orders_CafeId_DisplayNumber",
|
||||
table: "Orders");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DisplayNumber",
|
||||
table: "Orders");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2713
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class StockMovementPurchaseCost : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "BranchId",
|
||||
table: "StockMovements",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ExpenseId",
|
||||
table: "StockMovements",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "TotalCostToman",
|
||||
table: "StockMovements",
|
||||
type: "numeric(18,2)",
|
||||
precision: 18,
|
||||
scale: 2,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BranchId",
|
||||
table: "StockMovements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ExpenseId",
|
||||
table: "StockMovements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TotalCostToman",
|
||||
table: "StockMovements");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2716
File diff suppressed because it is too large
Load Diff
+29
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class BranchScheduledPermanentDelete : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "ScheduledPermanentDeleteAt",
|
||||
table: "Branches",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ScheduledPermanentDeleteAt",
|
||||
table: "Branches");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2874
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class GrowthOpsFeatures : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "KitchenStationId",
|
||||
table: "MenuCategories",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DiscoverBadgesJson",
|
||||
table: "Cafes",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsHidden",
|
||||
table: "CafeReviews",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CafeReviewPhotos",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
ReviewId = table.Column<string>(type: "text", nullable: false),
|
||||
Url = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
|
||||
SortOrder = table.Column<int>(type: "integer", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CafeReviewPhotos", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CafeReviewPhotos_CafeReviews_ReviewId",
|
||||
column: x => x.ReviewId,
|
||||
principalTable: "CafeReviews",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ConsumerAccounts",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Phone = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ConsumerAccounts", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "KitchenStations",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
BranchId = table.Column<string>(type: "text", nullable: true),
|
||||
Name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
PrinterIp = table.Column<string>(type: "character varying(45)", maxLength: 45, nullable: true),
|
||||
PrinterPort = table.Column<int>(type: "integer", nullable: false),
|
||||
SortOrder = table.Column<int>(type: "integer", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CafeId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_KitchenStations", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_KitchenStations_Branches_BranchId",
|
||||
column: x => x.BranchId,
|
||||
principalTable: "Branches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_KitchenStations_Cafes_CafeId",
|
||||
column: x => x.CafeId,
|
||||
principalTable: "Cafes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MenuCategories_KitchenStationId",
|
||||
table: "MenuCategories",
|
||||
column: "KitchenStationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CafeReviewPhotos_ReviewId",
|
||||
table: "CafeReviewPhotos",
|
||||
column: "ReviewId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConsumerAccounts_Phone",
|
||||
table: "ConsumerAccounts",
|
||||
column: "Phone",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_KitchenStations_BranchId",
|
||||
table: "KitchenStations",
|
||||
column: "BranchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_KitchenStations_CafeId_SortOrder",
|
||||
table: "KitchenStations",
|
||||
columns: new[] { "CafeId", "SortOrder" });
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_MenuCategories_KitchenStations_KitchenStationId",
|
||||
table: "MenuCategories",
|
||||
column: "KitchenStationId",
|
||||
principalTable: "KitchenStations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_MenuCategories_KitchenStations_KitchenStationId",
|
||||
table: "MenuCategories");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "CafeReviewPhotos");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ConsumerAccounts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "KitchenStations");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_MenuCategories_KitchenStationId",
|
||||
table: "MenuCategories");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "KitchenStationId",
|
||||
table: "MenuCategories");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DiscoverBadgesJson",
|
||||
table: "Cafes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsHidden",
|
||||
table: "CafeReviews");
|
||||
}
|
||||
}
|
||||
}
|
||||
+3087
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddWebsiteCms : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DemoRequests",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
ContactName = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
BusinessName = table.Column<string>(type: "character varying(300)", maxLength: 300, nullable: false),
|
||||
Phone = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
Email = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
BranchCount = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
Notes = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
|
||||
Source = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false, defaultValue: "website"),
|
||||
Status = table.Column<int>(type: "integer", nullable: false),
|
||||
AdminNotes = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
|
||||
ContactedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DemoRequests", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WebsiteBlogPosts",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Slug = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
TitleFa = table.Column<string>(type: "character varying(400)", maxLength: 400, nullable: false),
|
||||
TitleEn = table.Column<string>(type: "character varying(400)", maxLength: 400, nullable: false),
|
||||
ExcerptFa = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: false),
|
||||
ExcerptEn = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: false),
|
||||
ContentFa = table.Column<string>(type: "text", nullable: false),
|
||||
ContentEn = table.Column<string>(type: "text", nullable: false),
|
||||
Author = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
CategoryFa = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
CategoryEn = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
TagsJson = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: false, defaultValue: "[]"),
|
||||
CoverImage = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
|
||||
IsPublished = table.Column<bool>(type: "boolean", nullable: false),
|
||||
PublishedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
ViewCount = table.Column<int>(type: "integer", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WebsiteBlogPosts", x => x.Id);
|
||||
table.UniqueConstraint("AK_WebsiteBlogPosts_Slug", x => x.Slug);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WebsiteComments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
PostSlug = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
AuthorName = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
AuthorEmail = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
Content = table.Column<string>(type: "character varying(3000)", maxLength: 3000, nullable: false),
|
||||
IsApproved = table.Column<bool>(type: "boolean", nullable: false),
|
||||
IpAddress = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WebsiteComments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_WebsiteComments_WebsiteBlogPosts_PostSlug",
|
||||
column: x => x.PostSlug,
|
||||
principalTable: "WebsiteBlogPosts",
|
||||
principalColumn: "Slug",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DemoRequests_Status_CreatedAt",
|
||||
table: "DemoRequests",
|
||||
columns: new[] { "Status", "CreatedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WebsiteBlogPosts_IsPublished_PublishedAt",
|
||||
table: "WebsiteBlogPosts",
|
||||
columns: new[] { "IsPublished", "PublishedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WebsiteBlogPosts_Slug",
|
||||
table: "WebsiteBlogPosts",
|
||||
column: "Slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WebsiteComments_PostSlug_IsApproved_CreatedAt",
|
||||
table: "WebsiteComments",
|
||||
columns: new[] { "PostSlug", "IsApproved", "CreatedAt" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DemoRequests");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "WebsiteComments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "WebsiteBlogPosts");
|
||||
}
|
||||
}
|
||||
}
|
||||
+3099
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Meezi.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCafePublicProfile : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "GalleryJson",
|
||||
table: "Cafes",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "InstagramHandle",
|
||||
table: "Cafes",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WebsiteUrl",
|
||||
table: "Cafes",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WorkingHoursJson",
|
||||
table: "Cafes",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GalleryJson",
|
||||
table: "Cafes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "InstagramHandle",
|
||||
table: "Cafes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WebsiteUrl",
|
||||
table: "Cafes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WorkingHoursJson",
|
||||
table: "Cafes");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,320 @@
|
||||
using System.Text.Json;
|
||||
using Meezi.Core.Constants;
|
||||
using Meezi.Core.Entities;
|
||||
using Meezi.Core.Enums;
|
||||
using Meezi.Core.Platform;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
public static class PlatformDataSeeder
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||
|
||||
public static async Task SeedAsync(IServiceProvider services)
|
||||
{
|
||||
var env = services.GetRequiredService<IHostEnvironment>();
|
||||
if (!env.IsDevelopment())
|
||||
return;
|
||||
|
||||
var logger = services.GetRequiredService<ILoggerFactory>().CreateLogger("PlatformDataSeeder");
|
||||
await using var scope = services.CreateAsyncScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
await EnsureCatalogUpgradesAsync(db, logger);
|
||||
|
||||
if (!env.IsDevelopment())
|
||||
return;
|
||||
|
||||
await SeedSystemAdminAsync(db, logger);
|
||||
await SeedPlansAsync(db, logger);
|
||||
await SeedFeaturesAsync(db, logger);
|
||||
await SeedSettingsAsync(db, logger);
|
||||
await EnsureIntegrationSettingsAsync(db, logger);
|
||||
}
|
||||
|
||||
/// <summary>Idempotent plan/feature upgrades for all environments (including production).</summary>
|
||||
public static async Task EnsureCatalogUpgradesAsync(IServiceProvider services)
|
||||
{
|
||||
await using var scope = services.CreateAsyncScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("PlatformDataSeeder");
|
||||
await EnsureCatalogUpgradesAsync(db, logger);
|
||||
}
|
||||
|
||||
private static async Task EnsureCatalogUpgradesAsync(AppDbContext db, ILogger logger)
|
||||
{
|
||||
var featureAdds = new[]
|
||||
{
|
||||
("menu_3d", "منوی سهبعدی", "3D menu", "growth"),
|
||||
("menu_3d_ai", "تولید ۳D با هوش مصنوعی", "AI 3D menu", "growth"),
|
||||
("discover_profile", "پروفایل کشف", "Discover profile", "growth")
|
||||
};
|
||||
|
||||
var existingKeys = await db.PlatformFeatures.Select(f => f.Key).ToListAsync();
|
||||
var newFeatures = featureAdds
|
||||
.Where(f => !existingKeys.Contains(f.Item1))
|
||||
.Select(f => F(f.Item1, f.Item2, f.Item3, f.Item4))
|
||||
.ToList();
|
||||
if (newFeatures.Count > 0)
|
||||
{
|
||||
db.PlatformFeatures.AddRange(newFeatures);
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Platform upgrade: added {Count} features", newFeatures.Count);
|
||||
}
|
||||
|
||||
var plans = await db.PlatformPlanDefinitions.ToListAsync();
|
||||
var changed = 0;
|
||||
foreach (var plan in plans)
|
||||
{
|
||||
if (plan.Tier is PlanTier.Free or PlanTier.Enterprise)
|
||||
continue;
|
||||
|
||||
var keys = plan.Tier == PlanTier.Business || plan.Tier == PlanTier.Enterprise
|
||||
? new[] { "menu_3d", "menu_3d_ai", "discover_profile" }
|
||||
: new[] { "menu_3d", "discover_profile" };
|
||||
var merged = MergeFeaturesJson(plan.FeaturesJson ?? "[]", keys);
|
||||
if (merged == plan.FeaturesJson) continue;
|
||||
plan.FeaturesJson = merged;
|
||||
changed++;
|
||||
}
|
||||
|
||||
if (changed > 0)
|
||||
{
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Platform upgrade: updated features on {Count} plans", changed);
|
||||
}
|
||||
|
||||
await EnsureIntegrationSettingsAsync(db, logger);
|
||||
}
|
||||
|
||||
private static string MergeFeaturesJson(string json, params string[] keys)
|
||||
{
|
||||
var list = JsonSerializer.Deserialize<List<string>>(json, JsonOpts) ?? [];
|
||||
if (list.Contains("*"))
|
||||
return json;
|
||||
|
||||
var updated = false;
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (!list.Contains(key))
|
||||
{
|
||||
list.Add(key);
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return updated ? JsonSerializer.Serialize(list, JsonOpts) : json;
|
||||
}
|
||||
|
||||
private static async Task EnsureIntegrationSettingsAsync(AppDbContext db, ILogger logger)
|
||||
{
|
||||
var defaults = new[]
|
||||
{
|
||||
S("payment.activeGateway", "zarinpal", "payment", "درگاه پیشفرض اشتراک"),
|
||||
S("payment.zarinpal.enabled", "true", "payment", "فعال زرینپال"),
|
||||
S("payment.zarinpal.sandbox", "true", "payment", "حالت تست زرینپال"),
|
||||
S("payment.tara.enabled", "false", "payment", "فعال تارا"),
|
||||
S("payment.tara.sandbox", "true", "payment", "حالت تست تارا"),
|
||||
S("payment.snapppay.enabled", "false", "payment", "فعال اسنپپی"),
|
||||
S("payment.snapppay.sandbox", "true", "payment", "حالت تست اسنپپی"),
|
||||
S("payment.nextpay.enabled", "false", "payment", "فعال نکستپی"),
|
||||
S("payment.nextpay.sandbox", "true", "payment", "حالت تست نکستپی"),
|
||||
S("payment.vandar.enabled", "false", "payment", "فعال وندار"),
|
||||
S("payment.vandar.sandbox", "true", "payment", "حالت تست وندار"),
|
||||
S("integrations.kavenegar.enabled", "true", "integrations", "فعال کاوهنگار"),
|
||||
S("integrations.kavenegar.otpTemplate", "verify", "integrations", "قالب OTP"),
|
||||
S("integrations.openai.enabled", "false", "integrations", "فعال OpenAI"),
|
||||
S("integrations.openai.model", "gpt-4o-mini", "integrations", "مدل OpenAI"),
|
||||
S("integrations.openai.coffeeAdvisor.enabled", "true", "integrations", "مشاور قهوه"),
|
||||
S("integrations.meshy.enabled", "false", "integrations", "فعال Meshy"),
|
||||
S("integrations.meshy.menu3d.enabled", "true", "integrations", "ساخت ۳D منو")
|
||||
};
|
||||
|
||||
var existing = await db.PlatformSettings.Select(s => s.Key).ToListAsync();
|
||||
var missing = defaults.Where(d => !existing.Contains(d.Key)).ToList();
|
||||
if (missing.Count == 0) return;
|
||||
|
||||
db.PlatformSettings.AddRange(missing);
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Platform seed: added {Count} integration settings", missing.Count);
|
||||
}
|
||||
|
||||
private static async Task SeedSystemAdminAsync(AppDbContext db, ILogger logger)
|
||||
{
|
||||
const string phone = "09120000001";
|
||||
if (await db.SystemAdmins.AnyAsync(a => a.Phone == phone))
|
||||
return;
|
||||
|
||||
db.SystemAdmins.Add(new SystemAdmin
|
||||
{
|
||||
Id = "sysadmin_demo",
|
||||
Name = "مدیر سامانه",
|
||||
Phone = phone,
|
||||
IsActive = true
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Platform seed: system admin phone {Phone}", phone);
|
||||
}
|
||||
|
||||
private static async Task SeedPlansAsync(AppDbContext db, ILogger logger)
|
||||
{
|
||||
if (await db.PlatformPlanDefinitions.AnyAsync())
|
||||
return;
|
||||
|
||||
var plans = new[]
|
||||
{
|
||||
new PlatformPlanDefinition
|
||||
{
|
||||
Id = "plan_free",
|
||||
Tier = PlanTier.Free,
|
||||
DisplayNameFa = "رایگان",
|
||||
DisplayNameEn = "Free",
|
||||
MonthlyPriceToman = 0,
|
||||
IsBillableOnline = false,
|
||||
SortOrder = 0,
|
||||
LimitsJson = JsonSerializer.Serialize(PlanLimitsData.ForTier(PlanTier.Free), JsonOpts),
|
||||
FeaturesJson = JsonSerializer.Serialize(new[] { "pos", "menu", "tables", "qr_menu" }, JsonOpts)
|
||||
},
|
||||
new PlatformPlanDefinition
|
||||
{
|
||||
Id = "plan_pro",
|
||||
Tier = PlanTier.Pro,
|
||||
DisplayNameFa = "حرفهای",
|
||||
DisplayNameEn = "Pro",
|
||||
MonthlyPriceToman = PlanPricing.MonthlyToman(PlanTier.Pro),
|
||||
IsBillableOnline = true,
|
||||
SortOrder = 1,
|
||||
LimitsJson = JsonSerializer.Serialize(PlanLimitsData.ForTier(PlanTier.Pro), JsonOpts),
|
||||
FeaturesJson = JsonSerializer.Serialize(new[]
|
||||
{
|
||||
"pos", "menu", "tables", "qr_menu", "crm", "coupons", "reports", "kds", "inventory",
|
||||
"menu_3d", "discover_profile"
|
||||
}, JsonOpts)
|
||||
},
|
||||
new PlatformPlanDefinition
|
||||
{
|
||||
Id = "plan_business",
|
||||
Tier = PlanTier.Business,
|
||||
DisplayNameFa = "کسبوکار",
|
||||
DisplayNameEn = "Business",
|
||||
MonthlyPriceToman = PlanPricing.MonthlyToman(PlanTier.Business),
|
||||
IsBillableOnline = true,
|
||||
SortOrder = 2,
|
||||
LimitsJson = JsonSerializer.Serialize(PlanLimitsData.ForTier(PlanTier.Business), JsonOpts),
|
||||
FeaturesJson = JsonSerializer.Serialize(new[]
|
||||
{
|
||||
"pos", "menu", "tables", "qr_menu", "crm", "coupons", "reports", "kds", "inventory",
|
||||
"hr", "sms", "reservations", "delivery", "expenses", "branches",
|
||||
"menu_3d", "menu_3d_ai", "discover_profile"
|
||||
}, JsonOpts)
|
||||
},
|
||||
new PlatformPlanDefinition
|
||||
{
|
||||
Id = "plan_enterprise",
|
||||
Tier = PlanTier.Enterprise,
|
||||
DisplayNameFa = "سازمانی",
|
||||
DisplayNameEn = "Enterprise",
|
||||
MonthlyPriceToman = 0,
|
||||
IsBillableOnline = false,
|
||||
SortOrder = 3,
|
||||
LimitsJson = JsonSerializer.Serialize(PlanLimitsData.ForTier(PlanTier.Enterprise), JsonOpts),
|
||||
FeaturesJson = JsonSerializer.Serialize(new[] { "*" }, JsonOpts)
|
||||
}
|
||||
};
|
||||
|
||||
db.PlatformPlanDefinitions.AddRange(plans);
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Platform seed: {Count} subscription plans", plans.Length);
|
||||
}
|
||||
|
||||
private static async Task SeedFeaturesAsync(AppDbContext db, ILogger logger)
|
||||
{
|
||||
if (await db.PlatformFeatures.AnyAsync())
|
||||
return;
|
||||
|
||||
var features = new[]
|
||||
{
|
||||
F("pos", "صندوق", "POS", "core"),
|
||||
F("menu", "منو", "Menu", "core"),
|
||||
F("tables", "میزها", "Tables", "core"),
|
||||
F("qr_menu", "منوی QR", "QR menu", "core"),
|
||||
F("kds", "آشپزخانه", "KDS", "operations"),
|
||||
F("crm", "مشتریان", "CRM", "growth"),
|
||||
F("coupons", "کوپن", "Coupons", "growth"),
|
||||
F("reports", "گزارشها", "Reports", "analytics"),
|
||||
F("inventory", "انبار", "Inventory", "operations"),
|
||||
F("hr", "منابع انسانی", "HR", "operations"),
|
||||
F("sms", "پیامک", "SMS", "growth"),
|
||||
F("reservations", "رزرو", "Reservations", "growth"),
|
||||
F("delivery", "پذیرش آنلاین", "Delivery", "integrations"),
|
||||
F("expenses", "هزینهها", "Expenses", "analytics"),
|
||||
F("branches", "چند شعبه", "Branches", "core"),
|
||||
F("taxes", "مالیات", "Taxes", "compliance"),
|
||||
F("reviews", "نظرات", "Reviews", "growth"),
|
||||
F("queue", "صف", "Queue", "operations"),
|
||||
F("menu_3d", "منوی سهبعدی", "3D menu", "growth"),
|
||||
F("menu_3d_ai", "تولید ۳D با هوش مصنوعی", "AI 3D menu", "growth"),
|
||||
F("discover_profile", "پروفایل کشف", "Discover profile", "growth")
|
||||
};
|
||||
|
||||
db.PlatformFeatures.AddRange(features);
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Platform seed: {Count} feature flags", features.Length);
|
||||
}
|
||||
|
||||
private static PlatformFeature F(string key, string fa, string en, string group) => new()
|
||||
{
|
||||
Id = $"feat_{key}",
|
||||
Key = key,
|
||||
DisplayNameFa = fa,
|
||||
DisplayNameEn = en,
|
||||
ModuleGroup = group,
|
||||
IsEnabledGlobally = true
|
||||
};
|
||||
|
||||
private static async Task SeedSettingsAsync(AppDbContext db, ILogger logger)
|
||||
{
|
||||
if (await db.PlatformSettings.AnyAsync())
|
||||
return;
|
||||
|
||||
var settings = new[]
|
||||
{
|
||||
S("app.name", "میزی", "branding", "نام اپلیکیشن"),
|
||||
S("app.tagline", "میزت منتظرته", "branding", "شعار"),
|
||||
S("auth.maxOtpPerHour", "5", "auth", "حداکثر OTP در ساعت"),
|
||||
S("billing.zarinpalSandbox", "true", "billing", "درگاه تست زرینپال"),
|
||||
S("support.autoCloseDays", "14", "support", "بستن خودکار تیکت پس از روز"),
|
||||
S("payment.activeGateway", "zarinpal", "payment", "درگاه فعال اشتراک"),
|
||||
S("payment.zarinpal.enabled", "true", "payment", "فعال زرینپال"),
|
||||
S("payment.zarinpal.sandbox", "true", "payment", "حالت تست زرینپال"),
|
||||
S("payment.nextpay.enabled", "false", "payment", "فعال نکستپی"),
|
||||
S("payment.nextpay.sandbox", "true", "payment", "حالت تست نکستپی"),
|
||||
S("payment.vandar.enabled", "false", "payment", "فعال وندار"),
|
||||
S("payment.vandar.sandbox", "true", "payment", "حالت تست وندار"),
|
||||
S("integrations.kavenegar.enabled", "true", "integrations", "فعال کاوهنگار"),
|
||||
S("integrations.kavenegar.otpTemplate", "verify", "integrations", "قالب OTP"),
|
||||
S("integrations.openai.enabled", "false", "integrations", "فعال OpenAI"),
|
||||
S("integrations.openai.model", "gpt-4o-mini", "integrations", "مدل OpenAI"),
|
||||
S("integrations.openai.coffeeAdvisor.enabled", "true", "integrations", "مشاور قهوه"),
|
||||
S("integrations.meshy.enabled", "false", "integrations", "فعال Meshy"),
|
||||
S("integrations.meshy.menu3d.enabled", "true", "integrations", "ساخت ۳D منو")
|
||||
};
|
||||
|
||||
db.PlatformSettings.AddRange(settings);
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Platform seed: {Count} platform settings", settings.Length);
|
||||
}
|
||||
|
||||
private static PlatformSetting S(string key, string value, string category, string desc) => new()
|
||||
{
|
||||
Id = $"cfg_{key.Replace('.', '_')}",
|
||||
Key = key,
|
||||
Value = value,
|
||||
Category = category,
|
||||
DescriptionFa = desc
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Meezi.Core.Enums;
|
||||
using Meezi.Core.Interfaces;
|
||||
|
||||
namespace Meezi.Infrastructure.Data;
|
||||
|
||||
public class TenantContext : ITenantContext
|
||||
{
|
||||
public string? UserId { get; set; }
|
||||
public string? CafeId { get; set; }
|
||||
public EmployeeRole? Role { get; set; }
|
||||
public PlanTier? PlanTier { get; set; }
|
||||
public string? Language { get; set; }
|
||||
public string? BranchId { get; set; }
|
||||
public bool IsSystemAdmin { get; set; }
|
||||
public bool IsAuthenticated => IsSystemAdmin || !string.IsNullOrEmpty(CafeId);
|
||||
}
|
||||
Reference in New Issue
Block a user