diff --git a/LiteCharms.Entities/ShoppingCart.cs b/LiteCharms.Entities/ShoppingCart.cs index 3c974dc..ead1175 100644 --- a/LiteCharms.Entities/ShoppingCart.cs +++ b/LiteCharms.Entities/ShoppingCart.cs @@ -12,4 +12,6 @@ public class ShoppingCart : Models.ShoppingCart public virtual Quote? Quote { get; set; } public virtual ICollection? ShoppingCartItems { get; set; } + + public virtual ICollection? Packages { get; set; } } diff --git a/LiteCharms.Extensions/EntityModeMappers.cs b/LiteCharms.Extensions/EntityModeMappers.cs index b3b8e64..37ad38e 100644 --- a/LiteCharms.Extensions/EntityModeMappers.cs +++ b/LiteCharms.Extensions/EntityModeMappers.cs @@ -4,6 +4,36 @@ namespace LiteCharms.Extensions; public static class EntityModeMappers { + public static ShoppingCartPackage ToModel(this Entities.ShoppingCartPackage entity) => + new() + { + Id = entity.Id, + CreatedAt = entity.CreatedAt, + PackageId = entity.PackageId, + ShoppingCartId = entity.ShoppingCartId + }; + + public static PackageItem ToModel(this Entities.PackageItem entity) => + new() + { + Id = entity.Id, + Active = entity.Active, + CreatedAt = entity.CreatedAt, + PackageId = entity.PackageId, + ProductPriceId = entity.ProductPriceId + }; + + public static Package ToModel(this Entities.Package entity) => + new() + { + Id = entity.Id, + CreatedAt = entity.CreatedAt, + Active = entity.Active, + Description = entity.Description, + Name = entity.Name, + UpdatedAt = entity.UpdatedAt + }; + public static ShoppingCartItem ToModel(this Entities.ShoppingCartItem entity) => new() { @@ -36,7 +66,7 @@ public static class EntityModeMappers ExpiredAt = entity.ExpiredAt, Reason = entity.Reason, ShoppingCartId = entity.ShoppingCartId, - Status = entity.Status + Status = entity.Status }; public static Notification ToModel(this Entities.Notification entity) => @@ -53,7 +83,12 @@ public static class EntityModeMappers Platform = entity.Platform, Recipient = entity.Recipient, Subject = entity.Subject, - Processed = entity.Processed + Processed = entity.Processed, + SenderName = entity.SenderName, + RecipientAddress = entity.RecipientAddress, + Priority = entity.Priority, + UpdatedAt = entity?.UpdatedAt, + IsHtml = entity!.IsHtml }; public static Customer ToModel(this Entities.Customer entity) => @@ -78,7 +113,7 @@ public static class EntityModeMappers Slack = entity.Slack, Tax = entity.Tax, Website = entity.Website, - Whatsapp = entity.Whatsapp + Whatsapp = entity.Whatsapp }; public static Lead ToModel(this Entities.Lead entity) => @@ -113,7 +148,10 @@ public static class EntityModeMappers RefundId = entity.RefundId, QuoteId = entity.QuoteId, Status = entity.Status, - ShoppingCartId = entity.ShoppingCartId + ShoppingCartId = entity.ShoppingCartId, + DepositRequired = entity.DepositRequired, + Requirements = entity.Requirements, + Terms = entity.Terms }; public static OrderRefund ToModel(this Entities.OrderRefund entity) => diff --git a/LiteCharms.Features/CartPackages/Commands/AddPackageItemsCommand.cs b/LiteCharms.Features/CartPackages/Commands/AddPackageItemsCommand.cs new file mode 100644 index 0000000..be87a47 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/AddPackageItemsCommand.cs @@ -0,0 +1,25 @@ +namespace LiteCharms.Features.CartPackages.Commands; + +public class AddPackageItemCommand : IRequest> +{ + public Guid PackageId { get; set; } + + public Guid ProductPriceId { get; set; } + + private AddPackageItemCommand(Guid packageId, Guid productPriceId) + { + PackageId = packageId; + ProductPriceId = productPriceId; + } + + public static AddPackageItemCommand Create(Guid packageId, Guid productPriceId) + { + if (packageId == Guid.Empty) + throw new ArgumentException("Package id is required", nameof(packageId)); + + if (productPriceId == Guid.Empty) + throw new ArgumentException("Product price id is required", nameof(productPriceId)); + + return new(packageId, productPriceId); + } +} diff --git a/LiteCharms.Features/CartPackages/Commands/CreatePackageCommand.cs b/LiteCharms.Features/CartPackages/Commands/CreatePackageCommand.cs new file mode 100644 index 0000000..4a86846 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/CreatePackageCommand.cs @@ -0,0 +1,23 @@ +namespace LiteCharms.Features.CartPackages.Commands; + +public class CreatePackageCommand : IRequest> +{ + public string? Name { get; set; } + + public string? Description { get; set; } + + private CreatePackageCommand(string? name, string? description) + { + Name = name; + Description = description; + } + + public static CreatePackageCommand Create(string? name, string? description) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name, nameof(name)); + + ArgumentException.ThrowIfNullOrWhiteSpace(description, nameof(description)); + + return new(name, description); + } +} diff --git a/LiteCharms.Features/CartPackages/Commands/DeletePackageCommand.cs b/LiteCharms.Features/CartPackages/Commands/DeletePackageCommand.cs new file mode 100644 index 0000000..5957dce --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/DeletePackageCommand.cs @@ -0,0 +1,25 @@ +namespace LiteCharms.Features.CartPackages.Commands; + +public class DeletePackageItemCommand : IRequest +{ + public Guid PackageId { get; set; } + + public Guid PackageItemId { get; set; } + + private DeletePackageItemCommand(Guid packageId, Guid packageItemId) + { + PackageId = packageId; + PackageItemId = packageItemId; + } + + public static DeletePackageItemCommand Create(Guid packageId, Guid packageItemId) + { + if (packageId == Guid.Empty) + throw new ArgumentException("Package id is required", nameof(packageId)); + + if (packageItemId == Guid.Empty) + throw new ArgumentException("Product price id is required", nameof(packageItemId)); + + return new(packageId, packageItemId); + } +} diff --git a/LiteCharms.Features/CartPackages/Commands/DeletePackageItemsCommand.cs b/LiteCharms.Features/CartPackages/Commands/DeletePackageItemsCommand.cs new file mode 100644 index 0000000..c9aa3e0 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/DeletePackageItemsCommand.cs @@ -0,0 +1,16 @@ +namespace LiteCharms.Features.CartPackages.Commands; + +public class DeletePackageItemsCommand : IRequest +{ + public Guid PackageId { get; set; } + + private DeletePackageItemsCommand(Guid packageId) => PackageId = packageId; + + public static DeletePackageItemsCommand Create(Guid packageId) + { + if (packageId == Guid.Empty) + throw new ArgumentException("Package ID is required", nameof(packageId)); + + return new(packageId); + } +} diff --git a/LiteCharms.Features/CartPackages/Commands/Handlers/AddPackageItemCommandHandler.cs b/LiteCharms.Features/CartPackages/Commands/Handlers/AddPackageItemCommandHandler.cs new file mode 100644 index 0000000..ffdd24a --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/Handlers/AddPackageItemCommandHandler.cs @@ -0,0 +1,38 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.CartPackages.Commands.Handlers; + +public class AddPackageItemCommandHandler(IDbContextFactory contextFactory) : IRequestHandler> +{ + public async ValueTask> Handle(AddPackageItemCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.Packages.AnyAsync(p => p.Id == request.PackageId, cancellationToken)) + return Result.Fail($"Could not find package by ID {request.PackageId}"); + + if (!await context.ProductPrices.AnyAsync(p => p.Id == request.ProductPriceId && p.Active == true, cancellationToken)) + return Result.Fail($"Could not find an active product price by ID {request.ProductPriceId}"); + + if (await context.PackageItems.AnyAsync(p => p.ProductPriceId == request.ProductPriceId && p.PackageId == request.PackageId, cancellationToken)) + return Result.Fail($"Product price {request.ProductPriceId} is already added to this package {request.PackageId}"); + + var newPackageItem = context.PackageItems.Add(new Entities.PackageItem + { + PackageId = request.PackageId, + ProductPriceId = request.ProductPriceId, + Active = true + }); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok(newPackageItem.Entity.Id) + : Result.Fail($"Failed to add new package item by ID {request.ProductPriceId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/CartPackages/Commands/Handlers/CreatePackageCommandHandler.cs b/LiteCharms.Features/CartPackages/Commands/Handlers/CreatePackageCommandHandler.cs new file mode 100644 index 0000000..4945a73 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/Handlers/CreatePackageCommandHandler.cs @@ -0,0 +1,32 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.CartPackages.Commands.Handlers; + +public class CreatePackageCommandHandler(IDbContextFactory contextFactory) : IRequestHandler> +{ + public async ValueTask> Handle(CreatePackageCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (await context.Packages.AnyAsync(p => p.Name == request.Name, cancellationToken)) + return Result.Fail($"A package by the same name already exists: {request.Name}"); + + var newPackage = context.Packages.Add(new Entities.Package + { + Name = request.Name, + Description = request.Description, + Active = true + }); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok(newPackage.Entity.Id) + : Result.Fail($"Failed to create a new package by the name: {request.Name}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/CartPackages/Commands/Handlers/DeletePackageItemCommandHandler.cs b/LiteCharms.Features/CartPackages/Commands/Handlers/DeletePackageItemCommandHandler.cs new file mode 100644 index 0000000..5ec6745 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/Handlers/DeletePackageItemCommandHandler.cs @@ -0,0 +1,32 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.CartPackages.Commands.Handlers; + +public class DeletePackageItemCommandHandler(IDbContextFactory contextFactory) : IRequestHandler +{ + public async ValueTask Handle(DeletePackageItemCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.Packages.AnyAsync(p => p.Id == request.PackageId, cancellationToken)) + return Result.Fail($"Could not find package by ID {request.PackageId}"); + + var item = await context.PackageItems.FirstOrDefaultAsync(p => p.Id == request.PackageItemId && p.PackageId == request.PackageId, cancellationToken); + + if(item is null) + return Result.Fail($"Product item {request.PackageItemId} is already added to this package {request.PackageId}"); + + context.PackageItems.Remove(item); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail($"Failed to delete package item by id {request.PackageItemId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/CartPackages/Commands/Handlers/DeletePackageItemsCommandHandler.cs b/LiteCharms.Features/CartPackages/Commands/Handlers/DeletePackageItemsCommandHandler.cs new file mode 100644 index 0000000..8f4e8e7 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/Handlers/DeletePackageItemsCommandHandler.cs @@ -0,0 +1,29 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.CartPackages.Commands.Handlers; + +public class DeletePackageItemsCommandHandler(IDbContextFactory contextFactory) : IRequestHandler +{ + public async ValueTask Handle(DeletePackageItemsCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.Packages.AnyAsync(p => p.Id == request.PackageId, cancellationToken)) + return Result.Fail($"Could not find package by ID {request.PackageId}"); + + var items = await context.PackageItems.Where(i => i.PackageId == request.PackageId).ToArrayAsync(cancellationToken); + + context.PackageItems.RemoveRange(items); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail($"Failed to delete package {request.PackageId} items"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/CartPackages/Commands/Handlers/UpdatePackageCommandHandler.cs b/LiteCharms.Features/CartPackages/Commands/Handlers/UpdatePackageCommandHandler.cs new file mode 100644 index 0000000..7889c52 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/Handlers/UpdatePackageCommandHandler.cs @@ -0,0 +1,33 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.CartPackages.Commands.Handlers; + +public class UpdatePackageCommandHandler(IDbContextFactory contextFactory) : IRequestHandler +{ + public async ValueTask Handle(UpdatePackageCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (await context.Packages.AnyAsync(p => p.Name == request.Name, cancellationToken)) + return Result.Fail($"A package by the same name already exists: {request.Name}"); + + var package = await context.Packages.FirstOrDefaultAsync(p => p.Id == request.PackageId, cancellationToken); + + if (package is null) + return Result.Fail($"Could not find package by id {request.PackageId}"); + + package.Name = request.Name; + package.Description = request.Description; + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail($"Failed to update package with id {request.PackageId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/CartPackages/Commands/Handlers/UpdatePackageStatusCommandHandler.cs b/LiteCharms.Features/CartPackages/Commands/Handlers/UpdatePackageStatusCommandHandler.cs new file mode 100644 index 0000000..5ef4a91 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/Handlers/UpdatePackageStatusCommandHandler.cs @@ -0,0 +1,29 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.CartPackages.Commands.Handlers; + +public class UpdatePackageStatusCommandHandler(IDbContextFactory contextFactory) : IRequestHandler +{ + public async ValueTask Handle(UpdatePackageStatusCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var package = await context.Packages.FirstOrDefaultAsync(p => p.Id == request.PackageId, cancellationToken); + + if (package is null) + return Result.Fail($"Could not find package by id {request.PackageId}"); + + package.Active = request.Active; + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail($"Failed to update package with id {request.PackageId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/CartPackages/Commands/UpdatePackageCommand.cs b/LiteCharms.Features/CartPackages/Commands/UpdatePackageCommand.cs new file mode 100644 index 0000000..938ca44 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/UpdatePackageCommand.cs @@ -0,0 +1,28 @@ +namespace LiteCharms.Features.CartPackages.Commands; + +public class UpdatePackageCommand : IRequest +{ + public Guid PackageId { get; set; } + + public string? Name { get; set; } + + public string? Description { get; set; } + + private UpdatePackageCommand(Guid packageId, string? name, string? description) + { + PackageId = packageId; + Name = name; + Description = description; + } + + public static UpdatePackageCommand Create(Guid packageId, string? name, string? description) + { + if (packageId == Guid.Empty) + throw new ArgumentException($"Package ID is required", nameof(packageId)); + + ArgumentNullException.ThrowIfNullOrWhiteSpace(name, nameof(name)); + ArgumentNullException.ThrowIfNullOrWhiteSpace(description, nameof(description)); + + return new(packageId, name, description); + } +} diff --git a/LiteCharms.Features/CartPackages/Commands/UpdatePackageStatusCommand.cs b/LiteCharms.Features/CartPackages/Commands/UpdatePackageStatusCommand.cs new file mode 100644 index 0000000..7be4651 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Commands/UpdatePackageStatusCommand.cs @@ -0,0 +1,22 @@ +namespace LiteCharms.Features.CartPackages.Commands; + +public class UpdatePackageStatusCommand : IRequest +{ + public Guid PackageId { get; set; } + + public bool Active { get; set; } + + private UpdatePackageStatusCommand(Guid packageId, bool active) + { + PackageId = packageId; + Active = active; + } + + public static UpdatePackageStatusCommand Create(Guid packageId, bool active) + { + if(packageId == Guid.Empty) + throw new ArgumentException($"Package id is required", nameof(packageId)); + + return new(packageId, active); + } +} diff --git a/LiteCharms.Features/CartPackages/Queries/GetPackageItemsQuery.cs b/LiteCharms.Features/CartPackages/Queries/GetPackageItemsQuery.cs new file mode 100644 index 0000000..e0830af --- /dev/null +++ b/LiteCharms.Features/CartPackages/Queries/GetPackageItemsQuery.cs @@ -0,0 +1,18 @@ +using LiteCharms.Models; + +namespace LiteCharms.Features.CartPackages.Queries; + +public class GetPackageItemsQuery : IRequest> +{ + public Guid PackageId { get; set; } + + private GetPackageItemsQuery(Guid packageId) => PackageId = packageId; + + public static GetPackageItemsQuery Create(Guid packageId) + { + if (packageId == Guid.Empty) + throw new ArgumentException("Package ID is required", nameof(packageId)); + + return new(packageId); + } +} diff --git a/LiteCharms.Features/CartPackages/Queries/GetPackageQuery.cs b/LiteCharms.Features/CartPackages/Queries/GetPackageQuery.cs new file mode 100644 index 0000000..1384783 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Queries/GetPackageQuery.cs @@ -0,0 +1,18 @@ +using LiteCharms.Models; + +namespace LiteCharms.Features.CartPackages.Queries; + +public class GetPackageQuery : IRequest> +{ + public Guid PackageId { get; set; } + + private GetPackageQuery(Guid packageId) => PackageId = packageId; + + public static GetPackageQuery Create(Guid packageId) + { + if(packageId == Guid.Empty) + throw new ArgumentException("Package ID is required", nameof(packageId)); + + return new(packageId); + } +} diff --git a/LiteCharms.Features/CartPackages/Queries/GetPackagesQuery.cs b/LiteCharms.Features/CartPackages/Queries/GetPackagesQuery.cs new file mode 100644 index 0000000..351a141 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Queries/GetPackagesQuery.cs @@ -0,0 +1,33 @@ +using LiteCharms.Models; + +namespace LiteCharms.Features.CartPackages.Queries; + +public class GetPackagesQuery : IRequest> +{ + public DateOnly From { get; set; } + + public DateOnly To { get; set; } + + public int MaxRecords { get; set; } + + public bool Active { get; set; } + + private GetPackagesQuery(DateOnly from, DateOnly to, int maxRecords = 1000, bool active = true) + { + From = from; + To = to; + MaxRecords = maxRecords; + Active = active; + } + + public static GetPackagesQuery Create(DateOnly from, DateOnly to, int maxRecords = 1000, bool active = true) + { + if (from > to) + throw new ArgumentException("From date cannot be greater than To date."); + + if (maxRecords <= 0) + throw new ArgumentException("MaxRecords must be a positive integer."); + + return new(from, to, maxRecords, active); + } +} diff --git a/LiteCharms.Features/CartPackages/Queries/Handlers/GetPackageItemsQueryHandler.cs b/LiteCharms.Features/CartPackages/Queries/Handlers/GetPackageItemsQueryHandler.cs new file mode 100644 index 0000000..487b203 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Queries/Handlers/GetPackageItemsQueryHandler.cs @@ -0,0 +1,32 @@ +using LiteCharms.Extensions; +using LiteCharms.Infrastructure.Database; +using LiteCharms.Models; + +namespace LiteCharms.Features.CartPackages.Queries.Handlers; + +public class GetPackageItemsQueryHandler(IDbContextFactory contextFactory) : IRequestHandler> +{ + public async ValueTask> Handle(GetPackageItemsQuery request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.Packages.AnyAsync(p => p.Id == request.PackageId, cancellationToken)) + return Result.Fail($"Package could not be found with ID {request.PackageId}"); + + var items = await context.PackageItems.AsNoTracking() + .OrderByDescending(o => o.CreatedAt) + .Where(p => p.PackageId == request.PackageId) + .ToArrayAsync(cancellationToken); + + return items?.Length > 0 + ? Result.Ok(items.Select(i => i.ToModel()).ToArray()) + : Result.Fail($"Could not find package items by package ID {request.PackageId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/CartPackages/Queries/Handlers/GetPackageQueryHandler.cs b/LiteCharms.Features/CartPackages/Queries/Handlers/GetPackageQueryHandler.cs new file mode 100644 index 0000000..7a01fb5 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Queries/Handlers/GetPackageQueryHandler.cs @@ -0,0 +1,27 @@ +using LiteCharms.Extensions; +using LiteCharms.Infrastructure.Database; +using LiteCharms.Models; + +namespace LiteCharms.Features.CartPackages.Queries.Handlers; + +public class GetPackageQueryHandler(IDbContextFactory contextFactory) : IRequestHandler> +{ + public async ValueTask> Handle(GetPackageQuery request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var package = await context.Packages.FirstOrDefaultAsync(p => p.Id == request.PackageId, cancellationToken); + + return package is not null + ? Result.Ok(package.ToModel()) + : Result.Fail($"Failed to find package by ID {request.PackageId}"); + + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/CartPackages/Queries/Handlers/GetPackagesQueryHandler.cs b/LiteCharms.Features/CartPackages/Queries/Handlers/GetPackagesQueryHandler.cs new file mode 100644 index 0000000..b495bb0 --- /dev/null +++ b/LiteCharms.Features/CartPackages/Queries/Handlers/GetPackagesQueryHandler.cs @@ -0,0 +1,35 @@ +using LiteCharms.Extensions; +using LiteCharms.Infrastructure.Database; +using LiteCharms.Models; + +namespace LiteCharms.Features.CartPackages.Queries.Handlers; + +public class GetPackagesQueryHandler(IDbContextFactory contextFactory) : IRequestHandler> +{ + public async ValueTask> Handle(GetPackagesQuery request, CancellationToken cancellationToken) + { + try + { + var fromDate = request.From.ToDateTime(TimeOnly.MinValue); + var toDate = request.To.ToDateTime(TimeOnly.MaxValue); + + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var packages = await context.Packages + .AsNoTracking() + .OrderByDescending(o => o.CreatedAt) + .Where(p => p.CreatedAt >= fromDate && p.CreatedAt <= toDate) + .Where(p => p.Active == request.Active) + .Take(request.MaxRecords) + .ToArrayAsync(cancellationToken); + + return packages?.Length > 0 + ? Result.Ok(packages.Select(o => o.ToModel()).ToArray()) + : Result.Fail(new Error($"No packages found for the specified date range {request.From} - {request.To}.")); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/Notifications/Commands/CreateNotificationCommand.cs b/LiteCharms.Features/Notifications/Commands/CreateNotificationCommand.cs index be81438..a9cf063 100644 --- a/LiteCharms.Features/Notifications/Commands/CreateNotificationCommand.cs +++ b/LiteCharms.Features/Notifications/Commands/CreateNotificationCommand.cs @@ -6,15 +6,21 @@ public class CreateNotificationCommand : IRequest> { public NotificationDirection Direction { get; set; } - public string? Author { get; set; } + public string? Sender { get; set; } - public string? Title { get; set; } + public string? SenderAddress { get; set; } - public string? Description { get; set; } + public string? Subject { get; set; } + + public string? Message { get; set; } public NotificationPlatforms Platform { get; set; } - public string? PlatformAddress { get; set; } + public Priorities Priority { get; set; } + + public string? Recipient { get; set; } + + public string? RecipientAddress { get; set; } public string? CorrelationId { get; set; } @@ -22,39 +28,48 @@ public class CreateNotificationCommand : IRequest> public bool IsInternal { get; set; } - private CreateNotificationCommand(NotificationDirection direction, string author, string title, string description, NotificationPlatforms platform, string platformAddress, string correlationId, string correlationIdType, bool isInternal) + public bool IsHtml { get; set; } + + private CreateNotificationCommand(NotificationDirection direction, string sender, string senderAddress, string subject, string message, NotificationPlatforms platform, Priorities priority, string recipient, string recipientAddress, string correlationId, string correlationIdType, bool isInternal, bool isHtml = false) { Direction = direction; - Author = author; - Title = title; - Description = description; + Sender = sender; + SenderAddress = senderAddress; + Subject = subject; + Message = message; Platform = platform; - PlatformAddress = platformAddress; + Priority = priority; + Recipient = recipient; + RecipientAddress = recipientAddress; CorrelationId = correlationId; CorrelationIdType = correlationIdType; IsInternal = isInternal; + IsHtml = isHtml; } - public static CreateNotificationCommand Create(NotificationDirection direction, string author, string title, string description, NotificationPlatforms platform, string platformAddress, string correlationId, string correlationIdType, bool isInternal) + public static CreateNotificationCommand Create(NotificationDirection direction, string sender, string senderAddress, string subject, string message, NotificationPlatforms platform, Priorities priority, string recipient, string recipientAddress, string correlationId, string correlationIdType, bool isInternal, bool isHtml = false) { - if (string.IsNullOrWhiteSpace(author)) - throw new ArgumentException("Author cannot be null or whitespace.", nameof(author)); + if (string.IsNullOrWhiteSpace(sender)) + throw new ArgumentException("Sender name is required.", nameof(sender)); - if (string.IsNullOrWhiteSpace(title)) - throw new ArgumentException("Title cannot be null or whitespace.", nameof(title)); + if (string.IsNullOrWhiteSpace(subject)) + throw new ArgumentException("Subject is required.", nameof(subject)); - if (string.IsNullOrWhiteSpace(description)) - throw new ArgumentException("Description cannot be null or whitespace.", nameof(description)); + if (string.IsNullOrWhiteSpace(message)) + throw new ArgumentException("Message is required.", nameof(message)); - if (string.IsNullOrWhiteSpace(platformAddress)) - throw new ArgumentException("PlatformAddress cannot be null or whitespace.", nameof(platformAddress)); + if (string.IsNullOrWhiteSpace(recipient)) + throw new ArgumentException("Recipient name is required.", nameof(recipient)); + + if (string.IsNullOrWhiteSpace(recipientAddress)) + throw new ArgumentException("Recipient address is required.", nameof(recipientAddress)); if (string.IsNullOrWhiteSpace(correlationId)) - throw new ArgumentException("CorrelationId cannot be null or whitespace.", nameof(correlationId)); + throw new ArgumentException("CorrelationId is required.", nameof(correlationId)); if (string.IsNullOrWhiteSpace(correlationIdType)) - throw new ArgumentException("CorrelationIdType cannot be null or whitespace.", nameof(correlationIdType)); + throw new ArgumentException("CorrelationIdType is required.", nameof(correlationIdType)); - return new(direction, author, title, description, platform, platformAddress, correlationId, correlationIdType, isInternal); + return new(direction, sender, senderAddress, subject, message, platform, priority, recipient, recipientAddress, correlationId, correlationIdType, isInternal, isHtml); } } diff --git a/LiteCharms.Features/Notifications/Commands/Handlers/CreateNotificationCommandHandler.cs b/LiteCharms.Features/Notifications/Commands/Handlers/CreateNotificationCommandHandler.cs index d33fad6..e7a3fb4 100644 --- a/LiteCharms.Features/Notifications/Commands/Handlers/CreateNotificationCommandHandler.cs +++ b/LiteCharms.Features/Notifications/Commands/Handlers/CreateNotificationCommandHandler.cs @@ -6,29 +6,34 @@ public class CreateNotificationCommandHandler(IDbContextFactory c { public async ValueTask> Handle(CreateNotificationCommand request, CancellationToken cancellationToken) { - try - { + try + { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var newNotification = context.Notifications.Add(new Entities.Notification { Direction = request.Direction, - Sender = request.Author, - Subject = request.Title, - Message = request.Description, + SenderName = request.Sender, + Sender = request.SenderAddress, + Recipient = request.Recipient, + RecipientAddress = request.RecipientAddress, + Subject = request.Subject, + Message = request.Message, Platform = request.Platform, - Recipient = request.PlatformAddress, + Priority = request.Priority, CorrelationId = request.CorrelationId, CorrelationIdType = request.CorrelationIdType, IsInternal = request.IsInternal, + IsHtml = request.IsHtml, + Processed = false }); - return newNotification is not null - ? Result.Ok(newNotification.Entity.Id) + return newNotification is not null + ? Result.Ok(newNotification.Entity.Id) : Result.Fail(new Error("Failed to create notification")); } - catch (Exception ex) - { + catch (Exception ex) + { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } diff --git a/LiteCharms.Features/Orders/Queries/Handlers/GetOrdersQueryHandler.cs b/LiteCharms.Features/Orders/Queries/Handlers/GetOrdersQueryHandler.cs index 8276bb4..f244869 100644 --- a/LiteCharms.Features/Orders/Queries/Handlers/GetOrdersQueryHandler.cs +++ b/LiteCharms.Features/Orders/Queries/Handlers/GetOrdersQueryHandler.cs @@ -16,6 +16,7 @@ public class GetOrdersQueryHandler(IDbContextFactory contextFacto using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var orders = await context.Orders + .AsNoTracking() .OrderByDescending(o => o.CreatedAt) .Where(o => o.CreatedAt >= fromDate && o.CreatedAt <= toDate) .Take(request.MaxRecords) diff --git a/LiteCharms.Features/ShoppingCarts/Commands/AddPackageToShoppingCartCommand.cs b/LiteCharms.Features/ShoppingCarts/Commands/AddPackageToShoppingCartCommand.cs new file mode 100644 index 0000000..81e8bac --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/AddPackageToShoppingCartCommand.cs @@ -0,0 +1,25 @@ +namespace LiteCharms.Features.ShoppingCarts.Commands; + +public class AddPackageToShoppingCartCommand : IRequest +{ + public Guid ShoppingCartId { get; set; } + + public Guid PackageId { get; set; } + + private AddPackageToShoppingCartCommand(Guid shoppingCartId, Guid packageId) + { + ShoppingCartId = shoppingCartId; + PackageId = packageId; + } + + public static AddPackageToShoppingCartCommand Create(Guid shoppingCartId, Guid packageId) + { + if (shoppingCartId == Guid.Empty) + throw new ArgumentException($"Shopping cart ID is required", nameof(shoppingCartId)); + + if (packageId == Guid.Empty) + throw new ArgumentException($"Package ID is required", nameof(packageId)); + + return new(shoppingCartId, packageId); + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Commands/Handlers/AddPackageToShoppingCartCommandHandler.cs b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/AddPackageToShoppingCartCommandHandler.cs new file mode 100644 index 0000000..67e7949 --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/AddPackageToShoppingCartCommandHandler.cs @@ -0,0 +1,39 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.ShoppingCarts.Commands.Handlers; + +public class AddPackageToShoppingCartCommandHandler(IDbContextFactory contextFactory) : IRequestHandler +{ + public async ValueTask Handle(AddPackageToShoppingCartCommand request, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.Packages.AnyAsync(p => p.Id == request.PackageId, cancellationToken)) + return Result.Fail($"Package cold not be found by ID {request.PackageId}"); + + var shoppingCart = await context.ShoppingCarts.FirstOrDefaultAsync(c => c.Id == request.ShoppingCartId, cancellationToken); + + if (shoppingCart is null) + return Result.Fail($"Shopping cart could not be found by ID {request.ShoppingCartId}"); + + if (!await context.ShoppingCartPackages.AnyAsync(cp => cp.ShoppingCartId == request.ShoppingCartId, cancellationToken)) + return Result.Fail($"Package {request.PackageId} is already in the cart"); + + var newShoppingCartPackage = context.ShoppingCartPackages.Add(new Entities.ShoppingCartPackage + { + ShoppingCartId = request.ShoppingCartId, + PackageId = request.PackageId + }); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail($"Could not add package of id {request.PackageId} to shopping cart {request.ShoppingCartId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Commands/Handlers/RemovePackageFromShoppingCartCommandHandler.cs b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/RemovePackageFromShoppingCartCommandHandler.cs new file mode 100644 index 0000000..6294199 --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/Handlers/RemovePackageFromShoppingCartCommandHandler.cs @@ -0,0 +1,35 @@ +using LiteCharms.Infrastructure.Database; + +namespace LiteCharms.Features.ShoppingCarts.Commands.Handlers; + +public class RemovePackageFromShoppingCartCommandHandler(IDbContextFactory contextFactory) : IRequestHandler +{ + public async ValueTask Handle(RemovePackageFromShoppingCartCommand 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 cart could not be found by ID {request.ShoppingCartId}"); + + if (!await context.ShoppingCartPackages.AnyAsync(p => p.Id == request.ShoppingCartPackageId, cancellationToken)) + return Result.Fail($"Shopping cart package {request.ShoppingCartPackageId} is not in the shopping cart {request.ShoppingCartId}"); + + var shoppingCartPackage = await context.ShoppingCartPackages.FirstOrDefaultAsync(cp => cp.Id == request.ShoppingCartPackageId, cancellationToken); + + if (shoppingCartPackage is null) + return Result.Ok(); + + context.ShoppingCartPackages.Remove(shoppingCartPackage!); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail($"Could remove package of id {request.ShoppingCartPackageId} from shopping cart {request.ShoppingCartId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Commands/RemovePackageFromShoppingCartCommand.cs b/LiteCharms.Features/ShoppingCarts/Commands/RemovePackageFromShoppingCartCommand.cs new file mode 100644 index 0000000..6aa8f25 --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Commands/RemovePackageFromShoppingCartCommand.cs @@ -0,0 +1,25 @@ +namespace LiteCharms.Features.ShoppingCarts.Commands; + +public class RemovePackageFromShoppingCartCommand : IRequest +{ + public Guid ShoppingCartId { get; set; } + + public Guid ShoppingCartPackageId { get; set; } + + private RemovePackageFromShoppingCartCommand(Guid shoppingCartId, Guid shoppingCartPackageId) + { + ShoppingCartId = shoppingCartId; + ShoppingCartPackageId = shoppingCartPackageId; + } + + public static RemovePackageFromShoppingCartCommand Create(Guid shoppingCartId, Guid shoppingCartPackageId) + { + if (shoppingCartId == Guid.Empty) + throw new ArgumentException($"Shopping cart ID is required", nameof(shoppingCartId)); + + if (shoppingCartPackageId == Guid.Empty) + throw new ArgumentException($"Shopping cart Package ID is required", nameof(shoppingCartPackageId)); + + return new(shoppingCartId, shoppingCartPackageId); + } +} diff --git a/LiteCharms.Features/ShoppingCarts/Queries/GetShoppingCartPackagesQuery.cs b/LiteCharms.Features/ShoppingCarts/Queries/GetShoppingCartPackagesQuery.cs new file mode 100644 index 0000000..2f64359 --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Queries/GetShoppingCartPackagesQuery.cs @@ -0,0 +1,18 @@ +using LiteCharms.Models; + +namespace LiteCharms.Features.ShoppingCarts.Queries; + +public class GetShoppingCartPackagesQuery : IRequest> +{ + public Guid ShoppingCartId { get; set; } + + private GetShoppingCartPackagesQuery(Guid shoppingCartId) => ShoppingCartId = shoppingCartId; + + public static GetShoppingCartPackagesQuery 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/GetShoppingCartPackagesQueryHandler.cs b/LiteCharms.Features/ShoppingCarts/Queries/Handlers/GetShoppingCartPackagesQueryHandler.cs new file mode 100644 index 0000000..d6727ee --- /dev/null +++ b/LiteCharms.Features/ShoppingCarts/Queries/Handlers/GetShoppingCartPackagesQueryHandler.cs @@ -0,0 +1,32 @@ +using LiteCharms.Extensions; +using LiteCharms.Infrastructure.Database; +using LiteCharms.Models; + +namespace LiteCharms.Features.ShoppingCarts.Queries.Handlers; + +public class GetShoppingCartPackagesQueryHandler(IDbContextFactory contextFactory) : IRequestHandler> +{ + public async ValueTask> Handle(GetShoppingCartPackagesQuery 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 cart could not be found by ID {request.ShoppingCartId}"); + + var packages = await context.ShoppingCartPackages.AsNoTracking() + .OrderByDescending(o => o.CreatedAt) + .Where(cp => cp.ShoppingCartId == request.ShoppingCartId) + .ToArrayAsync(cancellationToken); + + return packages?.Length > 0 + ? Result.Ok(packages.Select(p => p.ToModel()).ToArray()) + : Result.Fail($"Could not find packaged in shopping cart by ID {request.ShoppingCartId}"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +}