first commit

This commit is contained in:
Dhananjaya99
2026-09-28 11:44:26 +05:30
parent 721743ae6b
commit bffc9b1686
424 changed files with 46474 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
namespace Fitness_core_BE.Models
{
public static class AIAgents
{
public static readonly Guid MealAgentId = Guid.Parse("00000000-0000-0000-0000-000000000101");
public static readonly Guid WorkoutAgentId = Guid.Parse("00000000-0000-0000-0000-000000000102");
}
}
+65
View File
@@ -0,0 +1,65 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Security.Cryptography;
using System.Text;
namespace Fitness_core_BE.Models
{
public class AccessDevice
{
[Key]
public Guid DeviceId { get; set; }
public string DeviceName { get; set; }
// DEPRECATED: connection details now live on the on-prem agent (Fitness_core_BE.Models.Agent),
// not in the cloud. Kept nullable so existing rows survive; no new code should read/write these.
[Obsolete("Device connection details now live on the on-prem agent, not in the cloud.")]
public string? PublicIp { get; set; }
[Obsolete("Device connection details now live on the on-prem agent, not in the cloud.")]
public string? Port { get; set; }
[Obsolete("Device connection details now live on the on-prem agent, not in the cloud.")]
public string? UserName { get; set; }
[Obsolete("Device connection details now live on the on-prem agent, not in the cloud.")]
public string? Password { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public Client Client { get; set; }
// The agent that currently owns this LAN device. Commands are dispatched only
// to this agent, which prevents a second agent for the same gym receiving them.
public Guid? FKAgentId { get; set; }
[ForeignKey(nameof(FKAgentId))]
public Agent? Agent { get; set; }
// Stable serial/device identifier reported by ISAPI. Used to re-claim the same
// cloud row after reinstalling the desktop agent instead of creating duplicates.
[MaxLength(160)]
public string? HardwareId { get; set; }
// Which Place (physical zone) this terminal sits in. Null = unscoped: reachable by
// every plan regardless of that plan's Places (keeps pre-existing devices and
// plans without any place restriction working unchanged). Unique when set --
// exactly one device per place for now (see Models/Place.cs).
public Guid? FKPlaceId { get; set; }
[ForeignKey(nameof(FKPlaceId))]
public Place? Place { get; set; }
public bool IsActive { get; set; } = true;
public DateTime? LastSeenAt { get; set; }
// Explicit reachability reported by the on-prem agent. LastSeenAt remains
// the last successful terminal operation; health reports are tracked
// separately so status does not depend on browser polling.
public bool IsReachable { get; set; }
public DateTime? LastHealthReportedAt { get; set; }
public DateTime CreatedAt { get; set; }= DateTime.UtcNow;
[Obsolete("Device connection details now live on the on-prem agent, not in the cloud.")]
public void SetPassword(string plainText)
=> Password = EncryptionHelper.Encrypt(plainText);
[Obsolete("Device connection details now live on the on-prem agent, not in the cloud.")]
public string GetPlainTextPassword()
=> EncryptionHelper.Decrypt(Password!);
}
}
+44
View File
@@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>
/// Maps one tenant access profile to one physical terminal user record.
/// DeviceEmployeeNo is the terminal's person identifier, not a fingerprint template.
/// </summary>
public class AccessDeviceBinding
{
[Key]
public Guid AccessDeviceBindingId { get; set; }
public long FKAccessProfileId { get; set; }
[ForeignKey(nameof(FKAccessProfileId))]
public AccessProfile AccessProfile { get; set; } = null!;
public Guid FKDeviceId { get; set; }
[ForeignKey(nameof(FKDeviceId))]
public AccessDevice AccessDevice { get; set; } = null!;
[MaxLength(32)]
public string DeviceEmployeeNo { get; set; } = string.Empty;
[MaxLength(20)]
public string ProvisionStatus { get; set; } = "Pending";
[MaxLength(20)]
public string ActualAccessState { get; set; } = "Unknown";
public int FingerprintCount { get; set; }
[MaxLength(128)]
public string? DeviceReportedName { get; set; }
[MaxLength(1000)]
public string? LastError { get; set; }
public DateTime? LastSyncedAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
}
+67
View File
@@ -0,0 +1,67 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>
/// Tenant-scoped physical-access identity. A platform user can belong to several
/// gyms, but receives one independent access profile per gym.
/// </summary>
public class AccessProfile
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long AccessProfileId { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey(nameof(FKClientId))]
public Client Client { get; set; } = null!;
public Guid FKUserId { get; set; }
[ForeignKey(nameof(FKUserId))]
public User User { get; set; } = null!;
[MaxLength(20)]
public string PersonType { get; set; } = "Customer";
[MaxLength(128)]
public string DisplayName { get; set; } = string.Empty;
[MaxLength(20)]
public string DesiredAccessState { get; set; } = "Enabled";
public int DesiredVersion { get; set; } = 1;
[MaxLength(100)]
public string? LockReason { get; set; }
public DateTime? LockedAt { get; set; }
// ---- Face photo (Hikvision face library) ----
// The image itself is never persisted here: it travels in the UpsertFacePhoto
// DeviceCommand payload only until the terminal confirms enrolment, at which point
// the payload is purged. What survives is the enrolment state plus the device-side
// record id, which is what a later replace/delete needs.
/// <summary>True once the terminal has accepted a face for this profile.</summary>
public bool HasFacePhoto { get; set; }
/// <summary>"None" | "Pending" | "Synced" | "Failed".</summary>
[MaxLength(20)]
public string FacePhotoStatus { get; set; } = "None";
/// <summary>Hikvision face-record id (FPID) returned on enrolment.</summary>
[MaxLength(64)]
public string? FacePhotoFPID { get; set; }
/// <summary>Why the terminal rejected the face (not frontal, eyes closed, ...).</summary>
[MaxLength(500)]
public string? FacePhotoError { get; set; }
public DateTime? FacePhotoUpdatedAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public ICollection<AccessDeviceBinding> DeviceBindings { get; set; } = new List<AccessDeviceBinding>();
}
}
+18
View File
@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class ActivityLog
{
[Key]
public Guid ActivityLogId { get; set; }
public Guid FKUserId { get; set; }
[ForeignKey("FKUserId")]
public virtual User User { get; set; }
public string UserName { get; set; }
public bool IsAdmin { get; set; }
public string Description { get; set; }
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}
}
+19
View File
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class Admin
{
[Key]
public Guid AdminId { get; set; }
public string AdminName { get; set; }
public Guid FKUserId { get; set; }
[ForeignKey("FKUserId")]
public virtual User User { get; set; }
public string PhoneNumber { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
public virtual ICollection<Ticket> AssignedTickets { get; set; }
}
}
+19
View File
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class Agent
{
[Key]
public Guid AgentId { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public Client Client { get; set; }
public string Name { get; set; }
public string ApiKeyHash { get; set; }
public bool IsActive { get; set; } = true;
public DateTime? LastSeenAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+41
View File
@@ -0,0 +1,41 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class Attendance
{
[Key]
public Guid AttendanceId { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public Client Client { get; set; }
public Guid? FKDeviceId { get; set; }
[ForeignKey(nameof(FKDeviceId))]
public AccessDevice? AccessDevice { get; set; }
// Tenant-bucket entity: this FK holds a TenantCustomerLinkId (not the central
// Customer.CusId directly) -- see Models/TenantCustomerLink.cs.
public Guid? FKCustomerId { get; set; }
[ForeignKey("FKCustomerId")]
public TenantCustomerLink? TenantCustomerLink { get; set; }
public Guid? FKUserId { get; set; }
[ForeignKey(nameof(FKUserId))]
public User? User { get; set; }
public long? FKAccessProfileId { get; set; }
[ForeignKey(nameof(FKAccessProfileId))]
public AccessProfile? AccessProfile { get; set; }
public int DeviceUserId { get; set; }
[MaxLength(32)]
public string DeviceEmployeeNo { get; set; } = string.Empty;
[MaxLength(64)]
public string? SourceEventKey { get; set; }
public DateTime EventTime { get; set; }
public string EventType { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+68
View File
@@ -0,0 +1,68 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>One reservation against a FacilitySession — either an existing member using a
/// CustomerPlan session allowance (FKCustomerId set, IsGuest = false) or a non-member who
/// booked online and pays per-session (IsGuest = true, no membership at all). The guest's
/// identity proof for check-in is the OTP issued at booking time, verified at the door
/// instead of a fingerprint/face profile a non-member will never have.</summary>
public class Booking
{
[Key]
public Guid BookingId { get; set; }
public Guid FKFacilitySessionId { get; set; }
[ForeignKey(nameof(FKFacilitySessionId))]
public FacilitySession FacilitySession { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey(nameof(FKClientId))]
public Client Client { get; set; }
public bool IsGuest { get; set; } = false;
// Member path. Tenant-bucket entity: holds a TenantCustomerLinkId, not the central
// Customer.CusId directly -- see Models/TenantCustomerLink.cs.
public Guid? FKCustomerId { get; set; }
[ForeignKey(nameof(FKCustomerId))]
public TenantCustomerLink? TenantCustomerLink { get; set; }
// A member's booking draws down this specific subscription's per-cycle session
// allowance; null for guest bookings (they have no subscription at all).
public Guid? FKCustomerSubscriptionId { get; set; }
[ForeignKey(nameof(FKCustomerSubscriptionId))]
public CustomerSubscription? CustomerSubscription { get; set; }
// Guest path -- no member record exists at all, so identity is captured inline.
[MaxLength(200)]
public string? GuestName { get; set; }
[MaxLength(32)]
public string? GuestPhone { get; set; }
/// <summary>Issued when the booking is created; the guest presents this at the door
/// instead of a fingerprint/face profile. Members don't need one -- their existing
/// access credential covers check-in.</summary>
[MaxLength(10)]
public string? OtpCode { get; set; }
public DateTime? OtpVerifiedAt { get; set; }
/// <summary>"Paid" | "Unpaid" | "NotRequired" (member using a plan's included session).</summary>
public string PaymentStatus { get; set; } = "NotRequired";
public decimal AmountCharged { get; set; } = 0m;
// Set only once payment is confirmed for a pay-per-session guest booking, or
// immediately for a member session (no payment gate) -- see PaymentStatus.
public Guid? FKPaymentTransactionId { get; set; }
[ForeignKey(nameof(FKPaymentTransactionId))]
public PaymentTransaction? PaymentTransaction { get; set; }
/// <summary>"Confirmed" | "CheckedIn" | "Cancelled" | "NoShow".</summary>
public string Status { get; set; } = "Confirmed";
public DateTime BookedAt { get; set; } = DateTime.UtcNow;
public DateTime? CheckedInAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+16
View File
@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class Chat
{
public Guid Id { get; set; }
public int UserId { get; set; }
public Guid FKSenderId { get; set; }
[ForeignKey("FKSenderId")]
public Guid FKReceiverId { get; set; }
[ForeignKey("FKReceiverId")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public bool IsAcitve { get; set; } = true;
}
}
+20
View File
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class ChatMessages
{
public Guid Id { get; set; }
public int UserId { get; set; }
public string Message { get; set; }
public Guid FKSenderId { get; set; }
[ForeignKey("FKSenderId")]
public User User { get; set; }
public string Type { get; set; } = "userMessages";
public Guid FKChatId { get; set; }
[ForeignKey("FKChatId")]
public Chat Chat { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public bool IsAcitve { get; set; } = true;
}
}
+46
View File
@@ -0,0 +1,46 @@
using Fitness_core_BE.Models;
using Fitness_core_BE.Models.Enums;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Collections.Generic;
namespace Fitness_core_BE.Models
{
public class Client
{
[Key]
public Guid ClientId { get; set; }
public string? ClientName { get; set; }
public Guid FKUserId { get; set; }
[ForeignKey("FKUserId")]
public virtual User User { get; set; }
public string? ClientPhone { get; set; }
public string? ClientEmail { get; set; }
public PaymentTrack? PaymentTrack { get; set; }
// Payment-Service's Platform.Id for this client, minted the first time this
// client needs to take a gateway-routed payment (see PaymentServiceClient).
public Guid? FKPaymentPlatformId { get; set; }
// Payment-Service ApiKey/HmacSecret returned alongside FKPaymentPlatformId when the
// platform was minted. Needed to sign card/payment/refund calls (RequireHmacValidation
// on Payment-Service's side) — provisioning only ever returns these once.
public string? ClientPaymentApiKey { get; set; }
public string? ClientPaymentHmacSecret { get; set; }
// Days a member can go without payment before OverdueLockJob auto-locks their
// fingerprint access. Client-configurable (see req 7); null defaults to
// DefaultLockAfterDaysOverdue; an explicit 0 disables auto-lock for this client.
public int? LockAfterDaysOverdue { get; set; }
public const int DefaultLockAfterDaysOverdue = 2;
public virtual ICollection<ClientBusiness> ClientBusinesses { get; set; } = new List<ClientBusiness>();
}
}
+53
View File
@@ -0,0 +1,53 @@
using Fitness_core_BE.Models.Enums;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace Fitness_core_BE.Models
{
public class ClientBusiness
{
[Key]
public Guid Bid { get; set; }
public Guid? FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client Client { get; set; }
public BusinessType BusinessType { get; set; }
public string? BusinessName { get; set; }
public string? BusinessLogoId { get; set; }
public string? Description { get; set; }
// Pysical Location
public bool IsPysicalLocation { get; set; } = true;
public string? Address { get; set; }
public string? City { get; set; }
public string? Facilities { get; set; }
//Coach
public string? CoachingMode { get; set; }
public string? Expertise { get; set; }
public string? Gender { get; set; }
public string? ExperienceYears { get; set; }
public string? SessionType { get; set; }
//Product Store
public string? StoreType { get; set; }
public string? ProductCategory { get; set; }
public string? DeliveryAvailable { get; set; }
//Geo Location
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public DateTime createdAt { get; set; } = DateTime.UtcNow;
public bool IsActive { get; set; } = true;
}
}
+31
View File
@@ -0,0 +1,31 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Fitness_core_BE.Models;
namespace Fitness_core_BE.Models
{
public class ClientCard
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CardId { get; set; }
public int JobId { get; set; }
public Guid FK_ClientId { get; set; }
[ForeignKey("FK_ClientId")]
public virtual Client Client { get; set; }
public string? WalletId { get; set; }
public string? CardNumber { get; set; }
public string? CardType { get; set; }
public string? CardDate { get; set; }
public bool IsActive { get; set; } = false;
public bool IsPrimary { get; set; } = false;
// Payment-Service's own card identifier — the one thing we keep locally beyond
// ApiKey/HmacSecret, because ClientInvoice.FKClientCardId needs a stable local row to
// point at. Everything else about the card (masked PAN, brand, expiry, live status)
// is fetched from Payment-Service on demand, never cached here.
public string? CardToken { get; set; }
}
}
+26
View File
@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
// Records what an admin actually agreed with a client for their fingerprint/access
// device at assignment time (full purchase vs. rental + one-time setup fee).
// DevicePricingConfig stays as the admin's reference/default rates; this is per-client.
public class ClientDeviceBilling
{
[Key]
public Guid Id { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client Client { get; set; }
public string Mode { get; set; } = "Rental"; // "FullPurchase" | "Rental"
public decimal AgreedAmount { get; set; } // full price if FullPurchase, monthly fee if Rental
public decimal SetupFee { get; set; } = 0;
public bool IsActive { get; set; } = true;
public DateTime AssignedAt { get; set; } = DateTime.UtcNow;
public Guid? AssignedByAdminId { get; set; }
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace Fitness_core_BE.Models.IOs
{
public class ClientGymResponse
{
public Guid Id { get; set; }
public string? Name { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public bool IsActive { get; set; }
}
}
+31
View File
@@ -0,0 +1,31 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class ClientInvoice
{
[Key]
public Guid ClientInvoiceId { get; set; }
public Guid FKClientSubscriptionId { get; set; }
[ForeignKey("FKClientSubscriptionId")]
public virtual ClientSubscription ClientSubscription { get; set; }
public Guid FKUserId { get; set; }
[ForeignKey("FKUserId")]
public virtual User User { get; set; }
public int FKClientCardId { get; set; }
[ForeignKey("FKClientCardId")]
public virtual ClientCard ClientCard { get; set; }
public decimal Amount { get; set; }
// "Pending" | "Partial" | "Paid"
public string Status { get; set; } = "Pending";
public decimal PaidAmount { get; set; } = 0;
public DateTime ClientInvoiceDate { get; set; } = DateTime.UtcNow;
public virtual ICollection<ClientInvoiceItem> Items { get; set; } = new List<ClientInvoiceItem>();
}
}
+18
View File
@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class ClientInvoiceItem
{
[Key]
public Guid ClientInvoiceItemId { get; set; }
public Guid FKClientInvoiceId { get; set; }
[ForeignKey("FKClientInvoiceId")]
public virtual ClientInvoice ClientInvoice { get; set; }
public string? ItemName { get; set; }
public string? ItemDescription { get; set; }
public decimal ItemPrice { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+46
View File
@@ -0,0 +1,46 @@
using System.ComponentModel.DataAnnotations;
namespace Fitness_core_BE.Models
{
public class ClientPackage
{
[Key]
public Guid ClientPackageId { get; set; }
public string? PackageName { get; set; }
public string? PackageDescription { get; set; }
public decimal PackagePrice { get; set; }
// Platform (HexaFitness -> gym) billing formula for this plan.
// baseFee = MinimumMonthlyBill + max(0, min(activeMembers, MaxBillableMemberCount) - IncludedMemberCount) * PerMemberRate,
// capped by MonthlyCap when set. PerMemberRate = 0 preserves legacy flat-PackagePrice behavior for existing rows.
public decimal PerMemberRate { get; set; } = 0;
public decimal MinimumMonthlyBill { get; set; } = 0;
public decimal? MonthlyCap { get; set; }
public decimal SetupFee { get; set; } = 0;
// Members already covered by MinimumMonthlyBill before PerMemberRate overage kicks in.
public int IncludedMemberCount { get; set; } = 0;
// Optional ceiling on the member count billed for (null = unlimited).
public int? MaxBillableMemberCount { get; set; }
// Platform's cut of a client's own member-billing, depending on which gateway
// (the client's own, or HexaFitness's) their member payments route through.
public decimal OwnGatewayFeePercent { get; set; } = 0;
public decimal OwnGatewayFeeFixed { get; set; } = 0;
public decimal OurGatewayFeePercent { get; set; } = 0;
public decimal OurGatewayFeeFixed { get; set; } = 0;
// Day of month (1-28) the client is paid out on, for clients on their own gateway.
public int PayoutDayOfMonth { get; set; } = 1;
// The plan applied to a client when it has no active ClientSubscription assignment.
// Only one row should have IsDefault = true.
public bool IsDefault { get; set; } = false;
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+48
View File
@@ -0,0 +1,48 @@
using Fitness_core_BE.Models.Enums;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class ClientPayoutKyc
{
[Key]
public Guid Id { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client Client { get; set; }
public int? BrCertificateDocId { get; set; }
[ForeignKey("BrCertificateDocId")]
public virtual UserDocument? BrCertificateDoc { get; set; }
public int? BankProofDocId { get; set; }
[ForeignKey("BankProofDocId")]
public virtual UserDocument? BankProofDoc { get; set; }
// Legacy payout-KYC uploads. New gateway onboarding reuses the NIC front
// and back collected during owner activation instead of uploading it again.
public int? NicDocId { get; set; }
[ForeignKey("NicDocId")]
public virtual UserDocument? NicDoc { get; set; }
// GatewayDefinition lives in Payment-Service, so this is an external
// catalog identifier rather than an EF foreign key.
public Guid? SelectedGatewayId { get; set; }
[MaxLength(100)]
public string? SelectedGatewayName { get; set; }
public KycStatus KycStatus { get; set; } = KycStatus.Pending;
public DateTime SubmittedAt { get; set; } = DateTime.UtcNow;
public DateTime? ReviewedAt { get; set; }
[MaxLength(500)]
public string? RejectionReason { get; set; }
public Guid? ReviewedByAdminId { get; set; }
[ForeignKey("ReviewedByAdminId")]
public virtual Admin? ReviewedByAdmin { get; set; }
}
}
+20
View File
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class ClientSubItems
{
[Key]
public Guid ClientSubItemsId { get; set; }
public Guid FKClientSubscriptionId { get; set; }
[ForeignKey("FKClientSubscriptionId")]
public virtual ClientSubscription ClientSubscription { get; set; }
public string? SubItemName { get; set; }
public string? SubItemDescription { get; set; }
public decimal SubItemPrice { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+29
View File
@@ -0,0 +1,29 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class ClientSubscription
{
[Key]
public Guid SubscriptionId { get; set;}
public Guid FKClientPackageId { get; set; }
[ForeignKey("FKClientPackageId")]
public virtual ClientPackage ClientPackage { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client Client { get; set; }
public DateTime OnboardDate { get; set; } = DateTime.UtcNow;
public DateTime? ExpiryDate { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+24
View File
@@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>A gym's own Terms &amp; Conditions, authored in the client portal. Only one row per
/// (ClientId, IsActive=true) at a time — publishing a new version deactivates the old one so
/// CustomerTermsAcceptance rows keep pointing at the exact version they accepted.</summary>
public class ClientTerms
{
[Key]
public Guid ClientTermsId { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client? Client { get; set; }
public int Version { get; set; } = 1;
public string Content { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+21
View File
@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class ClientWallet
{
[Key]
public Guid Id { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client Client { get; set; }
public decimal Balance { get; set; } = 0;
public string Currency { get; set; } = "LKR";
public DateTime? LastPayoutDate { get; set; }
public virtual ICollection<WalletLedgerEntry> LedgerEntries { get; set; } = new List<WalletLedgerEntry>();
}
}
+20
View File
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CusWeightLossProgress
{
[Key]
public Guid WeeklyProgressId { get; set; }
public decimal? Weight { get; set; }
public DateTime RecordedAt { get; set; } = DateTime.UtcNow;
public Guid FKCusId { get; set; }
[ForeignKey("FKCusId")]
public virtual Customer Customer { get; set; }
public Guid CustomerTargetId { get; set; }
[ForeignKey("CustomerTargetId")]
public virtual CustomerTarget CustomerTarget { get; set; }
public bool IsActive { get; set; } = true;
}
}
+47
View File
@@ -0,0 +1,47 @@
using Fitness_core_BE.Models;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
namespace Fitness_core_BE.Models
{
public class Customer
{
[Key]
public Guid CusId { get; set; }
public string? CusName { get; set; }
public string? PushTokenId { get; set; }
// Points at a UserDocument uploaded via POST /customer/documents. No EF relationship
// is configured (kept as a plain id) since we only ever need the id itself here — the
// mobile app builds the display URL client-side via docService/{id}.
public int? FKProfileImageDocId { get; set; }
public string? Gender { get; set; }
public DateOnly? BoD { get; set; }
public string? PrimaryPH { get; set; }
public bool IsPrimaryPHVerfied { get; set; } = false;
public string? SecondaryPH { get; set; }
public bool IsSecondaryPHVerfied { get; set; } = false;
public string? Email { get; set; }
public bool IsEmailVerfied { get; set; } = false;
public string? NIC { get; set; }
public string? Address { get; set; }
public Guid FKUserId { get; set; }
[ForeignKey("FKUserId")]
public virtual User User { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Legacy single-gym lock mirror. New physical-access decisions are stored on the
// tenant-scoped AccessProfile so one customer can have different access state at
// different gyms.
public bool IsLocked { get; set; } = false;
public string? LockReason { get; set; } // "Manual" | "Overdue"
public DateTime? LockedAt { get; set; }
public virtual ICollection<CustomerWorkout> CustomerWorkouts { get; set; }
public virtual ICollection<CustomerMeal> CustomerMeals { get; set; }
public virtual ICollection<CustomerTarget> CustomerTargets { get; set; }
public virtual ICollection<CustomerSubscription> CustomerSubscriptions { get; set; }
}
}
+18
View File
@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerAccess
{
public int Id { get; set; }
public string Name { get; set; }
public Guid FKDeviceId { get; set; }
[ForeignKey("FKDeviceId")]
public AccessDevice AccessDevice { get; set; }
public Guid FKCustomerId { get; set; }
[ForeignKey(nameof(FKCustomerId))]
public Customer Customer { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+36
View File
@@ -0,0 +1,36 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerCards
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid JobId { get; set; }
// Tenant-bucket entity: this FK holds a TenantCustomerLinkId (not the central
// Customer.CusId directly) -- see Models/TenantCustomerLink.cs.
public Guid? FK_CustomerId { get; set; }
[ForeignKey("FK_CustomerId")]
public virtual TenantCustomerLink? TenantCustomerLink { get; set; }
public Guid? FK_ClientId { get; set; }
[ForeignKey("FK_ClientId")]
public virtual Client? Client { get; set; }
public string? WalletId { get; set; }
public string? CardId { get; set; }
public string? CardNumber { get; set; }
public string? CardType { get; set; }
public string? CardDate { get; set; }
public bool IsActive { get; set; } = false;
public bool IsPrimary { get; set; } = false;
public int Priority { get; set; } = 0;
public string? NickName { get; set; }
/// <summary>Member's explicit opt-in to store this card for auto-deduct charges.</summary>
public bool ConsentGiven { get; set; } = false;
public DateTime? ConsentAt { get; set; }
}
}
+19
View File
@@ -0,0 +1,19 @@
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerHealth
{
[Key]
public Guid HealthId { get; set; }
public string? BloodGroup { get; set; }
public string? Disabilities { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public Guid FKCusId { get; set; }
[ForeignKey("FKCusId")]
public virtual Customer Customer { get; set; }
public bool IsActive { get; set; }
}
}
+33
View File
@@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerInvoice
{
[Key]
public Guid CustomerInvoiceId { get; set; }
public Guid FKCustomerSubscriptionId { get; set; }
[ForeignKey("FKCustomerSubscriptionId")]
public virtual CustomerSubscription CustomerSubscription { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client Client { get; set; }
public Guid FKUserId { get; set; }
[ForeignKey("FKUserId")]
public virtual User User { get; set; }
public decimal Amount { get; set; }
public string Status { get; set; } = "Pending";
// Approval gate for the renewal workflow: false means this invoice was auto-generated by
// the daily renewal sweep and is awaiting staff approve/change-plan/reject. Independent of
// Status and of CustomerSubscription.IsActive. Defaults true so every other invoice-
// creation path (onboarding, plan change, manual billing) is unaffected.
public bool IsActive { get; set; } = true;
public DateTime CustomerInvoiceDate { get; set; } = DateTime.UtcNow;
}
}
+18
View File
@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerInvoiceItem
{
[Key]
public Guid CustomerInvoiceItemId { get; set; }
public Guid FKCustomerInvoiceId { get; set; }
[ForeignKey("FKCustomerInvoiceId")]
public virtual CustomerInvoice CustomerInvoice { get; set; }
public string? ItemName { get; set; }
public string? ItemDescription { get; set; }
public decimal ItemPrice { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+27
View File
@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerMeal
{
[Key]
public Guid CustomerMealId { get; set; }
// Tenant-bucket entity: this FK holds a TenantCustomerLinkId (not the central
// Customer.CusId directly) -- see Models/TenantCustomerLink.cs.
public Guid FKCustomerId { get; set; }
[ForeignKey("FKCustomerId")]
public virtual TenantCustomerLink? TenantCustomerLink { get; set; }
public Guid FKMealPlanId { get; set; }
[ForeignKey("FKMealPlanId")]
public virtual MealPlan? MealPlan { get; set; }
public bool IsActive { get; set; } = true;
public DateTime AssignedDate { get; set; } = DateTime.UtcNow;
public DateTime? CompletedDate { get; set; }
public bool IsFollowing { get; set; } = false;
public string? Notes { get; set; }
}
}
+33
View File
@@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
// A persisted, in-app history of pushes sent to a customer (workout/meal plan assigned,
// a gym feed post, etc.) — backs the mobile Notification tab, independent of whether the
// device actually received/displayed the push itself.
public class CustomerNotification
{
[Key]
public Guid CustomerNotificationId { get; set; }
public Guid FKCustomerId { get; set; }
[ForeignKey("FKCustomerId")]
public virtual Customer? Customer { get; set; }
public string Title { get; set; } = string.Empty;
public string Body { get; set; } = string.Empty;
// "WorkoutPlanAssigned" | "MealPlanAssigned" | "FeedPost" — free text, not an enum, so
// new notification kinds don't need a migration.
public string NotificationType { get; set; } = string.Empty;
// Mirrors PushNavigationData's contract in the mobile app's pushNotification.ts, so a
// tap on this notification in the list can navigate the same way a live push would.
public string? RouteName { get; set; }
public string? RouteParamsJson { get; set; }
public bool IsRead { get; set; } = false;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+67
View File
@@ -0,0 +1,67 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerPlan
{
[Key]
public Guid CustomerPlanId { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client Client { get; set; }
public string? PlanName { get; set; }
public string? PlanDescription { get; set; }
public decimal PlanPrice { get; set; }
/// <summary>Whether this plan charges a one-time fee when a new subscription starts.</summary>
public bool HasOnboardingFee { get; set; } = false;
/// <summary>One-time amount added to the first invoice/installment.</summary>
public decimal OnboardingFeeAmount { get; set; } = 0m;
/// <summary>Used when DurationMode == "Rolling"; 0 for FixedRange plans.</summary>
public int DurationInDays { get; set; }
/// <summary>"Individual" | "Group" (couple/family — one subscription covers MemberLimit members).</summary>
public string PlanScope { get; set; } = "Individual";
/// <summary>1 for Individual; >= 2 for Group (couple = 2).</summary>
public int MemberLimit { get; set; } = 1;
/// <summary>"Rolling" (DurationInDays from join date) | "FixedRange" (ValidFrom–ValidTo).</summary>
public string DurationMode { get; set; } = "Rolling";
public DateTime? ValidFrom { get; set; }
public DateTime? ValidTo { get; set; }
/// <summary>Owner decides: when true the price splits into InstallmentCount scheduled parts.</summary>
public bool AllowSplitPayments { get; set; } = false;
/// <summary>Number of mostly-equal installments (1 = full payment upfront).</summary>
public int InstallmentCount { get; set; } = 1;
/// <summary>Null = unlimited-access plan (existing gym behavior). A number = this plan
/// grants that many bookable Facility sessions per billing cycle (e.g. "4 pool sessions
/// a month") — enforced by the booking flow, not by this model.</summary>
public int? SessionsPerCycle { get; set; }
/// <summary>"NoRollover" (unused sessions are forfeited when the cycle renews) |
/// "GracePeriod" (unused sessions stay usable for RolloverGraceDays after renewal).
/// Set per package at creation time, per owner's requirement.</summary>
public string SessionRolloverPolicy { get; set; } = "NoRollover";
/// <summary>Days into the new cycle that last cycle's unused sessions remain usable.
/// Only meaningful when SessionRolloverPolicy == "GracePeriod".</summary>
public int RolloverGraceDays { get; set; } = 0;
/// <summary>"Prepaid" (pay before the cycle starts, existing behavior) | "Postpaid"
/// (use first, invoice at cycle end — reuses Client.LockAfterDaysOverdue /
/// OverdueLockJob to suspend access if that invoice goes unpaid past due). Asked once,
/// per package, at creation time per owner's requirement.</summary>
public string BillingTiming { get; set; } = "Prepaid";
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+22
View File
@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>Many-to-many join: which Place(s) a CustomerPlan grants access to. A plan
/// with no rows here is unrestricted (reaches every active device) -- see
/// AccessDeviceService's allowed-device resolution.</summary>
public class CustomerPlanPlace
{
[Key]
public Guid CustomerPlanPlaceId { get; set; }
public Guid FKCustomerPlanId { get; set; }
[ForeignKey(nameof(FKCustomerPlanId))]
public CustomerPlan CustomerPlan { get; set; } = null!;
public Guid FKPlaceId { get; set; }
[ForeignKey(nameof(FKPlaceId))]
public Place Place { get; set; } = null!;
}
}
+26
View File
@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerSubItem
{
[Key]
public Guid CustomerSubItemId { get; set; }
public Guid FKCustomerSubscriptionId { get; set; }
[ForeignKey("FKCustomerSubscriptionId")]
public virtual CustomerSubscription CustomerSubscription { get; set; }
public string? SubItemName { get; set; }
public string? SubItemDescription { get; set; }
public decimal SubItemPrice { get; set; }
// "OneTime" (charged once, e.g. via a supplemental invoice) | "Recurring" (carried
// forward onto the invoice each time this subscription renews/upgrades).
public string Frequency { get; set; } = "OneTime";
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+61
View File
@@ -0,0 +1,61 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerSubscription
{
[Key]
public Guid SubscriptionId { get; set; }
public Guid FKCustomerPlanId { get; set; }
[ForeignKey("FKCustomerPlanId")]
public virtual CustomerPlan CustomerPlan { get; set; }
// Tenant-bucket entity: this FK holds a TenantCustomerLinkId (not the central
// Customer.CusId directly) -- see Models/TenantCustomerLink.cs.
public Guid FKCustomerId { get; set; }
[ForeignKey("FKCustomerId")]
public virtual TenantCustomerLink TenantCustomerLink { get; set; }
public DateTime OnboardDate { get; set; } = DateTime.UtcNow;
// Set once, at creation, and never touched again -- see RenewalDate for the field that
// actually reflects the subscription's current cycle end / renewal-eligibility date.
public DateTime? ExpiryDate { get; set; }
// Advances on every approved renewal cycle; every "is this due/expiring/current" query,
// filter, sort or status display should read this instead of ExpiryDate.
public DateTime? RenewalDate { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Set together at assignment time; "was a discount given" is DiscountValue.HasValue.
public string? DiscountType { get; set; } // "Percentage" | "Flat"
public decimal? DiscountValue { get; set; }
/// <summary>Member has consented to auto-deduct renewals from FKAutoChargeCardId.</summary>
public bool AutoChargeEnabled { get; set; } = false;
public Guid? FKAutoChargeCardId { get; set; }
[ForeignKey("FKAutoChargeCardId")]
public virtual CustomerCards? AutoChargeCard { get; set; }
// Session-count tracking for CustomerPlan.SessionsPerCycle plans (null for unlimited
// gym-access plans). SessionsUsedThisCycle resets on renewal; SessionsCarriedOver holds
// unused sessions brought forward when CustomerPlan.SessionRolloverPolicy == "GracePeriod"
// and is cleared once RolloverGraceDays elapses. Booking creation increments
// SessionsUsedThisCycle instead of duplicating this counter per facility.
public int SessionsUsedThisCycle { get; set; } = 0;
public int SessionsCarriedOver { get; set; } = 0;
// Set at renewal approval time when CustomerPlan.SessionRolloverPolicy == "GracePeriod"
// (now + RolloverGraceDays); null when there's nothing carried over. Booking creation
// treats SessionsCarriedOver as spent/expired once this passes, even though the column
// itself isn't zeroed until the next renewal -- see BookingService.CreateMemberBooking.
public DateTime? SessionsCarriedOverExpiresAt { get; set; }
public virtual ICollection<SubscriptionMember> SubscriptionMembers { get; set; }
public virtual ICollection<SubscriptionInstallment> SubscriptionInstallments { get; set; }
}
}
+25
View File
@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerTarget
{
[Key]
public Guid CustomerTargetId { get; set; }
public Guid FKCusId { get; set; }
[ForeignKey("FKCusId")]
public virtual Customer Customer { get; set; }
public decimal? CurrentWeight { get; set; }
public decimal? CurrentHeight { get; set; }
public decimal? TargetWeight { get; set; }
public string? Goal { get; set; }
public string? TimePeriod { get; set; }
public string? PredictedNutritionsJson { get; set; }
public string? PredictedWeightsJson { get; set; }
public virtual ICollection<CusWeightLossProgress> WeeklyProgresses { get; set; } = new List<CusWeightLossProgress>();
public bool IsChanged { get; set; } = false;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public bool IsActive { get; set; } = true;
}
}
+26
View File
@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>Records that a customer accepted a specific gym's specific Terms version —
/// e.g. at QR-join time. Kept even after the gym publishes a newer version.</summary>
public class CustomerTermsAcceptance
{
[Key]
public Guid CustomerTermsAcceptanceId { get; set; }
// Tenant-bucket entity: this FK holds a TenantCustomerLinkId (not the central
// Customer.CusId directly) -- see Models/TenantCustomerLink.cs.
public Guid FKCusId { get; set; }
[ForeignKey("FKCusId")]
public virtual TenantCustomerLink? TenantCustomerLink { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client? Client { get; set; }
public int Version { get; set; }
public DateTime AcceptedAt { get; set; } = DateTime.UtcNow;
}
}
+28
View File
@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerWorkout
{
[Key]
public Guid CustomerWorkoutId { get; set; }
// Tenant-bucket entity: this FK holds a TenantCustomerLinkId (not the central
// Customer.CusId directly) -- see Models/TenantCustomerLink.cs.
public Guid FKCustomerId { get; set; }
[ForeignKey("FKCustomerId")]
public virtual TenantCustomerLink? TenantCustomerLink { get; set; }
public Guid FKWorkoutPlanId { get; set; }
[ForeignKey("FKWorkoutPlanId")]
public virtual WorkoutPlan? WorkoutPlan { get; set; }
public bool IsActive { get; set; } = true;
public DateTime AssignedDate { get; set; } = DateTime.UtcNow;
public DateTime? CompletedDate { get; set; }
public bool IsFollowing { get; set; } = false;
public string? Notes { get; set; }
public virtual ICollection<CustomerWorkoutExerciseCompletion> ExerciseCompletions { get; set; } = new List<CustomerWorkoutExerciseCompletion>();
}
}
@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class CustomerWorkoutExerciseCompletion
{
[Key]
public Guid CustomerWorkoutExerciseCompletionId { get; set; }
public Guid FKCustomerWorkoutId { get; set; }
[ForeignKey("FKCustomerWorkoutId")]
public virtual CustomerWorkout? CustomerWorkout { get; set; }
public Guid FKWorkoutExerciseId { get; set; }
[ForeignKey("FKWorkoutExerciseId")]
public virtual WorkoutExercise? WorkoutExercise { get; set; }
public DateTime CompletedDate { get; set; } = DateTime.UtcNow;
}
}
+36
View File
@@ -0,0 +1,36 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class DeviceCommand
{
[Key]
public Guid Id { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public Client Client { get; set; }
public Guid? FKDeviceId { get; set; }
[ForeignKey("FKDeviceId")]
public AccessDevice? AccessDevice { get; set; }
public Guid? FKAccessDeviceBindingId { get; set; }
[ForeignKey(nameof(FKAccessDeviceBindingId))]
public AccessDeviceBinding? AccessDeviceBinding { get; set; }
// e.g. "AddUser", "WhiteListMember", "BlackListMember"
public string CommandType { get; set; }
public string PayloadJson { get; set; }
// "Pending" / "InProgress" / "Completed" / "Failed"
public string Status { get; set; } = "Pending";
public string? ResultJson { get; set; }
public int DesiredVersion { get; set; }
public int AttemptCount { get; set; }
public int MaxAttempts { get; set; } = 5;
public DateTime? LeaseExpiresAt { get; set; }
public DateTime? NextAttemptAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? ProcessedAt { get; set; }
}
}
+18
View File
@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace Fitness_core_BE.Models
{
// Single global row (admin-editable) for access-control-device fees.
// Not per-plan: every gym pays the same device rates in the source pricing model.
public class DevicePricingConfig
{
[Key]
public Guid DevicePricingConfigId { get; set; }
public decimal DeviceRentalFeePerMonth { get; set; } = 0;
public decimal DeviceInstallationFee { get; set; } = 0;
public decimal DeviceConfigureExistingFee { get; set; } = 0;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
}
+11
View File
@@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace Fitness_core_BE.Models
{
public class DocumentTypes
{
[Key]
public int DocumentId { get; set; }
public string DocumentType { get; set; }
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Fitness_core_BE.Models.Enums
{
public enum BeneficiaryType
{
Customer,
Client
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Fitness_core_BE.Models.Enums
{
public enum BusinessType
{
institute = 1,
cauching = 2,
productSeller = 3
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Fitness_core_BE.Models.Enums
{
public enum CustomerOnboardingStatus
{
Pending = 1,
Approved = 2,
Rejected = 3
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Fitness_core_BE.Models.Enums
{
public enum FirstChargeStatus
{
Pending = 1,
Succeeded = 2,
Failed = 3
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Fitness_core_BE.Models.Enums
{
public enum KycStatus
{
Pending = 1,
Approved = 2,
Rejected = 3
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace Fitness_core_BE.Models.Enums
{
public enum OnboardingStatus
{
New = 1,
Called = 2,
Approved = 3,
Rejected = 4,
Activated = 5
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Fitness_core_BE.Models.Enums
{
public enum PaymentTrack
{
OwnGateway = 1,
HexaFitness = 2
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Fitness_core_BE.Models.Enums
{
public enum PayoutBatchStatus
{
Pending = 1,
Processed = 2,
Failed = 3
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Fitness_core_BE.Models.Enums
{
public enum PriorityLevel
{
Low = 1,
Medium = 2,
High = 3
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace Fitness_core_BE.Models.Enums
{
public enum ProgressGoalType
{
Loss,
Gain,
Maintain,
Strength,
Endurance,
AthleticPerformance,
StayFit,
Flexibility,
Beginner
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Fitness_core_BE.Models.Enums
{
public enum Sex
{
Male,
Female
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Fitness_core_BE.Models.Enums
{
public enum TicketStatusEnum
{
Pending = 1,
Open = 2,
Closed = 3
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Fitness_core_BE.Models.Enums
{
public enum WalletEntryType
{
Credit = 1,
Debit = 2
}
}
+32
View File
@@ -0,0 +1,32 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>A bookable area of the club — the gym floor, the pool, a court, a class type
/// (karate, etc). Gym-floor access itself keeps working through the existing
/// CustomerPlan/Attendance flow unchanged; Facility/FacilitySession/Booking below are for
/// the new capacity-limited, session-based areas (pool, courts, classes).</summary>
public class Facility
{
[Key]
public Guid FacilityId { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey(nameof(FKClientId))]
public Client Client { get; set; }
public string Name { get; set; } = string.Empty;
/// <summary>"Gym" | "Pool" | "Court" | "Class" — informational grouping for the admin
/// UI; booking rules themselves live on FacilitySession/CustomerPlan, not on this type.</summary>
public string FacilityType { get; set; } = "Class";
public string? Description { get; set; }
/// <summary>Default per-session headcount limit; a specific FacilitySession can override.</summary>
public int DefaultCapacity { get; set; } = 1;
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+33
View File
@@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>One scheduled, bookable occurrence of a Facility (e.g. "Pool, Tue 6-7pm").
/// Capacity is enforced by counting non-cancelled Bookings against this row, not by a
/// denormalized counter, to avoid drift between the two.</summary>
public class FacilitySession
{
[Key]
public Guid FacilitySessionId { get; set; }
public Guid FKFacilityId { get; set; }
[ForeignKey(nameof(FKFacilityId))]
public Facility Facility { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey(nameof(FKClientId))]
public Client Client { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
/// <summary>Overrides Facility.DefaultCapacity when set; null = use the facility default.</summary>
public int? Capacity { get; set; }
public bool IsCancelled { get; set; } = false;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public virtual ICollection<Booking> Bookings { get; set; }
}
}
+19
View File
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class FeedLike
{
[Key]
public Guid LikeId { get; set; }
public Guid FKUserId { get; set; }
[ForeignKey("FKUserId")]
public virtual User User { get; set; }
public Guid FKFeedPostId { get; set; }
[ForeignKey("FKFeedPostId")]
public virtual FeedPost FeedPost { get; set; }
}
}
+19
View File
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class FeedPost
{
[Key]
public Guid FeedPostId { get; set; }
public string? Caption { get; set; }
public string? Description { get; set; }
public int? FkDocId { get; set; }
public Guid FKUserId { get; set; }
[ForeignKey("FKUserId")]
public virtual User User { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+23
View File
@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
// A gym owner's custom, per-tenant staff role. Blank slate — creating a role grants
// zero permissions; the owner assigns them explicitly via GymRolePermission afterward.
public class GymRole
{
[Key]
public Guid GymRoleId { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client Client { get; set; }
public string Name { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public virtual ICollection<GymRolePermission> Permissions { get; set; } = new List<GymRolePermission>();
}
}
+20
View File
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
// Join row: a GymRole is granted access to one NavItem/permission key.
// Composite key (FKGymRoleId, NavItemKey) is configured in TenantDbContext.OnModelCreating.
public class GymRolePermission
{
public Guid FKGymRoleId { get; set; }
[ForeignKey("FKGymRoleId")]
public virtual GymRole GymRole { get; set; }
// NavItem is central-bucket (global nav catalog); this FK is a plain scalar in
// TenantDbContext (see Ignore config there) -- resolve the label from NavItemKey
// against CentralDbContext.NavItems when actually needed.
public string NavItemKey { get; set; } = string.Empty;
[ForeignKey("NavItemKey")]
public virtual NavItem NavItem { get; set; }
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Fitness_core_BE.Models.IOs
{
public record AdminExerciseInput(string Name, string? BreathIn, string? BreathOut, string? Position, string? Reps, int? Sets, string? Duration, string? VideoUrl, string? Description);
public record AdminWorkoutDayInput(string Day, string? Description, string? SuitableFor, string? Duration, string? WarmUp, string? Cooldown, List<AdminExerciseInput> Exercises);
public record AdminWorkoutSuggestionRequest(string Name, string Category, string? CategoryDescription, string? Description, List<AdminWorkoutDayInput> Days);
public record AdminFoodInput(string Name, string? Quantity);
public record AdminMealInput(string Name, string? Description, decimal? Calories, decimal? Protein, decimal? Carbohydrates, decimal? Fats, string? ServingSize, string? PreparationNotes, List<AdminFoodInput> Foods);
public record AdminMealSuggestionRequest(string Name, string Category, string? CategoryDescription, string? Description, List<AdminMealInput> Meals);
}
+15
View File
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models.IOs
{
[NotMapped]
public class ApiRequest
{
public string FunctionName { get; set; }
public object Payload { get; set; }
public string Reference { get; set; }
}
}
+15
View File
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace Fitness_core_BE.Models.IOs
{
[NotMapped]
public class ApiResponse<T>
{
public int StatusCode { get; set; }
[Required]
public string Message { get; set; }
[Required]
public T Data { get; set; }
}
}
+80
View File
@@ -0,0 +1,80 @@
namespace Fitness_core_BE.Models.IOs
{
public record PagedResult<T>(List<T> Items, int Total, int Page, int PageSize); //commonly tabel use
// Shared OTP-send response shape -- used by the client's own contact-detail-change OTP flow
// (ClientService.SendContactOtp via AuthOtpResponseParser), not onboarding-specific despite
// originally living in that file.
public record SendOtpResult(string ReferenceNumber, int ExpiresInSeconds, int ResendAllowedInSeconds);
// ---------- Admin: client directory ----------
public record ClientSummary(Guid ClientId, string? ClientName, string? City, string? PaymentTrack, bool IsActive, DateTime CreatedAt);
public record ClientDirectoryQuery(string? Search, int Page = 1, int PageSize = 20);
// ---------- Admin: access-device pairing & status ----------
public record PairingCodeResult(string Code, DateTime ExpiresAt);
// IsOnline is inferred from the client's on-prem bridge agent's last heartbeat — the cloud
// never talks to a device directly. AccessDevice isn't tied to a specific agent, so every
// device under a client shares the same IsOnline signal (see AdminClientDirectoryService).
public record AdminAgentDto(Guid AgentId, string Name, bool IsActive, DateTime? LastSeenAt, bool IsOnline, DateTime CreatedAt);
public record AdminDeviceDto(
Guid DeviceId,
string DeviceName,
bool IsActive,
DateTime? LastSeenAt,
bool IsOnline,
DateTime? PresenceExpiresAt,
bool IsClaimed,
DateTime CreatedAt);
public record AdminClientDevicesResult(List<AdminAgentDto> Agents, List<AdminDeviceDto> Devices);
// ---------- Client's own customers / staff ----------
public record CreateCustomerWithPlansRequest(
string Fullname, string UserName, string Email, string MobileNumber, string? Nic, string Gender, string? Birthday,
List<Guid>? WorkoutPlanIds, List<Guid>? MealPlanIds, Guid? CustomerPlanId,
DateTime? OnboardDate, bool ChargePayment = true,
// Set true only on the resubmit after staff has confirmed "yes, link this existing
// platform customer to my gym" -- see ClientService.CreateCustomerWithPlans.
bool ConfirmLink = false,
string? DiscountType = null, decimal? DiscountValue = null);
// MobileNumber (not PhoneNumber) -- must match the frontend's MemberInput.mobileNumber
// JSON key, same convention CreateCustomerWithPlansRequest above already uses. A prior
// mismatch here (PhoneNumber) silently bound to null on every edit, since ASP.NET Core's
// JSON binder does not fuzzy-match differently-named keys -- every customer phone edit was
// wiping the number instead of changing it.
public record UpdateCustomerByClientRequest(
string Fullname, string UserName, string Email, string? Nic, string? Gender, string? Birthday,
string? MobileNumber);
// "Parent" path: starts a brand new subscription for a customer who was created without
// a plan, with them as the primary member.
public record AssignCustomerPlanRequest(
Guid CustomerPlanId, DateTime? ExpiryDate, DateTime? OnboardDate, bool ChargePayment = true,
string? DiscountType = null, decimal? DiscountValue = null);
// Switches an existing Individual-scope subscription to a different Individual-scope plan.
public record ChangeMembershipPlanRequest(Guid CustomerPlanId);
// ---------- Client's own gym profile (post-activation) ----------
public record EquipmentCountDto(string Name, int Count);
public record UpdateGymProfileRequest(
string? BusinessName, string? Address, string? City, string? Description,
bool? HasAc, List<EquipmentCountDto>? Equipment);
// ---------- Gym-specific Terms & Conditions authoring ----------
public record PublishGymTermsRequest(string Content);
// ---------- Client's own contact detail change (OTP-verified) ----------
public record SendContactOtpRequest(string Type, string Identifier);
public record VerifyContactOtpRequest(string Type, string Identifier, string ReferenceNumber, string OtpCode);
// ---------- Member medical details unlock ----------
public record MedicalUnlockRequest(string Password);
// GymRoleId optional -- must belong to the caller's own client (validated in StaffService).
// Left null, the new staff member has no dashboard access until a role is assigned later.
public record CreateStaffRequest(string UserName, string Email, string PhoneNumber, string? StaffPosition, string? Department, DateTime? HireDate, Guid? GymRoleId = null);
public record UpdateStaffRequest(string? UserName, string? Email, string? PhoneNumber, string? StaffPosition, string? Department);
}
+17
View File
@@ -0,0 +1,17 @@
namespace Fitness_core_BE.Models.IOs
{
public class SmsRequest
{
public string api_key { get; set; } = string.Empty;
public string phone_number { get; set; } = string.Empty;
public string msg_text { get; set; } = string.Empty;
}
public class OtpResponse
{
public int StatusCode { get; set; }
public string Message { get; set; } = string.Empty;
public string? ApiResponse { get; set; }
public string? Error { get; set; }
}
}
+22
View File
@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
namespace Fitness_core_BE.Models
{
// One row per (client, sweep) a background job processed. Central, not tenant-scoped --
// job history isn't gym-operational data, and needs to be visible from one place
// regardless of which tenant schema it's actually about.
public class JobRunLog
{
[Key]
public Guid JobRunLogId { get; set; }
public string JobName { get; set; } = string.Empty;
public Guid FKClientId { get; set; }
public DateTime RunAtUtc { get; set; } = DateTime.UtcNow;
// "Success" | "PartialFailure" | "Failed"
public string Status { get; set; } = string.Empty;
public int SubscriptionsRenewed { get; set; }
public string? ErrorMessage { get; set; }
}
}
+27
View File
@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
// Manual/ad-hoc ledger line (e.g. rent, utilities, misc income) — a simple ledger for
// things not already captured elsewhere. The full P&L view merges these with derived
// entries from PaymentTransaction (member payments), ClientInvoice (platform fees) and
// StaffPayment (payroll), rather than duplicating those into this table.
public class LedgerEntry
{
[Key]
public Guid Id { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client Client { get; set; }
public string EntryType { get; set; } = "Expense"; // "Income" | "Expense"
public string Category { get; set; } = "Other";
public string? Description { get; set; }
public decimal Amount { get; set; }
public DateTime EntryDate { get; set; } = DateTime.UtcNow;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+27
View File
@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class Meal
{
[Key]
public Guid MealId { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public decimal? Calories { get; set; }
public decimal? Protein { get; set; }
public decimal? Carbohydrates { get; set; }
public decimal? Fats { get; set; }
public string? ServingSize { get; set; }
public string? MealItems { get; set; } // JSON: {"Chicken": {"qountity" : 10pc, "Kal" : "40kal"}, "Rice": "200g"}
public string? PreparationNotes { get; set; }
public Guid FKMealPlanId { get; set; }
[ForeignKey("FKMealPlanId")]
public virtual MealPlan? MealPlan { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
}
}
+15
View File
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
namespace Fitness_core_BE.Models
{
public class MealCategory
{
[Key]
public Guid MealCategoryId { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
public virtual ICollection<MealPlan> MealPlans { get; set; }
}
}
+28
View File
@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class MealPlan
{
[Key]
public Guid MealPlanId { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public bool IsTemplate { get; set; } = false;
public Guid FKMealCategoryId { get; set; }
[ForeignKey("FKMealCategoryId")]
public virtual MealCategory MealCategory { get; set; }
public Guid? FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client? Client { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
public virtual ICollection<Meal> MealItems { get; set; } = new List<Meal>();
public virtual ICollection<CustomerMeal> CustomerMeals { get; set; } = new List<CustomerMeal>();
}
}
+25
View File
@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
namespace Fitness_core_BE.Models
{
// Global reference table (not per-tenant) driving the gym-owner dashboard's sidebar.
// Key doubles as the permission key used by GymRolePermission and RequiresPermissionAttribute —
// this is a coarse, one-permission-per-nav-section system, so a separate PermissionKey
// column would just be a redundant mirror of Key.
public class NavItem
{
[Key]
public string Key { get; set; } = string.Empty;
public string Label { get; set; } = string.Empty;
public string? ShortLabel { get; set; }
public string Href { get; set; } = string.Empty;
public string Icon { get; set; } = string.Empty;
public int SortOrder { get; set; }
// Rows like "settings" are never grantable to staff, regardless of role — the owner
// always sees them, staff never does.
public bool IsOwnerOnly { get; set; } = false;
public bool IsActive { get; set; } = true;
}
}
+20
View File
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Fitness_core_BE.Models
{
[Index(nameof(Code))]
public class PairingCode
{
[Key]
public Guid Id { get; set; }
public string Code { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public Client Client { get; set; }
public DateTime ExpiresAt { get; set; }
public bool IsUsed { get; set; } = false;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+48
View File
@@ -0,0 +1,48 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Fitness_core_BE.Models.Enums;
namespace Fitness_core_BE.Models
{
public class PaymentTransaction
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid AttemptId { get; set; }
public DateTime? PayDate { get; set; }
public string PayStatus { get; set; }
public decimal PayAmount { get; set; }
public string? PayNote { get; set; }
public Guid? FK_CardId { get; set; }
[ForeignKey("FK_CardId")]
public virtual CustomerCards? CustomerCard { get; set; }
public Guid? FK_CustomerInvoiceId { get; set; }
[ForeignKey("FK_CustomerInvoiceId")]
public virtual CustomerInvoice? CustomerInvoice { get; set; }
public Guid? FK_ClientInvoiceId { get; set; }
[ForeignKey("FK_ClientInvoiceId")]
public virtual ClientInvoice? ClientInvoice { get; set; }
public Guid? FK_ClientId { get; set; }
[ForeignKey("FK_ClientId")]
public virtual Client? Client { get; set; }
public Guid? FK_CustomerId { get; set; }
[ForeignKey("FK_CustomerId")]
public virtual Customer? Customer { get; set; }
public BeneficiaryType? BeneficiaryType { get; set; }
public string? RefType { get; set; } = "installment";
// How a manually-recorded (counter) payment was actually taken -- "Cash" | "Bank".
// Left null for gateway/auto-charge attempts, where the method is implied elsewhere.
public string? PaymentMethod { get; set; }
public string? BankRefNo { get; set; }
public string? PaymentReferenceId { get; set; }
public bool? IsActive { get; set; } = true;
public bool? IsApproved { get; set; } = false;
}
}
+21
View File
@@ -0,0 +1,21 @@
using Fitness_core_BE.Models.Enums;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class PayoutBatch
{
[Key]
public Guid Id { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client Client { get; set; }
public DateOnly ScheduledDate { get; set; }
public decimal Amount { get; set; }
public PayoutBatchStatus Status { get; set; } = PayoutBatchStatus.Pending;
public DateTime? ProcessedAt { get; set; }
}
}
+30
View File
@@ -0,0 +1,30 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>A physical access zone within the club (e.g. "Pool Area", "Indoor Gym"),
/// each fitted with its own fingerprint/face terminal. CustomerPlan grants access to
/// one or more Places via CustomerPlanPlace; AccessDevice.FKPlaceId says which Place a
/// terminal sits in. Deliberately separate from Facility, which models capacity-limited
/// bookable sessions (pool/court/class bookings) -- Places are about which physical
/// machine a member's access profile should reach, not about booking a time slot.</summary>
public class Place
{
[Key]
public Guid PlaceId { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey(nameof(FKClientId))]
public Client Client { get; set; }
[MaxLength(128)]
public string Name { get; set; } = string.Empty;
[MaxLength(500)]
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+19
View File
@@ -0,0 +1,19 @@
namespace Fitness_core_BE.Models.Receipt
{
/// <summary>Flat data used to render a "GYM OS" receipt PDF and its email/SMS notifications.</summary>
public class ReceiptModel
{
public Guid ClientId { get; set; }
public string ReceiptNo { get; set; } = "";
public DateTime Date { get; set; } = DateTime.UtcNow;
public string GymName { get; set; } = "GYM OS";
public string MemberName { get; set; } = "Member";
public string? MemberPhone { get; set; }
public string? MemberEmail { get; set; }
public string PlanName { get; set; } = "Membership";
public decimal AmountPaid { get; set; }
public decimal InvoiceAmount { get; set; }
public decimal Balance { get; set; }
public string Currency { get; set; } = "LKR";
}
}
+30
View File
@@ -0,0 +1,30 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class Staff
{
[Key]
public Guid StaffId { get; set; }
public Guid FKUserId { get; set; }
[ForeignKey("FKUserId")]
public virtual User User { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client Client { get; set; }
// Null = no role assigned yet = zero permissions (see PermissionService).
public Guid? FKGymRoleId { get; set; }
[ForeignKey("FKGymRoleId")]
public virtual GymRole? GymRole { get; set; }
public string? StaffPosition { get; set; }
public string? Department { get; set; }
public DateTime HireDate { get; set; } = DateTime.UtcNow;
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+21
View File
@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class StaffPayRate
{
[Key]
public Guid Id { get; set; }
public Guid FKStaffId { get; set; }
[ForeignKey("FKStaffId")]
public virtual Staff Staff { get; set; }
public string RateType { get; set; } = "Monthly"; // "Monthly" | "Hourly"
public decimal Amount { get; set; }
public DateTime EffectiveFrom { get; set; } = DateTime.UtcNow;
public bool IsActive { get; set; } = true;
}
}
+22
View File
@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class StaffPayment
{
[Key]
public Guid Id { get; set; }
public Guid FKStaffId { get; set; }
[ForeignKey("FKStaffId")]
public virtual Staff Staff { get; set; }
public decimal Amount { get; set; }
public string? Note { get; set; }
public DateTime PaidAt { get; set; } = DateTime.UtcNow;
// The gym-owner (Client.FKUserId) or staff user who recorded this payment.
public Guid FKPaidByUserId { get; set; }
}
}
+34
View File
@@ -0,0 +1,34 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>
/// One scheduled part of a subscription's price (mostly-equal split, e.g. a
/// Rs. 6000 six-month plan → 6 monthly rows of Rs. 1000; the last row absorbs
/// rounding). Payments are allocated to installments in InstallmentNo order.
/// InstallmentCount = 1 plans get a single row due at onboarding.
/// </summary>
public class SubscriptionInstallment
{
[Key]
public Guid SubscriptionInstallmentId { get; set; }
public Guid FKSubscriptionId { get; set; }
[ForeignKey("FKSubscriptionId")]
public virtual CustomerSubscription Subscription { get; set; }
/// <summary>1-based position in the schedule.</summary>
public int InstallmentNo { get; set; }
public DateTime DueDate { get; set; }
public decimal Amount { get; set; }
public decimal PaidAmount { get; set; } = 0m;
/// <summary>"Pending" | "Partial" | "Paid".</summary>
public string Status { get; set; } = "Pending";
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+38
View File
@@ -0,0 +1,38 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>
/// Links a customer to a subscription. Every subscription has one IsPrimary row
/// (mirroring CustomerSubscription.FKCustomerId); group plans add more rows up to
/// the plan's MemberLimit. The primary row is never removed.
/// </summary>
public class SubscriptionMember
{
[Key]
public Guid SubscriptionMemberId { get; set; }
public Guid FKSubscriptionId { get; set; }
[ForeignKey("FKSubscriptionId")]
public virtual CustomerSubscription Subscription { get; set; }
// Tenant-bucket entity: this FK holds a TenantCustomerLinkId (not the central
// Customer.CusId directly) -- see Models/TenantCustomerLink.cs.
public Guid FKCustomerId { get; set; }
[ForeignKey("FKCustomerId")]
public virtual TenantCustomerLink TenantCustomerLink { get; set; }
public bool IsPrimary { get; set; }
public DateTime JoinedAt { get; set; } = DateTime.UtcNow;
public bool IsActive { get; set; } = true;
/// <summary>
/// Whether the member is currently *using* this plan. Unassigning clears this and
/// leaves IsActive alone, so the member keeps their row in the gym's member list
/// (and their access/device enrollment) and the same button can re-assign them.
/// IsActive stays reserved for a real removal, which does hide the row.
/// </summary>
public bool IsPlanAssigned { get; set; } = true;
}
}
+33
View File
@@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations;
namespace Fitness_core_BE.Models
{
// Tenant-DB only, one row per customer this gym has ever enrolled. Every tenant-local
// table that used to FK straight to the central Customer.CusId keeps doing so, unchanged
// -- TenantCustomerLinkId IS the central Customer.CusId (same GUID value), not a new
// surrogate key. That's deliberate: a fresh surrogate key would mean "customerId" holds
// a different value depending on whether it's about to query a central or a tenant table,
// which is exactly the kind of silent, hard-to-spot bug this split needs to avoid. EF/MySQL
// still can't enforce this as a real cross-schema FK constraint -- it's just data -- but the
// ID space stays uniform everywhere, and this row is also the local anchor tenant tables
// join against for cached display fields without a central round-trip.
public class TenantCustomerLink
{
[Key]
public Guid TenantCustomerLinkId { get; set; } // == central Customer.CusId
// = the shared login User id (same GUID in Auth's Users table), cached.
public Guid CentralUserId { get; set; }
public string? DisplayName { get; set; }
public string? PrimaryPhoneCached { get; set; }
public string? EmailCached { get; set; }
// What a client-initiated delete flips. Central Customer.IsActive is never touched
// by a client-initiated delete -- see CustomerRepository.DeactivateCustomerForClient.
public bool IsActiveAtThisClient { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
}
+30
View File
@@ -0,0 +1,30 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Fitness_core_BE.Models.Enums;
namespace Fitness_core_BE.Models
{
public class Ticket
{
[Key]
public Guid TicketId { get; set; }
public string MainReason { get; set; }
public Guid? FKTicketCategoryId { get; set; }
[ForeignKey("FKTicketCategoryId")]
public virtual TicketCategory TicketCategory { get; set; }
public Guid? FKAssignerId { get; set; }
[ForeignKey("FKAssignerId")]
public virtual Admin Admin { get; set; }
public Guid FKUserId { get; set; }
[ForeignKey("FKUserId")]
public virtual User User { get; set; }
public TicketStatusEnum TicketStatus { get; set; } = TicketStatusEnum.Pending;
public int? UserRating { get; set; } = null;
public string? UserFeedback { get; set; } = null;
public DateTime? FeedbackAt { get; set; } = null;
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
public virtual ICollection<TicketMessage> Messages { get; set; }
}
}
+16
View File
@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
using Fitness_core_BE.Models.Enums;
namespace Fitness_core_BE.Models
{
public class TicketCategory
{
[Key]
public Guid TicketCategoryId { get; set; }
public string TicketCategoryName { get; set; }
public PriorityLevel PriorityLevel { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
public virtual ICollection<Ticket> Tickets { get; set; }
}
}
+22
View File
@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class TicketMessage
{
[Key]
public Guid MessageId { get; set; }
public bool IsSentByAdmin { get; set; }
public string Message { get; set; }
public string? AttachmentURL { get; set; }
public int? FKDocumentId { get; set; }
[ForeignKey("FKDocumentId")]
public virtual UserDocument UserDocument { get; set; }
public Guid FKTicketId { get; set; }
[ForeignKey("FKTicketId")]
public virtual Ticket Ticket { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
}
}
+16
View File
@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
using Fitness_core_BE.Models.Enums;
namespace Fitness_core_BE.Models
{
public class TicketStatus
{
[Key]
public Guid TicketStatusId { get; set; }
public string StatusName { get; set; }
public TicketStatusEnum StatusCode { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
public virtual ICollection<Ticket> Tickets { get; set; }
}
}
+58
View File
@@ -0,0 +1,58 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>A prepaid time balance on an RFID card, deducted by actual use via a tap-in/
/// tap-out swipe pattern (a 1-hour visit deducts 60 minutes, a 15-minute visit deducts 15 --
/// see ActiveSessionStartedAt) — separate from Booking's per-session OTP flow. Can belong to
/// a member (FKCustomerId set, e.g. an add-on to their plan) or be sold standalone to a
/// walk-in against just the RFID tag.</summary>
public class TimeBankCard
{
[Key]
public Guid TimeBankCardId { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey(nameof(FKClientId))]
public Client Client { get; set; }
// Tenant-bucket entity: holds a TenantCustomerLinkId, not the central Customer.CusId
// directly -- see Models/TenantCustomerLink.cs. Null for a card sold to a walk-in with
// no membership at all.
public Guid? FKCustomerId { get; set; }
[ForeignKey(nameof(FKCustomerId))]
public TenantCustomerLink? TenantCustomerLink { get; set; }
[MaxLength(64)]
public string RfidTagId { get; set; } = string.Empty;
public int TotalMinutesPurchased { get; set; }
public int MinutesRemaining { get; set; }
public decimal AmountPaid { get; set; } = 0m;
public Guid? FKPaymentTransactionId { get; set; }
[ForeignKey(nameof(FKPaymentTransactionId))]
public PaymentTransaction? PaymentTransaction { get; set; }
public DateTime PurchasedAt { get; set; } = DateTime.UtcNow;
/// <summary>Null = balance never expires (spend it whenever); business can still set one.</summary>
public DateTime? ExpiresAt { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Tap-in/tap-out state: null = not currently on the floor anywhere. A swipe with this
// null starts a session (tap-in, no deduction yet); a swipe with this set ends it
// (tap-out) and deducts the elapsed minutes. One open session per card at a time --
// a second tap-in without a matching tap-out first is rejected by the service, not
// silently overwritten, so a lost/forgotten tap-out surfaces instead of losing time.
public DateTime? ActiveSessionStartedAt { get; set; }
public Guid? ActiveSessionFacilityId { get; set; }
[ForeignKey(nameof(ActiveSessionFacilityId))]
public Facility? ActiveSessionFacility { get; set; }
public virtual ICollection<TimeBankCardUsageLog> UsageLogs { get; set; }
}
}
+35
View File
@@ -0,0 +1,35 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
/// <summary>One deduction event against a TimeBankCard's balance — an append-only ledger so
/// MinutesRemaining is always auditable/reconstructable, the same reasoning as LedgerEntry
/// for money.</summary>
public class TimeBankCardUsageLog
{
[Key]
public Guid TimeBankCardUsageLogId { get; set; }
public Guid FKTimeBankCardId { get; set; }
[ForeignKey(nameof(FKTimeBankCardId))]
public TimeBankCard TimeBankCard { get; set; }
public Guid FKClientId { get; set; }
[ForeignKey(nameof(FKClientId))]
public Client Client { get; set; }
// Which facility this swipe/use was against -- not a FacilitySession, since a time-bank
// card is spent on open-ended usage (e.g. "swim for however long"), not a fixed
// pre-booked slot the way Booking is.
public Guid FKFacilityId { get; set; }
[ForeignKey(nameof(FKFacilityId))]
public Facility Facility { get; set; }
public int MinutesDeducted { get; set; }
public int MinutesRemainingAfter { get; set; }
public DateTime UsedAt { get; set; } = DateTime.UtcNow;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+36
View File
@@ -0,0 +1,36 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
// A fingerprint enrolled directly on the physical access-control terminal (by gym
// staff, outside our system) that has no matching CustomerAccess yet. Surfaced by
// attendance ingestion (AgentService.IngestAttendanceAsync) whenever an incoming
// employeeNo/DeviceUserId has no CustomerAccess mapping for that device, so the
// client portal can offer it in a dropdown to link to a system Customer.
public class UnlinkedDeviceEnrollment
{
[Key]
public Guid Id { get; set; }
public Guid FKDeviceId { get; set; }
[ForeignKey("FKDeviceId")]
public virtual AccessDevice AccessDevice { get; set; }
public int DeviceUserId { get; set; }
[MaxLength(32)]
public string DeviceEmployeeNo { get; set; } = string.Empty;
public string? DeviceReportedName { get; set; }
[MaxLength(20)]
public string? DeviceUserType { get; set; }
public int FingerprintCount { get; set; }
public DateTime DiscoveredAt { get; set; } = DateTime.UtcNow;
public DateTime LastSeenAt { get; set; } = DateTime.UtcNow;
public DateTime? LinkedAt { get; set; }
public long? FKAccessProfileId { get; set; }
[ForeignKey(nameof(FKAccessProfileId))]
public virtual AccessProfile? AccessProfile { get; set; }
}
}
+17
View File
@@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
namespace Fitness_core_BE.Models
{
public class User
{
[Key]
public Guid UserId { get; set; }
public string? UserName { get; set; }
public string? Email { get; set; }
public string? PhoneNumber { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
public virtual ICollection<Ticket> Tickets { get; set; }
public virtual ICollection<UserDocument> Documents { get; set; }
}
}
+19
View File
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class UserDocument
{
[Key]
public int DocumentId { get; set; }
public Guid? UserId { get; set; }
public int FkDocumentTypeId { get; set; }
[ForeignKey("FkDocumentTypeId")]
public DocumentTypes DocumentType { get; set; }
public string DocumentUrl { get; set; }
public string DocumentName { get; set; }
public bool IsActive { get; set; } = true;
public DateTime Created_At { get; set; } = DateTime.Now;
}
}
+26
View File
@@ -0,0 +1,26 @@
using Fitness_core_BE.Models.Enums;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class WalletLedgerEntry
{
[Key]
public Guid Id { get; set; }
public Guid FKWalletId { get; set; }
[ForeignKey("FKWalletId")]
public virtual ClientWallet Wallet { get; set; }
public WalletEntryType EntryType { get; set; }
public decimal Amount { get; set; }
public Guid? RelatedCustomerInvoiceId { get; set; }
[ForeignKey("RelatedCustomerInvoiceId")]
public virtual CustomerInvoice? RelatedCustomerInvoice { get; set; }
public string? Description { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+15
View File
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
namespace Fitness_core_BE.Models
{
public class WorkoutCategory
{
[Key]
public Guid WorkoutCategoryId { get; set; }
public string WorkoutCategoryName { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
public virtual ICollection<WorkoutPlan> WorkoutPlans { get; set; }
}
}
+25
View File
@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class WorkoutExercise
{
[Key]
public Guid WorkoutExerciseId { get; set; }
public Guid FKWorkoutItemId { get; set; }
[ForeignKey("FKWorkoutItemId")]
public virtual WorkoutItem? WorkoutItem { get; set; }
public string Name { get; set; } = string.Empty;
public string? MuscleGroup { get; set; }
public int? Sets { get; set; }
public string? Reps { get; set; }
public string? Description { get; set; }
public string? BreathIn { get; set; }
public string? BreathOut { get; set; }
public string? VideoUrl { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
}
}
+26
View File
@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class WorkoutItem
{
[Key]
public Guid WorkoutItemId { get; set; }
public string ExerciseDay { get; set; } = string.Empty;
public string? Description { get; set; }
public string? Exercises { get; set; } // JSON: {"Push-ups": "3 sets x 10 reps", "Squats": "4 sets x 15 reps"}
public string? HowToDo { get; set; } // Structured JSON
public string? DoURL { get; set; }
public Guid FKWorkoutPlanId { get; set; }
[ForeignKey("FKWorkoutPlanId")]
public virtual WorkoutPlan? WorkoutPlan { get; set; }
public string? Duration { get; set; }
public string? WormUp { get; set; }
public string? Cooldown { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public virtual ICollection<WorkoutExercise> WorkoutExercises { get; set; } = new List<WorkoutExercise>();
}
}
+28
View File
@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Fitness_core_BE.Models
{
public class WorkoutPlan
{
[Key]
public Guid WorkoutPlanId { get; set; }
public string WorkoutPlanName { get; set; }
public string? Description { get; set; }
public bool IsTemplate { get; set; } = false;
public Guid FKWorkoutCategoryId { get; set; }
[ForeignKey("FKWorkoutCategoryId")]
public virtual WorkoutCategory WorkoutCategory { get; set; }
public Guid? FKClientId { get; set; }
[ForeignKey("FKClientId")]
public virtual Client? Client { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
public virtual ICollection<WorkoutItem> WorkoutItems { get; set; } = new List<WorkoutItem>();
public virtual ICollection<CustomerWorkout> CustomerWorkouts { get; set; } = new List<CustomerWorkout>();
}
}