diff --git a/src/Imprink.Application/Commands/Addresses/CreateAddress.cs b/src/Imprink.Application/Commands/Addresses/CreateAddress.cs new file mode 100644 index 0000000..96b8be8 --- /dev/null +++ b/src/Imprink.Application/Commands/Addresses/CreateAddress.cs @@ -0,0 +1,63 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Application.Services; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Addresses; + +public class CreateAddressCommand : IRequest +{ + public string AddressType { get; set; } = null!; + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Company { get; set; } + public string AddressLine1 { get; set; } = null!; + public string? AddressLine2 { get; set; } + public string? ApartmentNumber { get; set; } + public string? BuildingNumber { get; set; } + public string? Floor { get; set; } + public string City { get; set; } = null!; + public string State { get; set; } = null!; + public string PostalCode { get; set; } = null!; + public string Country { get; set; } = null!; + public string? PhoneNumber { get; set; } + public string? Instructions { get; set; } + public bool IsDefault { get; set; } + public bool IsActive { get; set; } = true; +} + +public class CreateAddress( + IUnitOfWork uw, + IMapper mapper, + ICurrentUserService userService) + : IRequestHandler +{ + public async Task Handle( + CreateAddressCommand request, + CancellationToken cancellationToken) + { + return await uw.TransactAsync(async () => + { + var address = mapper.Map
(request); + address.UserId = userService.GetCurrentUserId(); + + if (address.IsDefault) + { + var currentDefault = await uw.AddressRepository + .GetDefaultByUserIdAsync(address.UserId, cancellationToken); + + if (currentDefault != null) + { + currentDefault.IsDefault = false; + await uw.AddressRepository.UpdateAsync(currentDefault, cancellationToken); + } + } + + var createdAddress = await uw.AddressRepository.AddAsync(address, cancellationToken); + + await uw.SaveAsync(cancellationToken); + return mapper.Map(createdAddress); + }, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Addresses/GetAddressesByUserId.cs b/src/Imprink.Application/Commands/Addresses/GetAddressesByUserId.cs new file mode 100644 index 0000000..ab5a66e --- /dev/null +++ b/src/Imprink.Application/Commands/Addresses/GetAddressesByUserId.cs @@ -0,0 +1,43 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Addresses; + +public class GetAddressesByUserIdQuery : IRequest> +{ + public string UserId { get; set; } = null!; + public bool ActiveOnly { get; set; } + public string? AddressType { get; set; } +} + +public class GetAddressesByUserId( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler> +{ + public async Task> Handle( + GetAddressesByUserIdQuery request, + CancellationToken cancellationToken) + { + IEnumerable
addresses; + + if (!string.IsNullOrEmpty(request.AddressType)) + { + addresses = await uw.AddressRepository + .GetByUserIdAndTypeAsync(request.UserId, request.AddressType, cancellationToken); + } + else if (request.ActiveOnly) + { + addresses = await uw.AddressRepository + .GetActiveByUserIdAsync(request.UserId, cancellationToken); + } + else + { + addresses = await uw.AddressRepository.GetByUserIdAsync(request.UserId, cancellationToken); + } + + return mapper.Map>(addresses); + } +} diff --git a/src/Imprink.Application/Commands/Addresses/GetMyAddresses.cs b/src/Imprink.Application/Commands/Addresses/GetMyAddresses.cs new file mode 100644 index 0000000..1915d04 --- /dev/null +++ b/src/Imprink.Application/Commands/Addresses/GetMyAddresses.cs @@ -0,0 +1,26 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Application.Services; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Addresses; + +public class GetMyAddressesQuery : IRequest>; + +public class GetMyAddresses( + IUnitOfWork uw, + IMapper mapper, + ICurrentUserService userService) + : IRequestHandler> +{ + public async Task> Handle( + GetMyAddressesQuery request, + CancellationToken cancellationToken) + { + IEnumerable addresses = await uw.AddressRepository + .GetByUserIdAsync(userService.GetCurrentUserId(), cancellationToken); + + return mapper.Map>(addresses); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Categories/CreateCategoryHandler.cs b/src/Imprink.Application/Commands/Categories/CreateCategory.cs similarity index 82% rename from src/Imprink.Application/Commands/Categories/CreateCategoryHandler.cs rename to src/Imprink.Application/Commands/Categories/CreateCategory.cs index 310578a..44a95c1 100644 --- a/src/Imprink.Application/Commands/Categories/CreateCategoryHandler.cs +++ b/src/Imprink.Application/Commands/Categories/CreateCategory.cs @@ -14,9 +14,13 @@ public class CreateCategoryCommand : IRequest public Guid? ParentCategoryId { get; set; } } -public class CreateCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler +public class CreateCategory( + IUnitOfWork unitOfWork) + : IRequestHandler { - public async Task Handle(CreateCategoryCommand request, CancellationToken cancellationToken) + public async Task Handle( + CreateCategoryCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); @@ -32,7 +36,9 @@ public class CreateCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler public Guid Id { get; init; } } -public class DeleteCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler +public class DeleteCategory( + IUnitOfWork unitOfWork) + : IRequestHandler { - public async Task Handle(DeleteCategoryCommand request, CancellationToken cancellationToken) + public async Task Handle( + DeleteCategoryCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); try { - var exists = await unitOfWork.CategoryRepository.ExistsAsync(request.Id, cancellationToken); + var exists = await unitOfWork.CategoryRepository + .ExistsAsync(request.Id, cancellationToken); + if (!exists) { await unitOfWork.RollbackTransactionAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/Categories/GetCategoriesHandler.cs b/src/Imprink.Application/Commands/Categories/GetCategories.cs similarity index 87% rename from src/Imprink.Application/Commands/Categories/GetCategoriesHandler.cs rename to src/Imprink.Application/Commands/Categories/GetCategories.cs index b606dbe..0a28126 100644 --- a/src/Imprink.Application/Commands/Categories/GetCategoriesHandler.cs +++ b/src/Imprink.Application/Commands/Categories/GetCategories.cs @@ -10,10 +10,13 @@ public class GetCategoriesQuery : IRequest> public bool RootCategoriesOnly { get; set; } = false; } -public class GetCategoriesHandler(IUnitOfWork unitOfWork) +public class GetCategories( + IUnitOfWork unitOfWork) : IRequestHandler> { - public async Task> Handle(GetCategoriesQuery request, CancellationToken cancellationToken) + public async Task> Handle( + GetCategoriesQuery request, + CancellationToken cancellationToken) { IEnumerable categories; diff --git a/src/Imprink.Application/Commands/Categories/UpdateCategoryHandler.cs b/src/Imprink.Application/Commands/Categories/UpdateCategory.cs similarity index 83% rename from src/Imprink.Application/Commands/Categories/UpdateCategoryHandler.cs rename to src/Imprink.Application/Commands/Categories/UpdateCategory.cs index f9f5f23..aa2db76 100644 --- a/src/Imprink.Application/Commands/Categories/UpdateCategoryHandler.cs +++ b/src/Imprink.Application/Commands/Categories/UpdateCategory.cs @@ -15,15 +15,20 @@ public class UpdateCategoryCommand : IRequest public Guid? ParentCategoryId { get; set; } } -public class UpdateCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler +public class UpdateCategory( + IUnitOfWork unitOfWork) + : IRequestHandler { - public async Task Handle(UpdateCategoryCommand request, CancellationToken cancellationToken) + public async Task Handle( + UpdateCategoryCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); try { - var existingCategory = await unitOfWork.CategoryRepository.GetByIdAsync(request.Id, cancellationToken); + var existingCategory = await unitOfWork.CategoryRepository + .GetByIdAsync(request.Id, cancellationToken); if (existingCategory == null) { @@ -37,7 +42,9 @@ public class UpdateCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler +{ + public int Quantity { get; set; } + public Guid ProductId { get; set; } + public Guid ProductVariantId { get; set; } + public string[]? OriginalImageUrls { get; set; } = []; + public string? CustomizationImageUrl { get; set; } = null!; + public string? CustomizationDescription { get; set; } = null!; + public Guid AddressId { get; set; } +} + +public class CreateOrder( + IUnitOfWork uw, + IMapper mapper, + ICurrentUserService userService) + : IRequestHandler +{ + public async Task Handle( + CreateOrderCommand request, + CancellationToken cancellationToken) + { + return await uw.TransactAsync(async () => + { + var userId = userService.GetCurrentUserId()!; + + var sourceAddress = await uw.AddressRepository + .GetByIdAndUserIdAsync(request.AddressId, userId, cancellationToken); + + if (sourceAddress == null) + throw new NotFoundException($"Address {request.AddressId} not found for {userId}"); + + var order = mapper.Map(request); + order.UserId = userService.GetCurrentUserId(); + order.OrderDate = DateTime.UtcNow; + order.OrderStatusId = 0; + order.ShippingStatusId = 0; + + var variant = uw.ProductVariantRepository + .GetByIdAsync(request.ProductVariantId, cancellationToken).Result; + + if (variant == null) + throw new NotFoundException("Product variant not found"); + + order.Amount = variant.Price * request.Quantity; + + + var createdOrder = await uw.OrderRepository.AddAsync(order, cancellationToken); + + var orderAddress = new OrderAddress + { + OrderId = createdOrder.Id, + AddressType = sourceAddress.AddressType, + FirstName = sourceAddress.FirstName, + LastName = sourceAddress.LastName, + Company = sourceAddress.Company, + AddressLine1 = sourceAddress.AddressLine1, + AddressLine2 = sourceAddress.AddressLine2, + ApartmentNumber = sourceAddress.ApartmentNumber, + BuildingNumber = sourceAddress.BuildingNumber, + Floor = sourceAddress.Floor, + City = sourceAddress.City, + State = sourceAddress.State, + PostalCode = sourceAddress.PostalCode, + Country = sourceAddress.Country, + PhoneNumber = sourceAddress.PhoneNumber, + Instructions = sourceAddress.Instructions, + Order = createdOrder + }; + + await uw.OrderAddressRepository.AddAsync(orderAddress, cancellationToken); + + createdOrder.Product = (await uw.ProductRepository + .GetByIdAsync(createdOrder.ProductId, cancellationToken))!; + + if (!createdOrder.ProductVariantId.HasValue) + throw new NotFoundException("Product variant not found"); + + createdOrder.ProductVariant = await uw.ProductVariantRepository + .GetByIdAsync(createdOrder.ProductVariantId.Value, cancellationToken); + + await uw.SaveAsync(cancellationToken); + + return mapper.Map(createdOrder); + }, cancellationToken); + } +} diff --git a/src/Imprink.Application/Commands/Orders/GetOrderById.cs b/src/Imprink.Application/Commands/Orders/GetOrderById.cs new file mode 100644 index 0000000..8749cb3 --- /dev/null +++ b/src/Imprink.Application/Commands/Orders/GetOrderById.cs @@ -0,0 +1,36 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Orders; + +public class GetOrderByIdQuery : IRequest +{ + public Guid Id { get; set; } + public bool IncludeDetails { get; set; } +} + +public class GetOrderById( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler +{ + public async Task Handle( + GetOrderByIdQuery request, + CancellationToken cancellationToken) + { + Order? order; + + if (request.IncludeDetails) + { + order = await uw.OrderRepository.GetByIdWithDetailsAsync(request.Id, cancellationToken); + } + else + { + order = await uw.OrderRepository.GetByIdAsync(request.Id, cancellationToken); + } + + return order != null ? mapper.Map(order) : null; + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantId.cs b/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantId.cs new file mode 100644 index 0000000..10b8080 --- /dev/null +++ b/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantId.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Orders; + +public class GetOrdersByMerchantIdQuery : IRequest> +{ + public string MerchantId { get; set; } = null!; + public bool IncludeDetails { get; set; } +} + +public class GetOrdersByMerchantId( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler> +{ + public async Task> Handle( + GetOrdersByMerchantIdQuery request, + CancellationToken cancellationToken) + { + IEnumerable orders; + + if (request.IncludeDetails) + { + orders = await uw.OrderRepository + .GetByMerchantIdWithDetailsAsync(request.MerchantId, cancellationToken); + } + else + { + orders = await uw.OrderRepository + .GetByMerchantIdAsync(request.MerchantId, cancellationToken); + } + + return mapper.Map>(orders); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Orders/GetOrdersByUserId.cs b/src/Imprink.Application/Commands/Orders/GetOrdersByUserId.cs new file mode 100644 index 0000000..7889877 --- /dev/null +++ b/src/Imprink.Application/Commands/Orders/GetOrdersByUserId.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Orders; + +public class GetOrdersByUserIdQuery : IRequest> +{ + public string UserId { get; set; } = null!; + public bool IncludeDetails { get; set; } +} + +public class GetOrdersByUserId( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler> +{ + public async Task> Handle( + GetOrdersByUserIdQuery request, + CancellationToken cancellationToken) + { + IEnumerable orders; + + if (request.IncludeDetails) + { + orders = await uw.OrderRepository + .GetByUserIdWithDetailsAsync(request.UserId, cancellationToken); + } + else + { + orders = await uw.OrderRepository + .GetByUserIdAsync(request.UserId, cancellationToken); + } + + return mapper.Map>(orders); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/ProductVariants/CreateProductVariantHandler.cs b/src/Imprink.Application/Commands/ProductVariants/CreateProductVariant.cs similarity index 81% rename from src/Imprink.Application/Commands/ProductVariants/CreateProductVariantHandler.cs rename to src/Imprink.Application/Commands/ProductVariants/CreateProductVariant.cs index 0ab7aa7..ba0db6f 100644 --- a/src/Imprink.Application/Commands/ProductVariants/CreateProductVariantHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/CreateProductVariant.cs @@ -17,10 +17,14 @@ public class CreateProductVariantCommand : IRequest public bool IsActive { get; set; } = true; } -public class CreateProductVariantHandler(IUnitOfWork unitOfWork, IMapper mapper) +public class CreateProductVariant( + IUnitOfWork unitOfWork, + IMapper mapper) : IRequestHandler { - public async Task Handle(CreateProductVariantCommand request, CancellationToken cancellationToken) + public async Task Handle( + CreateProductVariantCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); @@ -30,7 +34,8 @@ public class CreateProductVariantHandler(IUnitOfWork unitOfWork, IMapper mapper) productVariant.Product = null!; - var createdVariant = await unitOfWork.ProductVariantRepository.AddAsync(productVariant, cancellationToken); + var createdVariant = await unitOfWork.ProductVariantRepository + .AddAsync(productVariant, cancellationToken); await unitOfWork.SaveAsync(cancellationToken); await unitOfWork.CommitTransactionAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariantHandler.cs b/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariant.cs similarity index 75% rename from src/Imprink.Application/Commands/ProductVariants/DeleteProductVariantHandler.cs rename to src/Imprink.Application/Commands/ProductVariants/DeleteProductVariant.cs index 8e6a2ea..3fda9f1 100644 --- a/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariantHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariant.cs @@ -7,15 +7,20 @@ public class DeleteProductVariantCommand : IRequest public Guid Id { get; set; } } -public class DeleteProductVariantHandler(IUnitOfWork unitOfWork) : IRequestHandler +public class DeleteProductVariant( + IUnitOfWork unitOfWork) + : IRequestHandler { - public async Task Handle(DeleteProductVariantCommand request, CancellationToken cancellationToken) + public async Task Handle( + DeleteProductVariantCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); try { - var exists = await unitOfWork.ProductVariantRepository.ExistsAsync(request.Id, cancellationToken); + var exists = await unitOfWork.ProductVariantRepository + .ExistsAsync(request.Id, cancellationToken); if (!exists) { diff --git a/src/Imprink.Application/Commands/ProductVariants/GetProductVariantsHandler.cs b/src/Imprink.Application/Commands/ProductVariants/GetProductVariants.cs similarity index 68% rename from src/Imprink.Application/Commands/ProductVariants/GetProductVariantsHandler.cs rename to src/Imprink.Application/Commands/ProductVariants/GetProductVariants.cs index 5e76ae2..a7ed4ac 100644 --- a/src/Imprink.Application/Commands/ProductVariants/GetProductVariantsHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/GetProductVariants.cs @@ -2,7 +2,6 @@ using AutoMapper; using Imprink.Application.Dtos; using Imprink.Domain.Entities; using MediatR; -using Microsoft.Extensions.Logging; namespace Imprink.Application.Commands.ProductVariants; @@ -13,10 +12,14 @@ public class GetProductVariantsQuery : IRequest> public bool InStockOnly { get; set; } = false; } -public class GetProductVariantsHandler(IUnitOfWork unitOfWork, IMapper mapper, ILogger logger) +public class GetProductVariants( + IUnitOfWork unitOfWork, + IMapper mapper) : IRequestHandler> { - public async Task> Handle(GetProductVariantsQuery request, CancellationToken cancellationToken) + public async Task> Handle( + GetProductVariantsQuery request, + CancellationToken cancellationToken) { IEnumerable variants; @@ -24,15 +27,18 @@ public class GetProductVariantsHandler(IUnitOfWork unitOfWork, IMapper mapper, I { if (request.InStockOnly) { - variants = await unitOfWork.ProductVariantRepository.GetInStockByProductIdAsync(request.ProductId.Value, cancellationToken); + variants = await unitOfWork.ProductVariantRepository + .GetInStockByProductIdAsync(request.ProductId.Value, cancellationToken); } else if (request.IsActive.HasValue && request.IsActive.Value) { - variants = await unitOfWork.ProductVariantRepository.GetActiveByProductIdAsync(request.ProductId.Value, cancellationToken); + variants = await unitOfWork.ProductVariantRepository + .GetActiveByProductIdAsync(request.ProductId.Value, cancellationToken); } else { - variants = await unitOfWork.ProductVariantRepository.GetByProductIdAsync(request.ProductId.Value, cancellationToken); + variants = await unitOfWork.ProductVariantRepository + .GetByProductIdAsync(request.ProductId.Value, cancellationToken); } } else diff --git a/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariantHandler.cs b/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariant.cs similarity index 79% rename from src/Imprink.Application/Commands/ProductVariants/UpdateProductVariantHandler.cs rename to src/Imprink.Application/Commands/ProductVariants/UpdateProductVariant.cs index 0c88be3..b91ee4b 100644 --- a/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariantHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariant.cs @@ -18,23 +18,29 @@ public class UpdateProductVariantCommand : IRequest public bool IsActive { get; set; } } -public class UpdateProductVariantHandler(IUnitOfWork unitOfWork, IMapper mapper) +public class UpdateProductVariant( + IUnitOfWork unitOfWork, + IMapper mapper) : IRequestHandler { - public async Task Handle(UpdateProductVariantCommand request, CancellationToken cancellationToken) + public async Task Handle( + UpdateProductVariantCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); try { - var existingVariant = await unitOfWork.ProductVariantRepository.GetByIdAsync(request.Id, cancellationToken); + var existingVariant = await unitOfWork.ProductVariantRepository + .GetByIdAsync(request.Id, cancellationToken); if (existingVariant == null) throw new NotFoundException($"Product variant with ID {request.Id} not found."); mapper.Map(request, existingVariant); - var updatedVariant = await unitOfWork.ProductVariantRepository.UpdateAsync(existingVariant, cancellationToken); + var updatedVariant = await unitOfWork.ProductVariantRepository + .UpdateAsync(existingVariant, cancellationToken); await unitOfWork.SaveAsync(cancellationToken); await unitOfWork.CommitTransactionAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/Products/CreateProductHandler.cs b/src/Imprink.Application/Commands/Products/CreateProduct.cs similarity index 81% rename from src/Imprink.Application/Commands/Products/CreateProductHandler.cs rename to src/Imprink.Application/Commands/Products/CreateProduct.cs index ac12e05..9f10a4e 100644 --- a/src/Imprink.Application/Commands/Products/CreateProductHandler.cs +++ b/src/Imprink.Application/Commands/Products/CreateProduct.cs @@ -16,9 +16,14 @@ public class CreateProductCommand : IRequest public Guid? CategoryId { get; set; } } -public class CreateProductHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +public class CreateProduct( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler { - public async Task Handle(CreateProductCommand request, CancellationToken cancellationToken) + public async Task Handle( + CreateProductCommand request, + CancellationToken cancellationToken) { return await uw.TransactAsync(async () => { diff --git a/src/Imprink.Application/Commands/Products/DeleteProductHandler.cs b/src/Imprink.Application/Commands/Products/DeleteProduct.cs similarity index 61% rename from src/Imprink.Application/Commands/Products/DeleteProductHandler.cs rename to src/Imprink.Application/Commands/Products/DeleteProduct.cs index 2e6c326..a032586 100644 --- a/src/Imprink.Application/Commands/Products/DeleteProductHandler.cs +++ b/src/Imprink.Application/Commands/Products/DeleteProduct.cs @@ -8,13 +8,18 @@ public class DeleteProductCommand : IRequest public Guid Id { get; set; } } -public class DeleteProductHandler(IUnitOfWork uw) : IRequestHandler +public class DeleteProduct( + IUnitOfWork uw) + : IRequestHandler { - public async Task Handle(DeleteProductCommand request, CancellationToken cancellationToken) + public async Task Handle( + DeleteProductCommand request, + CancellationToken cancellationToken) { await uw.TransactAsync(async () => { - var exists = await uw.ProductRepository.ExistsAsync(request.Id, cancellationToken); + var exists = await uw.ProductRepository + .ExistsAsync(request.Id, cancellationToken); if (!exists) { diff --git a/src/Imprink.Application/Commands/Products/GetProductById.cs b/src/Imprink.Application/Commands/Products/GetProductById.cs new file mode 100644 index 0000000..d8b5bb3 --- /dev/null +++ b/src/Imprink.Application/Commands/Products/GetProductById.cs @@ -0,0 +1,51 @@ +using Imprink.Application.Dtos; +using MediatR; + +namespace Imprink.Application.Commands.Products; + +public class GetProductByIdQuery : IRequest +{ + public Guid ProductId { get; set; } +} + +public class GetProductById( + IUnitOfWork unitOfWork) + : IRequestHandler +{ + public async Task Handle( + GetProductByIdQuery request, + CancellationToken cancellationToken) + { + var product = await unitOfWork.ProductRepository + .GetByIdWithCategoryAsync(request.ProductId, cancellationToken); + + if (product == null) + return null; + + return new ProductDto + { + Id = product.Id, + Name = product.Name, + Description = product.Description, + BasePrice = product.BasePrice, + IsCustomizable = product.IsCustomizable, + IsActive = product.IsActive, + ImageUrl = product.ImageUrl, + CategoryId = product.CategoryId, + Category = new CategoryDto + { + Id = product.Category.Id, + Name = product.Category.Name, + Description = product.Category.Description, + ImageUrl = product.Category.ImageUrl, + SortOrder = product.Category.SortOrder, + IsActive = product.Category.IsActive, + ParentCategoryId = product.Category.ParentCategoryId, + CreatedAt = product.Category.CreatedAt, + ModifiedAt = product.Category.ModifiedAt + }, + CreatedAt = product.CreatedAt, + ModifiedAt = product.ModifiedAt + }; + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Products/GetProductsHandler.cs b/src/Imprink.Application/Commands/Products/GetProducts.cs similarity index 79% rename from src/Imprink.Application/Commands/Products/GetProductsHandler.cs rename to src/Imprink.Application/Commands/Products/GetProducts.cs index 4e65018..8b3c97d 100644 --- a/src/Imprink.Application/Commands/Products/GetProductsHandler.cs +++ b/src/Imprink.Application/Commands/Products/GetProducts.cs @@ -9,11 +9,16 @@ public class GetProductsQuery : IRequest> public ProductFilterParameters FilterParameters { get; set; } = new(); } -public class GetProductsHandler(IUnitOfWork unitOfWork) : IRequestHandler> +public class GetProducts( + IUnitOfWork unitOfWork) + : IRequestHandler> { - public async Task> Handle(GetProductsQuery request, CancellationToken cancellationToken) + public async Task> Handle( + GetProductsQuery request, + CancellationToken cancellationToken) { - var pagedResult = await unitOfWork.ProductRepository.GetPagedAsync(request.FilterParameters, cancellationToken); + var pagedResult = await unitOfWork.ProductRepository + .GetPagedAsync(request.FilterParameters, cancellationToken); var productDtos = pagedResult.Items.Select(p => new ProductDto { diff --git a/src/Imprink.Application/Commands/Products/UpdateProductCommand.cs b/src/Imprink.Application/Commands/Products/UpdateProduct.cs similarity index 86% rename from src/Imprink.Application/Commands/Products/UpdateProductCommand.cs rename to src/Imprink.Application/Commands/Products/UpdateProduct.cs index 06f1313..28defbe 100644 --- a/src/Imprink.Application/Commands/Products/UpdateProductCommand.cs +++ b/src/Imprink.Application/Commands/Products/UpdateProduct.cs @@ -4,7 +4,7 @@ using MediatR; namespace Imprink.Application.Commands.Products; -public class UpdateProductCommand : IRequest +public class UpdateProduct : IRequest { public Guid Id { get; set; } public string Name { get; set; } = null!; @@ -16,15 +16,20 @@ public class UpdateProductCommand : IRequest public Guid? CategoryId { get; set; } } -public class UpdateProductHandler(IUnitOfWork unitOfWork) : IRequestHandler +public class UpdateProductHandler( + IUnitOfWork unitOfWork) + : IRequestHandler { - public async Task Handle(UpdateProductCommand request, CancellationToken cancellationToken) + public async Task Handle( + UpdateProduct request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); try { - var existingProduct = await unitOfWork.ProductRepository.GetByIdAsync(request.Id, cancellationToken); + var existingProduct = await unitOfWork.ProductRepository + .GetByIdAsync(request.Id, cancellationToken); if (existingProduct == null) throw new NotFoundException($"Product with ID {request.Id} not found."); @@ -37,7 +42,8 @@ public class UpdateProductHandler(IUnitOfWork unitOfWork) : IRequestHandler; -public class DeleteUserRoleHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +public class DeleteUserRole( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler { - public async Task Handle(DeleteUserRoleCommand request, CancellationToken cancellationToken) + public async Task Handle( + DeleteUserRoleCommand request, + CancellationToken cancellationToken) { await uw.BeginTransactionAsync(cancellationToken); @@ -18,12 +23,14 @@ public class DeleteUserRoleHandler(IUnitOfWork uw, IMapper mapper) : IRequestHan if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) throw new NotFoundException("User with ID: " + request.Sub + " does not exist."); - var existingUserRole = await uw.UserRoleRepository.GetUserRoleAsync(request.Sub, request.RoleId, cancellationToken); + var existingUserRole = await uw.UserRoleRepository + .GetUserRoleAsync(request.Sub, request.RoleId, cancellationToken); if (existingUserRole == null) throw new NotFoundException($"User role not found for user {request.Sub} and role {request.RoleId}"); - var removedRole = await uw.UserRoleRepository.RemoveUserRoleAsync(existingUserRole, cancellationToken); + var removedRole = await uw.UserRoleRepository + .RemoveUserRoleAsync(existingUserRole, cancellationToken); await uw.SaveAsync(cancellationToken); await uw.CommitTransactionAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/Users/GetAllRoles.cs b/src/Imprink.Application/Commands/Users/GetAllRoles.cs new file mode 100644 index 0000000..d3f8ba3 --- /dev/null +++ b/src/Imprink.Application/Commands/Users/GetAllRoles.cs @@ -0,0 +1,22 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using MediatR; + +namespace Imprink.Application.Commands.Users; + +public record GetAllRolesCommand : IRequest>; + +public class GetAllRoles( + IUnitOfWork uw, + IMapper mapper): IRequestHandler> +{ + public async Task> Handle( + GetAllRolesCommand request, + CancellationToken cancellationToken) + { + var roles = await uw.RoleRepository + .GetAllRolesAsync(cancellationToken); + + return mapper.Map>(roles); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Users/GetAllRolesHandler.cs b/src/Imprink.Application/Commands/Users/GetAllRolesHandler.cs deleted file mode 100644 index ec1661b..0000000 --- a/src/Imprink.Application/Commands/Users/GetAllRolesHandler.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Imprink.Application.Dtos; -using MediatR; - -namespace Imprink.Application.Commands.Users; - -public record GetAllRolesCommand : IRequest>; - -public class GetAllRolesHandler(IUnitOfWork uw, IMapper mapper): IRequestHandler> -{ - public async Task> Handle(GetAllRolesCommand request, CancellationToken cancellationToken) - { - var roles = await uw.RoleRepository.GetAllRolesAsync(cancellationToken); - - return mapper.Map>(roles); - } -} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Users/GetUserRolesHandler.cs b/src/Imprink.Application/Commands/Users/GetUserRoles.cs similarity index 56% rename from src/Imprink.Application/Commands/Users/GetUserRolesHandler.cs rename to src/Imprink.Application/Commands/Users/GetUserRoles.cs index ea3f8d8..5e3bb1d 100644 --- a/src/Imprink.Application/Commands/Users/GetUserRolesHandler.cs +++ b/src/Imprink.Application/Commands/Users/GetUserRoles.cs @@ -7,14 +7,20 @@ namespace Imprink.Application.Commands.Users; public record GetUserRolesCommand(string Sub) : IRequest>; -public class GetUserRolesHandler(IUnitOfWork uw, IMapper mapper): IRequestHandler> +public class GetUserRoles( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler> { - public async Task> Handle(GetUserRolesCommand request, CancellationToken cancellationToken) + public async Task> Handle( + GetUserRolesCommand request, + CancellationToken cancellationToken) { if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) throw new NotFoundException("User with ID: " + request.Sub + " does not exist."); - var roles = await uw.UserRoleRepository.GetUserRolesAsync(request.Sub, cancellationToken); + var roles = await uw.UserRoleRepository + .GetUserRolesAsync(request.Sub, cancellationToken); return mapper.Map>(roles); } diff --git a/src/Imprink.Application/Commands/Users/SetUserFullNameHandler.cs b/src/Imprink.Application/Commands/Users/SetUserFullName.cs similarity index 68% rename from src/Imprink.Application/Commands/Users/SetUserFullNameHandler.cs rename to src/Imprink.Application/Commands/Users/SetUserFullName.cs index 11c9597..fd22823 100644 --- a/src/Imprink.Application/Commands/Users/SetUserFullNameHandler.cs +++ b/src/Imprink.Application/Commands/Users/SetUserFullName.cs @@ -8,9 +8,15 @@ namespace Imprink.Application.Commands.Users; public record SetUserFullNameCommand(string FirstName, string LastName) : IRequest; -public class SetUserFullNameHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) : IRequestHandler +public class SetUserFullName( + IUnitOfWork uw, + IMapper mapper, + ICurrentUserService userService) + : IRequestHandler { - public async Task Handle(SetUserFullNameCommand request, CancellationToken cancellationToken) + public async Task Handle( + SetUserFullNameCommand request, + CancellationToken cancellationToken) { await uw.BeginTransactionAsync(cancellationToken); @@ -21,7 +27,8 @@ public class SetUserFullNameHandler(IUnitOfWork uw, IMapper mapper, ICurrentUser if (currentUser == null) throw new NotFoundException("User token could not be accessed."); - var user = await uw.UserRepository.SetUserFullNameAsync(currentUser, request.FirstName, request.LastName, cancellationToken); + var user = await uw.UserRepository + .SetUserFullNameAsync(currentUser, request.FirstName, request.LastName, cancellationToken); if (user == null) throw new DataUpdateException("User name could not be updated."); diff --git a/src/Imprink.Application/Commands/Users/SetUserPhoneHandler.cs b/src/Imprink.Application/Commands/Users/SetUserPhone.cs similarity index 69% rename from src/Imprink.Application/Commands/Users/SetUserPhoneHandler.cs rename to src/Imprink.Application/Commands/Users/SetUserPhone.cs index ac7bc68..38ede88 100644 --- a/src/Imprink.Application/Commands/Users/SetUserPhoneHandler.cs +++ b/src/Imprink.Application/Commands/Users/SetUserPhone.cs @@ -8,9 +8,15 @@ namespace Imprink.Application.Commands.Users; public record SetUserPhoneCommand(string PhoneNumber) : IRequest; -public class SetUserPhoneHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) : IRequestHandler +public class SetUserPhone( + IUnitOfWork uw, + IMapper mapper, + ICurrentUserService userService) + : IRequestHandler { - public async Task Handle(SetUserPhoneCommand request, CancellationToken cancellationToken) + public async Task Handle( + SetUserPhoneCommand request, + CancellationToken cancellationToken) { await uw.BeginTransactionAsync(cancellationToken); @@ -21,7 +27,8 @@ public class SetUserPhoneHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserSer if (currentUser == null) throw new NotFoundException("User token could not be accessed."); - var user = await uw.UserRepository.SetUserPhoneAsync(currentUser, request.PhoneNumber, cancellationToken); + var user = await uw.UserRepository + .SetUserPhoneAsync(currentUser, request.PhoneNumber, cancellationToken); if (user == null) throw new DataUpdateException("User phone could not be updated."); diff --git a/src/Imprink.Application/Commands/Users/SetUserRoleHandler.cs b/src/Imprink.Application/Commands/Users/SetUserRole.cs similarity index 73% rename from src/Imprink.Application/Commands/Users/SetUserRoleHandler.cs rename to src/Imprink.Application/Commands/Users/SetUserRole.cs index 7c265f0..63d54bb 100644 --- a/src/Imprink.Application/Commands/Users/SetUserRoleHandler.cs +++ b/src/Imprink.Application/Commands/Users/SetUserRole.cs @@ -8,9 +8,14 @@ namespace Imprink.Application.Commands.Users; public record SetUserRoleCommand(string Sub, Guid RoleId) : IRequest; -public class SetUserRoleHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +public class SetUserRole( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler { - public async Task Handle(SetUserRoleCommand request, CancellationToken cancellationToken) + public async Task Handle( + SetUserRoleCommand request, + CancellationToken cancellationToken) { await uw.BeginTransactionAsync(cancellationToken); @@ -25,7 +30,8 @@ public class SetUserRoleHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandle RoleId = request.RoleId }; - var addedRole = await uw.UserRoleRepository.AddUserRoleAsync(userRole, cancellationToken); + var addedRole = await uw.UserRoleRepository + .AddUserRoleAsync(userRole, cancellationToken); await uw.SaveAsync(cancellationToken); await uw.CommitTransactionAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/Users/SyncUserHandler.cs b/src/Imprink.Application/Commands/Users/SyncUser.cs similarity index 67% rename from src/Imprink.Application/Commands/Users/SyncUserHandler.cs rename to src/Imprink.Application/Commands/Users/SyncUser.cs index a304b27..7f22f21 100644 --- a/src/Imprink.Application/Commands/Users/SyncUserHandler.cs +++ b/src/Imprink.Application/Commands/Users/SyncUser.cs @@ -7,15 +7,21 @@ namespace Imprink.Application.Commands.Users; public record SyncUserCommand(Auth0User User) : IRequest; -public class SyncUserHandler(IUnitOfWork uw, IMapper mapper): IRequestHandler +public class SyncUser( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler { - public async Task Handle(SyncUserCommand request, CancellationToken cancellationToken) + public async Task Handle( + SyncUserCommand request, + CancellationToken cancellationToken) { await uw.BeginTransactionAsync(cancellationToken); try { - var user = await uw.UserRepository.UpdateOrCreateUserAsync(request.User, cancellationToken); + var user = await uw.UserRepository + .UpdateOrCreateUserAsync(request.User, cancellationToken); if (user == null) throw new Exception("User exists but could not be updated"); diff --git a/src/Imprink.Application/Dtos/AddressDto.cs b/src/Imprink.Application/Dtos/AddressDto.cs new file mode 100644 index 0000000..29512f5 --- /dev/null +++ b/src/Imprink.Application/Dtos/AddressDto.cs @@ -0,0 +1,24 @@ +namespace Imprink.Application.Dtos; + +public class AddressDto +{ + public Guid Id { get; set; } + public string UserId { get; set; } = null!; + public string AddressType { get; set; } = null!; + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Company { get; set; } + public string AddressLine1 { get; set; } = null!; + public string? AddressLine2 { get; set; } + public string? ApartmentNumber { get; set; } + public string? BuildingNumber { get; set; } + public string? Floor { get; set; } + public string City { get; set; } = null!; + public string State { get; set; } = null!; + public string PostalCode { get; set; } = null!; + public string Country { get; set; } = null!; + public string? PhoneNumber { get; set; } + public string? Instructions { get; set; } + public bool IsDefault { get; set; } + public bool IsActive { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/Dtos/OrderAddressDto.cs b/src/Imprink.Application/Dtos/OrderAddressDto.cs new file mode 100644 index 0000000..0fdfaad --- /dev/null +++ b/src/Imprink.Application/Dtos/OrderAddressDto.cs @@ -0,0 +1,24 @@ +namespace Imprink.Application.Dtos; + +public class OrderAddressDto +{ + public Guid Id { get; set; } + public Guid OrderId { get; set; } + public string AddressType { get; set; } = null!; + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Company { get; set; } + public string AddressLine1 { get; set; } = null!; + public string? AddressLine2 { get; set; } + public string? ApartmentNumber { get; set; } + public string? BuildingNumber { get; set; } + public string? Floor { get; set; } + public string City { get; set; } = null!; + public string State { get; set; } = null!; + public string PostalCode { get; set; } = null!; + public string Country { get; set; } = null!; + public string? PhoneNumber { get; set; } + public string? Instructions { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/Dtos/OrderDto.cs b/src/Imprink.Application/Dtos/OrderDto.cs new file mode 100644 index 0000000..947aea7 --- /dev/null +++ b/src/Imprink.Application/Dtos/OrderDto.cs @@ -0,0 +1,28 @@ +namespace Imprink.Application.Dtos; + +public class OrderDto +{ + public Guid Id { get; set; } + public string UserId { get; set; } = null!; + public DateTime OrderDate { get; set; } + public decimal Amount { get; set; } + public int Quantity { get; set; } + public Guid ProductId { get; set; } + public Guid? ProductVariantId { get; set; } + public int OrderStatusId { get; set; } + public int ShippingStatusId { get; set; } + public string? Notes { get; set; } + public string? MerchantId { get; set; } + public string? CustomizationImageUrl { get; set; } + public string[]? OriginalImageUrls { get; set; } = []; + public string? CustomizationDescription { get; set; } = null!; + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + + public OrderStatusDto? OrderStatus { get; set; } + public UserDto? User { get; set; } + public ShippingStatusDto? ShippingStatus { get; set; } + public OrderAddressDto? OrderAddress { get; set; } + public ProductDto? Product { get; set; } + public ProductVariantDto? ProductVariant { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/Dtos/OrderStatusDto.cs b/src/Imprink.Application/Dtos/OrderStatusDto.cs new file mode 100644 index 0000000..fb1622f --- /dev/null +++ b/src/Imprink.Application/Dtos/OrderStatusDto.cs @@ -0,0 +1,8 @@ +namespace Imprink.Application.Dtos; + +public class OrderStatusDto +{ + public int Id { get; set; } + public string Name { get; set; } = null!; + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/Dtos/ShippingStatusDto.cs b/src/Imprink.Application/Dtos/ShippingStatusDto.cs new file mode 100644 index 0000000..8ea670d --- /dev/null +++ b/src/Imprink.Application/Dtos/ShippingStatusDto.cs @@ -0,0 +1,8 @@ +namespace Imprink.Application.Dtos; + +public class ShippingStatusDto +{ + public int Id { get; set; } + public string Name { get; set; } = null!; + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/IUnitOfWork.cs b/src/Imprink.Application/IUnitOfWork.cs index a3346a2..507fe10 100644 --- a/src/Imprink.Application/IUnitOfWork.cs +++ b/src/Imprink.Application/IUnitOfWork.cs @@ -11,7 +11,8 @@ public interface IUnitOfWork public IUserRoleRepository UserRoleRepository { get; } public IRoleRepository RoleRepository { get; } public IOrderRepository OrderRepository { get; } - public IOrderItemRepository OrderItemRepository { get; } + public IAddressRepository AddressRepository { get; } + public IOrderAddressRepository OrderAddressRepository { get; } Task SaveAsync(CancellationToken cancellationToken = default); Task BeginTransactionAsync(CancellationToken cancellationToken = default); diff --git a/src/Imprink.Application/Imprink.Application.csproj b/src/Imprink.Application/Imprink.Application.csproj index bde7f6a..17480c4 100644 --- a/src/Imprink.Application/Imprink.Application.csproj +++ b/src/Imprink.Application/Imprink.Application.csproj @@ -19,8 +19,4 @@ - - - - diff --git a/src/Imprink.Application/Mappings/AddressMappingProfile.cs b/src/Imprink.Application/Mappings/AddressMappingProfile.cs new file mode 100644 index 0000000..13236f4 --- /dev/null +++ b/src/Imprink.Application/Mappings/AddressMappingProfile.cs @@ -0,0 +1,23 @@ +using AutoMapper; +using Imprink.Application.Commands.Addresses; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; + +namespace Imprink.Application.Mappings; + +public class AddressMappingProfile : Profile +{ + public AddressMappingProfile() + { + CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.CreatedAt, opt => opt.Ignore()) + .ForMember(dest => dest.ModifiedAt, opt => opt.Ignore()) + .ForMember(dest => dest.User, opt => opt.Ignore()); + + CreateMap(); + + CreateMap() + .ForMember(dest => dest.User, opt => opt.Ignore()); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Mappings/OrderAddressMappingProfile.cs b/src/Imprink.Application/Mappings/OrderAddressMappingProfile.cs new file mode 100644 index 0000000..6524393 --- /dev/null +++ b/src/Imprink.Application/Mappings/OrderAddressMappingProfile.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; + +namespace Imprink.Application.Mappings; + +public class OrderAddressMappingProfile : Profile +{ + public OrderAddressMappingProfile() + { + CreateMap(); + + CreateMap() + .ForMember(dest => dest.Order, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.OrderId, opt => opt.Ignore()) + .ForMember(dest => dest.Order, opt => opt.Ignore()) + .ForMember(dest => dest.CreatedAt, opt => opt.Ignore()) + .ForMember(dest => dest.ModifiedAt, opt => opt.Ignore()) + .ForMember(dest => dest.AddressType, opt => opt.MapFrom(src => src.AddressType)) + .ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName)) + .ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.LastName)) + .ForMember(dest => dest.Company, opt => opt.MapFrom(src => src.Company)) + .ForMember(dest => dest.AddressLine1, opt => opt.MapFrom(src => src.AddressLine1)) + .ForMember(dest => dest.AddressLine2, opt => opt.MapFrom(src => src.AddressLine2)) + .ForMember(dest => dest.ApartmentNumber, opt => opt.MapFrom(src => src.ApartmentNumber)) + .ForMember(dest => dest.BuildingNumber, opt => opt.MapFrom(src => src.BuildingNumber)) + .ForMember(dest => dest.Floor, opt => opt.MapFrom(src => src.Floor)) + .ForMember(dest => dest.City, opt => opt.MapFrom(src => src.City)) + .ForMember(dest => dest.State, opt => opt.MapFrom(src => src.State)) + .ForMember(dest => dest.PostalCode, opt => opt.MapFrom(src => src.PostalCode)) + .ForMember(dest => dest.Country, opt => opt.MapFrom(src => src.Country)) + .ForMember(dest => dest.PhoneNumber, opt => opt.MapFrom(src => src.PhoneNumber)) + .ForMember(dest => dest.Instructions, opt => opt.MapFrom(src => src.Instructions)); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Mappings/OrderMappingProfile.cs b/src/Imprink.Application/Mappings/OrderMappingProfile.cs new file mode 100644 index 0000000..82a83ad --- /dev/null +++ b/src/Imprink.Application/Mappings/OrderMappingProfile.cs @@ -0,0 +1,36 @@ +using AutoMapper; +using Imprink.Application.Commands.Orders; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; + +namespace Imprink.Application.Mappings; + +public class OrderMappingProfile : Profile +{ + public OrderMappingProfile() + { + CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.OrderDate, opt => opt.Ignore()) + .ForMember(dest => dest.OrderStatusId, opt => opt.Ignore()) + .ForMember(dest => dest.ShippingStatusId, opt => opt.Ignore()) + .ForMember(dest => dest.CreatedAt, opt => opt.Ignore()) + .ForMember(dest => dest.ModifiedAt, opt => opt.Ignore()) + .ForMember(dest => dest.OrderStatus, opt => opt.Ignore()) + .ForMember(dest => dest.User, opt => opt.Ignore()) + .ForMember(dest => dest.ShippingStatus, opt => opt.Ignore()) + .ForMember(dest => dest.OrderAddress, opt => opt.Ignore()) + .ForMember(dest => dest.Product, opt => opt.Ignore()) + .ForMember(dest => dest.ProductVariant, opt => opt.Ignore()); + + CreateMap(); + + CreateMap() + .ForMember(dest => dest.OrderStatus, opt => opt.Ignore()) + .ForMember(dest => dest.User, opt => opt.Ignore()) + .ForMember(dest => dest.ShippingStatus, opt => opt.Ignore()) + .ForMember(dest => dest.OrderAddress, opt => opt.Ignore()) + .ForMember(dest => dest.Product, opt => opt.Ignore()) + .ForMember(dest => dest.ProductVariant, opt => opt.Ignore()); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Mappings/OrderStatusMappingProfile.cs b/src/Imprink.Application/Mappings/OrderStatusMappingProfile.cs new file mode 100644 index 0000000..c61c7fe --- /dev/null +++ b/src/Imprink.Application/Mappings/OrderStatusMappingProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; + +namespace Imprink.Application.Mappings; + +public class OrderStatusMappingProfile : Profile +{ + public OrderStatusMappingProfile() + { + CreateMap(); + + CreateMap(); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Mappings/ProductMappingProfile.cs b/src/Imprink.Application/Mappings/ProductMappingProfile.cs index 389651f..ba17cc0 100644 --- a/src/Imprink.Application/Mappings/ProductMappingProfile.cs +++ b/src/Imprink.Application/Mappings/ProductMappingProfile.cs @@ -16,17 +16,15 @@ public class ProductMappingProfile: Profile .ForMember(dest => dest.ModifiedAt, opt => opt.Ignore()) .ForMember(dest => dest.CreatedBy, opt => opt.Ignore()) .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()) - .ForMember(dest => dest.Product, opt => opt.Ignore()) - .ForMember(dest => dest.OrderItems, opt => opt.Ignore()); + .ForMember(dest => dest.Product, opt => opt.Ignore()); CreateMap(); CreateMap() .ForMember(dest => dest.CreatedBy, opt => opt.Ignore()) - .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()) - .ForMember(dest => dest.OrderItems, opt => opt.Ignore()); + .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()); CreateMap(); - + CreateMap() .ForMember(dest => dest.Id, opt => opt.Ignore()) .ForMember(dest => dest.CreatedAt, opt => opt.Ignore()) @@ -34,8 +32,7 @@ public class ProductMappingProfile: Profile .ForMember(dest => dest.CreatedBy, opt => opt.Ignore()) .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()) .ForMember(dest => dest.Category, opt => opt.Ignore()) - .ForMember(dest => dest.ProductVariants, opt => opt.Ignore()) - .ForMember(dest => dest.OrderItems, opt => opt.Ignore()); + .ForMember(dest => dest.ProductVariants, opt => opt.Ignore()); CreateMap(); } diff --git a/src/Imprink.Application/Mappings/ShippingStatusMappingProfile.cs b/src/Imprink.Application/Mappings/ShippingStatusMappingProfile.cs new file mode 100644 index 0000000..16a60a5 --- /dev/null +++ b/src/Imprink.Application/Mappings/ShippingStatusMappingProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; + +namespace Imprink.Application.Mappings; + +public class ShippingStatusMappingProfile : Profile +{ + public ShippingStatusMappingProfile() + { + CreateMap(); + + CreateMap(); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Services/ICurrentUserService.cs b/src/Imprink.Application/Services/ICurrentUserService.cs index 83bbe41..b4b9c4a 100644 --- a/src/Imprink.Application/Services/ICurrentUserService.cs +++ b/src/Imprink.Application/Services/ICurrentUserService.cs @@ -2,5 +2,5 @@ namespace Imprink.Application.Services; public interface ICurrentUserService { - string? GetCurrentUserId(); + string GetCurrentUserId(); } \ No newline at end of file diff --git a/src/Imprink.Application/Validation/Addresses/CreateAddressCommandValidator.cs b/src/Imprink.Application/Validation/Addresses/CreateAddressCommandValidator.cs new file mode 100644 index 0000000..28f4db2 --- /dev/null +++ b/src/Imprink.Application/Validation/Addresses/CreateAddressCommandValidator.cs @@ -0,0 +1,117 @@ +using FluentValidation; +using Imprink.Application.Commands.Addresses; + +namespace Imprink.Application.Validation.Addresses; + +public class CreateAddressCommandValidator : AbstractValidator +{ + public CreateAddressCommandValidator() + { + RuleFor(x => x.AddressType) + .NotEmpty() + .WithMessage("Address type is required.") + .MaximumLength(50) + .WithMessage("Address type must not exceed 50 characters.") + .Must(BeValidAddressType) + .WithMessage("Address type must be one of: Home, Work, Billing, Shipping, Other."); + + RuleFor(x => x.FirstName) + .MaximumLength(100) + .WithMessage("First name must not exceed 100 characters.") + .Matches(@"^[a-zA-Z\s\-'\.]*$") + .When(x => !string.IsNullOrEmpty(x.FirstName)) + .WithMessage("First name can only contain letters, spaces, hyphens, apostrophes, and periods."); + + RuleFor(x => x.LastName) + .MaximumLength(100) + .WithMessage("Last name must not exceed 100 characters.") + .Matches(@"^[a-zA-Z\s\-'\.]*$") + .When(x => !string.IsNullOrEmpty(x.LastName)) + .WithMessage("Last name can only contain letters, spaces, hyphens, apostrophes, and periods."); + + RuleFor(x => x.Company) + .MaximumLength(200) + .WithMessage("Company name must not exceed 200 characters."); + + RuleFor(x => x.AddressLine1) + .NotEmpty() + .WithMessage("Address line 1 is required.") + .MaximumLength(255) + .WithMessage("Address line 1 must not exceed 255 characters."); + + RuleFor(x => x.AddressLine2) + .MaximumLength(255) + .WithMessage("Address line 2 must not exceed 255 characters."); + + RuleFor(x => x.ApartmentNumber) + .MaximumLength(20) + .WithMessage("Apartment number must not exceed 20 characters."); + + RuleFor(x => x.BuildingNumber) + .MaximumLength(20) + .WithMessage("Building number must not exceed 20 characters."); + + RuleFor(x => x.Floor) + .MaximumLength(20) + .WithMessage("Floor must not exceed 20 characters."); + + RuleFor(x => x.City) + .NotEmpty() + .WithMessage("City is required.") + .MaximumLength(100) + .WithMessage("City must not exceed 100 characters.") + .Matches(@"^[a-zA-Z\s\-'\.]*$") + .WithMessage("City can only contain letters, spaces, hyphens, apostrophes, and periods."); + + RuleFor(x => x.State) + .NotEmpty() + .WithMessage("State is required.") + .MaximumLength(100) + .WithMessage("State must not exceed 100 characters."); + + RuleFor(x => x.PostalCode) + .NotEmpty() + .WithMessage("Postal code is required.") + .MaximumLength(20) + .WithMessage("Postal code must not exceed 20 characters.") + .Matches(@"^[a-zA-Z0-9\s\-]*$") + .WithMessage("Postal code can only contain letters, numbers, spaces, and hyphens."); + + RuleFor(x => x.Country) + .NotEmpty() + .WithMessage("Country is required.") + .MaximumLength(100) + .WithMessage("Country must not exceed 100 characters.") + .Matches(@"^[a-zA-Z\s\-'\.]*$") + .WithMessage("Country can only contain letters, spaces, hyphens, apostrophes, and periods."); + + RuleFor(x => x.PhoneNumber) + .MaximumLength(20) + .WithMessage("Phone number must not exceed 20 characters.") + .Matches(@"^[\+]?[0-9\s\-\(\)\.]*$") + .When(x => !string.IsNullOrEmpty(x.PhoneNumber)) + .WithMessage("Phone number format is invalid. Use numbers, spaces, hyphens, parentheses, periods, and optional + prefix."); + + RuleFor(x => x.Instructions) + .MaximumLength(500) + .WithMessage("Instructions must not exceed 500 characters."); + + RuleFor(x => x) + .Must(HaveNameOrCompany) + .WithMessage("Either first name and last name, or company name must be provided.") + .WithName("Address"); + } + + private static bool BeValidAddressType(string addressType) + { + var validTypes = new[] { "Home", "Work", "Billing", "Shipping", "Other" }; + return validTypes.Contains(addressType, StringComparer.OrdinalIgnoreCase); + } + + private static bool HaveNameOrCompany(CreateAddressCommand command) + { + var hasName = !string.IsNullOrWhiteSpace(command.FirstName) && !string.IsNullOrWhiteSpace(command.LastName); + var hasCompany = !string.IsNullOrWhiteSpace(command.Company); + return hasName || hasCompany; + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Validation/Orders/CreateOrderCommandValidator.cs b/src/Imprink.Application/Validation/Orders/CreateOrderCommandValidator.cs new file mode 100644 index 0000000..71ec02e --- /dev/null +++ b/src/Imprink.Application/Validation/Orders/CreateOrderCommandValidator.cs @@ -0,0 +1,69 @@ +using FluentValidation; +using Imprink.Application.Commands.Orders; + +namespace Imprink.Application.Validation.Orders; + +public class CreateOrderCommandValidator : AbstractValidator +{ + public CreateOrderCommandValidator() + { + RuleFor(x => x.Quantity) + .GreaterThan(0) + .WithMessage("Quantity must be greater than 0.") + .LessThanOrEqualTo(1000) + .WithMessage("Quantity cannot exceed 1000 items per order."); + + RuleFor(x => x.ProductId) + .NotEmpty() + .WithMessage("Product ID is required."); + + RuleFor(x => x.ProductVariantId) + .NotEmpty() + .WithMessage("Product variant ID is required."); + + RuleFor(x => x.AddressId) + .NotEmpty() + .WithMessage("Address ID is required."); + + RuleFor(x => x.CustomizationImageUrl) + .MaximumLength(2048) + .WithMessage("Customization image URL must not exceed 2048 characters.") + .Must(BeValidUrl) + .When(x => !string.IsNullOrEmpty(x.CustomizationImageUrl)) + .WithMessage("Customization image URL must be a valid URL."); + + RuleFor(x => x.CustomizationDescription) + .MaximumLength(2000) + .WithMessage("Customization description must not exceed 2000 characters."); + + RuleFor(x => x.OriginalImageUrls) + .Must(HaveValidImageUrls) + .When(x => x.OriginalImageUrls != null && x.OriginalImageUrls.Length > 0) + .WithMessage("All original image URLs must be valid URLs.") + .Must(x => x == null || x.Length <= 10) + .WithMessage("Cannot have more than 10 original images."); + + RuleForEach(x => x.OriginalImageUrls) + .MaximumLength(2048) + .WithMessage("Each original image URL must not exceed 2048 characters.") + .Must(BeValidUrl) + .WithMessage("Each original image URL must be a valid URL."); + } + + private static bool BeValidUrl(string url) + { + if (string.IsNullOrEmpty(url)) + return true; + + return Uri.TryCreate(url, UriKind.Absolute, out var result) + && (result.Scheme == Uri.UriSchemeHttp || result.Scheme == Uri.UriSchemeHttps); + } + + private static bool HaveValidImageUrls(string[]? urls) + { + if (urls == null || urls.Length == 0) + return true; + + return urls.All(url => !string.IsNullOrEmpty(url) && BeValidUrl(url)); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Validation/Products/UpdateProductCommandValidator.cs b/src/Imprink.Application/Validation/Products/UpdateProductCommandValidator.cs index 3607612..21ac02d 100644 --- a/src/Imprink.Application/Validation/Products/UpdateProductCommandValidator.cs +++ b/src/Imprink.Application/Validation/Products/UpdateProductCommandValidator.cs @@ -3,7 +3,7 @@ using Imprink.Application.Commands.Products; namespace Imprink.Application.Validation.Products; -public class UpdateProductCommandValidator : AbstractValidator +public class UpdateProductCommandValidator : AbstractValidator { public UpdateProductCommandValidator() { diff --git a/src/Imprink.Domain/Entities/Address.cs b/src/Imprink.Domain/Entities/Address.cs index 857f331..42e592b 100644 --- a/src/Imprink.Domain/Entities/Address.cs +++ b/src/Imprink.Domain/Entities/Address.cs @@ -4,11 +4,22 @@ public class Address : EntityBase { public required string UserId { get; set; } public required string AddressType { get; set; } - public required string Street { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Company { get; set; } + public required string AddressLine1 { get; set; } + public string? AddressLine2 { get; set; } + public string? ApartmentNumber { get; set; } + public string? BuildingNumber { get; set; } + public string? Floor { get; set; } public required string City { get; set; } public required string State { get; set; } public required string PostalCode { get; set; } public required string Country { get; set; } + public string? PhoneNumber { get; set; } + public string? Instructions { get; set; } public required bool IsDefault { get; set; } public required bool IsActive { get; set; } + + public virtual User User { get; set; } = null!; } \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/Order.cs b/src/Imprink.Domain/Entities/Order.cs index 66d5e68..e67ff02 100644 --- a/src/Imprink.Domain/Entities/Order.cs +++ b/src/Imprink.Domain/Entities/Order.cs @@ -2,17 +2,25 @@ namespace Imprink.Domain.Entities; public class Order : EntityBase { - public string UserId { get; set; } = null!; + public required string UserId { get; set; } public DateTime OrderDate { get; set; } - public decimal TotalPrice { get; set; } + public decimal Amount { get; set; } + public int Quantity { get; set; } + public Guid ProductId { get; set; } + public Guid? ProductVariantId { get; set; } public int OrderStatusId { get; set; } public int ShippingStatusId { get; set; } - public string OrderNumber { get; set; } = null!; - public string Notes { get; set; } = null!; + public string? Notes { get; set; } + public string? MerchantId { get; set; } + public string? CustomizationImageUrl { get; set; } + public string[] OriginalImageUrls { get; set; } = []; + public string? CustomizationDescription { get; set; } - public OrderStatus OrderStatus { get; set; } = null!; - public User User { get; set; } = null!; - public ShippingStatus ShippingStatus { get; set; } = null!; - public OrderAddress OrderAddress { get; set; } = null!; - public virtual ICollection OrderItems { get; set; } = new List(); + public virtual OrderStatus OrderStatus { get; set; } = null!; + public virtual User User { get; set; } = null!; + public virtual User? Merchant { get; set; } + public virtual ShippingStatus ShippingStatus { get; set; } = null!; + public virtual OrderAddress OrderAddress { get; set; } = null!; + public virtual Product Product { get; set; } = null!; + public virtual ProductVariant? ProductVariant { get; set; } } \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/OrderAddress.cs b/src/Imprink.Domain/Entities/OrderAddress.cs index 1505f65..49fb270 100644 --- a/src/Imprink.Domain/Entities/OrderAddress.cs +++ b/src/Imprink.Domain/Entities/OrderAddress.cs @@ -3,11 +3,21 @@ namespace Imprink.Domain.Entities; public class OrderAddress : EntityBase { public Guid OrderId { get; set; } - public required string Street { get; set; } + public required string AddressType { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Company { get; set; } + public required string AddressLine1 { get; set; } + public string? AddressLine2 { get; set; } + public string? ApartmentNumber { get; set; } + public string? BuildingNumber { get; set; } + public string? Floor { get; set; } public required string City { get; set; } public required string State { get; set; } public required string PostalCode { get; set; } public required string Country { get; set; } - - public virtual required Order Order { get; set; } + public string? PhoneNumber { get; set; } + public string? Instructions { get; set; } + + public virtual Order Order { get; set; } = null!; } \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/OrderItem.cs b/src/Imprink.Domain/Entities/OrderItem.cs deleted file mode 100644 index 6d8b769..0000000 --- a/src/Imprink.Domain/Entities/OrderItem.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Imprink.Domain.Entities; - -public class OrderItem : EntityBase -{ - public Guid OrderId { get; set; } - public Guid ProductId { get; set; } - public Guid? ProductVariantId { get; set; } - public int Quantity { get; set; } - public decimal UnitPrice { get; set; } - public decimal TotalPrice { get; set; } - public string CustomizationImageUrl { get; set; } = null!; - public string CustomizationDescription { get; set; } = null!; - - public Order Order { get; set; } = null!; - public Product Product { get; set; } = null!; - public ProductVariant ProductVariant { get; set; } = null!; -} \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/Product.cs b/src/Imprink.Domain/Entities/Product.cs index d640552..1476817 100644 --- a/src/Imprink.Domain/Entities/Product.cs +++ b/src/Imprink.Domain/Entities/Product.cs @@ -12,5 +12,5 @@ public class Product : EntityBase public virtual required Category Category { get; set; } public virtual ICollection ProductVariants { get; set; } = new List(); - public virtual ICollection OrderItems { get; set; } = new List(); + public virtual ICollection Orders { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/ProductVariant.cs b/src/Imprink.Domain/Entities/ProductVariant.cs index e9fee5b..4829f98 100644 --- a/src/Imprink.Domain/Entities/ProductVariant.cs +++ b/src/Imprink.Domain/Entities/ProductVariant.cs @@ -12,5 +12,5 @@ public class ProductVariant : EntityBase public bool IsActive { get; set; } public virtual required Product Product { get; set; } - public virtual ICollection OrderItems { get; set; } = new List(); + public virtual ICollection Orders { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/User.cs b/src/Imprink.Domain/Entities/User.cs index 59edbfa..3bfef13 100644 --- a/src/Imprink.Domain/Entities/User.cs +++ b/src/Imprink.Domain/Entities/User.cs @@ -8,14 +8,15 @@ public class User public required string Email { get; set; } public bool EmailVerified { get; set; } - public string? FirstName { get; set; } = null!; - public string? LastName { get; set; } = null!; + public string? FirstName { get; set; } + public string? LastName { get; set; } public string? PhoneNumber { get; set; } public required bool IsActive { get; set; } public virtual ICollection
Addresses { get; set; } = new List
(); public virtual ICollection UserRoles { get; set; } = new List(); public virtual ICollection Orders { get; set; } = new List(); + public virtual ICollection MerchantOrders { get; set; } = new List(); public Address? DefaultAddress => Addresses.FirstOrDefault(a => a is { IsDefault: true, IsActive: true }); public IEnumerable Roles => UserRoles.Select(ur => ur.Role); diff --git a/src/Imprink.Domain/Repositories/IAddressRepository.cs b/src/Imprink.Domain/Repositories/IAddressRepository.cs new file mode 100644 index 0000000..61267ce --- /dev/null +++ b/src/Imprink.Domain/Repositories/IAddressRepository.cs @@ -0,0 +1,22 @@ +using Imprink.Domain.Entities; + +namespace Imprink.Domain.Repositories; + +public interface IAddressRepository +{ + Task> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default); + Task> GetActiveByUserIdAsync(string userId, CancellationToken cancellationToken = default); + Task GetDefaultByUserIdAsync(string? userId, CancellationToken cancellationToken = default); + Task> GetByUserIdAndTypeAsync(string userId, string addressType, CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); + Task GetByIdAndUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default); + Task
AddAsync(Address address, CancellationToken cancellationToken = default); + Task
UpdateAsync(Address address, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); + Task DeleteByUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default); + Task SetDefaultAddressAsync(string? userId, Guid addressId, CancellationToken cancellationToken = default); + Task DeactivateAddressAsync(Guid addressId, CancellationToken cancellationToken = default); + Task ActivateAddressAsync(Guid addressId, CancellationToken cancellationToken = default); + Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); + Task IsUserAddressAsync(Guid id, string userId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/Imprink.Domain/Repositories/IOrderAddressRepository.cs b/src/Imprink.Domain/Repositories/IOrderAddressRepository.cs new file mode 100644 index 0000000..fb5f53d --- /dev/null +++ b/src/Imprink.Domain/Repositories/IOrderAddressRepository.cs @@ -0,0 +1,16 @@ +using Imprink.Domain.Entities; + +namespace Imprink.Domain.Repositories; + +public interface IOrderAddressRepository +{ + Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); + Task GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); + Task> GetByOrderIdsAsync(IEnumerable orderIds, CancellationToken cancellationToken = default); + Task AddAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default); + Task UpdateAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); + Task DeleteByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); + Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); + Task ExistsByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/Imprink.Domain/Repositories/IOrderItemRepository.cs b/src/Imprink.Domain/Repositories/IOrderItemRepository.cs deleted file mode 100644 index 7f8d123..0000000 --- a/src/Imprink.Domain/Repositories/IOrderItemRepository.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Imprink.Domain.Entities; - -namespace Imprink.Domain.Repositories; - -public interface IOrderItemRepository -{ - Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdWithProductAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdWithVariantAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default); - Task> GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); - Task> GetByOrderIdWithProductsAsync(Guid orderId, CancellationToken cancellationToken = default); - Task> GetByProductIdAsync(Guid productId, CancellationToken cancellationToken = default); - Task> GetByProductVariantIdAsync(Guid productVariantId, CancellationToken cancellationToken = default); - Task> GetCustomizedItemsAsync(CancellationToken cancellationToken = default); - Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); - Task AddAsync(OrderItem orderItem, CancellationToken cancellationToken = default); - Task> AddRangeAsync(IEnumerable orderItems, CancellationToken cancellationToken = default); - Task UpdateAsync(OrderItem orderItem, CancellationToken cancellationToken = default); - Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); - Task DeleteByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); - Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); - Task GetTotalValueByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); - Task GetQuantityByProductIdAsync(Guid productId, CancellationToken cancellationToken = default); - Task GetQuantityByVariantIdAsync(Guid productVariantId, CancellationToken cancellationToken = default); - Task> GetProductSalesCountAsync(DateTime? startDate = null, DateTime? endDate = null, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/Imprink.Domain/Repositories/IOrderRepository.cs b/src/Imprink.Domain/Repositories/IOrderRepository.cs index 0f53fba..01e7276 100644 --- a/src/Imprink.Domain/Repositories/IOrderRepository.cs +++ b/src/Imprink.Domain/Repositories/IOrderRepository.cs @@ -6,22 +6,20 @@ namespace Imprink.Domain.Repositories; public interface IOrderRepository { Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdWithItemsAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdWithAddressAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByOrderNumberAsync(string orderNumber, CancellationToken cancellationToken = default); - Task> GetPagedAsync(OrderFilterParameters filterParameters, CancellationToken cancellationToken = default); + Task GetByIdWithDetailsAsync(Guid id, CancellationToken cancellationToken = default); Task> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default); - Task> GetByUserIdPagedAsync(string userId, int pageNumber, int pageSize, CancellationToken cancellationToken = default); - Task> GetByOrderStatusAsync(int orderStatusId, CancellationToken cancellationToken = default); - Task> GetByShippingStatusAsync(int shippingStatusId, CancellationToken cancellationToken = default); + Task> GetByUserIdWithDetailsAsync(string userId, CancellationToken cancellationToken = default); + Task> GetByMerchantIdAsync(string merchantId, CancellationToken cancellationToken = default); + Task> GetByMerchantIdWithDetailsAsync(string merchantId, CancellationToken cancellationToken = default); Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); + Task> GetByStatusAsync(int statusId, CancellationToken cancellationToken = default); + Task> GetByShippingStatusAsync(int shippingStatusId, CancellationToken cancellationToken = default); Task AddAsync(Order order, CancellationToken cancellationToken = default); Task UpdateAsync(Order order, CancellationToken cancellationToken = default); - Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); - Task OrderNumberExistsAsync(string orderNumber, Guid? excludeId = null, CancellationToken cancellationToken = default); - Task GetTotalRevenueAsync(CancellationToken cancellationToken = default); - Task GetTotalRevenueByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); - Task GetOrderCountByStatusAsync(int orderStatusId, CancellationToken cancellationToken = default); + Task UpdateStatusAsync(Guid orderId, int statusId, CancellationToken cancellationToken = default); + Task UpdateShippingStatusAsync(Guid orderId, int shippingStatusId, CancellationToken cancellationToken = default); + Task AssignMerchantAsync(Guid orderId, string merchantId, CancellationToken cancellationToken = default); + Task UnassignMerchantAsync(Guid orderId, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs b/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs index d1e4bbd..be115a3 100644 --- a/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs @@ -17,10 +17,31 @@ public class AddressConfiguration : EntityBaseConfiguration
builder.Property(a => a.AddressType) .IsRequired() .HasMaxLength(50); + + builder.Property(a => a.FirstName) + .HasMaxLength(100); + + builder.Property(a => a.LastName) + .HasMaxLength(100); + + builder.Property(a => a.Company) + .HasMaxLength(200); - builder.Property(a => a.Street) + builder.Property(a => a.AddressLine1) .IsRequired() .HasMaxLength(200); + + builder.Property(a => a.AddressLine2) + .HasMaxLength(200); + + builder.Property(a => a.ApartmentNumber) + .HasMaxLength(20); + + builder.Property(a => a.BuildingNumber) + .HasMaxLength(20); + + builder.Property(a => a.Floor) + .HasMaxLength(20); builder.Property(a => a.City) .IsRequired() @@ -36,6 +57,12 @@ public class AddressConfiguration : EntityBaseConfiguration
builder.Property(a => a.Country) .IsRequired() .HasMaxLength(100); + + builder.Property(a => a.PhoneNumber) + .HasMaxLength(20); + + builder.Property(a => a.Instructions) + .HasMaxLength(500); builder.Property(a => a.IsDefault) .IsRequired() @@ -45,6 +72,11 @@ public class AddressConfiguration : EntityBaseConfiguration
.IsRequired() .HasDefaultValue(true); + builder.HasOne(a => a.User) + .WithMany(u => u.Addresses) + .HasForeignKey(a => a.UserId) + .OnDelete(DeleteBehavior.Cascade); + builder.HasIndex(a => a.UserId) .HasDatabaseName("IX_Address_UserId"); diff --git a/src/Imprink.Infrastructure/EntityBaseConfiguration.cs b/src/Imprink.Infrastructure/Configuration/EntityBaseConfiguration.cs similarity index 84% rename from src/Imprink.Infrastructure/EntityBaseConfiguration.cs rename to src/Imprink.Infrastructure/Configuration/EntityBaseConfiguration.cs index 4e4f717..30f376e 100644 --- a/src/Imprink.Infrastructure/EntityBaseConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/EntityBaseConfiguration.cs @@ -2,7 +2,7 @@ using Imprink.Domain.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Imprink.Infrastructure; +namespace Imprink.Infrastructure.Configuration; public class EntityBaseConfiguration : IEntityTypeConfiguration where T : EntityBase { @@ -12,20 +12,16 @@ public class EntityBaseConfiguration : IEntityTypeConfiguration where T : builder.Property(e => e.Id) .HasDefaultValueSql("NEWID()"); - - builder.Property(e => e.CreatedAt) - .IsRequired(); + + builder.Property(e => e.CreatedAt); builder.Property(e => e.ModifiedAt) - .IsRequired() .HasDefaultValueSql("GETUTCDATE()"); builder.Property(e => e.CreatedBy) - .IsRequired() .HasMaxLength(450); builder.Property(e => e.ModifiedBy) - .IsRequired() .HasMaxLength(450); builder.HasIndex(e => e.CreatedAt) diff --git a/src/Imprink.Infrastructure/Configuration/OrderAddressConfiguration.cs b/src/Imprink.Infrastructure/Configuration/OrderAddressConfiguration.cs index 0fe6e64..c701bc7 100644 --- a/src/Imprink.Infrastructure/Configuration/OrderAddressConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/OrderAddressConfiguration.cs @@ -12,10 +12,35 @@ public class OrderAddressConfiguration : EntityBaseConfiguration builder.Property(oa => oa.OrderId) .IsRequired(); + + builder.Property(oa => oa.AddressType) + .IsRequired() + .HasMaxLength(50); + + builder.Property(oa => oa.FirstName) + .HasMaxLength(100); + + builder.Property(oa => oa.LastName) + .HasMaxLength(100); + + builder.Property(oa => oa.Company) + .HasMaxLength(200); - builder.Property(oa => oa.Street) + builder.Property(oa => oa.AddressLine1) .IsRequired() .HasMaxLength(200); + + builder.Property(oa => oa.AddressLine2) + .HasMaxLength(200); + + builder.Property(oa => oa.ApartmentNumber) + .HasMaxLength(20); + + builder.Property(oa => oa.BuildingNumber) + .HasMaxLength(20); + + builder.Property(oa => oa.Floor) + .HasMaxLength(20); builder.Property(oa => oa.City) .IsRequired() @@ -32,6 +57,12 @@ public class OrderAddressConfiguration : EntityBaseConfiguration .IsRequired() .HasMaxLength(100); + builder.Property(oa => oa.PhoneNumber) + .HasMaxLength(20); + + builder.Property(oa => oa.Instructions) + .HasMaxLength(500); + builder.HasIndex(oa => oa.OrderId) .IsUnique() .HasDatabaseName("IX_OrderAddress_OrderId"); diff --git a/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs b/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs index e4a827b..1683554 100644 --- a/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using Imprink.Domain.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -5,73 +6,116 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace Imprink.Infrastructure.Configuration; public class OrderConfiguration : EntityBaseConfiguration +{ + public override void Configure(EntityTypeBuilder builder) { - public override void Configure(EntityTypeBuilder builder) - { - base.Configure(builder); + base.Configure(builder); + + builder.Property(o => o.UserId) + .IsRequired() + .HasMaxLength(450); + + builder.Property(o => o.OrderDate) + .IsRequired(); - builder.Property(o => o.UserId) - .IsRequired() - .HasMaxLength(450); + builder.Property(o => o.Amount) + .IsRequired() + .HasColumnType("decimal(18,2)"); - builder.Property(o => o.OrderDate) - .IsRequired(); - - builder.Property(o => o.TotalPrice) - .IsRequired() - .HasColumnType("decimal(18,2)"); - - builder.Property(o => o.OrderStatusId) - .IsRequired(); - - builder.Property(o => o.ShippingStatusId) - .IsRequired(); - - builder.Property(o => o.OrderNumber) - .IsRequired() - .HasMaxLength(50); - - builder.Property(o => o.Notes) - .HasMaxLength(1000); + builder.Property(o => o.Quantity) + .IsRequired() + .HasDefaultValue(1); - builder.HasOne(o => o.OrderStatus) - .WithMany(os => os.Orders) - .HasForeignKey(o => o.OrderStatusId) - .OnDelete(DeleteBehavior.Restrict); - - builder.HasOne(o => o.ShippingStatus) - .WithMany(ss => ss.Orders) - .HasForeignKey(o => o.ShippingStatusId) - .OnDelete(DeleteBehavior.Restrict); - - builder.HasOne(o => o.OrderAddress) - .WithOne(oa => oa.Order) - .HasForeignKey(oa => oa.OrderId) - .OnDelete(DeleteBehavior.Cascade); + builder.Property(o => o.ProductId) + .IsRequired(); - builder.HasOne(o => o.User) - .WithMany(u => u.Orders) - .HasForeignKey(o => o.UserId) - .HasPrincipalKey(u => u.Id) - .OnDelete(DeleteBehavior.Restrict); + builder.Property(o => o.OrderStatusId) + .IsRequired(); + + builder.Property(o => o.ShippingStatusId) + .IsRequired(); + + builder.Property(o => o.Notes) + .HasMaxLength(1000); - builder.HasIndex(o => o.UserId) - .HasDatabaseName("IX_Order_UserId"); - - builder.HasIndex(o => o.OrderNumber) - .IsUnique() - .HasDatabaseName("IX_Order_OrderNumber"); - - builder.HasIndex(o => o.OrderDate) - .HasDatabaseName("IX_Order_OrderDate"); - - builder.HasIndex(o => o.OrderStatusId) - .HasDatabaseName("IX_Order_OrderStatusId"); - - builder.HasIndex(o => o.ShippingStatusId) - .HasDatabaseName("IX_Order_ShippingStatusId"); - - builder.HasIndex(o => new { o.UserId, o.OrderDate }) - .HasDatabaseName("IX_Order_User_Date"); - } - } \ No newline at end of file + builder.Property(o => o.MerchantId) + .HasMaxLength(450); + + builder.Property(o => o.OriginalImageUrls) + .HasConversion( + v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null), + v => JsonSerializer.Deserialize(v, (JsonSerializerOptions?)null) ?? Array.Empty()) + .HasColumnType("nvarchar(max)"); + + builder.Property(o => o.CustomizationImageUrl) + .HasMaxLength(1000); + + builder.Property(o => o.CustomizationDescription) + .HasMaxLength(2000); + + builder.HasOne(o => o.OrderStatus) + .WithMany(os => os.Orders) + .HasForeignKey(o => o.OrderStatusId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(o => o.ShippingStatus) + .WithMany(ss => ss.Orders) + .HasForeignKey(o => o.ShippingStatusId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(o => o.OrderAddress) + .WithOne(oa => oa.Order) + .HasForeignKey(oa => oa.OrderId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasOne(o => o.User) + .WithMany(u => u.Orders) + .HasForeignKey(o => o.UserId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(o => o.Merchant) + .WithMany(u => u.MerchantOrders) + .HasForeignKey(o => o.MerchantId) + .OnDelete(DeleteBehavior.SetNull); + + builder.HasOne(o => o.Product) + .WithMany(p => p.Orders) + .HasForeignKey(o => o.ProductId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(o => o.ProductVariant) + .WithMany(pv => pv.Orders) + .HasForeignKey(o => o.ProductVariantId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasIndex(o => o.UserId) + .HasDatabaseName("IX_Order_UserId"); + + builder.HasIndex(o => o.OrderDate) + .HasDatabaseName("IX_Order_OrderDate"); + + builder.HasIndex(o => o.OrderStatusId) + .HasDatabaseName("IX_Order_OrderStatusId"); + + builder.HasIndex(o => o.ShippingStatusId) + .HasDatabaseName("IX_Order_ShippingStatusId"); + + builder.HasIndex(o => o.MerchantId) + .HasDatabaseName("IX_Order_MerchantId"); + + builder.HasIndex(o => o.ProductId) + .HasDatabaseName("IX_Order_ProductId"); + + builder.HasIndex(o => o.ProductVariantId) + .HasDatabaseName("IX_Order_ProductVariantId"); + + builder.HasIndex(o => new { o.UserId, o.OrderDate }) + .HasDatabaseName("IX_Order_User_Date"); + + builder.HasIndex(o => new { o.MerchantId, o.OrderDate }) + .HasDatabaseName("IX_Order_Merchant_Date"); + + builder.HasIndex(o => new { o.ProductId, o.OrderDate }) + .HasDatabaseName("IX_Order_Product_Date"); + } +} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Configuration/OrderItemConfiguration.cs b/src/Imprink.Infrastructure/Configuration/OrderItemConfiguration.cs deleted file mode 100644 index e95654e..0000000 --- a/src/Imprink.Infrastructure/Configuration/OrderItemConfiguration.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Imprink.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace Imprink.Infrastructure.Configuration; - -public class OrderItemConfiguration : EntityBaseConfiguration - { - public override void Configure(EntityTypeBuilder builder) - { - base.Configure(builder); - - builder.Property(oi => oi.OrderId) - .IsRequired(); - - builder.Property(oi => oi.ProductId) - .IsRequired(); - - builder.Property(oi => oi.Quantity) - .IsRequired() - .HasDefaultValue(1); - - builder.Property(oi => oi.UnitPrice) - .IsRequired() - .HasColumnType("decimal(18,2)"); - - builder.Property(oi => oi.TotalPrice) - .IsRequired() - .HasColumnType("decimal(18,2)"); - - builder.Property(oi => oi.CustomizationImageUrl) - .HasMaxLength(500); - - builder.Property(oi => oi.CustomizationDescription) - .HasMaxLength(2000); - - builder.HasOne(oi => oi.Order) - .WithMany(o => o.OrderItems) - .HasForeignKey(oi => oi.OrderId) - .OnDelete(DeleteBehavior.Cascade); - - builder.HasOne(oi => oi.Product) - .WithMany(p => p.OrderItems) - .HasForeignKey(oi => oi.ProductId) - .OnDelete(DeleteBehavior.Restrict); - - builder.HasOne(oi => oi.ProductVariant) - .WithMany(pv => pv.OrderItems) - .HasForeignKey(oi => oi.ProductVariantId) - .OnDelete(DeleteBehavior.Restrict); - - builder.HasIndex(oi => oi.OrderId) - .HasDatabaseName("IX_OrderItem_OrderId"); - - builder.HasIndex(oi => oi.ProductId) - .HasDatabaseName("IX_OrderItem_ProductId"); - - builder.HasIndex(oi => oi.ProductVariantId) - .HasDatabaseName("IX_OrderItem_ProductVariantId"); - - builder.HasIndex(oi => new { oi.OrderId, oi.ProductId }) - .HasDatabaseName("IX_OrderItem_Order_Product"); - } - } \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Configuration/ProductConfiguration.cs b/src/Imprink.Infrastructure/Configuration/ProductConfiguration.cs index c0ef17f..31db7d8 100644 --- a/src/Imprink.Infrastructure/Configuration/ProductConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/ProductConfiguration.cs @@ -34,18 +34,6 @@ public class ProductConfiguration : EntityBaseConfiguration builder.Property(p => p.CategoryId) .IsRequired(false); - - builder.Property(c => c.CreatedAt) - .IsRequired(false); - - builder.Property(c => c.CreatedBy) - .IsRequired(false); - - builder.Property(c => c.ModifiedAt) - .IsRequired(false); - - builder.Property(c => c.ModifiedBy) - .IsRequired(false); builder.HasOne(p => p.Category) .WithMany(c => c.Products) diff --git a/src/Imprink.Infrastructure/Configuration/ProductVariantConfiguration.cs b/src/Imprink.Infrastructure/Configuration/ProductVariantConfiguration.cs index 2d323a1..89de1e3 100644 --- a/src/Imprink.Infrastructure/Configuration/ProductVariantConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/ProductVariantConfiguration.cs @@ -37,18 +37,6 @@ public class ProductVariantConfiguration : EntityBaseConfiguration pv.IsActive) .IsRequired() .HasDefaultValue(true); - - builder.Property(c => c.CreatedAt) - .IsRequired(false); - - builder.Property(c => c.CreatedBy) - .IsRequired(false); - - builder.Property(c => c.ModifiedAt) - .IsRequired(false); - - builder.Property(c => c.ModifiedBy) - .IsRequired(false); builder.HasOne(pv => pv.Product) .WithMany(p => p.ProductVariants) diff --git a/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs b/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs index 15b71bc..42f4a54 100644 --- a/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs @@ -8,6 +8,8 @@ public class UserConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { + builder.HasKey(u => u.Id); + builder.Property(u => u.Id) .HasMaxLength(450) .ValueGeneratedNever(); @@ -23,10 +25,9 @@ public class UserConfiguration : IEntityTypeConfiguration builder.Property(u => u.Nickname) .IsRequired() .HasMaxLength(100); - + builder.Property(u => u.EmailVerified) - .IsRequired() - .HasMaxLength(100); + .IsRequired(); builder.Property(u => u.FirstName) .HasMaxLength(100); @@ -48,12 +49,6 @@ public class UserConfiguration : IEntityTypeConfiguration builder.HasIndex(u => u.IsActive) .HasDatabaseName("IX_User_IsActive"); - builder.HasMany(u => u.Addresses) - .WithOne() - .HasForeignKey(a => a.UserId) - .HasPrincipalKey(u => u.Id) - .OnDelete(DeleteBehavior.Cascade); - builder.Ignore(u => u.DefaultAddress); builder.Ignore(u => u.Roles); } diff --git a/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs b/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs index 844fe62..b0f9746 100644 --- a/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs +++ b/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs @@ -11,8 +11,6 @@ public class ApplicationDbContext(DbContextOptions options public DbSet Products { get; set; } public DbSet ProductVariants { get; set; } public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet OrderAddresses { get; set; } public DbSet
Addresses { get; set; } public DbSet OrderStatuses { get; set; } public DbSet ShippingStatuses { get; set; } @@ -20,6 +18,7 @@ public class ApplicationDbContext(DbContextOptions options public DbSet UserRole { get; set; } public DbSet Roles { get; set; } public DbSet Categories { get; set; } + public DbSet OrderAddresses { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -28,7 +27,6 @@ public class ApplicationDbContext(DbContextOptions options modelBuilder.ApplyConfiguration(new ProductConfiguration()); modelBuilder.ApplyConfiguration(new ProductVariantConfiguration()); modelBuilder.ApplyConfiguration(new OrderConfiguration()); - modelBuilder.ApplyConfiguration(new OrderItemConfiguration()); modelBuilder.ApplyConfiguration(new OrderAddressConfiguration()); modelBuilder.ApplyConfiguration(new AddressConfiguration()); modelBuilder.ApplyConfiguration(new OrderStatusConfiguration()); diff --git a/src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.Designer.cs b/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.Designer.cs similarity index 81% rename from src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.Designer.cs rename to src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.Designer.cs index 18e4c1b..8e73eb7 100644 --- a/src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.Designer.cs +++ b/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Imprink.Infrastructure.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20250617163555_InitialSetup")] + [Migration("20250625223159_InitialSetup")] partial class InitialSetup { /// @@ -25,128 +25,94 @@ namespace Imprink.Infrastructure.Migrations SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => + modelBuilder.Entity("Imprink.Domain.Entities.Address", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier") .HasDefaultValueSql("NEWID()"); - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") + b.Property("AddressLine1") .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Notes") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("OrderDate") - .HasColumnType("datetime2"); - - b.Property("OrderNumber") + b.Property("AddressType") .IsRequired() .HasMaxLength(50) .HasColumnType("nvarchar(50)"); - b.Property("OrderStatusId") - .HasColumnType("int"); + b.Property("ApartmentNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); - b.Property("ShippingStatusId") - .HasColumnType("int"); - - b.Property("TotalPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Order_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Order_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Order_ModifiedAt"); - - b.HasIndex("OrderDate") - .HasDatabaseName("IX_Order_OrderDate"); - - b.HasIndex("OrderNumber") - .IsUnique() - .HasDatabaseName("IX_Order_OrderNumber"); - - b.HasIndex("OrderStatusId") - .HasDatabaseName("IX_Order_OrderStatusId"); - - b.HasIndex("ShippingStatusId") - .HasDatabaseName("IX_Order_ShippingStatusId"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Order_UserId"); - - b.HasIndex("UserId", "OrderDate") - .HasDatabaseName("IX_Order_User_Date"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); + b.Property("BuildingNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); b.Property("City") .IsRequired() .HasMaxLength(100) .HasColumnType("nvarchar(100)"); + b.Property("Company") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + b.Property("Country") .IsRequired() .HasMaxLength(100) .HasColumnType("nvarchar(100)"); - b.Property("CreatedAt") + b.Property("CreatedAt") .HasColumnType("datetime2"); b.Property("CreatedBy") - .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("ModifiedAt") + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Floor") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Instructions") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDefault") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ModifiedAt") .ValueGeneratedOnAdd() .HasColumnType("datetime2") .HasDefaultValueSql("GETUTCDATE()"); b.Property("ModifiedBy") - .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); b.Property("PostalCode") .IsRequired() @@ -158,193 +124,35 @@ namespace Imprink.Infrastructure.Migrations .HasMaxLength(100) .HasColumnType("nvarchar(100)"); - b.Property("Street") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_OrderAddress_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_OrderAddress_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_OrderAddress_ModifiedAt"); - - b.HasIndex("OrderId") - .IsUnique() - .HasDatabaseName("IX_OrderAddress_OrderId"); - - b.ToTable("OrderAddresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") + b.Property("UserId") .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("CustomizationDescription") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("CustomizationImageUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductVariantId") - .HasColumnType("uniqueidentifier"); - - b.Property("Quantity") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("TotalPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - b.HasKey("Id"); b.HasIndex("CreatedAt") - .HasDatabaseName("IX_OrderItem_CreatedAt"); + .HasDatabaseName("IX_Address_CreatedAt"); b.HasIndex("CreatedBy") - .HasDatabaseName("IX_OrderItem_CreatedBy"); + .HasDatabaseName("IX_Address_CreatedBy"); b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_OrderItem_ModifiedAt"); + .HasDatabaseName("IX_Address_ModifiedAt"); - b.HasIndex("OrderId") - .HasDatabaseName("IX_OrderItem_OrderId"); + b.HasIndex("UserId") + .HasDatabaseName("IX_Address_UserId"); - b.HasIndex("ProductId") - .HasDatabaseName("IX_OrderItem_ProductId"); + b.HasIndex("UserId", "AddressType") + .HasDatabaseName("IX_Address_User_Type"); - b.HasIndex("ProductVariantId") - .HasDatabaseName("IX_OrderItem_ProductVariantId"); + b.HasIndex("UserId", "IsDefault") + .HasDatabaseName("IX_Address_User_Default"); - b.HasIndex("OrderId", "ProductId") - .HasDatabaseName("IX_OrderItem_Order_Product"); - - b.ToTable("OrderItems"); + b.ToTable("Addresses"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_OrderStatus_Name"); - - b.ToTable("OrderStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Pending" - }, - new - { - Id = 1, - Name = "Processing" - }, - new - { - Id = 2, - Name = "Completed" - }, - new - { - Id = 3, - Name = "Cancelled" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.ShippingStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_ShippingStatus_Name"); - - b.ToTable("ShippingStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Prepping" - }, - new - { - Id = 1, - Name = "Packaging" - }, - new - { - Id = 2, - Name = "Shipped" - }, - new - { - Id = 3, - Name = "Delivered" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -461,7 +269,273 @@ namespace Imprink.Infrastructure.Migrations }); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("NEWID()"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("CustomizationDescription") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CustomizationImageUrl") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("MerchantId") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("ModifiedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("ModifiedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OrderDate") + .HasColumnType("datetime2"); + + b.Property("OrderStatusId") + .HasColumnType("int"); + + b.Property("OriginalImageUrls") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProductVariantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("ShippingStatusId") + .HasColumnType("int"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_Order_CreatedAt"); + + b.HasIndex("CreatedBy") + .HasDatabaseName("IX_Order_CreatedBy"); + + b.HasIndex("MerchantId") + .HasDatabaseName("IX_Order_MerchantId"); + + b.HasIndex("ModifiedAt") + .HasDatabaseName("IX_Order_ModifiedAt"); + + b.HasIndex("OrderDate") + .HasDatabaseName("IX_Order_OrderDate"); + + b.HasIndex("OrderStatusId") + .HasDatabaseName("IX_Order_OrderStatusId"); + + b.HasIndex("ProductId") + .HasDatabaseName("IX_Order_ProductId"); + + b.HasIndex("ProductVariantId") + .HasDatabaseName("IX_Order_ProductVariantId"); + + b.HasIndex("ShippingStatusId") + .HasDatabaseName("IX_Order_ShippingStatusId"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_Order_UserId"); + + b.HasIndex("MerchantId", "OrderDate") + .HasDatabaseName("IX_Order_Merchant_Date"); + + b.HasIndex("ProductId", "OrderDate") + .HasDatabaseName("IX_Order_Product_Date"); + + b.HasIndex("UserId", "OrderDate") + .HasDatabaseName("IX_Order_User_Date"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("NEWID()"); + + b.Property("AddressLine1") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("AddressType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ApartmentNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("BuildingNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Company") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Floor") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Instructions") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("LastName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ModifiedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("ModifiedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_OrderAddress_CreatedAt"); + + b.HasIndex("CreatedBy") + .HasDatabaseName("IX_OrderAddress_CreatedBy"); + + b.HasIndex("ModifiedAt") + .HasDatabaseName("IX_OrderAddress_ModifiedAt"); + + b.HasIndex("OrderId") + .IsUnique() + .HasDatabaseName("IX_OrderAddress_OrderId"); + + b.ToTable("OrderAddresses"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_OrderStatus_Name"); + + b.ToTable("OrderStatuses"); + + b.HasData( + new + { + Id = 0, + Name = "Pending" + }, + new + { + Id = 1, + Name = "Processing" + }, + new + { + Id = 2, + Name = "Completed" + }, + new + { + Id = 3, + Name = "Cancelled" + }); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -545,7 +619,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("Products"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -631,100 +705,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("ProductVariants"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Address", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("AddressType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("IsDefault") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("State") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Street") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Address_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Address_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Address_ModifiedAt"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Address_UserId"); - - b.HasIndex("UserId", "AddressType") - .HasDatabaseName("IX_Address_User_Type"); - - b.HasIndex("UserId", "IsDefault") - .HasDatabaseName("IX_Address_User_Default"); - - b.ToTable("Addresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Users.Role", b => + modelBuilder.Entity("Imprink.Domain.Entities.Role", b => { b.Property("Id") .HasColumnType("uniqueidentifier"); @@ -755,7 +736,48 @@ namespace Imprink.Infrastructure.Migrations }); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.User", b => + modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_ShippingStatus_Name"); + + b.ToTable("ShippingStatuses"); + + b.HasData( + new + { + Id = 0, + Name = "Prepping" + }, + new + { + Id = 1, + Name = "Packaging" + }, + new + { + Id = 2, + Name = "Shipped" + }, + new + { + Id = 3, + Name = "Delivered" + }); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.User", b => { b.Property("Id") .HasMaxLength(450) @@ -767,7 +789,6 @@ namespace Imprink.Infrastructure.Migrations .HasColumnType("nvarchar(256)"); b.Property("EmailVerified") - .HasMaxLength(100) .HasColumnType("bit"); b.Property("FirstName") @@ -809,7 +830,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("Users"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.UserRole", b => + modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => { b.Property("UserId") .HasMaxLength(450) @@ -829,73 +850,20 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("UserRole"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => + modelBuilder.Entity("Imprink.Domain.Entities.Address", b => { - b.HasOne("Imprink.Domain.Entities.Orders.OrderStatus", "OrderStatus") - .WithMany("Orders") - .HasForeignKey("OrderStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Orders.ShippingStatus", "ShippingStatus") - .WithMany("Orders") - .HasForeignKey("ShippingStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Users.User", "User") - .WithMany("Orders") + b.HasOne("Imprink.Domain.Entities.User", "User") + .WithMany("Addresses") .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("OrderStatus"); - - b.Navigation("ShippingStatus"); - b.Navigation("User"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderAddress", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { - b.HasOne("Imprink.Domain.Entities.Orders.Order", "Order") - .WithOne("OrderAddress") - .HasForeignKey("Imprink.Domain.Entities.Orders.OrderAddress", "OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Order"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderItem", b => - { - b.HasOne("Imprink.Domain.Entities.Orders.Order", "Order") - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Product.Product", "Product") - .WithMany("OrderItems") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Product.ProductVariant", "ProductVariant") - .WithMany("OrderItems") - .HasForeignKey("ProductVariantId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Order"); - - b.Navigation("Product"); - - b.Navigation("ProductVariant"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => - { - b.HasOne("Imprink.Domain.Entities.Product.Category", "ParentCategory") + b.HasOne("Imprink.Domain.Entities.Category", "ParentCategory") .WithMany("SubCategories") .HasForeignKey("ParentCategoryId") .OnDelete(DeleteBehavior.Restrict); @@ -903,9 +871,69 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("ParentCategory"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => { - b.HasOne("Imprink.Domain.Entities.Product.Category", "Category") + b.HasOne("Imprink.Domain.Entities.User", "Merchant") + .WithMany("MerchantOrders") + .HasForeignKey("MerchantId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Imprink.Domain.Entities.OrderStatus", "OrderStatus") + .WithMany("Orders") + .HasForeignKey("OrderStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.Product", "Product") + .WithMany("Orders") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.ProductVariant", "ProductVariant") + .WithMany("Orders") + .HasForeignKey("ProductVariantId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Imprink.Domain.Entities.ShippingStatus", "ShippingStatus") + .WithMany("Orders") + .HasForeignKey("ShippingStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.User", "User") + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Merchant"); + + b.Navigation("OrderStatus"); + + b.Navigation("Product"); + + b.Navigation("ProductVariant"); + + b.Navigation("ShippingStatus"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => + { + b.HasOne("Imprink.Domain.Entities.Order", "Order") + .WithOne("OrderAddress") + .HasForeignKey("Imprink.Domain.Entities.OrderAddress", "OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => + { + b.HasOne("Imprink.Domain.Entities.Category", "Category") .WithMany("Products") .HasForeignKey("CategoryId") .OnDelete(DeleteBehavior.SetNull); @@ -913,9 +941,9 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("Category"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { - b.HasOne("Imprink.Domain.Entities.Product.Product", "Product") + b.HasOne("Imprink.Domain.Entities.Product", "Product") .WithMany("ProductVariants") .HasForeignKey("ProductId") .OnDelete(DeleteBehavior.Cascade) @@ -924,24 +952,15 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("Product"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Address", b => + modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => { - b.HasOne("Imprink.Domain.Entities.Users.User", null) - .WithMany("Addresses") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Users.UserRole", b => - { - b.HasOne("Imprink.Domain.Entities.Users.Role", "Role") + b.HasOne("Imprink.Domain.Entities.Role", "Role") .WithMany("UserRoles") .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - b.HasOne("Imprink.Domain.Entities.Users.User", "User") + b.HasOne("Imprink.Domain.Entities.User", "User") .WithMany("UserRoles") .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -952,52 +971,52 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("User"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => - { - b.Navigation("OrderAddress") - .IsRequired(); - - b.Navigation("OrderItems"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.ShippingStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { b.Navigation("Products"); b.Navigation("SubCategories"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => { - b.Navigation("OrderItems"); + b.Navigation("OrderAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => + { + b.Navigation("Orders"); b.Navigation("ProductVariants"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { - b.Navigation("OrderItems"); + b.Navigation("Orders"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Role", b => + modelBuilder.Entity("Imprink.Domain.Entities.Role", b => { b.Navigation("UserRoles"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.User", b => + modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.User", b => { b.Navigation("Addresses"); + b.Navigation("MerchantOrders"); + b.Navigation("Orders"); b.Navigation("UserRoles"); diff --git a/src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.cs b/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.cs similarity index 87% rename from src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.cs rename to src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.cs index 30c801b..9606a5c 100644 --- a/src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.cs +++ b/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.cs @@ -84,7 +84,7 @@ namespace Imprink.Infrastructure.Migrations Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), Nickname = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - EmailVerified = table.Column(type: "bit", maxLength: 100, nullable: false), + EmailVerified = table.Column(type: "bit", nullable: false), FirstName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), LastName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), PhoneNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), @@ -130,17 +130,26 @@ namespace Imprink.Infrastructure.Migrations Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), UserId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), AddressType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Street = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + FirstName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + LastName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Company = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + AddressLine1 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + AddressLine2 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + ApartmentNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + BuildingNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + Floor = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), State = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), PostalCode = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), Country = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + PhoneNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + Instructions = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), IsDefault = table.Column(type: "bit", nullable: false, defaultValue: false), IsActive = table.Column(type: "bit", nullable: false, defaultValue: true), - CreatedAt = table.Column(type: "datetime2", nullable: false), - ModifiedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false) + CreatedAt = table.Column(type: "datetime2", nullable: true), + ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), + CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), + ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) }, constraints: table => { @@ -153,46 +162,6 @@ namespace Imprink.Infrastructure.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), - UserId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - OrderDate = table.Column(type: "datetime2", nullable: false), - TotalPrice = table.Column(type: "decimal(18,2)", nullable: false), - OrderStatusId = table.Column(type: "int", nullable: false), - ShippingStatusId = table.Column(type: "int", nullable: false), - OrderNumber = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Notes = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false), - ModifiedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - table.ForeignKey( - name: "FK_Orders_OrderStatuses_OrderStatusId", - column: x => x.OrderStatusId, - principalTable: "OrderStatuses", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Orders_ShippingStatuses_ShippingStatusId", - column: x => x.ShippingStatusId, - principalTable: "ShippingStatuses", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Orders_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - migrationBuilder.CreateTable( name: "UserRole", columns: table => new @@ -246,21 +215,95 @@ namespace Imprink.Infrastructure.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), + UserId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), + OrderDate = table.Column(type: "datetime2", nullable: false), + Amount = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false, defaultValue: 1), + ProductId = table.Column(type: "uniqueidentifier", nullable: false), + ProductVariantId = table.Column(type: "uniqueidentifier", nullable: true), + OrderStatusId = table.Column(type: "int", nullable: false), + ShippingStatusId = table.Column(type: "int", nullable: false), + Notes = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + MerchantId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), + CustomizationImageUrl = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + OriginalImageUrls = table.Column(type: "nvarchar(max)", nullable: false), + CustomizationDescription = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: true), + ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), + CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), + ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + table.ForeignKey( + name: "FK_Orders_OrderStatuses_OrderStatusId", + column: x => x.OrderStatusId, + principalTable: "OrderStatuses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Orders_ProductVariants_ProductVariantId", + column: x => x.ProductVariantId, + principalTable: "ProductVariants", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Orders_Products_ProductId", + column: x => x.ProductId, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Orders_ShippingStatuses_ShippingStatusId", + column: x => x.ShippingStatusId, + principalTable: "ShippingStatuses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Orders_Users_MerchantId", + column: x => x.MerchantId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_Orders_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + migrationBuilder.CreateTable( name: "OrderAddresses", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), OrderId = table.Column(type: "uniqueidentifier", nullable: false), - Street = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + AddressType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + FirstName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + LastName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Company = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + AddressLine1 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + AddressLine2 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + ApartmentNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + BuildingNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + Floor = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), State = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), PostalCode = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), Country = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false), - ModifiedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false) + PhoneNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + Instructions = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: true), + ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), + CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), + ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) }, constraints: table => { @@ -273,47 +316,6 @@ namespace Imprink.Infrastructure.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), - OrderId = table.Column(type: "uniqueidentifier", nullable: false), - ProductId = table.Column(type: "uniqueidentifier", nullable: false), - ProductVariantId = table.Column(type: "uniqueidentifier", nullable: true), - Quantity = table.Column(type: "int", nullable: false, defaultValue: 1), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - TotalPrice = table.Column(type: "decimal(18,2)", nullable: false), - CustomizationImageUrl = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), - CustomizationDescription = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false), - ModifiedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_OrderItems_ProductVariants_ProductVariantId", - column: x => x.ProductVariantId, - principalTable: "ProductVariants", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_OrderItems_Products_ProductId", - column: x => x.ProductId, - principalTable: "Products", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - migrationBuilder.InsertData( table: "Categories", columns: new[] { "Id", "CreatedAt", "CreatedBy", "Description", "ImageUrl", "IsActive", "ModifiedAt", "ModifiedBy", "Name", "ParentCategoryId", "SortOrder" }, @@ -446,41 +448,6 @@ namespace Imprink.Infrastructure.Migrations column: "OrderId", unique: true); - migrationBuilder.CreateIndex( - name: "IX_OrderItem_CreatedAt", - table: "OrderItems", - column: "CreatedAt"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_CreatedBy", - table: "OrderItems", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_ModifiedAt", - table: "OrderItems", - column: "ModifiedAt"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_Order_Product", - table: "OrderItems", - columns: new[] { "OrderId", "ProductId" }); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_OrderId", - table: "OrderItems", - column: "OrderId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_ProductId", - table: "OrderItems", - column: "ProductId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_ProductVariantId", - table: "OrderItems", - column: "ProductVariantId"); - migrationBuilder.CreateIndex( name: "IX_Order_CreatedAt", table: "Orders", @@ -491,6 +458,16 @@ namespace Imprink.Infrastructure.Migrations table: "Orders", column: "CreatedBy"); + migrationBuilder.CreateIndex( + name: "IX_Order_Merchant_Date", + table: "Orders", + columns: new[] { "MerchantId", "OrderDate" }); + + migrationBuilder.CreateIndex( + name: "IX_Order_MerchantId", + table: "Orders", + column: "MerchantId"); + migrationBuilder.CreateIndex( name: "IX_Order_ModifiedAt", table: "Orders", @@ -501,17 +478,26 @@ namespace Imprink.Infrastructure.Migrations table: "Orders", column: "OrderDate"); - migrationBuilder.CreateIndex( - name: "IX_Order_OrderNumber", - table: "Orders", - column: "OrderNumber", - unique: true); - migrationBuilder.CreateIndex( name: "IX_Order_OrderStatusId", table: "Orders", column: "OrderStatusId"); + migrationBuilder.CreateIndex( + name: "IX_Order_Product_Date", + table: "Orders", + columns: new[] { "ProductId", "OrderDate" }); + + migrationBuilder.CreateIndex( + name: "IX_Order_ProductId", + table: "Orders", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_Order_ProductVariantId", + table: "Orders", + column: "ProductVariantId"); + migrationBuilder.CreateIndex( name: "IX_Order_ShippingStatusId", table: "Orders", @@ -659,24 +645,21 @@ namespace Imprink.Infrastructure.Migrations migrationBuilder.DropTable( name: "OrderAddresses"); - migrationBuilder.DropTable( - name: "OrderItems"); - migrationBuilder.DropTable( name: "UserRole"); migrationBuilder.DropTable( name: "Orders"); - migrationBuilder.DropTable( - name: "ProductVariants"); - migrationBuilder.DropTable( name: "Roles"); migrationBuilder.DropTable( name: "OrderStatuses"); + migrationBuilder.DropTable( + name: "ProductVariants"); + migrationBuilder.DropTable( name: "ShippingStatuses"); diff --git a/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs index 1195e8b..9d264b9 100644 --- a/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -22,128 +22,94 @@ namespace Imprink.Infrastructure.Migrations SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => + modelBuilder.Entity("Imprink.Domain.Entities.Address", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier") .HasDefaultValueSql("NEWID()"); - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") + b.Property("AddressLine1") .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Notes") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("OrderDate") - .HasColumnType("datetime2"); - - b.Property("OrderNumber") + b.Property("AddressType") .IsRequired() .HasMaxLength(50) .HasColumnType("nvarchar(50)"); - b.Property("OrderStatusId") - .HasColumnType("int"); + b.Property("ApartmentNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); - b.Property("ShippingStatusId") - .HasColumnType("int"); - - b.Property("TotalPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Order_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Order_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Order_ModifiedAt"); - - b.HasIndex("OrderDate") - .HasDatabaseName("IX_Order_OrderDate"); - - b.HasIndex("OrderNumber") - .IsUnique() - .HasDatabaseName("IX_Order_OrderNumber"); - - b.HasIndex("OrderStatusId") - .HasDatabaseName("IX_Order_OrderStatusId"); - - b.HasIndex("ShippingStatusId") - .HasDatabaseName("IX_Order_ShippingStatusId"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Order_UserId"); - - b.HasIndex("UserId", "OrderDate") - .HasDatabaseName("IX_Order_User_Date"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); + b.Property("BuildingNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); b.Property("City") .IsRequired() .HasMaxLength(100) .HasColumnType("nvarchar(100)"); + b.Property("Company") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + b.Property("Country") .IsRequired() .HasMaxLength(100) .HasColumnType("nvarchar(100)"); - b.Property("CreatedAt") + b.Property("CreatedAt") .HasColumnType("datetime2"); b.Property("CreatedBy") - .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("ModifiedAt") + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Floor") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Instructions") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDefault") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ModifiedAt") .ValueGeneratedOnAdd() .HasColumnType("datetime2") .HasDefaultValueSql("GETUTCDATE()"); b.Property("ModifiedBy") - .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); b.Property("PostalCode") .IsRequired() @@ -155,193 +121,35 @@ namespace Imprink.Infrastructure.Migrations .HasMaxLength(100) .HasColumnType("nvarchar(100)"); - b.Property("Street") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_OrderAddress_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_OrderAddress_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_OrderAddress_ModifiedAt"); - - b.HasIndex("OrderId") - .IsUnique() - .HasDatabaseName("IX_OrderAddress_OrderId"); - - b.ToTable("OrderAddresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") + b.Property("UserId") .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("CustomizationDescription") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("CustomizationImageUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductVariantId") - .HasColumnType("uniqueidentifier"); - - b.Property("Quantity") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("TotalPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - b.HasKey("Id"); b.HasIndex("CreatedAt") - .HasDatabaseName("IX_OrderItem_CreatedAt"); + .HasDatabaseName("IX_Address_CreatedAt"); b.HasIndex("CreatedBy") - .HasDatabaseName("IX_OrderItem_CreatedBy"); + .HasDatabaseName("IX_Address_CreatedBy"); b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_OrderItem_ModifiedAt"); + .HasDatabaseName("IX_Address_ModifiedAt"); - b.HasIndex("OrderId") - .HasDatabaseName("IX_OrderItem_OrderId"); + b.HasIndex("UserId") + .HasDatabaseName("IX_Address_UserId"); - b.HasIndex("ProductId") - .HasDatabaseName("IX_OrderItem_ProductId"); + b.HasIndex("UserId", "AddressType") + .HasDatabaseName("IX_Address_User_Type"); - b.HasIndex("ProductVariantId") - .HasDatabaseName("IX_OrderItem_ProductVariantId"); + b.HasIndex("UserId", "IsDefault") + .HasDatabaseName("IX_Address_User_Default"); - b.HasIndex("OrderId", "ProductId") - .HasDatabaseName("IX_OrderItem_Order_Product"); - - b.ToTable("OrderItems"); + b.ToTable("Addresses"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_OrderStatus_Name"); - - b.ToTable("OrderStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Pending" - }, - new - { - Id = 1, - Name = "Processing" - }, - new - { - Id = 2, - Name = "Completed" - }, - new - { - Id = 3, - Name = "Cancelled" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.ShippingStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_ShippingStatus_Name"); - - b.ToTable("ShippingStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Prepping" - }, - new - { - Id = 1, - Name = "Packaging" - }, - new - { - Id = 2, - Name = "Shipped" - }, - new - { - Id = 3, - Name = "Delivered" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -458,7 +266,273 @@ namespace Imprink.Infrastructure.Migrations }); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("NEWID()"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("CustomizationDescription") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CustomizationImageUrl") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("MerchantId") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("ModifiedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("ModifiedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OrderDate") + .HasColumnType("datetime2"); + + b.Property("OrderStatusId") + .HasColumnType("int"); + + b.Property("OriginalImageUrls") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProductVariantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("ShippingStatusId") + .HasColumnType("int"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_Order_CreatedAt"); + + b.HasIndex("CreatedBy") + .HasDatabaseName("IX_Order_CreatedBy"); + + b.HasIndex("MerchantId") + .HasDatabaseName("IX_Order_MerchantId"); + + b.HasIndex("ModifiedAt") + .HasDatabaseName("IX_Order_ModifiedAt"); + + b.HasIndex("OrderDate") + .HasDatabaseName("IX_Order_OrderDate"); + + b.HasIndex("OrderStatusId") + .HasDatabaseName("IX_Order_OrderStatusId"); + + b.HasIndex("ProductId") + .HasDatabaseName("IX_Order_ProductId"); + + b.HasIndex("ProductVariantId") + .HasDatabaseName("IX_Order_ProductVariantId"); + + b.HasIndex("ShippingStatusId") + .HasDatabaseName("IX_Order_ShippingStatusId"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_Order_UserId"); + + b.HasIndex("MerchantId", "OrderDate") + .HasDatabaseName("IX_Order_Merchant_Date"); + + b.HasIndex("ProductId", "OrderDate") + .HasDatabaseName("IX_Order_Product_Date"); + + b.HasIndex("UserId", "OrderDate") + .HasDatabaseName("IX_Order_User_Date"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("NEWID()"); + + b.Property("AddressLine1") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("AddressType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ApartmentNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("BuildingNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Company") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Floor") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Instructions") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("LastName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ModifiedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("ModifiedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_OrderAddress_CreatedAt"); + + b.HasIndex("CreatedBy") + .HasDatabaseName("IX_OrderAddress_CreatedBy"); + + b.HasIndex("ModifiedAt") + .HasDatabaseName("IX_OrderAddress_ModifiedAt"); + + b.HasIndex("OrderId") + .IsUnique() + .HasDatabaseName("IX_OrderAddress_OrderId"); + + b.ToTable("OrderAddresses"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_OrderStatus_Name"); + + b.ToTable("OrderStatuses"); + + b.HasData( + new + { + Id = 0, + Name = "Pending" + }, + new + { + Id = 1, + Name = "Processing" + }, + new + { + Id = 2, + Name = "Completed" + }, + new + { + Id = 3, + Name = "Cancelled" + }); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -542,7 +616,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("Products"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -628,100 +702,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("ProductVariants"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Address", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("AddressType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("IsDefault") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("State") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Street") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Address_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Address_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Address_ModifiedAt"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Address_UserId"); - - b.HasIndex("UserId", "AddressType") - .HasDatabaseName("IX_Address_User_Type"); - - b.HasIndex("UserId", "IsDefault") - .HasDatabaseName("IX_Address_User_Default"); - - b.ToTable("Addresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Users.Role", b => + modelBuilder.Entity("Imprink.Domain.Entities.Role", b => { b.Property("Id") .HasColumnType("uniqueidentifier"); @@ -752,7 +733,48 @@ namespace Imprink.Infrastructure.Migrations }); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.User", b => + modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_ShippingStatus_Name"); + + b.ToTable("ShippingStatuses"); + + b.HasData( + new + { + Id = 0, + Name = "Prepping" + }, + new + { + Id = 1, + Name = "Packaging" + }, + new + { + Id = 2, + Name = "Shipped" + }, + new + { + Id = 3, + Name = "Delivered" + }); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.User", b => { b.Property("Id") .HasMaxLength(450) @@ -764,7 +786,6 @@ namespace Imprink.Infrastructure.Migrations .HasColumnType("nvarchar(256)"); b.Property("EmailVerified") - .HasMaxLength(100) .HasColumnType("bit"); b.Property("FirstName") @@ -806,7 +827,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("Users"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.UserRole", b => + modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => { b.Property("UserId") .HasMaxLength(450) @@ -826,73 +847,20 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("UserRole"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => + modelBuilder.Entity("Imprink.Domain.Entities.Address", b => { - b.HasOne("Imprink.Domain.Entities.Orders.OrderStatus", "OrderStatus") - .WithMany("Orders") - .HasForeignKey("OrderStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Orders.ShippingStatus", "ShippingStatus") - .WithMany("Orders") - .HasForeignKey("ShippingStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Users.User", "User") - .WithMany("Orders") + b.HasOne("Imprink.Domain.Entities.User", "User") + .WithMany("Addresses") .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("OrderStatus"); - - b.Navigation("ShippingStatus"); - b.Navigation("User"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderAddress", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { - b.HasOne("Imprink.Domain.Entities.Orders.Order", "Order") - .WithOne("OrderAddress") - .HasForeignKey("Imprink.Domain.Entities.Orders.OrderAddress", "OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Order"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderItem", b => - { - b.HasOne("Imprink.Domain.Entities.Orders.Order", "Order") - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Product.Product", "Product") - .WithMany("OrderItems") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Product.ProductVariant", "ProductVariant") - .WithMany("OrderItems") - .HasForeignKey("ProductVariantId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Order"); - - b.Navigation("Product"); - - b.Navigation("ProductVariant"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => - { - b.HasOne("Imprink.Domain.Entities.Product.Category", "ParentCategory") + b.HasOne("Imprink.Domain.Entities.Category", "ParentCategory") .WithMany("SubCategories") .HasForeignKey("ParentCategoryId") .OnDelete(DeleteBehavior.Restrict); @@ -900,9 +868,69 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("ParentCategory"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => { - b.HasOne("Imprink.Domain.Entities.Product.Category", "Category") + b.HasOne("Imprink.Domain.Entities.User", "Merchant") + .WithMany("MerchantOrders") + .HasForeignKey("MerchantId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Imprink.Domain.Entities.OrderStatus", "OrderStatus") + .WithMany("Orders") + .HasForeignKey("OrderStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.Product", "Product") + .WithMany("Orders") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.ProductVariant", "ProductVariant") + .WithMany("Orders") + .HasForeignKey("ProductVariantId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Imprink.Domain.Entities.ShippingStatus", "ShippingStatus") + .WithMany("Orders") + .HasForeignKey("ShippingStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.User", "User") + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Merchant"); + + b.Navigation("OrderStatus"); + + b.Navigation("Product"); + + b.Navigation("ProductVariant"); + + b.Navigation("ShippingStatus"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => + { + b.HasOne("Imprink.Domain.Entities.Order", "Order") + .WithOne("OrderAddress") + .HasForeignKey("Imprink.Domain.Entities.OrderAddress", "OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => + { + b.HasOne("Imprink.Domain.Entities.Category", "Category") .WithMany("Products") .HasForeignKey("CategoryId") .OnDelete(DeleteBehavior.SetNull); @@ -910,9 +938,9 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("Category"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { - b.HasOne("Imprink.Domain.Entities.Product.Product", "Product") + b.HasOne("Imprink.Domain.Entities.Product", "Product") .WithMany("ProductVariants") .HasForeignKey("ProductId") .OnDelete(DeleteBehavior.Cascade) @@ -921,24 +949,15 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("Product"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Address", b => + modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => { - b.HasOne("Imprink.Domain.Entities.Users.User", null) - .WithMany("Addresses") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Users.UserRole", b => - { - b.HasOne("Imprink.Domain.Entities.Users.Role", "Role") + b.HasOne("Imprink.Domain.Entities.Role", "Role") .WithMany("UserRoles") .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - b.HasOne("Imprink.Domain.Entities.Users.User", "User") + b.HasOne("Imprink.Domain.Entities.User", "User") .WithMany("UserRoles") .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -949,52 +968,52 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("User"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => - { - b.Navigation("OrderAddress") - .IsRequired(); - - b.Navigation("OrderItems"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.ShippingStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { b.Navigation("Products"); b.Navigation("SubCategories"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => { - b.Navigation("OrderItems"); + b.Navigation("OrderAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => + { + b.Navigation("Orders"); b.Navigation("ProductVariants"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { - b.Navigation("OrderItems"); + b.Navigation("Orders"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Role", b => + modelBuilder.Entity("Imprink.Domain.Entities.Role", b => { b.Navigation("UserRoles"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.User", b => + modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.User", b => { b.Navigation("Addresses"); + b.Navigation("MerchantOrders"); + b.Navigation("Orders"); b.Navigation("UserRoles"); diff --git a/src/Imprink.Infrastructure/Repositories/AddressRepository.cs b/src/Imprink.Infrastructure/Repositories/AddressRepository.cs new file mode 100644 index 0000000..10fb3e0 --- /dev/null +++ b/src/Imprink.Infrastructure/Repositories/AddressRepository.cs @@ -0,0 +1,168 @@ +using Imprink.Domain.Entities; +using Imprink.Domain.Repositories; +using Imprink.Infrastructure.Database; +using Microsoft.EntityFrameworkCore; + +namespace Imprink.Infrastructure.Repositories; + +public class AddressRepository(ApplicationDbContext context) : IAddressRepository +{ + public async Task> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default) + { + return await context.Addresses + .Where(a => a.UserId == userId) + .OrderByDescending(a => a.IsDefault) + .ThenBy(a => a.AddressType) + .ToListAsync(cancellationToken); + } + + public async Task> GetActiveByUserIdAsync(string userId, CancellationToken cancellationToken = default) + { + return await context.Addresses + .Where(a => a.UserId == userId && a.IsActive) + .OrderByDescending(a => a.IsDefault) + .ThenBy(a => a.AddressType) + .ToListAsync(cancellationToken); + } + + public async Task GetDefaultByUserIdAsync(string? userId, CancellationToken cancellationToken = default) + { + return await context.Addresses + .FirstOrDefaultAsync(a => a.UserId == userId && a.IsDefault && a.IsActive, cancellationToken); + } + + public async Task> GetByUserIdAndTypeAsync(string userId, string addressType, CancellationToken cancellationToken = default) + { + return await context.Addresses + .Where(a => a.UserId == userId && a.AddressType == addressType && a.IsActive) + .OrderByDescending(a => a.IsDefault) + .ToListAsync(cancellationToken); + } + + public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.Addresses + .FirstOrDefaultAsync(a => a.Id == id, cancellationToken); + } + + public async Task GetByIdAndUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default) + { + return await context.Addresses + .FirstOrDefaultAsync(a => a.Id == id && a.UserId == userId, cancellationToken); + } + + public async Task
AddAsync(Address address, CancellationToken cancellationToken = default) + { + if (address.IsDefault) + { + await UnsetDefaultAddressesAsync(address.UserId, cancellationToken); + } + + context.Addresses.Add(address); + return address; + } + + public async Task
UpdateAsync(Address address, CancellationToken cancellationToken = default) + { + var existingAddress = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == address.Id, cancellationToken); + + if (existingAddress == null) + throw new InvalidOperationException($"Address with ID {address.Id} not found"); + + if (address.IsDefault && !existingAddress.IsDefault) + { + await UnsetDefaultAddressesAsync(address.UserId, cancellationToken); + } + + context.Entry(existingAddress).CurrentValues.SetValues(address); + return existingAddress; + } + + public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) + { + var address = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == id, cancellationToken); + + if (address == null) + return false; + + context.Addresses.Remove(address); + return true; + } + + public async Task DeleteByUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default) + { + var address = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == id && a.UserId == userId, cancellationToken); + + if (address == null) + return false; + + context.Addresses.Remove(address); + return true; + } + + public async Task SetDefaultAddressAsync(string? userId, Guid addressId, CancellationToken cancellationToken = default) + { + await UnsetDefaultAddressesAsync(userId, cancellationToken); + + var address = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == addressId && a.UserId == userId, cancellationToken); + + if (address != null) + { + address.IsDefault = true; + } + } + + public async Task DeactivateAddressAsync(Guid addressId, CancellationToken cancellationToken = default) + { + var address = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken); + + if (address != null) + { + address.IsActive = false; + if (address.IsDefault) + { + address.IsDefault = false; + } + } + } + + public async Task ActivateAddressAsync(Guid addressId, CancellationToken cancellationToken = default) + { + var address = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken); + + if (address != null) + { + address.IsActive = true; + } + } + + public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.Addresses + .AnyAsync(a => a.Id == id, cancellationToken); + } + + public async Task IsUserAddressAsync(Guid id, string userId, CancellationToken cancellationToken = default) + { + return await context.Addresses + .AnyAsync(a => a.Id == id && a.UserId == userId, cancellationToken); + } + + private async Task UnsetDefaultAddressesAsync(string? userId, CancellationToken cancellationToken = default) + { + var defaultAddresses = await context.Addresses + .Where(a => a.UserId == userId && a.IsDefault) + .ToListAsync(cancellationToken); + + foreach (var addr in defaultAddresses) + { + addr.IsDefault = false; + } + } +} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Repositories/OrderAddressRepository.cs b/src/Imprink.Infrastructure/Repositories/OrderAddressRepository.cs new file mode 100644 index 0000000..33e44aa --- /dev/null +++ b/src/Imprink.Infrastructure/Repositories/OrderAddressRepository.cs @@ -0,0 +1,77 @@ +using Imprink.Domain.Entities; +using Imprink.Domain.Repositories; +using Imprink.Infrastructure.Database; +using Microsoft.EntityFrameworkCore; + +namespace Imprink.Infrastructure.Repositories; + +public class OrderAddressRepository(ApplicationDbContext context) : IOrderAddressRepository +{ + + public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.OrderAddresses + .Include(oa => oa.Order) + .FirstOrDefaultAsync(oa => oa.Id == id, cancellationToken); + } + + public async Task GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) + { + return await context.OrderAddresses + .Include(oa => oa.Order) + .FirstOrDefaultAsync(oa => oa.OrderId == orderId, cancellationToken); + } + + public async Task> GetByOrderIdsAsync(IEnumerable orderIds, CancellationToken cancellationToken = default) + { + return await context.OrderAddresses + .Include(oa => oa.Order) + .Where(oa => orderIds.Contains(oa.OrderId)) + .ToListAsync(cancellationToken); + } + + public async Task AddAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default) + { + var entry = await context.OrderAddresses.AddAsync(orderAddress, cancellationToken); + await context.SaveChangesAsync(cancellationToken); + return entry.Entity; + } + + public async Task UpdateAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default) + { + context.OrderAddresses.Update(orderAddress); + await context.SaveChangesAsync(cancellationToken); + return orderAddress; + } + + public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) + { + var orderAddress = await context.OrderAddresses.FindAsync([id], cancellationToken); + if (orderAddress == null) + return false; + + context.OrderAddresses.Remove(orderAddress); + await context.SaveChangesAsync(cancellationToken); + return true; + } + + public async Task DeleteByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) + { + var orderAddress = await context.OrderAddresses.FirstOrDefaultAsync(oa => oa.OrderId == orderId, cancellationToken); + if (orderAddress == null) + return false; + + context.OrderAddresses.Remove(orderAddress); + return true; + } + + public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.OrderAddresses.AnyAsync(oa => oa.Id == id, cancellationToken); + } + + public async Task ExistsByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) + { + return await context.OrderAddresses.AnyAsync(oa => oa.OrderId == orderId, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Repositories/OrderItemRepository.cs b/src/Imprink.Infrastructure/Repositories/OrderItemRepository.cs deleted file mode 100644 index 916e457..0000000 --- a/src/Imprink.Infrastructure/Repositories/OrderItemRepository.cs +++ /dev/null @@ -1,204 +0,0 @@ -using Imprink.Domain.Entities; -using Imprink.Domain.Repositories; -using Imprink.Infrastructure.Database; -using Microsoft.EntityFrameworkCore; - -namespace Imprink.Infrastructure.Repositories; - -public class OrderItemRepository(ApplicationDbContext context) : IOrderItemRepository -{ - public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .FirstOrDefaultAsync(oi => oi.Id == id, cancellationToken); - } - - public async Task GetByIdWithProductAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Product) - .ThenInclude(p => p.Category) - .FirstOrDefaultAsync(oi => oi.Id == id, cancellationToken); - } - - public async Task GetByIdWithVariantAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.ProductVariant) - .FirstOrDefaultAsync(oi => oi.Id == id, cancellationToken); - } - - public async Task GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Order) - .ThenInclude(o => o.User) - .Include(oi => oi.Product) - .ThenInclude(p => p.Category) - .Include(oi => oi.ProductVariant) - .FirstOrDefaultAsync(oi => oi.Id == id, cancellationToken); - } - - public async Task> GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Where(oi => oi.OrderId == orderId) - .OrderBy(oi => oi.CreatedAt) - .ToListAsync(cancellationToken); - } - - public async Task> GetByOrderIdWithProductsAsync(Guid orderId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Product) - .ThenInclude(p => p.Category) - .Include(oi => oi.ProductVariant) - .Where(oi => oi.OrderId == orderId) - .OrderBy(oi => oi.CreatedAt) - .ToListAsync(cancellationToken); - } - - public async Task> GetByProductIdAsync(Guid productId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Order) - .Where(oi => oi.ProductId == productId) - .OrderByDescending(oi => oi.CreatedAt) - .ToListAsync(cancellationToken); - } - - public async Task> GetByProductVariantIdAsync(Guid productVariantId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Order) - .Where(oi => oi.ProductVariantId == productVariantId) - .OrderByDescending(oi => oi.CreatedAt) - .ToListAsync(cancellationToken); - } - - public async Task> GetCustomizedItemsAsync(CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Product) - .Include(oi => oi.Order) - .Where(oi => !string.IsNullOrEmpty(oi.CustomizationImageUrl) || !string.IsNullOrEmpty(oi.CustomizationDescription)) - .OrderByDescending(oi => oi.CreatedAt) - .ToListAsync(cancellationToken); - } - - public async Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Order) - .Include(oi => oi.Product) - .Where(oi => oi.Order.OrderDate >= startDate && oi.Order.OrderDate <= endDate) - .OrderByDescending(oi => oi.Order.OrderDate) - .ToListAsync(cancellationToken); - } - - public Task AddAsync(OrderItem orderItem, CancellationToken cancellationToken = default) - { - orderItem.Id = Guid.NewGuid(); - orderItem.CreatedAt = DateTime.UtcNow; - orderItem.ModifiedAt = DateTime.UtcNow; - - context.OrderItems.Add(orderItem); - return Task.FromResult(orderItem); - } - - public Task> AddRangeAsync(IEnumerable orderItems, CancellationToken cancellationToken = default) - { - var items = orderItems.ToList(); - var utcNow = DateTime.UtcNow; - - foreach (var item in items) - { - item.Id = Guid.NewGuid(); - item.CreatedAt = utcNow; - item.ModifiedAt = utcNow; - } - - context.OrderItems.AddRange(items); - return Task.FromResult>(items); - } - - public Task UpdateAsync(OrderItem orderItem, CancellationToken cancellationToken = default) - { - orderItem.ModifiedAt = DateTime.UtcNow; - context.OrderItems.Update(orderItem); - return Task.FromResult(orderItem); - } - - public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) - { - var orderItem = await GetByIdAsync(id, cancellationToken); - if (orderItem != null) - { - context.OrderItems.Remove(orderItem); - } - } - - public async Task DeleteByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) - { - var orderItems = await context.OrderItems - .Where(oi => oi.OrderId == orderId) - .ToListAsync(cancellationToken); - - if (orderItems.Any()) - { - context.OrderItems.RemoveRange(orderItems); - } - } - - public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .AnyAsync(oi => oi.Id == id, cancellationToken); - } - - public async Task GetTotalValueByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Where(oi => oi.OrderId == orderId) - .SumAsync(oi => oi.TotalPrice, cancellationToken); - } - - public async Task GetQuantityByProductIdAsync(Guid productId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Where(oi => oi.ProductId == productId) - .SumAsync(oi => oi.Quantity, cancellationToken); - } - - public async Task GetQuantityByVariantIdAsync(Guid productVariantId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Where(oi => oi.ProductVariantId == productVariantId) - .SumAsync(oi => oi.Quantity, cancellationToken); - } - - public async Task> GetProductSalesCountAsync( - DateTime? startDate = null, - DateTime? endDate = null, - CancellationToken cancellationToken = default) - { - var query = context.OrderItems - .Include(oi => oi.Order) - .AsQueryable(); - - if (startDate.HasValue) - { - query = query.Where(oi => oi.Order.OrderDate >= startDate.Value); - } - - if (endDate.HasValue) - { - query = query.Where(oi => oi.Order.OrderDate <= endDate.Value); - } - - return await query - .GroupBy(oi => oi.ProductId) - .Select(g => new { ProductId = g.Key, TotalQuantity = g.Sum(oi => oi.Quantity) }) - .ToDictionaryAsync(x => x.ProductId, x => x.TotalQuantity, cancellationToken); - } -} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Repositories/OrderRepository.cs b/src/Imprink.Infrastructure/Repositories/OrderRepository.cs index 39a7655..d8e00b7 100644 --- a/src/Imprink.Infrastructure/Repositories/OrderRepository.cs +++ b/src/Imprink.Infrastructure/Repositories/OrderRepository.cs @@ -1,5 +1,4 @@ using Imprink.Domain.Entities; -using Imprink.Domain.Models; using Imprink.Domain.Repositories; using Imprink.Infrastructure.Database; using Microsoft.EntityFrameworkCore; @@ -14,159 +13,73 @@ public class OrderRepository(ApplicationDbContext context) : IOrderRepository .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); } - public async Task GetByIdWithItemsAsync(Guid id, CancellationToken cancellationToken = default) + public async Task GetByIdWithDetailsAsync(Guid id, CancellationToken cancellationToken = default) { return await context.Orders - .Include(o => o.OrderItems) - .ThenInclude(oi => oi.Product) - .Include(o => o.OrderItems) - .ThenInclude(oi => oi.ProductVariant) - .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); - } - - public async Task GetByIdWithAddressAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.Orders - .Include(o => o.OrderAddress) - .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); - } - - public async Task GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.Orders - .Include(o => o.User) .Include(o => o.OrderStatus) .Include(o => o.ShippingStatus) .Include(o => o.OrderAddress) - .Include(o => o.OrderItems) - .ThenInclude(oi => oi.Product) - .ThenInclude(p => p.Category) - .Include(o => o.OrderItems) - .ThenInclude(oi => oi.ProductVariant) - .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); - } - - public async Task GetByOrderNumberAsync(string orderNumber, CancellationToken cancellationToken = default) - { - return await context.Orders - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) - .FirstOrDefaultAsync(o => o.OrderNumber == orderNumber, cancellationToken); - } - - public async Task> GetPagedAsync( - OrderFilterParameters filterParameters, - CancellationToken cancellationToken = default) - { - var query = context.Orders + .Include(o => o.Product) + .Include(o => o.ProductVariant) .Include(o => o.User) - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) - .AsQueryable(); - - if (!string.IsNullOrEmpty(filterParameters.UserId)) - { - query = query.Where(o => o.UserId == filterParameters.UserId); - } - - if (!string.IsNullOrEmpty(filterParameters.OrderNumber)) - { - query = query.Where(o => o.OrderNumber.Contains(filterParameters.OrderNumber)); - } - - if (filterParameters.OrderStatusId.HasValue) - { - query = query.Where(o => o.OrderStatusId == filterParameters.OrderStatusId.Value); - } - - if (filterParameters.ShippingStatusId.HasValue) - { - query = query.Where(o => o.ShippingStatusId == filterParameters.ShippingStatusId.Value); - } - - if (filterParameters.StartDate.HasValue) - { - query = query.Where(o => o.OrderDate >= filterParameters.StartDate.Value); - } - - if (filterParameters.EndDate.HasValue) - { - query = query.Where(o => o.OrderDate <= filterParameters.EndDate.Value); - } - - if (filterParameters.MinTotalPrice.HasValue) - { - query = query.Where(o => o.TotalPrice >= filterParameters.MinTotalPrice.Value); - } - - if (filterParameters.MaxTotalPrice.HasValue) - { - query = query.Where(o => o.TotalPrice <= filterParameters.MaxTotalPrice.Value); - } - - query = filterParameters.SortBy.ToLower() switch - { - "orderdate" => filterParameters.SortDirection.Equals("DESC", StringComparison.CurrentCultureIgnoreCase) - ? query.OrderByDescending(o => o.OrderDate) - : query.OrderBy(o => o.OrderDate), - "totalprice" => filterParameters.SortDirection.Equals("DESC", StringComparison.CurrentCultureIgnoreCase) - ? query.OrderByDescending(o => o.TotalPrice) - : query.OrderBy(o => o.TotalPrice), - "ordernumber" => filterParameters.SortDirection.Equals("DESC", StringComparison.CurrentCultureIgnoreCase) - ? query.OrderByDescending(o => o.OrderNumber) - : query.OrderBy(o => o.OrderNumber), - _ => query.OrderByDescending(o => o.OrderDate) - }; - - var totalCount = await query.CountAsync(cancellationToken); - - var items = await query - .Skip((filterParameters.PageNumber - 1) * filterParameters.PageSize) - .Take(filterParameters.PageSize) - .ToListAsync(cancellationToken); - - return new PagedResult - { - Items = items, - TotalCount = totalCount, - PageNumber = filterParameters.PageNumber, - PageSize = filterParameters.PageSize - }; + .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); } public async Task> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default) { return await context.Orders - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) .Where(o => o.UserId == userId) .OrderByDescending(o => o.OrderDate) .ToListAsync(cancellationToken); } - public async Task> GetByUserIdPagedAsync( - string userId, - int pageNumber, - int pageSize, - CancellationToken cancellationToken = default) + public async Task> GetByUserIdWithDetailsAsync(string userId, CancellationToken cancellationToken = default) { return await context.Orders .Include(o => o.OrderStatus) .Include(o => o.ShippingStatus) + .Include(o => o.OrderAddress) + .Include(o => o.Product) + .Include(o => o.ProductVariant) .Where(o => o.UserId == userId) .OrderByDescending(o => o.OrderDate) - .Skip((pageNumber - 1) * pageSize) - .Take(pageSize) .ToListAsync(cancellationToken); } - public async Task> GetByOrderStatusAsync(int orderStatusId, CancellationToken cancellationToken = default) + public async Task> GetByMerchantIdAsync(string merchantId, CancellationToken cancellationToken = default) { return await context.Orders + .Where(o => o.MerchantId == merchantId) + .OrderByDescending(o => o.OrderDate) + .ToListAsync(cancellationToken); + } + + public async Task> GetByMerchantIdWithDetailsAsync(string merchantId, CancellationToken cancellationToken = default) + { + return await context.Orders + .Include(o => o.OrderStatus) + .Include(o => o.ShippingStatus) + .Include(o => o.OrderAddress) + .Include(o => o.Product) + .Include(o => o.ProductVariant) .Include(o => o.User) - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) - .Where(o => o.OrderStatusId == orderStatusId) + .Where(o => o.MerchantId == merchantId) + .OrderByDescending(o => o.OrderDate) + .ToListAsync(cancellationToken); + } + + public async Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default) + { + return await context.Orders + .Where(o => o.OrderDate >= startDate && o.OrderDate <= endDate) + .OrderByDescending(o => o.OrderDate) + .ToListAsync(cancellationToken); + } + + public async Task> GetByStatusAsync(int statusId, CancellationToken cancellationToken = default) + { + return await context.Orders + .Where(o => o.OrderStatusId == statusId) .OrderByDescending(o => o.OrderDate) .ToListAsync(cancellationToken); } @@ -174,52 +87,39 @@ public class OrderRepository(ApplicationDbContext context) : IOrderRepository public async Task> GetByShippingStatusAsync(int shippingStatusId, CancellationToken cancellationToken = default) { return await context.Orders - .Include(o => o.User) - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) .Where(o => o.ShippingStatusId == shippingStatusId) .OrderByDescending(o => o.OrderDate) .ToListAsync(cancellationToken); } - public async Task> GetByDateRangeAsync( - DateTime startDate, - DateTime endDate, - CancellationToken cancellationToken = default) - { - return await context.Orders - .Include(o => o.User) - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) - .Where(o => o.OrderDate >= startDate && o.OrderDate <= endDate) - .OrderByDescending(o => o.OrderDate) - .ToListAsync(cancellationToken); - } - public Task AddAsync(Order order, CancellationToken cancellationToken = default) { - order.Id = Guid.NewGuid(); - order.CreatedAt = DateTime.UtcNow; - order.ModifiedAt = DateTime.UtcNow; - context.Orders.Add(order); return Task.FromResult(order); } - public Task UpdateAsync(Order order, CancellationToken cancellationToken = default) + public async Task UpdateAsync(Order order, CancellationToken cancellationToken = default) { - order.ModifiedAt = DateTime.UtcNow; - context.Orders.Update(order); - return Task.FromResult(order); + var existingOrder = await context.Orders + .FirstOrDefaultAsync(o => o.Id == order.Id, cancellationToken); + + if (existingOrder == null) + throw new InvalidOperationException($"Order with ID {order.Id} not found"); + + context.Entry(existingOrder).CurrentValues.SetValues(order); + return existingOrder; } - public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) + public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) { - var order = await GetByIdAsync(id, cancellationToken); - if (order != null) - { - context.Orders.Remove(order); - } + var order = await context.Orders + .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); + + if (order == null) + return false; + + context.Orders.Remove(order); + return true; } public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) @@ -228,38 +128,47 @@ public class OrderRepository(ApplicationDbContext context) : IOrderRepository .AnyAsync(o => o.Id == id, cancellationToken); } - public async Task OrderNumberExistsAsync(string orderNumber, Guid? excludeId = null, CancellationToken cancellationToken = default) + public async Task UpdateStatusAsync(Guid orderId, int statusId, CancellationToken cancellationToken = default) { - var query = context.Orders.Where(o => o.OrderNumber == orderNumber); - - if (excludeId.HasValue) + var order = await context.Orders + .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); + + if (order != null) { - query = query.Where(o => o.Id != excludeId.Value); + order.OrderStatusId = statusId; } - - return await query.AnyAsync(cancellationToken); } - public async Task GetTotalRevenueAsync(CancellationToken cancellationToken = default) + public async Task UpdateShippingStatusAsync(Guid orderId, int shippingStatusId, CancellationToken cancellationToken = default) { - return await context.Orders - .Where(o => o.OrderStatusId != 5) // Assuming 5 is cancelled status - .SumAsync(o => o.TotalPrice, cancellationToken); + var order = await context.Orders + .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); + + if (order != null) + { + order.ShippingStatusId = shippingStatusId; + } } - public async Task GetTotalRevenueByDateRangeAsync( - DateTime startDate, - DateTime endDate, - CancellationToken cancellationToken = default) + public async Task AssignMerchantAsync(Guid orderId, string merchantId, CancellationToken cancellationToken = default) { - return await context.Orders - .Where(o => o.OrderDate >= startDate && o.OrderDate <= endDate && o.OrderStatusId != 5) - .SumAsync(o => o.TotalPrice, cancellationToken); + var order = await context.Orders + .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); + + if (order != null) + { + order.MerchantId = merchantId; + } } - public async Task GetOrderCountByStatusAsync(int orderStatusId, CancellationToken cancellationToken = default) + public async Task UnassignMerchantAsync(Guid orderId, CancellationToken cancellationToken = default) { - return await context.Orders - .CountAsync(o => o.OrderStatusId == orderStatusId, cancellationToken); + var order = await context.Orders + .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); + + if (order != null) + { + order.MerchantId = null; + } } } \ No newline at end of file diff --git a/src/Imprink.Infrastructure/UnitOfWork.cs b/src/Imprink.Infrastructure/UnitOfWork.cs index 22011f2..3025afd 100644 --- a/src/Imprink.Infrastructure/UnitOfWork.cs +++ b/src/Imprink.Infrastructure/UnitOfWork.cs @@ -13,7 +13,8 @@ public class UnitOfWork( IUserRoleRepository userRoleRepository, IRoleRepository roleRepository, IOrderRepository orderRepository, - IOrderItemRepository orderItemRepository) : IUnitOfWork + IAddressRepository addressRepository, + IOrderAddressRepository orderAddressRepository) : IUnitOfWork { public IProductRepository ProductRepository => productRepository; public IProductVariantRepository ProductVariantRepository => productVariantRepository; @@ -22,7 +23,8 @@ public class UnitOfWork( public IUserRoleRepository UserRoleRepository => userRoleRepository; public IRoleRepository RoleRepository => roleRepository; public IOrderRepository OrderRepository => orderRepository; - public IOrderItemRepository OrderItemRepository => orderItemRepository; + public IAddressRepository AddressRepository => addressRepository; + public IOrderAddressRepository OrderAddressRepository => orderAddressRepository; public async Task SaveAsync(CancellationToken cancellationToken = default) { @@ -50,7 +52,6 @@ public class UnitOfWork( try { var result = await operation(); - await SaveAsync(cancellationToken); await CommitTransactionAsync(cancellationToken); return result; } @@ -67,7 +68,6 @@ public class UnitOfWork( try { await operation(); - await SaveAsync(cancellationToken); await CommitTransactionAsync(cancellationToken); } catch diff --git a/src/Imprink.WebApi/Controllers/AddressesController.cs b/src/Imprink.WebApi/Controllers/AddressesController.cs new file mode 100644 index 0000000..79e3a39 --- /dev/null +++ b/src/Imprink.WebApi/Controllers/AddressesController.cs @@ -0,0 +1,49 @@ +using Imprink.Application.Commands.Addresses; +using Imprink.Application.Dtos; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Imprink.WebApi.Controllers; + +[ApiController] +[Route("/api/addresses")] +public class AddressesController(IMediator mediator) : ControllerBase +{ + + [HttpGet("me")] + [Authorize] + public async Task>> GetMyAddresses(CancellationToken cancellationToken = default) + { + var result = await mediator.Send(new GetMyAddressesQuery(), cancellationToken); + return Ok(result); + } + + [HttpGet("user/{userId}")] + [Authorize(Roles = "Admin")] + public async Task>> GetAddressesByUserId( + string userId, + [FromQuery] bool activeOnly = false, + [FromQuery] string? addressType = null, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(new GetAddressesByUserIdQuery + { + UserId = userId, + ActiveOnly = activeOnly, + AddressType = addressType + }, cancellationToken); + + return Ok(result); + } + + [HttpPost] + [Authorize] + public async Task> CreateAddress( + [FromBody] CreateAddressCommand command, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(command, cancellationToken); + return CreatedAtAction(nameof(CreateAddress), new { id = result.Id }, result); + } +} \ No newline at end of file diff --git a/src/Imprink.WebApi/Controllers/OrdersController.cs b/src/Imprink.WebApi/Controllers/OrdersController.cs new file mode 100644 index 0000000..da9366b --- /dev/null +++ b/src/Imprink.WebApi/Controllers/OrdersController.cs @@ -0,0 +1,74 @@ +using Imprink.Application.Commands.Orders; +using Imprink.Application.Dtos; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Imprink.WebApi.Controllers; + +[ApiController] +[Route("/api/orders")] +public class OrdersController(IMediator mediator) : ControllerBase +{ + + [HttpGet("{id:guid}")] + [Authorize] + public async Task> GetOrderById( + Guid id, + [FromQuery] bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(new GetOrderByIdQuery + { + Id = id, + IncludeDetails = includeDetails + }, cancellationToken); + + if (result == null) + return NotFound(); + + return Ok(result); + } + + [HttpGet("user/{userId}")] + [Authorize] + public async Task>> GetOrdersByUserId( + string userId, + [FromQuery] bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(new GetOrdersByUserIdQuery + { + UserId = userId, + IncludeDetails = includeDetails + }, cancellationToken); + + return Ok(result); + } + + [HttpGet("merchant/{merchantId}")] + [Authorize(Roles = "Admin,Merchant")] + public async Task>> GetOrdersByMerchantId( + string merchantId, + [FromQuery] bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(new GetOrdersByMerchantIdQuery + { + MerchantId = merchantId, + IncludeDetails = includeDetails + }, cancellationToken); + + return Ok(result); + } + + [HttpPost] + [Authorize] + public async Task> CreateOrder( + [FromBody] CreateOrderCommand command, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(command, cancellationToken); + return CreatedAtAction(nameof(GetOrderById), new { id = result.Id }, result); + } +} \ No newline at end of file diff --git a/src/Imprink.WebApi/Controllers/ProductVariantsController.cs b/src/Imprink.WebApi/Controllers/ProductVariantsController.cs index a1c8636..fa9c198 100644 --- a/src/Imprink.WebApi/Controllers/ProductVariantsController.cs +++ b/src/Imprink.WebApi/Controllers/ProductVariantsController.cs @@ -10,11 +10,12 @@ namespace Imprink.WebApi.Controllers; [Route("/api/products/variants")] public class ProductVariantsController(IMediator mediator) : ControllerBase { - [HttpGet] + [HttpGet("{id:guid}")] [AllowAnonymous] public async Task>> GetProductVariants( - [FromQuery] GetProductVariantsQuery query) + Guid id) { + var query = new GetProductVariantsQuery { ProductId = id }; return Ok(await mediator.Send(query)); } diff --git a/src/Imprink.WebApi/Controllers/ProductsController.cs b/src/Imprink.WebApi/Controllers/ProductsController.cs index 9c08e74..6d23d6c 100644 --- a/src/Imprink.WebApi/Controllers/ProductsController.cs +++ b/src/Imprink.WebApi/Controllers/ProductsController.cs @@ -21,6 +21,18 @@ public class ProductsController(IMediator mediator) : ControllerBase return Ok(result); } + [HttpGet("{id:guid}")] + [AllowAnonymous] + public async Task> GetProductById( + Guid id, + CancellationToken cancellationToken) + { + var result = await mediator + .Send(new GetProductByIdQuery { ProductId = id}, cancellationToken); + + return Ok(result); + } + [HttpPost] [Authorize(Roles = "Admin")] public async Task>> CreateProduct( @@ -34,7 +46,7 @@ public class ProductsController(IMediator mediator) : ControllerBase [Authorize(Roles = "Admin")] public async Task> UpdateProduct( Guid id, - [FromBody] UpdateProductCommand command) + [FromBody] UpdateProduct command) { if (id != command.Id) return BadRequest("ID mismatch"); diff --git a/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs b/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs index 86ee554..e10b03b 100644 --- a/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs +++ b/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs @@ -20,7 +20,8 @@ public static class StartupApplicationExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Imprink.WebApi/Startup.cs b/src/Imprink.WebApi/Startup.cs index 564050c..06c954b 100644 --- a/src/Imprink.WebApi/Startup.cs +++ b/src/Imprink.WebApi/Startup.cs @@ -24,7 +24,7 @@ public static class Startup services.AddDatabaseContexts(builder.Configuration); - services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(CreateProductHandler).Assembly)); + services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(CreateProduct).Assembly)); services.AddValidatorsFromAssembly(typeof(Auth0UserValidator).Assembly); diff --git a/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs b/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs index a1e30c0..3f95438 100644 --- a/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs +++ b/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs @@ -15,7 +15,7 @@ public class CreateCategoryHandlerIntegrationTest : IDisposable { private readonly ApplicationDbContext _context; private readonly IServiceProvider _serviceProvider; - private readonly CreateCategoryHandler _handler; + private readonly CreateCategory _handler; private readonly SqliteConnection _connection; public CreateCategoryHandlerIntegrationTest() @@ -35,14 +35,14 @@ public class CreateCategoryHandlerIntegrationTest : IDisposable services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); _serviceProvider = services.BuildServiceProvider(); _context = _serviceProvider.GetRequiredService(); - _handler = _serviceProvider.GetRequiredService(); + _handler = _serviceProvider.GetRequiredService(); _context.Database.EnsureCreated(); } @@ -216,7 +216,7 @@ public class CreateCategoryHandlerIntegrationTest : IDisposable var initialCount = await _context.Categories.CountAsync(); - var handler = _serviceProvider.GetRequiredService(); + var handler = _serviceProvider.GetRequiredService(); var result = await handler.Handle(command, CancellationToken.None); diff --git a/tests/Imprink.Application.Tests/UpdateCategoryHandlerUnitTest.cs b/tests/Imprink.Application.Tests/UpdateCategoryHandlerUnitTest.cs index 3336fe8..6fd74b8 100644 --- a/tests/Imprink.Application.Tests/UpdateCategoryHandlerUnitTest.cs +++ b/tests/Imprink.Application.Tests/UpdateCategoryHandlerUnitTest.cs @@ -6,13 +6,13 @@ using Moq; namespace Imprink.Application.Tests; -public class UpdateCategoryHandlerTests +public class UpdateCategoryTests { private readonly Mock _unitOfWorkMock; private readonly Mock _categoryRepositoryMock; - private readonly UpdateCategoryHandler _handler; + private readonly UpdateCategory _handler; - public UpdateCategoryHandlerTests() + public UpdateCategoryTests() { _unitOfWorkMock = new Mock(); _categoryRepositoryMock = new Mock(); @@ -20,7 +20,7 @@ public class UpdateCategoryHandlerTests _unitOfWorkMock.Setup(x => x.CategoryRepository) .Returns(_categoryRepositoryMock.Object); - _handler = new UpdateCategoryHandler(_unitOfWorkMock.Object); + _handler = new UpdateCategory(_unitOfWorkMock.Object); } [Fact] diff --git a/ui/src/app/builder/[id]/page.tsx b/ui/src/app/builder/[id]/page.tsx new file mode 100644 index 0000000..2268fff --- /dev/null +++ b/ui/src/app/builder/[id]/page.tsx @@ -0,0 +1,800 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useRouter, useParams } from 'next/navigation'; +import clientApi from '@/lib/clientApi'; +import { + Box, + Button, + Card, + CardContent, + CardMedia, + Typography, + Stepper, + Step, + StepLabel, + Grid, + TextField, + IconButton, + Radio, + RadioGroup, + FormControlLabel, + FormControl, + FormLabel, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Chip, + Container, + Paper, + Fade, + CircularProgress +} from '@mui/material'; +import { + Add as AddIcon, + Remove as RemoveIcon, + LocationOn as LocationIcon, + AddLocation as AddLocationIcon +} from '@mui/icons-material'; + +interface Category { + id: string; + name: string; + description: string; + imageUrl: string; + sortOrder: number; + isActive: boolean; + parentCategoryId: string; + createdAt: string; + modifiedAt: string; +} + +interface Product { + id: string; + name: string; + description: string; + basePrice: number; + isCustomizable: boolean; + isActive: boolean; + imageUrl: string; + categoryId: string; + category: Category; + createdAt: string; + modifiedAt: string; +} + +interface Variant { + id: string; + productId: string; + size: string; + color: string; + price: number; + imageUrl: string; + sku: string; + stockQuantity: number; + isActive: boolean; + product: Product; + createdAt: string; + modifiedAt: string; +} + +interface Address { + id: string; + userId: string; + addressType: string; + firstName: string; + lastName: string; + company: string; + addressLine1: string; + addressLine2: string; + apartmentNumber: string; + buildingNumber: string; + floor: string; + city: string; + state: string; + postalCode: string; + country: string; + phoneNumber: string; + instructions: string; + isDefault: boolean; + isActive: boolean; +} + +interface NewAddress { + addressType: string; + firstName: string; + lastName: string; + company: string; + addressLine1: string; + addressLine2: string; + apartmentNumber: string; + buildingNumber: string; + floor: string; + city: string; + state: string; + postalCode: string; + country: string; + phoneNumber: string; + instructions: string; + isDefault: boolean; + isActive: boolean; +} + +const steps = ['Product Details', 'Select Variant', 'Choose Quantity', 'Delivery Address', 'Review & Order']; + +export default function OrderBuilder() { + const router = useRouter(); + const params = useParams(); + const productId = params.id as string; + + const [activeStep, setActiveStep] = useState(0); + const [loading, setLoading] = useState(false); + const [product, setProduct] = useState(null); + const [variants, setVariants] = useState([]); + const [addresses, setAddresses] = useState([]); + const [selectedVariant, setSelectedVariant] = useState(null); + const [quantity, setQuantity] = useState(1); + const [selectedAddress, setSelectedAddress] = useState
(null); + const [showAddressDialog, setShowAddressDialog] = useState(false); + const [newAddress, setNewAddress] = useState({ + addressType: 'Home', + firstName: '', + lastName: '', + company: '', + addressLine1: '', + addressLine2: '', + apartmentNumber: '', + buildingNumber: '', + floor: '', + city: '', + state: '', + postalCode: '', + country: '', + phoneNumber: '', + instructions: '', + isDefault: false, + isActive: true + }); + + useEffect(() => { + if (productId) { + loadProduct(); + } + }, [productId]); + + const loadProduct = async () => { + setLoading(true); + try { + const productData = await clientApi.get(`/products/${productId}`); + setProduct(productData.data); + } catch (error) { + console.error('Failed to load product:', error); + } finally { + setLoading(false); + } + }; + + const loadVariants = async () => { + setLoading(true); + try { + const variantsData = await clientApi.get(`/products/variants/${productId}`); + setVariants(variantsData.data); + } catch (error) { + console.error('Failed to load variants:', error); + } finally { + setLoading(false); + } + }; + + const loadAddresses = async () => { + setLoading(true); + try { + const addressesData = await clientApi.get('/addresses/me'); + setAddresses(addressesData.data); + if (addressesData.data.length > 0) { + const defaultAddress = addressesData.data.find((addr: Address) => addr.isDefault) || addressesData.data[0]; + setSelectedAddress(defaultAddress); + } + } catch (error) { + console.error('Failed to load addresses:', error); + } finally { + setLoading(false); + } + }; + + const handleNext = () => { + if (activeStep === 0 && product) { + loadVariants(); + } else if (activeStep === 2) { + loadAddresses(); + } + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + const handleQuantityChange = (delta: number) => { + const newQuantity = quantity + delta; + if (newQuantity >= 1) { + setQuantity(newQuantity); + } + }; + + const handleAddAddress = async () => { + try { + const addedAddress = await clientApi.post('/addresses', newAddress); + setAddresses([...addresses, addedAddress.data]); + setSelectedAddress(addedAddress.data); + setShowAddressDialog(false); + setNewAddress({ + addressType: 'shipping', + firstName: '', + lastName: '', + company: '', + addressLine1: '', + addressLine2: '', + apartmentNumber: '', + buildingNumber: '', + floor: '', + city: '', + state: '', + postalCode: '', + country: '', + phoneNumber: '', + instructions: '', + isDefault: false, + isActive: true + }); + } catch (error) { + console.error('Failed to add address:', error); + } + }; + + const handlePlaceOrder = async () => { + if (!selectedVariant || !selectedAddress) return; + + const orderData = { + productId: product!.id, + productVariantId: selectedVariant.id, + quantity: quantity, + addressId: selectedAddress.id, + totalPrice: selectedVariant.price * quantity + }; + + try { + setLoading(true); + await clientApi.post('/orders', orderData); + router.push('/orders/success'); + } catch (error) { + console.error('Failed to place order:', error); + } finally { + setLoading(false); + } + }; + + const getTotalPrice = () => { + if (!selectedVariant) return 0; + return selectedVariant.price * quantity; + }; + + const canProceed = () => { + switch (activeStep) { + case 0: return product !== null; + case 1: return selectedVariant !== null; + case 2: return quantity > 0; + case 3: return selectedAddress !== null; + default: return true; + } + }; + + const renderStepContent = () => { + switch (activeStep) { + case 0: + return ( + + + {product && ( + + + + + {product.name} + + + {product.description} + + + ${product.basePrice.toFixed(2)} + + + + {product.isCustomizable && } + + + + )} + + + ); + + case 1: + return ( + + + + Select Variant + + + {variants.map((variant) => ( + + setSelectedVariant(variant)} + > + + + + {variant.size} - {variant.color} + + + SKU: {variant.sku} + + + ${variant.price.toFixed(2)} + + 0 ? 'success.main' : 'error.main'}> + {variant.stockQuantity > 0 ? `${variant.stockQuantity} in stock` : 'Out of stock'} + + + + + ))} + + + + ); + + case 2: + return ( + + + + Choose Quantity + + {selectedVariant && ( + + + + + + {selectedVariant.size} + + + {selectedVariant.product.name} + + + {selectedVariant.size} - {selectedVariant.color} + + + ${selectedVariant.price.toFixed(2)} each + + + + + + + handleQuantityChange(-1)} + disabled={quantity <= 1} + > + + + { + const val = parseInt(e.target.value) || 1; + if (val >= 1) setQuantity(val); + }} + inputProps={{ + style: { textAlign: 'center', fontSize: '1.2rem' }, + min: 1 + }} + sx={{ width: 80 }} + /> + handleQuantityChange(1)}> + + + + + + + + Total: ${getTotalPrice().toFixed(2)} + + + + + )} + + + ); + + case 3: + return ( + + + + Select Delivery Address + + + { + const addr = addresses.find(a => a.id === e.target.value); + setSelectedAddress(addr || null); + }} + > + + {addresses.map((address) => ( + + + + } + label="" + sx={{ position: 'absolute', top: 8, right: 8 }} + /> + + + + {address.firstName} {address.lastName} + + {address.isDefault && } + + {address.company && ( + + {address.company} + + )} + + {address.addressLine1} + + {address.addressLine2 && ( + + {address.addressLine2} + + )} + + {address.city}, {address.state} {address.postalCode} + + + {address.country} + + {address.phoneNumber && ( + + Phone: {address.phoneNumber} + + )} + + + + ))} + + setShowAddressDialog(true)} + > + + + + + Add New Address + + + + + + + + + + + ); + + case 4: + return ( + + + + Review Your Order + + + + + + + Order Summary + + {selectedVariant && ( + + {selectedVariant.size} + + + {selectedVariant.product.name} + + + {selectedVariant.size} - {selectedVariant.color} + + + Quantity: {quantity} + + + + ${getTotalPrice().toFixed(2)} + + + )} + + + + + + + + Delivery Address + + {selectedAddress && ( + + + {selectedAddress.firstName} {selectedAddress.lastName} + + {selectedAddress.company && ( + + {selectedAddress.company} + + )} + + {selectedAddress.addressLine1} + + {selectedAddress.addressLine2 && ( + + {selectedAddress.addressLine2} + + )} + + {selectedAddress.city}, {selectedAddress.state} {selectedAddress.postalCode} + + + {selectedAddress.country} + + + )} + + + + + + + ); + + default: + return null; + } + }; + + if (loading && !product) { + return ( + + + + ); + } + + return ( + + + + {steps.map((label) => ( + + {label} + + ))} + + + + {renderStepContent()} + + + + + + + + + setShowAddressDialog(false)} maxWidth="md" fullWidth> + Add New Address + + + + setNewAddress({...newAddress, firstName: e.target.value})} + /> + + + setNewAddress({...newAddress, lastName: e.target.value})} + /> + + + setNewAddress({...newAddress, company: e.target.value})} + /> + + + setNewAddress({...newAddress, addressLine1: e.target.value})} + /> + + + setNewAddress({...newAddress, addressLine2: e.target.value})} + /> + + + setNewAddress({...newAddress, apartmentNumber: e.target.value})} + /> + + + setNewAddress({...newAddress, buildingNumber: e.target.value})} + /> + + + setNewAddress({...newAddress, floor: e.target.value})} + /> + + + setNewAddress({...newAddress, city: e.target.value})} + /> + + + setNewAddress({...newAddress, state: e.target.value})} + /> + + + setNewAddress({...newAddress, postalCode: e.target.value})} + /> + + + setNewAddress({...newAddress, country: e.target.value})} + /> + + + setNewAddress({...newAddress, phoneNumber: e.target.value})} + /> + + + setNewAddress({...newAddress, instructions: e.target.value})} + /> + + + + + + + + + + ); +} \ No newline at end of file