291 lines
12 KiB
C#
291 lines
12 KiB
C#
using FruitBank.Common.Entities;
|
|
using FruitBank.Common.Server;
|
|
using LinqToDB;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Nop.Core;
|
|
using Nop.Core.Domain.Catalog;
|
|
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
|
|
using Nop.Plugin.Misc.FruitBankPlugin.Services;
|
|
using Nop.Services.Catalog;
|
|
using Nop.Services.Customers;
|
|
using Nop.Services.Localization;
|
|
using Nop.Web.Framework.Controllers;
|
|
|
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers;
|
|
|
|
[AutoValidateAntiforgeryToken]
|
|
public class PreorderController : BasePluginController
|
|
{
|
|
private readonly IWorkContext _workContext;
|
|
private readonly IStoreContext _storeContext;
|
|
private readonly ICustomerService _customerService;
|
|
private readonly ILocalizationService _localizationService;
|
|
private readonly FruitBankDbContext _dbContext;
|
|
private readonly PreorderDbContext _preorderDbContext;
|
|
private readonly FruitBankAttributeService _fruitBankAttributeService;
|
|
private readonly CustomPriceCalculationService _customPriceCalculationService;
|
|
|
|
private const string PendingDeliveryDateTimeKey = "PreorderPendingDeliveryDateTime";
|
|
private const string Prefix = "Plugins.Misc.FruitBankPlugin.Preorder.";
|
|
|
|
public PreorderController(
|
|
IWorkContext workContext,
|
|
IStoreContext storeContext,
|
|
ICustomerService customerService,
|
|
ILocalizationService localizationService,
|
|
FruitBankDbContext dbContext,
|
|
PreorderDbContext preorderDbContext,
|
|
FruitBankAttributeService fruitBankAttributeService,
|
|
IPriceCalculationService priceCalculationService)
|
|
{
|
|
_workContext = workContext;
|
|
_storeContext = storeContext;
|
|
_customerService = customerService;
|
|
_localizationService = localizationService;
|
|
_dbContext = dbContext;
|
|
_preorderDbContext = preorderDbContext;
|
|
_fruitBankAttributeService = fruitBankAttributeService;
|
|
_customPriceCalculationService = priceCalculationService as CustomPriceCalculationService;
|
|
}
|
|
|
|
private Task<string> L(string keySuffix)
|
|
=> _localizationService.GetResourceAsync(Prefix + keySuffix);
|
|
|
|
// ── INDEX ─────────────────────────────────────────────────────────────────
|
|
|
|
[HttpGet]
|
|
public async Task<IActionResult> Index()
|
|
{
|
|
var customer = await _workContext.GetCurrentCustomerAsync();
|
|
if (await _customerService.IsGuestAsync(customer))
|
|
return Challenge();
|
|
|
|
return View("~/Plugins/Misc.FruitBankPlugin/Views/Preorder/Index.cshtml");
|
|
}
|
|
|
|
// ── GET SAVED DELIVERY DATETIME (page restore) ────────────────────────────
|
|
|
|
[HttpGet]
|
|
public async Task<IActionResult> GetDeliveryDateTime()
|
|
{
|
|
var customer = await _workContext.GetCurrentCustomerAsync();
|
|
if (await _customerService.IsGuestAsync(customer))
|
|
return Json(new { success = false });
|
|
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
var saved = await _fruitBankAttributeService
|
|
.GetGenericAttributeValueAsync<Nop.Core.Domain.Customers.Customer, DateTime?>(
|
|
customer.Id, PendingDeliveryDateTimeKey, store.Id);
|
|
|
|
if (!saved.HasValue)
|
|
return Json(new { success = true, hasValue = false });
|
|
|
|
return Json(new
|
|
{
|
|
success = true,
|
|
hasValue = true,
|
|
date = saved.Value.ToString("yyyy-MM-dd"),
|
|
time = saved.Value.ToString("HH:mm"),
|
|
iso = saved.Value.ToString("yyyy-MM-ddTHH:mm")
|
|
});
|
|
}
|
|
|
|
// ── SET DELIVERY DATETIME ─────────────────────────────────────────────────
|
|
|
|
[HttpPost]
|
|
public async Task<IActionResult> SetDeliveryDateTime(string deliveryDateTime)
|
|
{
|
|
var customer = await _workContext.GetCurrentCustomerAsync();
|
|
if (await _customerService.IsGuestAsync(customer))
|
|
return Json(new { success = false, message = await L("NotLoggedIn") });
|
|
|
|
if (string.IsNullOrWhiteSpace(deliveryDateTime))
|
|
return Json(new { success = false, message = await L("NoDeliveryDateTimeProvided") });
|
|
|
|
if (!DateTime.TryParse(deliveryDateTime, out var parsed))
|
|
return Json(new { success = false, message = await L("InvalidDeliveryDateTime") });
|
|
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Nop.Core.Domain.Customers.Customer, DateTime>(
|
|
customer.Id, PendingDeliveryDateTimeKey, parsed, store.Id);
|
|
|
|
return Json(new { success = true });
|
|
}
|
|
|
|
// ── GET AVAILABLE PRODUCTS (filtered by preorder window) ──────────────────
|
|
|
|
[HttpGet]
|
|
public async Task<IActionResult> GetAvailableProducts()
|
|
{
|
|
var customer = await _workContext.GetCurrentCustomerAsync();
|
|
if (await _customerService.IsGuestAsync(customer))
|
|
return Json(new { success = false, message = await L("NotLoggedIn") });
|
|
|
|
try
|
|
{
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
var today = DateTime.UtcNow.Date;
|
|
|
|
// Load 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 == store.Id)
|
|
.ToListAsync();
|
|
|
|
var gaEnd = await _dbContext.GenericAttributes.Table
|
|
.Where(ga => ga.KeyGroup == nameof(Product)
|
|
&& ga.Key == FruitBankConst.PreorderWindowEnd
|
|
&& ga.StoreId == store.Id)
|
|
.ToListAsync();
|
|
|
|
var startById = gaStart.ToDictionary(g => g.EntityId, g => g.Value);
|
|
var endById = gaEnd.ToDictionary(g => g.EntityId, g => g.Value);
|
|
|
|
// Product IDs that are available today for preorder
|
|
var availableProductIds = startById.Keys
|
|
.Intersect(endById.Keys)
|
|
.Where(id =>
|
|
{
|
|
DateTime.TryParse(startById[id], out var ws);
|
|
DateTime.TryParse(endById[id], out var we);
|
|
return ws.Date <= today && today <= we.Date;
|
|
})
|
|
.ToHashSet();
|
|
|
|
if (!availableProductIds.Any())
|
|
return Json(new { success = true, products = Array.Empty<object>() });
|
|
|
|
// Load product DTOs for those IDs
|
|
var productDtos = await _dbContext.ProductDtos
|
|
.GetAll(true)
|
|
.Where(p => availableProductIds.Contains(p.Id))
|
|
.ToListAsync();
|
|
|
|
var result = new List<object>();
|
|
|
|
foreach (var dto in productDtos.OrderBy(p => p.Name))
|
|
{
|
|
decimal? unitPrice = null;
|
|
if (!dto.IsMeasurable && _customPriceCalculationService != null)
|
|
{
|
|
var product = await _dbContext.Products.GetByIdAsync(dto.Id);
|
|
if (product != null)
|
|
{
|
|
var priceResult = await _customPriceCalculationService.GetFinalPriceAsync(
|
|
product, customer, store, null, 0, true, 1, null, null);
|
|
unitPrice = priceResult.finalPrice;
|
|
}
|
|
}
|
|
|
|
result.Add(new
|
|
{
|
|
id = dto.Id,
|
|
name = dto.Name,
|
|
isMeasurable = dto.IsMeasurable,
|
|
unitPrice,
|
|
stockQuantity = dto.AvailableQuantity
|
|
});
|
|
}
|
|
|
|
return Json(new { success = true, products = result });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[Preorder] GetAvailableProducts error: {ex.Message}");
|
|
return Json(new { success = false, message = $"Hiba: {ex.Message}" });
|
|
}
|
|
}
|
|
|
|
// ── PLACE PREORDER ────────────────────────────────────────────────────────
|
|
|
|
[HttpPost]
|
|
public async Task<IActionResult> PlacePreorder([FromBody] PlacePreorderRequest request)
|
|
{
|
|
var customer = await _workContext.GetCurrentCustomerAsync();
|
|
if (await _customerService.IsGuestAsync(customer))
|
|
return Json(new { success = false, message = await L("NotLoggedIn") });
|
|
|
|
if (request?.Items == null || !request.Items.Any())
|
|
return Json(new { success = false, message = await L("NoItemsSelected") });
|
|
|
|
if (!DateTime.TryParse(request.DeliveryDateTime, out var deliveryDateTime))
|
|
return Json(new { success = false, message = await L("InvalidDeliveryDateTime") });
|
|
|
|
try
|
|
{
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
|
|
var preorder = new Preorder
|
|
{
|
|
CustomerId = customer.Id,
|
|
StoreId = store.Id,
|
|
DateOfReceipt = deliveryDateTime,
|
|
CustomerNote = request.CustomerNote?.Trim()
|
|
};
|
|
|
|
var items = new List<PreorderItem>();
|
|
foreach (var req in request.Items.Where(i => i.Quantity > 0))
|
|
{
|
|
var product = await _dbContext.Products.GetByIdAsync(req.ProductId);
|
|
if (product == null || product.Deleted || !product.Published)
|
|
continue;
|
|
|
|
decimal unitPrice = 0;
|
|
if (_customPriceCalculationService != null)
|
|
{
|
|
var pr = await _customPriceCalculationService.GetFinalPriceAsync(
|
|
product, customer, store, null, 0, true, req.Quantity, null, null);
|
|
unitPrice = pr.finalPrice;
|
|
}
|
|
|
|
items.Add(new PreorderItem
|
|
{
|
|
ProductId = req.ProductId,
|
|
RequestedQuantity = req.Quantity,
|
|
UnitPriceInclTax = unitPrice
|
|
});
|
|
}
|
|
|
|
if (!items.Any())
|
|
return Json(new { success = false, message = await L("NoValidItems") });
|
|
|
|
var saved = await _preorderDbContext.InsertPreorderAsync(preorder, items);
|
|
|
|
// Clean up the pending delivery datetime attribute
|
|
await _fruitBankAttributeService
|
|
.DeleteGenericAttributeAsync<Nop.Core.Domain.Customers.Customer>(
|
|
customer.Id, PendingDeliveryDateTimeKey, store.Id);
|
|
|
|
Console.WriteLine($"[Preorder] Placed #{saved.Id} — customer #{customer.Id}, {items.Count} items, delivery {deliveryDateTime:u}");
|
|
|
|
return Json(new
|
|
{
|
|
success = true,
|
|
preorderId = saved.Id,
|
|
message = await L("PlacedSuccessfully")
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[Preorder] PlacePreorder error: {ex.Message}");
|
|
return Json(new { success = false, message = $"Hiba: {ex.Message}" });
|
|
}
|
|
}
|
|
|
|
// ── INNER MODELS ──────────────────────────────────────────────────────────
|
|
|
|
public class PlacePreorderRequest
|
|
{
|
|
public string? DeliveryDateTime { get; set; }
|
|
public string? CustomerNote { get; set; }
|
|
public List<PreorderItemRequest> Items { get; set; } = new();
|
|
}
|
|
|
|
public class PreorderItemRequest
|
|
{
|
|
public int ProductId { get; set; }
|
|
public int Quantity { get; set; }
|
|
}
|
|
}
|