From 4321b03735c0f7505271668f713249bdafc4c98f Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Wed, 6 May 2026 10:48:02 +0200 Subject: [PATCH] Implemented shopping carts functionality elements --- .../LiteCharms.Features.csproj | 4 -- .../Commands/AddItemToShoppingCartCommand.cs | 30 ++++++++++++++ .../Commands/CreateShoppingCartCommand.cs | 21 +++++++++- .../Commands/EmptyShoppingCartCommand.cs | 16 ++++++++ .../AddItemToShoppingCartCommandHandler.cs | 40 +++++++++++++++++++ .../CreateShoppingCartCommandHandler.cs | 32 +++++++++++++++ .../EmptyShoppingCartCommandHandler.cs | 32 +++++++++++++++ .../RemoveShoppingCartItemCommandHandler.cs | 36 +++++++++++++++++ .../UpdateShoppingCartItemCommandHandler.cs | 32 +++++++++++++++ .../Commands/RemoveShoppingCartItemCommand.cs | 25 ++++++++++++ .../Commands/UpdateShoppingCartItemCommand.cs | 30 ++++++++++++++ .../Queries/GetShoppingCartItemsQuery.cs | 18 +++++++++ .../GetShoppingCartItemsQueryHandler.cs | 30 ++++++++++++++ 13 files changed, 341 insertions(+), 5 deletions(-) create mode 100644 LiteCharms.Features/ShoppingCarts/Commands/AddItemToShoppingCartCommand.cs create mode 100644 LiteCharms.Features/ShoppingCarts/Commands/EmptyShoppingCartCommand.cs create mode 100644 LiteCharms.Features/ShoppingCarts/Commands/Handlers/AddItemToShoppingCartCommandHandler.cs create mode 100644 LiteCharms.Features/ShoppingCarts/Commands/Handlers/CreateShoppingCartCommandHandler.cs create mode 100644 LiteCharms.Features/ShoppingCarts/Commands/Handlers/EmptyShoppingCartCommandHandler.cs create mode 100644 LiteCharms.Features/ShoppingCarts/Commands/Handlers/RemoveShoppingCartItemCommandHandler.cs create mode 100644 LiteCharms.Features/ShoppingCarts/Commands/Handlers/UpdateShoppingCartItemCommandHandler.cs create mode 100644 LiteCharms.Features/ShoppingCarts/Commands/RemoveShoppingCartItemCommand.cs create mode 100644 LiteCharms.Features/ShoppingCarts/Commands/UpdateShoppingCartItemCommand.cs create mode 100644 LiteCharms.Features/ShoppingCarts/Queries/GetShoppingCartItemsQuery.cs create mode 100644 LiteCharms.Features/ShoppingCarts/Queries/Handlers/GetShoppingCartItemsQueryHandler.cs diff --git a/LiteCharms.Features/LiteCharms.Features.csproj b/LiteCharms.Features/LiteCharms.Features.csproj index 9534619..3eee68a 100644 --- a/LiteCharms.Features/LiteCharms.Features.csproj +++ b/LiteCharms.Features/LiteCharms.Features.csproj @@ -61,8 +61,4 @@ - - - - diff --git a/LiteCharms.Features/ShoppingCarts/Commands/AddItemToShoppingCartCommand.cs b/LiteCharms.Features/ShoppingCarts/Commands/AddItemToShoppingCartCommand.cs new file mode 100644 index 0000000..009fccd --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/AddItemToShoppingCartCommand.cs @@ -0,0 +1,30 @@ +namespace LiteCharms.Features.ShoppingCarts.Commands; + +public class AddItemToShoppingCartCommand : IRequest +{ + public Guid ShoppingCartId { get; set; } + + public Guid ProductPriceId { get; set; } + + public int Quantity { get; set; } + + private AddItemToShoppingCartCommand(Guid shoppingCartId, Guid productPriceId, int quantity = 1) + { + ShoppingCartId = shoppingCartId; + ProductPriceId = productPriceId; + Quantity = quantity; + } + + public static AddItemToShoppingCartCommand Create(Guid shoppingCartId, Guid productPriceId, int quantity = 1) + { + if (shoppingCartId == Guid.Empty) + throw new ArgumentException($"Shopping cart ID is required", nameof(shoppingCartId)); + + if (productPriceId == Guid.Empty) + throw new ArgumentException($"Product item required", nameof(productPriceId)); + + if(quantity <= 0) throw new ArgumentException($"Quantity must be at least 1", nameof(quantity)); + + return new(shoppingCartId, productPriceId, quantity); + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Commands/CreateShoppingCartCommand.cs b/LiteCharms.Features/ShoppingCarts/Commands/CreateShoppingCartCommand.cs index 3c5098e..fdc7b0e 100644 --- a/LiteCharms.Features/ShoppingCarts/Commands/CreateShoppingCartCommand.cs +++ b/LiteCharms.Features/ShoppingCarts/Commands/CreateShoppingCartCommand.cs @@ -1,6 +1,25 @@ namespace LiteCharms.Features.ShoppingCarts.Commands; -public class CreateShoppingCartCommand : IRequest +public class CreateShoppingCartCommand : IRequest> { + public Guid? CustomerId { get; set; } + public Guid? OrderId { get; set; } + + public Guid? QuoteId { get; set; } + + private CreateShoppingCartCommand(Guid customerId, Guid? orderId = null, Guid? quoteId = null) + { + CustomerId = customerId; + OrderId = orderId; + QuoteId = quoteId; + } + + public static CreateShoppingCartCommand Create(Guid customerId, Guid? orderId = null, Guid? quoteId = null) + { + if (customerId == Guid.Empty) + throw new ArgumentException($"Customer ID is required", nameof(customerId)); + + return new(customerId, orderId, quoteId); + } } diff --git a/LiteCharms.Features/ShoppingCarts/Commands/EmptyShoppingCartCommand.cs b/LiteCharms.Features/ShoppingCarts/Commands/EmptyShoppingCartCommand.cs new file mode 100644 index 0000000..f535ca4 --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/EmptyShoppingCartCommand.cs @@ -0,0 +1,16 @@ +namespace LiteCharms.Features.ShoppingCarts.Commands; + +public class EmptyShoppingCartCommand : IRequest +{ + public Guid ShoppingCartId { get; set; } + + private EmptyShoppingCartCommand(Guid shoppingCartId) => ShoppingCartId = shoppingCartId; + + public static EmptyShoppingCartCommand Create(Guid shoppingCartId) + { + if(shoppingCartId == Guid.Empty) + throw new ArgumentException($"Shopping cart ID is required.", nameof(shoppingCartId)); + + return new(shoppingCartId); + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Commands/Handlers/AddItemToShoppingCartCommandHandler.cs b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/AddItemToShoppingCartCommandHandler.cs new file mode 100644 index 0000000..3490517 --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/AddItemToShoppingCartCommandHandler.cs @@ -0,0 +1,40 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.ShoppingCarts.Commands.Handlers; + +public class AddItemToShoppingCartCommandHandler(IDbContextFactory contextFactory) : IRequestHandler +{ + public async ValueTask Handle(AddItemToShoppingCartCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.ProductPrices.AnyAsync(c => c.Id == request.ProductPriceId, cancellationToken)) + return Result.Fail($"Product item could not be found with id {request.ProductPriceId}"); + + var cart = await context.ShoppingCarts.FirstOrDefaultAsync(c => c.Id == request.ShoppingCartId, cancellationToken); + + if (cart is null) + return Result.Fail($"Shopping cart could not be found with id {request.ShoppingCartId}"); + + if (cart.ShoppingCartItems?.Any(i => i.ProductPriceId == request.ProductPriceId) == true) + return Result.Fail($"Item already in shopping cart with id {request.ShoppingCartId}"); + + context.ShoppingCartItems.Add(new Entities.ShoppingCartItem + { + ShoppingCartId = request.ShoppingCartId, + ProductPriceId = request.ProductPriceId, + Quantity = request.Quantity + }); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail($"Failed to add cart item with id {request.ProductPriceId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Commands/Handlers/CreateShoppingCartCommandHandler.cs b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/CreateShoppingCartCommandHandler.cs new file mode 100644 index 0000000..c6d17b0 --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/CreateShoppingCartCommandHandler.cs @@ -0,0 +1,32 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.ShoppingCarts.Commands.Handlers; + +public class CreateShoppingCartCommandHandler(IDbContextFactory contextFactory) : IRequestHandler> +{ + public async ValueTask> Handle(CreateShoppingCartCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.Customers.AnyAsync(c => c.Id == request.CustomerId, cancellationToken)) + return Result.Fail($"Customer could not be found with id {request.CustomerId}"); + + var cart = context.ShoppingCarts.Add(new Entities.ShoppingCart + { + CustomerId = request.CustomerId, + OrderId = request.OrderId, + QuoteId = request.QuoteId + }); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok(cart.Entity.Id) + : Result.Fail($"Failed to create shopping cart for customer id {request.CustomerId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Commands/Handlers/EmptyShoppingCartCommandHandler.cs b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/EmptyShoppingCartCommandHandler.cs new file mode 100644 index 0000000..2787a8c --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/EmptyShoppingCartCommandHandler.cs @@ -0,0 +1,32 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.ShoppingCarts.Commands.Handlers; + +public class EmptyShoppingCartCommandHandler(IDbContextFactory contextFactory) : IRequestHandler +{ + public async ValueTask Handle(EmptyShoppingCartCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.ShoppingCarts.AnyAsync(c => c.Id == request.ShoppingCartId, cancellationToken)) + return Result.Fail($"Shopping could not be found with id {request.ShoppingCartId}"); + + if (await context.ShoppingCartItems.CountAsync(i => i.ShoppingCartId == request.ShoppingCartId, cancellationToken) == 0) + return Result.Ok(); + + var cartItems = await context.ShoppingCartItems.Where(i => i.ShoppingCartId == request.ShoppingCartId).ToListAsync(cancellationToken); + + context.RemoveRange(cartItems); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail($"Could not empty cart with id {request.ShoppingCartId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Commands/Handlers/RemoveShoppingCartItemCommandHandler.cs b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/RemoveShoppingCartItemCommandHandler.cs new file mode 100644 index 0000000..1d7944d --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/RemoveShoppingCartItemCommandHandler.cs @@ -0,0 +1,36 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.ShoppingCarts.Commands.Handlers; + +public class RemoveShoppingCartItemCommandHandler(IDbContextFactory contextFactory) : IRequestHandler +{ + public async ValueTask Handle(RemoveShoppingCartItemCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.ProductPrices.AnyAsync(c => c.Id == request.ShoppingCartItemId, cancellationToken)) + return Result.Fail($"Product item could not be found with id {request.ShoppingCartItemId}"); + + var cart = await context.ShoppingCarts.FirstOrDefaultAsync(c => c.Id == request.ShoppingCartId, cancellationToken); + + if (cart is null) + return Result.Fail($"Shopping cart item could not be found with id {request.ShoppingCartId}"); + + var item = await context.ShoppingCartItems.FirstOrDefaultAsync(i => i.Id == request.ShoppingCartItemId, cancellationToken); + + if (item is null) return Result.Ok(); + + context.ShoppingCartItems.Remove(item); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail($"Failed to remove shopping cart item with id {request.ShoppingCartItemId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Commands/Handlers/UpdateShoppingCartItemCommandHandler.cs b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/UpdateShoppingCartItemCommandHandler.cs new file mode 100644 index 0000000..b1880ec --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/UpdateShoppingCartItemCommandHandler.cs @@ -0,0 +1,32 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.ShoppingCarts.Commands.Handlers; + +public class UpdateShoppingCartItemCommandHandler(IDbContextFactory contextFactory) : IRequestHandler +{ + public async ValueTask Handle(UpdateShoppingCartItemCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.ShoppingCarts.AnyAsync(c => c.Id == request.ShoppingCartId, cancellationToken)) + return Result.Fail($"Shopping could not be found with id {request.ShoppingCartId}"); + + var item = await context.ShoppingCartItems.FirstOrDefaultAsync(i => i.ShoppingCartId == request.ShoppingCartId, cancellationToken); + + if(item is null) + return Result.Fail($"Shopping cart item could not be found with id {request.ShoppingCartItemId}"); + + item.Quantity = request.Quantity; + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail($"Failed to update cart item quntity"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Commands/RemoveShoppingCartItemCommand.cs b/LiteCharms.Features/ShoppingCarts/Commands/RemoveShoppingCartItemCommand.cs new file mode 100644 index 0000000..26706d8 --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/RemoveShoppingCartItemCommand.cs @@ -0,0 +1,25 @@ +namespace LiteCharms.Features.ShoppingCarts.Commands; + +public class RemoveShoppingCartItemCommand : IRequest +{ + public Guid ShoppingCartId { get; set; } + + public Guid ShoppingCartItemId { get; set; } + + private RemoveShoppingCartItemCommand(Guid shoppingCartId, Guid shoppingCartItemId) + { + ShoppingCartId = shoppingCartId; + ShoppingCartItemId = shoppingCartItemId; + } + + public static RemoveShoppingCartItemCommand Create(Guid shoppingCartId, Guid shoppingCartItemId) + { + if (shoppingCartId == Guid.Empty) + throw new ArgumentException($"Shopping cart ID is required", nameof(shoppingCartId)); + + if (shoppingCartItemId == Guid.Empty) + throw new ArgumentException($"Shopping cart item required", nameof(shoppingCartItemId)); + + return new(shoppingCartId, shoppingCartItemId); + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Commands/UpdateShoppingCartItemCommand.cs b/LiteCharms.Features/ShoppingCarts/Commands/UpdateShoppingCartItemCommand.cs new file mode 100644 index 0000000..d7cebe0 --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/UpdateShoppingCartItemCommand.cs @@ -0,0 +1,30 @@ +namespace LiteCharms.Features.ShoppingCarts.Commands; + +public class UpdateShoppingCartItemCommand : IRequest +{ + public Guid ShoppingCartId { get; set; } + + public Guid ShoppingCartItemId { get; set; } + + public int Quantity { get; set; } + + private UpdateShoppingCartItemCommand(Guid shoppingCartId, Guid shoppingCartItemId, int quantity = 1) + { + ShoppingCartId = shoppingCartId; + ShoppingCartItemId = shoppingCartItemId; + Quantity = quantity; + } + + public static UpdateShoppingCartItemCommand Create(Guid shoppingCartId, Guid shoppingCartItemId, int quantity = 1) + { + if (shoppingCartId == Guid.Empty) + throw new ArgumentException($"Shopping cart ID is required", nameof(shoppingCartId)); + + if (shoppingCartItemId == Guid.Empty) + throw new ArgumentException($"Shopping cart item is required", nameof(shoppingCartItemId)); + + if (quantity <= 0) throw new ArgumentException($"Quantity must be at least 1", nameof(quantity)); + + return new(shoppingCartId, shoppingCartItemId, quantity); + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Queries/GetShoppingCartItemsQuery.cs b/LiteCharms.Features/ShoppingCarts/Queries/GetShoppingCartItemsQuery.cs new file mode 100644 index 0000000..197d475 --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Queries/GetShoppingCartItemsQuery.cs @@ -0,0 +1,18 @@ +using LiteCharms.Models; + +namespace LiteCharms.Features.ShoppingCarts.Queries; + +public class GetShoppingCartItemsQuery : IRequest> +{ + public Guid ShoppingCartId { get; set; } + + private GetShoppingCartItemsQuery(Guid shoppingCartId) => ShoppingCartId = shoppingCartId; + + public static GetShoppingCartItemsQuery Create(Guid shoppingCartId) + { + if (shoppingCartId == Guid.Empty) + throw new ArgumentException("Shopping cart id is required", nameof(shoppingCartId)); + + return new(shoppingCartId); + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Queries/Handlers/GetShoppingCartItemsQueryHandler.cs b/LiteCharms.Features/ShoppingCarts/Queries/Handlers/GetShoppingCartItemsQueryHandler.cs new file mode 100644 index 0000000..e523c25 --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Queries/Handlers/GetShoppingCartItemsQueryHandler.cs @@ -0,0 +1,30 @@ +using LiteCharms.Extensions; +using LiteCharms.Infrastructure.Database; +using LiteCharms.Models; + +namespace LiteCharms.Features.ShoppingCarts.Queries.Handlers; + +public class GetShoppingCartItemsQueryHandler(IDbContextFactory contextFactory) : IRequestHandler> +{ + public async ValueTask> Handle(GetShoppingCartItemsQuery request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.ShoppingCarts.AnyAsync(i => i.Id == request.ShoppingCartId, cancellationToken)) + return Result.Fail($"Shopping cart could not be found with id {request.ShoppingCartId}"); + + var items = await context.ShoppingCartItems.AsNoTracking() + .Where(i => i.ShoppingCartId == request.ShoppingCartId).ToArrayAsync(cancellationToken); + + return items?.Length > 0 + ? Result.Ok(items.Select(i => i.ToModel()).ToArray()) + : Result.Fail($"Failed to retrieve shopping cart items with id {request.ShoppingCartId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +}