Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/PreorderAvailabilityControl...

259 lines
11 KiB
C#

using FruitBank.Common.Server;
using LinqToDB;
using Microsoft.AspNetCore.Mvc;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Services.Security;
using Nop.Web.Framework;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Mvc.Filters;
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers;
[AuthorizeAdmin]
[Area(AreaNames.ADMIN)]
[AutoValidateAntiforgeryToken]
public class PreorderAvailabilityController : BasePluginController
{
private readonly IPermissionService _permissionService;
private readonly FruitBankDbContext _dbContext;
private readonly FruitBankAttributeService _fruitBankAttributeService;
private readonly IStoreContext _storeContext;
public PreorderAvailabilityController(
IPermissionService permissionService,
FruitBankDbContext dbContext,
FruitBankAttributeService fruitBankAttributeService,
IStoreContext storeContext)
{
_permissionService = permissionService;
_dbContext = dbContext;
_fruitBankAttributeService = fruitBankAttributeService;
_storeContext = storeContext;
}
// ── INDEX ─────────────────────────────────────────────────────────────────
[HttpGet]
[Route("Admin/PreorderAvailability")]
public async Task<IActionResult> Index()
{
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return AccessDeniedView();
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/PreorderAvailability/Index.cshtml");
}
// ── ALL PRODUCTS — DataTables server-side ─────────────────────────────────
[HttpPost]
[Route("Admin/PreorderAvailability/ProductList")]
public async Task<IActionResult> ProductList()
{
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return Forbid();
_ = 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, 500);
var globalSearch = Request.Form["search[value]"].FirstOrDefault()?.Trim() ?? "";
var storeId = (await _storeContext.GetCurrentStoreAsync()).Id;
// 1. All published products
var products = await _dbContext.Products.Table
.Where(p => !p.Deleted && p.Published)
.OrderBy(p => p.Name)
.Select(p => new { p.Id, p.Name, p.Sku })
.ToListAsync();
// 2. All preorder window generic attributes — two queries, no N+1
var gaStart = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowStart
&& ga.StoreId == storeId)
.ToListAsync();
var gaEnd = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowEnd
&& ga.StoreId == storeId)
.ToListAsync();
var startByProduct = gaStart.ToDictionary(g => g.EntityId, g => g.Value);
var endByProduct = gaEnd.ToDictionary(g => g.EntityId, g => g.Value);
var today = DateTime.UtcNow.Date;
// 3. Build rows
var rows = products.Select(p =>
{
DateTime.TryParse(startByProduct.GetValueOrDefault(p.Id), out var ws);
DateTime.TryParse(endByProduct.GetValueOrDefault(p.Id), out var we);
var hasStart = startByProduct.ContainsKey(p.Id);
var hasEnd = endByProduct.ContainsKey(p.Id);
return new PreorderAvailabilityRow
{
ProductId = p.Id,
ProductName = p.Name,
Sku = p.Sku,
WindowStart = hasStart ? ws.ToString("yyyy-MM-dd") : null,
WindowEnd = hasEnd ? we.ToString("yyyy-MM-dd") : null,
IsAvailableToday = hasStart && hasEnd && ws.Date <= today && today <= we.Date
};
}).ToList();
int recordsTotal = rows.Count;
// 4. Global search
if (!string.IsNullOrWhiteSpace(globalSearch))
{
rows = rows.Where(r =>
r.ProductName.Contains(globalSearch, StringComparison.OrdinalIgnoreCase) ||
(r.Sku?.Contains(globalSearch, StringComparison.OrdinalIgnoreCase) ?? false)
).ToList();
}
int recordsFiltered = rows.Count;
// 5. Paginate
var page = rows.Skip(start).Take(length).ToList();
return Json(new { draw, recordsTotal, recordsFiltered, data = page });
}
// ── AVAILABLE TODAY — DataTables server-side ──────────────────────────────
[HttpPost]
[Route("Admin/PreorderAvailability/AvailableTodayList")]
public async Task<IActionResult> AvailableTodayList()
{
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return Forbid();
_ = 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, 500);
var storeId = (await _storeContext.GetCurrentStoreAsync()).Id;
var today = DateTime.UtcNow.Date;
// Reuse same build logic — filter to available today only
var products = await _dbContext.Products.Table
.Where(p => !p.Deleted && p.Published)
.OrderBy(p => p.Name)
.Select(p => new { p.Id, p.Name, p.Sku })
.ToListAsync();
var gaStart = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowStart
&& ga.StoreId == storeId)
.ToListAsync();
var gaEnd = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowEnd
&& ga.StoreId == storeId)
.ToListAsync();
var startByProduct = gaStart.ToDictionary(g => g.EntityId, g => g.Value);
var endByProduct = gaEnd.ToDictionary(g => g.EntityId, g => g.Value);
var rows = products
.Where(p =>
{
if (!startByProduct.TryGetValue(p.Id, out var sRaw)) return false;
if (!endByProduct.TryGetValue(p.Id, out var eRaw)) return false;
if (!DateTime.TryParse(sRaw, out var ws)) return false;
if (!DateTime.TryParse(eRaw, out var we)) return false;
return ws.Date <= today && today <= we.Date;
})
.Select(p =>
{
DateTime.TryParse(startByProduct[p.Id], out var ws);
DateTime.TryParse(endByProduct[p.Id], out var we);
return new PreorderAvailabilityRow
{
ProductId = p.Id,
ProductName = p.Name,
Sku = p.Sku,
WindowStart = ws.ToString("yyyy-MM-dd"),
WindowEnd = we.ToString("yyyy-MM-dd"),
IsAvailableToday = true
};
})
.ToList();
int recordsTotal = rows.Count;
int recordsFiltered = rows.Count;
var page = rows.Skip(start).Take(length).ToList();
return Json(new { draw, recordsTotal, recordsFiltered, data = page });
}
// ── SAVE WINDOW DATES for a product ───────────────────────────────────────
[HttpPost]
[Route("Admin/PreorderAvailability/SaveWindow")]
public async Task<IActionResult> SaveWindow(int productId, string? windowStart, string? windowEnd)
{
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return Json(new { success = false, error = "Access denied" });
try
{
var storeId = (await _storeContext.GetCurrentStoreAsync()).Id;
// WindowStart
if (string.IsNullOrWhiteSpace(windowStart))
{
await _fruitBankAttributeService
.DeleteGenericAttributeAsync<Product>(productId, FruitBankConst.PreorderWindowStart, storeId);
}
else if (DateTime.TryParse(windowStart, out var ws))
{
await _fruitBankAttributeService
.InsertOrUpdateGenericAttributeAsync<Product, DateTime>(
productId, FruitBankConst.PreorderWindowStart, ws.Date, storeId);
}
else return Json(new { success = false, error = $"Invalid start date: {windowStart}" });
// WindowEnd
if (string.IsNullOrWhiteSpace(windowEnd))
{
await _fruitBankAttributeService
.DeleteGenericAttributeAsync<Product>(productId, FruitBankConst.PreorderWindowEnd, storeId);
}
else if (DateTime.TryParse(windowEnd, out var we))
{
await _fruitBankAttributeService
.InsertOrUpdateGenericAttributeAsync<Product, DateTime>(
productId, FruitBankConst.PreorderWindowEnd, we.Date, storeId);
}
else return Json(new { success = false, error = $"Invalid end date: {windowEnd}" });
// Return the new availability state
var today = DateTime.UtcNow.Date;
DateTime.TryParse(windowStart, out var startParsed);
DateTime.TryParse(windowEnd, out var endParsed);
bool isAvailableToday = !string.IsNullOrWhiteSpace(windowStart)
&& !string.IsNullOrWhiteSpace(windowEnd)
&& startParsed.Date <= today
&& today <= endParsed.Date;
return Json(new { success = true, isAvailableToday });
}
catch (Exception ex)
{
return Json(new { success = false, error = ex.Message });
}
}
}