using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.Json; using System.Threading.Tasks; using gehGassi.Dto.Common; using gehGassi.Dto; using gehGassi.Dto.Walks; using gehGassiApp.Core.Data; using gehGassiApp.Core.Interfaces; using gehGassiApp.Core.Interfaces.Synchronization; using gehGassiApp.Core.Resources; using gehGassiApp.Domain.Common; using gehGassiApp.Domain.Users; using gehGassiApp.Domain.Walks; using gehGassi.Dto.Ratings; using gehGassiApp.Core.Mapper; namespace gehGassiApp.Core.Services { /// /// Service der die Verwaltung von Walks ermöglicht /// public class WalkService : ServiceBase, IWalkService { private readonly ICommunicationService _communicationService; private readonly ISyncInfoPullService _pullService; private readonly ISyncInfoPushService _pushService; private readonly ILocationService _locationService; private readonly IRepository _appUserRepository; /// /// Erstellt eine Instanz /// /// Instanz eines IUnitOfWork /// Instanz eines ICommunicationService /// Instanz eines ISyncInfoPullService /// Instanz eines ISyncInfoPushService /// Instanz eines ILocationService public WalkService(IUnitOfWork unitOfWork, ICommunicationService communicationService, ISyncInfoPullService syncInfoPullService, ISyncInfoPushService syncInfoPushService, ILocationService locationService) : base(unitOfWork) { _communicationService = communicationService; _pullService = syncInfoPullService; _pushService = syncInfoPushService; _locationService = locationService; _appUserRepository = unitOfWork.GetRepository(); } /// /// Gibt eine Entität anhand der eindeutigen Id zurück /// /// Id der Entität /// Gibt an ob NoTRacking verwendet werden soll. Es werden keine Entitäten im EF-Speicher gehalten /// Entität oder null, wenn nicht gefunden public override Walk Get(object id, bool noTracking = true) { throw new NotImplementedException(); } /// /// Gibt eine Entität anhand der eindeutigen Id zurück /// /// Id der Entität /// Gibt an ob NoTRacking verwendet werden soll. Es werden keine Entitäten im EF-Speicher gehalten /// Entität oder null, wenn nicht gefunden public override async Task GetAsync(object id, bool noTracking = true) { await Task.Delay(1); throw new NotImplementedException(); } /// /// Gibt einen Walk mit Namen aufgelöst zurück. Kommt vom Server /// /// Id des Walks /// Gewünschte Sprache /// Aktuelles Accesstoken /// CancellationToken /// WalkWithNames oder null, wenn nicht gefunden public async Task> GetWalkWithNamesAsync(string walkId, string language, string accessToken, CancellationToken token) { var result = new CommunicationResult(); result.Success = false; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var queryResult = await _communicationService.GetWalkWithNamesAsync(walkId, language, accessToken, token); if (queryResult.Success) { result.Success = true; result.Value = queryResult.Value; return result; } } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt den nächsten Walk für einen Hundebesitzer zurück /// /// Id des Hundebesitzers /// Datum ab wann gesucht werden soll. Normalerweise "Jetzt" /// Gewünschte Sprache /// Aktueller appMode /// Aktuelle Position /// Aktuelles Accesstoken /// CancellationToken /// Nächster Walk oder null, wenn keiner vorhanden public async Task> GetNextWalkForOwnerAsync(string ownerId, DateTimeOffset date, string language, AppMode appMode, LocationDto location, string accessToken, CancellationToken token) { var result = new CommunicationResult(); result.Success = false; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var query = new WalksQueryDto() { Location = location, Language = language, AppMode = (AppModeDto)appMode, Take = 1, Skip = 0, LastUpdate = null, DogOwnerId = ownerId, DogWalkerId = string.Empty, Date = date, Status = null, ServiceType = null, PaymentStatus = null, IgnoreCancelled = true, ForNextWalk = true }; var queryResult = await _communicationService.GetWalksWithNamesAsync(query, accessToken, token); if (queryResult.Success && queryResult.Value.Count >= 1) { result.Success = true; result.Value = queryResult.Value.First(); return result; } } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt den nächsten Walk für einen Dogwalker zurück /// /// Id des Hundebesitzers /// Datum ab wann gesucht werden soll. Normalerweise "Jetzt" /// Gewünschte Sprache /// Aktueller appMode /// Aktuelle Position /// Aktuelles Accesstoken /// CancellationToken /// Nächster Walk oder null, wenn keiner vorhanden public async Task> GetNextWalkForWalkerAsync(string walkerId, DateTimeOffset date, string language, AppMode appMode, LocationDto location, string accessToken, CancellationToken token) { var result = new CommunicationResult(); result.Success = false; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var query = new WalksQueryDto() { Location = location, Language = language, AppMode = (AppModeDto)appMode, Take = 1, Skip = 0, LastUpdate = null, DogOwnerId = string.Empty, DogWalkerId = walkerId, Date = date, Status = null, ServiceType = null, PaymentStatus = null, IgnoreCancelled = true, ForNextWalk = true }; var queryResult = await _communicationService.GetWalksWithNamesWalkerAsync(query, accessToken, token); if (queryResult.Success && queryResult.Value.Count >= 1) { result.Success = true; result.Value = queryResult.Value.First(); return result; } } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt Walks vom Server zurück /// /// Id des Hundebesitzers - angeben wenn für Hundebesitzer /// Id des Dogwalkers - angeben wenn für DogWalker /// Datum ab wann gesucht werden soll. Normalerweise "Jetzt", leer für alle /// Status des Walks, NULL wenn alle /// Service Type, NULL wenn alle /// Zahlungsstatus, NULL wenn alle /// Optinal: Filter Hund /// Optional: Filter Walker /// Optional: Filter Owner /// Stornierte ignorieren /// Sollen Walks die noch eine Anfrage sind, ohne Akzeptiert oder Agelehnt ignorniert werden? /// Sollen abgelehnte Anfragen ignoriert werden? /// Liste von Sortierangaben /// Gewünschte Sprache /// Aktueller appMode /// Aktuelle Position /// Wie viele Walks sollen abgerufen werden? -1 Wenn nicht anwenden. /// Wie viele Walks sollen ausgelassen werden? -1 Wenn nicht anwenden. /// Aktuelles Accesstoken /// CancellationToken /// Nächster Walk oder null, wenn keiner vorhanden public async Task>> GetWalksAsync(string dogOwnerId, string dogWalkerId, DateTimeOffset? date, WalkStatus? status, WalkServiceType? serviceType, PaymentStatus? paymentStatus, string dogFilter, string walkerFilter, string ownerFilter, bool ignoreCancelled, bool ignoreRequested, bool ignoreDeclined, List sortOrders, string language, AppMode appMode, LocationDto location, int take, int skip, string accessToken, CancellationToken token) { var result = new ListCommunicationResult>() { Value = new List() }; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var query = new WalksQueryDto() { Location = location, Language = language, AppMode = (AppModeDto)appMode, Take = take, Skip = skip, LastUpdate = null, DogOwnerId = dogOwnerId, DogWalkerId = dogWalkerId, Date = date, Status = (WalkStatusDto?)status, ServiceType = (WalkServiceTypeDto?)serviceType, PaymentStatus = (PaymentStatusDto?)paymentStatus, DogFilter = dogFilter, WalkerFilter = walkerFilter, OwnerFilter = ownerFilter, IgnoreCancelled = ignoreCancelled, IgnoreRequested = ignoreRequested, IgnoreDeclined = ignoreDeclined }; query.DynamicSortOrder = sortOrders.ToDto(); var queryResult = await _communicationService.GetWalksWithNamesAsync(query, accessToken, token); if (queryResult.Success && queryResult.Value.Count >= 1) { return queryResult; } } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt Walks vom Server zurück. /// Sondersituation: Für Walker werden nur Walks mit einem Paymentstatus größer gleich Authorized zurück /// /// Id des Hundebesitzers - angeben wenn für Hundebesitzer /// Id des Dogwalkers - angeben wenn für DogWalker /// Datum ab wann gesucht werden soll. Normalerweise "Jetzt", leer für alle /// Status des Walks, NULL wenn alle /// Service Type, NULL wenn alle /// Zahlungsstatus, NULL wenn alle /// Optinal: Filter Hund /// Optional: Filter Walker /// Optional: Filter Owner /// Stornierte ignorieren /// Sollen Walks die noch eine Anfrage sind, ohne Akzeptiert oder Agelehnt ignorniert werden? /// Sollen abgelehnte Anfragen ignoriert werden? /// Liste von Sortierangaben /// Gewünschte Sprache /// Aktueller appMode /// Aktuelle Position /// Wie viele Walks sollen abgerufen werden? -1 Wenn nicht anwenden. /// Wie viele Walks sollen ausgelassen werden? -1 Wenn nicht anwenden. /// Aktuelles Accesstoken /// CancellationToken /// Nächster Walk oder null, wenn keiner vorhanden public async Task>> GetWalksWalkerAsync(string dogOwnerId, string dogWalkerId, DateTimeOffset? date, WalkStatus? status, WalkServiceType? serviceType, PaymentStatus? paymentStatus, string dogFilter, string walkerFilter, string ownerFilter, bool ignoreCancelled, bool ignoreRequested, bool ignoreDeclined, List sortOrders, string language, AppMode appMode, LocationDto location, int take, int skip, string accessToken, CancellationToken token) { var result = new ListCommunicationResult>() { Value = new List() }; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var query = new WalksQueryDto() { Location = location, Language = language, AppMode = (AppModeDto)appMode, Take = take, Skip = skip, LastUpdate = null, DogOwnerId = dogOwnerId, DogWalkerId = dogWalkerId, Date = date, Status = (WalkStatusDto?)status, ServiceType = (WalkServiceTypeDto?)serviceType, PaymentStatus = (PaymentStatusDto?)paymentStatus, DogFilter = dogFilter, WalkerFilter = walkerFilter, OwnerFilter = ownerFilter, IgnoreCancelled = ignoreCancelled, IgnoreRequested = ignoreRequested, IgnoreDeclined = ignoreDeclined }; query.DynamicSortOrder = sortOrders.ToDto(); var queryResult = await _communicationService.GetWalksWithNamesWalkerAsync(query, accessToken, token); if (queryResult.Success && queryResult.Value.Count >= 1) { return queryResult; } } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt eine Liste der nächsten Walks, laufende, nicht abgeschlossene, nicht bezahlte usw. für einen Hundebesitzer zurück. /// /// Id des App-Users /// Datum ab dem gesucht /// Gewünschte Sprache /// Datensätze auslassen /// Datensätze nehmen /// Aktuelles Accesstoken /// CancellationToken /// ListCommunicationResult public async Task>> GetNextWalksOwnerAsync(string appUserId, DateTimeOffset date, string language, int take, int skip, string accessToken, CancellationToken token) { var result = new ListCommunicationResult>() { Value = new List() }; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var queryResult = await _communicationService.GetNextWalksOwnerAsync(appUserId, date, language, take, skip, accessToken, token); if (queryResult.Success && queryResult.Value.Count >= 1) { return queryResult; } } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt eine Liste der nächsten Walks, laufende, nicht abgeschlossene, nicht bezahlte usw. für einen Walker zurück. /// /// Id des App-Users /// Datum ab dem gesucht /// Gewünschte Sprache /// Datensätze auslassen /// Datensätze nehmen /// Aktuelles Accesstoken /// CancellationToken /// ListCommunicationResult public async Task>> GetNextWalksWalkerAsync(string appUserId, DateTimeOffset date, string language, int take, int skip, string accessToken, CancellationToken token) { var result = new ListCommunicationResult>() { Value = new List() }; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var queryResult = await _communicationService.GetNextWalksWalkerAsync(appUserId, date, language, take, skip, accessToken, token); if (queryResult.Success && queryResult.Value.Count >= 1) { return queryResult; } } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Gibt einen Walk zurück, welcher einer öffentlichen Anfrage zugeordnet ist /// /// Id der öffentlichen Anfrage /// Walk oder null, wenn nicht gefunden public async Task GetWalkByPublicRequestAsync(string publicWalkRequestId) { var localWalk = await Repository.FirstOrDefaultAsync(c => c.PublicWalkRequestId == publicWalkRequestId); return localWalk; } /// /// Gibt einen Walk zurück, welcher einer öffentlichen Anfrage zugeordnet ist - nur online /// /// Id der öffentlichen Anfrage /// Walk oder null, wenn nicht gefunden public async Task> GetWalkByPublicRequestAsync(string publicWalkRequestId, string accessToken, CancellationToken token) { var result = new CommunicationResult(); result.Success = false; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var queryResult = await _communicationService.GetWalkByPublicRequestAsync(publicWalkRequestId, accessToken, token); if (queryResult.Success && queryResult.Value != null) { result.Success = true; result.Value = queryResult.Value; return result; } } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Stornieren eines Walks /// /// ID des Walks /// Quelle der Stornierung /// Grund der Stornierung /// Aktuelles Accesstoken /// CancellationToken /// true wenn erfolgreich, false sonst public async Task CancelAsync(string walkId, CancellationSource cancellationSource, string cancellationReason, string accessToken, CancellationToken token) { var success = false; var localWalk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId, false); var updatedAt = DateTimeOffset.UtcNow; if (localWalk != null) { localWalk.Status = WalkStatus.Cancelled; localWalk.UpdatedAt = updatedAt; localWalk.CancelledBy = cancellationSource; localWalk.CancelledReason = cancellationReason; Repository.Update(localWalk); await CommitAsync(); success = true; } //Jetzt am Server oder Delta erstellen var createDelta = true; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var dto = new WalkCancelDto() { WalkId = walkId, UpdatedAt = updatedAt, CancellationSource = (CancellationSourceDto)cancellationSource, CancelledReason = cancellationReason }; var cancelResult = await _communicationService.CancelWalkAsync(dto, accessToken, token); if (cancelResult.Success) { if (cancelResult.Value) { success = true; createDelta = false; } } } if (createDelta) { await _pushService.AddAsync(nameof(Walk), localWalk.Id, SyncOperation.Edit, localWalk).ConfigureAwait(false); } return success; } /// /// Starten eines Walks /// /// ID des Walks /// Aktuelles Accesstoken /// CancellationToken /// true wenn erfolgreich, false sonst public async Task StartAsync(string walkId, string accessToken, CancellationToken token) { var success = false; var localWalk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId, false); var updatedAt = DateTimeOffset.UtcNow; if (localWalk != null) { localWalk.Status = WalkStatus.Started; localWalk.Started = updatedAt; localWalk.UpdatedAt = updatedAt; Repository.Update(localWalk); await CommitAsync(); success = true; } //Jetzt am Server oder Delta erstellen var createDelta = true; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var dto = new WalkStartDto() { WalkId = walkId, UpdatedAt = updatedAt }; var cancelResult = await _communicationService.StartWalkAsync(dto, accessToken, token); if (cancelResult.Success) { if (cancelResult.Value) { success = true; createDelta = false; } } } if (createDelta) { await _pushService.AddAsync(nameof(Walk), localWalk.Id, SyncOperation.Edit, localWalk).ConfigureAwait(false); } return success; } /// /// Abschließen eines Walks /// /// ID des Walks /// Kotabsatz? /// Info des Walkers zum abschluss /// Punkte für die Bewertung des Walks /// Anmerkungen zum Rating des Walks /// Punkte für die Bewertung des Hundes / der Hunde /// Anmerkung zum Rating des Hundes / der Hunde /// Aktuelles Accesstoken /// CancellationToken /// true wenn erfolgreich, false sonst public async Task CompleteAsync(string walkId, bool defactation, string completedInfo, decimal ratingPoints, string ratingComment, decimal ratingDogsPoints, string ratingDogsComment, string accessToken, CancellationToken token) { var success = false; var localWalk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId, false); var updatedAt = DateTimeOffset.UtcNow; if (localWalk != null) { localWalk.Status = WalkStatus.Completed; localWalk.Completed = updatedAt; localWalk.Defactation = defactation; localWalk.CompletedInfo = completedInfo; localWalk.UpdatedAt = updatedAt; Repository.Update(localWalk); await CommitAsync(); success = true; } //Jetzt am Server oder Delta erstellen var createDelta = true; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var dto = new WalkCompleteDto() { WalkId = walkId, CompletedInfo = completedInfo, Defactation = defactation, RatingInfo = ratingComment, RatingPoints = ratingPoints, RatingDogsPoints = ratingDogsPoints, RatingDogsInfo = ratingDogsComment, UpdatedAt = updatedAt }; var cancelResult = await _communicationService.CompleteWalkAsync(dto, accessToken, token); if (cancelResult.Success) { if (cancelResult.Value) { success = true; createDelta = false; } } } if (createDelta) { //TODO: Delta für Rating? Wie genau machen? await _pushService.AddAsync(nameof(Walk), localWalk.Id, SyncOperation.Edit, localWalk).ConfigureAwait(false); } return success; } /// /// Zahlungsstatus eines Walks setzen /// /// ID des Walks /// Zahlungsstatus der gesetzt werden soll /// Aktuelles Accesstoken /// CancellationToken /// true wenn erfolgreich, false sonst public async Task SetPaymentStatusAsync(string walkId, PaymentStatus paymentStatus, string accessToken, CancellationToken token) { var success = false; var localWalk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId, false); var updatedAt = DateTimeOffset.UtcNow; if (localWalk != null) { localWalk.PaymentStatus = paymentStatus; localWalk.UpdatedAt = updatedAt; Repository.Update(localWalk); await CommitAsync(); success = true; } //Jetzt am Server oder Delta erstellen var createDelta = true; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var dto = new WalkPaymentStatusDto() { WalkId = walkId, UpdatedAt = updatedAt, PaymentStatus = (PaymentStatusDto)paymentStatus }; var cancelResult = await _communicationService.SetWalkPaymentStatusAsync(dto, accessToken, token); if (cancelResult.Success) { if (cancelResult.Value) { success = true; createDelta = false; } } } if (createDelta) { await _pushService.AddAsync(nameof(Walk), localWalk.Id, SyncOperation.Edit, localWalk).ConfigureAwait(false); } return success; } /// /// Prüfen ob ein Walk für einen DogWalker in einem Zeitraum gebucht werden kann /// /// Id des DogWalkers /// Start /// Ende /// Aktuelles Accesstoken /// CancellationToken /// true wenn verfügbar, false sonst public async Task IsWalkPossibleAsync(string dogWalkerId, DateTimeOffset start, DateTimeOffset end, string accessToken, CancellationToken token) { var isConnected = await _communicationService.IsConnected(); if (isConnected) { var query = new WalkPossibleDto() { DogWalkerId = dogWalkerId, Start = start, End = end }; var queryResult = await _communicationService.IsWalkPossibleAsync(query, accessToken, token); if (queryResult.Success) return queryResult.Value; } return false; } /// /// Anlegen eines Walks wenn DIREKT! buchen möglich ist. /// Wird online versucht und erst dann lokal gespeichert /// /// WalkCreateDto /// Aktuelles Accesstoken /// CancellationToken /// Angelegter Walk oder null, wenn nicht erfolgreich public async Task> CreateWalkDirectAsync(WalkCreateDto requestDto, string accessToken, CancellationToken token) { var result = new CommunicationResult() { Value = null, Success = false}; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var createResult = await _communicationService.CreateWalkDirectAsync(requestDto, accessToken, token); if (createResult.Success && createResult.Value.Status == CreateStatus.Success) { var walk = createResult.Value.Value; if (walk != null) { Repository.Add(walk); await CommitAsync(); result.Success = true; result.Value = walk; } } else { result.Success = false; result.Value = null; result.ErrorCode = createResult.ErrorCode; result.ErrorMessage = createResult.ErrorMessage; } } return result; } /// /// Anlegen eines Walks als Anfrage /// Wird online versucht und erst dann lokal gespeichert /// /// WalkCreateDto /// Aktuelles Accesstoken /// CancellationToken /// Angelegter Walk oder null, wenn nicht erfolgreich public async Task> CreateWalkRequestAsync(WalkCreateDto requestDto, string accessToken, CancellationToken token) { var result = new CommunicationResult() { Value = null, Success = false }; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var createResult = await _communicationService.CreateWalkRequestAsync(requestDto, accessToken, token); if (createResult.Success && createResult.Value.Status == CreateStatus.Success) { var walk = createResult.Value.Value; if (walk != null) { Repository.Add(walk); await CommitAsync(); result.Success = true; result.Value = walk; } } else { result.Success = false; result.Value = null; result.ErrorCode = createResult.ErrorCode; result.ErrorMessage = createResult.ErrorMessage; } } return result; } /// /// Akzeptieren eines Walks /// /// ID des Walks /// Aktuelles Accesstoken /// CancellationToken /// true wenn erfolgreich, false sonst public async Task AcceptAsync(string walkId, string accessToken, CancellationToken token) { var success = false; var localWalk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId, false); var updatedAt = DateTimeOffset.UtcNow; if (localWalk != null) { localWalk.Status = WalkStatus.Accepted; localWalk.PaymentStatus = PaymentStatus.Pending; localWalk.UpdatedAt = updatedAt; if(localWalk.Price == 0) localWalk.PaymentStatus = PaymentStatus.Paid; Repository.Update(localWalk); await CommitAsync(); success = true; } //Jetzt am Server oder Delta erstellen var createDelta = true; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var dto = new WalkAcceptDto() { WalkId = walkId, UpdatedAt = updatedAt }; var cancelResult = await _communicationService.AcceptWalkAsync(dto, accessToken, token); if (cancelResult.Success) { if (cancelResult.Value) { success = true; createDelta = false; } } } if (createDelta) { await _pushService.AddAsync(nameof(Walk), localWalk.Id, SyncOperation.Edit, localWalk).ConfigureAwait(false); } return success; } /// /// Ablehnen eines Walks /// /// ID des Walks /// Grund der Ablehnung /// Aktuelles Accesstoken /// CancellationToken /// true wenn erfolgreich, false sonst public async Task DeclineAsync(string walkId, string declineReason, string accessToken, CancellationToken token) { var success = false; var localWalk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId, false); var updatedAt = DateTimeOffset.UtcNow; if (localWalk != null) { localWalk.Status = WalkStatus.Declined; localWalk.UpdatedAt = updatedAt; localWalk.DeclinedReason = declineReason; Repository.Update(localWalk); await CommitAsync(); success = true; } //Jetzt am Server oder Delta erstellen var createDelta = true; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var dto = new WalkDeclineDto() { WalkId = walkId, UpdatedAt = updatedAt, DeclineReason = declineReason }; var cancelResult = await _communicationService.DeclineWalkAsync(dto, accessToken, token); if (cancelResult.Success) { if (cancelResult.Value) { success = true; createDelta = false; } } } if (createDelta) { await _pushService.AddAsync(nameof(Walk), localWalk.Id, SyncOperation.Edit, localWalk).ConfigureAwait(false); } return success; } /// /// Gibt zurück ob ein Walker Anfragen hat /// /// Id des Walkers /// true wenn ja, false sonst public async Task HasRequestedAsync(string dogWalkerId) { var requested = await Repository.CountAsync(c => c.DogWalkerId == dogWalkerId && c.Status == WalkStatus.Requested && c.Deleted == false).ConfigureAwait(false); return requested > 0; } /// /// Gibt eine Liste von Walks zurück, welche gleich gestartet werden sollen /// /// ID des AppUsers /// Datum für den Vergleich /// Liste von Walks die gestartet werden sollen public async Task> GetWalksToStartAsync(string appUserId, DateTimeOffset date) { var upperDate = date.AddHours(1); var walks = await Repository.FindAsync(c => c.DogWalkerId == appUserId && c.Status == WalkStatus.Accepted && c.Start >= date && c.Start <= upperDate && c.Deleted == false).ConfigureAwait(false); return walks.ToList(); } /// /// Gibt eine Liste von Walks zurück, welche beendet werden sollen /// /// ID des AppUsers /// Datum für den Vergleich /// Liste von Walks die gestartet werden sollen public async Task> GetWalksToCompleteAsync(string appUserId, DateTimeOffset date) { var walks = await Repository.FindAsync(c => c.DogWalkerId == appUserId && (c.Status == WalkStatus.Accepted || c.Status == WalkStatus.Started) && c.End <= date && c.Deleted == false).ConfigureAwait(false); return walks.ToList(); } /// /// Aktualisieren des Zahlungsstatus eines Walks in der lokalen DB /// /// Id des Wals /// Zu setzender Zahlungsstatus /// Datum Aktualisierung /// Walk oder null, wenn nicht gefunden public async Task SetPaymentStatusLocalAsync(string walkId, PaymentStatus paymentStatus, DateTimeOffset updatedAt) { var localWalk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId, false); if (localWalk != null) { localWalk.PaymentStatus = paymentStatus; localWalk.UpdatedAt = updatedAt; Repository.Update(localWalk); await CommitAsync(); await _pullService.AddOrUpddateAsync(nameof(Walk), updatedAt).ConfigureAwait(false); return localWalk; } return null; } /// /// Bestätigen eines Walks. Löst die Bezahlung für einen Walker aus. Geht nur Online! /// /// Id des Hundebesitzers /// Id des Walks /// Aktuelles Accesstoken /// CancellationToken /// CommunicationResult mit aktualisierten Walk public async Task> ConfirmAsync(string appUserId, string walkId, string accessToken, CancellationToken token) { var result = new CommunicationResult(); var hasConnection = await _communicationService.IsConnected(); if (hasConnection) { var model = new WalkConfirmDto() { AppUserId = appUserId, WalkId = walkId }; var serverResult = await _communicationService.ConfirmWalkAsync(model, accessToken, token); if (serverResult.Success && serverResult.Value != null) { //jetzt auch lokal die Änderung speichern var localWalk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId, false); if (localWalk != null) { localWalk.Status = serverResult.Value.Status; localWalk.Confirmed = serverResult.Value.Confirmed; localWalk.PaymentStatus = serverResult.Value.PaymentStatus; localWalk.UpdatedAt = serverResult.Value.UpdatedAt; Repository.Update(localWalk); await CommitAsync(); await _pullService.AddOrUpddateAsync(nameof(Walk), localWalk.UpdatedAt); } } return serverResult; } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Bestätigen eines Walks mit Rating. Löst die Bezahlung für einen Walker aus. Geht nur Online! /// /// Id des Hundebesitzers /// Id des Walks /// Punkte für das Rating /// Anmerkungen zum Rating /// Aktuelles Accesstoken /// CancellationToken /// CommunicationResult mit aktualisierten Walk public async Task> ConfirmWithRatingAsync(string appUserId, string walkId, decimal ratingPoints, string ratingInfo, string accessToken, CancellationToken token) { var result = new CommunicationResult(); var hasConnection = await _communicationService.IsConnected(); if (hasConnection) { var model = new WalkConfirmWithRatingDto() { AppUserId = appUserId, WalkId = walkId, RatingPoints = ratingPoints, RatingInfo = ratingInfo }; var serverResult = await _communicationService.ConfirmWalkWithRatingAsync(model, accessToken, token); if (serverResult.Success && serverResult.Value != null) { //jetzt auch lokal die Änderung speichern var localWalk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId, false); if (localWalk != null) { localWalk.Status = serverResult.Value.Status; localWalk.Confirmed = serverResult.Value.Confirmed; localWalk.PaymentStatus = serverResult.Value.PaymentStatus; localWalk.UpdatedAt = serverResult.Value.UpdatedAt; Repository.Update(localWalk); await CommitAsync(); await _pullService.AddOrUpddateAsync(nameof(Walk), localWalk.UpdatedAt); } } return serverResult; } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Reklamieren eines Walks /// /// ID des Walks /// Typ der Reklamation /// Grund der Reklamation als Text /// Aktuelles Accesstoken /// CancellationToken /// true wenn erfolgreich, false sonst public async Task> ComplainAsync(string walkId, WalkComplaintType type, string complainReason, string accessToken, CancellationToken token) { var result = new CommunicationResult(); var hasConnection = await _communicationService.IsConnected(); if (hasConnection) { var model = new WalkComplaintCreateDto() { WalkId = walkId, Type = type.ToDto(), Message = complainReason }; var serverResult = await _communicationService.ComplainWalkAsync(model, accessToken, token); if (serverResult.Success && serverResult.Value != null) { //jetzt auch lokal die Änderung speichern var localWalk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId, false); if (localWalk != null) { localWalk.Status = serverResult.Value.Status; localWalk.Complained = serverResult.Value.Complained; localWalk.ComplainReason = serverResult.Value.ComplainReason; localWalk.UpdatedAt = serverResult.Value.UpdatedAt; Repository.Update(localWalk); await CommitAsync(); await _pullService.AddOrUpddateAsync(nameof(Walk), localWalk.UpdatedAt); result.Success = true; result.Value = true; } } } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Holen einer Reklamation zu einem Walk /// /// ID des Walks /// Aktuelles Accesstoken /// CancellationToken /// CommunicationResult public async Task> GetWalkComplaintAsync(string walkId, string accessToken, CancellationToken token) { var result = new CommunicationResult(); result.Success = false; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var queryResult = await _communicationService.GetWalkComplaintAsync(walkId, accessToken, token); if (queryResult.Success) { result.Success = true; result.Value = queryResult.Value; return result; } } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } #region Pull-Push Implementation /// /// Holen der letzten Daten vom Server und Synchronisieren mit den lokalen Daten /// /// Sprache /// Aktuelles Accesstoken /// CancellationToken /// Task public async Task> PullAsync(string language, string accessToken, CancellationToken token) { var result = new SyncResult(); var isConnected = await _communicationService.IsConnected(); if (isConnected) { var user = await _appUserRepository.FirstOrDefaultAsync(c => c.Id != "", false).ConfigureAwait(false); //Zuerst lezte Aktivität holen... DateTimeOffset? lastUpdate = null; var lastSyncInfo = await _pullService.GetAsync(nameof(Walk)); if (lastSyncInfo != null) lastUpdate = lastSyncInfo.LastUpdate; var requestResult = await _communicationService.GetWalksForSyncAsync(user.Id, lastUpdate, accessToken, token); if (requestResult.Success) { result = await HandleChangesAsync(requestResult.Value); if (result.LastUpdate != null) await _pullService.AddOrUpddateAsync(nameof(Walk), result.LastUpdate.Value).ConfigureAwait(false); } result.LastUpdate ??= lastUpdate; } return result; } /// /// Holen der Daten vom lokalen Speicher die noch nicht synchronisiert wurden und senden an den Server /// /// Aktuelles Accesstoken /// CancellationToken /// Task public async Task> PushAsync(string accessToken, CancellationToken token) { var result = new SyncResult(); if (await _pushService.HasOpenAsync(nameof(Walk)) > 0) { var deltas = await _pushService.GetAllAsync(nameof(Walk)); if (deltas.Any()) { var user = await _appUserRepository.FirstOrDefaultAsync(c => c.Id != "", false).ConfigureAwait(false); deltas = deltas.OrderBy(c => c.DateTime).ToList(); var isConnected = await _communicationService.IsConnected(); if (!isConnected) return result; foreach (var syncInfoPush in deltas) { try { if (!string.IsNullOrWhiteSpace(syncInfoPush.Value)) { var request = JsonSerializer.Deserialize(syncInfoPush.Value, new JsonSerializerOptions(JsonSerializerDefaults.Web)); if (syncInfoPush.Operation == SyncOperation.Create) { var createDto = request.ToDto(); var createResult = await _communicationService.CreateWalkAsync(createDto, accessToken, token); if (createResult.Success && createResult.Value.Status != CreateStatus.Error) { await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); } //TODO: Was tun wenn Fehler? } else if (syncInfoPush.Operation == SyncOperation.Edit) { var updateDto = request.ToDto(); var updateResult = await _communicationService.UpdateWalkAsync(updateDto, accessToken, token); if (updateResult.Success && updateResult.Value) await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); //TODO: Was tun wenn Fehler? } else { var deleteDto = request.ToDto(); var deleteResult = await _communicationService.DeleteWalkAsync(deleteDto, user.Id, accessToken, token); if (deleteResult.Success && deleteResult.Value) await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); //TODO: Was tun wenn Fehler? } } else { await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); } } //Else ist nichts tun, konnte nicht übertragen werden. } } return result; } #endregion #region Private /// /// Behandeln der Liste von öffentlichern Anfragen wenn welche vom Online-Store geholt werden. /// /// Liste der anfragen /// Task private async Task> HandleChangesAsync(List walks) { var result = new SyncResult(); //Je Rasse durchgehen ob was gemacht werden soll foreach (var walk in walks) { var localWalk = await Repository.FirstOrDefaultAsync(c => c.Id == walk.Id, false); if (localWalk != null && localWalk.UpdatedAt < walk.UpdatedAt) { if (localWalk.UpdatedAt >= walk.UpdatedAt) { if (result.LastUpdate == null || result.LastUpdate < localWalk.UpdatedAt) result.LastUpdate = localWalk.UpdatedAt; continue; } var deleted = localWalk.Deleted == false && walk.Deleted; localWalk.Version = walk.Version; localWalk.UpdatedAt = walk.UpdatedAt; localWalk.Deleted = walk.Deleted; localWalk.Type = walk.Type; localWalk.ServiceType = walk.ServiceType; localWalk.DogWalkerId = walk.DogWalkerId; localWalk.DogOwnerId = walk.DogOwnerId; localWalk.PublicWalkRequestId = walk.PublicWalkRequestId; localWalk.DogsJson = walk.DogsJson; localWalk.DogCount = walk.DogCount; localWalk.Start = walk.Start; localWalk.End = walk.End; localWalk.PickupAddress = walk.PickupAddress; localWalk.PickupAddressLat = walk.PickupAddressLat; localWalk.PickupAddressLng = walk.PickupAddressLng; localWalk.ReturnAddress = walk.ReturnAddress; localWalk.ReturnAddressLat = walk.ReturnAddressLat; localWalk.ReturnAddressLng = walk.ReturnAddressLng; localWalk.Price = walk.Price; localWalk.Info = walk.Info; localWalk.Status = walk.Status; localWalk.Started = walk.Started; localWalk.Completed = walk.Completed; localWalk.Confirmed = walk.Confirmed; localWalk.Complained = walk.Complained; localWalk.ComplainReason = walk.ComplainReason; localWalk.DeclinedReason = walk.DeclinedReason; localWalk.CancelledReason = walk.CancelledReason; localWalk.CancelledBy = walk.CancelledBy; localWalk.CompletedInfo = walk.CompletedInfo; localWalk.Defactation = walk.Defactation; localWalk.PaymentStatus = walk.PaymentStatus; localWalk.Created = walk.Created; if (result.LastUpdate == null || result.LastUpdate < localWalk.UpdatedAt) result.LastUpdate = localWalk.UpdatedAt; if (!deleted) result.Updated.Add(localWalk); else result.Deleted.Add(localWalk); Repository.Update(localWalk); } if (localWalk == null) { localWalk = new Walk() { Id = walk.Id, Version = walk.Version, UpdatedAt = walk.UpdatedAt, Deleted = walk.Deleted, Type = walk.Type, ServiceType = walk.ServiceType, DogWalkerId = walk.DogWalkerId, DogOwnerId = walk.DogOwnerId, PublicWalkRequestId = walk.PublicWalkRequestId, DogsJson = walk.DogsJson, DogCount = walk.DogCount, Start = walk.Start, End = walk.End, PickupAddress = walk.PickupAddress, PickupAddressLat = walk.PickupAddressLat, PickupAddressLng = walk.PickupAddressLng, ReturnAddress = walk.ReturnAddress, ReturnAddressLat = walk.ReturnAddressLat, ReturnAddressLng = walk.ReturnAddressLng, Price = walk.Price, Info = walk.Info, Status = walk.Status, Started = walk.Started, Completed = walk.Completed, Confirmed = walk.Confirmed, Complained = walk.Complained, ComplainReason = walk.ComplainReason, DeclinedReason = walk.DeclinedReason, CancelledReason = walk.CancelledReason, CancelledBy = walk.CancelledBy, CompletedInfo = walk.CompletedInfo, Defactation = walk.Defactation, PaymentStatus = walk.PaymentStatus, Created = walk.Created }; Repository.Add(localWalk); if (result.LastUpdate == null || result.LastUpdate < localWalk.UpdatedAt) result.LastUpdate = localWalk.UpdatedAt; result.Added.Add(localWalk); } } if (result.HasChanges) { try { await CommitAsync().ConfigureAwait(false); } catch (Exception ex) { var err = ex.Message; } } return result; } #endregion } }