Add Order/Address/OrderAddress repos, and handlers

This commit is contained in:
lumijiez
2025-06-25 22:34:39 +03:00
parent 3887e7bc44
commit c21c01c432
19 changed files with 484 additions and 6 deletions

View File

@@ -0,0 +1,52 @@
using AutoMapper;
using Imprink.Application.Dtos;
using Imprink.Domain.Entities;
using MediatR;
namespace Imprink.Application.Commands.Addresses;
public class CreateAddressCommand : IRequest<AddressDto>
{
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; } = true;
}
public class CreateAddressHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler<CreateAddressCommand, AddressDto>
{
public async Task<AddressDto> Handle(CreateAddressCommand request, CancellationToken cancellationToken)
{
return await uw.TransactAsync(async () =>
{
var address = mapper.Map<Address>(request);
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);
return mapper.Map<AddressDto>(createdAddress);
}, cancellationToken);
}
}

View File

@@ -0,0 +1,31 @@
using AutoMapper;
using Imprink.Application.Dtos;
using Imprink.Domain.Entities;
using MediatR;
namespace Imprink.Application.Commands.Addresses;
public class GetAddressByIdQuery : IRequest<AddressDto?>
{
public Guid Id { get; set; }
public string? UserId { get; set; }
}
public class GetAddressByIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler<GetAddressByIdQuery, AddressDto?>
{
public async Task<AddressDto?> Handle(GetAddressByIdQuery request, CancellationToken cancellationToken)
{
Address? address;
if (!string.IsNullOrEmpty(request.UserId))
{
address = await uw.AddressRepository.GetByIdAndUserIdAsync(request.Id, request.UserId, cancellationToken);
}
else
{
address = await uw.AddressRepository.GetByIdAsync(request.Id, cancellationToken);
}
return address != null ? mapper.Map<AddressDto>(address) : null;
}
}

View File

@@ -0,0 +1,36 @@
using AutoMapper;
using Imprink.Application.Dtos;
using Imprink.Domain.Entities;
using MediatR;
namespace Imprink.Application.Commands.Addresses;
public class GetAddressesByUserIdQuery : IRequest<IEnumerable<AddressDto>>
{
public string UserId { get; set; } = null!;
public bool ActiveOnly { get; set; } = false;
public string? AddressType { get; set; }
}
public class GetAddressesByUserIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler<GetAddressesByUserIdQuery, IEnumerable<AddressDto>>
{
public async Task<IEnumerable<AddressDto>> Handle(GetAddressesByUserIdQuery request, CancellationToken cancellationToken)
{
IEnumerable<Address> 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<IEnumerable<AddressDto>>(addresses);
}
}

View File

@@ -0,0 +1,77 @@
using AutoMapper;
using Imprink.Application.Dtos;
using Imprink.Domain.Entities;
using MediatR;
namespace Imprink.Application.Commands.Orders;
public class CreateOrderCommand : IRequest<OrderDto>
{
public string UserId { get; set; } = null!;
public decimal Amount { get; set; }
public int Quantity { get; set; }
public Guid ProductId { get; set; }
public Guid? ProductVariantId { get; set; }
public string? Notes { get; set; }
public string? MerchantId { get; set; }
public string? ComposingImageUrl { 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 CreateOrderHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler<CreateOrderCommand, OrderDto>
{
public async Task<OrderDto> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
return await uw.TransactAsync(async () =>
{
var sourceAddress = await uw.AddressRepository.GetByIdAndUserIdAsync(request.AddressId, request.UserId, cancellationToken);
if (sourceAddress == null)
{
throw new ArgumentException($"Address with ID {request.AddressId} not found for user {request.UserId}");
}
var order = mapper.Map<Order>(request);
order.OrderDate = DateTime.UtcNow;
order.OrderStatusId = 1;
order.ShippingStatusId = 1;
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)
{
createdOrder.ProductVariant = await uw.ProductVariantRepository.GetByIdAsync(createdOrder.ProductVariantId.Value, cancellationToken);
}
return mapper.Map<OrderDto>(createdOrder);
}, cancellationToken);
}
}

View File

@@ -0,0 +1,31 @@
using AutoMapper;
using Imprink.Application.Dtos;
using Imprink.Domain.Entities;
using MediatR;
namespace Imprink.Application.Commands.Orders;
public class GetOrderByIdQuery : IRequest<OrderDto?>
{
public Guid Id { get; set; }
public bool IncludeDetails { get; set; } = false;
}
public class GetOrderByIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler<GetOrderByIdQuery, OrderDto?>
{
public async Task<OrderDto?> 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<OrderDto>(order) : null;
}
}

View File

@@ -0,0 +1,31 @@
using AutoMapper;
using Imprink.Application.Dtos;
using Imprink.Domain.Entities;
using MediatR;
namespace Imprink.Application.Commands.Orders;
public class GetOrdersByMerchantIdQuery : IRequest<IEnumerable<OrderDto>>
{
public string MerchantId { get; set; } = null!;
public bool IncludeDetails { get; set; } = false;
}
public class GetOrdersByMerchantIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler<GetOrdersByMerchantIdQuery, IEnumerable<OrderDto>>
{
public async Task<IEnumerable<OrderDto>> Handle(GetOrdersByMerchantIdQuery request, CancellationToken cancellationToken)
{
IEnumerable<Order> orders;
if (request.IncludeDetails)
{
orders = await uw.OrderRepository.GetByMerchantIdWithDetailsAsync(request.MerchantId, cancellationToken);
}
else
{
orders = await uw.OrderRepository.GetByMerchantIdAsync(request.MerchantId, cancellationToken);
}
return mapper.Map<IEnumerable<OrderDto>>(orders);
}
}

