Implement complete e-commerce functionality with shipping and order management
Features Added: - Standard e-commerce properties (Price, Weight, shipping fields) - Order management with Create/Edit views and shipping information - ShippingRates system for weight-based shipping calculations - Comprehensive test coverage with JWT authentication tests - Sample data seeder with 5 orders demonstrating full workflow - Photo upload functionality for products - Multi-cryptocurrency payment support (BTC, XMR, USDT, etc.) Database Changes: - Added ShippingRates table - Added shipping fields to Orders (Name, Address, City, PostCode, Country) - Renamed properties to standard names (BasePrice to Price, ProductWeight to Weight) - Added UpdatedAt timestamps to models UI Improvements: - Added Create/Edit views for Orders - Added ShippingRates management UI - Updated navigation menu with Shipping option - Enhanced Order Details view with shipping information Sample Data: - 3 Categories (Electronics, Clothing, Books) - 5 Products with various prices - 5 Shipping rates (Royal Mail options) - 5 Orders in different statuses (Pending to Delivered) - 3 Crypto payments demonstrating payment flow Security: - All API endpoints secured with JWT authentication - No public endpoints - client apps must authenticate - Privacy-focused design with minimal data collection Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
486
LittleShop/Services/DataSeederService.cs
Normal file
486
LittleShop/Services/DataSeederService.cs
Normal file
@@ -0,0 +1,486 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using LittleShop.Data;
|
||||
using LittleShop.Models;
|
||||
using LittleShop.Enums;
|
||||
|
||||
namespace LittleShop.Services;
|
||||
|
||||
public interface IDataSeederService
|
||||
{
|
||||
Task SeedSampleDataAsync();
|
||||
}
|
||||
|
||||
public class DataSeederService : IDataSeederService
|
||||
{
|
||||
private readonly LittleShopContext _context;
|
||||
private readonly ILogger<DataSeederService> _logger;
|
||||
|
||||
public DataSeederService(LittleShopContext context, ILogger<DataSeederService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task SeedSampleDataAsync()
|
||||
{
|
||||
// Check if we already have data
|
||||
var hasCategories = await _context.Categories.AnyAsync();
|
||||
if (hasCategories)
|
||||
{
|
||||
_logger.LogInformation("Sample data already exists, skipping seed");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Seeding sample data...");
|
||||
|
||||
// Create Categories
|
||||
var categories = new List<Category>
|
||||
{
|
||||
new Category
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Electronics",
|
||||
Description = "Electronic devices and accessories",
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
new Category
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Clothing",
|
||||
Description = "Apparel and fashion items",
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
new Category
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Books",
|
||||
Description = "Physical and digital books",
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
}
|
||||
};
|
||||
|
||||
_context.Categories.AddRange(categories);
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("Created {Count} categories", categories.Count);
|
||||
|
||||
// Create Products
|
||||
var products = new List<Product>
|
||||
{
|
||||
new Product
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Wireless Headphones",
|
||||
Description = "High-quality Bluetooth headphones with noise cancellation",
|
||||
Price = 89.99m,
|
||||
Weight = 250,
|
||||
WeightUnit = ProductWeightUnit.Grams,
|
||||
CategoryId = categories[0].Id,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
new Product
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Smartphone Case",
|
||||
Description = "Durable protective case for latest smartphones",
|
||||
Price = 19.99m,
|
||||
Weight = 50,
|
||||
WeightUnit = ProductWeightUnit.Grams,
|
||||
CategoryId = categories[0].Id,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
new Product
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "T-Shirt",
|
||||
Description = "100% cotton comfortable t-shirt",
|
||||
Price = 24.99m,
|
||||
Weight = 200,
|
||||
WeightUnit = ProductWeightUnit.Grams,
|
||||
CategoryId = categories[1].Id,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
new Product
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Jeans",
|
||||
Description = "Classic denim jeans",
|
||||
Price = 59.99m,
|
||||
Weight = 500,
|
||||
WeightUnit = ProductWeightUnit.Grams,
|
||||
CategoryId = categories[1].Id,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
new Product
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Programming Book",
|
||||
Description = "Learn programming with practical examples",
|
||||
Price = 34.99m,
|
||||
Weight = 800,
|
||||
WeightUnit = ProductWeightUnit.Grams,
|
||||
CategoryId = categories[2].Id,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
}
|
||||
};
|
||||
|
||||
_context.Products.AddRange(products);
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("Created {Count} products", products.Count);
|
||||
|
||||
// Create Shipping Rates
|
||||
var shippingRates = new List<ShippingRate>
|
||||
{
|
||||
new ShippingRate
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Royal Mail First Class",
|
||||
Description = "Next working day delivery",
|
||||
Country = "United Kingdom",
|
||||
MinWeight = 0,
|
||||
MaxWeight = 100,
|
||||
Price = 2.99m,
|
||||
MinDeliveryDays = 1,
|
||||
MaxDeliveryDays = 2,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
new ShippingRate
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Royal Mail Second Class",
|
||||
Description = "2-3 working days delivery",
|
||||
Country = "United Kingdom",
|
||||
MinWeight = 0,
|
||||
MaxWeight = 100,
|
||||
Price = 1.99m,
|
||||
MinDeliveryDays = 2,
|
||||
MaxDeliveryDays = 3,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
new ShippingRate
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Royal Mail Small Parcel",
|
||||
Description = "For items up to 2kg",
|
||||
Country = "United Kingdom",
|
||||
MinWeight = 100,
|
||||
MaxWeight = 2000,
|
||||
Price = 4.99m,
|
||||
MinDeliveryDays = 1,
|
||||
MaxDeliveryDays = 3,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
new ShippingRate
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Royal Mail Medium Parcel",
|
||||
Description = "For items 2kg to 10kg",
|
||||
Country = "United Kingdom",
|
||||
MinWeight = 2000,
|
||||
MaxWeight = 10000,
|
||||
Price = 8.99m,
|
||||
MinDeliveryDays = 1,
|
||||
MaxDeliveryDays = 3,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
},
|
||||
new ShippingRate
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Express Delivery",
|
||||
Description = "Guaranteed next day delivery",
|
||||
Country = "United Kingdom",
|
||||
MinWeight = 0,
|
||||
MaxWeight = 30000,
|
||||
Price = 14.99m,
|
||||
MinDeliveryDays = 1,
|
||||
MaxDeliveryDays = 1,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
}
|
||||
};
|
||||
|
||||
_context.ShippingRates.AddRange(shippingRates);
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("Created {Count} shipping rates", shippingRates.Count);
|
||||
|
||||
// Create Sample Orders with different statuses
|
||||
var orders = new List<Order>
|
||||
{
|
||||
// Order 1: Pending Payment
|
||||
new Order
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
IdentityReference = "CUST001",
|
||||
Status = OrderStatus.PendingPayment,
|
||||
TotalAmount = 109.98m,
|
||||
Currency = "GBP",
|
||||
ShippingName = "John Smith",
|
||||
ShippingAddress = "123 High Street",
|
||||
ShippingCity = "London",
|
||||
ShippingPostCode = "SW1A 1AA",
|
||||
ShippingCountry = "United Kingdom",
|
||||
Notes = "Please leave with neighbor if not home",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-5),
|
||||
UpdatedAt = DateTime.UtcNow.AddDays(-5)
|
||||
},
|
||||
// Order 2: Payment Received
|
||||
new Order
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
IdentityReference = "CUST002",
|
||||
Status = OrderStatus.PaymentReceived,
|
||||
TotalAmount = 44.98m,
|
||||
Currency = "GBP",
|
||||
ShippingName = "Sarah Johnson",
|
||||
ShippingAddress = "456 Oak Avenue",
|
||||
ShippingCity = "Manchester",
|
||||
ShippingPostCode = "M1 2AB",
|
||||
ShippingCountry = "United Kingdom",
|
||||
Notes = null,
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-3),
|
||||
UpdatedAt = DateTime.UtcNow.AddDays(-2),
|
||||
PaidAt = DateTime.UtcNow.AddDays(-2)
|
||||
},
|
||||
// Order 3: Processing
|
||||
new Order
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
IdentityReference = "CUST003",
|
||||
Status = OrderStatus.Processing,
|
||||
TotalAmount = 84.98m,
|
||||
Currency = "GBP",
|
||||
ShippingName = "Michael Brown",
|
||||
ShippingAddress = "789 Park Lane",
|
||||
ShippingCity = "Birmingham",
|
||||
ShippingPostCode = "B1 1AA",
|
||||
ShippingCountry = "United Kingdom",
|
||||
Notes = "Gift wrapping requested",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-4),
|
||||
UpdatedAt = DateTime.UtcNow.AddDays(-1),
|
||||
PaidAt = DateTime.UtcNow.AddDays(-3)
|
||||
},
|
||||
// Order 4: Shipped
|
||||
new Order
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
IdentityReference = "CUST004",
|
||||
Status = OrderStatus.Shipped,
|
||||
TotalAmount = 79.98m,
|
||||
Currency = "GBP",
|
||||
ShippingName = "Emma Wilson",
|
||||
ShippingAddress = "321 Queen Street",
|
||||
ShippingCity = "Liverpool",
|
||||
ShippingPostCode = "L1 1AA",
|
||||
ShippingCountry = "United Kingdom",
|
||||
Notes = "Express delivery",
|
||||
TrackingNumber = "RM123456789GB",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-7),
|
||||
UpdatedAt = DateTime.UtcNow.AddHours(-12),
|
||||
PaidAt = DateTime.UtcNow.AddDays(-6),
|
||||
ShippedAt = DateTime.UtcNow.AddHours(-12)
|
||||
},
|
||||
// Order 5: Delivered
|
||||
new Order
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
IdentityReference = "CUST005",
|
||||
Status = OrderStatus.Delivered,
|
||||
TotalAmount = 34.99m,
|
||||
Currency = "GBP",
|
||||
ShippingName = "David Taylor",
|
||||
ShippingAddress = "555 Castle Road",
|
||||
ShippingCity = "Edinburgh",
|
||||
ShippingPostCode = "EH1 1AA",
|
||||
ShippingCountry = "United Kingdom",
|
||||
Notes = null,
|
||||
TrackingNumber = "RM987654321GB",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-10),
|
||||
UpdatedAt = DateTime.UtcNow.AddDays(-2),
|
||||
PaidAt = DateTime.UtcNow.AddDays(-9),
|
||||
ShippedAt = DateTime.UtcNow.AddDays(-7)
|
||||
}
|
||||
};
|
||||
|
||||
_context.Orders.AddRange(orders);
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("Created {Count} orders", orders.Count);
|
||||
|
||||
// Create Order Items
|
||||
var orderItems = new List<OrderItem>
|
||||
{
|
||||
// Order 1 items
|
||||
new OrderItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[0].Id,
|
||||
ProductId = products[0].Id, // Wireless Headphones
|
||||
Quantity = 1,
|
||||
UnitPrice = 89.99m,
|
||||
TotalPrice = 89.99m
|
||||
},
|
||||
new OrderItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[0].Id,
|
||||
ProductId = products[1].Id, // Smartphone Case
|
||||
Quantity = 1,
|
||||
UnitPrice = 19.99m,
|
||||
TotalPrice = 19.99m
|
||||
},
|
||||
// Order 2 items
|
||||
new OrderItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[1].Id,
|
||||
ProductId = products[2].Id, // T-Shirt
|
||||
Quantity = 1,
|
||||
UnitPrice = 24.99m,
|
||||
TotalPrice = 24.99m
|
||||
},
|
||||
new OrderItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[1].Id,
|
||||
ProductId = products[1].Id, // Smartphone Case
|
||||
Quantity = 1,
|
||||
UnitPrice = 19.99m,
|
||||
TotalPrice = 19.99m
|
||||
},
|
||||
// Order 3 items
|
||||
new OrderItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[2].Id,
|
||||
ProductId = products[2].Id, // T-Shirt
|
||||
Quantity = 2,
|
||||
UnitPrice = 24.99m,
|
||||
TotalPrice = 49.98m
|
||||
},
|
||||
new OrderItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[2].Id,
|
||||
ProductId = products[4].Id, // Programming Book
|
||||
Quantity = 1,
|
||||
UnitPrice = 34.99m,
|
||||
TotalPrice = 34.99m
|
||||
},
|
||||
// Order 4 items
|
||||
new OrderItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[3].Id,
|
||||
ProductId = products[3].Id, // Jeans
|
||||
Quantity = 1,
|
||||
UnitPrice = 59.99m,
|
||||
TotalPrice = 59.99m
|
||||
},
|
||||
new OrderItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[3].Id,
|
||||
ProductId = products[1].Id, // Smartphone Case
|
||||
Quantity = 1,
|
||||
UnitPrice = 19.99m,
|
||||
TotalPrice = 19.99m
|
||||
},
|
||||
// Order 5 items
|
||||
new OrderItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[4].Id,
|
||||
ProductId = products[4].Id, // Programming Book
|
||||
Quantity = 1,
|
||||
UnitPrice = 34.99m,
|
||||
TotalPrice = 34.99m
|
||||
}
|
||||
};
|
||||
|
||||
_context.OrderItems.AddRange(orderItems);
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("Created {Count} order items", orderItems.Count);
|
||||
|
||||
// Create sample crypto payments for some orders
|
||||
var payments = new List<CryptoPayment>
|
||||
{
|
||||
// Payment for Order 2 (Paid)
|
||||
new CryptoPayment
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[1].Id,
|
||||
Currency = CryptoCurrency.BTC,
|
||||
WalletAddress = "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
|
||||
RequiredAmount = 0.00089m,
|
||||
PaidAmount = 0.00089m,
|
||||
Status = PaymentStatus.Paid,
|
||||
BTCPayInvoiceId = "INV001",
|
||||
TransactionHash = "3a1b9e330afbe003e0f8c7d0e3c3f7e3a1b9e330afbe003e0",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-2).AddHours(-1),
|
||||
PaidAt = DateTime.UtcNow.AddDays(-2),
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(-1)
|
||||
},
|
||||
// Payment for Order 3 (Paid)
|
||||
new CryptoPayment
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[2].Id,
|
||||
Currency = CryptoCurrency.XMR,
|
||||
WalletAddress = "4AdUndXHHZ6cfufTMvppY6JwXNb9b1LoaGain57XbP",
|
||||
RequiredAmount = 0.45m,
|
||||
PaidAmount = 0.45m,
|
||||
Status = PaymentStatus.Paid,
|
||||
BTCPayInvoiceId = "INV002",
|
||||
TransactionHash = "7c4b5e440bfce113f1f9c8d1f4e4f8e7c4b5e440bfce113f1",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-3).AddHours(-2),
|
||||
PaidAt = DateTime.UtcNow.AddDays(-3),
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(-2)
|
||||
},
|
||||
// Payment for Order 4 (Paid)
|
||||
new CryptoPayment
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orders[3].Id,
|
||||
Currency = CryptoCurrency.USDT,
|
||||
WalletAddress = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7",
|
||||
RequiredAmount = 79.98m,
|
||||
PaidAmount = 79.98m,
|
||||
Status = PaymentStatus.Paid,
|
||||
BTCPayInvoiceId = "INV003",
|
||||
TransactionHash = "0x9f2e5b550afe223c5e1f9c9d2f5e5f9f2e5b550afe223c5e1",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-6).AddHours(-1),
|
||||
PaidAt = DateTime.UtcNow.AddDays(-6),
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(-5)
|
||||
}
|
||||
};
|
||||
|
||||
_context.CryptoPayments.AddRange(payments);
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("Created {Count} crypto payments", payments.Count);
|
||||
|
||||
_logger.LogInformation("Sample data seeding completed successfully!");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user