Add address/order handling

This commit is contained in:
lumijiez
2025-06-25 22:20:50 +03:00
parent 028424a346
commit 3887e7bc44
24 changed files with 562 additions and 588 deletions

View File

@@ -11,7 +11,7 @@ public interface IUnitOfWork
public IUserRoleRepository UserRoleRepository { get; } public IUserRoleRepository UserRoleRepository { get; }
public IRoleRepository RoleRepository { get; } public IRoleRepository RoleRepository { get; }
public IOrderRepository OrderRepository { get; } public IOrderRepository OrderRepository { get; }
public IOrderItemRepository OrderItemRepository { get; } public IAddressRepository AddressRepository { get; }
Task SaveAsync(CancellationToken cancellationToken = default); Task SaveAsync(CancellationToken cancellationToken = default);
Task BeginTransactionAsync(CancellationToken cancellationToken = default); Task BeginTransactionAsync(CancellationToken cancellationToken = default);

View File

@@ -16,14 +16,12 @@ public class ProductMappingProfile: Profile
.ForMember(dest => dest.ModifiedAt, opt => opt.Ignore()) .ForMember(dest => dest.ModifiedAt, opt => opt.Ignore())
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore()) .ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
.ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()) .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore())
.ForMember(dest => dest.Product, opt => opt.Ignore()) .ForMember(dest => dest.Product, opt => opt.Ignore());
.ForMember(dest => dest.OrderItems, opt => opt.Ignore());
CreateMap<ProductVariant, ProductVariantDto>(); CreateMap<ProductVariant, ProductVariantDto>();
CreateMap<ProductVariantDto, ProductVariant>() CreateMap<ProductVariantDto, ProductVariant>()
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore()) .ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
.ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()) .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore());
.ForMember(dest => dest.OrderItems, opt => opt.Ignore());
CreateMap<Product, ProductDto>(); CreateMap<Product, ProductDto>();
@@ -34,8 +32,7 @@ public class ProductMappingProfile: Profile
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore()) .ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
.ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()) .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore())
.ForMember(dest => dest.Category, opt => opt.Ignore()) .ForMember(dest => dest.Category, opt => opt.Ignore())
.ForMember(dest => dest.ProductVariants, opt => opt.Ignore()) .ForMember(dest => dest.ProductVariants, opt => opt.Ignore());
.ForMember(dest => dest.OrderItems, opt => opt.Ignore());
CreateMap<Category, CategoryDto>(); CreateMap<Category, CategoryDto>();
} }

View File

@@ -4,11 +4,22 @@ public class Address : EntityBase
{ {
public required string UserId { get; set; } public required string UserId { get; set; }
public required string AddressType { 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 City { get; set; }
public required string State { get; set; } public required string State { get; set; }
public required string PostalCode { get; set; } public required string PostalCode { get; set; }
public required string Country { 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 IsDefault { get; set; }
public required bool IsActive { get; set; } public required bool IsActive { get; set; }
public virtual User User { get; set; } = null!;
} }

View File

@@ -4,15 +4,24 @@ public class Order : EntityBase
{ {
public string UserId { get; set; } = null!; public string UserId { get; set; } = null!;
public DateTime OrderDate { 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 OrderStatusId { get; set; }
public int ShippingStatusId { get; set; } public int ShippingStatusId { get; set; }
public string OrderNumber { get; set; } = null!; public string OrderNumber { get; set; } = null!;
public string Notes { 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 OrderStatus OrderStatus { get; set; } = null!; public OrderStatus OrderStatus { get; set; } = null!;
public User User { get; set; } = null!; public User User { get; set; } = null!;
public ShippingStatus ShippingStatus { get; set; } = null!; public ShippingStatus ShippingStatus { get; set; } = null!;
public OrderAddress OrderAddress { get; set; } = null!; public OrderAddress OrderAddress { get; set; } = null!;
public virtual ICollection<OrderItem> OrderItems { get; set; } = new List<OrderItem>(); public Product Product { get; set; } = null!;
public ProductVariant? ProductVariant { get; set; }
} }

View File

@@ -3,11 +3,21 @@ namespace Imprink.Domain.Entities;
public class OrderAddress : EntityBase public class OrderAddress : EntityBase
{ {
public Guid OrderId { get; set; } 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 City { get; set; }
public required string State { get; set; } public required string State { get; set; }
public required string PostalCode { get; set; } public required string PostalCode { get; set; }
public required string Country { get; set; } public required string Country { get; set; }
public string? PhoneNumber { get; set; }
public string? Instructions { get; set; }
public virtual required Order Order { get; set; } public virtual required Order Order { get; set; }
} }

View File

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

View File

@@ -12,5 +12,5 @@ public class Product : EntityBase
public virtual required Category Category { get; set; } public virtual required Category Category { get; set; }
public virtual ICollection<ProductVariant> ProductVariants { get; set; } = new List<ProductVariant>(); public virtual ICollection<ProductVariant> ProductVariants { get; set; } = new List<ProductVariant>();
public virtual ICollection<OrderItem> OrderItems { get; set; } = new List<OrderItem>(); public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
} }

View File

@@ -12,5 +12,5 @@ public class ProductVariant : EntityBase
public bool IsActive { get; set; } public bool IsActive { get; set; }
public virtual required Product Product { get; set; } public virtual required Product Product { get; set; }
public virtual ICollection<OrderItem> OrderItems { get; set; } = new List<OrderItem>(); public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
} }

