Implement product multi-buys and variants system
Major restructuring of product variations: - Renamed ProductVariation to ProductMultiBuy for quantity-based pricing (e.g., "3 for £25") - Added new ProductVariant model for string-based options (colors, flavors) - Complete separation of multi-buy pricing from variant selection Features implemented: - Multi-buy deals with automatic price-per-unit calculation - Product variants for colors/flavors/sizes with stock tracking - TeleBot checkout supports both multi-buys and variant selection - Shopping cart correctly calculates multi-buy bundle prices - Order system tracks selected variants and multi-buy choices - Real-time bot activity monitoring with SignalR - Public bot directory page with QR codes for Telegram launch - Admin dashboard shows multi-buy and variant metrics Technical changes: - Updated all DTOs, services, and controllers - Fixed cart total calculation for multi-buy bundles - Comprehensive test coverage for new functionality - All existing tests passing with new features Database changes: - Migrated ProductVariations to ProductMultiBuys - Added ProductVariants table - Updated OrderItems to track variants 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,9 @@ public class LittleShopContext : DbContext
|
||||
public DbSet<Category> Categories { get; set; }
|
||||
public DbSet<Product> Products { get; set; }
|
||||
public DbSet<ProductPhoto> ProductPhotos { get; set; }
|
||||
public DbSet<ProductVariation> ProductVariations { get; set; }
|
||||
public DbSet<ProductMultiBuy> ProductMultiBuys { get; set; }
|
||||
public DbSet<ProductVariant> ProductVariants { get; set; }
|
||||
public DbSet<BotActivity> BotActivities { get; set; }
|
||||
public DbSet<Order> Orders { get; set; }
|
||||
public DbSet<OrderItem> OrderItems { get; set; }
|
||||
public DbSet<CryptoPayment> CryptoPayments { get; set; }
|
||||
@@ -54,30 +56,72 @@ public class LittleShopContext : DbContext
|
||||
.HasForeignKey(pp => pp.ProductId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasMany(p => p.Variations)
|
||||
entity.HasMany(p => p.MultiBuys)
|
||||
.WithOne(pmb => pmb.Product)
|
||||
.HasForeignKey(pmb => pmb.ProductId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasMany(p => p.Variants)
|
||||
.WithOne(pv => pv.Product)
|
||||
.HasForeignKey(pv => pv.ProductId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasMany(p => p.Activities)
|
||||
.WithOne(ba => ba.Product)
|
||||
.HasForeignKey(ba => ba.ProductId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
entity.HasMany(p => p.OrderItems)
|
||||
.WithOne(oi => oi.Product)
|
||||
.HasForeignKey(oi => oi.ProductId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
// ProductVariation entity
|
||||
modelBuilder.Entity<ProductVariation>(entity =>
|
||||
// ProductMultiBuy entity
|
||||
modelBuilder.Entity<ProductMultiBuy>(entity =>
|
||||
{
|
||||
entity.HasIndex(e => new { e.ProductId, e.Quantity }).IsUnique(); // One variation per quantity per product
|
||||
entity.HasIndex(e => new { e.ProductId, e.Quantity }).IsUnique(); // One multi-buy per quantity per product
|
||||
entity.HasIndex(e => new { e.ProductId, e.SortOrder });
|
||||
entity.HasIndex(e => e.IsActive);
|
||||
|
||||
entity.HasMany(pv => pv.OrderItems)
|
||||
.WithOne(oi => oi.ProductVariation)
|
||||
.HasForeignKey(oi => oi.ProductVariationId)
|
||||
entity.HasMany(pmb => pmb.OrderItems)
|
||||
.WithOne(oi => oi.ProductMultiBuy)
|
||||
.HasForeignKey(oi => oi.ProductMultiBuyId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
// ProductVariant entity
|
||||
modelBuilder.Entity<ProductVariant>(entity =>
|
||||
{
|
||||
entity.HasIndex(e => new { e.ProductId, e.Name }).IsUnique(); // Unique variant names per product
|
||||
entity.HasIndex(e => new { e.ProductId, e.SortOrder });
|
||||
entity.HasIndex(e => e.IsActive);
|
||||
});
|
||||
|
||||
// BotActivity entity
|
||||
modelBuilder.Entity<BotActivity>(entity =>
|
||||
{
|
||||
entity.HasOne(ba => ba.Bot)
|
||||
.WithMany()
|
||||
.HasForeignKey(ba => ba.BotId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne(ba => ba.Product)
|
||||
.WithMany(p => p.Activities)
|
||||
.HasForeignKey(ba => ba.ProductId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
entity.HasOne(ba => ba.Order)
|
||||
.WithMany()
|
||||
.HasForeignKey(ba => ba.OrderId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
entity.HasIndex(e => new { e.BotId, e.Timestamp });
|
||||
entity.HasIndex(e => e.SessionIdentifier);
|
||||
entity.HasIndex(e => e.ActivityType);
|
||||
entity.HasIndex(e => e.Timestamp);
|
||||
});
|
||||
|
||||
// Order entity
|
||||
modelBuilder.Entity<Order>(entity =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user