using Microsoft.AspNetCore.Mvc; using Nop.Core; using Nop.Core.Domain.Orders; using Nop.Plugin.Misc.FruitBankPlugin.Services; using Nop.Services.Common; using Nop.Services.Orders; using Nop.Web.Framework; using Nop.Web.Framework.Controllers; using Nop.Web.Framework.Mvc.Filters; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers { [Area(AreaNames.ADMIN)] [AuthorizeAdmin] public class InnVoiceOrderSyncController : BasePluginController { private readonly IOrderService _orderService; private readonly IStoreContext _storeContext; private readonly InnVoiceOrderService _innVoiceOrderService; private readonly IGenericAttributeService _genericAttributeService; public InnVoiceOrderSyncController( IOrderService _orderService, IStoreContext storeContext, InnVoiceOrderService innVoiceOrderService, IGenericAttributeService genericAttributeService) { this._orderService = _orderService; _storeContext = storeContext; _innVoiceOrderService = innVoiceOrderService; _genericAttributeService = genericAttributeService; } /// /// Display the order sync page /// [HttpGet] public IActionResult Index() { return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/InnVoiceOrderSync/Index.cshtml"); } /// /// Sync orders between NopCommerce and InnVoice for a given date range /// [HttpPost] public async Task SyncOrders(DateTime startDate, DateTime endDate) { try { // Validate dates if (startDate > endDate) { return Json(new { success = false, message = "Start date cannot be after end date" }); } // Get store ID var storeId = (await _storeContext.GetCurrentStoreAsync()).Id; // Step 1: Get all InnVoice orders for the date range using UpdateTime var innVoiceOrders = await GetInnVoiceOrdersByDateRange(startDate, endDate); Console.WriteLine($"DEBUG: Found {innVoiceOrders.Count} InnVoice orders"); Console.WriteLine($"DEBUG: InnVoice TableIds: {string.Join(", ", innVoiceOrders.Select(o => o.TableId))}"); // Step 2: Get all NopCommerce orders in the date range (filtered by DateOfReceipt) var nopOrders = await GetNopOrdersByDateRange(startDate, endDate, storeId); Console.WriteLine($"DEBUG: Found {nopOrders.Count} NopCommerce orders with DateOfReceipt in range"); // Step 3: Get TableIds from NopCommerce orders var nopOrderTableIds = new Dictionary(); // orderId -> tableId var nopOrdersWithoutTableId = new List(); // Track orders without TableId foreach (var order in nopOrders) { var tableIdStr = await _genericAttributeService.GetAttributeAsync( order, "InnVoiceOrderTableId", storeId ); if (!string.IsNullOrEmpty(tableIdStr) && int.TryParse(tableIdStr, out int tableId)) { nopOrderTableIds[order.Id] = tableId; Console.WriteLine($"DEBUG: Order {order.Id} has TableId {tableId}"); } else { nopOrdersWithoutTableId.Add(order.Id); Console.WriteLine($"DEBUG: Order {order.Id} has NO TableId (tableIdStr='{tableIdStr}')"); } } // Step 4: Compare and find discrepancies var innVoiceTableIds = new HashSet(innVoiceOrders.Select(o => o.TableId).Where(t => t > 0)); var nopTableIds = new HashSet(nopOrderTableIds.Values); // Orders in InnVoice but not in NopCommerce var innVoiceOnly = innVoiceTableIds.Except(nopTableIds).ToList(); // Orders in NopCommerce but not in InnVoice var nopOnly = nopTableIds.Except(innVoiceTableIds).ToList(); // Orders in both systems var inBoth = innVoiceTableIds.Intersect(nopTableIds).ToList(); // Build detailed results var innVoiceOnlyDetails = innVoiceOrders .Where(o => innVoiceOnly.Contains(o.TableId)) .Select(o => new { tableId = o.TableId, techId = o.TechId, customerName = o.CustomerName, totalGross = o.TotalGross, orderDate = o.OrderDate, externalOrderNumber = o.ExternalOrderNumber }) .ToList(); var nopOnlyDetails = nopOrders .Where(o => nopOrderTableIds.ContainsKey(o.Id) && nopOnly.Contains(nopOrderTableIds[o.Id])) .Select(o => new { orderId = o.Id, tableId = nopOrderTableIds.ContainsKey(o.Id) ? nopOrderTableIds[o.Id] : 0, customOrderNumber = o.CustomOrderNumber, orderTotal = o.OrderTotal, createdOn = o.CreatedOnUtc }) .ToList(); // Orders without TableId (never uploaded to InnVoice) var nopOrdersNotUploadedDetails = new List(); foreach (var order in nopOrders.Where(o => nopOrdersWithoutTableId.Contains(o.Id))) { // Check if order has items var orderItems = await _orderService.GetOrderItemsAsync(order.Id); var hasItems = orderItems.Any(); nopOrdersNotUploadedDetails.Add(new { orderId = order.Id, customOrderNumber = order.CustomOrderNumber, orderTotal = order.OrderTotal, createdOn = order.CreatedOnUtc, orderStatus = order.OrderStatus.ToString(), orderStatusId = (int)order.OrderStatus, hasItems = hasItems, itemCount = orderItems.Count() }); } // Separate orders by whether they have items var deletedOrders = nopOrdersNotUploadedDetails .Where(o => !o.hasItems) // No items = deleted/empty order .ToList(); var missingUploads = nopOrdersNotUploadedDetails .Where(o => o.hasItems) // Has items = should have been uploaded .ToList(); return Json(new { success = true, data = new { summary = new { totalInnVoiceOrders = innVoiceOrders.Count, totalNopOrders = nopOrders.Count, totalNopOrdersWithTableId = nopOrderTableIds.Count, totalNopOrdersWithoutTableId = nopOrdersWithoutTableId.Count, totalDeletedOrders = deletedOrders.Count, totalMissingUploads = missingUploads.Count, inBothSystems = inBoth.Count, onlyInInnVoice = innVoiceOnly.Count, onlyInNopCommerce = nopOnly.Count }, innVoiceOnly = innVoiceOnlyDetails, nopCommerceOnly = nopOnlyDetails, nopCommerceNotUploaded = nopOrdersNotUploadedDetails, deletedOrders = deletedOrders, missingUploads = missingUploads, dateRange = new { startDate = startDate.ToString("yyyy-MM-dd"), endDate = endDate.ToString("yyyy-MM-dd") } } }); } catch (Exception ex) { return Json(new { success = false, message = $"Error: {ex.Message}" }); } } /// /// Quick action: Sync last 30 days /// [HttpPost] public async Task SyncLast30Days() { var endDate = DateTime.Now; var startDate = endDate.AddDays(-30); return await SyncOrders(startDate, endDate); } /// /// Get InnVoice orders by UpdateTime range /// We query by UpdateTime but DON'T filter by MegrendelesKelte /// This allows comparing ALL orders regardless of order date mismatches /// private async Task> GetInnVoiceOrdersByDateRange(DateTime startDate, DateTime endDate) { var allOrders = new List(); // Add 14-day buffer to catch orders that might have been modified/created around this time //var queryStartDate = startDate.AddDays(-14); //var queryEndDate = endDate.AddDays(7); //var currentDate = queryStartDate; var currentDate = startDate; //Console.WriteLine($"DEBUG: Querying InnVoice from {queryStartDate:yyyy-MM-dd} to {queryEndDate:yyyy-MM-dd}"); Console.WriteLine($"DEBUG: Querying InnVoice from {startDate:yyyy-MM-dd} to {endDate:yyyy-MM-dd}"); // Query day by day to avoid rate limits while (currentDate <= endDate) { try { var orders = await _innVoiceOrderService.GetOrdersByOrderDateAsync(currentDate); if (orders != null && orders.Any()) { Console.WriteLine($"DEBUG: Found {orders.Count} orders updated on {currentDate:yyyy-MM-dd}"); allOrders.AddRange(orders); } currentDate = currentDate.AddDays(1); } catch (Exception ex) { // Log and continue Console.WriteLine($"Error fetching InnVoice orders for {currentDate:yyyy-MM-dd}: {ex.Message}"); currentDate = currentDate.AddDays(1); } } // Remove duplicates based on TableId var uniqueOrders = allOrders .GroupBy(o => o.TableId) .Select(g => g.First()) .ToList(); Console.WriteLine($"DEBUG: Total unique InnVoice orders: {uniqueOrders.Count}"); return uniqueOrders; } /// /// Get NopCommerce orders by DateOfReceipt generic attribute date range /// private async Task> GetNopOrdersByDateRange(DateTime startDate, DateTime endDate, int storeId) { // Get ALL orders (we'll filter by DateOfReceipt manually) var allOrders = await _orderService.SearchOrdersAsync(); var filteredOrders = new List(); foreach (var order in allOrders) { // Get the DateOfReceipt generic attribute var dateOfReceiptStr = await _genericAttributeService.GetAttributeAsync( order, "DateOfReceipt", storeId ); // Parse and check if within range if (!string.IsNullOrEmpty(dateOfReceiptStr) && DateTime.TryParse(dateOfReceiptStr, out DateTime dateOfReceipt)) { // Compare dates (ignoring time component) var receiptDate = dateOfReceipt.Date; var start = startDate.Date; var end = endDate.Date; if (receiptDate >= start && receiptDate <= end) { filteredOrders.Add(order); } } } return filteredOrders; } } }