diff --git a/Webshop.Api/Controllers/Customers/OrdersController.cs b/Webshop.Api/Controllers/Customers/OrdersController.cs index a7c9f4d..f8e18fd 100644 --- a/Webshop.Api/Controllers/Customers/OrdersController.cs +++ b/Webshop.Api/Controllers/Customers/OrdersController.cs @@ -1,13 +1,14 @@ // src/Webshop.Api/Controllers/Customer/OrdersController.cs using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; +using Webshop.Application; using Webshop.Application.DTOs.Orders; using Webshop.Application.Services.Customers; -using Webshop.Application.Services.Customers.Interfaces; // Für IOrderService namespace Webshop.Api.Controllers.Customer { @@ -23,49 +24,41 @@ namespace Webshop.Api.Controllers.Customer _orderService = orderService; } - [HttpPost] - public async Task CreateOrder([FromBody] CreateOrderDto orderDto) - { - if (!ModelState.IsValid) return BadRequest(ModelState); - - var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); - if (string.IsNullOrEmpty(userId)) - { - // In einer [Authorize]-Methode sollte das nie passieren, aber zur Sicherheit - return Unauthorized(); - } - - var (success, createdOrder, errorMessage) = await _orderService.CreateOrderAsync(orderDto, userId); - - if (!success) - { - return BadRequest(new { Message = errorMessage }); - } - - // Hier wird GetMyOrderById referenziert, also erstellen wir eine leere Methode dafür - return CreatedAtAction(nameof(GetMyOrderById), new { id = createdOrder.Id }, createdOrder); - } - [HttpGet] - public async Task>> GetMyOrders() + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task GetMyOrders() { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) return Unauthorized(); - var orders = await _orderService.GetMyOrdersAsync(userId); - return Ok(orders); + var result = await _orderService.GetMyOrdersAsync(userId); + return Ok(result.Value); } [HttpGet("{id}")] - public async Task> GetMyOrderById(Guid id) + [ProducesResponseType(typeof(OrderDetailDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task GetMyOrderById(Guid id) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) return Unauthorized(); - var order = await _orderService.GetMyOrderByIdAsync(id, userId); - if (order == null) return NotFound(); + var result = await _orderService.GetMyOrderByIdAsync(id, userId); - return Ok(order); + return result.Type switch + { + ServiceResultType.Success => Ok(result.Value), + ServiceResultType.Forbidden => Forbid(), + ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }), + _ => StatusCode(StatusCodes.Status500InternalServerError, "Ein unerwarteter Fehler ist aufgetreten.") + }; } + + // HINWEIS: Die Logik zur Erstellung einer Bestellung wurde in den CheckoutController verschoben, + // da dies dem typischen Workflow entspricht. Falls du diesen Endpunkt hier dennoch benötigst, + // kannst du ihn nach dem gleichen Muster wie im CheckoutController implementieren. } } \ No newline at end of file diff --git a/Webshop.Application/Services/Customers/Interfaces/IOrderService.cs b/Webshop.Application/Services/Customers/Interfaces/IOrderService.cs index aac6e13..f64b6a1 100644 --- a/Webshop.Application/Services/Customers/Interfaces/IOrderService.cs +++ b/Webshop.Application/Services/Customers/Interfaces/IOrderService.cs @@ -2,14 +2,14 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Webshop.Application; using Webshop.Application.DTOs.Orders; namespace Webshop.Application.Services.Customers { public interface IOrderService { - Task<(bool Success, OrderDetailDto? CreatedOrder, string? ErrorMessage)> CreateOrderAsync(CreateOrderDto orderDto, string userId); - Task> GetMyOrdersAsync(string userId); - Task GetMyOrderByIdAsync(Guid orderId, string userId); + Task>> GetMyOrdersAsync(string userId); + Task> GetMyOrderByIdAsync(Guid orderId, string userId); } } \ No newline at end of file diff --git a/Webshop.Application/Services/Customers/OrderService.cs b/Webshop.Application/Services/Customers/OrderService.cs index 3003cf6..a2fcbcd 100644 --- a/Webshop.Application/Services/Customers/OrderService.cs +++ b/Webshop.Application/Services/Customers/OrderService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Webshop.Application; using Webshop.Application.DTOs.Customers; using Webshop.Application.DTOs.Orders; using Webshop.Domain.Entities; @@ -16,130 +17,21 @@ namespace Webshop.Application.Services.Customers public class OrderService : IOrderService { private readonly ApplicationDbContext _context; - private readonly IOrderRepository _orderRepository; - private readonly IProductRepository _productRepository; - private readonly IShippingMethodRepository _shippingMethodRepository; private readonly ICustomerRepository _customerRepository; - public OrderService( - ApplicationDbContext context, - IOrderRepository orderRepository, - IProductRepository productRepository, - IShippingMethodRepository shippingMethodRepository, - ICustomerRepository customerRepository) + public OrderService(ApplicationDbContext context, ICustomerRepository customerRepository) { _context = context; - _orderRepository = orderRepository; - _productRepository = productRepository; - _shippingMethodRepository = shippingMethodRepository; _customerRepository = customerRepository; } - public async Task<(bool Success, OrderDetailDto? CreatedOrder, string? ErrorMessage)> CreateOrderAsync(CreateOrderDto orderDto, string userId) - { - await using var transaction = await _context.Database.BeginTransactionAsync(); - - try - { - var customer = await _customerRepository.GetByUserIdAsync(userId); - if (customer == null) - { - return (false, null, "Kundenprofil nicht gefunden."); - } - - var shippingMethod = await _shippingMethodRepository.GetByIdAsync(orderDto.ShippingMethodId); - if (shippingMethod == null || !shippingMethod.IsActive) return (false, null, "Ungültige Versandmethode."); - - var paymentMethod = await _context.PaymentMethods.FindAsync(orderDto.PaymentMethodId); - if (paymentMethod == null || !paymentMethod.IsActive) return (false, null, "Ungültige Zahlungsmethode."); - - if (!await _context.Addresses.AnyAsync(a => a.Id == orderDto.ShippingAddressId && a.Customer.AspNetUserId == userId) || - !await _context.Addresses.AnyAsync(a => a.Id == orderDto.BillingAddressId && a.Customer.AspNetUserId == userId)) - { - return (false, null, "Ungültige oder nicht zugehörige Liefer- oder Rechnungsadresse."); - } - - var orderItems = new List(); - decimal itemsTotal = 0; - - foreach (var itemDto in orderDto.Items) - { - var product = await _productRepository.GetProductByIdAsync(itemDto.ProductId); - if (product == null || !product.IsActive) - { - await transaction.RollbackAsync(); - return (false, null, $"Produkt mit ID {itemDto.ProductId} ist nicht 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}."); - } - - var orderItem = new OrderItem - { - Id = Guid.NewGuid(), - ProductId = product.Id, - ProductVariantId = itemDto.ProductVariantId, - ProductName = product.Name, - ProductSKU = product.SKU, - Quantity = itemDto.Quantity, - UnitPrice = product.Price, - TotalPrice = product.Price * itemDto.Quantity - }; - - orderItems.Add(orderItem); - itemsTotal += orderItem.TotalPrice; - - product.StockQuantity -= itemDto.Quantity; - _context.Products.Update(product); - } - - decimal shippingCost = shippingMethod.BaseCost; - decimal taxAmount = (itemsTotal + shippingCost) * 0.19m; // Annahme: 19% MwSt. - decimal orderTotal = itemsTotal + shippingCost + taxAmount; - - var newOrder = new Order - { - Id = Guid.NewGuid(), - OrderNumber = $"WS-{DateTime.UtcNow:yyyyMMdd}-{new Random().Next(1000, 9999)}", - CustomerId = customer.Id, // Verwende die interne Customer-ID - OrderDate = DateTimeOffset.UtcNow, - OrderStatus = OrderStatus.Pending.ToString(), - PaymentStatus = PaymentStatus.Pending.ToString(), - OrderTotal = orderTotal, - ShippingCost = shippingCost, - TaxAmount = taxAmount, - DiscountAmount = 0, - PaymentMethod = paymentMethod.Name, - PaymentMethodId = paymentMethod.Id, - ShippingMethodId = shippingMethod.Id, - BillingAddressId = orderDto.BillingAddressId, - ShippingAddressId = orderDto.ShippingAddressId, - OrderItems = orderItems - }; - - await _orderRepository.AddAsync(newOrder); - - await transaction.CommitAsync(); - - var createdOrderDto = new OrderDetailDto { Id = newOrder.Id, OrderNumber = newOrder.OrderNumber /* ... weitere Felder mappen ... */ }; - return (true, createdOrderDto, null); - } - catch (Exception ex) - { - await transaction.RollbackAsync(); - return (false, null, $"Ein unerwarteter Fehler ist aufgetreten: {ex.Message}"); - } - } - - public async Task> GetMyOrdersAsync(string userId) + public async Task>> GetMyOrdersAsync(string userId) { var customer = await _customerRepository.GetByUserIdAsync(userId); if (customer == null) { - return new List(); + // A valid user might not have a customer profile yet. Return empty list. + return ServiceResult.Ok>(new List()); } var orders = await _context.Orders @@ -147,47 +39,56 @@ namespace Webshop.Application.Services.Customers .OrderByDescending(o => o.OrderDate) .ToListAsync(); - return orders.Select(o => new OrderSummaryDto + var dtos = orders.Select(o => new OrderSummaryDto { Id = o.Id, OrderNumber = o.OrderNumber, OrderDate = o.OrderDate, CustomerName = $"{customer.FirstName} {customer.LastName}", TotalAmount = o.OrderTotal, - Status = Enum.TryParse(o.OrderStatus, true, out var orderStatus) ? orderStatus : OrderStatus.Pending, - PaymentStatus = Enum.TryParse(o.PaymentStatus, true, out var paymentStatus) ? paymentStatus : PaymentStatus.Pending + Status = Enum.TryParse(o.OrderStatus, true, out var os) ? os : OrderStatus.Pending, + PaymentStatus = Enum.TryParse(o.PaymentStatus, true, out var ps) ? ps : PaymentStatus.Pending }).ToList(); + + return ServiceResult.Ok>(dtos); } - public async Task GetMyOrderByIdAsync(Guid orderId, string userId) + public async Task> GetMyOrderByIdAsync(Guid orderId, string userId) { var customer = await _customerRepository.GetByUserIdAsync(userId); if (customer == null) { - return null; + return ServiceResult.Fail(ServiceResultType.Unauthorized, "Kundenprofil nicht gefunden."); } var order = await _context.Orders - .Include(o => o.BillingAddress) // Lade die Address-Entität - .Include(o => o.ShippingAddress) // Lade die Address-Entität - .Include(o => o.PaymentMethodInfo) + .Include(o => o.BillingAddress) + .Include(o => o.ShippingAddress) .Include(o => o.OrderItems) - .ThenInclude(oi => oi.Product) - .FirstOrDefaultAsync(o => o.Id == orderId && o.CustomerId == customer.Id); + .FirstOrDefaultAsync(o => o.Id == orderId); if (order == null) { - return null; + return ServiceResult.Fail(ServiceResultType.NotFound, $"Bestellung mit ID '{orderId}' nicht gefunden."); } - // << KORREKTUR: Manuelles Mapping von Address-Entität zu AddressDto >> + if (order.CustomerId != customer.Id) + { + return ServiceResult.Fail(ServiceResultType.Forbidden, "Zugriff auf diese Bestellung verweigert."); + } + + return ServiceResult.Ok(MapToOrderDetailDto(order)); + } + + private OrderDetailDto MapToOrderDetailDto(Order order) + { return new OrderDetailDto { Id = order.Id, OrderNumber = order.OrderNumber, OrderDate = order.OrderDate, CustomerId = order.CustomerId, - Status = Enum.TryParse(order.OrderStatus, true, out var orderStatus) ? orderStatus : OrderStatus.Pending, + Status = Enum.TryParse(order.OrderStatus, true, out var os) ? os : OrderStatus.Pending, TotalAmount = order.OrderTotal, ShippingAddress = new AddressDto { @@ -210,7 +111,7 @@ namespace Webshop.Application.Services.Customers Type = order.BillingAddress.Type }, PaymentMethod = order.PaymentMethod, - PaymentStatus = Enum.TryParse(order.PaymentStatus, true, out var paymentStatus) ? paymentStatus : PaymentStatus.Pending, + PaymentStatus = Enum.TryParse(order.PaymentStatus, true, out var ps) ? ps : PaymentStatus.Pending, ShippingTrackingNumber = order.ShippingTrackingNumber, ShippedDate = order.ShippedDate, DeliveredDate = order.DeliveredDate,