606 lines
31 KiB
C#
606 lines
31 KiB
C#
|
|
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);
|
||
|
|
});
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|