265 lines
9.7 KiB
C#
265 lines
9.7 KiB
C#
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<List<ClientPackage>> GetPricingPlansAsync(CancellationToken ct = default)
|
|
{
|
|
return _context.ClientPackages
|
|
.AsNoTracking()
|
|
.OrderByDescending(package => package.IsDefault)
|
|
.ThenBy(package => package.PackageName)
|
|
.ToListAsync(ct);
|
|
}
|
|
|
|
public async Task<ClientPackage?> 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<List<ClientPackage>> GetOtherDefaultPackagesAsync(
|
|
Guid exceptPackageId,
|
|
CancellationToken ct = default)
|
|
{
|
|
return _context.ClientPackages
|
|
.Where(package =>
|
|
package.IsDefault &&
|
|
package.ClientPackageId != exceptPackageId)
|
|
.ToListAsync(ct);
|
|
}
|
|
|
|
public Task<ClientPackage?> GetDefaultPackageAsync(CancellationToken ct = default)
|
|
{
|
|
return _context.ClientPackages
|
|
.FirstOrDefaultAsync(package => package.IsDefault, ct);
|
|
}
|
|
|
|
public async Task<ClientPackage?> 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<Client?> GetClientByIdAsync(
|
|
Guid clientId,
|
|
CancellationToken ct = default)
|
|
{
|
|
return _context.Client
|
|
.FirstOrDefaultAsync(client => client.ClientId == clientId, ct);
|
|
}
|
|
|
|
public Task<List<ClientSubscription>> GetActiveClientSubscriptionsAsync(
|
|
Guid clientId,
|
|
CancellationToken ct = default)
|
|
{
|
|
return _context.ClientSubscriptions
|
|
.Where(subscription =>
|
|
subscription.FKClientId == clientId &&
|
|
subscription.IsActive)
|
|
.ToListAsync(ct);
|
|
}
|
|
|
|
public Task<ClientSubscription?> 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<List<ClientDeviceBilling>> GetActiveDeviceBillingsAsync(
|
|
Guid clientId,
|
|
CancellationToken ct = default)
|
|
{
|
|
return _context.ClientDeviceBillings
|
|
.Where(billing =>
|
|
billing.FKClientId == clientId &&
|
|
billing.IsActive)
|
|
.ToListAsync(ct);
|
|
}
|
|
|
|
public Task<ClientDeviceBilling?> 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<bool> HasActiveDeviceAsync(
|
|
Guid clientId,
|
|
CancellationToken ct = default)
|
|
{
|
|
return _tenant.AccessDevices.AnyAsync(device =>
|
|
device.FKClientId == clientId &&
|
|
device.IsActive, ct);
|
|
}
|
|
|
|
public Task<List<Guid>> 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<List<ClientInvoice>> 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<ClientInvoice?> 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<ClientCard?> 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<List<ClientCard>> 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<ClientCard?> 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<ClientInvoiceItem> items,
|
|
CancellationToken ct = default)
|
|
{
|
|
await _context.ClientInvoices.AddAsync(invoice, ct);
|
|
await _context.ClientInvoiceItems.AddRangeAsync(items, ct);
|
|
}
|
|
|
|
public Task<DevicePricingConfig?> GetDevicePricingConfigAsync(
|
|
CancellationToken ct = default)
|
|
{
|
|
return _context.DevicePricingConfigs.FirstOrDefaultAsync(ct);
|
|
}
|
|
|
|
public async Task AddDevicePricingConfigAsync(
|
|
DevicePricingConfig config,
|
|
CancellationToken ct = default)
|
|
{
|
|
await _context.DevicePricingConfigs.AddAsync(config, ct);
|
|
}
|
|
}
|
|
}
|