View File

@@ -8,14 +8,15 @@ public class User
public required string Email { get; set; } public required string Email { get; set; }
public bool EmailVerified { get; set; } public bool EmailVerified { get; set; }
public string? FirstName { get; set; } = null!; public string? FirstName { get; set; }
public string? LastName { get; set; } = null!; public string? LastName { get; set; }
public string? PhoneNumber { get; set; } public string? PhoneNumber { get; set; }
public required bool IsActive { get; set; } public required bool IsActive { get; set; }
public virtual ICollection<Address> Addresses { get; set; } = new List<Address>(); public virtual ICollection<Address> Addresses { get; set; } = new List<Address>();
public virtual ICollection<UserRole> UserRoles { get; set; } = new List<UserRole>(); public virtual ICollection<UserRole> UserRoles { get; set; } = new List<UserRole>();
public virtual ICollection<Order> Orders { get; set; } = new List<Order>(); public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
public virtual ICollection<Order> MerchantOrders { get; set; } = new List<Order>();
public Address? DefaultAddress => Addresses.FirstOrDefault(a => a is { IsDefault: true, IsActive: true }); public Address? DefaultAddress => Addresses.FirstOrDefault(a => a is { IsDefault: true, IsActive: true });
public IEnumerable<Role> Roles => UserRoles.Select(ur => ur.Role); public IEnumerable<Role> Roles => UserRoles.Select(ur => ur.Role);

View File

@@ -0,0 +1,22 @@
using Imprink.Domain.Entities;
namespace Imprink.Domain.Repositories;
public interface IAddressRepository
{
Task<IEnumerable<Address>> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default);
Task<IEnumerable<Address>> GetActiveByUserIdAsync(string userId, CancellationToken cancellationToken = default);
Task<Address?> GetDefaultByUserIdAsync(string userId, CancellationToken cancellationToken = default);
Task<IEnumerable<Address>> GetByUserIdAndTypeAsync(string userId, string addressType, CancellationToken cancellationToken = default);
Task<Address?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<Address?> GetByIdAndUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default);
Task<Address> AddAsync(Address address, CancellationToken cancellationToken = default);
Task<Address> UpdateAsync(Address address, CancellationToken cancellationToken = default);
Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task<bool> 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<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default);
Task<bool> IsUserAddressAsync(Guid id, string userId, CancellationToken cancellationToken = default);
}

View File

@@ -1,27 +0,0 @@
using Imprink.Domain.Entities;
namespace Imprink.Domain.Repositories;
public interface IOrderItemRepository
{
Task<OrderItem?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<OrderItem?> GetByIdWithProductAsync(Guid id, CancellationToken cancellationToken = default);
Task<OrderItem?> GetByIdWithVariantAsync(Guid id, CancellationToken cancellationToken = default);
Task<OrderItem?> GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default);
Task<IEnumerable<OrderItem>> GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default);
Task<IEnumerable<OrderItem>> GetByOrderIdWithProductsAsync(Guid orderId, CancellationToken cancellationToken = default);
Task<IEnumerable<OrderItem>> GetByProductIdAsync(Guid productId, CancellationToken cancellationToken = default);
Task<IEnumerable<OrderItem>> GetByProductVariantIdAsync(Guid productVariantId, CancellationToken cancellationToken = default);
Task<IEnumerable<OrderItem>> GetCustomizedItemsAsync(CancellationToken cancellationToken = default);
Task<IEnumerable<OrderItem>> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default);
Task<OrderItem> AddAsync(OrderItem orderItem, CancellationToken cancellationToken = default);
Task<IEnumerable<OrderItem>> AddRangeAsync(IEnumerable<OrderItem> orderItems, CancellationToken cancellationToken = default);
Task<OrderItem> UpdateAsync(OrderItem orderItem, CancellationToken cancellationToken = default);
Task DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task DeleteByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default);
Task<decimal> GetTotalValueByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default);
Task<int> GetQuantityByProductIdAsync(Guid productId, CancellationToken cancellationToken = default);
Task<int> GetQuantityByVariantIdAsync(Guid productVariantId, CancellationToken cancellationToken = default);
Task<Dictionary<Guid, int>> GetProductSalesCountAsync(DateTime? startDate = null, DateTime? endDate = null, CancellationToken cancellationToken = default);
}

