diff --git a/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj b/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj
index 34c16e0..552ce45 100644
--- a/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj
+++ b/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj
@@ -163,8 +163,4 @@
-
-
-
-
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Entities/Order.cs b/LiteCharms.Features.MidrandBooks/Orders/Entities/Order.cs
new file mode 100644
index 0000000..ddc8468
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Entities/Order.cs
@@ -0,0 +1,11 @@
+namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
+
+[EntityTypeConfiguration]
+public class Order : Models.Order
+{
+ public virtual Shipping? Shipping { get; set; }
+
+ public virtual ICollection OrderItems { get; set; } = [];
+
+ public virtual ICollection Refunds { get; set; } = [];
+}
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Entities/OrderConfiguration.cs b/LiteCharms.Features.MidrandBooks/Orders/Entities/OrderConfiguration.cs
new file mode 100644
index 0000000..4063456
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Entities/OrderConfiguration.cs
@@ -0,0 +1,17 @@
+namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
+
+public class OrderConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Entities/OrderItem.cs b/LiteCharms.Features.MidrandBooks/Orders/Entities/OrderItem.cs
new file mode 100644
index 0000000..d255a04
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Entities/OrderItem.cs
@@ -0,0 +1,14 @@
+using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
+using LiteCharms.Features.MidrandBooks.Products.Entities;
+
+namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
+
+[EntityTypeConfiguration]
+public class OrderItem : Models.OrderItem
+{
+ public virtual Order? Order { get; set; }
+
+ public virtual AuthorBook? AuthorBook { get; set; }
+
+ public virtual ProductPrice? ProductPrice { get; set; }
+}
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Entities/OrderItemConfiguration.cs b/LiteCharms.Features.MidrandBooks/Orders/Entities/OrderItemConfiguration.cs
new file mode 100644
index 0000000..50ac4da
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Entities/OrderItemConfiguration.cs
@@ -0,0 +1,31 @@
+namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
+
+public class OrderItemConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Entities/Refund.cs b/LiteCharms.Features.MidrandBooks/Orders/Entities/Refund.cs
new file mode 100644
index 0000000..3116896
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Entities/Refund.cs
@@ -0,0 +1,7 @@
+namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
+
+[EntityTypeConfiguration]
+public class Refund : Models.Refund
+{
+ public virtual Order? Order { get; set; }
+}
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Entities/RefundConfiguration.cs b/LiteCharms.Features.MidrandBooks/Orders/Entities/RefundConfiguration.cs
new file mode 100644
index 0000000..566b779
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Entities/RefundConfiguration.cs
@@ -0,0 +1,22 @@
+namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
+
+public class RefundConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Entities/Shipping.cs b/LiteCharms.Features.MidrandBooks/Orders/Entities/Shipping.cs
new file mode 100644
index 0000000..d8f9e07
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Entities/Shipping.cs
@@ -0,0 +1,13 @@
+using LiteCharms.Features.MidrandBooks.Customers.Entities;
+
+namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
+
+[EntityTypeConfiguration]
+public class Shipping : Models.Shipping
+{
+ public virtual Order? Order { get; set; }
+
+ public virtual Address? Address { get; set; }
+
+ public virtual ShippingProvider? ShippingProvider { get; set; }
+}
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Entities/ShippingConfiguration.cs b/LiteCharms.Features.MidrandBooks/Orders/Entities/ShippingConfiguration.cs
new file mode 100644
index 0000000..2da9465
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Entities/ShippingConfiguration.cs
@@ -0,0 +1,32 @@
+namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
+
+public class ShippingConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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(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);
+ }
+}
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Entities/ShippingProvider.cs b/LiteCharms.Features.MidrandBooks/Orders/Entities/ShippingProvider.cs
new file mode 100644
index 0000000..1fc3127
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Entities/ShippingProvider.cs
@@ -0,0 +1,6 @@
+namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
+
+public class ShippingProvider : Models.ShippingProvider
+{
+ public virtual ICollection Shippings { get; set; } = [];
+}
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Entities/ShippingProviderConfiguration.cs b/LiteCharms.Features.MidrandBooks/Orders/Entities/ShippingProviderConfiguration.cs
new file mode 100644
index 0000000..003ab98
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Entities/ShippingProviderConfiguration.cs
@@ -0,0 +1,17 @@
+namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
+
+public class ShippingProviderConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/LiteCharms.Features.MidrandBooks/Order/Models/Order.cs b/LiteCharms.Features.MidrandBooks/Orders/Models/Order.cs
similarity index 69%
rename from LiteCharms.Features.MidrandBooks/Order/Models/Order.cs
rename to LiteCharms.Features.MidrandBooks/Orders/Models/Order.cs
index e0c1c73..a86c03e 100644
--- a/LiteCharms.Features.MidrandBooks/Order/Models/Order.cs
+++ b/LiteCharms.Features.MidrandBooks/Orders/Models/Order.cs
@@ -1,4 +1,4 @@
-namespace LiteCharms.Features.MidrandBooks.Order.Models;
+namespace LiteCharms.Features.MidrandBooks.Orders.Models;
public class Order
{
@@ -14,9 +14,7 @@ public class Order
public decimal Total { get; set; }
- public string[]? Notes { get; set; }
-
- public string[]? Terms { get; set; }
+ public string? Notes { get; set; }
public string? InvoiceUrl { get; set; }
}
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Models/OrderItem.cs b/LiteCharms.Features.MidrandBooks/Orders/Models/OrderItem.cs
new file mode 100644
index 0000000..424ad4f
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Models/OrderItem.cs
@@ -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; }
+}
diff --git a/LiteCharms.Features.MidrandBooks/Orders/Models/Records.cs b/LiteCharms.Features.MidrandBooks/Orders/Models/Records.cs
new file mode 100644
index 0000000..a77d3aa
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/Models/Records.cs
@@ -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);
diff --git a/LiteCharms.Features.MidrandBooks/Order/Models/Refund.cs b/LiteCharms.Features.MidrandBooks/Orders/Models/Refund.cs
similarity index 85%
rename from LiteCharms.Features.MidrandBooks/Order/Models/Refund.cs
rename to LiteCharms.Features.MidrandBooks/Orders/Models/Refund.cs
index 920145e..3f5eec4 100644
--- a/LiteCharms.Features.MidrandBooks/Order/Models/Refund.cs
+++ b/LiteCharms.Features.MidrandBooks/Orders/Models/Refund.cs
@@ -1,4 +1,4 @@
-namespace LiteCharms.Features.MidrandBooks.Order.Models;
+namespace LiteCharms.Features.MidrandBooks.Orders.Models;
public class Refund
{
diff --git a/LiteCharms.Features.MidrandBooks/Order/Models/Shipping.cs b/LiteCharms.Features.MidrandBooks/Orders/Models/Shipping.cs
similarity index 61%
rename from LiteCharms.Features.MidrandBooks/Order/Models/Shipping.cs
rename to LiteCharms.Features.MidrandBooks/Orders/Models/Shipping.cs
index 975ab91..c43bc1f 100644
--- a/LiteCharms.Features.MidrandBooks/Order/Models/Shipping.cs
+++ b/LiteCharms.Features.MidrandBooks/Orders/Models/Shipping.cs
@@ -1,4 +1,4 @@
-namespace LiteCharms.Features.MidrandBooks.Order.Models;
+namespace LiteCharms.Features.MidrandBooks.Orders.Models;
public class Shipping
{
@@ -10,5 +10,9 @@ public class Shipping
public long OrderId { get; set; }
+ public long AddressId { get; set; }
+
+ public long ShippingProviderId { get; set; }
+
public ShippingStatuses Status { get; set; }
}
diff --git a/LiteCharms.Features.MidrandBooks/Order/Models/ShippingProvider.cs b/LiteCharms.Features.MidrandBooks/Orders/Models/ShippingProvider.cs
similarity index 84%
rename from LiteCharms.Features.MidrandBooks/Order/Models/ShippingProvider.cs
rename to LiteCharms.Features.MidrandBooks/Orders/Models/ShippingProvider.cs
index 8c129b4..76a396e 100644
--- a/LiteCharms.Features.MidrandBooks/Order/Models/ShippingProvider.cs
+++ b/LiteCharms.Features.MidrandBooks/Orders/Models/ShippingProvider.cs
@@ -1,4 +1,4 @@
-namespace LiteCharms.Features.MidrandBooks.Order.Models;
+namespace LiteCharms.Features.MidrandBooks.Orders.Models;
public class ShippingProvider
{
diff --git a/LiteCharms.Features.MidrandBooks/Orders/OrderService.cs b/LiteCharms.Features.MidrandBooks/Orders/OrderService.cs
new file mode 100644
index 0000000..9e37122
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks/Orders/OrderService.cs
@@ -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 contextFactory) : IService
+{
+ public async ValueTask> 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("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("Failed to create order.");
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(new Error(ex.Message).CausedBy(ex));
+ }
+ }
+
+ public async ValueTask> 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("Order not found.");
+
+ if(!await context.Books.AnyAsync(ab => ab.Id == request.AuthorBookId, cancellationToken))
+ return Result.Fail("Author book not found.");
+
+ if (!await context.Prices.AnyAsync(pp => pp.Id == request.ProductPriceId, cancellationToken))
+ return Result.Fail("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("Failed to add item to order.");
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(new Error(ex.Message).CausedBy(ex));
+ }
+ }
+
+ public async ValueTask 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));
+ }
+ }
+}
diff --git a/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs b/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs
index d786b93..901933e 100644
--- a/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs
+++ b/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs
@@ -1,6 +1,7 @@
using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
using LiteCharms.Features.MidrandBooks.Authors.Entities;
using LiteCharms.Features.MidrandBooks.Customers.Entities;
+using LiteCharms.Features.MidrandBooks.Orders.Entities;
using LiteCharms.Features.MidrandBooks.Pages.Entities;
using LiteCharms.Features.MidrandBooks.Products.Entities;
@@ -23,4 +24,14 @@ public class MidrandBooksDbContext(DbContextOptions optio
public DbSet Addresses => Set();
public DbSet Customers => Set();
+
+ public DbSet Orders => Set();
+
+ public DbSet OrderItems => Set();
+
+ public DbSet Refunds => Set();
+
+ public DbSet Shippings => Set();
+
+ public DbSet ShippingProviders => Set();
}