gehgassi_backend/gehGassi.Core/Services/PublicWalkRequestService.cs

516 lines
27 KiB
C#

using gehGassi.Core.Interfaces;
using gehGassi.Domain.Advertisements;
using gehGassi.Domain.Common;
using gehGassi.Domain.Walks;
using Microsoft.EntityFrameworkCore;
using NetTopologySuite.Geometries;
using NetTopologySuite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using gehGassi.Common.Extensions;
namespace gehGassi.Core.Services
{
/// <summary>
/// Service der die Verwaltung von öffentlichen Anfragen ermöglicht
/// </summary>
public class PublicWalkRequestService : ServiceBase<PublicWalkRequest>, IPublicWalkRequestService
{
private readonly IRepository<PublicWalkRequestWithNames> _namesRepository;
private readonly IRepository<PublicWalkRequestWithNamesAndResponseStatus> _namesWithStatusRepository;
private readonly IRepository<PublicWalkResponse> _responsesRepository;
private readonly IRepository<PublicWalkRequestNotification> _notificationsRepository;
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="unitOfWork">Instanz eines IUnitOfWork</param>
public PublicWalkRequestService(IUnitOfWork unitOfWork) : base(unitOfWork)
{
_namesRepository = unitOfWork.GetRepository<PublicWalkRequestWithNames>();
_namesWithStatusRepository = unitOfWork.GetRepository<PublicWalkRequestWithNamesAndResponseStatus>();
_responsesRepository = unitOfWork.GetRepository<PublicWalkResponse>();
_notificationsRepository = unitOfWork.GetRepository<PublicWalkRequestNotification>();
}
/// <summary>
/// Erstellen einer öffentlichen Anfrage
/// </summary>
/// <returns></returns>
public PublicWalkRequest Create()
{
var request = new PublicWalkRequest
{
Id = Guid.NewGuid().ToString("N"),
Created = DateTimeOffset.UtcNow
};
return request;
}
/// <summary>
/// Gibt eine Liste der öffentlichen Anfragen eines Benutzers zurück
/// </summary>
/// <param name="appUserId">Id des AppUsers</param>
/// <param name="lastUpdate">Letztes Update</param>
/// <returns>Liste der öffentlichen Anfragen</returns>
public async Task<List<PublicWalkRequest>> GetForSyncAppAsync(string appUserId, DateTimeOffset? lastUpdate)
{
var query = Repository.Query(c => c.DogOwnerId == appUserId);
if (lastUpdate.HasValue)
query = query.Where(c => c.UpdatedAt > lastUpdate);
return await query.ToListAsync().ConfigureAwait(false);
}
/// <summary>
/// Gibt die anzahl öffentlicher Anfragen eines AppUsers zurück
/// </summary>
/// <param name="appUserId">AppUserId</param>
/// <returns>Anzahl öffentliche Anfragen</returns>
public async Task<int> CountAsync(string appUserId)
{
var items = await Repository.CountAsync(c => c.DogOwnerId == appUserId);
return items;
}
/// <summary>
/// Abfrage der öffentlichen Anfragen eines AppUsers "Latest"
/// </summary>
/// <param name="appUserId">Id des AppUsers</param>
/// <param name="take">Wie viele Datensätze</param>
/// <returns>Liste der öffentlichen Anfragen</returns>
public async Task<List<PublicWalkRequestWithNames>> GetLatestAsync(string appUserId, int take)
{
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_PublicWalkRequestWithNames()", c => c.DogOwnerId == appUserId && c.Deleted == false);
var requests = await baseQuery.OrderByDescending(c => c.UpdatedAt).Take(take).ToListAsync();
return requests;
}
/// <summary>
/// Gibt eine Liste von eigenen öffentlichen Anfragen für die App zurück.
/// </summary>
/// <param name="ignoreId">App-User Id die ignoriert werden soll (dogOwnerId)</param>
/// <param name="dogOwnerId">Optional: Id des AppUsers der die Anfragen erstellt hat</param>
/// <param name="dogWalkerId">Optional: Id des DogWalkers</param>
/// <param name="status">Gesuchert Status. Alle wenn NULL</param>
/// <param name="sortList">Optional: Liste der sortierung</param>
/// <param name="lat">Optional: Breitengrad</param>
/// <param name="lng">Optional: Längengrad</param>
/// <param name="skip">Datensätze auslassen</param>
/// <param name="take">Datensätze nehmen</param>
/// <returns>Liste passender Anfragen</returns>
public async Task<(int total, List<PublicWalkRequestWithNames> list)> GetAsync(string ignoreId, string dogOwnerId, string dogWalkerId, PublicWalkRequestStatus? status, List<DynamicSortOrder> sortList, double lat, double lng, int skip, int take)
{
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_PublicWalkRequestWithNames()", c => c.Deleted == false);
if (status.HasValue)
baseQuery = baseQuery.Where(c => c.Status == status);
if (!string.IsNullOrEmpty(ignoreId))
baseQuery = baseQuery.Where(c => c.DogOwnerId != ignoreId);
if (!string.IsNullOrEmpty(dogOwnerId))
baseQuery = baseQuery.Where(c => c.DogOwnerId == dogOwnerId);
if (!string.IsNullOrEmpty(dogWalkerId))
baseQuery = baseQuery.Where(c => c.DogWalkerId == dogWalkerId);
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var location = geometryFactory.CreatePoint(new Coordinate(lng, lat));
var total = await baseQuery.CountAsync().ConfigureAwait(false);
if (sortList.Any())
{
var isFirst = true;
var orderedBaseQuery = baseQuery.SpecialOrderBy(sortList.First().Property, sortList.First().SortDirection); ;
foreach (var sortOrder in sortList)
{
if (isFirst)
{
isFirst = false;
continue;
}
else
{
orderedBaseQuery = orderedBaseQuery.SpecialThenBy(sortOrder.Property, sortOrder.SortDirection);
}
}
var list = await orderedBaseQuery.ThenBy(c => c.Location.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
else
{
var list = await baseQuery.OrderBy(c => c.Location.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
}
/// <summary>
/// Gibt eine Liste von eigenen öffentlichen Anfragen für die App zurück.
/// </summary>
/// <param name="ignoreId">App-User Id die ignoriert werden soll (dogOwnerId)</param>
/// <param name="dogOwnerId">Optional: Id des AppUsers der die Anfragen erstellt hat</param>
/// <param name="dogWalkerId">Optional: Id des DogWalkers</param>
/// <param name="status">Gesuchert Status. Alle wenn NULL</param>
/// <param name="minPrice">Mindest-Preis</param>
/// <param name="sortList">Optional: Liste der sortierung</param>
/// <param name="lat">Optional: Breitengrad</param>
/// <param name="lng">Optional: Längengrad</param>
/// <param name="skip">Datensätze auslassen</param>
/// <param name="take">Datensätze nehmen</param>
/// <param name="maxRadius">Maximaler Radius</param>
/// <param name="type">Typ der öffentlichen Anfrage, Alle wenn NULL</param>
/// <returns>Liste passender Anfragen</returns>
public async Task<(int total, List<PublicWalkRequestWithNames> list)> GetAsync(string ignoreId, string dogOwnerId, string dogWalkerId, PublicWalkRequestStatus? status, double maxRadius, PublicWalkRequestType? type, decimal minPrice, List<DynamicSortOrder> sortList, double lat, double lng, int skip, int take)
{
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var location = geometryFactory.CreatePoint(new Coordinate(lng, lat));
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_PublicWalkRequestWithNames()", c => c.Deleted == false);
if (status.HasValue)
baseQuery = baseQuery.Where(c => c.Status == status);
if (!string.IsNullOrEmpty(ignoreId))
baseQuery = baseQuery.Where(c => c.DogOwnerId != ignoreId);
if (!string.IsNullOrEmpty(dogOwnerId))
baseQuery = baseQuery.Where(c => c.DogOwnerId == dogOwnerId);
if (!string.IsNullOrEmpty(dogWalkerId))
baseQuery = baseQuery.Where(c => c.DogWalkerId == dogWalkerId);
if(type.HasValue)
baseQuery = baseQuery.Where(c => c.RequestType == type);
if(minPrice > 0)
baseQuery = baseQuery.Where(c => c.Price >= minPrice);
baseQuery = baseQuery.Where(c => c.Location.IsWithinDistance(location, maxRadius * 1000));
var total = await baseQuery.CountAsync().ConfigureAwait(false);
if (sortList.Any())
{
var isFirst = true;
var orderedBaseQuery = baseQuery.SpecialOrderBy(sortList.First().Property, sortList.First().SortDirection); ;
foreach (var sortOrder in sortList)
{
if (isFirst)
{
isFirst = false;
continue;
}
else
{
orderedBaseQuery = orderedBaseQuery.SpecialThenBy(sortOrder.Property, sortOrder.SortDirection);
}
}
var list = await orderedBaseQuery.ThenBy(c => c.Location.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
else
{
var list = await baseQuery.OrderBy(c => c.Location.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
}
/// <summary>
/// Gibt eine Liste von eigenen öffentlichen Anfragen für die App zurück. Mit dem Antwort-Status für einen DogWalker
/// </summary>
/// <param name="ignoreId">App-User Id die ignoriert werden soll (dogOwnerId)</param>
/// <param name="dogOwnerId">Optional: Id des AppUsers der die Anfragen erstellt hat</param>
/// <param name="dogWalkerId">Optional: Id des DogWalkers</param>
/// <param name="status">Gesuchert Status. Alle wenn NULL</param>
/// <param name="mustHaveResponse">es muss eine Antwort vorhanden sein</param>
/// <param name="sortList">Optional: Liste der sortierung</param>
/// <param name="includeBlockedAndLocked">Sollten blockierte unnd/oder gesperrte Benutzer in die Liste eingefügt werden?</param>
/// <param name="lat">Optional: Breitengrad</param>
/// <param name="lng">Optional: Längengrad</param>
/// <param name="skip">Datensätze auslassen</param>
/// <param name="take">Datensätze nehmen</param>
/// <param name="targetDogWalkerId">Id des Dogwalkers für welchen die Liste aufbereitet werden soll</param>
/// <param name="responseStatus">Gesuchter Status oder aller wenn null</param>
/// <returns>Liste passender Anfragen</returns>
public async Task<(int total, List<PublicWalkRequestWithNamesAndResponseStatus> list)> GetWithResponseStatusAsync(string ignoreId, string dogOwnerId, string dogWalkerId, PublicWalkRequestStatus? status, string targetDogWalkerId, PublicWalkResponseStatus? responseStatus, bool mustHaveResponse, List<DynamicSortOrder> sortList, double lat, double lng, bool includeBlockedAndLocked, int skip, int take)
{
var baseQuery = _namesWithStatusRepository.QueryStoredProcedure("SELECT * FROM tv_PublicWalkRequestWithNamesAndResponseStatus({0})", c => c.Deleted == false, targetDogWalkerId);
if (!includeBlockedAndLocked)
baseQuery = baseQuery.Where(c => c.DogOwnerBlocked == false && c.DogOwnerLocked == false);
if (status.HasValue)
baseQuery = baseQuery.Where(c => c.Status == status);
if (!string.IsNullOrEmpty(ignoreId))
baseQuery = baseQuery.Where(c => c.DogOwnerId != ignoreId);
if (!string.IsNullOrEmpty(dogOwnerId))
baseQuery = baseQuery.Where(c => c.DogOwnerId == dogOwnerId);
if (!string.IsNullOrEmpty(dogWalkerId))
baseQuery = baseQuery.Where(c => c.DogWalkerId == dogWalkerId);
if (responseStatus.HasValue)
baseQuery = baseQuery.Where(c => c.ResponseStatus == responseStatus || c.ResponseStatus == null);
if (mustHaveResponse)
baseQuery = baseQuery.Where(c => c.ResponseStatus != null);
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var location = geometryFactory.CreatePoint(new Coordinate(lng, lat));
var total = await baseQuery.CountAsync().ConfigureAwait(false);
if (sortList.Any())
{
var isFirst = true;
var orderedBaseQuery = baseQuery.SpecialOrderBy(sortList.First().Property, sortList.First().SortDirection); ;
foreach (var sortOrder in sortList)
{
if (isFirst)
{
isFirst = false;
continue;
}
else
{
orderedBaseQuery = orderedBaseQuery.SpecialThenBy(sortOrder.Property, sortOrder.SortDirection);
}
}
var list = await orderedBaseQuery.ThenBy(c => c.Location.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
else
{
var list = await baseQuery.OrderBy(c => c.Location.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
}
/// <summary>
/// Gibt eine Liste von eigenen öffentlichen Anfragen für die App zurück. Mit dem Antwort-Status für einen DogWalker
/// </summary>
/// <param name="ignoreId">App-User Id die ignoriert werden soll (dogOwnerId)</param>
/// <param name="dogOwnerId">Optional: Id des AppUsers der die Anfragen erstellt hat</param>
/// <param name="dogWalkerId">Optional: Id des DogWalkers</param>
/// <param name="status">Gesuchert Status. Alle wenn null</param>
/// <param name="searchText">Suchtext für Suche in Hundename, OwnerName oder Stadt</param>
/// <param name="mustHaveResponse">es muss eine Antwort vorhanden sein</param>
/// <param name="sortList">Optional: Liste der sortierung</param>
/// <param name="lat">Optional: Breitengrad</param>
/// <param name="lng">Optional: Längengrad</param>
/// <param name="includeBlockedAndLocked">Sollen blockierte unnd/oder gesperrte Benutzer in die Liste eingefügt werden?</param>
/// <param name="skip">Datensätze auslassen</param>
/// <param name="take">Datensätze nehmen</param>
/// <param name="targetDogWalkerId">Id des Dogwalkers für welchen die Liste aufbereitet werden soll</param>
/// <param name="responseStatus">Gesuchter Status oder alle wenn null</param>
/// <param name="maxRadius">Maximaler Radius</param>
/// <param name="type">Typ der öffentlichen Anfrage, oder alle wenn NULL</param>
/// <param name="minPrice">Mindest-Preis</param>
/// <returns>Liste passender Anfragen</returns>
public async Task<(int total, List<PublicWalkRequestWithNamesAndResponseStatus> list)> GetWithResponseStatusAsync(string ignoreId, string dogOwnerId, string dogWalkerId, PublicWalkRequestStatus? status, string targetDogWalkerId, PublicWalkResponseStatus? responseStatus, double maxRadius, PublicWalkRequestType? type, decimal minPrice, bool mustHaveResponse, string searchText, List<DynamicSortOrder> sortList, double lat, double lng, bool includeBlockedAndLocked, int skip, int take)
{
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var location = geometryFactory.CreatePoint(new Coordinate(lng, lat));
var baseQuery = _namesWithStatusRepository.QueryStoredProcedure("SELECT * FROM tv_PublicWalkRequestWithNamesAndResponseStatus({0})", c => c.Deleted == false, targetDogWalkerId);
if (!includeBlockedAndLocked)
baseQuery = baseQuery.Where(c => c.DogOwnerBlocked == false && c.DogOwnerLocked == false);
if (status.HasValue)
baseQuery = baseQuery.Where(c => c.Status == status);
if (!string.IsNullOrEmpty(ignoreId))
baseQuery = baseQuery.Where(c => c.DogOwnerId != ignoreId);
if (!string.IsNullOrEmpty(dogOwnerId))
baseQuery = baseQuery.Where(c => c.DogOwnerId == dogOwnerId);
if (!string.IsNullOrEmpty(dogWalkerId))
baseQuery = baseQuery.Where(c => c.DogWalkerId == dogWalkerId);
if (type.HasValue)
baseQuery = baseQuery.Where(c => c.RequestType == type);
if (minPrice > 0)
baseQuery = baseQuery.Where(c => c.Price >= minPrice);
if (responseStatus.HasValue)
baseQuery = baseQuery.Where(c => c.ResponseStatus == responseStatus || c.ResponseStatus == null);
if (mustHaveResponse)
baseQuery = baseQuery.Where(c => c.ResponseStatus != null);
if (!string.IsNullOrWhiteSpace(searchText))
baseQuery = baseQuery.Where(c => c.DogName.Contains(searchText) || c.DogsJson.Contains(searchText) || c.DogOwnerName.Contains(searchText) || c.PickupAddress_City.Contains(searchText));
baseQuery = baseQuery.Where(c => c.Location.IsWithinDistance(location, maxRadius * 1000));
var total = await baseQuery.CountAsync().ConfigureAwait(false);
if (sortList.Any())
{
var isFirst = true;
var orderedBaseQuery = baseQuery.SpecialOrderBy(sortList.First().Property, sortList.First().SortDirection); ;
foreach (var sortOrder in sortList)
{
if (isFirst)
{
isFirst = false;
continue;
}
else
{
orderedBaseQuery = orderedBaseQuery.SpecialThenBy(sortOrder.Property, sortOrder.SortDirection);
}
}
var list = await orderedBaseQuery.ThenBy(c => c.Location.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
else
{
var list = await baseQuery.OrderBy(c => c.StartDate).ThenBy(c => c.Location.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
}
/// <summary>
/// Gibt eine öffentliche Ausschreibung mit Namen aufgelöst zurück
/// </summary>
/// <param name="id">ID der Ausschreibung</param>
/// <returns>öffentliche Ausschreibung oder null, wenn nicht gefunden</returns>
public async Task<PublicWalkRequestWithNames> GetWithNamesAsync(string id)
{
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_PublicWalkRequestWithNames()", c => c.Id == id);
return await baseQuery.FirstOrDefaultAsync().ConfigureAwait(false);
}
/// <summary>
/// Setzt öffntliche Anfragen auf abgelaufen, wenn diese nicht angenommen oder storniert wurden
/// </summary>
/// <param name="date">Datum für Prüfung</param>
/// <returns>Anzahl timed out</returns>
public async Task<int> SetTimedOutAsync(DateTimeOffset date)
{
var requests = await Repository.FindAsync(c => c.ExpirationDate <= date && c.Deleted == false && c.Status == PublicWalkRequestStatus.Open).ConfigureAwait(false);
foreach (var request in requests)
{
request.Status = PublicWalkRequestStatus.TimedOut;
request.UpdatedAt = DateTimeOffset.UtcNow;
var responses = await _responsesRepository.FindAsync(c => c.PublicWalkRequestId == request.Id).ConfigureAwait(false);
foreach (var response in responses)
{
response.Status = PublicWalkResponseStatus.Closed;
response.UpdatedAt = DateTimeOffset.UtcNow;
}
}
return requests.Count();
}
/// <summary>
/// Zurücksetzen einer öffentlichen Anfrage
/// </summary>
/// <param name="id">Id der öffentlichen Anfrage</param>
/// <returns>true wenn erfolgreich, false sonst</returns>
public async Task<bool> ResetAsync(string id)
{
var request = await Repository.FirstOrDefaultAsync(c => c.Id == id);
if (request != null && request.Deleted == false && request.Status == PublicWalkRequestStatus.Placed)
{
request.Status = PublicWalkRequestStatus.Open;
request.PlacedDate = null;
request.PublicWalkResponseId = string.Empty;
request.DogWalkerId = string.Empty;
request.UpdatedAt = DateTimeOffset.UtcNow;
return true;
}
return false;
}
/// <summary>
/// Gibt eine Liste der offenen Anfragen für einen Hundebesitzer in einem gewählten Status zurück
/// </summary>
/// <param name="dogOwnerId">Id des Hundebesitzers</param>
/// <param name="status">Gesuchter Status</param>
/// <returns></returns>
public async Task<List<PublicWalkRequest>> GetByStatusAsync(string dogOwnerId, PublicWalkRequestStatus status)
{
var requests = await Repository.FindAsync(c => c.DogOwnerId == dogOwnerId && c.Status == status && c.Deleted == false).ConfigureAwait(false);
return requests.ToList();
}
/// <summary>
/// Gibt eine gefilterte Liste von öffentlichen Anfragen zurück
/// </summary>
/// <param name="filter">Filterbegriff der in bestimmten Feldern gesucht wird</param>
/// <param name="includeDeleted">Sollen gelöschte inkludiert werden?</param>
/// <returns>Liste betroffener öffentlicher Anfragen</returns>
public IQueryable<PublicWalkRequestWithNames> FilterPublicWalkRequestsWithNames(string filter, bool includeDeleted)
{
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_PublicWalkRequestWithNames()", c => c.DogOwnerName.Contains(filter) || c.PickupAddress_City.Contains(filter));
if (!includeDeleted)
baseQuery = baseQuery.Where(c => c.Deleted == false);
return baseQuery;
}
#region Notifications
/// <summary>
/// Erstellt einen Eintrag für die Benachrichtigung einer neuen öffentlichen Anfrage
/// </summary>
/// <param name="publicWalkRequestId">Id der öffentlichen Anfrage</param>
/// <param name="dogOwnerId">Id des Hundebesitzers - AppUserId</param>
/// <param name="pickupAddress">Abholadresse</param>
/// <param name="location">GEO-Koordinaten</param>
/// <returns>PublicWalkRequestNotification</returns>
public PublicWalkRequestNotification CreateNotification(string publicWalkRequestId, string dogOwnerId, Address pickupAddress, Point location)
{
var item = new PublicWalkRequestNotification
{
PublicWalkRequestId = publicWalkRequestId,
DogOwnerId = dogOwnerId,
SentDate = null,
SentCount = 0,
HasBeenSent = false,
Created = DateTimeOffset.UtcNow,
PickupAddress =
{
AddressLine1 = pickupAddress.AddressLine1,
AddressLine2 = pickupAddress.AddressLine2,
Zip = pickupAddress.Zip,
City = pickupAddress.City,
CountryCode = pickupAddress.CountryCode,
State = pickupAddress.State
},
Location = location
};
return item;
}
/// <summary>
/// Hinzufügen einer Benachrichtigung
/// </summary>
/// <param name="publicWalkRequestNotification">Benachrichtigungs-Objekt</param>
public void AddNotification(PublicWalkRequestNotification publicWalkRequestNotification)
{
_notificationsRepository.Add(publicWalkRequestNotification);
}
/// <summary>
/// Gibt eine Liste von zu sendenden Benachrichtigungen zurück
/// </summary>
/// <returns>Liste zu sendende Benachrichtigungen</returns>
public async Task<List<PublicWalkRequestNotification>> GetNotificationsToSendAsync()
{
var notifications = await _notificationsRepository.FindAsync(c => c.HasBeenSent == false && c.SentDate == null).ConfigureAwait(false);
return notifications.ToList();
}
/// <summary>
/// Zählt wie viele öffenltiche Anfragen in einem bestimmten Zeitraum durch einen Benutzer erstellt wurden
/// </summary>
/// <param name="startDate">Start-Datum</param>
/// <param name="endDate">End-Datum</param>
/// <param name="dogOwnerId">Id des Hundebesitzers</param>
/// <param name="maxStatus">Max Status der Ausschreibung</param>
/// <returns>Anzahl öffentliche Anfragen</returns>
public async Task<int> CountByCreationDateAsync(DateTimeOffset startDate, DateTimeOffset endDate, string dogOwnerId, PublicWalkRequestStatus maxStatus)
{
var count = await Repository.CountAsync(c => c.DogOwnerId == dogOwnerId && c.Created >= startDate && c.Created <= endDate && c.Status <= maxStatus).ConfigureAwait(false);
return count;
}
#endregion
}
}