diff --git a/LittleShop/Areas/Admin/Controllers/ProductsController.cs b/LittleShop/Areas/Admin/Controllers/ProductsController.cs index a0a59fb..6d123b8 100644 --- a/LittleShop/Areas/Admin/Controllers/ProductsController.cs +++ b/LittleShop/Areas/Admin/Controllers/ProductsController.cs @@ -306,4 +306,21 @@ public class ProductsController : Controller } return Json(collection); } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task 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)); + } + } } \ No newline at end of file diff --git a/LittleShop/Services/ProductImportService.cs b/LittleShop/Services/ProductImportService.cs index 5cf104a..acb35d7 100644 --- a/LittleShop/Services/ProductImportService.cs +++ b/LittleShop/Services/ProductImportService.cs @@ -14,6 +14,7 @@ public interface IProductImportService Task ImportFromTextAsync(string csvText, bool replaceAll = false); Task ImportFromHumanTextAsync(string textContent, bool replaceAll = false); Task DeleteAllProductsAndCategoriesAsync(); + Task DeleteAllOrdersAndSalesDataAsync(); string GenerateTemplateAsCsv(); string GenerateTemplateAsHumanText(); Task ExportProductsAsCsvAsync(); @@ -731,33 +732,113 @@ stock: 75 { var deletedCount = 0; - // Delete all product photos - var photos = await _context.ProductPhotos.ToListAsync(); - _context.ProductPhotos.RemoveRange(photos); - deletedCount += photos.Count; + try + { + // Use ExecuteSqlRaw to avoid EF tracking issues and optimistic concurrency errors + // Order matters: delete child records first, then parents - // Delete all product variants - var variants = await _context.ProductVariants.ToListAsync(); - _context.ProductVariants.RemoveRange(variants); - deletedCount += variants.Count; + // Delete order items (references products) + var orderItemsDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM OrderItems"); + deletedCount += orderItemsDeleted; + _logger.LogInformation("Deleted {Count} order items", orderItemsDeleted); - // Delete all product multi-buys - var multiBuys = await _context.ProductMultiBuys.ToListAsync(); - _context.ProductMultiBuys.RemoveRange(multiBuys); - deletedCount += multiBuys.Count; + // Delete product photos + var photosDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM ProductPhotos"); + deletedCount += photosDeleted; + _logger.LogInformation("Deleted {Count} product photos", photosDeleted); - // Delete all products - var products = await _context.Products.ToListAsync(); - _context.Products.RemoveRange(products); - deletedCount += products.Count; + // Delete product variants + var variantsDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM ProductVariants"); + deletedCount += variantsDeleted; + _logger.LogInformation("Deleted {Count} product variants", variantsDeleted); - // Delete all categories - var categories = await _context.Categories.ToListAsync(); - _context.Categories.RemoveRange(categories); - deletedCount += categories.Count; + // Delete product multi-buys + var multiBuysDeleted = await _context.Database.ExecuteSqlRawAsync("DELETE FROM ProductMultiBuys"); + deletedCount += multiBuysDeleted; + _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 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); + } } } \ No newline at end of file