discounts überarbeitet

This commit is contained in:
Tizian.Breuch
2025-08-29 13:57:39 +02:00
parent 6e6f38397b
commit a2e3a44e94
7 changed files with 257 additions and 112 deletions

View File

@@ -6,85 +6,113 @@ using System.Linq;
using System.Threading.Tasks;
using Webshop.Application.Services.Public.Interfaces;
using Webshop.Domain.Entities;
using Webshop.Domain.Interfaces;
using Webshop.Domain.Enums;
using Webshop.Infrastructure.Data;
namespace Webshop.Application.Services.Public
{
public class DiscountService : IDiscountService
{
private readonly ApplicationDbContext _context; // Direkter Zugriff auf DbSets
private readonly IDiscountRepository _discountRepository;
private readonly IProductRepository _productRepository;
private readonly ApplicationDbContext _context;
public DiscountService(
ApplicationDbContext context,
IDiscountRepository discountRepository,
IProductRepository productRepository)
public DiscountService(ApplicationDbContext context)
{
_context = context;
_discountRepository = discountRepository;
_productRepository = productRepository;
}
public async Task<decimal> CalculateDiscountAsync(List<OrderItem> orderItems, string? couponCode)
public async Task<DiscountCalculationResult> CalculateDiscountAsync(List<OrderItem> orderItems, string? couponCode)
{
decimal totalDiscount = 0;
var result = new DiscountCalculationResult();
var now = DateTimeOffset.UtcNow;
decimal itemsTotal = orderItems.Sum(i => i.TotalPrice);
// 1. Hole alle relevanten Rabatte
var discounts = await _context.Discounts
// 1. Lade alle potenziell anwendbaren Rabatte
var query = _context.Discounts
.Include(d => d.ProductDiscounts)
.Include(d => d.categorieDiscounts)
.Where(d => d.IsActive && d.StartDate <= DateTimeOffset.UtcNow && (!d.EndDate.HasValue || d.EndDate >= DateTimeOffset.UtcNow))
.ToListAsync();
.Where(d => d.IsActive && d.StartDate <= now &&
(!d.EndDate.HasValue || d.EndDate >= now) &&
(!d.MaximumUsageCount.HasValue || d.CurrentUsageCount < d.MaximumUsageCount.Value));
// Filtern nach Gutscheincode
var potentialDiscounts = await query.ToListAsync();
Discount? bestDiscount = null;
// 2. Finde den besten anwendbaren Rabatt
if (!string.IsNullOrEmpty(couponCode))
{
discounts = discounts.Where(d => d.CouponCode == couponCode && d.RequiresCouponCode).ToList();
// Wenn ein Code eingegeben wurde, suchen wir nur nach diesem einen Rabatt
bestDiscount = potentialDiscounts.FirstOrDefault(d => d.RequiresCouponCode && d.CouponCode == couponCode);
}
if (bestDiscount == null)
{
return result; // Kein passender Rabatt gefunden
}
// 3. Prüfe die Bedingungen des besten Rabatts
if (bestDiscount.MinimumOrderAmount.HasValue && itemsTotal < bestDiscount.MinimumOrderAmount.Value)
{
return result; // Mindestbestellwert nicht erreicht
}
decimal discountAmount = 0;
// 4. Berechne den Rabattbetrag
// Fall A: Rabatt gilt für den gesamten Warenkorb
if (!bestDiscount.ProductDiscounts.Any() && !bestDiscount.categorieDiscounts.Any())
{
discountAmount = CalculateAmount(bestDiscount, itemsTotal);
}
// Fall B: Rabatt gilt für spezifische Produkte/Kategorien
else
{
// Rabatte ohne Gutscheincode, falls zutreffend
discounts = discounts.Where(d => !d.RequiresCouponCode).ToList();
}
// Lade die Kategorie-Zuweisungen aller Produkte im Warenkorb
var productCategoryIds = await _context.Productcategories
.Where(pc => orderItems.Select(oi => oi.ProductId).Contains(pc.ProductId))
.ToDictionaryAsync(pc => pc.ProductId, pc => pc.categorieId);
// 2. Wende Rabatte auf Artikel an
foreach (var item in orderItems)
{
decimal itemDiscount = 0;
// Wir nehmen an, dass ein Artikel nur den besten Rabatt erhalten kann
var applicableDiscount = discounts.FirstOrDefault(d =>
d.ProductDiscounts.Any(pd => pd.ProductId == item.ProductId) || // Rabatt auf Produkt
d.categorieDiscounts.Any(cd => _context.Productcategories.Any(pc => pc.ProductId == item.ProductId && pc.categorieId == cd.categorieId)) // Rabatt auf Kategorie
);
if (applicableDiscount != null)
decimal applicableItemsTotal = 0;
foreach (var item in orderItems)
{
itemDiscount = item.UnitPrice * item.Quantity * (applicableDiscount.DiscountValue / 100);
// Hier müsste die Logik für FixedAmount-Rabatte hin
}
if (!item.ProductId.HasValue)
{
continue;
}
totalDiscount += itemDiscount;
Guid productId = item.ProductId.Value;
bool isApplicable = bestDiscount.ProductDiscounts.Any(pd => pd.ProductId == productId) ||
(productCategoryIds.ContainsKey(productId) &&
bestDiscount.categorieDiscounts.Any(cd => cd.categorieId == productCategoryIds[productId]));
if (isApplicable)
{
applicableItemsTotal += item.TotalPrice;
}
}
discountAmount = CalculateAmount(bestDiscount, applicableItemsTotal);
}
// 3. Wende Warenkorb-Rabatte an (falls keine spezifischen Produkte/Kategorien zugewiesen sind)
var cartDiscounts = discounts.Where(d => !d.ProductDiscounts.Any() && !d.categorieDiscounts.Any()).ToList();
result.TotalDiscountAmount = discountAmount;
result.AppliedDiscountIds.Add(bestDiscount.Id);
decimal cartTotal = orderItems.Sum(i => i.TotalPrice);
foreach (var discount in cartDiscounts)
return result;
}
// Private Helper-Methode zur Berechnung des Rabattwertes
private decimal CalculateAmount(Discount discount, decimal baseAmount)
{
if (discount.DiscountType == DiscountType.FixedAmount.ToString())
{
if (discount.MinimumOrderAmount.HasValue && cartTotal < discount.MinimumOrderAmount.Value) continue;
// Beispiel: Prozentualer Rabatt auf den Warenkorb
if (discount.DiscountType == DiscountType.Percentage.ToString())
{
totalDiscount += cartTotal * (discount.DiscountValue / 100);
}
// Stelle sicher, dass der Rabatt nicht höher als der Betrag ist
return Math.Min(baseAmount, discount.DiscountValue);
}
return totalDiscount;
if (discount.DiscountType == DiscountType.Percentage.ToString())
{
return baseAmount * (discount.DiscountValue / 100m);
}
return 0;
}
}
}

View File

@@ -6,8 +6,24 @@ using Webshop.Domain.Entities;
namespace Webshop.Application.Services.Public.Interfaces
{
/// <summary>
/// Stellt das Ergebnis der Rabattberechnung dar.
/// </summary>
public class DiscountCalculationResult
{
/// <summary>
/// Der gesamte berechnete Rabattbetrag.
/// </summary>
public decimal TotalDiscountAmount { get; set; } = 0;
/// <summary>
/// Die IDs der Rabatte, die erfolgreich angewendet wurden.
/// </summary>
public List<Guid> AppliedDiscountIds { get; set; } = new List<Guid>();
}
public interface IDiscountService
{
Task<decimal> CalculateDiscountAsync(List<OrderItem> orderItems, string? couponCode);
Task<DiscountCalculationResult> CalculateDiscountAsync(List<OrderItem> orderItems, string? couponCode);
}
}