View File

@@ -6,22 +6,24 @@ namespace Imprink.Domain.Repositories;
public interface IOrderRepository public interface IOrderRepository
{ {
Task<Order?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default); Task<Order?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<Order?> GetByIdWithItemsAsync(Guid id, CancellationToken cancellationToken = default); Task<Order?> GetByIdWithDetailsAsync(Guid id, CancellationToken cancellationToken = default);
Task<Order?> GetByIdWithAddressAsync(Guid id, CancellationToken cancellationToken = default);
Task<Order?> GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default);
Task<Order?> GetByOrderNumberAsync(string orderNumber, CancellationToken cancellationToken = default); Task<Order?> GetByOrderNumberAsync(string orderNumber, CancellationToken cancellationToken = default);
Task<PagedResult<Order>> GetPagedAsync(OrderFilterParameters filterParameters, CancellationToken cancellationToken = default);
Task<IEnumerable<Order>> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default); Task<IEnumerable<Order>> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default);
Task<IEnumerable<Order>> GetByUserIdPagedAsync(string userId, int pageNumber, int pageSize, CancellationToken cancellationToken = default); Task<IEnumerable<Order>> GetByUserIdWithDetailsAsync(string userId, CancellationToken cancellationToken = default);
Task<IEnumerable<Order>> GetByOrderStatusAsync(int orderStatusId, CancellationToken cancellationToken = default); Task<IEnumerable<Order>> GetByMerchantIdAsync(string merchantId, CancellationToken cancellationToken = default);
Task<IEnumerable<Order>> GetByShippingStatusAsync(int shippingStatusId, CancellationToken cancellationToken = default); Task<IEnumerable<Order>> GetByMerchantIdWithDetailsAsync(string merchantId, CancellationToken cancellationToken = default);
Task<IEnumerable<Order>> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); Task<IEnumerable<Order>> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default);
Task<IEnumerable<Order>> GetByStatusAsync(int statusId, CancellationToken cancellationToken = default);
Task<IEnumerable<Order>> GetByShippingStatusAsync(int shippingStatusId, CancellationToken cancellationToken = default);
Task<Order> AddAsync(Order order, CancellationToken cancellationToken = default); Task<Order> AddAsync(Order order, CancellationToken cancellationToken = default);
Task<Order> UpdateAsync(Order order, CancellationToken cancellationToken = default); Task<Order> UpdateAsync(Order order, CancellationToken cancellationToken = default);
Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default); Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default);
Task<bool> OrderNumberExistsAsync(string orderNumber, Guid? excludeId = null, CancellationToken cancellationToken = default); Task<bool> IsOrderNumberUniqueAsync(string orderNumber, CancellationToken cancellationToken = default);
Task<decimal> GetTotalRevenueAsync(CancellationToken cancellationToken = default); Task<bool> IsOrderNumberUniqueAsync(string orderNumber, Guid excludeOrderId, CancellationToken cancellationToken = default);
Task<decimal> GetTotalRevenueByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); Task<string> GenerateOrderNumberAsync(CancellationToken cancellationToken = default);
Task<int> 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);
} }

View File

@@ -18,10 +18,31 @@ public class AddressConfiguration : EntityBaseConfiguration<Address>
.IsRequired() .IsRequired()
.HasMaxLength(50); .HasMaxLength(50);
builder.Property(a => a.Street) 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.AddressLine1)
.IsRequired() .IsRequired()
.HasMaxLength(200); .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) builder.Property(a => a.City)
.IsRequired() .IsRequired()
.HasMaxLength(100); .HasMaxLength(100);
@@ -37,6 +58,12 @@ public class AddressConfiguration : EntityBaseConfiguration<Address>
.IsRequired() .IsRequired()
.HasMaxLength(100); .HasMaxLength(100);
builder.Property(a => a.PhoneNumber)
.HasMaxLength(20);
builder.Property(a => a.Instructions)
.HasMaxLength(500);
builder.Property(a => a.IsDefault) builder.Property(a => a.IsDefault)
.IsRequired() .IsRequired()
.HasDefaultValue(false); .HasDefaultValue(false);
@@ -45,6 +72,12 @@ public class AddressConfiguration : EntityBaseConfiguration<Address>
.IsRequired() .IsRequired()
.HasDefaultValue(true); .HasDefaultValue(true);
builder.HasOne(a => a.User)
.WithMany(u => u.Addresses)
.HasForeignKey(a => a.UserId)
.HasPrincipalKey(u => u.Id)
.OnDelete(DeleteBehavior.Cascade);
builder.HasIndex(a => a.UserId) builder.HasIndex(a => a.UserId)
.HasDatabaseName("IX_Address_UserId"); .HasDatabaseName("IX_Address_UserId");

