diff --git a/Webshop.Api/Controllers/Admin/AdminDiscountsController.cs b/Webshop.Api/Controllers/Admin/AdminDiscountsController.cs
index ceea512..13051aa 100644
--- a/Webshop.Api/Controllers/Admin/AdminDiscountsController.cs
+++ b/Webshop.Api/Controllers/Admin/AdminDiscountsController.cs
@@ -1,9 +1,10 @@
// src/Webshop.Api/Controllers/Admin/AdminDiscountsController.cs
-using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Webshop.Application;
using Webshop.Application.DTOs.Discounts;
using Webshop.Application.Services.Admin.Interfaces;
@@ -58,11 +59,20 @@ namespace Webshop.Api.Controllers.Admin
///
/// Das Datenobjekt des zu erstellenden Rabatts.
[HttpPost]
+ [ProducesResponseType(typeof(DiscountDto), StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task> CreateDiscount([FromBody] DiscountDto discountDto)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
- var createdDiscount = await _adminDiscountService.CreateDiscountAsync(discountDto);
- return CreatedAtAction(nameof(GetDiscountById), new { id = createdDiscount.Id }, createdDiscount);
+
+ var result = await _adminDiscountService.CreateDiscountAsync(discountDto);
+
+ if (result.Type == ServiceResultType.Success)
+ {
+ return CreatedAtAction(nameof(GetDiscountById), new { id = result.Value!.Id }, result.Value);
+ }
+
+ return BadRequest(new { Message = result.ErrorMessage });
}
///
@@ -71,15 +81,23 @@ namespace Webshop.Api.Controllers.Admin
/// Die ID des zu aktualisierenden Rabatts.
/// Die neuen Daten für den Rabatt.
[HttpPut("{id}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task UpdateDiscount(Guid id, [FromBody] DiscountDto discountDto)
{
- if (id != discountDto.Id) return BadRequest();
+ if (id != discountDto.Id) return BadRequest("ID in URL und Body stimmen nicht überein.");
if (!ModelState.IsValid) return BadRequest(ModelState);
- var success = await _adminDiscountService.UpdateDiscountAsync(discountDto);
- if (!success) return NotFound();
+ var result = await _adminDiscountService.UpdateDiscountAsync(discountDto);
- return NoContent();
+ return result.Type switch
+ {
+ ServiceResultType.Success => NoContent(),
+ ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }),
+ ServiceResultType.InvalidInput => BadRequest(new { Message = result.ErrorMessage }),
+ _ => StatusCode(StatusCodes.Status500InternalServerError, "Ein unerwarteter Fehler ist aufgetreten.")
+ };
}
///
@@ -87,12 +105,18 @@ namespace Webshop.Api.Controllers.Admin
///
/// Die ID des zu löschenden Rabatts.
[HttpDelete("{id}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task DeleteDiscount(Guid id)
{
- var success = await _adminDiscountService.DeleteDiscountAsync(id);
- if (!success) return NotFound();
+ var result = await _adminDiscountService.DeleteDiscountAsync(id);
- return NoContent();
+ return result.Type switch
+ {
+ ServiceResultType.Success => NoContent(),
+ ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }),
+ _ => StatusCode(StatusCodes.Status500InternalServerError, "Ein unerwarteter Fehler ist aufgetreten.")
+ };
}
}
}
\ No newline at end of file
diff --git a/Webshop.Application/DTOs/Orders/CreateOrderItemDto.cs b/Webshop.Application/DTOs/Orders/CreateOrderItemDto.cs
index d49e99a..bb93c4c 100644
--- a/Webshop.Application/DTOs/Orders/CreateOrderItemDto.cs
+++ b/Webshop.Application/DTOs/Orders/CreateOrderItemDto.cs
@@ -1,6 +1,7 @@
// Auto-generiert von CreateWebshopFiles.ps1
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
@@ -8,6 +9,7 @@ namespace Webshop.Application.DTOs.Orders
{
public class CreateOrderItemDto
{
+ [Required]
public Guid ProductId { get; set; }
public Guid? ProductVariantId { get; set; }
public int Quantity { get; set; }
diff --git a/Webshop.Application/Services/Admin/AdminDiscountService.cs b/Webshop.Application/Services/Admin/AdminDiscountService.cs
index 2e14c84..2c34b84 100644
--- a/Webshop.Application/Services/Admin/AdminDiscountService.cs
+++ b/Webshop.Application/Services/Admin/AdminDiscountService.cs
@@ -1,4 +1,5 @@
// src/Webshop.Application/Services/Admin/AdminDiscountService.cs
+using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -8,16 +9,19 @@ using Webshop.Application.Services.Admin.Interfaces;
using Webshop.Domain.Entities;
using Webshop.Domain.Enums;
using Webshop.Domain.Interfaces;
+using Webshop.Infrastructure.Data;
namespace Webshop.Application.Services.Admin
{
public class AdminDiscountService : IAdminDiscountService
{
private readonly IDiscountRepository _discountRepository;
+ private readonly ApplicationDbContext _context; // Für Unique-Prüfungen
- public AdminDiscountService(IDiscountRepository discountRepository)
+ public AdminDiscountService(IDiscountRepository discountRepository, ApplicationDbContext context)
{
_discountRepository = discountRepository;
+ _context = context;
}
public async Task> GetAllDiscountsAsync()
@@ -32,19 +36,41 @@ namespace Webshop.Application.Services.Admin
return discount != null ? MapToDto(discount) : null;
}
- public async Task CreateDiscountAsync(DiscountDto discountDto)
+ public async Task> CreateDiscountAsync(DiscountDto discountDto)
{
+ // Validierung: Gutscheincode muss eindeutig sein, wenn er benötigt wird
+ if (discountDto.RequiresCouponCode && !string.IsNullOrEmpty(discountDto.CouponCode))
+ {
+ var existing = await _context.Discounts.FirstOrDefaultAsync(d => d.CouponCode == discountDto.CouponCode);
+ if (existing != null)
+ {
+ return ServiceResult.Fail(ServiceResultType.InvalidInput, $"Der Gutscheincode '{discountDto.CouponCode}' existiert bereits.");
+ }
+ }
+
var discount = MapToEntity(discountDto);
discount.Id = Guid.NewGuid();
await _discountRepository.AddAsync(discount);
- return MapToDto(discount);
+ return ServiceResult.Ok(MapToDto(discount));
}
- public async Task UpdateDiscountAsync(DiscountDto discountDto)
+ public async Task UpdateDiscountAsync(DiscountDto discountDto)
{
var existingDiscount = await _discountRepository.GetByIdAsync(discountDto.Id);
- if (existingDiscount == null) return false;
+ if (existingDiscount == null)
+ {
+ return ServiceResult.Fail(ServiceResultType.NotFound, "Rabatt nicht gefunden.");
+ }
+
+ if (discountDto.RequiresCouponCode && !string.IsNullOrEmpty(discountDto.CouponCode))
+ {
+ var existing = await _context.Discounts.FirstOrDefaultAsync(d => d.CouponCode == discountDto.CouponCode && d.Id != discountDto.Id);
+ if (existing != null)
+ {
+ return ServiceResult.Fail(ServiceResultType.InvalidInput, $"Der Gutscheincode '{discountDto.CouponCode}' wird bereits von einem anderen Rabatt verwendet.");
+ }
+ }
// Update simple properties
existingDiscount.Name = discountDto.Name;
@@ -59,33 +85,39 @@ namespace Webshop.Application.Services.Admin
existingDiscount.MinimumOrderAmount = discountDto.MinimumOrderAmount;
existingDiscount.MaximumUsageCount = discountDto.MaximumUsageCount;
+ // << HIER IST DER WICHTIGE, WIEDERHERGESTELLTE TEIL >>
// Sync assigned products
existingDiscount.ProductDiscounts.Clear();
foreach (var productId in discountDto.AssignedProductIds)
{
- existingDiscount.ProductDiscounts.Add(new ProductDiscount { ProductId = productId });
+ existingDiscount.ProductDiscounts.Add(new ProductDiscount { DiscountId = existingDiscount.Id, ProductId = productId });
}
// Sync assigned categories
existingDiscount.categorieDiscounts.Clear();
foreach (var categoryId in discountDto.AssignedCategoryIds)
{
- existingDiscount.categorieDiscounts.Add(new CategorieDiscount { categorieId = categoryId });
+ existingDiscount.categorieDiscounts.Add(new CategorieDiscount { DiscountId = existingDiscount.Id, categorieId = categoryId });
}
+ // << ENDE DES WICHTIGEN TEILS >>
await _discountRepository.UpdateAsync(existingDiscount);
- return true;
+ return ServiceResult.Ok();
}
- public async Task DeleteDiscountAsync(Guid id)
+ public async Task DeleteDiscountAsync(Guid id)
{
var discount = await _discountRepository.GetByIdAsync(id);
- if (discount == null) return false;
+ if (discount == null)
+ {
+ return ServiceResult.Fail(ServiceResultType.NotFound, "Rabatt nicht gefunden.");
+ }
await _discountRepository.DeleteAsync(id);
- return true;
+ return ServiceResult.Ok();
}
+
// Helper methods for mapping
private DiscountDto MapToDto(Discount discount)
{
diff --git a/Webshop.Application/Services/Admin/Interfaces/IAdminDiscountService.cs b/Webshop.Application/Services/Admin/Interfaces/IAdminDiscountService.cs
index 3d41737..18cb6cd 100644
--- a/Webshop.Application/Services/Admin/Interfaces/IAdminDiscountService.cs
+++ b/Webshop.Application/Services/Admin/Interfaces/IAdminDiscountService.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Webshop.Application; // << NEU: Für ServiceResult >>
using Webshop.Application.DTOs.Discounts;
namespace Webshop.Application.Services.Admin.Interfaces
@@ -10,8 +11,8 @@ namespace Webshop.Application.Services.Admin.Interfaces
{
Task> GetAllDiscountsAsync();
Task GetDiscountByIdAsync(Guid id);
- Task CreateDiscountAsync(DiscountDto discountDto);
- Task UpdateDiscountAsync(DiscountDto discountDto);
- Task DeleteDiscountAsync(Guid id);
+ Task> CreateDiscountAsync(DiscountDto discountDto);
+ Task UpdateDiscountAsync(DiscountDto discountDto);
+ Task DeleteDiscountAsync(Guid id);
}
}
\ No newline at end of file
diff --git a/Webshop.Application/Services/Customers/CheckoutService.cs b/Webshop.Application/Services/Customers/CheckoutService.cs
index ecfe356..45693ea 100644
--- a/Webshop.Application/Services/Customers/CheckoutService.cs
+++ b/Webshop.Application/Services/Customers/CheckoutService.cs
@@ -40,16 +40,33 @@ namespace Webshop.Application.Services.Customers
_discountService = discountService;
}
- public async Task<(bool Success, OrderDetailDto? CreatedOrder, string? ErrorMessage)> CreateOrderAsync(CreateOrderDto orderDto, string userId)
+ public async Task<(bool Success, OrderDetailDto? CreatedOrder, string? ErrorMessage)> CreateOrderAsync(CreateOrderDto orderDto, string? userId)
{
- // Startet eine Datenbank-Transaktion. Entweder alles klappt, oder alles wird zurückgerollt.
await using var transaction = await _context.Database.BeginTransactionAsync();
-
try
{
- // --- 1. Validierung der Eingabedaten ---
- var customer = await _customerRepository.GetByUserIdAsync(userId);
- if (customer == null) return (false, null, "Kundenprofil nicht gefunden.");
+ // --- 1. Validierung von Kunde, Adressen und Methoden ---
+ Customer? customer = null;
+ if (!string.IsNullOrEmpty(userId))
+ {
+ customer = await _customerRepository.GetByUserIdAsync(userId);
+ if (customer == null) return (false, null, "Kundenprofil nicht gefunden.");
+
+ // Validiere, dass die Adressen zum eingeloggten Kunden gehören
+ if (!await _context.Addresses.AnyAsync(a => a.Id == orderDto.ShippingAddressId && a.CustomerId == customer.Id) ||
+ !await _context.Addresses.AnyAsync(a => a.Id == orderDto.BillingAddressId && a.CustomerId == customer.Id))
+ {
+ return (false, null, "Ungültige oder nicht zugehörige Liefer- oder Rechnungsadresse.");
+ }
+ }
+ else
+ {
+ // Gast-Checkout: Validierung der Gast-Daten
+ if (string.IsNullOrWhiteSpace(orderDto.GuestEmail))
+ return (false, null, "Für Gastbestellungen ist eine E-Mail-Adresse erforderlich.");
+ // Hinweis: Bei Gastbestellungen müssten die Adress-DTOs mitgesendet und hier neue Adressen erstellt werden.
+ // Der Einfachheit halber nehmen wir an, die Adress-IDs sind gültig, aber nicht mit einem Kunden verknüpft.
+ }
var shippingMethod = await _shippingMethodRepository.GetByIdAsync(orderDto.ShippingMethodId);
if (shippingMethod == null || !shippingMethod.IsActive) return (false, null, "Ungültige oder inaktive Versandmethode.");
@@ -57,33 +74,27 @@ namespace Webshop.Application.Services.Customers
var paymentMethod = await _context.PaymentMethods.FindAsync(orderDto.PaymentMethodId);
if (paymentMethod == null || !paymentMethod.IsActive) return (false, null, "Ungültige oder inaktive Zahlungsmethode.");
- if (!await _context.Addresses.AnyAsync(a => a.Id == orderDto.ShippingAddressId && a.CustomerId == customer.Id) ||
- !await _context.Addresses.AnyAsync(a => a.Id == orderDto.BillingAddressId && a.CustomerId == customer.Id))
- {
- return (false, null, "Ungültige oder nicht zugehörige Liefer- oder Rechnungsadresse.");
- }
-
// --- 2. Artikel verarbeiten und Lagerbestand prüfen ---
var orderItems = new List();
+ var productIds = orderDto.Items.Select(i => i.ProductId).ToList();
+ var products = await _context.Products.Where(p => productIds.Contains(p.Id)).ToListAsync();
decimal itemsTotal = 0;
foreach (var itemDto in orderDto.Items)
{
- var product = await _context.Products.FindAsync(itemDto.ProductId);
+ var product = products.FirstOrDefault(p => p.Id == itemDto.ProductId);
if (product == null || !product.IsActive)
{
await transaction.RollbackAsync();
- return (false, null, $"Produkt mit ID {itemDto.ProductId} ist nicht verfügbar.");
+ return (false, null, $"Ein Produkt im Warenkorb ist nicht mehr verfügbar.");
}
-
if (product.StockQuantity < itemDto.Quantity)
{
await transaction.RollbackAsync();
- return (false, null, $"Nicht genügend Lagerbestand für '{product.Name}'. Verfügbar: {product.StockQuantity}.");
+ return (false, null, $"Nicht genügend Lagerbestand für '{product.Name}'. Verfügbar: {product.StockQuantity}, benötigt: {itemDto.Quantity}.");
}
- // Lagerbestand reduzieren
- product.StockQuantity -= itemDto.Quantity;
+ product.StockQuantity -= itemDto.Quantity; // Lagerbestand reduzieren
var orderItem = new OrderItem
{
@@ -95,13 +106,13 @@ namespace Webshop.Application.Services.Customers
UnitPrice = product.Price,
TotalPrice = product.Price * itemDto.Quantity
};
-
orderItems.Add(orderItem);
itemsTotal += orderItem.TotalPrice;
}
// --- 3. Preise, Rabatte, Steuern und Gesamtbetrag berechnen ---
- decimal discountAmount = await _discountService.CalculateDiscountAsync(orderItems, orderDto.CouponCode);
+ var discountResult = await _discountService.CalculateDiscountAsync(orderItems, orderDto.CouponCode);
+ decimal discountAmount = discountResult.TotalDiscountAmount;
decimal shippingCost = shippingMethod.BaseCost;
decimal subTotal = itemsTotal + shippingCost - discountAmount;
if (subTotal < 0) subTotal = 0;
@@ -113,47 +124,53 @@ namespace Webshop.Application.Services.Customers
// --- 4. Bestellung erstellen ---
var newOrder = new Order
{
- CustomerId = customer.Id,
+ CustomerId = customer?.Id,
+ GuestEmail = customer == null ? orderDto.GuestEmail : null,
+ GuestPhoneNumber = customer == null ? orderDto.GuestPhoneNumber : null,
OrderNumber = $"WS-{DateTime.UtcNow:yyyyMMdd}-{new Random().Next(1000, 9999)}",
OrderDate = DateTimeOffset.UtcNow,
OrderStatus = OrderStatus.Pending.ToString(),
- PaymentStatus = PaymentStatus.Pending.ToString(), // Wird später vom Zahlungsanbieter aktualisiert
-
+ PaymentStatus = PaymentStatus.Pending.ToString(),
OrderTotal = orderTotal,
ShippingCost = shippingCost,
TaxAmount = taxAmount,
DiscountAmount = discountAmount,
-
PaymentMethod = paymentMethod.Name,
PaymentMethodId = paymentMethod.Id,
ShippingMethodId = shippingMethod.Id,
-
BillingAddressId = orderDto.BillingAddressId,
ShippingAddressId = orderDto.ShippingAddressId,
-
OrderItems = orderItems
};
+ await _orderRepository.AddAsync(newOrder);
- await _orderRepository.AddAsync(newOrder); // Speichert die Bestellung und die Artikel
- await _context.SaveChangesAsync(); // Speichert die Lagerbestandsänderungen
+ // --- 5. Rabattnutzung erhöhen ---
+ if (discountResult.AppliedDiscountIds.Any())
+ {
+ var appliedDiscounts = await _context.Discounts
+ .Where(d => discountResult.AppliedDiscountIds.Contains(d.Id))
+ .ToListAsync();
+ foreach (var discount in appliedDiscounts)
+ {
+ discount.CurrentUsageCount++;
+ }
+ }
- await transaction.CommitAsync(); // Transaktion erfolgreich abschließen
-
- // --- 5. Erfolgreiche Antwort erstellen ---
- var createdOrder = await _orderRepository.GetByIdAsync(newOrder.Id); // Lade die erstellte Bestellung mit allen Details
- var orderDetailDto = MapToOrderDetailDto(createdOrder!); // Mapping zum DTO
+ await _context.SaveChangesAsync(); // Speichert Lagerbestandsänderungen & Rabattnutzung
+ await transaction.CommitAsync();
+ // --- 6. Erfolgreiche Antwort erstellen ---
+ var createdOrder = await _orderRepository.GetByIdAsync(newOrder.Id);
+ var orderDetailDto = MapToOrderDetailDto(createdOrder!);
return (true, orderDetailDto, null);
}
catch (Exception ex)
{
await transaction.RollbackAsync();
- // Hier Fehler loggen
return (false, null, $"Ein unerwarteter Fehler ist aufgetreten: {ex.Message}");
}
}
- // Helper-Methode für das Mapping, um Code-Duplizierung zu vermeiden
private OrderDetailDto MapToOrderDetailDto(Order order)
{
return new OrderDetailDto
@@ -164,10 +181,35 @@ namespace Webshop.Application.Services.Customers
CustomerId = order.CustomerId,
Status = Enum.Parse(order.OrderStatus),
TotalAmount = order.OrderTotal,
- ShippingAddress = new AddressDto { /* Mapping */ },
- BillingAddress = new AddressDto { /* Mapping */ },
+ ShippingAddress = new AddressDto
+ {
+ Id = order.ShippingAddress.Id,
+ FirstName = order.ShippingAddress.FirstName,
+ LastName = order.ShippingAddress.LastName,
+ Street = order.ShippingAddress.Street,
+ HouseNumber = order.ShippingAddress.HouseNumber,
+ City = order.ShippingAddress.City,
+ PostalCode = order.ShippingAddress.PostalCode,
+ Country = order.ShippingAddress.Country,
+ Type = order.ShippingAddress.Type
+ },
+ BillingAddress = new AddressDto
+ {
+ Id = order.BillingAddress.Id,
+ FirstName = order.BillingAddress.FirstName,
+ LastName = order.BillingAddress.LastName,
+ Street = order.BillingAddress.Street,
+ HouseNumber = order.BillingAddress.HouseNumber,
+ City = order.BillingAddress.City,
+ PostalCode = order.BillingAddress.PostalCode,
+ Country = order.BillingAddress.Country,
+ Type = order.BillingAddress.Type
+ },
PaymentMethod = order.PaymentMethod,
PaymentStatus = Enum.Parse(order.PaymentStatus),
+ ShippingTrackingNumber = order.ShippingTrackingNumber,
+ ShippedDate = order.ShippedDate,
+ DeliveredDate = order.DeliveredDate,
OrderItems = order.OrderItems.Select(oi => new OrderItemDto
{
Id = oi.Id,
diff --git a/Webshop.Application/Services/Public/DiscountService.cs b/Webshop.Application/Services/Public/DiscountService.cs
index d16d303..e4e45d7 100644
--- a/Webshop.Application/Services/Public/DiscountService.cs
+++ b/Webshop.Application/Services/Public/DiscountService.cs
@@ -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 CalculateDiscountAsync(List orderItems, string? couponCode)
+ public async Task CalculateDiscountAsync(List 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;
}
}
}
\ No newline at end of file
diff --git a/Webshop.Application/Services/Public/Interfaces/IDiscountService.cs b/Webshop.Application/Services/Public/Interfaces/IDiscountService.cs
index 9ba3087..c45e150 100644
--- a/Webshop.Application/Services/Public/Interfaces/IDiscountService.cs
+++ b/Webshop.Application/Services/Public/Interfaces/IDiscountService.cs
@@ -6,8 +6,24 @@ using Webshop.Domain.Entities;
namespace Webshop.Application.Services.Public.Interfaces
{
+ ///
+ /// Stellt das Ergebnis der Rabattberechnung dar.
+ ///
+ public class DiscountCalculationResult
+ {
+ ///
+ /// Der gesamte berechnete Rabattbetrag.
+ ///
+ public decimal TotalDiscountAmount { get; set; } = 0;
+
+ ///
+ /// Die IDs der Rabatte, die erfolgreich angewendet wurden.
+ ///
+ public List AppliedDiscountIds { get; set; } = new List();
+ }
+
public interface IDiscountService
{
- Task CalculateDiscountAsync(List orderItems, string? couponCode);
+ Task CalculateDiscountAsync(List orderItems, string? couponCode);
}
}
\ No newline at end of file