Zmodyfikuj kod, aby metoda NotifyVisitMovedAsync w metodzie MoveValidatedAsync nie wykonywała się, kiedy oldVisitData i visitMoveData mają taką samą datę, godzinę oraz miejsce.
	
	private Task<Either<VisitOperationSet<PatientVisitResponseData>>> MoveValidatedAsync(ResolvedPatientVisit resolvedVisit, OldVisitData oldVisitData, VisitMoveData visitMoveData,
        Guid? visitLockId)
    {
        var createInput = new CreateValidatedVisitInput(resolvedVisit)
        {
            LockId = visitLockId,
            WithDependencies = !oldVisitData.MimicUpdate,
            SkipAutoConfirm = oldVisitData.MimicUpdate,
            OldVisitId = oldVisitData.Id
        };
        return CancelVisitWithDependencies(oldVisitData.Id, visitMoveData.Source, oldVisitData.Reason)
            .FlatMapAsync(cancelOps => CreateValidatedAsync(createInput)
                .TransparentExecuteAsync(visit => SwitchConfirmationsIfNeeded(oldVisitData.Id, visit.Value.MasterVisit.Entity, oldVisitData.MimicUpdate))
                .TransparentExecuteAsync(visit => SwitchDependentEntities(oldVisitData, visit.Value))
                .TransparentExecuteAsync(visit => SwitchDependentVisitsIfNeeded(oldVisitData.Id, visit.Value.MasterVisit.Entity, cancelOps.VisitIds))
                .TransparentExecuteAsync(
                    async visit => { await _patientVisitNotificationService.NotifyVisitMovedAsync(visit.Value.MasterVisit.Entity, visitMoveData.Source, oldVisitData.Id); })
                .TransparentExecuteAsync(newVisit => LogMoveVisitEvent(oldVisitData.Entity, newVisit.Value.MasterVisit, oldVisitData.MimicUpdate, resolvedVisit.Force))
                .MapAsync(createOperationSet => createOperationSet.Merge(cancelOps).Map(v => v.MasterVisit))
            );
    }
	
	/// OldVisitData.cs
	
	public record OldVisitData(IShallowPatientVisit Entity, VisitCancellationReason Reason, ICollection<PatientVisitConfirmationRequest> ConfirmationRequests)
{

    public long Id => Entity.Id;
    public ICollection<DependentVisit> DependentVisits { get; init; } = new List<DependentVisit>();

    public bool MimicUpdate => Reason == VisitCancellationReason.VisitMovedMimicUpdate;

    public record DependentVisit(long Id, long MedicalServiceId);

    public VisitInputResolverOldVisitData ResolverOldVisitData => new(Id) { MimicUpdate = MimicUpdate };
};

/// PatientVisitsDbAdapter.cs

#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Abp.Application.Services.Dto;
using Abp.Domain.Repositories;
using Abp.EntityFrameworkCore.Extensions;
using Abp.Linq.Extensions;
using Medica.Authentication.PublicUsers;
using Medica.Commerce.PublicCommon;
using Medica.Commerce.Schedules.SchedulesLocking;
using Medica.Commerce.Visits.Process.Payments;
using Medica.Commerce.VisitsSearch.CoreQueries;
using Medica.Commerce.VisitsSearch.PatientVisitQueries;
using Medica.Common.Extensions;
using Medica.Communication.CallCenter.Extensions;
using Medica.Extensions;
using Medica.Helpers;
using Medica.Helpers.Database;
using Medica.Patients;
using Medica.Patients.PatientCareData;
using Medica.PatientVisits;
using Medica.PatientVisits.Extensions;
using Medica.PatientVisits.HospitalWards.Repository;
using Medica.Telemetry;
using Microsoft.EntityFrameworkCore;

namespace Medica.Commerce.VisitsSearch;

public class PatientVisitsDbAdapter : DbAdapterBase<PatientVisit>, IPatientVisitsDbAdapter
{
    private readonly IRepository<Guardian, Guid> _guardianRepository;
    private readonly IRepository<PublicUserChild, Guid> _publicUserChildRepository;
    private readonly ITimeProvider _timeProvider;

    public PatientVisitsDbAdapter(
        IRepository<PatientVisit, long> repository,
        IEntityPropsCache entityPropsCache,
        ITelemetry telemetry,
        IRepository<Guardian, Guid> guardianRepository,
        IRepository<PublicUserChild, Guid> publicUserChildRepository,
        ITimeProvider timeProvider) : base(repository, entityPropsCache, telemetry)
    {
        _guardianRepository = guardianRepository;
        _publicUserChildRepository = publicUserChildRepository;
        _timeProvider = timeProvider;
    }

    private static IQueryable<PatientVisit> AddSearchIncludes(IQueryable<PatientVisit> baseQuery)
    {
        return baseQuery
            .AsSplitQuery()
            .Include(v => v.OrganizationUnit)
            .Include(v => v.Patient)
            .Include(v => v.MedicalService)
            .Include(v => v.User).ThenInclude(v => v!.MedicalStaffData)
            .Include(v => v.ConfirmationRequests)
            .Include(v => v.Institution).ThenInclude(v => v!.SpecializationOrgUnits).ThenInclude(v => v.Specialization);
    }

    private static IQueryable<PatientVisit> AddPublicSearchIncludes(IQueryable<PatientVisit> baseQuery)
    {
        return baseQuery
            .AsSplitQuery()
            .Include(v => v.Patient)
            .Include(v => v.DependentVisits!.Where(d => d.Entity!.Status != PatientVisitStatus.Cancelled))
            .Include(v => v.MasterVisit);
    }

    protected override IQueryable<PatientVisit> AddIncludes(IQueryable<PatientVisit> baseQuery)
    {
        return AddSearchIncludes(baseQuery);
    }

