gehgassi_backend/gehGassi.Core/Services/VoucherCampaignService.cs

499 lines
22 KiB
C#

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
{
/// <summary>
/// Service der die Verwaltung von Gutscheinkampagnen übernimmt
/// </summary>
public class VoucherCampaignService : ServiceBase<VoucherCampaign>, IVoucherCampaignService
{
private readonly IRepository<VoucherCampaignWithNames> _namesRepository;
private readonly IRepository<Voucher> _vouchersRepository;
private readonly IRepository<VoucherUsage> _voucherUsagesRepository;
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="unitOfWork">Instanz eines IUnitOfWork</param>
public VoucherCampaignService(IUnitOfWork unitOfWork) : base(unitOfWork)
{
_namesRepository = unitOfWork.GetRepository<VoucherCampaignWithNames>();
_vouchersRepository = unitOfWork.GetRepository<Voucher>();
_voucherUsagesRepository = unitOfWork.GetRepository<VoucherUsage>();
}
/// <summary>
/// Gibt eine gefilterte Liste von Gutschein-Kampagnen zurück
/// </summary>
/// <param name="filter">Filterbegriff der in bestimmten Feldern gesucht wird</param>
/// <returns>List betroffener Gutschein-Kampagnen</returns>
public IQueryable<VoucherCampaign> Filter(string filter)
{
return Repository.Query(c => c.Title.Contains(filter) || c.NameLocalized.Contains(filter));
}
/// <summary>
/// Erstellen einer Gutschein-Kampagne
/// </summary>
/// <returns>Gutschein-Kampagne</returns>
public VoucherCampaign Create()
{
var item = new VoucherCampaign()
{
Id = Guid.NewGuid().ToString("N"),
Created = DateTimeOffset.UtcNow
};
return item;
}
/// <summary>
/// Gibt eine gefilterte Liste von Gutschein-Kampagnen zurück.
/// </summary>
/// <param name="filter">Filterbegriff der in bestimmten Feldern gesucht wird</param>
/// <returns>List betroffener Gutschein-Kampagnen</returns>
public async Task<List<VoucherCampaign>> SearchAsync(string filter)
{
return (await Repository.FindAsync(c => c.Title.Contains(filter) || c.NameLocalized.Contains(filter))).ToList();
}
/// <summary>
/// Gibt eine gefilterte Liste von Gutschein-Kampagnen 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 Gutschein-Kampagnen</returns>
public IQueryable<VoucherCampaignWithNames> 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;
}
/// <summary>
/// Gibt eine gefilterte Liste von Gutschein-Kampagnen 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 Gutschein-Kampagnen</returns>
public IQueryable<VoucherCampaignWithNames> 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;
}
/// <summary>
/// Gibt die Anzahl von Gutschein-Kampagnen 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 Gutschein-Kampagnen</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 != VoucherCampaignStatus.Draft);
return await query.CountAsync().ConfigureAwait(false);
}
/// <summary>
/// Gibt die Anzahl von Gutschein-Kampagnen in einem Status zurück
/// </summary>
/// <param name="status">Gesuchter Status</param>
/// <param name="includeDeleted">Sollen gelöschte inkludiert werden?</param>
/// <returns>Anzahl Gutschein-Kampagnen</returns>
public async Task<int> 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);
}
/// <summary>
/// Gibt eine Gutschein-Kampagne für einen Artikel im Warenkorb zurück
/// </summary>
/// <param name="cartItemId">Id des Artikels im Warenkorb</param>
/// <returns>Gutschein-Kampagne oder null, wenn nicht gefunden</returns>
public async Task<VoucherCampaign> GetByCartItemAsync(long cartItemId)
{
return await Repository.FirstOrDefaultAsync(c => c.CartItemId == cartItemId && c.Status == VoucherCampaignStatus.Draft);
}
/// <summary>
/// Gibt einen Gutschein-Kampagne für einen Artikel in einer Bestellung zurück
/// </summary>
/// <param name="orderItemId">Id des Artikels in der Bestellung</param>
/// <returns>Gutschein-Kampagne oder null, wenn nicht gefunden</returns>
public async Task<VoucherCampaign> GetByOrderItemAsync(long orderItemId)
{
return await Repository.FirstOrDefaultAsync(c => c.OrderItemId == orderItemId);
}
/// <summary>
/// Setzen des Status von Gutschein-Kampagnen nach Bestellung
/// </summary>
/// <param name="orderId">Id der Bestellung</param>
/// <param name="status">Zu setzender Status</param>
/// <returns>Anzahl betroffener Gutschein-Kampagnen</returns>
public async Task<int> 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();
}
/// <summary>
/// Setzen des Bezahlstatus von Gutschein-Kampagnen nach Bestellung
/// </summary>
/// <param name="orderId">Id der Bestellung</param>
/// <param name="status">Zu setzender Status</param>
/// <returns>Anzahl betroffener Gutschein-Kampagnen</returns>
public async Task<int> 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();
}
/// <summary>
/// Starten von gebuchten Gutschein-Kampagnen wenn diese das Start-Datum erreicht haben
/// </summary>
/// <returns>Anzahl gestartete Gutschein-Kampagnen</returns>
public async Task<int> 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();
}
/// <summary>
/// Stoppen von abgelaufenen Gutschein-Kampagnen wenn diese das Enddatum erreicht haben
/// </summary>
/// <returns>Anzahl gestoppte Gutschein-Kampagnen</returns>
public async Task<int> 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();
}
/// <summary>
/// Erstellen von Gutscheinen für eine Gutschein-Kampagne
/// </summary>
/// <param name="voucherCampaignId">Id der Kampagne</param>
/// <param name="userName">Der Benutzer der die Gutscheine angelegt hat</param>
/// <returns>Anzahl der erstellten Gutscheine</returns>
public async Task<int> 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;
}
/// <summary>
/// Prüft ob ein Präfix für eine Gutschein-Kampagne verfügbar ist
/// </summary>
/// <param name="prefix">Präfix der geprüft werden soll</param>
/// <param name="voucherCampaignId">Id der Kampagne die ignoriert werden soll</param>
/// <returns>true wenn verfügbar, false sonst</returns>
public async Task<bool> IsPrefixAvailableAsync(string prefix, string voucherCampaignId)
{
var itemFound = await Repository.FirstOrDefaultAsync(c => c.Id != voucherCampaignId && c.Prefix == prefix).ConfigureAwait(false);
return itemFound == null;
}
/// <summary>
/// Aktualisiert den Gutschein-Betrag für alle noch nicht eingelösten oder deaktivierten Gutscheine einer Kampagne
/// </summary>
/// <param name="voucherCampaignId">Id der Kampagne</param>
/// <param name="voucherAmmount">Wert der gesetzt werden soll</param>
/// <returns>Anzahl Gutscheine die betroffen sind</returns>
public async Task<int> 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();
}
/// <summary>
/// Löschen der Gutscheine und deren Verwendung für eine Kampagne
/// </summary>
/// <param name="voucherCampaignId">Id der Kampagne</param>
/// <returns>Anzahl gelöscht</returns>
public async Task<int> 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();
}
/// <summary>
/// Gibt eine Liste von Gutscheinen für eine Kampagne zurück
/// </summary>
/// <param name="voucherCampaignId">Id der Kampagne</param>
/// <returns>Liste Gutscheine</returns>
public async Task<List<Voucher>> GetVouchersForCampaignAsync(string voucherCampaignId)
{
var vouchers = await _vouchersRepository.FindAsync(c => c.VoucherCampaignId == voucherCampaignId).ConfigureAwait(false);
return vouchers.ToList();
}
/// <summary>
/// Prüft ob ein manueller Gutscheincode für einen Benutzer gültig ist und eingesetzt werden kann
/// </summary>
/// <param name="code">Code</param>
/// <param name="appUserId">Id des AppUsers</param>
/// <returns>Validierungs-Ergebnis</returns>
public async Task<VoucherValidationResult> 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;
}
/// <summary>
/// Gibt einen Gutschein anhand des Codes zurück
/// </summary>
/// <param name="code">Code des Gutscheins</param>
/// <returns>Gutschein oder null, wenn nicht gefunden</returns>
public async Task<Voucher> GetVoucherByCodeAsync(string code)
{
var voucher = await _vouchersRepository.FirstOrDefaultAsync(c => c.Code == code).ConfigureAwait(false);
return voucher;
}
/// <summary>
/// Gibt einen Gutschein anhand des QR-Codes zurück
/// </summary>
/// <param name="qrCode">QR-Code des Gutscheins</param>
/// <returns>Gutschein oder null, wenn nicht gefunden</returns>
public async Task<Voucher> GetVoucherByQrCodeAsync(string qrCode)
{
var voucher = await _vouchersRepository.FirstOrDefaultAsync(c => c.QrCode == qrCode).ConfigureAwait(false);
return voucher;
}
/// <summary>
/// Reserviert einen Gutschein für einen Benutzer und einen Walk
/// </summary>
/// <param name="voucherId">Id des Vouchers</param>
/// <param name="appUserId">Id des AppUsers</param>
/// <param name="walkId">Id des Walks</param>
/// <param name="voucherAmmount">Wert des Gutscheins</param>
/// <param name="voucherAmmountUsed">Wert der verbraucht wurde. Kann kleiner Gutscheinwert sein</param>
/// <returns>VoucherUsage</returns>
public async Task<VoucherUsage> 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;
}
/// <summary>
/// Löschen der Verwendung eines Gutscheins
/// </summary>
/// <param name="voucherUsage">VoucherUsage</param>
public void RemoveUsage(VoucherUsage voucherUsage)
{
_voucherUsagesRepository.Remove(voucherUsage);
}
/// <summary>
/// Gibt die Verwendung eines Gutscheins zurück
/// </summary>
/// <param name="voucherId">Id des Gutscheins</param>
/// <param name="appUserId">Id des AppUsers</param>
/// <param name="walkId">Id des Walks</param>
/// <returns>VoucherUsage oder null, wenn nicht gefunden</returns>
public async Task<VoucherUsage> 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;
}
/// <summary>
/// Gibt einen Gutschein zurück
/// </summary>
/// <param name="voucherId">Id des Gutscheins</param>
/// <returns>Voucher oder null, wenn nicht gefunden</returns>
public async Task<Voucher> GetVoucherAsync(string voucherId)
{
var voucher = await _vouchersRepository.FirstOrDefaultAsync(c => c.Id == voucherId).ConfigureAwait(false);
return voucher;
}
/// <summary>
/// Löschen von Voucher-Usages für einen AppUser
/// </summary>
/// <param name="appUserId">Id des AppUsers</param>
/// <returns>Anzahl gelöscht</returns>
public async Task<int> 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
/// <summary>
/// Erstellt einen zufälligen Code in einer bestimmten Länge
/// </summary>
/// <param name="length">Länge des Codes</param>
/// <returns>Code</returns>
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
}
}