// src/Webshop.Application/Services/Public/DiscountService.cs using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Webshop.Application.Services.Public.Interfaces; using Webshop.Domain.Entities; using Webshop.Domain.Enums; using Webshop.Infrastructure.Data; namespace Webshop.Application.Services.Public { public class DiscountService : IDiscountService { private readonly ApplicationDbContext _context; public DiscountService(ApplicationDbContext context) { _context = context; } public async Task CalculateDiscountAsync(List orderItems, string? couponCode) { var result = new DiscountCalculationResult(); var now = DateTimeOffset.UtcNow; decimal itemsTotal = orderItems.Sum(i => i.TotalPrice); // 1. Lade alle potenziell anwendbaren Rabatte var query = _context.Discounts .Include(d => d.ProductDiscounts) .Include(d => d.categorieDiscounts) .Where(d => d.IsActive && d.StartDate <= now && (!d.EndDate.HasValue || d.EndDate >= now) && (!d.MaximumUsageCount.HasValue || d.CurrentUsageCount < d.MaximumUsageCount.Value)); var potentialDiscounts = await query.ToListAsync(); Discount? bestDiscount = null; // 2. Finde den besten anwendbaren Rabatt if (!string.IsNullOrEmpty(couponCode)) { // 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 { // 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); decimal applicableItemsTotal = 0; foreach (var item in orderItems) { if (!item.ProductId.HasValue) { continue; } 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); } result.TotalDiscountAmount = discountAmount; result.AppliedDiscountIds.Add(bestDiscount.Id); return result; } // Private Helper-Methode zur Berechnung des Rabattwertes private decimal CalculateAmount(Discount discount, decimal baseAmount) { if (discount.DiscountType == DiscountType.FixedAmount.ToString()) { // Stelle sicher, dass der Rabatt nicht höher als der Betrag ist return Math.Min(baseAmount, discount.DiscountValue); } if (discount.DiscountType == DiscountType.Percentage.ToString()) { return baseAmount * (discount.DiscountValue / 100m); } return 0; } } }