View File

@@ -13,10 +13,35 @@ public class OrderAddressConfiguration : EntityBaseConfiguration<OrderAddress>
builder.Property(oa => oa.OrderId) builder.Property(oa => oa.OrderId)
.IsRequired(); .IsRequired();
builder.Property(oa => oa.Street) 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.AddressLine1)
.IsRequired() .IsRequired()
.HasMaxLength(200); .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) builder.Property(oa => oa.City)
.IsRequired() .IsRequired()
.HasMaxLength(100); .HasMaxLength(100);
@@ -32,6 +57,12 @@ public class OrderAddressConfiguration : EntityBaseConfiguration<OrderAddress>
.IsRequired() .IsRequired()
.HasMaxLength(100); .HasMaxLength(100);
builder.Property(oa => oa.PhoneNumber)
.HasMaxLength(20);
builder.Property(oa => oa.Instructions)
.HasMaxLength(500);
builder.HasIndex(oa => oa.OrderId) builder.HasIndex(oa => oa.OrderId)
.IsUnique() .IsUnique()
.HasDatabaseName("IX_OrderAddress_OrderId"); .HasDatabaseName("IX_OrderAddress_OrderId");

View File

@@ -1,3 +1,4 @@
using System.Text.Json;
using Imprink.Domain.Entities; using Imprink.Domain.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
@@ -5,73 +6,131 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Imprink.Infrastructure.Configuration; namespace Imprink.Infrastructure.Configuration;
public class OrderConfiguration : EntityBaseConfiguration<Order> public class OrderConfiguration : EntityBaseConfiguration<Order>
{
public override void Configure(EntityTypeBuilder<Order> builder)
{ {
public override void Configure(EntityTypeBuilder<Order> builder) base.Configure(builder);
{
base.Configure(builder);
builder.Property(o => o.UserId) builder.Property(o => o.UserId)
.IsRequired() .IsRequired()
.HasMaxLength(450); .HasMaxLength(450);
builder.Property(o => o.OrderDate) builder.Property(o => o.OrderDate)
.IsRequired(); .IsRequired();
builder.Property(o => o.TotalPrice) builder.Property(o => o.Amount)
.IsRequired() .IsRequired()
.HasColumnType("decimal(18,2)"); .HasColumnType("decimal(18,2)");
builder.Property(o => o.OrderStatusId) builder.Property(o => o.Quantity)
.IsRequired(); .IsRequired()
.HasDefaultValue(1);
builder.Property(o => o.ShippingStatusId) builder.Property(o => o.ProductId)
.IsRequired(); .IsRequired();
builder.Property(o => o.OrderNumber) builder.Property(o => o.OrderStatusId)
.IsRequired() .IsRequired();
.HasMaxLength(50);
builder.Property(o => o.Notes) builder.Property(o => o.ShippingStatusId)
.HasMaxLength(1000); .IsRequired();
builder.HasOne(o => o.OrderStatus) builder.Property(o => o.OrderNumber)
.WithMany(os => os.Orders) .IsRequired()
.HasForeignKey(o => o.OrderStatusId) .HasMaxLength(50);
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(o => o.ShippingStatus) builder.Property(o => o.Notes)
.WithMany(ss => ss.Orders) .HasMaxLength(1000);
.HasForeignKey(o => o.ShippingStatusId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(o => o.OrderAddress) builder.Property(o => o.MerchantId)
.WithOne(oa => oa.Order) .HasMaxLength(450);
.HasForeignKey<OrderAddress>(oa => oa.OrderId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(o => o.User) builder.Property(o => o.ComposingImageUrl)
.WithMany(u => u.Orders) .HasMaxLength(1000);
.HasForeignKey(o => o.UserId)
.HasPrincipalKey(u => u.Id)
.OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(o => o.UserId) builder.Property(o => o.OriginalImageUrls)
.HasDatabaseName("IX_Order_UserId"); .HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => JsonSerializer.Deserialize<string[]>(v, (JsonSerializerOptions?)null) ?? Array.Empty<string>())
.HasColumnType("nvarchar(max)");
builder.HasIndex(o => o.OrderNumber) builder.Property(o => o.CustomizationImageUrl)
.IsUnique() .IsRequired()
.HasDatabaseName("IX_Order_OrderNumber"); .HasMaxLength(1000);
builder.HasIndex(o => o.OrderDate) builder.Property(o => o.CustomizationDescription)
.HasDatabaseName("IX_Order_OrderDate"); .IsRequired()
.HasMaxLength(2000);
builder.HasIndex(o => o.OrderStatusId) builder.HasOne(o => o.OrderStatus)
.HasDatabaseName("IX_Order_OrderStatusId"); .WithMany(os => os.Orders)
.HasForeignKey(o => o.OrderStatusId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(o => o.ShippingStatusId) builder.HasOne(o => o.ShippingStatus)
.HasDatabaseName("IX_Order_ShippingStatusId"); .WithMany(ss => ss.Orders)
.HasForeignKey(o => o.ShippingStatusId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(o => new { o.UserId, o.OrderDate }) builder.HasOne(o => o.OrderAddress)
.HasDatabaseName("IX_Order_User_Date"); .WithOne(oa => oa.Order)
} .HasForeignKey<OrderAddress>(oa => oa.OrderId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(o => o.User)
.WithMany(u => u.Orders)
.HasForeignKey(o => o.UserId)
.HasPrincipalKey(u => u.Id)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<User>()
.WithMany(u => u.MerchantOrders)
.HasForeignKey(o => o.MerchantId)
.HasPrincipalKey(u => u.Id)
.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.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 => 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");
} }
}

View File

@@ -1,64 +0,0 @@
using Imprink.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Imprink.Infrastructure.Configuration;
public class OrderItemConfiguration : EntityBaseConfiguration<OrderItem>
{
public override void Configure(EntityTypeBuilder<OrderItem> 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");
}
}

View File

@@ -25,8 +25,7 @@ public class UserConfiguration : IEntityTypeConfiguration<User>
.HasMaxLength(100); .HasMaxLength(100);
builder.Property(u => u.EmailVerified) builder.Property(u => u.EmailVerified)
.IsRequired() .IsRequired();
.HasMaxLength(100);
builder.Property(u => u.FirstName) builder.Property(u => u.FirstName)
.HasMaxLength(100); .HasMaxLength(100);

View File

@@ -11,7 +11,6 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
public DbSet<Product> Products { get; set; } public DbSet<Product> Products { get; set; }
public DbSet<ProductVariant> ProductVariants { get; set; } public DbSet<ProductVariant> ProductVariants { get; set; }
public DbSet<Order> Orders { get; set; } public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
public DbSet<OrderAddress> OrderAddresses { get; set; } public DbSet<OrderAddress> OrderAddresses { get; set; }
public DbSet<Address> Addresses { get; set; } public DbSet<Address> Addresses { get; set; }
public DbSet<OrderStatus> OrderStatuses { get; set; } public DbSet<OrderStatus> OrderStatuses { get; set; }
@@ -28,7 +27,6 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
modelBuilder.ApplyConfiguration(new ProductConfiguration()); modelBuilder.ApplyConfiguration(new ProductConfiguration());
modelBuilder.ApplyConfiguration(new ProductVariantConfiguration()); modelBuilder.ApplyConfiguration(new ProductVariantConfiguration());
modelBuilder.ApplyConfiguration(new OrderConfiguration()); modelBuilder.ApplyConfiguration(new OrderConfiguration());
modelBuilder.ApplyConfiguration(new OrderItemConfiguration());
modelBuilder.ApplyConfiguration(new OrderAddressConfiguration()); modelBuilder.ApplyConfiguration(new OrderAddressConfiguration());
modelBuilder.ApplyConfiguration(new AddressConfiguration()); modelBuilder.ApplyConfiguration(new AddressConfiguration());
modelBuilder.ApplyConfiguration(new OrderStatusConfiguration()); modelBuilder.ApplyConfiguration(new OrderStatusConfiguration());

View File

@@ -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<IEnumerable<Address>> 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<IEnumerable<Address>> 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<Address?> GetDefaultByUserIdAsync(string userId, CancellationToken cancellationToken = default)
{
return await context.Addresses
.FirstOrDefaultAsync(a => a.UserId == userId && a.IsDefault && a.IsActive, cancellationToken);
}
public async Task<IEnumerable<Address>> 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<Address?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return await context.Addresses
.FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
}
public async Task<Address?> GetByIdAndUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default)
{
return await context.Addresses
.FirstOrDefaultAsync(a => a.Id == id && a.UserId == userId, cancellationToken);
}
public async Task<Address> AddAsync(Address address, CancellationToken cancellationToken = default)
{
if (address.IsDefault)
{
await UnsetDefaultAddressesAsync(address.UserId, cancellationToken);
}
context.Addresses.Add(address);
return address;
}
public async Task<Address> 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<bool> 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<bool> 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<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
{
return await context.Addresses
.AnyAsync(a => a.Id == id, cancellationToken);
}
public async Task<bool> 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;
}
}
}