    private static IEnumerable<Expression<Func<PatientVisit, bool>>> GetTimeRangeExpressions(IDateTimeVisitQuery query)
    {
        if (query.TimeRange.FromTime is { } fromTime)
        {
            var fromHours = fromTime.Hours;
            var fromMinutes = fromTime.Minutes;
            yield return v =>
                v.From.Hour > fromHours || (v.From.Hour == fromHours && v.From.Minute >= fromMinutes) ||
                (query.IncludeOffSchedule && v.SlotReferenceDate != null && v.SlotReferenceDate != v.From &&
                 v.SlotReferenceDate.Value.Date == v.From.Date && // avoid false positives if visit is moved outside portal
                 (v.SlotReferenceDate.Value.Hour > fromHours || (v.SlotReferenceDate.Value.Hour == fromHours && v.SlotReferenceDate.Value.Minute >= fromMinutes)));
        }

        if (query.TimeRange.ToTime is { } toTime)
        {
            var toHours = toTime.Hours;
            var toMinutes = toTime.Minutes;
            yield return v =>
                (!query.FullFit && (v.From.Hour < toHours || (v.From.Hour == toHours && v.From.Minute <= toMinutes))) ||
                (v.To.Hour < toHours || (v.To.Hour == toHours && v.To.Minute <= toMinutes)) ||
                (query.IncludeOffSchedule && v.SlotReferenceDate != null && v.SlotReferenceDate != v.From && v.SlotReferenceDate.Value.Date == v.From.Date &&
                 (v.SlotReferenceDate.Value.Hour < toHours || (v.SlotReferenceDate.Value.Hour == toHours && v.SlotReferenceDate.Value.Minute <= toMinutes)));
        }
    }


    private async Task<ISet<long>?> ResolvePatientId(SinglePatientVisitQuery query)
    {
        if (query.PatientId is not { } patientId) return null;
        if (query.PublicUserChildId != null) return patientId.AsSet();
        var idsByCareTakerDirectory = query.IncludeChildrenMode.HasFlag(PatientVisitIncludeChildrenMode.DirectoryCareTakers)
            ? await _guardianRepository.GetAll().AsNoTracking()
                .Where(v => v.AccountId == patientId)
                .Select(v => v.CareTakerId)
                .ToListAsync()
            : new List<long>();

        var idsByPublicUserChild = query.PublicUserAccountId is { } publicUserAccountId &&
                                   query.IncludeChildrenMode.HasFlag(PatientVisitIncludeChildrenMode.PublicUserChildren)
            ? await _publicUserChildRepository.GetAll().AsNoTracking()
                .Where(v => v.PublicUserId == publicUserAccountId)
                .Where(v => v.PatientId.HasValue)
                .Select(v => v.PatientId!.Value)
                .ToListAsync()
            : new List<long>();


        return idsByCareTakerDirectory.Concat(idsByPublicUserChild).ConcatSingle(patientId).ToHashSet();
    }

    private async Task<Expression<Func<PatientVisit, bool>>> GetUserVisitsIdentityExpression(SinglePatientVisitQuery query)
    {
        var patientIds = await ResolvePatientId(query);
        if (patientIds != null && query.PublicUserAccountId != null)
        {
            if (query.IncludeChildrenMode.HasFlag(PatientVisitIncludeChildrenMode.PublicUserChildren) && query.PublicUserChildId == null)
            {
                return v => (v.PatientId != null && patientIds.Contains(v.PatientId.Value)) ||
                            (v.PatientId == null && v.PublicUserAccountId == query.PublicUserAccountId);
            }

            return v => (v.PatientId != null && patientIds.Contains(v.PatientId.Value)) ||
                        (v.PatientId == null && v.PublicUserAccountId == query.PublicUserAccountId && v.PublicUserChildId == query.PublicUserChildId);
        }

        if (patientIds != null)
        {
            return v => v.PatientId != null && patientIds.Contains(v.PatientId.Value);
        }

        if (query.PatientWithoutId != null)
        {
            if (string.IsNullOrWhiteSpace(query.PatientWithoutId.Name))
                throw new ArgumentException(nameof(query.PatientWithoutId.Name));

            if (string.IsNullOrWhiteSpace(query.PatientWithoutId.PhoneNumber))
                throw new ArgumentException(nameof(query.PatientWithoutId.PhoneNumber));

            var parsedPatientName = query.PatientWithoutId.Name.ParsePatientName();
            var patientName = string.Join(' ', new[] { parsedPatientName.Name, parsedPatientName.Surname }.Where(v => !string.IsNullOrEmpty(v)));
            var reversedPatientName = string.Join(' ', new[] { parsedPatientName.Surname, parsedPatientName.Name }.Where(v => !string.IsNullOrEmpty(v)));

            return v => v.PatientId == null
                        && v.PatientName != null && (EF.Functions.ILike(v.PatientName, patientName) || EF.Functions.ILike(v.PatientName, reversedPatientName))
                        && v.PhoneNumber != null && v.PhoneNumber == query.PatientWithoutId.PhoneNumber;
        }

        if (query.PublicUserAccountId != null)
        {
            if (query.IncludeChildrenMode.HasFlag(PatientVisitIncludeChildrenMode.PublicUserChildren) && query.PublicUserChildId == null)
            {
                return v => v.PatientId == null && v.PublicUserAccountId == query.PublicUserAccountId;
            }

            return v => v.PatientId == null && v.PublicUserAccountId == query.PublicUserAccountId && v.PublicUserChildId == query.PublicUserChildId;
        }

        return v => false;
    }

    public async Task<Either<int>> GetSinglePatientVisitsCountAsync(SinglePatientVisitQuery query)
    {
        var queryMode = await BaseSinglePatientVisitsQuery(query);
        return await CountAsync(dbQuery => queryMode(dbQuery).Where(v => v.Status == PatientVisitStatus.Upcoming));
    }

