Compare commits
6 Commits
c4d4a27798
...
test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf24eac58b | ||
|
|
3c6eeb5fe5 | ||
|
|
d48d83c87d | ||
|
|
19366febb8 | ||
|
|
de67e01f2c | ||
|
|
9c48f62ff0 |
54
Webshop.Api/Controllers/Customers/CartController.cs
Normal file
54
Webshop.Api/Controllers/Customers/CartController.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Webshop.Application;
|
||||||
|
using Webshop.Application.DTOs.Customers; // Für CartDto
|
||||||
|
using Webshop.Application.DTOs.Shipping; // Für CartItemDto
|
||||||
|
using Webshop.Application.Services.Customers.Interfaces;
|
||||||
|
|
||||||
|
namespace Webshop.Api.Controllers.Customer
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/v1/customer/[controller]")]
|
||||||
|
[Authorize(Roles = "Customer")]
|
||||||
|
public class CartController : ControllerBase // <--- WICHTIG: Muss public sein und erben
|
||||||
|
{
|
||||||
|
private readonly ICartService _cartService;
|
||||||
|
|
||||||
|
public CartController(ICartService cartService)
|
||||||
|
{
|
||||||
|
_cartService = cartService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(CartDto), StatusCodes.Status200OK)]
|
||||||
|
public async Task<IActionResult> GetCart()
|
||||||
|
{
|
||||||
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
var result = await _cartService.GetCartAsync(userId!);
|
||||||
|
return Ok(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("items")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
|
||||||
|
public async Task<IActionResult> AddToCart([FromBody] CartItemDto item)
|
||||||
|
{
|
||||||
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
var result = await _cartService.AddToCartAsync(userId!, item);
|
||||||
|
return result.Type == ServiceResultType.Success ? Ok() : BadRequest(new { Message = result.ErrorMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("items/{productId}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
public async Task<IActionResult> RemoveItem(Guid productId)
|
||||||
|
{
|
||||||
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
await _cartService.RemoveFromCartAsync(userId!, productId);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ using System.Security.Claims;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Webshop.Application;
|
using Webshop.Application;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Webshop.Application.Services.Customers;
|
||||||
|
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||||
|
|
||||||
namespace Webshop.Api.Controllers.Customer
|
namespace Webshop.Api.Controllers.Customer
|
||||||
{
|
{
|
||||||
@@ -18,25 +20,34 @@ namespace Webshop.Api.Controllers.Customer
|
|||||||
public class CheckoutController : ControllerBase
|
public class CheckoutController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly ICheckoutService _checkoutService;
|
private readonly ICheckoutService _checkoutService;
|
||||||
|
private readonly ICartService _cartService;
|
||||||
|
|
||||||
public CheckoutController(ICheckoutService checkoutService)
|
public CheckoutController(ICheckoutService checkoutService , ICartService cartService)
|
||||||
{
|
{
|
||||||
_checkoutService = checkoutService;
|
_checkoutService = checkoutService;
|
||||||
|
_cartService = cartService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NEU: Endpoint um verf<72>gbare Versandmethoden basierend auf dem Warenkorb zu holen ---
|
[HttpGet("available-shipping-methods")] // War vorher POST
|
||||||
[HttpPost("available-shipping-methods")]
|
|
||||||
[ProducesResponseType(typeof(IEnumerable<ShippingMethodDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(IEnumerable<ShippingMethodDto>), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
public async Task<IActionResult> GetAvailableShippingMethods()
|
||||||
public async Task<IActionResult> GetAvailableShippingMethods([FromBody] ShippingCalculationRequestDto request)
|
|
||||||
{
|
{
|
||||||
var result = await _checkoutService.GetCompatibleShippingMethodsAsync(request.Items);
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
|
||||||
|
// 1. Warenkorb laden
|
||||||
|
var cartResult = await _cartService.GetCartAsync(userId!);
|
||||||
|
if (cartResult.Value == null || !cartResult.Value.Items.Any())
|
||||||
|
{
|
||||||
|
return Ok(new List<ShippingMethodDto>()); // Leerer Korb -> keine Methoden
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Berechnung aufrufen (nutzt die Overload Methode mit List<CartItemDto>)
|
||||||
|
var result = await _checkoutService.GetCompatibleShippingMethodsAsync(cartResult.Value.Items);
|
||||||
|
|
||||||
return result.Type switch
|
return result.Type switch
|
||||||
{
|
{
|
||||||
ServiceResultType.Success => Ok(result.Value),
|
ServiceResultType.Success => Ok(result.Value),
|
||||||
ServiceResultType.InvalidInput => BadRequest(new { Message = result.ErrorMessage }),
|
_ => StatusCode(StatusCodes.Status500InternalServerError, new { Message = "Fehler." })
|
||||||
_ => StatusCode(StatusCodes.Status500InternalServerError, new { Message = "Fehler beim Laden der Versandmethoden." })
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
Webshop.Application/DTOs/Customers/CartDto.cs
Normal file
16
Webshop.Application/DTOs/Customers/CartDto.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Webshop.Application.DTOs.Shipping; // Wichtig für CartItemDto
|
||||||
|
|
||||||
|
namespace Webshop.Application.DTOs.Customers
|
||||||
|
{
|
||||||
|
public class CartDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string UserId { get; set; } = string.Empty;
|
||||||
|
public List<CartItemDto> Items { get; set; } = new List<CartItemDto>();
|
||||||
|
|
||||||
|
// Optional: Gesamtsumme zur Anzeige im Frontend
|
||||||
|
public decimal TotalPrice { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,27 @@
|
|||||||
// src/Webshop.Application/DTOs/Orders/CreateOrderDto.cs
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Webshop.Application.DTOs.Orders
|
namespace Webshop.Application.DTOs.Orders
|
||||||
{
|
{
|
||||||
public class CreateOrderDto
|
public class CreateOrderDto
|
||||||
{
|
{
|
||||||
public Guid? CustomerId { get; set; } // Nullable für Gastbestellung
|
// Items Liste wurde ENTFERNT!
|
||||||
public string? GuestEmail { get; set; }
|
|
||||||
public string? GuestPhoneNumber { get; set; }
|
[Required]
|
||||||
public Guid ShippingAddressId { get; set; }
|
public Guid ShippingAddressId { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
public Guid BillingAddressId { get; set; }
|
public Guid BillingAddressId { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
public Guid PaymentMethodId { get; set; }
|
public Guid PaymentMethodId { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
public Guid ShippingMethodId { get; set; }
|
public Guid ShippingMethodId { get; set; }
|
||||||
|
|
||||||
public string? CouponCode { get; set; } // << NEU >>
|
public string? CouponCode { get; set; }
|
||||||
|
|
||||||
public List<CreateOrderItemDto> Items { get; set; } = new List<CreateOrderItemDto>();
|
// Optional: Falls du Gastbestellungen ohne vorherigen DB-Cart erlauben willst,
|
||||||
|
// bräuchtest du eine separate Logik. Wir gehen hier vom Standard User-Flow aus.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,9 @@ namespace Webshop.Application.Services.Admin
|
|||||||
}
|
}
|
||||||
|
|
||||||
string? imageUrl = null;
|
string? imageUrl = null;
|
||||||
if (categorieDto.ImageFile != null)
|
|
||||||
|
// FIX: Pr<50>fung auf Length > 0 hinzugef<65>gt, wie im ProductService
|
||||||
|
if (categorieDto.ImageFile != null && categorieDto.ImageFile.Length > 0)
|
||||||
{
|
{
|
||||||
await using var stream = categorieDto.ImageFile.OpenReadStream();
|
await using var stream = categorieDto.ImageFile.OpenReadStream();
|
||||||
imageUrl = await _fileStorageService.SaveFileAsync(stream, categorieDto.ImageFile.FileName, categorieDto.ImageFile.ContentType);
|
imageUrl = await _fileStorageService.SaveFileAsync(stream, categorieDto.ImageFile.FileName, categorieDto.ImageFile.ContentType);
|
||||||
@@ -84,23 +86,41 @@ namespace Webshop.Application.Services.Admin
|
|||||||
return ServiceResult.Fail(ServiceResultType.Conflict, "Eine andere Kategorie mit diesem Slug existiert bereits.");
|
return ServiceResult.Fail(ServiceResultType.Conflict, "Eine andere Kategorie mit diesem Slug existiert bereits.");
|
||||||
}
|
}
|
||||||
|
|
||||||
string? imageUrl = existing.ImageUrl;
|
// -----------------------------------------------------------------------
|
||||||
if (categorieDto.ImageFile != null)
|
// BILD UPDATE LOGIK (Verbessert)
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// 1. Neues Bild hochladen, falls vorhanden
|
||||||
|
if (categorieDto.ImageFile != null && categorieDto.ImageFile.Length > 0)
|
||||||
{
|
{
|
||||||
await using var stream = categorieDto.ImageFile.OpenReadStream();
|
await using var stream = categorieDto.ImageFile.OpenReadStream();
|
||||||
imageUrl = await _fileStorageService.SaveFileAsync(stream, categorieDto.ImageFile.FileName, categorieDto.ImageFile.ContentType);
|
var newUrl = await _fileStorageService.SaveFileAsync(stream, categorieDto.ImageFile.FileName, categorieDto.ImageFile.ContentType);
|
||||||
}
|
|
||||||
else if (string.IsNullOrEmpty(categorieDto.ImageUrl) && !string.IsNullOrEmpty(existing.ImageUrl))
|
|
||||||
{
|
|
||||||
// Hier k<>nnte Logik zum L<>schen der alten Datei stehen, falls gew<65>nscht
|
|
||||||
imageUrl = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Bestehendes Bild <20>berschreiben
|
||||||
|
existing.ImageUrl = newUrl;
|
||||||
|
}
|
||||||
|
// 2. Optional: Logik zum L<>schen des Bildes
|
||||||
|
// ACHTUNG: Die vorherige Logik hat das Bild oft versehentlich gel<65>scht, wenn das DTO
|
||||||
|
// das Feld 'ImageUrl' leer hatte (was bei Datei-Uploads oft passiert).
|
||||||
|
// Wir l<>schen nur, wenn KEIN neues File da ist UND explizit ein leerer String <20>bergeben wurde,
|
||||||
|
// aber wir m<>ssen vorsichtig sein, dass null (nicht gesendet) das Bild nicht l<>scht.
|
||||||
|
// Im Zweifel: Wenn du das Bild l<>schen willst, br<62>uchtest du ein explizites Flag (z.B. bool DeleteImage).
|
||||||
|
// Hier belassen wir es dabei: Wenn kein neues Bild kommt, bleibt das alte erhalten.
|
||||||
|
|
||||||
|
// Falls du explizit L<>schen unterst<73>tzen willst, wenn ImageUrl leer ist, nutze dies mit Vorsicht:
|
||||||
|
/*
|
||||||
|
else if (categorieDto.ImageUrl != null && string.IsNullOrEmpty(categorieDto.ImageUrl))
|
||||||
|
{
|
||||||
|
// Nur l<>schen, wenn ImageUrl explizit als leerer String (nicht null) gesendet wurde
|
||||||
|
existing.ImageUrl = null;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Mapping der restlichen Felder
|
||||||
existing.Name = categorieDto.Name;
|
existing.Name = categorieDto.Name;
|
||||||
existing.Slug = categorieDto.Slug;
|
existing.Slug = categorieDto.Slug;
|
||||||
existing.Description = categorieDto.Description;
|
existing.Description = categorieDto.Description;
|
||||||
existing.ParentcategorieId = categorieDto.ParentcategorieId;
|
existing.ParentcategorieId = categorieDto.ParentcategorieId;
|
||||||
existing.ImageUrl = imageUrl;
|
|
||||||
existing.IsActive = categorieDto.IsActive;
|
existing.IsActive = categorieDto.IsActive;
|
||||||
existing.DisplayOrder = categorieDto.DisplayOrder;
|
existing.DisplayOrder = categorieDto.DisplayOrder;
|
||||||
existing.LastModifiedDate = DateTimeOffset.UtcNow;
|
existing.LastModifiedDate = DateTimeOffset.UtcNow;
|
||||||
@@ -124,8 +144,6 @@ namespace Webshop.Application.Services.Admin
|
|||||||
return ServiceResult.Fail(ServiceResultType.Conflict, "Kategorie kann nicht gel<65>scht werden, da sie als <20>bergeordnete Kategorie f<>r andere Kategorien dient.");
|
return ServiceResult.Fail(ServiceResultType.Conflict, "Kategorie kann nicht gel<65>scht werden, da sie als <20>bergeordnete Kategorie f<>r andere Kategorien dient.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hier k<>nnte man auch pr<70>fen, ob Produkte dieser Kategorie zugeordnet sind.
|
|
||||||
|
|
||||||
await _categorieRepository.DeleteAsync(id);
|
await _categorieRepository.DeleteAsync(id);
|
||||||
return ServiceResult.Ok();
|
return ServiceResult.Ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Webshop.Application;
|
||||||
|
using Webshop.Application.DTOs.Customers; // <--- DIESE ZEILE HAT GEFEHLT (für CartDto)
|
||||||
|
using Webshop.Application.DTOs.Shipping; // (für CartItemDto)
|
||||||
using Webshop.Application.Services.Customers.Interfaces;
|
using Webshop.Application.Services.Customers.Interfaces;
|
||||||
|
using Webshop.Domain.Entities;
|
||||||
using Webshop.Infrastructure.Data;
|
using Webshop.Infrastructure.Data;
|
||||||
|
|
||||||
namespace Webshop.Application.Services.Customers
|
namespace Webshop.Application.Services.Customers
|
||||||
@@ -15,24 +20,95 @@ namespace Webshop.Application.Services.Customers
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Die Implementierung muss exakt so aussehen:
|
||||||
|
public async Task<ServiceResult<CartDto>> GetCartAsync(string userId)
|
||||||
|
{
|
||||||
|
var cart = await GetCartEntity(userId);
|
||||||
|
|
||||||
|
// Wenn kein Warenkorb da ist, geben wir einen leeren zurück (Success)
|
||||||
|
if (cart == null)
|
||||||
|
{
|
||||||
|
return ServiceResult.Ok(new CartDto { UserId = userId });
|
||||||
|
}
|
||||||
|
|
||||||
|
var dto = new CartDto
|
||||||
|
{
|
||||||
|
Id = cart.Id,
|
||||||
|
UserId = userId,
|
||||||
|
Items = cart.Items.Select(i => new CartItemDto
|
||||||
|
{
|
||||||
|
ProductId = i.ProductId,
|
||||||
|
ProductVariantId = i.ProductVariantId,
|
||||||
|
Quantity = i.Quantity
|
||||||
|
}).ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
return ServiceResult.Ok(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ServiceResult> AddToCartAsync(string userId, CartItemDto itemDto)
|
||||||
|
{
|
||||||
|
var cart = await GetCartEntity(userId);
|
||||||
|
if (cart == null)
|
||||||
|
{
|
||||||
|
cart = new Cart { UserId = userId, Id = Guid.NewGuid() };
|
||||||
|
_context.Carts.Add(cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingItem = cart.Items.FirstOrDefault(i => i.ProductId == itemDto.ProductId && i.ProductVariantId == itemDto.ProductVariantId);
|
||||||
|
|
||||||
|
if (existingItem != null)
|
||||||
|
{
|
||||||
|
existingItem.Quantity += itemDto.Quantity;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var newItem = new CartItem
|
||||||
|
{
|
||||||
|
CartId = cart.Id,
|
||||||
|
ProductId = itemDto.ProductId,
|
||||||
|
ProductVariantId = itemDto.ProductVariantId,
|
||||||
|
Quantity = itemDto.Quantity
|
||||||
|
};
|
||||||
|
// WICHTIG: Zur Collection hinzufügen UND dem Context Bescheid geben
|
||||||
|
cart.Items.Add(newItem);
|
||||||
|
_context.CartItems.Add(newItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
cart.LastModified = DateTime.UtcNow;
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return ServiceResult.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ServiceResult> RemoveFromCartAsync(string userId, Guid productId)
|
||||||
|
{
|
||||||
|
var cart = await GetCartEntity(userId);
|
||||||
|
if (cart == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Warenkorb leer.");
|
||||||
|
|
||||||
|
var item = cart.Items.FirstOrDefault(i => i.ProductId == productId);
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
_context.CartItems.Remove(item);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
return ServiceResult.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ClearCartAsync(string userId)
|
public async Task ClearCartAsync(string userId)
|
||||||
{
|
{
|
||||||
// Wir suchen den Warenkorb des Users
|
var cart = await GetCartEntity(userId);
|
||||||
// Annahme: Es gibt eine 'Carts' Tabelle und 'CartItems'
|
|
||||||
var cart = await _context.Carts
|
|
||||||
.Include(c => c.Items)
|
|
||||||
.FirstOrDefaultAsync(c => c.UserId == userId);
|
|
||||||
|
|
||||||
if (cart != null && cart.Items.Any())
|
if (cart != null && cart.Items.Any())
|
||||||
{
|
{
|
||||||
// Option A: Nur Items löschen, Warenkorb behalten
|
|
||||||
_context.CartItems.RemoveRange(cart.Items);
|
_context.CartItems.RemoveRange(cart.Items);
|
||||||
|
|
||||||
// Option B: Ganzen Warenkorb löschen (dann wird er beim nächsten AddToCart neu erstellt)
|
|
||||||
// _context.Carts.Remove(cart);
|
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<Cart?> GetCartEntity(string userId)
|
||||||
|
{
|
||||||
|
return await _context.Carts
|
||||||
|
.Include(c => c.Items)
|
||||||
|
.FirstOrDefaultAsync(c => c.UserId == userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,46 +117,54 @@ namespace Webshop.Application.Services.Customers
|
|||||||
// 1.1 Kunde Validieren
|
// 1.1 Kunde Validieren
|
||||||
var customer = await _customerRepository.GetByUserIdAsync(userId!);
|
var customer = await _customerRepository.GetByUserIdAsync(userId!);
|
||||||
if (customer == null)
|
if (customer == null)
|
||||||
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Unauthorized, "Kundenprofil nicht gefunden.");
|
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Unauthorized, "Kundenprofil fehlt.");
|
||||||
|
|
||||||
// =================================================================================
|
// E-Mail Logik
|
||||||
// FIX: E-Mail holen (Passend zu deiner Entity Customer.cs)
|
|
||||||
// Customer hat keine Email, aber eine AspNetUserId. Wir holen die Mail aus der Users Tabelle.
|
|
||||||
// =================================================================================
|
|
||||||
var userEmail = await _context.Users
|
var userEmail = await _context.Users
|
||||||
.Where(u => u.Id == userId) // userId entspricht customer.AspNetUserId
|
.Where(u => u.Id == userId)
|
||||||
.Select(u => u.Email)
|
.Select(u => u.Email)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(userEmail))
|
if (string.IsNullOrEmpty(userEmail))
|
||||||
|
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Failure, "Keine E-Mail gefunden.");
|
||||||
|
|
||||||
|
// =================================================================================
|
||||||
|
// 1.2 Warenkorb aus der Datenbank laden (Single Source of Truth)
|
||||||
|
// =================================================================================
|
||||||
|
var cart = await _context.Carts
|
||||||
|
.Include(c => c.Items)
|
||||||
|
.FirstOrDefaultAsync(c => c.UserId == userId);
|
||||||
|
|
||||||
|
if (cart == null || !cart.Items.Any())
|
||||||
{
|
{
|
||||||
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Failure, "Keine E-Mail-Adresse für den Benutzer gefunden.");
|
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.InvalidInput, "Ihr Warenkorb ist leer. Bitte fügen Sie Produkte hinzu.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.2 Adressen Validieren
|
// 1.3 Adressen Validieren
|
||||||
|
// Wir laden beide Adressen und stellen sicher, dass sie dem Kunden gehören
|
||||||
var addresses = await _context.Addresses
|
var addresses = await _context.Addresses
|
||||||
.Where(a => (a.Id == orderDto.ShippingAddressId || a.Id == orderDto.BillingAddressId) && a.CustomerId == customer.Id)
|
.Where(a => (a.Id == orderDto.ShippingAddressId || a.Id == orderDto.BillingAddressId) && a.CustomerId == customer.Id)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
if (!addresses.Any(a => a.Id == orderDto.ShippingAddressId) || !addresses.Any(a => a.Id == orderDto.BillingAddressId))
|
if (!addresses.Any(a => a.Id == orderDto.ShippingAddressId) || !addresses.Any(a => a.Id == orderDto.BillingAddressId))
|
||||||
{
|
{
|
||||||
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Forbidden, "Die angegebenen Adressen sind ungültig oder gehören nicht zu diesem Konto.");
|
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Forbidden, "Die angegebenen Adressen sind ungültig oder gehören nicht zu diesem Konto.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.3 Methoden Validieren
|
// 1.4 Methoden Validieren
|
||||||
var shippingMethod = await _shippingMethodRepository.GetByIdAsync(orderDto.ShippingMethodId);
|
var shippingMethod = await _shippingMethodRepository.GetByIdAsync(orderDto.ShippingMethodId);
|
||||||
if (shippingMethod == null || !shippingMethod.IsActive)
|
if (shippingMethod == null || !shippingMethod.IsActive)
|
||||||
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.InvalidInput, "Versandmethode ungültig.");
|
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.InvalidInput, "Versandart ungültig.");
|
||||||
|
|
||||||
var paymentMethod = await _context.PaymentMethods.FindAsync(orderDto.PaymentMethodId);
|
var paymentMethod = await _context.PaymentMethods.FindAsync(orderDto.PaymentMethodId);
|
||||||
if (paymentMethod == null || !paymentMethod.IsActive)
|
if (paymentMethod == null || !paymentMethod.IsActive)
|
||||||
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.InvalidInput, "Zahlungsmethode ungültig.");
|
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.InvalidInput, "Zahlungsart ungültig.");
|
||||||
|
|
||||||
// 1.4 Produkte Validieren
|
// =================================================================================
|
||||||
if (orderDto.Items == null || !orderDto.Items.Any())
|
// 1.5 Produkte Validieren & Vorbereiten (BASIEREND AUF CART ITEMS)
|
||||||
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.InvalidInput, "Warenkorb ist leer.");
|
// =================================================================================
|
||||||
|
|
||||||
var productIds = orderDto.Items.Select(i => i.ProductId).Distinct().ToList();
|
var productIds = cart.Items.Select(i => i.ProductId).Distinct().ToList();
|
||||||
var products = await _context.Products.Where(p => productIds.Contains(p.Id)).ToListAsync();
|
var products = await _context.Products.Where(p => productIds.Contains(p.Id)).ToListAsync();
|
||||||
|
|
||||||
var validationErrors = new StringBuilder();
|
var validationErrors = new StringBuilder();
|
||||||
@@ -164,48 +172,69 @@ namespace Webshop.Application.Services.Customers
|
|||||||
decimal itemsTotal = 0;
|
decimal itemsTotal = 0;
|
||||||
var preparedOrderItems = new List<OrderItem>();
|
var preparedOrderItems = new List<OrderItem>();
|
||||||
|
|
||||||
foreach (var itemDto in orderDto.Items)
|
foreach (var cartItem in cart.Items)
|
||||||
{
|
{
|
||||||
var product = products.FirstOrDefault(p => p.Id == itemDto.ProductId);
|
var product = products.FirstOrDefault(p => p.Id == cartItem.ProductId);
|
||||||
|
|
||||||
if (product == null) { validationErrors.AppendLine($"- Produkt {itemDto.ProductId} fehlt."); continue; }
|
if (product == null)
|
||||||
if (!product.IsActive) validationErrors.AppendLine($"- '{product.Name}' ist nicht verfügbar.");
|
{
|
||||||
if (product.StockQuantity < itemDto.Quantity) validationErrors.AppendLine($"- '{product.Name}': Zu wenig Bestand.");
|
validationErrors.AppendLine($"- Ein Produkt im Warenkorb (ID {cartItem.ProductId}) existiert nicht mehr.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!product.IsActive)
|
||||||
|
{
|
||||||
|
validationErrors.AppendLine($"- '{product.Name}' ist derzeit nicht verfügbar.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfung Menge
|
||||||
|
if (product.StockQuantity < cartItem.Quantity)
|
||||||
|
{
|
||||||
|
validationErrors.AppendLine($"- '{product.Name}': Nicht genügend Bestand (Verfügbar: {product.StockQuantity}, im Korb: {cartItem.Quantity}).");
|
||||||
|
}
|
||||||
|
|
||||||
if (validationErrors.Length == 0)
|
if (validationErrors.Length == 0)
|
||||||
{
|
{
|
||||||
totalOrderWeight += (decimal)(product.Weight ?? 0) * itemDto.Quantity;
|
// Gewicht berechnen (Fallback auf 0 falls null)
|
||||||
|
totalOrderWeight += (decimal)(product.Weight ?? 0) * cartItem.Quantity;
|
||||||
|
|
||||||
var orderItem = new OrderItem
|
var orderItem = new OrderItem
|
||||||
{
|
{
|
||||||
ProductId = product.Id,
|
ProductId = product.Id,
|
||||||
ProductVariantId = itemDto.ProductVariantId,
|
ProductVariantId = cartItem.ProductVariantId,
|
||||||
ProductName = product.Name,
|
ProductName = product.Name,
|
||||||
ProductSKU = product.SKU,
|
ProductSKU = product.SKU,
|
||||||
Quantity = itemDto.Quantity,
|
Quantity = cartItem.Quantity,
|
||||||
UnitPrice = product.Price,
|
UnitPrice = product.Price, // Preis IMMER frisch aus der DB nehmen!
|
||||||
TotalPrice = product.Price * itemDto.Quantity
|
TotalPrice = product.Price * cartItem.Quantity
|
||||||
};
|
};
|
||||||
preparedOrderItems.Add(orderItem);
|
preparedOrderItems.Add(orderItem);
|
||||||
itemsTotal += orderItem.TotalPrice;
|
itemsTotal += orderItem.TotalPrice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Abbruch bei Produktfehlern
|
||||||
if (validationErrors.Length > 0)
|
if (validationErrors.Length > 0)
|
||||||
{
|
{
|
||||||
await transaction.RollbackAsync();
|
await transaction.RollbackAsync();
|
||||||
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Conflict, $"Fehler:\n{validationErrors}");
|
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Conflict, $"Bestellung nicht möglich:\n{validationErrors}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1.6 Gewichtsprüfung
|
||||||
if (totalOrderWeight < shippingMethod.MinWeight || totalOrderWeight > shippingMethod.MaxWeight)
|
if (totalOrderWeight < shippingMethod.MinWeight || totalOrderWeight > shippingMethod.MaxWeight)
|
||||||
{
|
{
|
||||||
await transaction.RollbackAsync();
|
await transaction.RollbackAsync();
|
||||||
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.InvalidInput, $"Gesamtgewicht ({totalOrderWeight} kg) passt nicht zur Versandart.");
|
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.InvalidInput, $"Gesamtgewicht ({totalOrderWeight} kg) passt nicht zur Versandart '{shippingMethod.Name}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Preise & Rabatte
|
// =================================================================================
|
||||||
|
// PHASE 2: BERECHNUNG (Finanzen)
|
||||||
|
// =================================================================================
|
||||||
|
|
||||||
decimal discountAmount = 0;
|
decimal discountAmount = 0;
|
||||||
var discountResult = new DiscountCalculationResult();
|
var discountResult = new DiscountCalculationResult();
|
||||||
|
|
||||||
|
// Rabatt berechnen
|
||||||
if (!string.IsNullOrWhiteSpace(orderDto.CouponCode))
|
if (!string.IsNullOrWhiteSpace(orderDto.CouponCode))
|
||||||
{
|
{
|
||||||
discountResult = await _discountService.CalculateDiscountAsync(preparedOrderItems, orderDto.CouponCode);
|
discountResult = await _discountService.CalculateDiscountAsync(preparedOrderItems, orderDto.CouponCode);
|
||||||
@@ -218,27 +247,42 @@ namespace Webshop.Application.Services.Customers
|
|||||||
discountAmount = discountResult.TotalDiscountAmount;
|
discountAmount = discountResult.TotalDiscountAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gesamtsummen berechnen
|
||||||
decimal shippingCost = shippingMethod.BaseCost;
|
decimal shippingCost = shippingMethod.BaseCost;
|
||||||
decimal subTotalBeforeDiscount = itemsTotal + shippingCost;
|
decimal subTotalBeforeDiscount = itemsTotal + shippingCost;
|
||||||
if (discountAmount > subTotalBeforeDiscount) discountAmount = subTotalBeforeDiscount;
|
|
||||||
|
|
||||||
decimal subTotalAfterDiscount = subTotalBeforeDiscount - discountAmount;
|
// Rabatt darf nicht höher als die Summe sein
|
||||||
decimal taxRate = await _settingService.GetSettingValueAsync<decimal>("GlobalTaxRate", 0.19m);
|
if (discountAmount > subTotalBeforeDiscount)
|
||||||
decimal taxAmount = subTotalAfterDiscount * taxRate; // Steuer auf reduzierten Betrag
|
|
||||||
decimal orderTotal = subTotalAfterDiscount + taxAmount;
|
|
||||||
|
|
||||||
// 3. Ausführung (Execution)
|
|
||||||
foreach (var itemDto in orderDto.Items)
|
|
||||||
{
|
{
|
||||||
var product = products.First(p => p.Id == itemDto.ProductId);
|
discountAmount = subTotalBeforeDiscount;
|
||||||
if (product.StockQuantity < itemDto.Quantity)
|
|
||||||
{
|
|
||||||
await transaction.RollbackAsync();
|
|
||||||
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Conflict, "Lagerbestand hat sich während des Vorgangs geändert.");
|
|
||||||
}
|
|
||||||
product.StockQuantity -= itemDto.Quantity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decimal subTotalAfterDiscount = subTotalBeforeDiscount - discountAmount;
|
||||||
|
|
||||||
|
// Steuer berechnen (Settings abrufen)
|
||||||
|
decimal taxRate = await _settingService.GetSettingValueAsync<decimal>("GlobalTaxRate", 0.19m);
|
||||||
|
decimal taxAmount = subTotalAfterDiscount * taxRate;
|
||||||
|
|
||||||
|
decimal orderTotal = subTotalAfterDiscount + taxAmount;
|
||||||
|
|
||||||
|
// =================================================================================
|
||||||
|
// PHASE 3: EXECUTION (Schreiben in DB)
|
||||||
|
// =================================================================================
|
||||||
|
|
||||||
|
// 3.1 Lagerbestand reduzieren
|
||||||
|
foreach (var oi in preparedOrderItems)
|
||||||
|
{
|
||||||
|
var product = products.First(p => p.Id == oi.ProductId);
|
||||||
|
// Letzte Sicherheitsprüfung (Concurrency)
|
||||||
|
if (product.StockQuantity < oi.Quantity)
|
||||||
|
{
|
||||||
|
await transaction.RollbackAsync();
|
||||||
|
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Conflict, $"Lagerbestand für '{product.Name}' hat sich während des Vorgangs geändert.");
|
||||||
|
}
|
||||||
|
product.StockQuantity -= oi.Quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.2 Order erstellen
|
||||||
var newOrder = new Order
|
var newOrder = new Order
|
||||||
{
|
{
|
||||||
CustomerId = customer.Id,
|
CustomerId = customer.Id,
|
||||||
@@ -260,6 +304,7 @@ namespace Webshop.Application.Services.Customers
|
|||||||
|
|
||||||
await _orderRepository.AddAsync(newOrder);
|
await _orderRepository.AddAsync(newOrder);
|
||||||
|
|
||||||
|
// 3.3 Discount Nutzung erhöhen
|
||||||
if (discountResult.AppliedDiscountIds.Any())
|
if (discountResult.AppliedDiscountIds.Any())
|
||||||
{
|
{
|
||||||
var appliedDiscounts = await _context.Discounts
|
var appliedDiscounts = await _context.Discounts
|
||||||
@@ -268,6 +313,7 @@ namespace Webshop.Application.Services.Customers
|
|||||||
foreach (var d in appliedDiscounts) d.CurrentUsageCount++;
|
foreach (var d in appliedDiscounts) d.CurrentUsageCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3.4 Speichern & Commit
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
@@ -276,18 +322,30 @@ namespace Webshop.Application.Services.Customers
|
|||||||
catch (DbUpdateConcurrencyException ex)
|
catch (DbUpdateConcurrencyException ex)
|
||||||
{
|
{
|
||||||
await transaction.RollbackAsync();
|
await transaction.RollbackAsync();
|
||||||
_logger.LogWarning(ex, "Concurrency Konflikt bei Bestellung.");
|
_logger.LogWarning(ex, "Concurrency Fehler beim Checkout (User {UserId}).", userId);
|
||||||
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Conflict, "Artikel wurden soeben von einem anderen Kunden gekauft.");
|
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Conflict, "Ein oder mehrere Artikel wurden soeben von einem anderen Kunden gekauft.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Post-Processing (Warenkorb & Email)
|
// =================================================================================
|
||||||
try { await _cartService.ClearCartAsync(userId!); }
|
// PHASE 4: CLEANUP & POST-PROCESSING
|
||||||
catch (Exception ex) { _logger.LogError(ex, "Warenkorb konnte nicht geleert werden."); }
|
// =================================================================================
|
||||||
|
|
||||||
try { await _emailService.SendOrderConfirmationAsync(newOrder.Id, userEmail); }
|
// 4.1 Warenkorb leeren
|
||||||
catch (Exception ex) { _logger.LogError(ex, "Bestätigungs-Email fehlgeschlagen."); }
|
try
|
||||||
|
{
|
||||||
|
await _cartService.ClearCartAsync(userId!);
|
||||||
|
}
|
||||||
|
catch (Exception ex) { _logger.LogError(ex, "Cart Clear Failed for Order {OrderNumber}", newOrder.OrderNumber); }
|
||||||
|
|
||||||
// Manuelles Setzen der Navigation Properties für das Mapping
|
// 4.2 Bestätigungs-Email senden
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _emailService.SendOrderConfirmationAsync(newOrder.Id, userEmail);
|
||||||
|
}
|
||||||
|
catch (Exception ex) { _logger.LogError(ex, "Email Failed for Order {OrderNumber}", newOrder.OrderNumber); }
|
||||||
|
|
||||||
|
// 4.3 Mapping für Rückgabe
|
||||||
|
// Wir setzen die Navigation Properties manuell, um DB-Reload zu sparen
|
||||||
newOrder.PaymentMethodInfo = paymentMethod;
|
newOrder.PaymentMethodInfo = paymentMethod;
|
||||||
newOrder.ShippingAddress = addresses.First(a => a.Id == orderDto.ShippingAddressId);
|
newOrder.ShippingAddress = addresses.First(a => a.Id == orderDto.ShippingAddressId);
|
||||||
newOrder.BillingAddress = addresses.First(a => a.Id == orderDto.BillingAddressId);
|
newOrder.BillingAddress = addresses.First(a => a.Id == orderDto.BillingAddressId);
|
||||||
@@ -297,7 +355,7 @@ namespace Webshop.Application.Services.Customers
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await transaction.RollbackAsync();
|
await transaction.RollbackAsync();
|
||||||
_logger.LogError(ex, "Checkout Exception");
|
_logger.LogError(ex, "Unerwarteter Fehler im Checkout für User {UserId}", userId);
|
||||||
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Failure, "Ein unerwarteter Fehler ist aufgetreten.");
|
return ServiceResult.Fail<OrderDetailDto>(ServiceResultType.Failure, "Ein unerwarteter Fehler ist aufgetreten.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System; // Für Guid
|
||||||
|
using Webshop.Application.DTOs.Customers; // <--- WICHTIG für CartDto
|
||||||
|
using Webshop.Application.DTOs.Shipping;
|
||||||
|
using Webshop.Application;
|
||||||
|
|
||||||
namespace Webshop.Application.Services.Customers.Interfaces
|
namespace Webshop.Application.Services.Customers.Interfaces
|
||||||
{
|
{
|
||||||
public interface ICartService
|
public interface ICartService
|
||||||
{
|
{
|
||||||
|
Task<ServiceResult<CartDto>> GetCartAsync(string userId);
|
||||||
|
Task<ServiceResult> AddToCartAsync(string userId, CartItemDto item);
|
||||||
|
Task<ServiceResult> RemoveFromCartAsync(string userId, Guid productId);
|
||||||
Task ClearCartAsync(string userId);
|
Task ClearCartAsync(string userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1443
Webshop.Infrastructure/Migrations/20251126164411_checkout-cart.Designer.cs
generated
Normal file
1443
Webshop.Infrastructure/Migrations/20251126164411_checkout-cart.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Webshop.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class checkoutcart : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Carts",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
UserId = table.Column<string>(type: "text", nullable: true),
|
||||||
|
SessionId = table.Column<string>(type: "text", nullable: true),
|
||||||
|
LastModified = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Carts", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CartItems",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
CartId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
ProductId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
ProductVariantId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
Quantity = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CartItems", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CartItems_Carts_CartId",
|
||||||
|
column: x => x.CartId,
|
||||||
|
principalTable: "Carts",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CartItems_Products_ProductId",
|
||||||
|
column: x => x.ProductId,
|
||||||
|
principalTable: "Products",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CartItems_CartId",
|
||||||
|
table: "CartItems",
|
||||||
|
column: "CartId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CartItems_ProductId",
|
||||||
|
table: "CartItems",
|
||||||
|
column: "ProductId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CartItems");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Carts");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -215,6 +215,53 @@ namespace Webshop.Infrastructure.Migrations
|
|||||||
b.ToTable("Addresses");
|
b.ToTable("Addresses");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Webshop.Domain.Entities.Cart", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("SessionId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Carts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Webshop.Domain.Entities.CartItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("CartId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("ProductId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("ProductVariantId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CartId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("CartItems");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Webshop.Domain.Entities.Categorie", b =>
|
modelBuilder.Entity("Webshop.Domain.Entities.Categorie", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@@ -1113,6 +1160,25 @@ namespace Webshop.Infrastructure.Migrations
|
|||||||
b.Navigation("Customer");
|
b.Navigation("Customer");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Webshop.Domain.Entities.CartItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Webshop.Domain.Entities.Cart", "Cart")
|
||||||
|
.WithMany("Items")
|
||||||
|
.HasForeignKey("CartId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Webshop.Domain.Entities.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Cart");
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Webshop.Domain.Entities.Categorie", b =>
|
modelBuilder.Entity("Webshop.Domain.Entities.Categorie", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Webshop.Domain.Entities.Categorie", "Parentcategorie")
|
b.HasOne("Webshop.Domain.Entities.Categorie", "Parentcategorie")
|
||||||
@@ -1311,6 +1377,11 @@ namespace Webshop.Infrastructure.Migrations
|
|||||||
b.Navigation("Address");
|
b.Navigation("Address");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Webshop.Domain.Entities.Cart", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Items");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Webshop.Domain.Entities.Categorie", b =>
|
modelBuilder.Entity("Webshop.Domain.Entities.Categorie", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Productcategories");
|
b.Navigation("Productcategories");
|
||||||
|
|||||||
Reference in New Issue
Block a user