View File

@@ -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<OrderItem?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return await context.OrderItems
.FirstOrDefaultAsync(oi => oi.Id == id, cancellationToken);
}
public async Task<OrderItem?> 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<OrderItem?> GetByIdWithVariantAsync(Guid id, CancellationToken cancellationToken = default)
{
return await context.OrderItems
.Include(oi => oi.ProductVariant)
.FirstOrDefaultAsync(oi => oi.Id == id, cancellationToken);
}
public async Task<OrderItem?> 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<IEnumerable<OrderItem>> GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default)
{
return await context.OrderItems
.Where(oi => oi.OrderId == orderId)
.OrderBy(oi => oi.CreatedAt)
.ToListAsync(cancellationToken);
}
public async Task<IEnumerable<OrderItem>> 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<IEnumerable<OrderItem>> 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<IEnumerable<OrderItem>> 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<IEnumerable<OrderItem>> 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<IEnumerable<OrderItem>> 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<OrderItem> 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<IEnumerable<OrderItem>> AddRangeAsync(IEnumerable<OrderItem> 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<IEnumerable<OrderItem>>(items);
}
public Task<OrderItem> 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<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
{
return await context.OrderItems
.AnyAsync(oi => oi.Id == id, cancellationToken);
}
public async Task<decimal> GetTotalValueByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default)
{
return await context.OrderItems
.Where(oi => oi.OrderId == orderId)
.SumAsync(oi => oi.TotalPrice, cancellationToken);
}
public async Task<int> GetQuantityByProductIdAsync(Guid productId, CancellationToken cancellationToken = default)
{
return await context.OrderItems
.Where(oi => oi.ProductId == productId)
.SumAsync(oi => oi.Quantity, cancellationToken);
}
public async Task<int> GetQuantityByVariantIdAsync(Guid productVariantId, CancellationToken cancellationToken = default)
{
return await context.OrderItems
.Where(oi => oi.ProductVariantId == productVariantId)
.SumAsync(oi => oi.Quantity, cancellationToken);
}
public async Task<Dictionary<Guid, int>> 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);
}
}

