118 lines
4.6 KiB
C#
118 lines
4.6 KiB
C#
// 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<DiscountCalculationResult> CalculateDiscountAsync(List<OrderItem> 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;
|
|
}
|
|
}
|
|
} |