Fix: Import concurrency errors + Add sales data cleanup
- 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)'
This commit is contained in:
parent
6c8106ff90
commit
86e30d7203
@ -306,4 +306,21 @@ public class ProductsController : Controller
|
|||||||
}
|
}
|
||||||
return Json(collection);
|
return Json(collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> DeleteAllSalesData()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var deletedCount = await _importService.DeleteAllOrdersAndSalesDataAsync();
|
||||||
|
TempData["SuccessMessage"] = $"✅ Successfully deleted {deletedCount} sales records (orders, payments, customers, messages)";
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
TempData["ErrorMessage"] = $"Failed to delete sales data: {ex.Message}";
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -14,6 +14,7 @@ public interface IProductImportService
|
|||||||
Task<ProductImportResultDto> ImportFromTextAsync(string csvText, bool replaceAll = false);
|
Task<ProductImportResultDto> ImportFromTextAsync(string csvText, bool replaceAll = false);
|
||||||
Task<ProductImportResultDto> ImportFromHumanTextAsync(string textContent, bool replaceAll = false);
|
Task<ProductImportResultDto> ImportFromHumanTextAsync(string textContent, bool replaceAll = false);
|
||||||
Task<int> DeleteAllProductsAndCategoriesAsync();
|
Task<int> DeleteAllProductsAndCategoriesAsync();
|
||||||
|
Task<int> DeleteAllOrdersAndSalesDataAsync();
|
||||||
string GenerateTemplateAsCsv();
|
string GenerateTemplateAsCsv();
|
||||||
string GenerateTemplateAsHumanText();
|
string GenerateTemplateAsHumanText();
|
||||||
Task<string> ExportProductsAsCsvAsync();
|
Task<string> ExportProductsAsCsvAsync();
|
||||||
@ -731,33 +732,113 @@ stock: 75
|
|||||||
{
|
{
|
||||||
var deletedCount = 0;
|
var deletedCount = 0;
|
||||||
|
|
||||||
// Delete all product photos
|
try
|
||||||
var photos = await _context.ProductPhotos.ToListAsync();
|
{
|
||||||
_context.ProductPhotos.RemoveRange(photos);
|
// Use ExecuteSqlRaw to avoid EF tracking issues and optimistic concurrency errors
|
||||||
deletedCount += photos.Count;
|
// Order matters: delete child records first, then parents
|
||||||
|
|
||||||
// Delete all product variants
|
// Delete order items (references products)
|
||||||
var variants = await _context.ProductVariants.ToListAsync();
|
var orderItemsDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM OrderItems");
|
||||||
_context.ProductVariants.RemoveRange(variants);
|
deletedCount += orderItemsDeleted;
|
||||||
deletedCount += variants.Count;
|
_logger.LogInformation("Deleted {Count} order items", orderItemsDeleted);
|
||||||
|
|
||||||
// Delete all product multi-buys
|
// Delete product photos
|
||||||
var multiBuys = await _context.ProductMultiBuys.ToListAsync();
|
var photosDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM ProductPhotos");
|
||||||
_context.ProductMultiBuys.RemoveRange(multiBuys);
|
deletedCount += photosDeleted;
|
||||||
deletedCount += multiBuys.Count;
|
_logger.LogInformation("Deleted {Count} product photos", photosDeleted);
|
||||||
|
|
||||||
// Delete all products
|
// Delete product variants
|
||||||
var products = await _context.Products.ToListAsync();
|
var variantsDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM ProductVariants");
|
||||||
_context.Products.RemoveRange(products);
|
deletedCount += variantsDeleted;
|
||||||
deletedCount += products.Count;
|
_logger.LogInformation("Deleted {Count} product variants", variantsDeleted);
|
||||||
|
|
||||||
// Delete all categories
|
// Delete product multi-buys
|
||||||
var categories = await _context.Categories.ToListAsync();
|
var multiBuysDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM ProductMultiBuys");
|
||||||
_context.Categories.RemoveRange(categories);
|
deletedCount += multiBuysDeleted;
|
||||||
deletedCount += categories.Count;
|
_logger.LogInformation("Deleted {Count} product multi-buys", multiBuysDeleted);
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
// Delete products
|
||||||
|
var productsDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM Products");
|
||||||
|
deletedCount += productsDeleted;
|
||||||
|
_logger.LogInformation("Deleted {Count} products", productsDeleted);
|
||||||
|
|
||||||
return deletedCount;
|
// Delete categories
|
||||||
|
var categoriesDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM Categories");
|
||||||
|
deletedCount += categoriesDeleted;
|
||||||
|
_logger.LogInformation("Deleted {Count} categories", categoriesDeleted);
|
||||||
|
|
||||||
|
_logger.LogInformation("Successfully deleted {TotalCount} total records", deletedCount);
|
||||||
|
return deletedCount;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to delete products and categories");
|
||||||
|
throw new InvalidOperationException($"Failed to delete data: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> DeleteAllOrdersAndSalesDataAsync()
|
||||||
|
{
|
||||||
|
var deletedCount = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogWarning("DELETING ALL ORDERS AND SALES DATA - This action is irreversible");
|
||||||
|
|
||||||
|
// Delete in proper order (child tables first)
|
||||||
|
|
||||||
|
// Delete customer messages (references customers and orders)
|
||||||
|
var messagesDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM CustomerMessages");
|
||||||
|
deletedCount += messagesDeleted;
|
||||||
|
_logger.LogInformation("Deleted {Count} customer messages", messagesDeleted);
|
||||||
|
|
||||||
|
// Delete crypto payments (references orders)
|
||||||
|
var paymentsDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM CryptoPayments");
|
||||||
|
deletedCount += paymentsDeleted;
|
||||||
|
_logger.LogInformation("Deleted {Count} crypto payments", paymentsDeleted);
|
||||||
|
|
||||||
|
// Delete order items (references orders and products)
|
||||||
|
var orderItemsDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM OrderItems");
|
||||||
|
deletedCount += orderItemsDeleted;
|
||||||
|
_logger.LogInformation("Deleted {Count} order items", orderItemsDeleted);
|
||||||
|
|
||||||
|
// Delete orders (parent table)
|
||||||
|
var ordersDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM Orders");
|
||||||
|
deletedCount += ordersDeleted;
|
||||||
|
_logger.LogInformation("Deleted {Count} orders", ordersDeleted);
|
||||||
|
|
||||||
|
// Delete reviews (sales feedback data)
|
||||||
|
var reviewsDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM Reviews");
|
||||||
|
deletedCount += reviewsDeleted;
|
||||||
|
_logger.LogInformation("Deleted {Count} reviews", reviewsDeleted);
|
||||||
|
|
||||||
|
// Delete push subscriptions (user notification data)
|
||||||
|
var subscriptionsDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM PushSubscriptions");
|
||||||
|
deletedCount += subscriptionsDeleted;
|
||||||
|
_logger.LogInformation("Deleted {Count} push subscriptions", subscriptionsDeleted);
|
||||||
|
|
||||||
|
// Delete bot contacts (telegram bot interaction data)
|
||||||
|
var contactsDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM BotContacts");
|
||||||
|
deletedCount += contactsDeleted;
|
||||||
|
_logger.LogInformation("Deleted {Count} bot contacts", contactsDeleted);
|
||||||
|
|
||||||
|
// Delete bot metrics (analytics data)
|
||||||
|
var metricsDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM BotMetrics");
|
||||||
|
deletedCount += metricsDeleted;
|
||||||
|
_logger.LogInformation("Deleted {Count} bot metrics", metricsDeleted);
|
||||||
|
|
||||||
|
// Delete customers (will cascade any remaining references)
|
||||||
|
var customersDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM Customers");
|
||||||
|
deletedCount += customersDeleted;
|
||||||
|
_logger.LogInformation("Deleted {Count} customers", customersDeleted);
|
||||||
|
|
||||||
|
_logger.LogInformation("✅ Successfully deleted {TotalCount} total sales records", deletedCount);
|
||||||
|
return deletedCount;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to delete orders and sales data");
|
||||||
|
throw new InvalidOperationException($"Failed to delete sales data: {ex.Message}", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user