using gehGassi.Core.Interfaces;
using gehGassi.Domain.Vouchers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using gehGassi.Domain.Common;
using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography;
using NetTopologySuite.Algorithm;
using static Org.BouncyCastle.Asn1.Cmp.Challenge;
namespace gehGassi.Core.Services
{
///
/// Service der die Verwaltung von Gutscheinkampagnen übernimmt
///
public class VoucherCampaignService : ServiceBase, IVoucherCampaignService
{
private readonly IRepository _namesRepository;
private readonly IRepository _vouchersRepository;
private readonly IRepository _voucherUsagesRepository;
///
/// Erstellt eine Instanz
///
/// Instanz eines IUnitOfWork
public VoucherCampaignService(IUnitOfWork unitOfWork) : base(unitOfWork)
{
_namesRepository = unitOfWork.GetRepository();
_vouchersRepository = unitOfWork.GetRepository();
_voucherUsagesRepository = unitOfWork.GetRepository();
}
///
/// Gibt eine gefilterte Liste von Gutschein-Kampagnen zurück
///
/// Filterbegriff der in bestimmten Feldern gesucht wird
/// List betroffener Gutschein-Kampagnen
public IQueryable Filter(string filter)
{
return Repository.Query(c => c.Title.Contains(filter) || c.NameLocalized.Contains(filter));
}
///
/// Erstellen einer Gutschein-Kampagne
///
/// Gutschein-Kampagne
public VoucherCampaign Create()
{
var item = new VoucherCampaign()
{
Id = Guid.NewGuid().ToString("N"),
Created = DateTimeOffset.UtcNow
};
return item;
}
///
/// Gibt eine gefilterte Liste von Gutschein-Kampagnen zurück.
///
/// Filterbegriff der in bestimmten Feldern gesucht wird
/// List betroffener Gutschein-Kampagnen
public async Task> SearchAsync(string filter)
{
return (await Repository.FindAsync(c => c.Title.Contains(filter) || c.NameLocalized.Contains(filter))).ToList();
}
///
/// Gibt eine gefilterte Liste von Gutschein-Kampagnen mit Namen zurück in einer gewählten Sprache
///
/// Filterbegriff der in bestimmten Feldern gesucht wird
/// Gewünschte Sprache
/// Fallback Sprache
/// Sollen gelöschte inkludiert werden?
/// Sollen Entitäten im Draft-Status (Cart) ausgeblendet werden?
/// Liste betroffener Gutschein-Kampagnen
public IQueryable FilterWithNames(string filter, string language, string fallback, bool includeDeleted, bool excludeDraft)
{
language = language.ToUpperInvariant();
fallback = fallback.ToUpperInvariant();
var baseQuery = _namesRepository.QueryStoredProcedure("SELECT * FROM tv_VoucherCampignsWithNames({0},{1})", c => c.Title.Contains(filter) || 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 != VoucherCampaignStatus.Draft);
return baseQuery;
}
///
/// Gibt eine gefilterte Liste von Gutschein-Kampagnen mit Namen für Kunden zurück in einer gewählten Sprache
///
/// Id des Kunden
/// Filterbegriff der in bestimmten Feldern gesucht wird
/// Gewünschte Sprache
/// Fallback Sprache
/// Sollen gelöschte inkludiert werden?
/// Sollen Entitäten im Draft-Status (Cart) ausgeblendet werden?
/// Liste betroffener Gutschein-Kampagnen
public IQueryable 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_VoucherCampignsWithNames({0},{1})", c => c.CustomerId == customerId && (c.Title.Contains(filter) || 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 != VoucherCampaignStatus.Draft);
return baseQuery;
}
///
/// Gibt die Anzahl von Gutschein-Kampagnen zurück
///
/// Sollen gelöschte inkludiert werden?
/// Sollen Entitäten im Draft-Status (Cart) ausgeblendet werden?
/// Anzahl Gutschein-Kampagnen
public async Task 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 != VoucherCampaignStatus.Draft);
return await query.CountAsync().ConfigureAwait(false);
}
///
/// Gibt die Anzahl von Gutschein-Kampagnen in einem Status zurück
///
/// Gesuchter Status
/// Sollen gelöschte inkludiert werden?
/// Anzahl Gutschein-Kampagnen
public async Task CountAsync(VoucherCampaignStatus 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);
}
///
/// Gibt eine Gutschein-Kampagne für einen Artikel im Warenkorb zurück
///
/// Id des Artikels im Warenkorb
/// Gutschein-Kampagne oder null, wenn nicht gefunden
public async Task GetByCartItemAsync(long cartItemId)
{
return await Repository.FirstOrDefaultAsync(c => c.CartItemId == cartItemId && c.Status == VoucherCampaignStatus.Draft);
}
///
/// Gibt einen Gutschein-Kampagne für einen Artikel in einer Bestellung zurück
///
/// Id des Artikels in der Bestellung
/// Gutschein-Kampagne oder null, wenn nicht gefunden
public async Task GetByOrderItemAsync(long orderItemId)
{
return await Repository.FirstOrDefaultAsync(c => c.OrderItemId == orderItemId);
}
///
/// Setzen des Status von Gutschein-Kampagnen nach Bestellung
///
/// Id der Bestellung
/// Zu setzender Status
/// Anzahl betroffener Gutschein-Kampagnen
public async Task SetStatusByOrderAsync(long orderId, VoucherCampaignStatus status)
{
var items = await Repository.FindAsync(c => c.OrderId == orderId);
foreach (var item in items)
{
item.Status = status;
}
return items.Count();
}
///
/// Setzen des Bezahlstatus von Gutschein-Kampagnen nach Bestellung
///
/// Id der Bestellung
/// Zu setzender Status
/// Anzahl betroffener Gutschein-Kampagnen
public async Task SetPaymentStatusByOrderAsync(long orderId, PaymentStatus status)
{
var items = await Repository.FindAsync(c => c.OrderId == orderId);
foreach (var item in items)
{
item.PaymentStatus = status;
}
return items.Count();
}
///
/// Starten von gebuchten Gutschein-Kampagnen wenn diese das Start-Datum erreicht haben
///
/// Anzahl gestartete Gutschein-Kampagnen
public async Task StartBookedAsnyc()
{
var items = await Repository.FindAsync(c => c.Status == VoucherCampaignStatus.Booked && c.Deleted == false && c.StartDate <= DateTimeOffset.UtcNow);
foreach (var item in items)
{
item.Status = VoucherCampaignStatus.Running;
}
return items.Count();
}
///
/// Stoppen von abgelaufenen Gutschein-Kampagnen wenn diese das Enddatum erreicht haben
///
/// Anzahl gestoppte Gutschein-Kampagnen
public async Task StopRunningAsync()
{
var items = await Repository.FindAsync(c => (c.Status == VoucherCampaignStatus.Running && c.Deleted == false && c.BudgetUsed >= c.Budget) || (c.Status == VoucherCampaignStatus.Running && c.Deleted == false && c.EndDate.Value <= DateTimeOffset.UtcNow));
foreach (var item in items)
{
item.EndDate = DateTimeOffset.UtcNow;
item.Status = VoucherCampaignStatus.Ended;
}
return items.Count();
}
///
/// Erstellen von Gutscheinen für eine Gutschein-Kampagne
///
/// Id der Kampagne
/// Der Benutzer der die Gutscheine angelegt hat
/// Anzahl der erstellten Gutscheine
public async Task CreateVouchersForCampaign(string voucherCampaignId, string userName)
{
var campaign = await Repository.FirstOrDefaultAsync(c => c.Id == voucherCampaignId);
if (campaign != null)
{
int sufficientBufferSizeInBytes = (8 * 6 + 7) / 8;
for (var i = 0; i < campaign.NumberOfVouchers; i++)
{
var buffer = new byte[sufficientBufferSizeInBytes];
buffer = RandomNumberGenerator.GetBytes(8);
string code = Convert.ToBase64String(buffer).Substring(0, 8);
var voucher = new Voucher()
{
Id = Guid.NewGuid().ToString("N"),
VoucherCampaignId = campaign.Id,
Code = $"{campaign.Prefix}-" + GenerateManualCode(8),
QrCode = Guid.NewGuid().ToString("N").ToUpperInvariant(),
UsedCount = 0,
VoucherAmmount = campaign.VoucherAmmount,
IsValid = true
};
_vouchersRepository.Add(voucher);
if (i % 100 == 0)
{
await CommitAsync(userName, noAudit: true);
}
}
await CommitAsync(userName, noAudit: true);
return campaign.NumberOfVouchers;
}
return 0;
}
///
/// Prüft ob ein Präfix für eine Gutschein-Kampagne verfügbar ist
///
/// Präfix der geprüft werden soll
/// Id der Kampagne die ignoriert werden soll
/// true wenn verfügbar, false sonst
public async Task IsPrefixAvailableAsync(string prefix, string voucherCampaignId)
{
var itemFound = await Repository.FirstOrDefaultAsync(c => c.Id != voucherCampaignId && c.Prefix == prefix).ConfigureAwait(false);
return itemFound == null;
}
///
/// Aktualisiert den Gutschein-Betrag für alle noch nicht eingelösten oder deaktivierten Gutscheine einer Kampagne
///
/// Id der Kampagne
/// Wert der gesetzt werden soll
/// Anzahl Gutscheine die betroffen sind
public async Task UpdateVoucherAmmountAsync(string voucherCampaignId, decimal voucherAmmount)
{
var vouchers = await _vouchersRepository.FindAsync(c => c.VoucherCampaignId == voucherCampaignId && c.UsedCount == 0 && c.IsValid == true);
foreach (var voucher in vouchers)
{
voucher.VoucherAmmount = voucherAmmount;
}
return vouchers.Count();
}
///
/// Löschen der Gutscheine und deren Verwendung für eine Kampagne
///
/// Id der Kampagne
/// Anzahl gelöscht
public async Task RemoveVouchersAsync(string voucherCampaignId)
{
var vouchers = await _vouchersRepository.FindAsync(c => c.VoucherCampaignId == voucherCampaignId);
foreach (var voucher in vouchers)
{
var usages = await _voucherUsagesRepository.FindAsync(c => c.VoucherId == voucher.Id);
foreach (var usage in usages)
{
_voucherUsagesRepository.Remove(usage);
}
_vouchersRepository.Remove(voucher);
}
return vouchers.Count();
}
///
/// Gibt eine Liste von Gutscheinen für eine Kampagne zurück
///
/// Id der Kampagne
/// Liste Gutscheine
public async Task> GetVouchersForCampaignAsync(string voucherCampaignId)
{
var vouchers = await _vouchersRepository.FindAsync(c => c.VoucherCampaignId == voucherCampaignId).ConfigureAwait(false);
return vouchers.ToList();
}
///
/// Prüft ob ein manueller Gutscheincode für einen Benutzer gültig ist und eingesetzt werden kann
///
/// Code
/// Id des AppUsers
/// Validierungs-Ergebnis
public async Task ValidateVoucherCodeAsync(string code, string appUserId)
{
var voucher = await _vouchersRepository.FirstOrDefaultAsync(c => c.Code == code).ConfigureAwait(false);
if (voucher != null)
{
var voucherCampaign = await Repository.FirstOrDefaultAsync(c => c.Id == voucher.VoucherCampaignId).ConfigureAwait(false);
if (voucherCampaign != null)
{
//Läuft die Kampange noch nicht?
if(voucherCampaign.Status < VoucherCampaignStatus.Running)
return VoucherValidationResult.CampaignNotStarted;
//Ist die Kampagne schon beendet?
if (voucherCampaign.Status > VoucherCampaignStatus.Running)
return VoucherValidationResult.CampaignEnded;
//Wurde das Budget der Kampagne schon aufgebraucht?
if (voucherCampaign.BudgetUsed >= voucherCampaign.Budget)
return VoucherValidationResult.CampaignBudgetUsedUp;
//Ist der Gutschein noch gültig?
if(voucher.IsValid == false)
return VoucherValidationResult.VoucherAlreadyUsed;
//Wurde der Gutschein schon zu oft verwendet?
var usedByUserCount = await _voucherUsagesRepository.CountAsync(c => c.VoucherId == voucher.Id && c.AppUserId == appUserId).ConfigureAwait(false);
if(usedByUserCount >= voucherCampaign.MaxUsagePerUser)
return VoucherValidationResult.MaxUsageReached;
return VoucherValidationResult.Valid;
}
}
return VoucherValidationResult.Invalid;
}
///
/// Gibt einen Gutschein anhand des Codes zurück
///
/// Code des Gutscheins
/// Gutschein oder null, wenn nicht gefunden
public async Task GetVoucherByCodeAsync(string code)
{
var voucher = await _vouchersRepository.FirstOrDefaultAsync(c => c.Code == code).ConfigureAwait(false);
return voucher;
}
///
/// Gibt einen Gutschein anhand des QR-Codes zurück
///
/// QR-Code des Gutscheins
/// Gutschein oder null, wenn nicht gefunden
public async Task GetVoucherByQrCodeAsync(string qrCode)
{
var voucher = await _vouchersRepository.FirstOrDefaultAsync(c => c.QrCode == qrCode).ConfigureAwait(false);
return voucher;
}
///
/// Reserviert einen Gutschein für einen Benutzer und einen Walk
///
/// Id des Vouchers
/// Id des AppUsers
/// Id des Walks
/// Wert des Gutscheins
/// Wert der verbraucht wurde. Kann kleiner Gutscheinwert sein
/// VoucherUsage
public async Task ReserveVoucherAsync(string voucherId, string appUserId, string walkId, decimal voucherAmmount, decimal voucherAmmountUsed)
{
var voucherUsage = new VoucherUsage()
{
VoucherId = voucherId,
AppUserId = appUserId,
WalkId = walkId,
VoucherAmmount = voucherAmmount,
VoucherAmmountUsed = voucherAmmountUsed,
UsageStatus = VoucherUsageStatus.Reserved,
Created = DateTimeOffset.UtcNow
};
_voucherUsagesRepository.Add(voucherUsage);
return voucherUsage;
}
///
/// Löschen der Verwendung eines Gutscheins
///
/// VoucherUsage
public void RemoveUsage(VoucherUsage voucherUsage)
{
_voucherUsagesRepository.Remove(voucherUsage);
}
///
/// Gibt die Verwendung eines Gutscheins zurück
///
/// Id des Gutscheins
/// Id des AppUsers
/// Id des Walks
/// VoucherUsage oder null, wenn nicht gefunden
public async Task GetUsageAsync(string voucherId, string appUserId, string walkId)
{
var voucherUsage = await _voucherUsagesRepository.FirstOrDefaultAsync(c => c.VoucherId == voucherId && c.AppUserId == appUserId && c.WalkId == walkId).ConfigureAwait(false);
return voucherUsage;
}
///
/// Gibt einen Gutschein zurück
///
/// Id des Gutscheins
/// Voucher oder null, wenn nicht gefunden
public async Task GetVoucherAsync(string voucherId)
{
var voucher = await _vouchersRepository.FirstOrDefaultAsync(c => c.Id == voucherId).ConfigureAwait(false);
return voucher;
}
///
/// Löschen von Voucher-Usages für einen AppUser
///
/// Id des AppUsers
/// Anzahl gelöscht
public async Task RemoveVoucherUsageForAppUserAsync(string appUserId)
{
var usages = await _voucherUsagesRepository.FindAsync(c => c.AppUserId == appUserId).ConfigureAwait(false);
foreach (var voucherUsage in usages)
{
_voucherUsagesRepository.Remove(voucherUsage);
}
return usages.Count();
}
#region Helper
///
/// Erstellt einen zufälligen Code in einer bestimmten Länge
///
/// Länge des Codes
/// Code
private string GenerateManualCode(int length)
{
var sufficientBufferSizeInBytes = (length * 6 + 7) / 8;
var buffer = RandomNumberGenerator.GetBytes(sufficientBufferSizeInBytes);
var code = Convert.ToBase64String(buffer)[..length];
code = code.ToUpperInvariant();
var finalCode = $"{code.Substring(0,length / 2)}-{code.Substring(length/2, length/2)}";
if (finalCode.Contains("+") || finalCode.Contains("/") || finalCode.Contains("="))
{
return GenerateManualCode(length);
}
return finalCode;
}
#endregion
}
}