View File

@@ -0,0 +1,31 @@
using AutoMapper;
using Imprink.Application.Dtos;
using Imprink.Domain.Entities;
using MediatR;
namespace Imprink.Application.Commands.Orders;
public class GetOrdersByUserIdQuery : IRequest<IEnumerable<OrderDto>>
{
public string UserId { get; set; } = null!;
public bool IncludeDetails { get; set; } = false;
}
public class GetOrdersByUserIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler<GetOrdersByUserIdQuery, IEnumerable<OrderDto>>
{
public async Task<IEnumerable<OrderDto>> Handle(GetOrdersByUserIdQuery request, CancellationToken cancellationToken)
{
IEnumerable<Order> orders;
if (request.IncludeDetails)
{
orders = await uw.OrderRepository.GetByUserIdWithDetailsAsync(request.UserId, cancellationToken);
}
else
{
orders = await uw.OrderRepository.GetByUserIdAsync(request.UserId, cancellationToken);
}
return mapper.Map<IEnumerable<OrderDto>>(orders);
}
}

View File

@@ -0,0 +1,26 @@
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; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}

View File

@@ -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; }
}

View File

@@ -0,0 +1,30 @@
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 OrderNumber { get; set; } = null!;
public string? Notes { get; set; }
public string? MerchantId { get; set; }
public string? ComposingImageUrl { get; set; }
public string[] OriginalImageUrls { get; set; } = [];
public string CustomizationImageUrl { get; set; } = null!;
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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -12,6 +12,7 @@ public interface IUnitOfWork
public IRoleRepository RoleRepository { get; }
public IOrderRepository OrderRepository { get; }
public IAddressRepository AddressRepository { get; }
public IOrderAddressRepository OrderAddressRepository { get; }
Task SaveAsync(CancellationToken cancellationToken = default);
Task BeginTransactionAsync(CancellationToken cancellationToken = default);

View File

@@ -19,8 +19,4 @@
<ProjectReference Include="..\Imprink.Domain\Imprink.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Commands\Orders\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,16 @@
using Imprink.Domain.Entities;
namespace Imprink.Domain.Repositories;
public interface IOrderAddressRepository
{
Task<OrderAddress?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<OrderAddress?> GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default);
Task<IEnumerable<OrderAddress>> GetByOrderIdsAsync(IEnumerable<Guid> orderIds, CancellationToken cancellationToken = default);
Task<OrderAddress> AddAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default);
Task<OrderAddress> UpdateAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default);
Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task<bool> DeleteByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default);
Task<bool> ExistsByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default);
}

View File

@@ -11,7 +11,6 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
public DbSet<Product> Products { get; set; }
public DbSet<ProductVariant> ProductVariants { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderAddress> OrderAddresses { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<OrderStatus> OrderStatuses { get; set; }
public DbSet<ShippingStatus> ShippingStatuses { get; set; }
@@ -19,6 +18,7 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
public DbSet<UserRole> UserRole { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<OrderAddress> OrderAddresses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{

View File

@@ -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<OrderAddress?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return await context.OrderAddresses
.Include(oa => oa.Order)
.FirstOrDefaultAsync(oa => oa.Id == id, cancellationToken);
}
public async Task<OrderAddress?> GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default)
{
return await context.OrderAddresses
.Include(oa => oa.Order)
.FirstOrDefaultAsync(oa => oa.OrderId == orderId, cancellationToken);
}
public async Task<IEnumerable<OrderAddress>> GetByOrderIdsAsync(IEnumerable<Guid> orderIds, CancellationToken cancellationToken = default)
{
return await context.OrderAddresses
.Include(oa => oa.Order)
.Where(oa => orderIds.Contains(oa.OrderId))
.ToListAsync(cancellationToken);
}
public async Task<OrderAddress> AddAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default)
{
var entry = await context.OrderAddresses.AddAsync(orderAddress, cancellationToken);
await context.SaveChangesAsync(cancellationToken);
return entry.Entity;
}
public async Task<OrderAddress> UpdateAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default)
{
context.OrderAddresses.Update(orderAddress);
await context.SaveChangesAsync(cancellationToken);
return orderAddress;
}
public async Task<bool> 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<bool> 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<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
{
return await context.OrderAddresses.AnyAsync(oa => oa.Id == id, cancellationToken);
}
public async Task<bool> ExistsByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default)
{
return await context.OrderAddresses.AnyAsync(oa => oa.OrderId == orderId, cancellationToken);
}
}

View File

@@ -13,7 +13,8 @@ public class UnitOfWork(
IUserRoleRepository userRoleRepository,
IRoleRepository roleRepository,
IOrderRepository orderRepository,
IAddressRepository addressRepository) : IUnitOfWork
IAddressRepository addressRepository,
IOrderAddressRepository orderAddressRepository) : IUnitOfWork
{
public IProductRepository ProductRepository => productRepository;
public IProductVariantRepository ProductVariantRepository => productVariantRepository;
@@ -23,6 +24,7 @@ public class UnitOfWork(
public IRoleRepository RoleRepository => roleRepository;
public IOrderRepository OrderRepository => orderRepository;
public IAddressRepository AddressRepository => addressRepository;
public IOrderAddressRepository OrderAddressRepository => orderAddressRepository;
public async Task SaveAsync(CancellationToken cancellationToken = default)
{

View File

@@ -20,6 +20,7 @@ public static class StartupApplicationExtensions
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IUserRoleRepository, UserRoleRepository>();
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IOrderAddressRepository, OrderAddressRepository>();
services.AddScoped<IAddressRepository, AddressRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<ICurrentUserService, CurrentUserService>();