    private async Task<Func<IQueryable<PatientVisit>, IQueryable<PatientVisit>>> BaseSinglePatientVisitsQuery(SinglePatientVisitQuery query)
    {
        var patientIdEx = await GetUserVisitsIdentityExpression(query);
        return dbQuery =>
        {
            var baseQuery = dbQuery
                .WithNonEmptyGenericAppointmentTypeId(skipFilter: query.IncludeExternal)
                .Where(patientIdEx)
                .Where(v => v.Status != PatientVisitStatus.Cancelled)
                .WhereIf(query.VisitStatus != null, v => query.VisitStatus!.Contains(v.Status))
                .WhereIf(query.OnlyBeforeCancellationThreshold != null, v => v.CancellationThresholdDate == null || v.CancellationThresholdDate > query.OnlyBeforeCancellationThreshold)
                .WhereIf(query.FromDate != null, v => v.From >= query.FromDate)
                .WhereIf(query.ExcludeId != null, v => v.Id != query.ExcludeId)
                .WhereIf(query.ToDate != null, v => v.From <= query.ToDate)
                .WhereIf(query.NoWardVisits, v => v.HospitalWardId == null)
                .WhereIf(query.SpecializationType != null, v => v.Specialization!.Type == query.SpecializationType)
                .WhereIf(query.SpecializationId != null, v => v.SpecializationId == query.SpecializationId);


            return baseQuery;
        };
    }


    public Task<Either<PatientVisit>> GetForNotificationAsync(long id)
    {
        return GetSingleAsync(id, baseQuery => baseQuery
            .AsSplitQuery()
            .Include(v => v.AppointmentType).ThenInclude(v => v!.MedicalServices)!.ThenInclude(v => v.MedicalService!.FileBucket!.Files)
            .Include(v => v.Specialization)
            .Include(v => v.MedicalService).ThenInclude(v => v!.FileBucket!.Files)
            .Include(v => v.Patient)
            .Include(v => v.OrganizationUnit)
            .Include(v => v.DependentVisits)!.ThenInclude(v => v.Entity!.Specialization)
            .Include(v => v.DependentVisits)!.ThenInclude(v => v.Entity!.AppointmentType)
        );
    }

    public Task<Either<IList<PatientVisit>>> GetForPaymentResolution(ISet<long> ids)
    {
        return GetListAsync(baseQuery => WithIdFilter(ids.ToList(), baseQuery)
            .AsSplitQuery()
            .Include(v => v.PatientCare!.PrinterJobSet!.Jobs)
            .Include(v => v.Patient)
            .Include(v => v.NoShowDebt!.PrinterJobSet!.Jobs)
            .Include(v => v.OrganizationUnit)
            .Include(v => v.Specialization)
        );
    }


    public Task<Either<PatientVisit>> GetForConfirmation(long id) => GetSingleAsync(id, dbQuery => dbQuery.Include(v => v.ConfirmationRequests), true);
    public Task<Either<PatientVisit>> GetForMove(long id) => GetSingleAsync(id, dbQuery => dbQuery
        .Include(v => v.ConfirmationRequests)
        .Include(v => v.VisitPrenatalProgram)
        .Include(v => v.DependentVisits!.Where(dv => dv.Entity!.Status == PatientVisitStatus.Upcoming))
    );


    public Task<Either<PatientVisit>> GetForPaymentModesAllowed(long id, long specializationId) => GetSingleAsync(id, dbQuery => dbQuery
        .Include(v => v.Specialization)
        .Include(v => v.AppointmentType)
        .Include(v => v.Staff!.Specializations!.Where(ss => ss.SpecializationId == specializationId))
        .Include(v => v.OrganizationUnit!.SpecializationOrgUnits!.Where(sou => sou.SpecializationId == specializationId))
    );

    public Task<Either<PatientVisit>> GetForZlaAssembly(long id, bool includeCare) => GetSingleAsync(id, q => q
        .Include(v => v.OrganizationUnit)
        .Include(v => v.Staff)
        .Include(v => v.Patient)
        .Include(v => v.Specialization)
        .IncludeIf(includeCare, v => v.PatientCare!.Diagnoses)
    );


    public Task<Either<PatientVisit>> GetForCancellation(long id) => GetSingleAsync(id, dbQuery => dbQuery.Include(v => v.DependentVisits));

    public Task<Either<PatientVisit>> GetForCardView(long id, bool includeReferrals)
    {
        return GetSingleAsync(id, baseQuery => baseQuery
            .AsSplitQuery()
            .Include(v => v.Patient).ThenInclude(v => v!.PatientDeclarations)
            .IncludeIf(includeReferrals, v => v.Referrals)
            .IncludeIf(includeReferrals, v => v.ReferralRequests)
            .Include(v => v.ConfirmationRequests)
            .Include(v => v.VisitPrenatalProgram)
            .IncludeCheckIns()
            .Include(v => v.Survey)
            .Include(v => v.HospitalWardVisit!.HospitalWardStay)
            .Include(v => v.DependentVisits)!.ThenInclude(v => v.DependentMedicalService)
            .Include(v => v.DependentVisits)!.ThenInclude(v => v.DependentService!.OccupationalMedicineInfo)
            .Include(v => v.DependentVisits)!.ThenInclude(v => v.Entity)
            .Include(v => v.MasterVisit).ThenInclude(v => v!.DependentMedicalService)
            .Include(v => v.MasterVisit).ThenInclude(v => v!.DependentService!.OccupationalMedicineInfo)
            .Include(v => v.MasterVisit).ThenInclude(v => v!.MasterVisit)
        );
    }

    public Task<Either<PatientVisit>> GetForCardViewSummary(long id)
    {
        return GetSingleAsync(id, baseQuery => baseQuery
            .Include(v => v.Patient)
            .Include(v => v.DependentVisits)!.ThenInclude(v => v.Entity)
            .Include(v => v.DependentVisits)!.ThenInclude(v => v.DependentService!.OccupationalMedicineInfo)
        );
    }


