Florian Mihalits 38142f2435 Add payment type handling for walks
- Updated `Walk` class to include `PaymentType` property.
- Introduced `WalkPaymentTypeDto` enum for payment types.
- Modified DTOs (`PublicWalkRequestAcceptDto`, `WalkCreateDto`, `WalkDto`, `WalkWithNamesDto`) to include `PaymentType`.
- Enhanced `ApiWalksController` with new endpoints for accepting and canceling walks, and confirming walks with ratings.
- Updated `MappingProfile` for seamless conversion between `WalkPaymentType` and `WalkPaymentTypeDto`.
2025-06-30 15:08:57 +02:00

873 lines
48 KiB
C#

using gehGassi.Core.Interfaces;
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;
using gehGassi.Domain.Common;
using Microsoft.Data.SqlClient;
using gehGassi.Domain.Dogs;
using gehGassi.Domain.Payment;
using Azure;
using gehGassi.Dto.Messages;
namespace gehGassi.Core.Services
{
/// <summary>
/// Service der die Verwaltung von Walks ermöglicht
/// </summary>
public class WalkService : ServiceBase<Walk>, IWalkService
{
private readonly IServiceProvider _serviceProvider;
private readonly IRepository<WalkWithNames> _namesRepository;
private readonly IRepository<PublicWalkRequest> _publicWalkRequestRepository;
private readonly IRepository<PublicWalkResponse> _publicWalkResponseRepository;
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="unitOfWork">Instanz eines IUnitOfWork</param>
/// <param name="serviceProvider">Instanz eines IServiceProvider</param>
public WalkService(IUnitOfWork unitOfWork, IServiceProvider serviceProvider) : base(unitOfWork)
{
_serviceProvider = serviceProvider;
_namesRepository = unitOfWork.GetRepository<WalkWithNames>();
_publicWalkRequestRepository = unitOfWork.GetRepository<PublicWalkRequest>();
_publicWalkResponseRepository = unitOfWork.GetRepository<PublicWalkResponse>();
}
/// <summary>
/// Erstellen eines Walks
/// </summary>
/// <returns>Walk</returns>
public Walk Create()
{
var walk = new Walk()
{
Id = Guid.NewGuid().ToString("N"),
PaymentType = WalkPaymentType.MangoPay,
Created = DateTimeOffset.UtcNow
};
return walk;
}
/// <summary>
/// Gibt eine Liste der Walks eines Benutzers zurück. Kann Owner und Walker sein
/// </summary>
/// <param name="appUserId">Id des AppUsers</param>
/// <param name="lastUpdate">Letztes Update</param>
/// <returns>Liste der öffentlichen Anfragen</returns>
public async Task<List<Walk>> GetForSyncAppAsync(string appUserId, DateTimeOffset? lastUpdate)
{
var query = Repository.Query(c => c.DogOwnerId == appUserId || c.DogWalkerId == appUserId);
if (lastUpdate.HasValue)
query = query.Where(c => c.UpdatedAt > lastUpdate);
return await query.ToListAsync().ConfigureAwait(false);
}
/// <summary>
/// Gibt eine gefilterte Liste von Walks 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<WalkWithNames> FilterWithNames(string filter, bool includeDeleted)
{
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.DogOwnerName.Contains(filter) || c.DogWalkerName.Contains(filter) || c.PickupAddress_City.Contains(filter));
if (!includeDeleted)
baseQuery = baseQuery.Where(c => c.Deleted == false);
return baseQuery;
}
/// <summary>
/// Gibt eine Liste von Walks mit Namen aufgelöst zurück
/// </summary>
/// <param name="dogWalkerId">Id des Dogwalkers oder leer wenn nicht für DogWalker</param>
/// <param name="dogOwnerId">Id des DogOwners oder leer wenn nicht für DogOwner</param>
/// <param name="date">Datum ab dem gesucht werden soll oder NULL wenn alle</param>
/// <param name="status">Status des Walks, NULL wenn alle</param>
/// <param name="serviceType">Service Type, NULL wenn alle</param>
/// <param name="paymentStatus">Zahlungsstatus, NULL wenn alle</param>
/// <param name="dogFilter">Optinal: nach HundeID filtern</param>
/// <param name="walkerFilter">Optional: nach Walkername filtern</param>
/// <param name="ownerFilter">Optional: nach Ownername filtern</param>
/// <param name="ignoreCancelled">Stornierte Ignorieren</param>
/// <param name="forNextWalk">Sollen nur Walks für den nächsten Walk selektiert werden? Dann nur Angenommene oder Laufende</param>
/// <param name="ignoreRequested">Sollen Walks die noch eine Anfrage sind, ohne Akzeptiert oder Agelehnt ignorniert werden?</param>
/// <param name="ignoreDeclined">Sollen abgelehnte Walks ignoriert werden?</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 gefudene Walks</returns>
public async Task<(int total, List<WalkWithNames> list)> GetWithNamesAsync(string dogWalkerId, string dogOwnerId, DateTimeOffset? date, WalkStatus? status, WalkServiceType? serviceType, PaymentStatus? paymentStatus, string dogFilter, string walkerFilter, string ownerFilter, bool ignoreCancelled, bool forNextWalk, bool ignoreRequested, bool ignoreDeclined, 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_WalksWithNames()", c => c.Deleted == false);
if(!string.IsNullOrWhiteSpace(dogWalkerId))
baseQuery = baseQuery.Where(c => c.DogWalkerId == dogWalkerId);
if (!string.IsNullOrWhiteSpace(dogOwnerId))
baseQuery = baseQuery.Where(c => c.DogOwnerId == dogOwnerId);
if(date.HasValue)
baseQuery = baseQuery.Where(c => c.Start >= date);
if (status.HasValue)
{
baseQuery = baseQuery.Where(c => c.Status == status);
}
else
{
if(ignoreCancelled)
baseQuery = baseQuery.Where(c => c.Status != WalkStatus.Cancelled);
if (forNextWalk)
{
baseQuery = baseQuery.Where(c => c.Status == WalkStatus.Accepted || c.Status == WalkStatus.Started);
}
if (ignoreRequested)
baseQuery = baseQuery.Where(c => c.Status != WalkStatus.Requested);
if (ignoreDeclined)
baseQuery = baseQuery.Where(c => c.Status != WalkStatus.Declined);
}
if (serviceType.HasValue)
baseQuery = baseQuery.Where(c => c.ServiceType == serviceType);
if (paymentStatus.HasValue)
baseQuery = baseQuery.Where(c => c.PaymentStatus == paymentStatus);
if (!string.IsNullOrWhiteSpace(dogFilter))
baseQuery = baseQuery.Where(c => c.DogsJson.Contains(dogFilter));
if (!string.IsNullOrWhiteSpace(walkerFilter))
baseQuery = baseQuery.Where(c => c.DogWalkerName.Contains(walkerFilter));
if (!string.IsNullOrWhiteSpace(ownerFilter))
baseQuery = baseQuery.Where(c => c.DogOwnerName.Contains(ownerFilter));
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.PickupLocation.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
else
{
var list = await baseQuery.OrderBy(c => c.Start).ThenBy(c => c.PickupLocation.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
}
/// <summary>
/// Gibt eine Liste von Walks mit Namen aufgelöst zurück für Walker
/// Sonderfall hier ist, dass keine Walks mit einem PaymentStatus unter Authorized zurückgibt
/// </summary>
/// <param name="dogWalkerId">Id des Dogwalkers oder leer wenn nicht für DogWalker</param>
/// <param name="dogOwnerId">Id des DogOwners oder leer wenn nicht für DogOwner</param>
/// <param name="date">Datum ab dem gesucht werden soll oder NULL wenn alle</param>
/// <param name="status">Status des Walks, NULL wenn alle</param>
/// <param name="serviceType">Service Type, NULL wenn alle</param>
/// <param name="paymentStatus">Zahlungsstatus, NULL wenn alle</param>
/// <param name="dogFilter">Optinal: nach HundeID filtern</param>
/// <param name="walkerFilter">Optional: nach Walkername filtern</param>
/// <param name="ownerFilter">Optional: nach Ownername filtern</param>
/// <param name="ignoreCancelled">Stornierte Ignorieren</param>
/// <param name="forNextWalk">Sollen nur Walks für den nächsten Walk selektiert werden? Dann nur Angenommene oder Laufende</param>
/// <param name="ignoreRequested">Sollen Walks die noch eine Anfrage sind, ohne Akzeptiert oder Agelehnt ignorniert werden?</param>
/// <param name="ignoreDeclined">Sollen abgelehnte Walks ignoriert werden?</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 gefudene Walks</returns>
public async Task<(int total, List<WalkWithNames> list)> GetWithNamesForWalkerAsync(string dogWalkerId, string dogOwnerId, DateTimeOffset? date, WalkStatus? status, WalkServiceType? serviceType, PaymentStatus? paymentStatus, string dogFilter, string walkerFilter, string ownerFilter, bool ignoreCancelled, bool forNextWalk, bool ignoreRequested, bool ignoreDeclined, 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_WalksWithNames()", c => c.Deleted == false);
if (!string.IsNullOrWhiteSpace(dogWalkerId))
baseQuery = baseQuery.Where(c => c.DogWalkerId == dogWalkerId);
if (!string.IsNullOrWhiteSpace(dogOwnerId))
baseQuery = baseQuery.Where(c => c.DogOwnerId == dogOwnerId);
if (date.HasValue)
baseQuery = baseQuery.Where(c => c.Start >= date);
if (status.HasValue)
{
baseQuery = baseQuery.Where(c => c.Status == status);
}
else
{
if (ignoreCancelled)
baseQuery = baseQuery.Where(c => c.Status != WalkStatus.Cancelled);
if (forNextWalk)
{
baseQuery = baseQuery.Where(c => c.Status == WalkStatus.Accepted || c.Status == WalkStatus.Started);
}
if (ignoreRequested)
baseQuery = baseQuery.Where(c => c.Status != WalkStatus.Requested);
if (ignoreDeclined)
baseQuery = baseQuery.Where(c => c.Status != WalkStatus.Declined);
}
if (serviceType.HasValue)
baseQuery = baseQuery.Where(c => c.ServiceType == serviceType);
if (paymentStatus.HasValue)
baseQuery = baseQuery.Where(c => c.PaymentStatus == paymentStatus && c.PaymentStatus >= PaymentStatus.Authorized);
else
baseQuery = baseQuery.Where(c => c.PaymentStatus >= PaymentStatus.Authorized || (c.PaymentStatus == PaymentStatus.None && (c.Status == WalkStatus.Requested || c.Status == WalkStatus.Declined)));
if (!string.IsNullOrWhiteSpace(dogFilter))
baseQuery = baseQuery.Where(c => c.DogsJson.Contains(dogFilter));
if (!string.IsNullOrWhiteSpace(walkerFilter))
baseQuery = baseQuery.Where(c => c.DogWalkerName.Contains(walkerFilter));
if (!string.IsNullOrWhiteSpace(ownerFilter))
baseQuery = baseQuery.Where(c => c.DogOwnerName.Contains(ownerFilter));
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.PickupLocation.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
else
{
var list = await baseQuery.OrderBy(c => c.Start).ThenBy(c => c.PickupLocation.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
}
/// <summary>
/// Gibt eine Liste von Walks mit Namen aufgelöst zurück für Hundebesitzer die anstehen, laufen, nicht abgeschlossen sind, nicht gestartet wurden oder zu bezahlen sind
/// </summary>
/// <param name="dogOwnerId">Id des Hundebesitzers</param>
/// <param name="date">Datum ab dem gesucht werden soll oder NULL wenn alle</param>
/// <param name="skip">Datensätze auslassen</param>
/// <param name="take">Datensätze nehmen</param>
/// <returns>Liste gefudene Walks</returns>
public async Task<(int total, List<WalkWithNames> list)> GetNextWithNamesForOwnerAsync(string dogOwnerId, DateTimeOffset date, int skip, int take)
{
var total = 0;
var list = new List<WalkWithNames>();
//1. Aktuell laufende Walks
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.DogOwnerId == dogOwnerId && c.Deleted == false && c.Status == WalkStatus.Started);
//baseQuery = baseQuery.Where(c => c.Start <= date && c.End >= date);
//Anpassung, Walks auch dann anzeigen, wenn gestartet aber das Startdatum noch nicht erreicht ist
//baseQuery = baseQuery.Where(c => c.Start <= date && c.End >= date);
baseQuery = baseQuery.Where(c => c.End >= date);
list.AddRange(await baseQuery.OrderBy(c => c.Start).ToListAsync().ConfigureAwait(false));
//2. Nicht gestartete Walks
baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.DogOwnerId == dogOwnerId && c.Deleted == false && c.Status == WalkStatus.Accepted && c.PaymentStatus == PaymentStatus.Authorized);
baseQuery = baseQuery.Where(c => c.Start <= date && c.End >= date);
list.AddRange(await baseQuery.OrderBy(c => c.Start).ToListAsync().ConfigureAwait(false));
//3. Zu bezahlende Walks
baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.DogOwnerId == dogOwnerId && c.Deleted == false && c.Status == WalkStatus.Accepted && c.PaymentStatus == PaymentStatus.Pending);
list.AddRange(await baseQuery.OrderBy(c => c.Start).ToListAsync().ConfigureAwait(false));
//4. Nicht abgeschlossene Walks
baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.DogOwnerId == dogOwnerId && c.Deleted == false && (c.Status == WalkStatus.Accepted || c.Status == WalkStatus.Started) && c.PaymentStatus == PaymentStatus.Authorized);
baseQuery = baseQuery.Where(c => c.End <= date);
list.AddRange(await baseQuery.OrderBy(c => c.Start).ToListAsync().ConfigureAwait(false));
//5. Nicht bestätigte Walks
baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.DogOwnerId == dogOwnerId && c.Deleted == false && c.Status == WalkStatus.Completed && c.PaymentStatus == PaymentStatus.Authorized);
baseQuery = baseQuery.Where(c => c.End <= date);
list.AddRange(await baseQuery.OrderBy(c => c.Start).ToListAsync().ConfigureAwait(false));
//6. Nächster anstehender Walk
baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.DogOwnerId == dogOwnerId && c.Deleted == false && c.Status == WalkStatus.Accepted && c.PaymentStatus == PaymentStatus.Authorized);
baseQuery = baseQuery.Where(c => c.Start >= date).OrderBy(c => c.Start);
var nextWaks = (await baseQuery.ToListAsync().ConfigureAwait(false)).Skip(0).Take(3);
if(nextWaks.Any())
list.AddRange(nextWaks);
total = list.Count;
list = list.Skip(skip).Take(take).ToList();
return (total, list);
}
/// <summary>
/// Gibt eine Liste von Walks mit Namen aufgelöst zurück für Walker die anstehen, laufen, nicht abgeschlossen sind oder nicht gestartet wurden
/// </summary>
/// <param name="dogWalkerId">Id des Walkers</param>
/// <param name="date">Datum ab dem gesucht werden soll oder NULL wenn alle</param>
/// <param name="skip">Datensätze auslassen</param>
/// <param name="take">Datensätze nehmen</param>
/// <returns>Liste gefudene Walks</returns>
public async Task<(int total, List<WalkWithNames> list)> GetNextWithNamesForWalkerAsync(string dogWalkerId, DateTimeOffset date, int skip, int take)
{
var total = 0;
var list = new List<WalkWithNames>();
//1. Aktuell laufende Walks
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.DogWalkerId == dogWalkerId && c.Deleted == false && c.Status == WalkStatus.Started);
//baseQuery = baseQuery.Where(c => c.Start <= date && c.End >= date);
//Anpassung, Walks auch dann anzeigen, wenn gestartet aber das Startdatum noch nicht erreicht ist
baseQuery = baseQuery.Where(c => c.End >= date);
list.AddRange(await baseQuery.OrderBy(c => c.Start).ToListAsync().ConfigureAwait(false));
//2. Nicht gestartete Walks
baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.DogWalkerId == dogWalkerId && c.Deleted == false && c.Status == WalkStatus.Accepted && c.PaymentStatus == PaymentStatus.Authorized);
baseQuery = baseQuery.Where(c => c.Start <= date && c.End >= date);
list.AddRange(await baseQuery.OrderBy(c => c.Start).ToListAsync().ConfigureAwait(false));
//3. Nicht abgeschlossene Walks
baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.DogWalkerId == dogWalkerId && c.Deleted == false && (c.Status == WalkStatus.Accepted || c.Status == WalkStatus.Started) && c.PaymentStatus == PaymentStatus.Authorized);
baseQuery = baseQuery.Where(c => c.End <= date);
list.AddRange(await baseQuery.OrderBy(c => c.Start).ToListAsync().ConfigureAwait(false));
//5. Nächster anstehender Walk
baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.DogWalkerId == dogWalkerId && c.Deleted == false && c.Status == WalkStatus.Accepted && c.PaymentStatus == PaymentStatus.Authorized);
baseQuery = baseQuery.Where(c => c.Start >= date).OrderBy(c => c.Start);
var nextWaks = (await baseQuery.ToListAsync().ConfigureAwait(false)).Skip(0).Take(3);
if (nextWaks.Any())
list.AddRange(nextWaks);
total = list.Count;
list = list.Skip(skip).Take(take).ToList();
return (total, list);
}
/// <summary>
/// Gibt einen Walk mit Namen aufgelöst zurück
/// </summary>
/// <param name="walkId">Id des Walks</param>
/// <returns>Walk oder null, wenn nicht gefunden</returns>
public async Task<WalkWithNames> GetWalkWithNamesAsync(string walkId)
{
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_WalksWithNames()", c => c.Deleted == false);
baseQuery = baseQuery.Where(c => c.Id == walkId && c.Deleted == false);
return await baseQuery.FirstOrDefaultAsync().ConfigureAwait(false);
}
/// <summary>
/// Zurückgeben einer Liste von Walks die einen bestimmten Status haben
/// </summary>
/// <param name="status">Gesuchter Status</param>
/// <returns>Liste von Walks</returns>
public async Task<List<Walk>> GetByStatusAsync(WalkStatus status)
{
var walks = await Repository.FindAsync(c => c.Status == status && c.Deleted == false).ConfigureAwait(false);
return walks.ToList();
}
/// <summary>
/// Gibt einen Walk zurück der mit einer öffentlichen Anfrage verknüpft ist
/// </summary>
/// <param name="publicWalkRequestId">Id der öffentlichen Anfrage</param>
/// <returns>Walk oder null, wenn nicht gefunden</returns>
public async Task<Walk> GetWalkByPublicRequestAsync(string publicWalkRequestId)
{
var walk = await Repository.FirstOrDefaultAsync(c => c.PublicWalkRequestId == publicWalkRequestId && c.Deleted == false).ConfigureAwait(false);
return walk;
}
/// <summary>
/// Prüft ob ein Walk für einen Zeitraum gebucht werden kann.
/// Sittings werden dabei ignoriert!
/// </summary>
/// <param name="dogWalkerId">Id des dogwalkers</param>
/// <param name="start">Startdatum</param>
/// <param name="end">Enddatum</param>
/// <returns>true wenn verfügbar, false sonst</returns>
public async Task<bool> IsWalkPossibleAsync(string dogWalkerId, DateTimeOffset start, DateTimeOffset end)
{
var walkFound = await Repository.FirstOrDefaultAsync(c => c.DogWalkerId == dogWalkerId && c.ServiceType == WalkServiceType.Walking && start < c.End && end > c.Start && c.Deleted == false && (c.Status == WalkStatus.Accepted || c.Status == WalkStatus.Started)).ConfigureAwait(false);
return walkFound == null;
}
/// <summary>
/// Setzt Walks die im Status "Request" sind auf Cancel.
/// Wird verwendet wenn eine FIXE buchung gemacht wird und andere noch auf eine Antwort warten.
/// </summary>
/// <param name="dogWalkerId">Id des dogwalkers</param>
/// <param name="start">Startdatum</param>
/// <param name="end">Enddatum</param>
/// <returns>Anzahl betroffene Walks</returns>
public async Task<int> CancelWalkRequestsAsync(string dogWalkerId, DateTimeOffset start, DateTimeOffset end)
{
var walks = await Repository.FindAsync(c => c.DogWalkerId == dogWalkerId && c.ServiceType == WalkServiceType.Walking && start < c.End && end > c.Start && c.Deleted == false && c.Status == WalkStatus.Requested).ConfigureAwait(false);
foreach (var walk in walks)
{
walk.Status = WalkStatus.Cancelled;
walk.CancelledBy = CancellationSource.GehGassi;
walk.CancelledReason = string.Empty;
walk.UpdatedAt = DateTimeOffset.UtcNow;
}
return walks.Count();
}
/// <summary>
/// Walks stornieren welche im Status Request sind und das Startdatum erreicht wurde
/// </summary>
/// <param name="date">Datum für Prüfung</param>
/// <returns>Anzahl timed out</returns>
public async Task<int> CancelTimedOutAsync(DateTimeOffset date)
{
var walks = await Repository.FindAsync(c => c.Start < DateTimeOffset.UtcNow && c.Deleted == false && c.Status == WalkStatus.Requested).ConfigureAwait(false);
foreach (var walk in walks)
{
walk.Status = WalkStatus.Cancelled;
walk.CancelledBy = CancellationSource.GehGassi;
walk.CancelledReason = string.Empty;
walk.UpdatedAt = DateTimeOffset.UtcNow;
}
return walks.Count();
}
/// <summary>
/// Stornieren von Walks die im Zahlungsstatus Pending sind und länger als X Minuten alt sind
/// </summary>
/// <param name="date">Datum für Prüfung</param>
/// <param name="minutes">Ablaufzeit in Minuten</param>
/// <returns>Anzahl storniert</returns>
public async Task<int> CancelPendingAsync(DateTimeOffset date, int minutes)
{
var publicWalkRequestService = (IPublicWalkRequestService)_serviceProvider.GetService(typeof(IPublicWalkRequestService));
var publicWalkResponseService = (IPublicWalkResponseService)_serviceProvider.GetService(typeof(IPublicWalkResponseService));
var voucherCampaignService = (IVoucherCampaignService)_serviceProvider.GetService(typeof(IVoucherCampaignService));
var customerService = (ICustomerService)_serviceProvider.GetService(typeof(ICustomerService));
var mangoPayService = (IMangoPayService)_serviceProvider.GetService(typeof(IMangoPayService));
var compareDate = date.AddMinutes(-minutes);
var walks = await Repository.FindAsync(c => c.PaymentStatus == PaymentStatus.Pending && c.Deleted == false && c.Status == WalkStatus.Accepted && c.Created < compareDate).ConfigureAwait(false);
foreach (var walk in walks)
{
if (!string.IsNullOrWhiteSpace(walk.VoucherId) && walk.VoucherAmmount > 0)
{
var usage = await voucherCampaignService.GetUsageAsync(walk.VoucherId, walk.DogOwnerId, walk.Id);
if (usage != null)
{
var voucher = await voucherCampaignService.GetVoucherAsync(usage.VoucherId);
if (voucher != null && voucher.Id == usage.VoucherId)
{
var campaign = await voucherCampaignService.GetAsync(voucher.VoucherCampaignId);
if (campaign != null)
{
var customer = await customerService.GetAsync(campaign.CustomerId);
if (customer != null)
{
//Geld vom Transferkonto der Kamapgne auf das Guthabenkonto überweisen mit Verweis auf den Walk und Transaktion erfassen
var ammount = (int)(usage.VoucherAmmountUsed * 100);
var transferResult = await mangoPayService.TransferMoneyAsync(customer.MangopayPaymentId, campaign.WalletFeeId,
customer.MangopayPaymentId, campaign.WalletCreditId, walk.Id, ammount, 0, $"VoucherId_{voucher.Id}_WalkId_{walk.Id}", "", "");
if (transferResult.Success)
{
//Usage beim voucher zurücksetzen (UsedCount, IsValid wenn Einzelgutschein)
voucher.UsedCount -= 1;
if (voucher.UsedCount < 0)
voucher.UsedCount = 0;
voucher.IsValid = true;
//Usage in der Kampagne zurücksetzen (VouchersUsed, BudgetUsed)
campaign.VouchersUsed -= 1;
if (campaign.VouchersUsed < 0)
campaign.VouchersUsed = 0;
campaign.BudgetUsed -= usage.VoucherAmmountUsed;
if (campaign.BudgetUsed < 0)
campaign.BudgetUsed = 0;
walk.VoucherId = null;
walk.VoucherAmmount = 0;
//Reservieren durch die Anlage einer VoucherUsage
voucherCampaignService.RemoveUsage(usage);
}
}
}
}
}
}
walk.Status = WalkStatus.Cancelled;
walk.CancelledBy = CancellationSource.GehGassi;
walk.CancelledReason = string.Empty;
walk.UpdatedAt = DateTimeOffset.UtcNow;
if (walk.Type == WalkType.OpenRequest)
{
var resetSuccess = await publicWalkRequestService.ResetAsync(walk.PublicWalkRequestId);
if (resetSuccess)
{
var reopenedCount = await publicWalkResponseService.SetOpenForClosedOrAcceptedAsync(walk.PublicWalkRequestId);
}
walk.PublicWalkRequestId = string.Empty;
}
}
return walks.Count();
}
/// <summary>
/// Abschliessen eines Walks, falls das vergessen wurde
/// </summary>
/// <param name="date">Datum für Prüfung</param>
/// <param name="minutes">Ablaufzeit in Minuten</param>
/// <returns>Anzahl abgeschlossen</returns>
public async Task<int> CompleteAutoAsync(DateTimeOffset date, int minutes)
{
var compareDate = date.AddMinutes(-minutes);
var walks = await Repository.FindAsync(c => c.PaymentStatus == PaymentStatus.Authorized && c.Deleted == false && (c.Status == WalkStatus.Accepted || c.Status == WalkStatus.Started) && c.End < compareDate).ConfigureAwait(false);
foreach (var walk in walks)
{
walk.Status = WalkStatus.Completed;
walk.UpdatedAt = DateTimeOffset.UtcNow;
}
return walks.Count();
}
/// <summary>
/// Bestätigen von Walks die im Status Abgeschlossen sind und länger als X Minuten alt sind.
/// Löst die Überweisung von den Kosten für den Walk vom hundebesitzer an den Dogwalker aus.
/// </summary>
/// <param name="date">Datum für Prüfung</param>
/// <param name="minutes">Ablaufzeit in Minuten</param>
/// <returns>Anzahl bestätigt</returns>
public async Task<int> ConfirmCompletedAsync(DateTimeOffset date, int minutes)
{
var compareDate = date.AddMinutes(-minutes);
var mangoPayService = (IMangoPayService)_serviceProvider.GetService(typeof(IMangoPayService));
var gehGassiTransactionService = (ITransactionFeeService)_serviceProvider.GetService(typeof(ITransactionFeeService));
var walletService = (IWalletService)_serviceProvider.GetService(typeof(IWalletService));
var appUserService = (IAppUserService)_serviceProvider.GetService(typeof(IAppUserService));
var voucherCampaignService = (IVoucherCampaignService)_serviceProvider.GetService(typeof(IVoucherCampaignService));
var customerService = (ICustomerService)_serviceProvider.GetService(typeof(ICustomerService));
var walks = await Repository.FindAsync(c => c.Status == WalkStatus.Completed && c.Deleted == false && c.PaymentStatus == PaymentStatus.Authorized && c.Completed< compareDate).ConfigureAwait(false);
foreach (var walk in walks)
{
walk.Status = WalkStatus.Confirmed;
walk.Confirmed = DateTimeOffset.UtcNow;
walk.UpdatedAt = DateTimeOffset.UtcNow;
walk.PaymentStatus = PaymentStatus.Paid;
await CommitAsync("System");
decimal feeDecimal = 0;
long feeFromUser = 0;
long feeFromVoucher = 0;
var gehGassiFee = await gehGassiTransactionService.GetMinGehGassiFeeAsync(walk.Price);
if (gehGassiFee != null)
{
if (gehGassiFee.Percent > 0)
{
var percent = walk.Price * gehGassiFee.Percent / 100;
feeDecimal = percent;
}
if (gehGassiFee.Fixed > 0)
{
feeDecimal += gehGassiFee.Fixed;
}
}
if (feeDecimal > 0)
{
feeFromUser = (long)(feeDecimal * 100);
}
//Wenn es einen gutschein gibt, dann Geld vom Transferkontos des Gutscheins auf das transferkonto des Hundebesitzers überweisen
if (!string.IsNullOrWhiteSpace(walk.VoucherId) && walk.VoucherAmmount > 0)
{
var voucherUsed = await voucherCampaignService.GetUsageAsync(walk.VoucherId, walk.DogOwnerId, walk.Id);
if (voucherUsed != null)
{
var ammountFromVoucher = (long)(voucherUsed.VoucherAmmountUsed * 100);
voucherUsed.UsageStatus = VoucherUsageStatus.Used;
await CommitAsync("System");
var voucher = await voucherCampaignService.GetVoucherAsync(voucherUsed.VoucherId);
if (voucher != null)
{
var campaign = await voucherCampaignService.GetAsync(voucher.VoucherCampaignId);
if (campaign != null)
{
var customer = await customerService.GetAsync(campaign.CustomerId);
if (customer != null)
{
var dogOwner = await appUserService.GetAsync(walk.DogOwnerId);
if (dogOwner != null)
{
var wallet = await walletService.GetAsync(walk.DogOwnerId, WalletType.Fees);
if (wallet != null)
{
var transferResult = await mangoPayService.TransferMoneyAsync(customer.MangopayPaymentId, campaign.WalletFeeId, dogOwner.PaymentId, wallet.Id, walk.Id, ammountFromVoucher, 0, $"VoucherId_{voucher.Id}_WalkId_{walk.Id}", targetAppUserId: dogOwner.Id);
}
}
}
}
}
}
}
var sourceWallet = await walletService.GetAsync(walk.DogOwnerId, WalletType.Fees);
var targetWallet = await walletService.GetAsync(walk.DogWalkerId, WalletType.Credits);
if (sourceWallet != null && targetWallet != null)
{
var sourceUser = await appUserService.GetAsync(walk.DogOwnerId);
var targetUser = await appUserService.GetAsync(walk.DogWalkerId);
if (sourceUser != null && targetUser != null)
{
var ammount = (long)(walk.Price * 100);
var transcationSuccess = await mangoPayService.TransferMoneyAsync(walk.DogOwnerId, walk.DogWalkerId, walk.Id, ammount, feeFromUser, $"WalkId_Payment_{walk.Id}");
//Wallets aktualisieren
await mangoPayService.GetWalletBalanceAsync(sourceUser.Id, WalletType.Credits);
await mangoPayService.GetWalletBalanceAsync(sourceUser.Id, WalletType.Fees);
await mangoPayService.GetWalletBalanceAsync(targetUser.Id, WalletType.Credits);
await mangoPayService.GetWalletBalanceAsync(targetUser.Id, WalletType.Fees);
}
}
}
return walks.Count();
}
/// <summary>
/// Bestätigen von Walks die im Status Abgeschlossen sind und länger als X Minuten alt sind.
/// Löst die Überweisung von den Kosten für den Walk vom hundebesitzer an den Dogwalker aus.
/// </summary>
/// <param name="date">Datum für Prüfung</param>
/// <param name="minutes">Ablaufzeit in Minuten</param>
/// <returns>Anzahl bestätigt</returns>
public async Task<int> ConfirmCompletedReminderAsync(DateTimeOffset date, int minutes)
{
var pushNotificationService = (IPushNotificationService)_serviceProvider.GetService(typeof(IPushNotificationService));
var compareDateLower = date.AddMinutes(-minutes - 1);
var compareDateUpper = date.AddMinutes(-minutes);
var walks = await Repository.FindAsync(c => c.Status == WalkStatus.Completed && c.Deleted == false && c.PaymentStatus == PaymentStatus.Authorized && c.Completed >= compareDateLower && c.Completed <= compareDateUpper).ConfigureAwait(false);
foreach (var walk in walks)
{
await pushNotificationService.SendWalkCompletedReminderAsync(walk.DogOwnerId, walk.Id);
}
return walks.Count();
}
/// <summary>
/// Storniert Liste von Walks zurück, welche für 2 App-User in Beziehung stehen wenn eine Blockierung stattgefunden hat.
/// Wird bidirektional gesucht, da nicht immer sichergestellt ist, dass einer Walker und einer Owner ist.
/// Es werden nur Walks im Status Requested oder Accepted berücksichtigt
/// </summary>
/// <param name="blockingAppUserId">Id des App-Users der blockiert hat</param>
/// <param name="blockedAppUserId">Id des App-Users der blockiert wurde</param>
/// <returns>Anzahl stornierte Walks</returns>
public async Task<int> CancelWalksForBlockAsync(string blockingAppUserId, string blockedAppUserId)
{
var mangoPayService = (IMangoPayService)_serviceProvider.GetService(typeof(IMangoPayService));
var systemMessageService = (ISystemMessageService)_serviceProvider.GetService(typeof(ISystemMessageService));
var pushNotificationService = (IPushNotificationService)_serviceProvider.GetService(typeof(IPushNotificationService));
var walks = await Repository.FindAsync(c => c.Status <= WalkStatus.Accepted && c.Deleted == false && ((c.DogOwnerId == blockingAppUserId && c.DogWalkerId == blockedAppUserId) || (c.DogOwnerId == blockedAppUserId && c.DogWalkerId == blockingAppUserId))).ConfigureAwait(false);
foreach (var walk in walks)
{
walk.Status = WalkStatus.Cancelled;
walk.UpdatedAt = DateTimeOffset.UtcNow;
walk.CancelledBy = CancellationSource.GehGassi;
walk.CancelledReason = "User blocked";
//Jetzt noch wegen Zahlungsstatus prüfen
if (walk.PaymentStatus == PaymentStatus.Paid || walk.PaymentStatus == PaymentStatus.Authorized)
{
//Zurücküberweisen der Summe vom Transaktionskonto auf das Guthabenkonto
if (walk.Price > 0)
{
var ammount = (long)(walk.Price * 100);
var tag = $"Walk_Cancel_{walk.Id}";
var refundResult = await mangoPayService.TransferMoneyFromFeeToCreditAccountAsync(walk.DogOwnerId, ammount, tag);
if (refundResult.Success)
walk.PaymentStatus = PaymentStatus.Refunded;
else
walk.PaymentStatus = PaymentStatus.Voided;
}
else
walk.PaymentStatus = PaymentStatus.Voided;
await CommitAsync("System");
//Wallets aktualisieren
await mangoPayService.GetWalletBalanceAsync(walk.DogOwnerId, WalletType.Credits);
await mangoPayService.GetWalletBalanceAsync(walk.DogOwnerId, WalletType.Fees);
systemMessageService.Add(walk.DogOwnerId, AppUserType.DogOwner, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkCancelled, DateTimeOffset.UtcNow.AddDays(14));
systemMessageService.Add(walk.DogWalkerId, AppUserType.DogWalker, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkCancelled, DateTimeOffset.UtcNow.AddDays(14));
await systemMessageService.CommitAsync("System");
await pushNotificationService.SendWalkCancelledAsync(walk.DogOwnerId, walk.Id, AppMode.DogOwner);
await pushNotificationService.SendWalkCancelledAsync(walk.DogWalkerId, walk.Id, AppMode.DogWalker);
}
}
return walks.Count();
}
/// <summary>
/// Stornieren aller Walks eines Benutzers der gesperrt wurde.
/// Wird bidirektional gesucht, da nicht immer sichergestellt ist, dass einer Walker und einer Owner ist.
/// Es werden nur Walks im Status Requested oder Accepted berücksichtigt
/// </summary>
/// <param name="appUserId">Id des App-Users</param>
/// <returns>Anzahl stornierte Walks</returns>
public async Task<int> CancelWalksForLockedAsync(string appUserId)
{
var mangoPayService = (IMangoPayService)_serviceProvider.GetService(typeof(IMangoPayService));
var systemMessageService = (ISystemMessageService)_serviceProvider.GetService(typeof(ISystemMessageService));
var pushNotificationService = (IPushNotificationService)_serviceProvider.GetService(typeof(IPushNotificationService));
var walks = await Repository.FindAsync(c => c.Status <= WalkStatus.Accepted && c.Deleted == false && (c.DogOwnerId == appUserId || c.DogWalkerId == appUserId)).ConfigureAwait(false);
foreach (var walk in walks)
{
walk.Status = WalkStatus.Cancelled;
walk.UpdatedAt = DateTimeOffset.UtcNow;
walk.CancelledBy = CancellationSource.GehGassi;
walk.CancelledReason = "User locked";
//Jetzt noch wegen Zahlungsstatus prüfen
if (walk.PaymentStatus == PaymentStatus.Paid || walk.PaymentStatus == PaymentStatus.Authorized)
{
//Zurücküberweisen der Summe vom Transaktionskonto auf das Guthabenkonto
if (walk.Price > 0)
{
var ammount = (long)(walk.Price * 100);
var tag = $"Walk_Cancel_{walk.Id}";
var refundResult = await mangoPayService.TransferMoneyFromFeeToCreditAccountAsync(walk.DogOwnerId, ammount, tag);
if (refundResult.Success)
walk.PaymentStatus = PaymentStatus.Refunded;
else
walk.PaymentStatus = PaymentStatus.Voided;
}
else
walk.PaymentStatus = PaymentStatus.Voided;
await CommitAsync("System");
//Wallets aktualisieren
await mangoPayService.GetWalletBalanceAsync(walk.DogOwnerId, WalletType.Credits);
await mangoPayService.GetWalletBalanceAsync(walk.DogOwnerId, WalletType.Fees);
//Es werden nur Nachrichten an den nicht gesperrten Benutzer gesendet
if(walk.DogOwnerId != appUserId)
systemMessageService.Add(walk.DogOwnerId, AppUserType.DogOwner, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkCancelled, DateTimeOffset.UtcNow.AddDays(14));
else
systemMessageService.Add(walk.DogWalkerId, AppUserType.DogWalker, walk.Id, SystemMessageTables.Walk, SystemMessageType.WalkCancelled, DateTimeOffset.UtcNow.AddDays(14));
await systemMessageService.CommitAsync("System");
if (walk.DogOwnerId != appUserId)
await pushNotificationService.SendWalkCancelledAsync(walk.DogOwnerId, walk.Id, AppMode.DogOwner);
else
await pushNotificationService.SendWalkCancelledAsync(walk.DogWalkerId, walk.Id, AppMode.DogWalker);
}
}
return walks.Count();
}
/// <summary>
/// Fügt die Verwendung eines Gutscheins zu einem Walk hinzu
/// </summary>
/// <param name="walkId">Id des Walks</param>
/// <param name="voucherId">Id des Gutscheins</param>
/// <param name="ammount">Verwendeter Gutscheinwert</param>
/// <returns>true wenn erfolgreich, false sonst</returns>
public async Task<bool> AddVoucherToWalk(string walkId, string voucherId, decimal ammount)
{
var walk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId && c.Deleted == false).ConfigureAwait(false);
if (walk != null)
{
walk.VoucherId = voucherId;
walk.VoucherAmmount = ammount;
return true;
}
return false;
}
/// <summary>
/// Entfernt die Verwendung eines Gutscheins zu einem Walk
/// </summary>
/// <param name="walkId">Id des Walks</param>
/// <param name="voucherId">Id des Gutscheins</param>
/// <returns>true wenn erfolgreich, false sonst</returns>
public async Task<bool> RemoveVoucherFromWalk(string walkId, string voucherId)
{
var walk = await Repository.FirstOrDefaultAsync(c => c.Id == walkId && c.Deleted == false).ConfigureAwait(false);
if (walk != null && walk.VoucherId == voucherId)
{
walk.VoucherId = null;
walk.VoucherAmmount = 0;
return true;
}
return false;
}
/// <summary>
/// Gibt die Anzahl von Walks für einen Dogwalker mit dem Status Bestätigt in einem bestimmten Zeitraum zurück
/// </summary>
/// <param name="dogWalkerId">Id des DogWalkers</param>
/// <param name="startDate">StartDatum</param>
/// <param name="endDate">Enddatum</param>
/// <returns>Anzahl Walks</returns>
public async Task<int> CountConfirmedByTimeRangeAsync(string dogWalkerId, DateTimeOffset startDate, DateTimeOffset endDate)
{
var count = await Repository.CountAsync(c => c.DogWalkerId == dogWalkerId && c.Confirmed >= startDate && c.Confirmed <= endDate && c.Status == WalkStatus.Confirmed && c.Deleted == false).ConfigureAwait(false);
return count;
}
}
}