146 lines
6.7 KiB
C#
146 lines
6.7 KiB
C#
#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<PartnerCenter>, IPartnerCenterRepository
|
|
{
|
|
private const int DashboardNurseCap = 50;
|
|
|
|
public PartnerCenterRepository(ApplicationDbContext dbContext) : base(dbContext)
|
|
{
|
|
}
|
|
|
|
public Task AddAsync(PartnerCenter center, CancellationToken cancellationToken) => base.AddAsync(center);
|
|
|
|
public Task<PartnerCenter?> GetTrackedAsync(long centerId, CancellationToken cancellationToken)
|
|
=> Entities.FirstOrDefaultAsync(c => c.Id == centerId, cancellationToken);
|
|
|
|
public Task<bool> ExistsAsync(long centerId, CancellationToken cancellationToken)
|
|
=> Entities.AnyAsync(c => c.Id == centerId, cancellationToken);
|
|
|
|
public async Task<PartnerCenterDetailDto?> 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<NurseProfile>().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<PagedResult<PartnerCenterListItemDto>> 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<NurseProfile>().Count(n => n.PartnerCenterId == c.Id),
|
|
c.AdminUserId))
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return new PagedResult<PartnerCenterListItemDto>(items, total, page, pageSize);
|
|
}
|
|
|
|
public async Task<CenterForBookingDto?> ResolveCenterForBookingAsync(long bookingId, CancellationToken cancellationToken)
|
|
{
|
|
var booking = await DbContext.Set<Booking>().AsNoTracking()
|
|
.Where(b => b.Id == bookingId)
|
|
.Select(b => new { b.NurseId })
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
if (booking is null)
|
|
return null;
|
|
|
|
var centerId = await DbContext.Set<NurseProfile>().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<int?> GetAdminUserIdAsync(long centerId, CancellationToken cancellationToken)
|
|
=> Entities.AsNoTracking()
|
|
.Where(c => c.Id == centerId)
|
|
.Select(c => (int?)c.AdminUserId)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
public async Task<CenterDashboardDto?> 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<NurseProfile>().AsNoTracking()
|
|
.CountAsync(n => n.PartnerCenterId == centerId, cancellationToken);
|
|
|
|
var sponsoredNurses = await DbContext.Set<NurseProfile>().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<Booking>().AsNoTracking()
|
|
.CountAsync(b => DbContext.Set<NurseProfile>().Any(n => n.Id == b.NurseId && n.PartnerCenterId == centerId), cancellationToken);
|
|
|
|
var invoiceCount = await DbContext.Set<Invoice>().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);
|
|
}
|
|
}
|