    public Task<Either<PatientVisit>> GetWithCareData(long id)
    {
        return GetSingleAsync(id, baseQuery => baseQuery
            .AsSplitQuery()
            .Include(v => v.Institution)
            .IncludeCheckIns()
            .Include(v => v.Patient!.Guardians)!.ThenInclude(v => v.Account)
            .Include(v => v.Specialization!.HostCompany)
            .Include(v => v.Staff!.Accounting)
            .Include(v => v.MasterVisit)
            .Include(v => v.MedicalService!.OccupationalMedicineInfo)
            .Include(v => v.PatientCare!.PatientCarePriceListItems)!.ThenInclude(v => v.PriceListItem!.HostPriceListItemEntries)!.ThenInclude(v => v.PriceListVersion)
            .Include(v => v.PatientCare!.PatientCarePriceListItems)!.ThenInclude(v => v.PriceListItem!.ForeignPriceListItem)
            .Include(v => v.PatientCare!.Procedures)
            .Include(v => v.PatientCare!.Diagnoses)
            .Include(v => v.PatientCare!.PrinterJobSet!.Jobs)
            .Include(v => v.PatientCare!.NfzCare!.Items)
            .Include(v => v.PatientCare!.NfzCare!.Staff)
            .Include(v => v.PatientCare!.DentalCare).ThenInclude(v => v!.DentalDiagram)
        );
    }

    public async Task<Either<IList<PatientVisit>>> RescheduleVisitsSearchAsync(VisitRescheduleQuery query, bool returnTracking = true)
    {
        try
        {
            var baseQuery = returnTracking ? _repository.GetAll() : _repository.GetAll().AsNoTracking();
            baseQuery = baseQuery
                .AsSplitQuery()
                .Include(v => v.Patient);

            var dbResult = await GetTimeRangeExpressions(query)
                .Concat(query.GetDateRangeExpressions())
                .Aggregate(baseQuery, (current, filter) => current.Where(filter))
                .Where(v => v.StaffId == query.UserId)
                .Where(v => v.Status != PatientVisitStatus.Closed && v.Status != PatientVisitStatus.Cancelled)
                .WhereIf(query.ExcludedIds != null, v => !query.ExcludedIds!.Contains(v.Id))
                .OrderBy(v => v.From)
                .ToListAsync();
            return Either.Success(dbResult);
        }
        catch (Exception e)
        {
            return Either<IList<PatientVisit>>.InternalError(e);
        }
    }


    public Task<Either<PagedResultDto<PatientVisit>>> SearchAsync(PatientVisitsQuery query) => SearchInternalAsync(query, v => v);


    public Task<Either<PagedResultDto<PatientVisit>>> SearchAsync(PatientAccountingQuery query)
    {
        return PagedNotFilteredSearchAsync(query, baseQuery =>
        {
            var removedPaymentStatusSet = PatientCarePriceListItem.RemovedPaymentStatusSet.ToHashSet();
            var visitStatus = new HashSet<PatientVisitStatus> { PatientVisitStatus.Closed, PatientVisitStatus.Pending, PatientVisitStatus.CheckedIn };
            var filteredQuery = query.GetDateRangeExpressions().Aggregate(baseQuery, (current, filter) => current.Where(filter))
                .AsSplitQuery()
                .Include(v => v.PatientCare).ThenInclude(v => v!.PatientCarePriceListItems!).ThenInclude(v => v.PriceListItem!).ThenInclude(v => v.ForeignPriceListItem)
                .Include(v => v.PatientCare).ThenInclude(v => v!.PrinterJobSet!).ThenInclude(v => v.Jobs)
                .Include(v => v.AppointmentType)
                .Include(v => v.Patient)
                .Where(v => visitStatus.Contains(v.Status))
                .WithNonEmptyGenericAppointmentTypeId()
                .WhereIf(query.OrganizationUnitId != null, v => v.OrganizationUnitId == query.OrganizationUnitId)
                .WhereIf(query.PatientIds != null, v => v.PatientId != null && query.PatientIds!.Contains(v.PatientId.Value))
                .WhereIf(query.Status != null, v => query.Status!.Contains(v.Status))
                .WhereIf(!string.IsNullOrWhiteSpace(query.ReceiptId), v => v.PatientCare!.PrinterJobSet!.Jobs!.Any(j => j.ReceiptGlobalId == query.ReceiptId))
                .WhereIf(query.StaffId != null, v => v.StaffId == query.StaffId)
                .WhereIf(query.SpecializationId != null, v => v.SpecializationId == query.SpecializationId)
                .WhereIf(!query.IncludeProcessed,
                    v => v.PatientCare!.PatientCarePriceListItems!.Any(i => i.PaymentStatus == PriceItemPaymentStatus.NeedsPayment || i.PaymentStatus == PriceItemPaymentStatus.NeedsVerification))
                .WhereIf(!query.KioskCheckoutDestination, v => v.PatientCare!.PatientCarePriceListItems!.Any(i => !removedPaymentStatusSet.Contains(i.PaymentStatus)))
                .WhereIf(query.KioskCheckoutDestination,
                    v => v.From.Date == _timeProvider.Now().Date || v.PatientCare!.PatientCarePriceListItems!.Any(i => i.PaymentStatus == PriceItemPaymentStatus.NeedsPayment));

            if (query.SearchPhrase is { } searchPhrase && !string.IsNullOrWhiteSpace(searchPhrase))
            {
                var chunks = searchPhrase.Split(" ").Select(v => v.Trim()).ToList();
                var priceItemStringExp = new QueryableSearchExtensions.ColumnSearch<PatientVisit>(v => v.PatientCare!.PatientCarePriceListItems!.Select(i => i.PriceListItem!.Name),
                    QueryableSearchExtensions.WildcardType.Contains);
                filteredQuery = filteredQuery.ToSearchQueryable(chunks, VisitStringSearchExpressions().Concat(priceItemStringExp.AsList()).ToArray());
            }

            return filteredQuery.OrderByDescending(v => v.From);
        });
    }

