using Fitness_core_BE.Interfaces; using Fitness_core_BE.Models; using Microsoft.EntityFrameworkCore; namespace Fitness_core_BE.Repos { public class PlatformBillingRepository : IPlatformBillingRepository { // Platform billing is central-bucket (HexaFitness's own revenue relationship with // each gym); AccessDevices (used only to check device-billing eligibility) is tenant. private readonly CentralDbContext _context; private readonly TenantDbContext _tenant; public PlatformBillingRepository(CentralDbContext context, TenantDbContext tenant) { _context = context; _tenant = tenant; } public Task> GetPricingPlansAsync(CancellationToken ct = default) { return _context.ClientPackages .AsNoTracking() .OrderByDescending(package => package.IsDefault) .ThenBy(package => package.PackageName) .ToListAsync(ct); } public async Task GetPackageByIdAsync( Guid packageId, CancellationToken ct = default) { return await _context.ClientPackages.FindAsync(new object[] { packageId }, ct); } public async Task AddPackageAsync( ClientPackage package, CancellationToken ct = default) { await _context.ClientPackages.AddAsync(package, ct); } public Task> GetOtherDefaultPackagesAsync( Guid exceptPackageId, CancellationToken ct = default) { return _context.ClientPackages .Where(package => package.IsDefault && package.ClientPackageId != exceptPackageId) .ToListAsync(ct); } public Task GetDefaultPackageAsync(CancellationToken ct = default) { return _context.ClientPackages .FirstOrDefaultAsync(package => package.IsDefault, ct); } public async Task GetActivePackageForClientAsync( Guid clientId, CancellationToken ct = default) { return await _context.ClientSubscriptions .AsNoTracking() .Where(subscription => subscription.FKClientId == clientId && subscription.IsActive) .OrderByDescending(subscription => subscription.OnboardDate) .Select(subscription => subscription.ClientPackage) .FirstOrDefaultAsync(ct); } public Task GetClientByIdAsync( Guid clientId, CancellationToken ct = default) { return _context.Client .FirstOrDefaultAsync(client => client.ClientId == clientId, ct); } public Task> GetActiveClientSubscriptionsAsync( Guid clientId, CancellationToken ct = default) { return _context.ClientSubscriptions .Where(subscription => subscription.FKClientId == clientId && subscription.IsActive) .ToListAsync(ct); } public Task GetActiveClientSubscriptionAsync( Guid clientId, CancellationToken ct = default) { return _context.ClientSubscriptions .Where(subscription => subscription.FKClientId == clientId && subscription.IsActive) .OrderByDescending(subscription => subscription.OnboardDate) .FirstOrDefaultAsync(ct); } public async Task AddClientSubscriptionAsync( ClientSubscription subscription, CancellationToken ct = default) { await _context.ClientSubscriptions.AddAsync(subscription, ct); } public Task> GetActiveDeviceBillingsAsync( Guid clientId, CancellationToken ct = default) { return _context.ClientDeviceBillings .Where(billing => billing.FKClientId == clientId && billing.IsActive) .ToListAsync(ct); } public Task GetLatestActiveDeviceBillingAsync( Guid clientId, CancellationToken ct = default) { return _context.ClientDeviceBillings .Where(billing => billing.FKClientId == clientId && billing.IsActive) .OrderByDescending(billing => billing.AssignedAt) .FirstOrDefaultAsync(ct); } public async Task AddDeviceBillingAsync( ClientDeviceBilling billing, CancellationToken ct = default) { await _context.ClientDeviceBillings.AddAsync(billing, ct); } public Task HasActiveDeviceAsync( Guid clientId, CancellationToken ct = default) { return _tenant.AccessDevices.AnyAsync(device => device.FKClientId == clientId && device.IsActive, ct); } public Task> GetMonthlyBillingCandidatesAsync( DateTime startOfMonth, CancellationToken ct = default) { return _context.Client .AsNoTracking() .Where(client => _context.ClientCard.Any(card => card.FK_ClientId == client.ClientId) && !_context.ClientInvoices.Any(invoice => invoice.ClientSubscription.FKClientId == client.ClientId && invoice.ClientInvoiceDate >= startOfMonth)) .Select(client => client.ClientId) .ToListAsync(ct); } public Task> GetInvoicesByStatusAsync( string status, bool newestFirst, CancellationToken ct = default) { var query = _context.ClientInvoices .AsNoTracking() .Include(invoice => invoice.ClientSubscription) .ThenInclude(subscription => subscription.Client) .ThenInclude(client => client.ClientBusinesses) .Where(invoice => status == "Outstanding" ? invoice.Status == "Pending" || invoice.Status == "Partial" : invoice.Status == status); query = newestFirst ? query.OrderByDescending(invoice => invoice.ClientInvoiceDate) : query.OrderBy(invoice => invoice.ClientInvoiceDate); return query.ToListAsync(ct); } public Task GetInvoiceWithClientAsync( Guid invoiceId, bool includeBusinesses, CancellationToken ct = default) { var query = _context.ClientInvoices .Include(invoice => invoice.ClientSubscription) .ThenInclude(subscription => subscription.Client) .AsQueryable(); if (includeBusinesses) { query = query.Include(invoice => invoice.ClientSubscription.Client.ClientBusinesses); } return query.FirstOrDefaultAsync( invoice => invoice.ClientInvoiceId == invoiceId, ct); } public Task GetClientCardAsync( Guid clientId, CancellationToken ct = default) { // The card billing charges: the active card marked primary. CardToken != null // excludes legacy rows created by ActivationService.ChargeAndActivateAsync before // real card onboarding existed — those were never confirmed by Payment-Service and // can't actually be charged, so they must never outrank (or stand in for) a real card. return _context.ClientCard .Where(card => card.FK_ClientId == clientId && card.IsActive && card.CardToken != null) .OrderByDescending(card => card.IsPrimary) .FirstOrDefaultAsync(ct); } public Task> GetClientCardsAsync( Guid clientId, CancellationToken ct = default) { return _context.ClientCard .Where(card => card.FK_ClientId == clientId && card.IsActive && card.CardToken != null) .OrderByDescending(card => card.IsPrimary) .ToListAsync(ct); } public Task GetClientCardByTokenAsync( Guid clientId, string cardToken, CancellationToken ct = default) { return _context.ClientCard .FirstOrDefaultAsync(card => card.FK_ClientId == clientId && card.CardToken == cardToken, ct); } public async Task AddInvoiceAsync( ClientInvoice invoice, IReadOnlyCollection items, CancellationToken ct = default) { await _context.ClientInvoices.AddAsync(invoice, ct); await _context.ClientInvoiceItems.AddRangeAsync(items, ct); } public Task GetDevicePricingConfigAsync( CancellationToken ct = default) { return _context.DevicePricingConfigs.FirstOrDefaultAsync(ct); } public async Task AddDevicePricingConfigAsync( DevicePricingConfig config, CancellationToken ct = default) { await _context.DevicePricingConfigs.AddAsync(config, ct); } } }