Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/OrderDraftAdminController.cs

297 lines
14 KiB
C#

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<IActionResult> OrderDraftList()
{
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return Json(new { draw = 1, recordsTotal = 0, recordsFiltered = 0, data = Array.Empty<object>() });
_ = 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<OrderDraftStatus>(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<int, string>();
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<IActionResult> 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<List<int>>(i.SuggestedProductIdsJson) ?? []; }
catch { return []; }
})
.Distinct()
.ToList();
var productMap = new Dictionary<int, (string Name, int Stock)>();
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<int>();
try { candidateIds = JsonSerializer.Deserialize<List<int>>(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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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 });
}
}