    private static IEnumerable<QueryableSearchExtensions.ColumnSearch<PatientVisit>> VisitStringSearchExpressions()
    {
        return new QueryableSearchExtensions.ColumnSearch<PatientVisit>[]
        {
            new(v => v.User!.Name, QueryableSearchExtensions.WildcardType.StartsWith),
            new(v => v.User!.Surname, QueryableSearchExtensions.WildcardType.StartsWith),
            new(v => v.Patient!.Name, QueryableSearchExtensions.WildcardType.StartsWith),
            new(v => v.Patient!.Surname, QueryableSearchExtensions.WildcardType.StartsWith),
            new(v => v.Patient!.Pesel, QueryableSearchExtensions.WildcardType.Contains)
        };
    }

    public Task<Either<PagedResultDto<PatientVisit>>> SearchAsync(VisitsToConfirmQuery query)
    {
        return PagedSearchAsync(query, baseQuery =>
        {
            var filteredQuery = query.GetDateRangeExpressions().Aggregate(baseQuery, (current, filter) => current.Where(filter))
                .AsSplitQuery()
                .Include(v => v.Patient)
                .Include(v => v.ConfirmationRequests!.Where(cr => cr.Type == PatientVisitConfirmationType.ConfirmVisit))
                .WhereIf(query.VisitId.HasValue, v => v.Id == query.VisitId!.Value)
                .Where(v => v.Status == PatientVisitStatus.Upcoming)
                .Where(v => v.ConfirmationRequests!.Any(cr => cr.Type == PatientVisitConfirmationType.ConfirmVisit && cr.Result == null))
                .Where(v => v.PatientName != SchedulesLockService._lockPatientName)
                .WithNonEmptyGenericAppointmentTypeId();
            if (query.SearchPhrase is { } searchPhrase && !string.IsNullOrWhiteSpace(searchPhrase))
            {
                var chunks = searchPhrase.Split(" ").Select(v => v.Trim()).ToList();
                filteredQuery = filteredQuery.ToSearchQueryable(chunks, VisitStringSearchExpressions().ToArray());
            }

            return filteredQuery.OrderByDescending(v => v.From);
        });
    }

    public Task<Either<IList<PatientVisit>>> SearchAsync(SimpleStaffVisitsQuery query)
    {
        return GetListAsync(dbQuery =>
        {
            var filteredQuery = query.GetDateRangeExpressions().Aggregate(dbQuery, (current, filter) => current.Where(filter))
                .AsSplitQuery()
                .Include(v => v.Patient)
                .Where(v => v.StaffId == query.StaffId)
                .WhereIf(query.VisitStatus != null, v => query.VisitStatus!.Contains(v.Status))
                .Where(v => v.PatientName != SchedulesLockService._lockPatientName);

            return filteredQuery.OrderByDescending(v => v.From);
        });
    }

    public Task<Either<IList<PatientVisit>>> ListGroupAsync(Guid groupId)
    {
        return GetListAsync(dbQuery =>
        {
            var filteredQuery = dbQuery
                .AsSplitQuery()
                .Include(v => v.Patient)
                .Where(v => v.Status != PatientVisitStatus.Cancelled)
                .Where(v => v.GroupId == groupId);
            return filteredQuery.OrderByDescending(v => v.From);
        });
    }


    private Task<Either<PagedResultDto<PatientVisit>>> SearchInternalAsync(PatientVisitsQuery query, Func<IQueryable<PatientVisit>, IQueryable<PatientVisit>> extraFilter)
    {
        return PagedSearchAsync(query, baseQuery =>
        {
            var queryWithIncludes = baseQuery
                .AsSplitQuery()
                .IncludeIf(query.IncludePatient, v => v.Patient)
                .IncludeIf(query.IncludeConfirmations, v => v.ConfirmationRequests!.Where(cr => cr.Type == PatientVisitConfirmationType.ConfirmVisit));

            var filteredQuery = query.ApplyFilter(queryWithIncludes);
            return query.GetPatientExpressions().Concat(query.GetDateRangeExpressions())
                .Aggregate(extraFilter(filteredQuery), (current, filter) => current.Where(filter))
                .Where(v => v.PatientName != SchedulesLockService._lockPatientName)
                .Where(v => v.OrganizationUnitId != null)
                .OrderByWithParam(v => v.From, query.NewestFirst);
        });
    }


    public Task<Either<PagedResultDto<PatientVisit>>> SearchVisitsToRescheduleAsync(PatientVisitsQuery query)
    {
        return SearchInternalAsync(query, dbQuery => dbQuery
            .Where(v => v.Status != PatientVisitStatus.Cancelled)
            .WithNonEmptyGenericAppointmentTypeId()
            .Where(v => v.RescheduleReason != null));
    }

    public Task<Either<PagedResultDto<PatientVisit>>> SearchAsync(StaffVisitsQuery query)
    {
        return PagedNotFilteredSearchAsync(query, baseQuery =>
        {
            var withIncludes = baseQuery
                .Include(v => v.Patient)
                .Include(v => v.Survey)
                .Include(v => v.MasterVisit)
                .IncludeCheckIns()
                .AsQueryable();

            return query.GetDateRangeExpressions()
                .Aggregate(withIncludes, (current, filter) => current.Where(filter))
                .AsSplitQuery()
                .Where(v => v.Status != PatientVisitStatus.Cancelled)
                .WhereIf(!query.ShowClosed, v => v.Status != PatientVisitStatus.Closed)
                .WhereIf(query.PaymentMode != null, v => query.PaymentMode!.Contains(v.PaymentMode))
                .Where(v => v.StaffId == query.StaffId)
                .Where(v => v.OrganizationUnitId != null)
                .OrderBy(v => v.From);
        });
    }