View File

@@ -1,5 +1,4 @@
using Imprink.Domain.Entities; using Imprink.Domain.Entities;
using Imprink.Domain.Models;
using Imprink.Domain.Repositories; using Imprink.Domain.Repositories;
using Imprink.Infrastructure.Database; using Imprink.Infrastructure.Database;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -14,159 +13,79 @@ public class OrderRepository(ApplicationDbContext context) : IOrderRepository
.FirstOrDefaultAsync(o => o.Id == id, cancellationToken); .FirstOrDefaultAsync(o => o.Id == id, cancellationToken);
} }
public async Task<Order?> GetByIdWithItemsAsync(Guid id, CancellationToken cancellationToken = default) public async Task<Order?> GetByIdWithDetailsAsync(Guid id, CancellationToken cancellationToken = default)
{ {
return await context.Orders 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<Order?> GetByIdWithAddressAsync(Guid id, CancellationToken cancellationToken = default)
{
return await context.Orders
.Include(o => o.OrderAddress)
.FirstOrDefaultAsync(o => o.Id == id, cancellationToken);
}
public async Task<Order?> GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default)
{
return await context.Orders
.Include(o => o.User)
.Include(o => o.OrderStatus) .Include(o => o.OrderStatus)
.Include(o => o.ShippingStatus) .Include(o => o.ShippingStatus)
.Include(o => o.OrderAddress) .Include(o => o.OrderAddress)
.Include(o => o.OrderItems) .Include(o => o.Product)
.ThenInclude(oi => oi.Product) .Include(o => o.ProductVariant)
.ThenInclude(p => p.Category) .Include(o => o.User)
.Include(o => o.OrderItems)
.ThenInclude(oi => oi.ProductVariant)
.FirstOrDefaultAsync(o => o.Id == id, cancellationToken); .FirstOrDefaultAsync(o => o.Id == id, cancellationToken);
} }
public async Task<Order?> GetByOrderNumberAsync(string orderNumber, CancellationToken cancellationToken = default) public async Task<Order?> GetByOrderNumberAsync(string orderNumber, CancellationToken cancellationToken = default)
{ {
return await context.Orders return await context.Orders
.Include(o => o.OrderStatus)
.Include(o => o.ShippingStatus)
.FirstOrDefaultAsync(o => o.OrderNumber == orderNumber, cancellationToken); .FirstOrDefaultAsync(o => o.OrderNumber == orderNumber, cancellationToken);
} }
public async Task<PagedResult<Order>> GetPagedAsync(
OrderFilterParameters filterParameters,
CancellationToken cancellationToken = default)
{
var query = context.Orders
.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<Order>
{
Items = items,
TotalCount = totalCount,
PageNumber = filterParameters.PageNumber,
PageSize = filterParameters.PageSize
};
}
public async Task<IEnumerable<Order>> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default) public async Task<IEnumerable<Order>> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default)
{ {
return await context.Orders return await context.Orders
.Include(o => o.OrderStatus)
.Include(o => o.ShippingStatus)
.Where(o => o.UserId == userId) .Where(o => o.UserId == userId)
.OrderByDescending(o => o.OrderDate) .OrderByDescending(o => o.OrderDate)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
} }
public async Task<IEnumerable<Order>> GetByUserIdPagedAsync( public async Task<IEnumerable<Order>> GetByUserIdWithDetailsAsync(string userId, CancellationToken cancellationToken = default)
string userId,
int pageNumber,
int pageSize,
CancellationToken cancellationToken = default)
{ {
return await context.Orders return await context.Orders
.Include(o => o.OrderStatus) .Include(o => o.OrderStatus)
.Include(o => o.ShippingStatus) .Include(o => o.ShippingStatus)
.Include(o => o.OrderAddress)
.Include(o => o.Product)
.Include(o => o.ProductVariant)
.Where(o => o.UserId == userId) .Where(o => o.UserId == userId)
.OrderByDescending(o => o.OrderDate) .OrderByDescending(o => o.OrderDate)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
} }
public async Task<IEnumerable<Order>> GetByOrderStatusAsync(int orderStatusId, CancellationToken cancellationToken = default) public async Task<IEnumerable<Order>> GetByMerchantIdAsync(string merchantId, CancellationToken cancellationToken = default)
{ {
return await context.Orders return await context.Orders
.Where(o => o.MerchantId == merchantId)
.OrderByDescending(o => o.OrderDate)
.ToListAsync(cancellationToken);
}
public async Task<IEnumerable<Order>> 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.User)
.Include(o => o.OrderStatus) .Where(o => o.MerchantId == merchantId)
.Include(o => o.ShippingStatus) .OrderByDescending(o => o.OrderDate)
.Where(o => o.OrderStatusId == orderStatusId) .ToListAsync(cancellationToken);
}
public async Task<IEnumerable<Order>> 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<IEnumerable<Order>> GetByStatusAsync(int statusId, CancellationToken cancellationToken = default)
{
return await context.Orders
.Where(o => o.OrderStatusId == statusId)
.OrderByDescending(o => o.OrderDate) .OrderByDescending(o => o.OrderDate)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
} }
@@ -174,52 +93,39 @@ public class OrderRepository(ApplicationDbContext context) : IOrderRepository
public async Task<IEnumerable<Order>> GetByShippingStatusAsync(int shippingStatusId, CancellationToken cancellationToken = default) public async Task<IEnumerable<Order>> GetByShippingStatusAsync(int shippingStatusId, CancellationToken cancellationToken = default)
{ {
return await context.Orders return await context.Orders
.Include(o => o.User)
.Include(o => o.OrderStatus)
.Include(o => o.ShippingStatus)
.Where(o => o.ShippingStatusId == shippingStatusId) .Where(o => o.ShippingStatusId == shippingStatusId)
.OrderByDescending(o => o.OrderDate) .OrderByDescending(o => o.OrderDate)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
} }
public async Task<IEnumerable<Order>> 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<Order> AddAsync(Order order, CancellationToken cancellationToken = default) public Task<Order> AddAsync(Order order, CancellationToken cancellationToken = default)
{ {
order.Id = Guid.NewGuid();
order.CreatedAt = DateTime.UtcNow;
order.ModifiedAt = DateTime.UtcNow;
context.Orders.Add(order); context.Orders.Add(order);
return Task.FromResult(order); return Task.FromResult(order);
} }
public Task<Order> UpdateAsync(Order order, CancellationToken cancellationToken = default) public async Task<Order> UpdateAsync(Order order, CancellationToken cancellationToken = default)
{ {
order.ModifiedAt = DateTime.UtcNow; var existingOrder = await context.Orders
context.Orders.Update(order); .FirstOrDefaultAsync(o => o.Id == order.Id, cancellationToken);
return Task.FromResult(order);
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<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{ {
var order = await GetByIdAsync(id, cancellationToken); var order = await context.Orders
if (order != null) .FirstOrDefaultAsync(o => o.Id == id, cancellationToken);
{
context.Orders.Remove(order); if (order == null)
} return false;
context.Orders.Remove(order);
return true;
} }
public async Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default) public async Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
@@ -228,38 +134,78 @@ public class OrderRepository(ApplicationDbContext context) : IOrderRepository
.AnyAsync(o => o.Id == id, cancellationToken); .AnyAsync(o => o.Id == id, cancellationToken);
} }
public async Task<bool> OrderNumberExistsAsync(string orderNumber, Guid? excludeId = null, CancellationToken cancellationToken = default) public async Task<bool> IsOrderNumberUniqueAsync(string orderNumber, CancellationToken cancellationToken = default)
{ {
var query = context.Orders.Where(o => o.OrderNumber == orderNumber); return !await context.Orders
.AnyAsync(o => o.OrderNumber == orderNumber, cancellationToken);
}
if (excludeId.HasValue) public async Task<bool> IsOrderNumberUniqueAsync(string orderNumber, Guid excludeOrderId, CancellationToken cancellationToken = default)
{
return !await context.Orders
.AnyAsync(o => o.OrderNumber == orderNumber && o.Id != excludeOrderId, cancellationToken);
}
public async Task<string> GenerateOrderNumberAsync(CancellationToken cancellationToken = default)
{
string orderNumber;
bool isUnique;
do
{ {
query = query.Where(o => o.Id != excludeId.Value); // Generate order number format: ORD-YYYYMMDD-XXXXXX (where X is random)
var datePart = DateTime.UtcNow.ToString("yyyyMMdd");
var randomPart = Random.Shared.Next(100000, 999999).ToString();
orderNumber = $"ORD-{datePart}-{randomPart}";
isUnique = await IsOrderNumberUniqueAsync(orderNumber, cancellationToken);
} }
while (!isUnique);
return await query.AnyAsync(cancellationToken); return orderNumber;
} }
public async Task<decimal> GetTotalRevenueAsync(CancellationToken cancellationToken = default) public async Task UpdateStatusAsync(Guid orderId, int statusId, CancellationToken cancellationToken = default)
{ {
return await context.Orders var order = await context.Orders
.Where(o => o.OrderStatusId != 5) // Assuming 5 is cancelled status .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
.SumAsync(o => o.TotalPrice, cancellationToken);
if (order != null)
{
order.OrderStatusId = statusId;
}
} }
public async Task<decimal> GetTotalRevenueByDateRangeAsync( public async Task UpdateShippingStatusAsync(Guid orderId, int shippingStatusId, CancellationToken cancellationToken = default)
DateTime startDate,
DateTime endDate,
CancellationToken cancellationToken = default)
{ {
return await context.Orders var order = await context.Orders
.Where(o => o.OrderDate >= startDate && o.OrderDate <= endDate && o.OrderStatusId != 5) .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
.SumAsync(o => o.TotalPrice, cancellationToken);
if (order != null)
{
order.ShippingStatusId = shippingStatusId;
}
} }
public async Task<int> GetOrderCountByStatusAsync(int orderStatusId, CancellationToken cancellationToken = default) public async Task AssignMerchantAsync(Guid orderId, string merchantId, CancellationToken cancellationToken = default)
{ {
return await context.Orders var order = await context.Orders
.CountAsync(o => o.OrderStatusId == orderStatusId, cancellationToken); .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
if (order != null)
{
order.MerchantId = merchantId;
}
}
public async Task UnassignMerchantAsync(Guid orderId, CancellationToken cancellationToken = default)
{
var order = await context.Orders
.FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
if (order != null)
{
order.MerchantId = null;
}
} }
} }

