280 lines
13 KiB
C#
280 lines
13 KiB
C#
#nullable enable
|
|
using Baya.Application.Contracts.Persistence;
|
|
using Baya.Application.Models.Booking;
|
|
using Baya.Application.Models.Common;
|
|
using Baya.Application.Models.Payments;
|
|
using Baya.Domain.Entities.Booking;
|
|
using Baya.Domain.Entities.Identity;
|
|
using Baya.Infrastructure.Persistence.Repositories.Common;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Baya.Infrastructure.Persistence.Repositories;
|
|
|
|
internal sealed class BookingRepository : BaseAsyncRepository<Booking>, IBookingRepository
|
|
{
|
|
public BookingRepository(ApplicationDbContext dbContext) : base(dbContext)
|
|
{
|
|
}
|
|
|
|
public Task AddAsync(Booking booking, CancellationToken cancellationToken)
|
|
=> base.AddAsync(booking);
|
|
|
|
public Task<long?> GetBookingIdByRequestIdAsync(long bookingRequestId, CancellationToken cancellationToken)
|
|
=> TableNoTracking
|
|
.Where(b => b.BookingRequestId == bookingRequestId)
|
|
.Select(b => (long?)b.Id)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
public Task<BookingLedgerAmounts?> GetLedgerAmountsAsync(long id, CancellationToken cancellationToken)
|
|
=> TableNoTracking
|
|
.Where(b => b.Id == id)
|
|
.Select(b => new BookingLedgerAmounts(b.NurseId, b.GrossPriceIrr, b.BalinyaarCommissionIrr, b.NursePayoutAmount))
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
public async Task<BookingDetailProjection?> GetDetailAsync(long id, CancellationToken cancellationToken)
|
|
{
|
|
var row = await (
|
|
from b in TableNoTracking
|
|
where b.Id == id
|
|
join n in DbContext.Set<NurseProfile>() on b.NurseId equals n.Id
|
|
join p in DbContext.Set<Patient>() on b.PatientId equals p.Id
|
|
select new
|
|
{
|
|
Booking = b,
|
|
NurseName = n.User.Name,
|
|
NurseFamily = n.User.FamilyName,
|
|
PatientName = p.DisplayName,
|
|
Sessions = b.Sessions
|
|
.OrderBy(s => s.SessionIndex)
|
|
.Select(s => new BookingSessionProjection(
|
|
s.Id,
|
|
s.SessionIndex,
|
|
s.ScheduledDate,
|
|
s.ScheduledTimeStart,
|
|
s.ScheduledTimeEnd,
|
|
s.Status,
|
|
s.VisitPayoutAmount,
|
|
s.PayoutEligibleAt,
|
|
s.Verification == null ? null : s.Verification.Status,
|
|
s.Verification == null ? null : s.Verification.CheckInAt,
|
|
s.Verification == null ? null : s.Verification.CheckOutAt,
|
|
s.Verification == null ? null : s.Verification.CheckInAddressMatch))
|
|
.ToList()
|
|
})
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
if (row is null)
|
|
return null;
|
|
|
|
var bk = row.Booking;
|
|
return new BookingDetailProjection(
|
|
bk.Id, bk.BookingRequestId, bk.Status, bk.CustomerId, bk.NurseId,
|
|
ComposeName(row.NurseName, row.NurseFamily), bk.PatientId, row.PatientName,
|
|
bk.VariantId, bk.VariantSnapshotJson, bk.CustomerAddressId, bk.AddressSnapshotJson,
|
|
bk.GrossPriceIrr, bk.BalinyaarCommissionIrr, bk.PlatformFeeRate, bk.NursePayoutAmount, bk.PspFeeAmount,
|
|
bk.SessionCount, bk.ScheduledDate, bk.ScheduledTimeStart, bk.ScheduledTimeEnd,
|
|
bk.ConfirmedAt, bk.CompletedAt, bk.CancelledAt, bk.CancelledBy, bk.CancellationReason,
|
|
bk.CancellationPolicyCode, bk.CancellationRefundPercentage, bk.RefundableAmountIrr,
|
|
bk.DisputeWindowEndsAt, bk.CreatedAt, row.Sessions);
|
|
}
|
|
|
|
public Task<PagedResult<BookingListItemDto>> ListForCustomerAsync(long customerId, string? status, int page, int pageSize, CancellationToken cancellationToken)
|
|
=> ListAsync(TableNoTracking.Where(b => b.CustomerId == customerId), status, forNurse: false, page, pageSize, cancellationToken);
|
|
|
|
public Task<PagedResult<BookingListItemDto>> ListForNurseAsync(long nurseId, string? status, int page, int pageSize, CancellationToken cancellationToken)
|
|
=> ListAsync(TableNoTracking.Where(b => b.NurseId == nurseId), status, forNurse: true, page, pageSize, cancellationToken);
|
|
|
|
public Task<PagedResult<BookingListItemDto>> ListAllAsync(string? status, int page, int pageSize, CancellationToken cancellationToken)
|
|
=> ListAsync(TableNoTracking, status, forNurse: false, page, pageSize, cancellationToken);
|
|
|
|
private async Task<PagedResult<BookingListItemDto>> ListAsync(
|
|
IQueryable<Booking> source, string? status, bool forNurse, int page, int pageSize, CancellationToken cancellationToken)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(status))
|
|
source = source.Where(b => b.Status == status);
|
|
|
|
var total = await source.CountAsync(cancellationToken);
|
|
|
|
var rows = await (
|
|
from b in source
|
|
join n in DbContext.Set<NurseProfile>() on b.NurseId equals n.Id
|
|
join p in DbContext.Set<Patient>() on b.PatientId equals p.Id
|
|
orderby b.Id descending
|
|
select new
|
|
{
|
|
b.Id,
|
|
b.Status,
|
|
NurseName = n.User.Name,
|
|
NurseFamily = n.User.FamilyName,
|
|
PatientName = p.DisplayName,
|
|
b.ScheduledDate,
|
|
b.SessionCount,
|
|
b.GrossPriceIrr,
|
|
b.NursePayoutAmount,
|
|
b.DisputeWindowEndsAt,
|
|
b.CreatedAt
|
|
})
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
var items = rows
|
|
.Select(r => new BookingListItemDto(
|
|
r.Id,
|
|
r.Status,
|
|
forNurse ? r.PatientName : ComposeName(r.NurseName, r.NurseFamily),
|
|
r.ScheduledDate,
|
|
r.SessionCount,
|
|
(forNurse ? r.NursePayoutAmount : r.GrossPriceIrr).ToString(),
|
|
r.DisputeWindowEndsAt,
|
|
r.CreatedAt))
|
|
.ToList();
|
|
|
|
return new PagedResult<BookingListItemDto>(items, total, page, pageSize);
|
|
}
|
|
|
|
public Task<BookingParticipants?> GetParticipantsAsync(long bookingId, CancellationToken cancellationToken)
|
|
=> (
|
|
from b in TableNoTracking
|
|
where b.Id == bookingId
|
|
join c in DbContext.Set<CustomerProfile>() on b.CustomerId equals c.Id
|
|
join n in DbContext.Set<NurseProfile>() on b.NurseId equals n.Id
|
|
select new BookingParticipants(c.UserId, n.UserId))
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
public Task<Booking?> GetTrackedWithSessionsAsync(long id, CancellationToken cancellationToken)
|
|
=> Table.Include(b => b.Sessions).FirstOrDefaultAsync(b => b.Id == id, cancellationToken);
|
|
|
|
public Task<Booking?> GetTrackedWithCareAsync(long id, CancellationToken cancellationToken)
|
|
=> Table.Include(b => b.CareInstructions).FirstOrDefaultAsync(b => b.Id == id, cancellationToken);
|
|
|
|
public async Task<Booking?> GetTrackedBookingBySessionAsync(long sessionId, CancellationToken cancellationToken)
|
|
{
|
|
var bookingId = await DbContext.Set<BookingSession>()
|
|
.Where(s => s.Id == sessionId)
|
|
.Select(s => (long?)s.BookingId)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
if (bookingId is not { } bid)
|
|
return null;
|
|
|
|
return await Table
|
|
.Include(b => b.Sessions).ThenInclude(s => s.Verification)
|
|
.FirstOrDefaultAsync(b => b.Id == bid, cancellationToken);
|
|
}
|
|
|
|
public Task<CareInstructionsGate?> GetCareInstructionsGateAsync(long bookingId, CancellationToken cancellationToken)
|
|
=> TableNoTracking
|
|
.Where(b => b.Id == bookingId)
|
|
.Select(b => new CareInstructionsGate(
|
|
b.Status,
|
|
b.NurseId,
|
|
b.CustomerId,
|
|
b.CareInstructions == null
|
|
? null
|
|
: new CareInstructionsDto(
|
|
b.Id,
|
|
b.CareInstructions.CurrentConditions,
|
|
b.CareInstructions.Medications,
|
|
b.CareInstructions.Allergies,
|
|
b.CareInstructions.SpecialInstructions,
|
|
b.CareInstructions.EmergencyContactName,
|
|
b.CareInstructions.EmergencyContactPhone)))
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
public async Task<PagedResult<BookingSessionListItemDto>> ListSessionsForNurseAsync(
|
|
long nurseId, DateOnly? date, int page, int pageSize, CancellationToken cancellationToken)
|
|
{
|
|
var query = DbContext.Set<BookingSession>().AsNoTracking()
|
|
.Where(s => s.Booking.NurseId == nurseId);
|
|
|
|
if (date is { } d)
|
|
query = query.Where(s => s.ScheduledDate == d);
|
|
|
|
var total = await query.CountAsync(cancellationToken);
|
|
|
|
var items = await (
|
|
from s in query
|
|
join p in DbContext.Set<Patient>() on s.Booking.PatientId equals p.Id
|
|
orderby s.ScheduledDate, s.ScheduledTimeStart
|
|
select new BookingSessionListItemDto(
|
|
s.Id,
|
|
s.BookingId,
|
|
s.SessionIndex,
|
|
p.DisplayName,
|
|
s.ScheduledDate,
|
|
s.ScheduledTimeStart,
|
|
s.ScheduledTimeEnd,
|
|
s.Status,
|
|
s.Verification == null ? VisitVerificationStatus.Pending : s.Verification.Status))
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return new PagedResult<BookingSessionListItemDto>(items, total, page, pageSize);
|
|
}
|
|
|
|
public Task<EvvGate?> GetEvvForSessionAsync(long sessionId, CancellationToken cancellationToken)
|
|
=> DbContext.Set<BookingSession>().AsNoTracking()
|
|
.Where(s => s.Id == sessionId)
|
|
.Select(s => new EvvGate(
|
|
s.Booking.NurseId,
|
|
s.Booking.CustomerId,
|
|
s.Verification == null
|
|
? null
|
|
: new VisitVerificationDto(
|
|
s.Verification.Id,
|
|
s.Id,
|
|
s.Verification.Status,
|
|
s.Verification.CheckInAt,
|
|
s.Verification.CheckInLat,
|
|
s.Verification.CheckInLng,
|
|
s.Verification.CheckOutAt,
|
|
s.Verification.CheckOutLat,
|
|
s.Verification.CheckOutLng,
|
|
s.Verification.CheckInAddressMatch,
|
|
s.Verification.CheckInDistanceMeters)))
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
public async Task<PagedResult<AdminEvvItemDto>> ListAdminEvvAsync(string type, int page, int pageSize, CancellationToken cancellationToken)
|
|
{
|
|
var query = DbContext.Set<BookingSession>().AsNoTracking().AsQueryable();
|
|
|
|
// The two admin review lanes: an advisory location mismatch, or a no-show (missed) session.
|
|
query = type == "no_show"
|
|
? query.Where(s => s.Status == BookingSessionStatus.Missed)
|
|
: query.Where(s => s.Verification != null && s.Verification.CheckInAddressMatch == false);
|
|
|
|
var total = await query.CountAsync(cancellationToken);
|
|
|
|
var items = await query
|
|
.OrderByDescending(s => s.Id)
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.Select(s => new AdminEvvItemDto(
|
|
s.Id,
|
|
s.BookingId,
|
|
s.Booking.NurseId,
|
|
s.Status,
|
|
s.ScheduledDate,
|
|
s.ScheduledTimeStart,
|
|
s.Verification == null ? null : s.Verification.CheckInAt,
|
|
s.Verification == null ? null : s.Verification.CheckInAddressMatch,
|
|
s.Verification == null ? null : s.Verification.CheckInDistanceMeters))
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return new PagedResult<AdminEvvItemDto>(items, total, page, pageSize);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<BookingSession>> GetNoShowCandidatesAsync(DateOnly today, int batchSize, CancellationToken cancellationToken)
|
|
=> await DbContext.Set<BookingSession>()
|
|
.Include(s => s.Booking)
|
|
.Where(s => s.Status == BookingSessionStatus.Scheduled && s.ScheduledDate <= today)
|
|
.OrderBy(s => s.Id)
|
|
.Take(batchSize)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
private static string ComposeName(string? name, string? familyName)
|
|
=> string.Join(' ', new[] { name, familyName }.Where(s => !string.IsNullOrWhiteSpace(s))).Trim();
|
|
}
|