    public Task<Either<IList<PatientVisit>>> SearchForAccountingAsync(StaffAccountingVisitsQuery query)
    {
        return GetListAsync(baseQuery => baseQuery
            .Include(v => v.Patient)
            .Include(v => v.PatientCare!.PatientCarePriceListItems)!.ThenInclude(v => v.PriceListItem!.HostPriceListItemEntries)!.ThenInclude(v => v.PriceListVersion)
            .Include(v => v.PatientCare!.PatientCarePriceListItems)!.ThenInclude(v => v.WithdrawCashEntry)
            .Where(v => (v.From < query.DateRange.EndTime && v.From >= query.DateRange.StartTime) ||
                        v.PatientCare!.PatientCarePriceListItems!.Any(i =>
                            i.WithdrawCashEntry!.CreationTime >= query.DateRange.StartTime && i.WithdrawCashEntry.CreationTime < query.DateRange.EndTime) ||
                        v.PatientCare!.PatientCarePriceListItems!.Any(i =>
                            i.StatusModificationTime < query.DateRange.EndTime && i.StatusModificationTime >= query.DateRange.StartTime && PatientCarePriceListItem.RemovedPaidPaymentStatusSet.Contains(i.PaymentStatus))
            )
            .Where(v => v.Status != PatientVisitStatus.Cancelled)
            .Where(v => v.StaffId == query.StaffId)
            .Where(v => v.PatientCareId != null)
            .Where(v => v.OrganizationUnitId == query.OrgUnitId)
            .OrderBy(v => v.From));
    }


    public async Task<Either<PagedResultDto<PatientVisit>>> SinglePatientVisitsSearchAsync(PagedSinglePatientVisitQuery query, bool resolveCare = false, bool resolveConfirmations = false)
    {
        var queryMode = await BaseSinglePatientVisitsQuery(query);

        return await PagedNotFilteredSearchAsync(query, baseQuery =>
        {
            var queryWithIncludes = AddPublicSearchIncludes(baseQuery)
                .IncludeIf(resolveCare, v => v.PatientCare);

            if (resolveConfirmations)
            {
                queryWithIncludes = queryWithIncludes
                    .Include(v => v.ConfirmationRequests);
            }

            return queryMode(queryWithIncludes)
                .WhereIf(query.VisitId != null, v => v.Id == query.VisitId)
                .OrderByWithParam(v => v.From, query.NewestFirst);
        });
    }

    public async Task<Either<IList<PatientVisit>>> SinglePatientVisitsSearchAsync(SinglePatientVisitQuery query)
    {
        var queryMod = await BaseSinglePatientVisitsQuery(query);
        return await GetListAsync(baseQuery => queryMod(baseQuery).OrderBy(v => v.From));
    }

    public Task<Either<IList<long>>> TechnicalVisitsSearchAsync(IHavePublicPatientId publicPatientId, DateTime threshold, int limit)
    {
        return GetListAsync(baseQuery => baseQuery
            .Where(v => v.PublicUserAccountId == publicPatientId.PublicUserAccountId)
            .WhereIf(publicPatientId.IsChild(), v => v.PublicUserChildId == publicPatientId.PatientPublicId)
            .WhereIf(!publicPatientId.IsChild(), v => v.PublicUserChildId == null)
            .Where(v => v.PatientId == null)
            .Where(v => v.Status == PatientVisitStatus.Upcoming)
            .Where(v => v.From > threshold)
            .OrderBy(v => v.From)
            .Take(limit)
            .Select(v => v.Id)
        );
    }

    public Task<Either<IList<long>>> FilterByWardMode(ISet<long> ids, HospitalWardVisitMode mode) =>
        GetIdsAsync(dbQuery => WithIdFilter(ids.ToList(), dbQuery)
            .WhereIf(mode == HospitalWardVisitMode.None, v => v.HospitalWardVisit == null || v.HospitalWardVisit.Mode == mode)
            .WhereIf(mode != HospitalWardVisitMode.None, v => v.HospitalWardVisit != null && v.HospitalWardVisit.Mode == mode)
        );
}

/// PatientVisit.cs

#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text.RegularExpressions;
using Abp.Auditing;
using Abp.Domain.Entities.Auditing;
using Abp.Organizations;
using Medica.Authentication.Users;
using Medica.Commerce.AppointmentTypes;
using Medica.Commerce.Hospital.Config.Repository;
using Medica.Commerce.IcdCodes;
using Medica.Commerce.MedicalServices;
using Medica.Commerce.MedicalStaff;
using Medica.Commerce.OccupationalMedicine.ReferralRequests;
using Medica.Commerce.OccupationalMedicine.Surveys.Repository;
using Medica.Commerce.P1.Prescriptions;
using Medica.Commerce.P1.Referrals.Repository;
using Medica.Commerce.PatientNotificationTracking;
using Medica.Commerce.Patients.MedicalData.Repository;
using Medica.Commerce.Penalties.AccountLocks;
using Medica.Commerce.Penalties.NoShows;
using Medica.Commerce.Specializations;
using Medica.Commerce.Visits;
using Medica.Commerce.Visits.Process.Statements;
using Medica.Commerce.VisitsSearch;
using Medica.Commerce.VisitsSearch.SimpleSearch;
using Medica.Commerce.Zus;
using Medica.Communication.VideoCalls.Models;
using Medica.Files;
using Medica.OrganizationUnits;
using Medica.Patients;
using Medica.Patients.PatientCareData;
using Medica.PatientVisits.CheckIn;
using Medica.PatientVisits.Confirmations;
using Medica.PatientVisits.DependentVisits;
using Medica.PatientVisits.ExternalDbServices;
using Medica.PatientVisits.HospitalWards.Repository;
using Medica.PatientVisits.MultiVisits;
using NetTopologySuite.Geometries;
using Newtonsoft.Json;