View File

@@ -13,7 +13,7 @@ public class UnitOfWork(
IUserRoleRepository userRoleRepository, IUserRoleRepository userRoleRepository,
IRoleRepository roleRepository, IRoleRepository roleRepository,
IOrderRepository orderRepository, IOrderRepository orderRepository,
IOrderItemRepository orderItemRepository) : IUnitOfWork IAddressRepository addressRepository) : IUnitOfWork
{ {
public IProductRepository ProductRepository => productRepository; public IProductRepository ProductRepository => productRepository;
public IProductVariantRepository ProductVariantRepository => productVariantRepository; public IProductVariantRepository ProductVariantRepository => productVariantRepository;
@@ -22,7 +22,7 @@ public class UnitOfWork(
public IUserRoleRepository UserRoleRepository => userRoleRepository; public IUserRoleRepository UserRoleRepository => userRoleRepository;
public IRoleRepository RoleRepository => roleRepository; public IRoleRepository RoleRepository => roleRepository;
public IOrderRepository OrderRepository => orderRepository; public IOrderRepository OrderRepository => orderRepository;
public IOrderItemRepository OrderItemRepository => orderItemRepository; public IAddressRepository AddressRepository => addressRepository;
public async Task SaveAsync(CancellationToken cancellationToken = default) public async Task SaveAsync(CancellationToken cancellationToken = default)
{ {

View File

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

View File

@@ -35,7 +35,7 @@ public class CreateCategoryHandlerIntegrationTest : IDisposable
services.AddScoped<IUserRoleRepository, UserRoleRepository>(); services.AddScoped<IUserRoleRepository, UserRoleRepository>();
services.AddScoped<IRoleRepository, RoleRepository>(); services.AddScoped<IRoleRepository, RoleRepository>();
services.AddScoped<IOrderRepository, OrderRepository>(); services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IOrderItemRepository, OrderItemRepository>(); services.AddScoped<IAddressRepository, AddressRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>(); services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<CreateCategoryHandler>(); services.AddScoped<CreateCategoryHandler>();