476 lines
25 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using gehGassi.Core.Interfaces;
using gehGassi.Domain.Advertisements;
using gehGassi.Domain.Common;
using gehGassi.Domain.News;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using NetTopologySuite.Geometries;
using NetTopologySuite;
namespace gehGassi.Core.Services
{
/// <summary>
/// Service für die Verwaltung von Listungen
/// </summary>
public class ListingService : ServiceBase<Listing>, IListingService
{
private readonly IOptions<ListingOptions> _listingOptions;
private readonly IRepository<ListingWithNames> _namesRepository;
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="unitOfWork">Instanz einer IUnitOfWork</param>
/// <param name="listingOptions">Instanz von ListingOptions</param>
public ListingService(IUnitOfWork unitOfWork, IOptions<ListingOptions> listingOptions) : base(unitOfWork)
{
_listingOptions = listingOptions;
_namesRepository = unitOfWork.GetRepository<ListingWithNames>();
}
/// <summary>
/// Gibt eine gefilterte Liste von Listings zurück
/// </summary>
/// <param name="filter">Filterbegriff der in bestimmten Feldern gesucht wird</param>
/// <returns>List betroffener Listings</returns>
public IQueryable<Listing> Filter(string filter)
{
return Repository.Query(c => c.NameLocalized.Contains(filter));
}
/// <summary>
/// Erstellen einer Listung
/// </summary>
/// <returns>Listung</returns>
public Listing Create()
{
var item = new Listing()
{
Id = Guid.NewGuid().ToString("N"),
Created = DateTimeOffset.UtcNow
};
return item;
}
/// <summary>
/// Gibt eine gefilterte Liste von Listings zurück.
/// </summary>
/// <param name="filter">Filterbegriff der in bestimmten Feldern gesucht wird</param>
/// <returns>List betroffener Listings</returns>
public async Task<List<Listing>> SearchAsync(string filter)
{
return (await Repository.FindAsync(c => c.NameLocalized.Contains(filter))).ToList();
}
/// <summary>
/// Gibt eine gefilterte Liste von Listings mit Namen zurück in einer gewählten Sprache
/// </summary>
/// <param name="filter">Filterbegriff der in bestimmten Feldern gesucht wird</param>
/// <param name="language">Gewünschte Sprache</param>
/// <param name="fallback">Fallback Sprache</param>
/// <param name="includeDeleted">Sollen gelöschte inkludiert werden?</param>
/// <param name="excludeDraft">Sollen Entitäten im Draft-Status (Cart) ausgeblendet werden?</param>
/// <returns>Liste betroffener Listings</returns>
public IQueryable<ListingWithNames> FilterWithNames(string filter, string language, string fallback, bool includeDeleted, bool excludeDraft)
{
language = language.ToUpperInvariant();
fallback = fallback.ToUpperInvariant();
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_ListingsWithNames({0},{1})", c => c.Name.Contains(filter) || c.CustomerName.Contains(filter), language, fallback);
if (!includeDeleted)
baseQuery = baseQuery.Where(c => c.Deleted == false);
if (excludeDraft)
baseQuery = baseQuery.Where(c => c.Status != ListingStatus.Draft);
return baseQuery;
}
/// <summary>
/// Gibt eine gefilterte Liste von Listings mit Namen für Kunden zurück in einer gewählten Sprache
/// </summary>
/// <param name="customerId">Id des Kunden</param>
/// <param name="filter">Filterbegriff der in bestimmten Feldern gesucht wird</param>
/// <param name="language">Gewünschte Sprache</param>
/// <param name="fallback">Fallback Sprache</param>
/// <param name="includeDeleted">Sollen gelöschte inkludiert werden?</param>
/// <param name="excludeDraft">Sollen Entitäten im Draft-Status (Cart) ausgeblendet werden?</param>
/// <returns>Liste betroffener Listings</returns>
public IQueryable<ListingWithNames> FilterWithNamesCustomer(long customerId, string filter, string language, string fallback, bool includeDeleted, bool excludeDraft)
{
language = language.ToUpperInvariant();
fallback = fallback.ToUpperInvariant();
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_ListingsWithNames({0},{1})", c => c.CustomerId == customerId && (c.Name.Contains(filter) || c.CustomerName.Contains(filter)), language, fallback);
if (!includeDeleted)
baseQuery = baseQuery.Where(c => c.Deleted == false);
if (excludeDraft)
baseQuery = baseQuery.Where(c => c.Status != ListingStatus.Draft);
return baseQuery;
}
/// <summary>
/// Löschen von Listungen basierend auf einer Branche
/// </summary>
/// <param name="branchId">Id der Branche</param>
/// <param name="forceDelete">Wenn true, dann wirklich löschen, auf gelöscht setzen sonst</param>
/// <returns>Task</returns>
public async Task DeleteByBranchAsync(string branchId, bool forceDelete)
{
var items = await Repository.FindAsync(c => c.BranchId == branchId).ConfigureAwait(false);
foreach (var listing in items)
{
if(!forceDelete)
listing.Deleted = true;
else
Repository.Remove(listing);
}
}
/// <summary>
/// Prüfen der Verfügbarkeit einer Listung für eine Buchung
/// </summary>
/// <param name="customerId">Id des Kunden</param>
/// <param name="branchId">Id der Branche</param>
/// <param name="listingType">Listungs-Typ</param>
/// <param name="startDate">Startdatum</param>
/// <param name="endDate">Enddatum</param>
/// <param name="countryIso">ISO2 des Landes</param>
/// <param name="ignoreId">Id die ignoriert werden soll</param>
/// <returns>ListingValidationResult</returns>
public async Task<ListingValidationResult> IsAvailableAsync(long customerId, string branchId, ListingType listingType, DateTimeOffset startDate, DateTimeOffset endDate, string countryIso, string ignoreId)
{
var result = new ListingValidationResult()
{
Valid = true,
Booked = 0,
Reserved = 0,
ErrorCode = ListingValidationError.None
};
//anpassung --> alle Listungstypen berücksichtigen. Es darf nur eine Listung je Kunde je Land und Branche geben. Der Listungstyp ist dabei egal.
//var customerCount = await Repository.CountAsync(c => c.Id != ignoreId && c.Deleted == false && c.BranchId == branchId && c.ListingType == listingType && c.CustomerId == customerId && c.Address.CountryCode == countryIso && (c.Status == ListingStatus.ToPay || c.Status == ListingStatus.Booked || c.Status == ListingStatus.Running) && ((startDate >= c.StartDate && startDate <= c.EndDate) || (endDate >= c.StartDate && endDate <= c.EndDate)));
var customerCount = await Repository.CountAsync(c => c.Id != ignoreId && c.Deleted == false && c.BranchId == branchId && c.CustomerId == customerId && c.Address.CountryCode == countryIso && (c.Status == ListingStatus.ToPay || c.Status == ListingStatus.Booked || c.Status == ListingStatus.Running) && ((startDate >= c.StartDate && startDate <= c.EndDate) || (endDate >= c.StartDate && endDate <= c.EndDate)));
var bookedCount = await Repository.CountAsync(c => c.Id != ignoreId && c.Deleted == false && c.BranchId == branchId && c.ListingType == listingType && c.CustomerId != customerId && c.Address.CountryCode == countryIso && (c.Status == ListingStatus.ToPay || c.Status == ListingStatus.Booked || c.Status == ListingStatus.Running) && ((startDate >= c.StartDate && startDate <= c.EndDate) || (endDate >= c.StartDate && endDate <= c.EndDate)));
var reservedCount = await Repository.CountAsync(c => c.Id != ignoreId && c.Deleted == false && c.BranchId == branchId && c.ListingType == listingType && c.CustomerId != customerId && c.Address.CountryCode == countryIso && c.ReservedUntil != null);
result.Booked = bookedCount;
result.Reserved = reservedCount;
var total = bookedCount + reservedCount;
if (customerCount > 0)
{
result.ErrorCode = ListingValidationError.AlreadyBooked;
result.Valid = false;
}
else
{
switch (listingType)
{
case ListingType.Base:
if (total >= _listingOptions.Value.Base)
{
result.ErrorCode = ListingValidationError.NoSlotsAvailable;
result.Valid = false;
}
break;
case ListingType.Silver:
if (total >= _listingOptions.Value.Silver)
{
result.ErrorCode = ListingValidationError.NoSlotsAvailable;
result.Valid = false;
}
break;
case ListingType.Gold:
if (total >= _listingOptions.Value.Gold)
{
result.ErrorCode = ListingValidationError.NoSlotsAvailable;
result.Valid = false;
}
break;
case ListingType.Platin:
if (total >= _listingOptions.Value.Platin)
{
result.ErrorCode = ListingValidationError.NoSlotsAvailable;
result.Valid = false;
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(listingType), listingType, null);
}
}
return result;
}
/// <summary>
/// Gibt die Anzahl von Listungen zurück
/// </summary>
/// <param name="includeDeleted">Sollen gelöschte inkludiert werden?</param>
/// <param name="excludeDraft">Sollen Entitäten im Draft-Status (Cart) ausgeblendet werden?</param>
/// <returns>Anzahl Listungen</returns>
public async Task<int> CountAsync(bool includeDeleted, bool excludeDraft)
{
var query = Repository.Query(c => c.Id != "");
if (!includeDeleted)
query = query.Where(c => c.Deleted == false);
if(excludeDraft)
query = query.Where(c => c.Status != ListingStatus.Draft);
return await query.CountAsync().ConfigureAwait(false);
}
/// <summary>
/// Gibt die Anzahl von Listungen in einem Status zurück
/// </summary>
/// <param name="status">Gesuchter Status</param>
/// <param name="includeDeleted">Sollen gelöschte inkludiert werden?</param>
/// <returns>Anzahl Listungen</returns>
public async Task<int> CountAsync(ListingStatus status, bool includeDeleted)
{
var query = Repository.Query(c => c.Status == status);
if (!includeDeleted)
query = query.Where(c => c.Deleted == false);
return await query.CountAsync().ConfigureAwait(false);
}
/// <summary>
/// Prüft ob sich eine Listung bereits im Warenkorb befindet.
/// Listungen mit bestimmten Parametern dürfen nur einmal vorkommen
/// </summary>
/// <param name="cartId">Id des Warenkorb</param>
/// <param name="customerId">Id des Kunden</param>
/// <param name="branchId">Id der Branche</param>
/// <param name="listingType">Listungs-Typ</param>
/// <param name="startDate">Startdatum</param>
/// <param name="endDate">Enddatum</param>
/// <param name="countryIso">ISO2 des Landes</param>
/// <param name="ignoreId">Id die ignoriert werden soll</param>
/// <returns>True wenn die Listung gefunden wurde, false sonst</returns>
public async Task<bool> CheckListingInCartAsync(long cartId, long customerId, string branchId, ListingType listingType, DateTimeOffset startDate, DateTimeOffset endDate, string countryIso, string ignoreId)
{
//anpassung --> alle Listungstypen berücksichtigen. Es darf nur eine Listung je Kunde je Land und Branche geben. Der Listungstyp ist dabei egal.
//var count = await Repository.CountAsync(c => c.Id != ignoreId && c.Deleted == false && c.CartId == cartId && c.BranchId == branchId && c.ListingType == listingType && c.CustomerId == customerId && c.Address.CountryCode == countryIso && c.Status == ListingStatus.Draft && ((startDate >= c.StartDate && startDate <= c.EndDate) || (endDate >= c.StartDate && endDate <= c.EndDate)));
var count = await Repository.CountAsync(c => c.Id != ignoreId && c.Deleted == false && c.CartId == cartId && c.BranchId == branchId && c.CustomerId == customerId && c.Address.CountryCode == countryIso && c.Status == ListingStatus.Draft && ((startDate >= c.StartDate && startDate <= c.EndDate) || (endDate >= c.StartDate && endDate <= c.EndDate)));
return count != 0;
}
/// <summary>
/// Gibt eine Listung für einen Artikel im Warenkorb zurück
/// </summary>
/// <param name="cartItemId">Id des Artikels im Warenkorb</param>
/// <returns>Listing oder null, wenn nicht gefunden</returns>
public async Task<Listing> GetByCartItemAsync(long cartItemId)
{
return await Repository.FirstOrDefaultAsync(c => c.CartItemId == cartItemId && c.Status == ListingStatus.Draft);
}
/// <summary>
/// Gibt eine Listung für einen Artikel in einer Bestellung zurück
/// </summary>
/// <param name="orderItemId">Id des Artikels in der Bestellung</param>
/// <returns>Listing oder null, wenn nicht gefunden</returns>
public async Task<Listing> GetByOrderItemAsync(long orderItemId)
{
return await Repository.FirstOrDefaultAsync(c => c.OrderItemId == orderItemId);
}
/// <summary>
/// Setzen des Status von Listungen nach Bestellung
/// </summary>
/// <param name="orderId">Id der Bestellung</param>
/// <param name="status">Zu setzender Status</param>
/// <param name="reservedDate">Datum das für die Reservierung gesetzt werden soll</param>
/// <returns>Anzahl betroffener Listungen</returns>
public async Task<int> SetStatusByOrderAsync(long orderId, ListingStatus status, DateTimeOffset? reservedDate)
{
var listings = await Repository.FindAsync(c => c.OrderId == orderId);
foreach (var listing in listings)
{
listing.Status = status;
listing.ReservedUntil = reservedDate;
}
return listings.Count();
}
/// <summary>
/// Setzen des Bezahlstatus von Listingen nach Bestellung
/// </summary>
/// <param name="orderId">Id der Bestellung</param>
/// <param name="status">Zu setzender Status</param>
/// <returns>Anzahl betroffener Listungen</returns>
public async Task<int> SetPaymentStatusByOrderAsync(long orderId, PaymentStatus status)
{
var listings = await Repository.FindAsync(c => c.OrderId == orderId);
foreach (var listing in listings)
{
listing.PaymentStatus = status;
}
return listings.Count();
}
/// <summary>
/// Zurücksetzen von Reservierungen wenn eine Zeit erreicht ist
/// </summary>
/// <param name="reservedDate">Obere Grenze der Reservierung</param>
/// <returns>Anzahl betroffener Listungen</returns>
public async Task<int> ResetReservedAsync(DateTimeOffset reservedDate)
{
var listings = await Repository.FindAsync(c => c.Status == ListingStatus.Reserverd && ((c.ReservedUntil.HasValue && c.ReservedUntil < reservedDate) || c.ReservedUntil == null));
foreach (var listing in listings)
{
listing.ReservedUntil = null;
listing.Status = ListingStatus.Draft;
}
return listings.Count();
}
/// <summary>
/// Gibt eine Liste von Listungen zurück für die Anzeige der Partner in der Nähe
/// </summary>
/// <param name="country">Land</param>
/// <param name="state">Bundesland</param>
/// <param name="lat">Optional: Breitengrad</param>
/// <param name="lng">Optional: Längengrad</param>
/// <param name="language">Gewünschte Sprache</param>
/// <param name="fallback">Fallback Sprache</param>
/// <param name="skip">Datensätze auslassen</param>
/// <param name="take">Datensätze nehmen</param>
/// <returns>Liste passender Listungen und gesamt verfügbar</returns>
public async Task<(int total, List<ListingWithNames> list)> GetNearForAppAsync(string country, string state, double lat, double lng, string language, string fallback, int skip, int take)
{
language = language.ToUpperInvariant();
fallback = fallback.ToUpperInvariant();
if (!string.IsNullOrWhiteSpace(country))
country = country.ToUpperInvariant();
if (!string.IsNullOrWhiteSpace(state))
state = state.ToUpperInvariant();
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var location = geometryFactory.CreatePoint(new Coordinate(lng, lat));
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_ListingsWithNames({0},{1})", c => c.Deleted == false && c.Status == ListingStatus.Running && c.BranchOnlineStatus == OnlineStatus.Online &&
(c.GeoMode == GeoMode.None ||
(c.GeoMode == GeoMode.Country && c.Address_CountryCode == country) ||
(c.GeoMode == GeoMode.CountryAndState && c.Address_CountryCode == country && c.Address_State == state) ||
(c.GeoMode == GeoMode.Location && c.Location.IsWithinDistance(location, c.Radius * 1000))), language, fallback);
var total = await baseQuery.CountAsync().ConfigureAwait(false);
//TODO: noch überlegen wie wir eine Lokalisierung machen können
baseQuery = baseQuery.OrderBy(c => c.ListingLocation.Distance(location));
baseQuery = baseQuery.Skip(skip).Take(take);
var list = await baseQuery.ToListAsync().ConfigureAwait(false);
return (total, list);
}
/// <summary>
/// Gibt eine Liste von Listungen zurück für eine Branche zurück.
/// Die Sortierung erfolgt nach Listungstyp und dann nach Distanz
/// </summary>
/// <param name="branchId">Id der Branche</param>
/// <param name="filter">Optional: Filter für Name und/oder Ort</param>
/// <param name="listingCountry">Optional: Listings-Land</param>
/// <param name="country">Land</param>
/// <param name="state">Bundesland</param>
/// <param name="lat">Optional: Breitengrad</param>
/// <param name="lng">Optional: Längengrad</param>
/// <param name="language">Gewünschte Sprache</param>
/// <param name="fallback">Fallback Sprache</param>
/// <param name="skip">Datensätze auslassen</param>
/// <param name="take">Datensätze nehmen</param>
/// <returns>Liste passender Listungen</returns>
public async Task<(int total, List<ListingWithNames> list)> GetByBranchForAppAsync(string branchId, string filter, string listingCountry, string country, string state, double lat, double lng, string language, string fallback, int skip, int take)
{
language = language.ToUpperInvariant();
fallback = fallback.ToUpperInvariant();
if (!string.IsNullOrWhiteSpace(country))
country = country.ToUpperInvariant();
if (!string.IsNullOrWhiteSpace(state))
state = state.ToUpperInvariant();
if (!string.IsNullOrWhiteSpace(listingCountry))
listingCountry = listingCountry.ToUpperInvariant();
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var location = geometryFactory.CreatePoint(new Coordinate(lng, lat));
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_ListingsWithNames({0},{1})", c => c.BranchId == branchId && c.Deleted == false && c.Status == ListingStatus.Running && c.BranchOnlineStatus == OnlineStatus.Online &&
(c.GeoMode == GeoMode.None ||
(c.GeoMode == GeoMode.Country && c.Address_CountryCode == country) ||
(c.GeoMode == GeoMode.CountryAndState && c.Address_CountryCode == country && c.Address_State == state) ||
(c.GeoMode == GeoMode.Location && c.Location.IsWithinDistance(location, c.Radius * 1000))), language, fallback);
if (!string.IsNullOrWhiteSpace(filter))
{
baseQuery = baseQuery.Where(c => c.Name.Contains(filter) || c.ListingAddress_City.Contains(filter));
}
if (!string.IsNullOrWhiteSpace(listingCountry))
{
baseQuery = baseQuery.Where(c => c.ListingAddress_CountryCode == listingCountry);
}
var total = await baseQuery.CountAsync().ConfigureAwait(false);
var list = await baseQuery.OrderByDescending(c => c.ListingType).ThenBy(c => c.ListingLocation.Distance(location)).Skip(skip).Take(take).ToListAsync().ConfigureAwait(false);
return (total, list);
}
/// <summary>
/// Gibt eine Listung in einer gewünschten Sprache zurück
/// </summary>
/// <param name="id">Id der Listung</param>
/// <param name="language">Gewünschte Sprache</param>
/// <param name="fallback">Fallback Sprache</param>
/// <returns>Gefundene Werbung, null sonst</returns>
public async Task<ListingWithNames> GetRunningWithNamesAsync(string id, string language, string fallback)
{
language = language.ToUpperInvariant();
fallback = fallback.ToUpperInvariant();
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_ListingsWithNames({0},{1})", c => c.Id == id && c.Deleted == false && c.Status == ListingStatus.Running && c.EndDate > DateTimeOffset.UtcNow, language, fallback);
var result = await baseQuery.FirstOrDefaultAsync().ConfigureAwait(false);
return result;
}
/// <summary>
/// Starten von gebuchten Listungen wenn diese das Start-Datum erreicht haben
/// </summary>
/// <returns>Anzahl gestartete Listungen</returns>
public async Task<int> StartBookedAsnyc()
{
var listings = await Repository.FindAsync(c => c.Status == ListingStatus.Booked && c.Deleted == false && c.StartDate <= DateTimeOffset.UtcNow && c.EndDate.Value >= DateTimeOffset.UtcNow);
foreach (var listing in listings)
{
listing.Status = ListingStatus.Running;
}
return listings.Count();
}
/// <summary>
/// Stoppen von abgelaufenen Listungen wenn diese das Enddatum erreicht haben
/// </summary>
/// <returns>Anzahl gestoppte Listungen</returns>
public async Task<int> StopRunningAsync()
{
var listings = await Repository.FindAsync(c => c.Status == ListingStatus.Running && c.Deleted == false && c.EndDate.Value <= DateTimeOffset.UtcNow);
foreach (var listing in listings)
{
listing.Status = ListingStatus.Ended;
}
return listings.Count();
}
}
}