using FruitBank.Common.Entities; using FruitBank.Common.Enums; using LinqToDB; using Microsoft.AspNetCore.Mvc; using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; using Nop.Plugin.Misc.FruitBankPlugin.Services; using Nop.Services.Catalog; using Nop.Services.Customers; using Nop.Services.Security; using Nop.Web.Framework; using Nop.Web.Framework.Controllers; using Nop.Web.Framework.Mvc.Filters; using System.Text.Json; namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers; [AuthorizeAdmin] [Area(AreaNames.ADMIN)] public class OrderDraftAdminController : BasePluginController { private readonly IOrderDraftService _draftService; private readonly FruitBankDbContext _dbContext; private readonly ICustomerService _customerService; private readonly IProductService _productService; private readonly IPermissionService _permissionService; public OrderDraftAdminController( IOrderDraftService draftService, FruitBankDbContext dbContext, ICustomerService customerService, IProductService productService, IPermissionService permissionService) { _draftService = draftService; _dbContext = dbContext; _customerService = customerService; _productService = productService; _permissionService = permissionService; } // ── LISTA ───────────────────────────────────────────────────────────────── [HttpGet] public IActionResult List() { return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/OrderDraft/List.cshtml", new OrderDraftListModel()); } [HttpPost] public async Task OrderDraftList() { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { draw = 1, recordsTotal = 0, recordsFiltered = 0, data = Array.Empty() }); _ = int.TryParse(Request.Form["draw"].FirstOrDefault(), out int draw); draw = Math.Max(draw, 1); _ = int.TryParse(Request.Form["start"].FirstOrDefault(), out int start); start = Math.Max(start, 0); _ = int.TryParse(Request.Form["length"].FirstOrDefault(), out int length); length = length < 1 ? 25 : Math.Min(length, 200); var statusFilter = Request.Form["statusFilter"].FirstOrDefault() ?? "Pending"; var globalSearch = Request.Form["search[value]"].FirstOrDefault()?.Trim() ?? ""; // 1. Draftek lekérése var query = _dbContext.OrderDrafts.GetAll(false); if (Enum.TryParse(statusFilter, out var statusEnum)) query = query.Where(d => d.Status == statusEnum); var allDrafts = await query.OrderByDescending(d => d.CreatedOnUtc).ToListAsync(); // 2. Kapcsolódó customer nevek — batch var customerIds = allDrafts.Where(d => d.CustomerId.HasValue).Select(d => d.CustomerId!.Value).Distinct().ToList(); var customers = new Dictionary(); if (customerIds.Any()) { var customerList = await _dbContext.Customers.Table .Where(c => customerIds.Contains(c.Id)) .Select(c => new { c.Id, c.FirstName, c.LastName, c.Email }) .ToListAsync(); customers = customerList.ToDictionary( c => c.Id, c => string.IsNullOrWhiteSpace($"{c.FirstName} {c.LastName}".Trim()) ? c.Email ?? c.Id.ToString() : $"{c.FirstName} {c.LastName}".Trim()); } // 3. Item count — batch var draftIds = allDrafts.Select(d => d.Id).ToList(); var itemCounts = await _dbContext.OrderDraftItems.GetAll() .Where(i => draftIds.Contains(i.DraftId)) .GroupBy(i => i.DraftId) .Select(g => new { DraftId = g.Key, Count = g.Count() }) .ToListAsync(); var itemCountDict = itemCounts.ToDictionary(x => x.DraftId, x => x.Count); var now = DateTime.UtcNow; var rows = allDrafts.Select(d => new OrderDraftListRow { Id = d.Id, ExternalUsername = d.ExternalUsername, CustomerId = d.CustomerId, CustomerName = d.CustomerId.HasValue && customers.TryGetValue(d.CustomerId.Value, out var cn) ? cn : "— ismeretlen —", RawMessageTextShort = d.RawMessageText.Length > 80 ? d.RawMessageText[..80] + "…" : d.RawMessageText, ParsedDeliveryDate = d.ParsedDeliveryDate.ToString("yyyy-MM-dd"), ItemCount = itemCountDict.TryGetValue(d.Id, out var cnt) ? cnt : 0, Status = d.Status.ToString(), StatusBadgeClass = d.Status switch { OrderDraftStatus.Pending => "badge-warning", OrderDraftStatus.Approved => "badge-success", OrderDraftStatus.Rejected => "badge-danger", OrderDraftStatus.Expired => "badge-secondary", _ => "badge-light" }, IsUrgent = d.Status == OrderDraftStatus.Pending && (now - d.CreatedOnUtc).TotalHours > 20, CreatedOnUtc = d.CreatedOnUtc.ToString("yyyy-MM-dd HH:mm") }).ToList(); int recordsTotal = rows.Count; if (!string.IsNullOrWhiteSpace(globalSearch)) rows = rows.Where(r => r.ExternalUsername.Contains(globalSearch, StringComparison.OrdinalIgnoreCase) || r.CustomerName.Contains(globalSearch, StringComparison.OrdinalIgnoreCase) || r.RawMessageTextShort.Contains(globalSearch, StringComparison.OrdinalIgnoreCase) ).ToList(); int recordsFiltered = rows.Count; var page = rows.Skip(start).Take(length).ToList(); return Json(new { draw, recordsTotal, recordsFiltered, data = page }); } // ── RÉSZLETEK / JÓVÁHAGYÓ OLDAL ────────────────────────────────────────── [HttpGet] public async Task Detail(int id) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); var draft = await _draftService.GetByIdAsync(id, true); if (draft == null) return NotFound(); // Customer név string customerName = "— ismeretlen partner —"; if (draft.CustomerId.HasValue) { var customer = await _customerService.GetCustomerByIdAsync(draft.CustomerId.Value); if (customer != null) customerName = string.IsNullOrWhiteSpace($"{customer.FirstName} {customer.LastName}".Trim()) ? customer.Email ?? customer.Id.ToString() : $"{customer.FirstName} {customer.LastName}".Trim(); } // Termék nevek és kandidátusok batch lekérése var allCandidateIds = draft.Items .SelectMany(i => { try { return JsonSerializer.Deserialize>(i.SuggestedProductIdsJson) ?? []; } catch { return []; } }) .Distinct() .ToList(); var productMap = new Dictionary(); if (allCandidateIds.Any()) { var products = await _dbContext.ProductDtos .GetAllByIds(allCandidateIds, false) .ToListAsync(); productMap = products.ToDictionary( p => p.Id, p => (p.Name, p.StockQuantity)); } var model = new OrderDraftDetailModel { Id = draft.Id, ExternalUsername = draft.ExternalUsername, CustomerId = draft.CustomerId, CustomerName = customerName, RawMessageText = draft.RawMessageText, ParsedDeliveryDate = draft.ParsedDeliveryDate, IsPreorder = draft.ParsedDeliveryDate.Date > DateTime.Today.AddDays(4), Status = draft.Status.ToString(), AdminNote = draft.AdminNote ?? string.Empty, CreatedOnUtc = draft.CreatedOnUtc.ToString("yyyy-MM-dd HH:mm"), CanApprove = draft.Status == OrderDraftStatus.Pending, Items = draft.Items.OrderBy(i => i.SortOrder).Select(i => { var candidateIds = new List(); try { candidateIds = JsonSerializer.Deserialize>(i.SuggestedProductIdsJson) ?? []; } catch { /* */ } var selectedName = i.SelectedProductId.HasValue && productMap.TryGetValue(i.SelectedProductId.Value, out var sel) ? sel.Name : string.Empty; return new OrderDraftItemModel { Id = i.Id, RawProductText = i.RawProductText, AiResolvedProductName = i.AiResolvedProductName, RequestedQuantity = i.RequestedQuantity, IsPreorder = i.IsPreorder, SortOrder = i.SortOrder, SelectedProductId = i.SelectedProductId, SelectedProductName = selectedName, Candidates = candidateIds .Where(pid => productMap.ContainsKey(pid)) .Select(pid => new OrderDraftProductCandidate { Id = pid, Name = productMap[pid].Name, Stock = productMap[pid].Stock.ToString() }).ToList() }; }).ToList() }; return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/OrderDraft/Detail.cshtml", model); } // ── AJAX: TÉTEL FRISSÍTÉSE ──────────────────────────────────────────────── [HttpPost] public async Task UpdateItem(int draftItemId, int selectedProductId, int quantity) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false }); await _draftService.UpdateDraftItemAsync(draftItemId, selectedProductId, quantity); return Json(new { success = true }); } // ── AJAX: PARTNER HOZZÁRENDELÉSE ────────────────────────────────────────── [HttpPost] public async Task AssignCustomer(int draftId, int customerId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false }); var workContext = HttpContext.RequestServices.GetService(typeof(Nop.Core.IWorkContext)) as Nop.Core.IWorkContext; var admin = workContext != null ? await workContext.GetCurrentCustomerAsync() : null; await _draftService.AssignCustomerAsync(draftId, customerId, admin?.Id ?? 0); var customer = await _customerService.GetCustomerByIdAsync(customerId); var name = customer == null ? customerId.ToString() : string.IsNullOrWhiteSpace($"{customer.FirstName} {customer.LastName}".Trim()) ? customer.Email ?? customerId.ToString() : $"{customer.FirstName} {customer.LastName}".Trim(); return Json(new { success = true, customerName = name }); } // ── AJAX: JÓVÁHAGYÁS ───────────────────────────────────────────────────── [HttpPost] public async Task Approve(int draftId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, error = "Hozzáférés megtagadva." }); var workContext = HttpContext.RequestServices.GetService(typeof(Nop.Core.IWorkContext)) as Nop.Core.IWorkContext; var admin = workContext != null ? await workContext.GetCurrentCustomerAsync() : null; var result = await _draftService.ApproveAsync(draftId, admin?.Id ?? 0); return Json(new { success = result.Success, error = result.ErrorMessage, preOrderIds = result.PreOrderIds, orderIds = result.OrderIds }); } // ── AJAX: ELUTASÍTÁS ────────────────────────────────────────────────────── [HttpPost] public async Task Reject(int draftId, string? adminNote) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false }); var workContext = HttpContext.RequestServices.GetService(typeof(Nop.Core.IWorkContext)) as Nop.Core.IWorkContext; var admin = workContext != null ? await workContext.GetCurrentCustomerAsync() : null; await _draftService.RejectAsync(draftId, admin?.Id ?? 0, adminNote); return Json(new { success = true }); } }