Add variant collections system and enhance ProductVariant with weight/stock tracking
This commit introduces a comprehensive variant management system and enhances the existing ProductVariant model with per-variant weight overrides and stock tracking, integrated across Admin Panel and TeleBot. Features Added: - Variant Collections: Reusable variant templates (e.g., "Standard Sizes") - Admin UI for managing variant collections (CRUD operations) - Dynamic variant editor with JavaScript-based UI - Per-variant weight and weight unit overrides - Per-variant stock level tracking - SalesLedger model for financial tracking ProductVariant Enhancements: - Added Weight (decimal, nullable) field for variant-specific weights - Added WeightUnit (enum, nullable) field for variant-specific units - Maintains backward compatibility with product-level weights TeleBot Integration: - Enhanced variant selection UI to display stock levels - Shows weight information with proper unit conversion (µg, g, oz, lb, ml, L) - Compact button format: "Medium (15 in stock, 350g)" - Real-time stock availability display Database Migrations: - 20250928014850_AddVariantCollectionsAndSalesLedger - 20250928155814_AddWeightToProductVariants Technical Changes: - Updated Product model to support VariantCollectionId and VariantsJson - Extended ProductService with variant collection operations - Enhanced OrderService to handle variant-specific pricing and weights - Updated LittleShop.Client DTOs to match server models - Added JavaScript dynamic variant form builder Files Modified: 15 Files Added: 17 Lines Changed: ~2000 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -862,8 +862,8 @@ namespace LittleShop.Migrations
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SelectedVariant")
|
||||
.HasMaxLength(100)
|
||||
b.Property<string>("SelectedVariants")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal>("TotalPrice")
|
||||
@@ -916,6 +916,12 @@ namespace LittleShop.Migrations
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("VariantCollectionId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("VariantsJson")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal>("Weight")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
@@ -926,6 +932,8 @@ namespace LittleShop.Migrations
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.HasIndex("VariantCollectionId");
|
||||
|
||||
b.ToTable("Products");
|
||||
});
|
||||
|
||||
@@ -1050,6 +1058,12 @@ namespace LittleShop.Migrations
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal?>("Weight")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("WeightUnit")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IsActive");
|
||||
@@ -1190,6 +1204,57 @@ namespace LittleShop.Migrations
|
||||
b.ToTable("Reviews");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LittleShop.Models.SalesLedger", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Cryptocurrency")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FiatCurrency")
|
||||
.IsRequired()
|
||||
.HasMaxLength(3)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("OrderId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ProductId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProductName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal?>("SalePriceBTC")
|
||||
.HasColumnType("decimal(18,8)");
|
||||
|
||||
b.Property<decimal>("SalePriceFiat")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("SoldAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OrderId");
|
||||
|
||||
b.HasIndex("ProductId");
|
||||
|
||||
b.HasIndex("SoldAt");
|
||||
|
||||
b.HasIndex("ProductId", "SoldAt");
|
||||
|
||||
b.ToTable("SalesLedgers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LittleShop.Models.ShippingRate", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -1303,6 +1368,39 @@ namespace LittleShop.Migrations
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LittleShop.Models.VariantCollection", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PropertiesJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IsActive");
|
||||
|
||||
b.HasIndex("Name");
|
||||
|
||||
b.ToTable("VariantCollections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LittleShop.Models.BotActivity", b =>
|
||||
{
|
||||
b.HasOne("LittleShop.Models.Bot", "Bot")
|
||||
@@ -1454,7 +1552,13 @@ namespace LittleShop.Migrations
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("LittleShop.Models.VariantCollection", "VariantCollection")
|
||||
.WithMany()
|
||||
.HasForeignKey("VariantCollectionId");
|
||||
|
||||
b.Navigation("Category");
|
||||
|
||||
b.Navigation("VariantCollection");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LittleShop.Models.ProductMultiBuy", b =>
|
||||
@@ -1541,6 +1645,25 @@ namespace LittleShop.Migrations
|
||||
b.Navigation("Product");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LittleShop.Models.SalesLedger", b =>
|
||||
{
|
||||
b.HasOne("LittleShop.Models.Order", "Order")
|
||||
.WithMany()
|
||||
.HasForeignKey("OrderId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("LittleShop.Models.Product", "Product")
|
||||
.WithMany("SalesLedgers")
|
||||
.HasForeignKey("ProductId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Order");
|
||||
|
||||
b.Navigation("Product");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LittleShop.Models.Bot", b =>
|
||||
{
|
||||
b.Navigation("Metrics");
|
||||
@@ -1584,6 +1707,8 @@ namespace LittleShop.Migrations
|
||||
|
||||
b.Navigation("Reviews");
|
||||
|
||||
b.Navigation("SalesLedgers");
|
||||
|
||||
b.Navigation("Variants");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user