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,12 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class Attendance : BaseEntity
|
||||
{
|
||||
public string EmployeeId { get; set; } = string.Empty;
|
||||
public DateOnly Date { get; set; }
|
||||
public DateTime? ClockIn { get; set; }
|
||||
public DateTime? ClockOut { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public Employee Employee { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public abstract class BaseEntity
|
||||
{
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString("N");
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
}
|
||||
|
||||
public abstract class TenantEntity : BaseEntity
|
||||
{
|
||||
public string CafeId { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Physical branch (شعبه) under a café tenant.</summary>
|
||||
public class Branch : TenantEntity
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Address { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>When set with <see cref="BaseEntity.DeletedAt"/>, branch can be restored until this UTC time.</summary>
|
||||
public DateTime? ScheduledPermanentDeleteAt { get; set; }
|
||||
|
||||
// Thermal printer (TCP ESC/POS)
|
||||
public string? ReceiptPrinterIp { get; set; }
|
||||
public int? ReceiptPrinterPort { get; set; }
|
||||
public string? KitchenPrinterIp { get; set; }
|
||||
public int? KitchenPrinterPort { get; set; }
|
||||
public int PaperWidthMm { get; set; } = 80;
|
||||
public bool AutoCutEnabled { get; set; } = true;
|
||||
public string? ReceiptHeader { get; set; }
|
||||
public string? ReceiptFooter { get; set; }
|
||||
public string? WifiPassword { get; set; }
|
||||
/// <summary>Branch-specific logo on QR guest menu (falls back to café logo).</summary>
|
||||
public string? LogoUrl { get; set; }
|
||||
public string? WelcomeText { get; set; }
|
||||
public string? AccentColor { get; set; }
|
||||
/// <summary>Branch tax % when café <see cref="Cafe.AllowBranchTaxOverride"/> is true.</summary>
|
||||
public decimal? TaxRate { get; set; }
|
||||
|
||||
// Card POS terminal (HTTP bridge on local network)
|
||||
public string? PosDeviceIp { get; set; }
|
||||
public int? PosDevicePort { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public ICollection<TableSection> Sections { get; set; } = [];
|
||||
public ICollection<Table> Tables { get; set; } = [];
|
||||
public ICollection<Order> Orders { get; set; } = [];
|
||||
public ICollection<Employee> Staff { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Per-branch availability and price override for a catalog menu item.</summary>
|
||||
public class BranchMenuItemOverride : TenantEntity
|
||||
{
|
||||
public string BranchId { get; set; } = string.Empty;
|
||||
public string MenuItemId { get; set; } = string.Empty;
|
||||
/// <summary>false = hidden at this branch.</summary>
|
||||
public bool IsAvailable { get; set; } = true;
|
||||
/// <summary>null = use MenuItem.Price.</summary>
|
||||
public decimal? PriceOverride { get; set; }
|
||||
public int? SortOrderOverride { get; set; }
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public string? UpdatedByUserId { get; set; }
|
||||
|
||||
public Branch Branch { get; set; } = null!;
|
||||
public MenuItem MenuItem { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class Cafe : BaseEntity
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? NameAr { get; set; }
|
||||
public string? NameEn { get; set; }
|
||||
public string Slug { get; set; } = string.Empty;
|
||||
public string? Phone { get; set; }
|
||||
public string? Address { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? LogoUrl { get; set; }
|
||||
public string? CoverImageUrl { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public PlanTier PlanTier { get; set; } = PlanTier.Free;
|
||||
public DateTime? PlanExpiresAt { get; set; }
|
||||
public bool IsVerified { get; set; }
|
||||
/// <summary>When true, merchant API access is blocked until reactivated by platform admin.</summary>
|
||||
public bool IsSuspended { get; set; }
|
||||
public string? SnappfoodVendorId { get; set; }
|
||||
public string? Tap30VendorId { get; set; }
|
||||
public string? DigikalaVendorId { get; set; }
|
||||
public string PreferredLanguage { get; set; } = "fa";
|
||||
/// <summary>JSON <see cref="Branding.CafeTheme"/> for dashboard + guest menu colors/styles.</summary>
|
||||
public string? ThemeJson { get; set; }
|
||||
/// <summary>JSON <see cref="Discover.CafeDiscoverProfile"/> for discover / AI matching.</summary>
|
||||
public string? DiscoverProfileJson { get; set; }
|
||||
/// <summary>JSON array of <see cref="Discover.CafeBadgeCatalog"/> keys (Enterprise, admin-assigned).</summary>
|
||||
public string? DiscoverBadgesJson { get; set; }
|
||||
/// <summary>JSON array of up to 8 gallery photo URLs (owner-managed).</summary>
|
||||
public string? GalleryJson { get; set; }
|
||||
/// <summary>JSON <see cref="Discover.WorkingHoursSchedule"/> per-day schedule.</summary>
|
||||
public string? WorkingHoursJson { get; set; }
|
||||
/// <summary>Instagram handle without @, max 80 chars.</summary>
|
||||
public string? InstagramHandle { get; set; }
|
||||
/// <summary>Cafe website URL, max 300 chars.</summary>
|
||||
public string? WebsiteUrl { get; set; }
|
||||
/// <summary>Default VAT/sales tax % for all branches unless branch override is allowed.</summary>
|
||||
public decimal DefaultTaxRate { get; set; } = 9m;
|
||||
public bool AllowBranchTaxOverride { get; set; }
|
||||
|
||||
public ICollection<Branch> Branches { get; set; } = [];
|
||||
public ICollection<Table> Tables { get; set; } = [];
|
||||
public ICollection<Employee> Employees { get; set; } = [];
|
||||
public ICollection<MenuCategory> MenuCategories { get; set; } = [];
|
||||
public ICollection<MenuItem> MenuItems { get; set; } = [];
|
||||
public ICollection<Order> Orders { get; set; } = [];
|
||||
public ICollection<Customer> Customers { get; set; } = [];
|
||||
public ICollection<Coupon> Coupons { get; set; } = [];
|
||||
public ICollection<Tax> Taxes { get; set; } = [];
|
||||
public ICollection<CafeReview> Reviews { get; set; } = [];
|
||||
public ICollection<SubscriptionPayment> SubscriptionPayments { get; set; } = [];
|
||||
public ICollection<Ingredient> Ingredients { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class CafeFeatureOverride : TenantEntity
|
||||
{
|
||||
public string FeatureKey { get; set; } = string.Empty;
|
||||
public bool IsEnabled { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class CafeNotification : TenantEntity
|
||||
{
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string? Body { get; set; }
|
||||
public string? ReferenceId { get; set; }
|
||||
public string? TableNumber { get; set; }
|
||||
public bool IsRead { get; set; }
|
||||
public DateTime? ReadAt { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class CafeReview : BaseEntity
|
||||
{
|
||||
public string CafeId { get; set; } = string.Empty;
|
||||
public string AuthorName { get; set; } = string.Empty;
|
||||
public string? AuthorPhone { get; set; }
|
||||
public int Rating { get; set; }
|
||||
public string? Comment { get; set; }
|
||||
public string? OwnerReply { get; set; }
|
||||
public DateTime? OwnerRepliedAt { get; set; }
|
||||
public bool IsHidden { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public ICollection<CafeReviewPhoto> Photos { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class CafeReviewPhoto : BaseEntity
|
||||
{
|
||||
public string ReviewId { get; set; } = string.Empty;
|
||||
public string Url { get; set; } = string.Empty;
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
public CafeReview Review { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class CashTransaction : TenantEntity
|
||||
{
|
||||
public string ShiftId { get; set; } = string.Empty;
|
||||
public string? BranchId { get; set; }
|
||||
public CashTransactionType Type { get; set; }
|
||||
public PaymentMethod Method { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string? ReferenceId { get; set; }
|
||||
public string? Note { get; set; }
|
||||
public string CreatedByUserId { get; set; } = string.Empty;
|
||||
|
||||
public Shift Shift { get; set; } = null!;
|
||||
public Branch? Branch { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Platform guest account (OTP login) for order history across cafés.</summary>
|
||||
public class ConsumerAccount : BaseEntity
|
||||
{
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class Coupon : TenantEntity
|
||||
{
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public CouponType Type { get; set; }
|
||||
public decimal Value { get; set; }
|
||||
public decimal? MinOrderAmount { get; set; }
|
||||
public decimal? MaxDiscount { get; set; }
|
||||
public int? UsageLimit { get; set; }
|
||||
public int UsedCount { get; set; }
|
||||
public CustomerGroup? TargetGroup { get; set; }
|
||||
public DateTime? StartsAt { get; set; }
|
||||
public DateTime? ExpiresAt { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public ICollection<Order> Orders { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class Customer : TenantEntity
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
public string? NationalId { get; set; }
|
||||
public string? BirthDateJalali { get; set; }
|
||||
public CustomerGroup Group { get; set; } = CustomerGroup.Regular;
|
||||
public int LoyaltyPoints { get; set; }
|
||||
public string? ReferredBy { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public ICollection<Order> Orders { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Pre-aggregated daily metrics per branch for fast dashboard reads.</summary>
|
||||
public class DailyReport : TenantEntity
|
||||
{
|
||||
public string BranchId { get; set; } = string.Empty;
|
||||
public DateOnly Date { get; set; }
|
||||
public decimal TotalRevenue { get; set; }
|
||||
public decimal CashRevenue { get; set; }
|
||||
public decimal CardRevenue { get; set; }
|
||||
public decimal CreditRevenue { get; set; }
|
||||
public int TotalOrders { get; set; }
|
||||
public decimal AvgOrderValue { get; set; }
|
||||
public int TotalVoids { get; set; }
|
||||
public decimal VoidAmount { get; set; }
|
||||
public decimal TotalExpenses { get; set; }
|
||||
public decimal NetIncome { get; set; }
|
||||
public List<TopProductEntry> TopProducts { get; set; } = [];
|
||||
public DateTime GeneratedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public Branch Branch { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Configurable platform commission % per café (falls back to app defaults).</summary>
|
||||
public class DeliveryCommissionRate : TenantEntity
|
||||
{
|
||||
public DeliveryPlatform Platform { get; set; }
|
||||
public decimal RatePercent { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class DemoRequest : BaseEntity
|
||||
{
|
||||
public string ContactName { get; set; } = string.Empty;
|
||||
public string BusinessName { get; set; } = string.Empty;
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
public string? Email { get; set; }
|
||||
public string BranchCount { get; set; } = "1";
|
||||
public string? Notes { get; set; }
|
||||
public string Source { get; set; } = "website";
|
||||
public DemoRequestStatus Status { get; set; } = DemoRequestStatus.New;
|
||||
public string? AdminNotes { get; set; }
|
||||
public DateTime? ContactedAt { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class Employee : TenantEntity
|
||||
{
|
||||
public string? BranchId { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
public string? NationalId { get; set; }
|
||||
public EmployeeRole Role { get; set; }
|
||||
public decimal BaseSalary { get; set; }
|
||||
public string? PinCode { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public Branch? Branch { get; set; }
|
||||
public ICollection<Order> Orders { get; set; } = [];
|
||||
public ICollection<EmployeeSalary> Salaries { get; set; } = [];
|
||||
public ICollection<Attendance> Attendances { get; set; } = [];
|
||||
public ICollection<EmployeeSchedule> Schedules { get; set; } = [];
|
||||
public ICollection<LeaveRequest> LeaveRequests { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class EmployeeSalary : BaseEntity
|
||||
{
|
||||
public string EmployeeId { get; set; } = string.Empty;
|
||||
public string MonthYear { get; set; } = string.Empty;
|
||||
public decimal BaseSalary { get; set; }
|
||||
public decimal OvertimePay { get; set; }
|
||||
public decimal Deductions { get; set; }
|
||||
public decimal NetSalary { get; set; }
|
||||
public bool IsPaid { get; set; }
|
||||
|
||||
public Employee Employee { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Weekly HR work schedule for an employee (not cash register).</summary>
|
||||
public class EmployeeSchedule : BaseEntity
|
||||
{
|
||||
public string EmployeeId { get; set; } = string.Empty;
|
||||
public int DayOfWeek { get; set; }
|
||||
public ShiftType ShiftType { get; set; }
|
||||
|
||||
public Employee Employee { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class Expense : TenantEntity
|
||||
{
|
||||
public string BranchId { get; set; } = string.Empty;
|
||||
public string? ShiftId { get; set; }
|
||||
public ExpenseCategory Category { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string? Note { get; set; }
|
||||
public string? ReceiptImageUrl { get; set; }
|
||||
public string CreatedByUserId { get; set; } = string.Empty;
|
||||
|
||||
public Branch Branch { get; set; } = null!;
|
||||
public Shift? Shift { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class Ingredient : TenantEntity
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Unit { get; set; } = "عدد";
|
||||
public decimal QuantityOnHand { get; set; }
|
||||
public decimal ReorderLevel { get; set; }
|
||||
/// <summary>Cost per unit (Toman) for COGS reporting.</summary>
|
||||
public decimal UnitCost { get; set; }
|
||||
/// <summary>Target / full stock level used with warning percent (e.g. 500 g).</summary>
|
||||
public decimal ParLevel { get; set; }
|
||||
/// <summary>Warn when on-hand falls below ParLevel × (this ÷ 100). Default 20 = 20%.</summary>
|
||||
public decimal LowStockWarningPercent { get; set; } = 20m;
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public ICollection<StockMovement> Movements { get; set; } = [];
|
||||
public ICollection<MenuItemIngredient> MenuItemRecipes { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Kitchen/bar print station — routes category items to a dedicated printer.</summary>
|
||||
public class KitchenStation : TenantEntity
|
||||
{
|
||||
public string? BranchId { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? PrinterIp { get; set; }
|
||||
public int PrinterPort { get; set; } = 9100;
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public Branch? Branch { get; set; }
|
||||
public ICollection<MenuCategory> Categories { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class LeaveRequest : BaseEntity
|
||||
{
|
||||
public string EmployeeId { get; set; } = string.Empty;
|
||||
public DateOnly StartDate { get; set; }
|
||||
public DateOnly EndDate { get; set; }
|
||||
public string? Reason { get; set; }
|
||||
public LeaveStatus Status { get; set; } = LeaveStatus.Pending;
|
||||
public string? ReviewedBy { get; set; }
|
||||
|
||||
public Employee Employee { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class MenuCategory : TenantEntity
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? NameAr { get; set; }
|
||||
public string? NameEn { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public string? TaxId { get; set; }
|
||||
public decimal DiscountPercent { get; set; }
|
||||
/// <summary>Emoji or short icon label shown when no preset/image is set.</summary>
|
||||
public string? Icon { get; set; }
|
||||
/// <summary>Preset icon id (see CategoryIconPresets.PresetId).</summary>
|
||||
public string? IconPresetId { get; set; }
|
||||
/// <summary>Preset visual style: flat, modern, real, minimal, outline.</summary>
|
||||
public string? IconStyle { get; set; }
|
||||
public string? ImageUrl { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public string? KitchenStationId { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public KitchenStation? KitchenStation { get; set; }
|
||||
public Tax? Tax { get; set; }
|
||||
public ICollection<MenuItem> MenuItems { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class MenuItem : TenantEntity
|
||||
{
|
||||
public string CategoryId { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? NameAr { get; set; }
|
||||
public string? NameEn { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
/// <summary>Item-level promo (0–100). Takes precedence over category discount when > 0.</summary>
|
||||
public decimal DiscountPercent { get; set; }
|
||||
public string? ImageUrl { get; set; }
|
||||
public string? VideoUrl { get; set; }
|
||||
/// <summary>GLB/GLTF 3D model for QR menu interactive view (poster = ImageUrl).</summary>
|
||||
public string? Model3dUrl { get; set; }
|
||||
public bool IsAvailable { get; set; } = true;
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public MenuCategory Category { get; set; } = null!;
|
||||
public ICollection<OrderItem> OrderItems { get; set; } = [];
|
||||
public ICollection<BranchMenuItemOverride> BranchOverrides { get; set; } = [];
|
||||
public ICollection<MenuItemIngredient> RecipeIngredients { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Bill of materials: how much of an ingredient each menu item consumes per unit sold.</summary>
|
||||
public class MenuItemIngredient : TenantEntity
|
||||
{
|
||||
public string MenuItemId { get; set; } = string.Empty;
|
||||
public string IngredientId { get; set; } = string.Empty;
|
||||
/// <summary>Amount of ingredient used per 1 sold unit (e.g. 10 g coffee per espresso).</summary>
|
||||
public decimal QuantityPerUnit { get; set; }
|
||||
|
||||
public MenuItem MenuItem { get; set; } = null!;
|
||||
public Ingredient Ingredient { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class Order : TenantEntity
|
||||
{
|
||||
public string? BranchId { get; set; }
|
||||
public string? TableId { get; set; }
|
||||
public string? ReservationId { get; set; }
|
||||
/// <summary>Walk-in label at POS when no CRM customer is linked.</summary>
|
||||
public string? GuestName { get; set; }
|
||||
public string? GuestPhone { get; set; }
|
||||
public string? CustomerId { get; set; }
|
||||
public string? EmployeeId { get; set; }
|
||||
public OrderType OrderType { get; set; }
|
||||
public OrderSource Source { get; set; } = OrderSource.Pos;
|
||||
public OrderStatus Status { get; set; } = OrderStatus.Pending;
|
||||
/// <summary>Human-facing order number (digits only), unique per cafe.</summary>
|
||||
public int DisplayNumber { get; set; }
|
||||
public DateTime StatusUpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
/// <summary>Secret token for guest order tracking (QR) without login.</summary>
|
||||
public string? GuestTrackingToken { get; set; }
|
||||
public string? CouponId { get; set; }
|
||||
public decimal DiscountAmount { get; set; }
|
||||
public decimal Subtotal { get; set; }
|
||||
public decimal TaxTotal { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
public string? SnappfoodOrderId { get; set; }
|
||||
/// <summary>External order id from delivery platform (Snappfood, Tap30, etc.).</summary>
|
||||
public string? ExternalOrderId { get; set; }
|
||||
public DeliveryPlatform? DeliveryPlatform { get; set; }
|
||||
/// <summary>Platform commission amount deducted from gross (stored for finance reports).</summary>
|
||||
public decimal PlatformCommission { get; set; }
|
||||
/// <summary>JSON snapshot: driver, address, delivery ETA, etc.</summary>
|
||||
public string? DeliveryMetaJson { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public Branch? Branch { get; set; }
|
||||
public Table? Table { get; set; }
|
||||
public TableReservation? Reservation { get; set; }
|
||||
public Customer? Customer { get; set; }
|
||||
public Employee? Employee { get; set; }
|
||||
public Coupon? Coupon { get; set; }
|
||||
public ICollection<OrderItem> Items { get; set; } = [];
|
||||
public ICollection<Payment> Payments { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class OrderItem : BaseEntity
|
||||
{
|
||||
public string OrderId { get; set; } = string.Empty;
|
||||
public string MenuItemId { get; set; } = string.Empty;
|
||||
public int Quantity { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public bool IsVoided { get; set; }
|
||||
public DateTime? VoidedAt { get; set; }
|
||||
public string? VoidedByUserId { get; set; }
|
||||
|
||||
public Order Order { get; set; } = null!;
|
||||
public MenuItem MenuItem { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class Payment : BaseEntity
|
||||
{
|
||||
public string OrderId { get; set; } = string.Empty;
|
||||
public PaymentMethod Method { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public PaymentStatus Status { get; set; } = PaymentStatus.Pending;
|
||||
public string? Reference { get; set; }
|
||||
|
||||
public Order Order { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class PlatformFeature : BaseEntity
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string DisplayNameFa { get; set; } = string.Empty;
|
||||
public string? DisplayNameEn { get; set; }
|
||||
public string ModuleGroup { get; set; } = "general";
|
||||
public bool IsEnabledGlobally { get; set; } = true;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class PlatformPlanDefinition : BaseEntity
|
||||
{
|
||||
public PlanTier Tier { get; set; }
|
||||
public string DisplayNameFa { get; set; } = string.Empty;
|
||||
public string? DisplayNameEn { get; set; }
|
||||
public decimal MonthlyPriceToman { get; set; }
|
||||
public bool IsBillableOnline { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public int SortOrder { get; set; }
|
||||
/// <summary>JSON <see cref="Platform.PlanLimitsData"/>.</summary>
|
||||
public string LimitsJson { get; set; } = "{}";
|
||||
/// <summary>JSON array of feature keys enabled on this plan.</summary>
|
||||
public string? FeaturesJson { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class PlatformSetting : BaseEntity
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
public string Category { get; set; } = "general";
|
||||
public string? DescriptionFa { get; set; }
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Daily take-a-number ticket; sequence resets each calendar day (branch-local).</summary>
|
||||
public class QueueTicket : TenantEntity
|
||||
{
|
||||
public string? BranchId { get; set; }
|
||||
public DateOnly ServiceDate { get; set; }
|
||||
public int Number { get; set; }
|
||||
public string? CustomerLabel { get; set; }
|
||||
public string? IssuedByUserId { get; set; }
|
||||
public QueueTicketStatus Status { get; set; } = QueueTicketStatus.Waiting;
|
||||
public string? OrderId { get; set; }
|
||||
public DateTime IssuedAt { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public Branch? Branch { get; set; }
|
||||
public Order? Order { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Cash register session (باز / بسته کردن صندوق) for a branch.</summary>
|
||||
public class Shift : TenantEntity
|
||||
{
|
||||
public string BranchId { get; set; } = string.Empty;
|
||||
public string OpenedByUserId { get; set; } = string.Empty;
|
||||
public string? ClosedByUserId { get; set; }
|
||||
public DateTime OpenedAt { get; set; }
|
||||
public DateTime? ClosedAt { get; set; }
|
||||
public decimal OpeningCash { get; set; }
|
||||
public decimal? ClosingCash { get; set; }
|
||||
public decimal ExpectedCash { get; set; }
|
||||
public decimal? Discrepancy { get; set; }
|
||||
public ShiftStatus Status { get; set; } = ShiftStatus.Open;
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public Branch Branch { get; set; } = null!;
|
||||
public Employee OpenedBy { get; set; } = null!;
|
||||
public Employee? ClosedBy { get; set; }
|
||||
public ICollection<CashTransaction> Transactions { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class StockMovement : TenantEntity
|
||||
{
|
||||
public string IngredientId { get; set; } = string.Empty;
|
||||
public string? BranchId { get; set; }
|
||||
public decimal Delta { get; set; }
|
||||
public StockMovementKind Kind { get; set; } = StockMovementKind.Manual;
|
||||
public string? OrderId { get; set; }
|
||||
/// <summary>Total amount paid for this movement (Toman), when stock is received.</summary>
|
||||
public decimal? TotalCostToman { get; set; }
|
||||
/// <summary>Linked expense row for budget / reports (purchase only).</summary>
|
||||
public string? ExpenseId { get; set; }
|
||||
public string? Note { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public Ingredient Ingredient { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class SubscriptionPayment : TenantEntity
|
||||
{
|
||||
public PlanTier PlanTier { get; set; }
|
||||
public int Months { get; set; }
|
||||
public decimal AmountToman { get; set; }
|
||||
public long AmountRials { get; set; }
|
||||
public PaymentProvider Provider { get; set; } = PaymentProvider.ZarinPal;
|
||||
public string? Authority { get; set; }
|
||||
public string? RefId { get; set; }
|
||||
public SubscriptionPaymentStatus Status { get; set; } = SubscriptionPaymentStatus.Pending;
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class SupportTicket : TenantEntity
|
||||
{
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public SupportTicketStatus Status { get; set; } = SupportTicketStatus.Open;
|
||||
public SupportTicketPriority Priority { get; set; } = SupportTicketPriority.Normal;
|
||||
public string CreatedByEmployeeId { get; set; } = string.Empty;
|
||||
public string? AssignedAdminId { get; set; }
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? ClosedAt { get; set; }
|
||||
|
||||
public Cafe? Cafe { get; set; }
|
||||
public Employee? CreatedByEmployee { get; set; }
|
||||
public ICollection<SupportTicketMessage> Messages { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class SupportTicketMessage : BaseEntity
|
||||
{
|
||||
public string TicketId { get; set; } = string.Empty;
|
||||
public TicketMessageSenderKind SenderKind { get; set; }
|
||||
public string SenderId { get; set; } = string.Empty;
|
||||
public string Body { get; set; } = string.Empty;
|
||||
|
||||
public SupportTicket? Ticket { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class SystemAdmin : BaseEntity
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class Table : TenantEntity
|
||||
{
|
||||
public string BranchId { get; set; } = string.Empty;
|
||||
public string? SectionId { get; set; }
|
||||
public string Number { get; set; } = string.Empty;
|
||||
public int Capacity { get; set; } = 4;
|
||||
public string? Floor { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public string QrCode { get; set; } = string.Empty;
|
||||
public string? ImageUrl { get; set; }
|
||||
public string? VideoUrl { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public bool IsCleaning { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public Branch Branch { get; set; } = null!;
|
||||
public TableSection? Section { get; set; }
|
||||
public ICollection<Order> Orders { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class TableReservation : BaseEntity
|
||||
{
|
||||
public string CafeId { get; set; } = string.Empty;
|
||||
public string? TableId { get; set; }
|
||||
public string? CustomerId { get; set; }
|
||||
public string GuestName { get; set; } = string.Empty;
|
||||
public string GuestPhone { get; set; } = string.Empty;
|
||||
public DateOnly Date { get; set; }
|
||||
public TimeOnly Time { get; set; }
|
||||
public int PartySize { get; set; }
|
||||
public ReservationStatus Status { get; set; } = ReservationStatus.Pending;
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public Table? Table { get; set; }
|
||||
public Customer? Customer { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Floor area within a branch (سالن، تراس، VIP).</summary>
|
||||
public class TableSection : TenantEntity
|
||||
{
|
||||
public string BranchId { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public Branch Branch { get; set; } = null!;
|
||||
public ICollection<Table> Tables { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class Tax : TenantEntity
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public decimal Rate { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
public bool IsRequired { get; set; }
|
||||
public bool IsCompound { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public ICollection<MenuCategory> MenuCategories { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class TopProductEntry
|
||||
{
|
||||
public string ProductId { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public int Quantity { get; set; }
|
||||
public decimal Revenue { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Meezi.Core.Enums;
|
||||
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
/// <summary>Audit log for inbound delivery platform webhooks (debugging & dead-letter).</summary>
|
||||
public class WebhookLog : BaseEntity
|
||||
{
|
||||
public string? CafeId { get; set; }
|
||||
public DeliveryPlatform Platform { get; set; }
|
||||
public string RawBody { get; set; } = string.Empty;
|
||||
public string? SignatureHeader { get; set; }
|
||||
public bool SignatureValid { get; set; }
|
||||
public bool Processed { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
public int AttemptCount { get; set; }
|
||||
public string? ExternalOrderId { get; set; }
|
||||
public string? MeeziOrderId { get; set; }
|
||||
public DateTime? ProcessedAt { get; set; }
|
||||
|
||||
public Cafe? Cafe { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class WebsiteBlogPost : BaseEntity
|
||||
{
|
||||
public string Slug { get; set; } = string.Empty;
|
||||
public string TitleFa { get; set; } = string.Empty;
|
||||
public string TitleEn { get; set; } = string.Empty;
|
||||
public string ExcerptFa { get; set; } = string.Empty;
|
||||
public string ExcerptEn { get; set; } = string.Empty;
|
||||
public string ContentFa { get; set; } = string.Empty; // Markdown
|
||||
public string ContentEn { get; set; } = string.Empty;
|
||||
public string Author { get; set; } = "تیم میزی";
|
||||
public string CategoryFa { get; set; } = string.Empty;
|
||||
public string CategoryEn { get; set; } = string.Empty;
|
||||
public string TagsJson { get; set; } = "[]"; // JSON array of strings
|
||||
public string? CoverImage { get; set; }
|
||||
public bool IsPublished { get; set; } = false;
|
||||
public DateTime? PublishedAt { get; set; }
|
||||
public int ViewCount { get; set; } = 0;
|
||||
|
||||
// Navigation
|
||||
public ICollection<WebsiteComment> Comments { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Meezi.Core.Entities;
|
||||
|
||||
public class WebsiteComment : BaseEntity
|
||||
{
|
||||
public string PostSlug { get; set; } = string.Empty;
|
||||
public string AuthorName { get; set; } = string.Empty;
|
||||
public string? AuthorEmail { get; set; }
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public bool IsApproved { get; set; } = false;
|
||||
public string? IpAddress { get; set; }
|
||||
|
||||
// Navigation
|
||||
public WebsiteBlogPost? Post { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user