259 lines
11 KiB
C#
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 });
|
|
}
|
|
}
|
|
}
|