namespace Medica.PatientVisits;

public class PatientVisit : AuditedEntity<long>, IHasIcdCodes, IHaveVisitSearchData, IHaveStaff, IMayHaveOrganizationUnit, IShallowPatientVisit,
    IMayHaveSpecialization, IMayHaveHospitalWard, IHavePatientNotificationTracking, IHaveAosData
{
    private static readonly Regex _tagsRegex = new(@"/ *\[[^\]]*]/g", RegexOptions.Compiled);

    public PatientVisitRegistrationSource RegistrationSource { get; set; }
    public PaymentMode PaymentMode { get; set; }

    public VisitSlotType SlotType { get; set; }
    public VisitCancellationReason CancellationReason { get; set; }
    public PatientVisitStatus Status { get; set; }
    public VisitAosMode AosMode { get; set; }
    public string? ReferralCode { get; set; }

    [Obsolete]
    public MedicaVisitType MedicaVisitType { get; set; }

    public DateTime StatusModificationTime { get; set; }
    public long? CancelledById { get; set; }
    public PatientVisitRegistrationSource? CancellationSource { get; set; }
    public long? PatientCareId { get; set; }
    public virtual PatientCare? PatientCare { get; set; }
    public bool ProcessedInPortal { get; set; }

    public Guid? MultiPatientVisitId { get; init; }
    public virtual MultiPatientVisit? MultiPatientVisit { get; init; }

    public MedicalServiceOccupationalMode DeclaredOccupationalMode { get; set; }
    public virtual VisitSurvey? Survey { get; set; }
    public DateTime From { get; set; }
    public DateTime To { get; set; }


    public DateTime? SlotReferenceDate { get; set; }
    public DateTime? CancellationThresholdDate { get; set; }
    public Guid? AccountLockId { get; set; }
    public virtual PatientAccountLock? AccountLock { get; set; }

    public string? PhoneNumber { get; set; }
    public string? SessionPhoneNumber { get; set; }
    public bool? EarlierFailed { get; set; }
    public RescheduleOptions RescheduleOptions { get; set; }

    public string? RescheduleReason { get; set; }
    public string? RescheduleRemarks { get; set; }

    [DisableAuditing]
    [JsonIgnore]
    [System.Text.Json.Serialization.JsonIgnore]
    public Point? PatientPostalCodeLocation { get; set; }


    public virtual MedicalStaffData? Staff { get; set; }
    public virtual User? User { get; set; }
    public long StaffId { get; set; }


    public int StaffPrefix { get; set; } = -1;
    public int StaffPrefixCount { get; set; } = -1;
    public int Prefix { get; set; } = -1;
    public int PrefixCount { get; set; } = -1;

    public long? OrganizationUnitId { get; set; }
    public virtual MedicaOrganizationUnit? OrganizationUnit { get; set; }

    public Guid? HospitalWardId { get; set; }
    public virtual HospitalWard? HospitalWard { get; set; }

    public string InstitutionId { get; set; } = default!;
    public virtual Institution? Institution { get; set; }
    public Guid? PublicUserAccountId { get; set; }
    public Guid? PublicUserChildId { get; set; }
    public long? PatientId { get; set; }
    public virtual Patient? Patient { get; set; }

    public long? MedicalServiceId { get; set; }

    public virtual MedicalService? MedicalService { get; set; }

    public long? SpecializationId { get; set; }
    public virtual Specialization? Specialization { get; set; }

    public long? AppointmentTypeId { get; set; }
    public virtual AppointmentType? AppointmentType { get; set; }

    public Guid? HospitalTreatmentTypeId { get; set; }
    public virtual HospitalTreatmentType? HospitalTreatmentType { get; set; }
    public long? ExternalIdPrzyjSkier { get; set; }
    public long? ExternalIdPlanPrzyj { get; set; }
    public long ExternalStaffId { get; set; }
    public DateTime ExternalModificationTime { get; set; }

    public VisitAppointmentMode VisitAppointmentMode { get; set; }

    public string? Description { get; set; }
    public string? PatientName { get; set; }
    public string? PatientPesel { get; set; }
    public long? ExternalId { get; set; }

    public virtual HospitalWardVisit? HospitalWardVisit { get; set; }

    [Column(TypeName = "jsonb")]
    public VisitConfirmationActionsStatus? ConfirmationActionsStatus { get; set; }

    public virtual VideoCallInvitation? VideoCallInvitation { get; set; }
    public Guid? VideoCallInvitationId { get; set; }

    public virtual ICollection<PatientVisitFileBucket>? FileBuckets { get; set; }
    public virtual ICollection<PatientVisitMessage>? Messages { get; set; }
    public virtual ICollection<PatientVisitConfirmationRequest>? ConfirmationRequests { get; set; }
    public virtual ICollection<PatientVisitConfirmationRequest>? CausePatientVisits { get; set; }
    public virtual ICollection<DependentVisit>? DependentVisits { get; set; }
    public virtual ICollection<PrescriptionSet>? PrescriptionSets { get; set; }
    public virtual ICollection<MedicalStatement>? MedicalStatements { get; set; }
    public virtual ICollection<PatientVisitCheckIn>? CheckIns { get; set; }
    public virtual ICollection<Referral>? Referrals { get; set; }
    public virtual ICollection<Zla>? Zlas { get; set; }
    public virtual ICollection<MedicalDataItem>? MedicalDataItems { get; set; }
    public virtual DependentVisit? MasterVisit { get; set; }
    public virtual NoShowDebt? NoShowDebt { get; set; }
    public virtual ICollection<ReferralRequest>? ReferralRequests { get; set; }


    public string? GetDescriptionWithoutTags() => string.IsNullOrWhiteSpace(Description) ? Description : _tagsRegex.Replace(Description, "");

    [NotMapped]
    public IcdCodesSet IcdCodes => PatientCare?.IcdCodes ?? IcdCodesSet.Empty();


    [NotMapped]
    public SatIdTuple? SatIdTuple => SpecializationId is { } specializationId && GenericAppointmentTypeId is { } appointmentTypeId
        ? new SatIdTuple(specializationId, StaffId, appointmentTypeId)
        : null;

    [NotMapped]
    public long SlotOrgUnitId => OrganizationUnitId ?? throw new ArgumentException($"Patient Visit {Id} does not have org unit id");

    public ConfirmationData ConfirmationData
    {
        get
        {
            if (ConfirmationRequests?.Where(v => v.Type == PatientVisitConfirmationType.ConfirmVisit).ToList() is not
                { Count: > 0 } confirmationRequests) return ConfirmationData.Empty();
            var acceptedConfirmation = confirmationRequests.FirstOrDefault(v => v.Result == PatientVisitConfirmationResult.Accepted);
            var anyPendingConfirmations = confirmationRequests.Any(v => v.Result == null);
            var status = acceptedConfirmation is not null ? ConfirmationStatus.Accepted
                : anyPendingConfirmations ? ConfirmationStatus.ConfirmationSent
                : ConfirmationStatus.None;
            return new ConfirmationData(status)
            {
                ResultTime = acceptedConfirmation?.ResultTime,
                Source = acceptedConfirmation?.ResultSource
            };
        }
    }

    [NotMapped]
    public bool HasRescheduleReason => RescheduleReason != null;

    [NotMapped]
    public long? EntityOrganizationUnitId => OrganizationUnitId;

    [NotMapped]
    public GenericAppointmentTypeId? GenericAppointmentTypeId
    {
        get => AppointmentTypeId is { } appointmentTypeId
            ? GenericAppointmentTypeId.ByAppointmentType(appointmentTypeId)
            : HospitalTreatmentTypeId is { } hospitalTreatmentTypeId
                ? GenericAppointmentTypeId.ByHospitalTreatmentType(hospitalTreatmentTypeId)
                : null;
        set
        {
            AppointmentTypeId = value?.AppointmentTypeId;
            HospitalTreatmentTypeId = value?.HospitalTreatmentTypeId;
        }
    }

    public Guid? PatientNotificationTrackEntityId { get; set; }
    public virtual PatientNotificationTrackEntity? PatientNotificationTrackEntity { get; set; }

    public virtual PatientVisitPrenatalProgram? VisitPrenatalProgram { get; set; }
    public Guid? VisitPrenatalProgramId { get; set; }

    public static ISet<PatientVisitStatus> IsStaffPatientStatusSet => new[]
        { PatientVisitStatus.Closed, PatientVisitStatus.Pending, PatientVisitStatus.CheckedIn }.ToHashSet();

    public virtual VisitGroup? Group { get; set; }
    public Guid? GroupId { get; set; }
}

