#nullable enable using Baya.Application.Common; using Baya.Application.Contracts.Persistence; using Baya.Application.Models.Common; using Baya.Application.Models.PartnerCenters; using Baya.Domain.Entities.Booking; using Baya.Domain.Entities.Identity; using Baya.Domain.Entities.Invoices; using Baya.Domain.Entities.PartnerCenters; using Baya.Infrastructure.Persistence.Repositories.Common; using Microsoft.EntityFrameworkCore; namespace Baya.Infrastructure.Persistence.Repositories; internal sealed class PartnerCenterRepository : BaseAsyncRepository, IPartnerCenterRepository { private const int DashboardNurseCap = 50; public PartnerCenterRepository(ApplicationDbContext dbContext) : base(dbContext) { } public Task AddAsync(PartnerCenter center, CancellationToken cancellationToken) => base.AddAsync(center); public Task GetTrackedAsync(long centerId, CancellationToken cancellationToken) => Entities.FirstOrDefaultAsync(c => c.Id == centerId, cancellationToken); public Task ExistsAsync(long centerId, CancellationToken cancellationToken) => Entities.AnyAsync(c => c.Id == centerId, cancellationToken); public async Task GetDetailAsync(long centerId, CancellationToken cancellationToken) { // Project (the converter decrypts settlement_iban here); the plaintext IBAN is masked in memory below and // never leaves this method. var raw = await Entities.AsNoTracking() .Where(c => c.Id == centerId) .Select(c => new { c.Id, c.Name, c.LegalEntityType, c.MohEstablishmentPermitNo, c.TechnicalDirectorNurseUserId, c.TechnicalDirectorLicenseNo, c.EnamadCode, c.SettlementIban, c.IsMerchantOfRecord, c.CommissionRate, c.AdminUserId, c.IsActive, c.VerifiedAt, c.CreatedAt, SponsoredCount = DbContext.Set().Count(n => n.PartnerCenterId == c.Id) }) .FirstOrDefaultAsync(cancellationToken); if (raw is null) return null; return new PartnerCenterDetailDto( raw.Id, raw.Name, raw.LegalEntityType, raw.MohEstablishmentPermitNo, raw.TechnicalDirectorNurseUserId, raw.TechnicalDirectorLicenseNo, raw.EnamadCode, string.IsNullOrEmpty(raw.SettlementIban) ? null : Mask.IbanTail(raw.SettlementIban), raw.IsMerchantOfRecord, raw.CommissionRate, raw.AdminUserId, raw.IsActive, raw.VerifiedAt, raw.SponsoredCount, raw.CreatedAt); } public async Task> ListAsync(bool? isActive, int page, int pageSize, CancellationToken cancellationToken) { var query = Entities.AsNoTracking().AsQueryable(); if (isActive is { } active) query = query.Where(c => c.IsActive == active); var total = await query.CountAsync(cancellationToken); var items = await query .OrderByDescending(c => c.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .Select(c => new PartnerCenterListItemDto( c.Id, c.Name, c.LegalEntityType, c.IsMerchantOfRecord, c.IsActive, c.VerifiedAt, DbContext.Set().Count(n => n.PartnerCenterId == c.Id), c.AdminUserId)) .ToListAsync(cancellationToken); return new PagedResult(items, total, page, pageSize); } public async Task ResolveCenterForBookingAsync(long bookingId, CancellationToken cancellationToken) { var booking = await DbContext.Set().AsNoTracking() .Where(b => b.Id == bookingId) .Select(b => new { b.NurseId }) .FirstOrDefaultAsync(cancellationToken); if (booking is null) return null; var centerId = await DbContext.Set().AsNoTracking() .Where(n => n.Id == booking.NurseId) .Select(n => n.PartnerCenterId) .FirstOrDefaultAsync(cancellationToken); if (centerId is { } id) { var center = await Entities.AsNoTracking() .Where(c => c.Id == id) .Select(c => new { c.Id, c.Name, c.IsMerchantOfRecord }) .FirstOrDefaultAsync(cancellationToken); // Merchant-of-record center → it is the invoice issuer + settlement target. Otherwise the platform // issues (a non-MoR sponsor does not change the issuer). if (center is { IsMerchantOfRecord: true }) return new CenterForBookingDto(bookingId, InvoiceIssuingEntityType.PartnerCenter, center.Id, center.Name, true); } return new CenterForBookingDto(bookingId, InvoiceIssuingEntityType.Platform, null, null, false); } public Task GetAdminUserIdAsync(long centerId, CancellationToken cancellationToken) => Entities.AsNoTracking() .Where(c => c.Id == centerId) .Select(c => (int?)c.AdminUserId) .FirstOrDefaultAsync(cancellationToken); public async Task GetDashboardAsync(long centerId, CancellationToken cancellationToken) { var center = await Entities.AsNoTracking() .Where(c => c.Id == centerId) .Select(c => new { c.Id, c.Name, c.IsMerchantOfRecord, c.IsActive, c.SettlementIban }) .FirstOrDefaultAsync(cancellationToken); if (center is null) return null; var sponsoredNurseCount = await DbContext.Set().AsNoTracking() .CountAsync(n => n.PartnerCenterId == centerId, cancellationToken); var sponsoredNurses = await DbContext.Set().AsNoTracking() .Where(n => n.PartnerCenterId == centerId) .OrderBy(n => n.Id) .Take(DashboardNurseCap) .Select(n => new SponsoredNurseDto(n.Id, n.UserId, n.IsVerified, n.AverageRating, n.TotalCompletedBookings)) .ToListAsync(cancellationToken); var sponsoredBookingCount = await DbContext.Set().AsNoTracking() .CountAsync(b => DbContext.Set().Any(n => n.Id == b.NurseId && n.PartnerCenterId == centerId), cancellationToken); var invoiceCount = await DbContext.Set().AsNoTracking() .CountAsync(i => i.PartnerCenterId == centerId, cancellationToken); return new CenterDashboardDto( center.Id, center.Name, center.IsMerchantOfRecord, center.IsActive, string.IsNullOrEmpty(center.SettlementIban) ? null : Mask.IbanTail(center.SettlementIban), sponsoredNurseCount, sponsoredBookingCount, invoiceCount, sponsoredNurses); } }