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:
@@ -30,7 +30,7 @@ public class OrderService : IOrderService
|
||||
.Include(o => o.Items)
|
||||
.ThenInclude(oi => oi.Product)
|
||||
.Include(o => o.Items)
|
||||
.ThenInclude(oi => oi.ProductVariation)
|
||||
.ThenInclude(oi => oi.ProductMultiBuy)
|
||||
.Include(o => o.Payments)
|
||||
.OrderByDescending(o => o.CreatedAt)
|
||||
.ToListAsync();
|
||||
@@ -45,7 +45,7 @@ public class OrderService : IOrderService
|
||||
.Include(o => o.Items)
|
||||
.ThenInclude(oi => oi.Product)
|
||||
.Include(o => o.Items)
|
||||
.ThenInclude(oi => oi.ProductVariation)
|
||||
.ThenInclude(oi => oi.ProductMultiBuy)
|
||||
.Include(o => o.Payments)
|
||||
.Where(o => o.IdentityReference == identityReference)
|
||||
.OrderByDescending(o => o.CreatedAt)
|
||||
@@ -61,7 +61,7 @@ public class OrderService : IOrderService
|
||||
.Include(o => o.Items)
|
||||
.ThenInclude(oi => oi.Product)
|
||||
.Include(o => o.Items)
|
||||
.ThenInclude(oi => oi.ProductVariation)
|
||||
.ThenInclude(oi => oi.ProductMultiBuy)
|
||||
.Include(o => o.Payments)
|
||||
.Where(o => o.CustomerId == customerId)
|
||||
.OrderByDescending(o => o.CreatedAt)
|
||||
@@ -77,7 +77,7 @@ public class OrderService : IOrderService
|
||||
.Include(o => o.Items)
|
||||
.ThenInclude(oi => oi.Product)
|
||||
.Include(o => o.Items)
|
||||
.ThenInclude(oi => oi.ProductVariation)
|
||||
.ThenInclude(oi => oi.ProductMultiBuy)
|
||||
.Include(o => o.Payments)
|
||||
.FirstOrDefaultAsync(o => o.Id == id);
|
||||
|
||||
@@ -146,20 +146,20 @@ public class OrderService : IOrderService
|
||||
throw new ArgumentException($"Product {itemDto.ProductId} not found or inactive");
|
||||
}
|
||||
|
||||
ProductVariation? variation = null;
|
||||
ProductMultiBuy? multiBuy = null;
|
||||
decimal unitPrice = product.Price;
|
||||
|
||||
if (itemDto.ProductVariationId.HasValue)
|
||||
if (itemDto.ProductMultiBuyId.HasValue)
|
||||
{
|
||||
variation = await _context.ProductVariations.FindAsync(itemDto.ProductVariationId.Value);
|
||||
if (variation == null || !variation.IsActive || variation.ProductId != itemDto.ProductId)
|
||||
multiBuy = await _context.ProductMultiBuys.FindAsync(itemDto.ProductMultiBuyId.Value);
|
||||
if (multiBuy == null || !multiBuy.IsActive || multiBuy.ProductId != itemDto.ProductId)
|
||||
{
|
||||
throw new ArgumentException($"Product variation {itemDto.ProductVariationId} not found, inactive, or doesn't belong to product {itemDto.ProductId}");
|
||||
throw new ArgumentException($"Product multi-buy {itemDto.ProductMultiBuyId} not found, inactive, or doesn't belong to product {itemDto.ProductId}");
|
||||
}
|
||||
|
||||
// When using a variation, the quantity represents how many of that variation bundle
|
||||
// For example: buying 2 of the "3 for £25" variation means 6 total items for £50
|
||||
unitPrice = variation.Price;
|
||||
// When using a multi-buy, the quantity represents how many of that multi-buy bundle
|
||||
// For example: buying 2 of the "3 for £25" multi-buy means 6 total items for £50
|
||||
unitPrice = multiBuy.Price;
|
||||
}
|
||||
|
||||
var orderItem = new OrderItem
|
||||
@@ -167,7 +167,7 @@ public class OrderService : IOrderService
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = order.Id,
|
||||
ProductId = itemDto.ProductId,
|
||||
ProductVariationId = itemDto.ProductVariationId,
|
||||
ProductMultiBuyId = itemDto.ProductMultiBuyId,
|
||||
Quantity = itemDto.Quantity,
|
||||
UnitPrice = unitPrice,
|
||||
TotalPrice = unitPrice * itemDto.Quantity
|
||||
@@ -321,9 +321,9 @@ public class OrderService : IOrderService
|
||||
{
|
||||
Id = oi.Id,
|
||||
ProductId = oi.ProductId,
|
||||
ProductVariationId = oi.ProductVariationId,
|
||||
ProductMultiBuyId = oi.ProductMultiBuyId,
|
||||
ProductName = oi.Product.Name,
|
||||
ProductVariationName = oi.ProductVariation?.Name,
|
||||
ProductMultiBuyName = oi.ProductMultiBuy?.Name,
|
||||
Quantity = oi.Quantity,
|
||||
UnitPrice = oi.UnitPrice,
|
||||
TotalPrice = oi.TotalPrice
|
||||
@@ -500,7 +500,7 @@ public class OrderService : IOrderService
|
||||
.Include(o => o.Items)
|
||||
.ThenInclude(oi => oi.Product)
|
||||
.Include(o => o.Items)
|
||||
.ThenInclude(oi => oi.ProductVariation)
|
||||
.ThenInclude(oi => oi.ProductMultiBuy)
|
||||
.Include(o => o.Payments)
|
||||
.Where(o => o.Status == status)
|
||||
.OrderByDescending(o => o.CreatedAt)
|
||||
|
||||
Reference in New Issue
Block a user