/// VisitMoveData.cs

using Medica.PatientVisits;

namespace Medica.Commerce.Visits;

public class VisitMoveData
{
    public VisitCreateData CreateData { get; init; }
    public long OldVisitId { get; init; }


    public VisitCancellationReason Reason { get; init; }
    public PatientVisitRegistrationSource Source { get; init; }
}

/// VisitCreateData.cs

#nullable enable
using System;
using System.Collections.Generic;
using Medica.Commerce.VisitsSearch;
using Medica.Commerce.VisitsSearch.SimpleSearch;
using Medica.PatientVisits;
using Medica.PatientVisits.HospitalWards.Repository;

namespace Medica.Commerce.Visits;

public record VisitCreateData(SatIdTuple SatIdTuple, VisitCreateCommonData CommonData) : VisitCreateBaseData(SatIdTuple, CommonData.PaymentMode), IHaveVisitData
{
    public PatientVisitRegistrationSource RegistrationSource => CommonData.RegistrationSource;
    public string? Description { get; init; }
    public string? PatientName => CommonData.PatientName;
    public string? PatientPesel => CommonData.PatientPesel;
    public string? PhoneNumber => CommonData.PhoneNumber;
    public string? SessionPhoneNumber => CommonData.SessionPhoneNumber;
    public RescheduleOptions RescheduleOptions { get; init; }
    public Guid? PublicUserAccountId => CommonData.PublicUserAccountId;
    public Guid? PublicUserChildId => CommonData.PublicUserChildId;
    public long? PatientId => CommonData.PatientId;
    public long? MedicalServiceId { get; init; }

    public VisitCreateWardData? WardCreateData { get; init; }
    public Guid? MultiPatientVisitId { get; init; }

    public long? ExistingMasterVisitId { get; init; }
    public bool IsMultiVisit => WardCreateData?.Mode is HospitalWardVisitMode.SecondaryGroup or HospitalWardVisitMode.Primary;

    public VisitCreateMasterVisitData? MasterVisit { get; init; }
    public int? PatientAge => CommonData.PatientAge;

    public string? ReferralCode { get; init; }
    public Guid? VisitLockId { get; init; }
    public bool Force => CommonData.Force;
    public bool ForceMultipleVisits { get; init; }
    public VisitSlotType SlotType => CommonData.SlotType;
    public HospitalWardVisitMode WardMode => WardCreateData?.Mode ?? HospitalWardVisitMode.None;
    public ISet<VisitOperationIssueType> IgnoredIssues => CommonData.IgnoredIssues;
    public IList<VisitCreateDependencyData> DependentVisits { get; set; } = new List<VisitCreateDependencyData>();

    public PrenatalProgramData? PrenatalProgram { get; init; }
    public bool IsMultiMember { get; init; }

    public MostRelevantHitsQuery MostRelevantHitsQuery() => new(this, PaymentMode.Single(), StaffId)
    {
        SpecializationId = SpecializationId,
        AppointmentTypeId = AppointmentTypeId,
        MedicalServiceId = MedicalServiceId
    };

    public Guid? GroupId { get; set; }
}