CRITICAL BUG FIX: Orders were totaling £0 because imported products had:
- Base price set to £0
- Variants stored as ProductMultiBuys (not ProductVariants)
- Order creation couldn't find variant prices → used base price of £0
Changes:
- CSV import now detects format: semicolons (;) = variants, colons (:) = multi-buys
- Added ImportProductVariantsAsync() to handle pipe-delimited variant format
- Format: "value; price; stock|value; price; stock" (e.g., "10; 30.00; 100|25; 70.00; 50")
- Updated CSV export to prioritize ProductVariants over MultiBuys
- Updated CSV template with correct variant format examples
- Added comprehensive documentation in VARIANT_CSV_FORMAT.md
Migration Required:
- Existing products with incorrect MultiBuy data need re-import
- Convert format from "name:qty:price;name:qty:price" to "value; price; stock|value; price; stock"
- Ensure base price is 0 for variant-based products
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Previous Approach (WRONG):**
- Created a separate table section to display ProductVariants
- User wanted data in the EXISTING collapsible panels, not a new section
**Proper Fix:**
- ProductImportService creates records in ProductVariants table
- Edit page's collapsible panels read from VariantsJson field (different system)
- Solution: Convert ProductVariants → VariantsJson format on page load
**Changes:**
1. **ProductsController.cs (Lines 105-115):**
- Load ProductVariants from database
- If VariantsJson empty but ProductVariants exist, convert them
- Format: `[{Weight: "28g", Price: 700, StockQty: 100}, ...]`
- JavaScript reads Price and StockQty to populate collapsible panel fields
2. **Edit.cshtml:**
- Removed the extra table section
- Existing collapsible panels now display imported data automatically
**Result:**
When you open Edit page, expand "Product Variants" → "Price, Stock & Weight Details",
the fields will be PRE-FILLED with your imported values (700, 100, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem:**
- Variant price overrides and stock quantities were hidden in collapsible
panels in the Product Edit page
- The Edit page was showing the VariantCollections system (VariantsJson)
instead of the actual ProductVariant records created by text import
- User had to expand each variant panel to see price and stock values
**Solution:**
1. **ProductsController.cs (Lines 101-103):**
- Added call to GetProductVariantsAsync() to load actual variant records
- Added ViewData["ProductVariants"] to pass data to view
2. **Edit.cshtml (Lines 320-413):**
- Added new collapsible section "Product Variants"
- Displays variants in a table with directly visible columns:
* Name, Type, Price, Stock Level, Sort Order, Status
- No hidden panels - all information visible at a glance
- Added quick summary with total variants, stock, and price range
- Includes helpful links to ProductVariants management page
**Technical Details:**
- Price displays in green with £ symbol when override exists
- Stock shows color-coded badges (green=in stock, red=out of stock)
- Section only appears if variants exist (conditional rendering)
- Expanded by default (aria-expanded="true") for immediate visibility
**Impact:**
- User can now see all variant prices and stock quantities immediately
- No need to click/expand individual variant panels
- Better UX for products imported via text import format
- Maintains separation between VariantCollections system and ProductVariants
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added detailed logging to ParseAndImportVariantLine to output:
- Variant name/value
- Price override
- Stock quantity
- Variant type
This will help diagnose why variant price and stock aren't showing up
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add replaceAll checkbox to ImportText view with warning
- Pass replaceAll parameter to import service
- Allows complete product catalog replacement via text import
- Enhanced documentation for unit types and variant collections
- Fix optimistic concurrency errors in product import by using ExecuteSqlRaw instead of EF tracking
- Add DeleteAllOrdersAndSalesDataAsync() method to clear orders, payments, customers, and messages
- Add DeleteAllSalesData endpoint to ProductsController for admin access
- Proper deletion order to avoid foreign key violations
- Enhanced logging for troubleshooting data cleanup operations
Resolves: Import errors with 'database operation expected to affect 1 row(s)'
- Added Replace All checkbox to import UI for clean slate imports
- Implemented DeleteAllProductsAndCategoriesAsync for complete data wipe
- Added auto-creation of categories during CSV import
- Created products_import.csv with 13 products across 4 categories
- Added comprehensive IMPORT_INSTRUCTIONS.md documentation
Technical changes:
- ProductImportService: Added replaceAll parameter to all import methods
- ProductImportService: Categories now auto-created if missing from CSV
- ProductsController: Added replaceAll parameter to Import action
- Import.cshtml: Added Replace All checkbox with danger warnings
Categories: Flour, Cereal, Vitamins, Herbal
Products: 13 products with full variant pricing structures
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Created NotificationHub for instant browser notifications
- Updated CryptoPaymentService to broadcast via SignalR
- Added JavaScript client with toast notifications
- Works with custom SSL certificates (no FCM dependency)
- Automatic reconnection with exponential backoff
- Notification sound and visual indicators
- Bypasses all Web Push SSL certificate issues
TeleBot Configuration:
- Added TeleBot API URL and API key to docker-compose.yml
- Configured to connect to telebot-service:5000 internally
- Enables customer notifications via Telegram bot
Expired Payment Handling:
- Auto-cancel orders when payment status is Expired
- Only cancels orders in PendingPayment status
- Logs cancellation for audit trail
Customer View Improvements:
- Hide cancelled orders from customer order lists
- Filters applied to both GetOrdersByIdentityAsync and GetOrdersByCustomerIdAsync
- Prevents confusion from displaying cancelled/expired orders
This resolves:
- No notifications to customers (TeleBot not configured)
- No notifications to admin (TeleBot connection failed)
- Expired orders remaining visible to customers
- Orders not auto-cancelled when payment expires
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Webhook Improvements:
- Added Confirmations field to PaymentWebhookDto (default: 0)
- Updated webhook controller to pass confirmations to service layer
- Fixed notification logic to match order update conditions
Payment Confirmation Logic:
- Paid (2): Confirmed immediately regardless of confirmations
- Overpaid (3): Confirmed immediately regardless of confirmations
- Completed (7): Requires 3+ blockchain confirmations
- Notifications only sent when order is actually updated
This prevents premature notifications for unconfirmed 'Completed' status
while maintaining immediate processing for 'Paid' and 'Overpaid' statuses.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added quick action button on Packing tab to dispatch orders
- Created dispatch modal with tracking number input
- Modal includes tracking number, estimated days, and notes fields
- Button appears on both desktop table and mobile card views
- Fixes workflow gap where Packing orders had no quick action
- Orders now properly flow: Accepted → Packing → Dispatched
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added PaymentStatus.Overpaid to notification trigger conditions
- Overpaid payments now update order status to PaymentReceived
- Overpaid payments now send admin push notifications
- Overpaid payments now send TeleBot customer notifications
- Resolves issue where successful overpayments were silent
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed Issues:
1. Removed "maskable" purpose from icons (was causing padding warnings)
2. Added screenshots with form_factor for richer install UI
- Desktop: form_factor: "wide"
- Mobile: form_factor: "narrow"
Changes:
- Icons now use "purpose: any" only (no maskable)
- Added screenshots array with wide/narrow form factors
- This enables richer PWA install prompts on supported browsers
All PWA manifest warnings should now be resolved.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problems Fixed:
1. Blank white screen on initial load (loading screen had display:none)
2. Only showed once (sessionStorage.blazorLoaded prevented repeat shows)
3. Fast connections meant users never saw it
Solution:
1. Removed display:none from HTML - screen visible immediately
2. Removed sessionStorage check - shows on every page load
3. Screen visible by default, hides when Blazor.start() completes
Behavior Now:
- Loading screen appears instantly (no blank white screen)
- Shows on every page load (full page refresh)
- Hides when SignalR connection established
- Works correctly with slow/throttled connections
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem:
- Loading screen was hiding immediately without waiting for Blazor
- Page detection logic was too restrictive (only /blazor paths)
- Most admin pages don't have Blazor components, so screen hid instantly
Solution:
- Blazor Server is loaded on ALL admin pages via _Layout.cshtml
- Removed restrictive path checking (was checking for /blazor or components)
- Now always calls Blazor.start() and waits for SignalR connection
- Loading screen properly shows while SignalR establishes connection
Expected behavior:
- First load: Screen shows → Blazor connects → Screen fades out
- Console: "Starting Blazor Server..." → "Started successfully" → "Hiding"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: Orders created with CustomerInfo had NULL IdentityReference
- CancelOrderAsync checked order.IdentityReference != identityReference
- NULL != "telegram:12345:username" → always returned false
- User saw "already processed" error even for pending orders
Fix implemented:
- Include Customer entity in CancelOrderAsync query
- Extract Telegram user ID from identity reference format
- Match against Customer.TelegramUserId for modern orders
- Fallback to IdentityReference matching for legacy orders
- Enhanced logging to debug ownership/status issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: Order queries were missing .ThenInclude(oi => oi.ProductVariant)
which caused ProductVariantName to be null in order DTOs even though
ProductVariantId was stored correctly.
Fixed queries:
- GetAllOrdersAsync (admin panel order list)
- GetOrdersByIdentityAsync (TeleBot order lookup)
- GetOrdersByCustomerIdAsync (customer order history)
- UpdateOrderStatusAsync (order status updates)
Now both TeleBot and admin panel will show which product variation
was selected in order details and order lists.
**Root Cause**: EF Core Include() was not properly materializing the Variants navigation
property despite correct SQL JOIN generation.
**Solution**: Load variants separately and manually group by ProductId for DTO mapping.
This bypasses EF Core's navigation property fixup issues.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
AsNoTracking() prevents EF Core from properly wiring up navigation properties.
Removing it allows Include() to populate Variants collection correctly.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem**: EF Core was not materializing Variants navigation property when using
.Select() projection directly in the query. The .Include() was being ignored.
**Solution**: Changed approach to:
1. Load entities with .Include() + .ToListAsync() first
2. Then project to DTO with in-memory .Select()
This ensures navigation properties are fully loaded before mapping to DTOs.
**Impact**: Variants will now properly appear in all product API responses.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude <noreply@anthropic.com>
Previous commit (e931f77) only fixed GetAllProductsAsync and GetProductByIdAsync.
This commit fixes GetProductsByCategoryAsync which also had the broken filtered Include syntax.
**Impact**: Variants will now appear when browsing products by category in TeleBot.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude <noreply@anthropic.com>
Critical bug where ProductVariants were never loaded from database.
**Problem:**
`.Include(p => p.Variants.Where(v => v.IsActive))` syntax was NOT
generating SQL JOIN statements in EF Core 9.0, causing all products
to return empty variants array even when variants exist in database.
**Solution:**
- Changed to simple `.Include(p => p.Variants)`
- Filtering still happens in DTO mapping (Select statement)
- Only IsActive variants are returned to API consumers
**Impact:**
- TeleBot can now display product variants with selection UI
- Variant pricing and stock levels now visible to customers
- Multi-variant products (e.g., Size/Color) now functional
**Test Case:**
Product 131cc3ad-07f4-4ec9-89ca-b05a0b4cfb41 has 7 variants:
- Size: Small, Medium, Large, XL
- Color: Black, White, Navy Blue
These will now appear in API responses and TeleBot UI.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude <noreply@anthropic.com>
Critical fix for production deployment issue where code changes were
deployed without corresponding database schema updates.
Changes:
- Add Price column to ProductVariants table (decimal 18,2, nullable)
- Add ProductVariantId column to OrderItems table (TEXT, nullable)
- Add index on OrderItems.ProductVariantId for query performance
This migration was manually applied to production on 2025-10-04 to
resolve "no such column: p2.Price" errors that broke the product
catalog API.
Future deployments must include database migration steps in CI/CD.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude <noreply@anthropic.com>
Added Price override input field to the JavaScript variant collection editor on the product Edit page.
**Changes:**
- Added Price input field (with £ symbol) in variant details section
- Updated serialization to save Price to VariantsJson
- Excluded Price from variant label generation
- Updated button text: "Price, Stock & Weight Details"
**Location:**
Product Edit > Variants Collection > Toggle Details > Price Override
Now variant prices can be set through BOTH methods:
1. Individual variant management (CreateVariant/EditVariant)
2. Bulk variant collection editor (product Edit page)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enables individual variants to have their own prices, overriding the base product price.
**Database Changes:**
- Added Price (decimal?, nullable) to ProductVariants table
- Added ProductVariantId to OrderItems table with foreign key relationship
- Created index on OrderItems.ProductVariantId for performance
**API Changes:**
- ProductVariantDto: Added Price field
- CreateProductVariantDto: Added Price field with validation
- UpdateProductVariantDto: Added Price field
- OrderItemDto: Added ProductVariantId and ProductVariantName
- CreateOrderItemDto: Added ProductVariantId
**Business Logic:**
- OrderService: Variant price overrides base price (but multi-buy takes precedence)
- ProductService: All variant CRUD operations support Price field
**Admin UI:**
- CreateVariant: Price input with £ symbol and base price placeholder
- EditVariant: Price editing with £ symbol
- ProductVariants list: Shows variant price or "(base)" indicator
**Client Library:**
- Updated all DTOs to match server-side changes
- Full support for variant pricing in order creation
**Migration:**
- EF Core migration: 20251003173458_AddVariantPricing
- Backward compatible: NULL values supported for existing data
**Use Case:**
Products with size/color variants can now have different prices:
- Small T-shirt: £15.00 (variant override)
- Medium T-shirt: £18.00 (uses base price)
- Large T-shirt: £20.00 (variant override)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Issue
Order creation failing with 400 BadRequest when using CustomerInfo (Telegram users).
Validator required IdentityReference to always be populated, but it's null when using CustomerInfo.
## Root Cause
CreateOrderDtoValidator.cs:10-12 enforced NotEmpty() on IdentityReference unconditionally.
TeleBot sends CustomerInfo for identified users, leaving IdentityReference null.
## Solution
Updated validator to accept EITHER IdentityReference OR CustomerInfo:
- New rule: At least one must be provided
- IdentityReference validation only applies when it's provided (.When() condition)
- Maintains backward compatibility with anonymous orders
## Impact
✅ Telegram bot orders can now be created successfully
✅ Anonymous orders still require IdentityReference
✅ Proper validation error messages for both scenarios
## Testing Required
- Create order via Telegram bot (with CustomerInfo)
- Create anonymous order (with IdentityReference)
- Verify both scenarios work correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed 404 error - the controller is named VariantCollectionsController,
not ProductVariantsController.
Changes:
- Updated desktop nav to use VariantCollections controller
- Updated mobile menu to use VariantCollections controller
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Re-added Variants menu item to both desktop and mobile navigation.
User needs access to ProductVariants management to create variant collections.
Changes:
- Desktop nav: Added Variants between Products and Orders
- Mobile drawer: Added Variants between Products and Shipping
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>