Created Order, Refund, Shipping
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||
|
||||
[EntityTypeConfiguration<OrderConfiguration, Order>]
|
||||
public class Order : Models.Order
|
||||
{
|
||||
public virtual Shipping? Shipping { get; set; }
|
||||
|
||||
public virtual ICollection<OrderItem> OrderItems { get; set; } = [];
|
||||
|
||||
public virtual ICollection<Refund> Refunds { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||
|
||||
public class OrderConfiguration : IEntityTypeConfiguration<Order>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Order> builder)
|
||||
{
|
||||
builder.ToTable("Orders");
|
||||
|
||||
builder.HasKey(o => o.Id);
|
||||
builder.Property(o => o.CustomerId).IsRequired();
|
||||
builder.Property(o => o.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||
builder.Property(o => o.UpdatedAt).HasDefaultValueSql("now()");
|
||||
builder.Property(o => o.Status).IsRequired();
|
||||
builder.Property(o => o.Total).IsRequired().HasColumnType("decimal(18,2)");
|
||||
builder.Property(o => o.Notes).HasMaxLength(1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
||||
using LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||
|
||||
[EntityTypeConfiguration<OrderItemConfiguration, OrderItem>]
|
||||
public class OrderItem : Models.OrderItem
|
||||
{
|
||||
public virtual Order? Order { get; set; }
|
||||
|
||||
public virtual AuthorBook? AuthorBook { get; set; }
|
||||
|
||||
public virtual ProductPrice? ProductPrice { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||
|
||||
public class OrderItemConfiguration : IEntityTypeConfiguration<OrderItem>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<OrderItem> builder)
|
||||
{
|
||||
builder.ToTable("OrderItems");
|
||||
|
||||
builder.HasKey(oi => oi.Id);
|
||||
builder.Property(oi => oi.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("new()");
|
||||
builder.Property(oi => oi.OrderId).IsRequired();
|
||||
builder.Property(oi => oi.AuthorBookId).IsRequired();
|
||||
builder.Property(oi => oi.ProductPriceId).IsRequired();
|
||||
builder.Property(oi => oi.Quantity).IsRequired();
|
||||
|
||||
builder.HasOne(oi => oi.Order)
|
||||
.WithMany(o => o.OrderItems)
|
||||
.HasForeignKey(oi => oi.OrderId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasOne(oi => oi.AuthorBook)
|
||||
.WithMany()
|
||||
.HasForeignKey(oi => oi.AuthorBookId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(oi => oi.ProductPrice)
|
||||
.WithMany()
|
||||
.HasForeignKey(oi => oi.ProductPriceId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||
|
||||
[EntityTypeConfiguration<RefundConfiguration, Refund>]
|
||||
public class Refund : Models.Refund
|
||||
{
|
||||
public virtual Order? Order { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||
|
||||
public class RefundConfiguration : IEntityTypeConfiguration<Refund>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Refund> builder)
|
||||
{
|
||||
builder.ToTable("Refunds");
|
||||
|
||||
builder.HasKey(r => r.Id);
|
||||
builder.Property(r => r.OrderId).IsRequired();
|
||||
builder.Property(o => o.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||
builder.Property(o => o.UpdatedAt).HasDefaultValueSql("now()");
|
||||
builder.Property(o => o.Status).IsRequired();
|
||||
builder.Property(r => r.Amount).IsRequired().HasPrecision(18, 2);
|
||||
builder.Property(r => r.Reason).HasMaxLength(1000);
|
||||
|
||||
builder.HasOne(r => r.Order)
|
||||
.WithMany(o => o.Refunds)
|
||||
.HasForeignKey(r => r.OrderId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using LiteCharms.Features.MidrandBooks.Customers.Entities;
|
||||
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||
|
||||
[EntityTypeConfiguration<ShippingConfiguration, Shipping>]
|
||||
public class Shipping : Models.Shipping
|
||||
{
|
||||
public virtual Order? Order { get; set; }
|
||||
|
||||
public virtual Address? Address { get; set; }
|
||||
|
||||
public virtual ShippingProvider? ShippingProvider { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||
|
||||
public class ShippingConfiguration : IEntityTypeConfiguration<Shipping>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Shipping> builder)
|
||||
{
|
||||
builder.ToTable("Shippings");
|
||||
|
||||
builder.HasKey(s => s.Id);
|
||||
builder.Property(s => s.OrderId).IsRequired();
|
||||
builder.Property(s => s.AddressId).IsRequired();
|
||||
builder.Property(s => s.ShippingProviderId).IsRequired();
|
||||
builder.Property(s => s.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||
builder.Property(s => s.UpdatedAt).HasDefaultValueSql("now()");
|
||||
builder.Property(s => s.Status).IsRequired();
|
||||
|
||||
builder.HasOne(s => s.Order)
|
||||
.WithOne(o => o.Shipping)
|
||||
.HasForeignKey<Shipping>(s => s.OrderId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(s => s.Address)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.AddressId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(f => f.ShippingProvider)
|
||||
.WithMany(f => f.Shippings)
|
||||
.HasForeignKey(f => f.ShippingProviderId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||
|
||||
public class ShippingProvider : Models.ShippingProvider
|
||||
{
|
||||
public virtual ICollection<Shipping> Shippings { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||
|
||||
public class ShippingProviderConfiguration : IEntityTypeConfiguration<ShippingProvider>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ShippingProvider> builder)
|
||||
{
|
||||
builder.ToTable("ShippingProviders");
|
||||
|
||||
builder.HasKey(f => f.Id);
|
||||
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||
builder.Property(f => f.UpdatedAt).HasDefaultValueSql("now()");
|
||||
builder.Property(f => f.Type).IsRequired();
|
||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(100);
|
||||
builder.Property(f => f.Price).IsRequired().HasPrecision(18, 2);
|
||||
builder.Property(f => f.Enabled).HasDefaultValue(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||
|
||||
public class Order
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public long CustomerId { get; set; }
|
||||
|
||||
public OrderStatus Status { get; set; }
|
||||
|
||||
public decimal Total { get; set; }
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public string? InvoiceUrl { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||
|
||||
public class OrderItem
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public long OrderId { get; set; }
|
||||
|
||||
public long AuthorBookId { get; set; }
|
||||
|
||||
public long ProductPriceId { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||
|
||||
public record CreateOrder(long CustomerId, decimal TotalPrice, string? Notes);
|
||||
|
||||
public record CreateOrderItem(long AuthorBookId, long ProductPriceId, int Quantity);
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||
|
||||
public class Refund
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public Guid OrderId { get; set; }
|
||||
|
||||
public RefundTypes Type { get; set; }
|
||||
|
||||
public RefundStatus Status { get; set; }
|
||||
|
||||
public string? Reason { get; set; }
|
||||
|
||||
public decimal Amount { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||
|
||||
public class Shipping
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public long OrderId { get; set; }
|
||||
|
||||
public long AddressId { get; set; }
|
||||
|
||||
public long ShippingProviderId { get; set; }
|
||||
|
||||
public ShippingStatuses Status { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||
|
||||
public class ShippingProvider
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public ShippingProviderTypes Type { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public decimal? Price { get; set; }
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||
using LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders;
|
||||
|
||||
public class OrderService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||
{
|
||||
public async ValueTask<Result<long>> CreateOrderAsync(long customerId, CreateOrder request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
||||
return Result.Fail<long>("Customer not found.");
|
||||
|
||||
var order = context.Orders.Add(new Entities.Order
|
||||
{
|
||||
CustomerId = customerId,
|
||||
Status = OrderStatus.Pending,
|
||||
Total = request.TotalPrice
|
||||
});
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok(order.Entity.Id)
|
||||
: Result.Fail<long>("Failed to create order.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result<long>> AddItemToOrderAsync(long orderId, CreateOrderItem request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
||||
return Result.Fail<long>("Order not found.");
|
||||
|
||||
if(!await context.Books.AnyAsync(ab => ab.Id == request.AuthorBookId, cancellationToken))
|
||||
return Result.Fail<long>("Author book not found.");
|
||||
|
||||
if (!await context.Prices.AnyAsync(pp => pp.Id == request.ProductPriceId, cancellationToken))
|
||||
return Result.Fail<long>("Product price not found.");
|
||||
|
||||
var orderItem = context.OrderItems.Add(new Entities.OrderItem
|
||||
{
|
||||
OrderId = orderId,
|
||||
AuthorBookId = request.AuthorBookId,
|
||||
ProductPriceId = request.ProductPriceId,
|
||||
Quantity = request.Quantity
|
||||
});
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok(orderItem.Entity.Id)
|
||||
: Result.Fail<long>("Failed to add item to order.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result> AddItemsToOrderAsync(long orderId, CreateOrderItem[] items, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(items.Length == 0)
|
||||
return Result.Fail("No items to add.");
|
||||
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
||||
return Result.Fail("Order not found.");
|
||||
|
||||
var existingItems = context.OrderItems.Where(oi => oi.OrderId == orderId);
|
||||
context.OrderItems.RemoveRange(existingItems);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!await context.Books.AnyAsync(ab => ab.Id == item.AuthorBookId, cancellationToken))
|
||||
return Result.Fail($"Author book with ID {item.AuthorBookId} not found.");
|
||||
|
||||
if (!await context.Prices.AnyAsync(pp => pp.Id == item.ProductPriceId, cancellationToken))
|
||||
return Result.Fail($"Product price with ID {item.ProductPriceId} not found.");
|
||||
|
||||
context.OrderItems.Add(new Entities.OrderItem
|
||||
{
|
||||
OrderId = orderId,
|
||||
AuthorBookId = item.AuthorBookId,
|
||||
ProductPriceId = item.ProductPriceId,
|
||||
Quantity = item.Quantity
|
||||
});
|
||||
}
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to add items to order.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user