Rename Preorder domain to PreOrder across codebase

Comprehensively renamed all "Preorder" domain types, services, controllers, models, enums, database contexts, routes, views, JS, localization resources, and documentation to "PreOrder" (PascalCase, capital "O"). Updated all references in backend, frontend, localization, and schema docs for consistency. No business logic changes; this is a naming and normalization refactor.
This commit is contained in:
Loretta 2026-05-30 16:26:51 +02:00
parent 2a8dfeaca9
commit 1468aa651c
42 changed files with 910 additions and 746 deletions

View File

@ -176,7 +176,7 @@
</table> </table>
</div> </div>
<div class="card-footer text-muted small"> <div class="card-footer text-muted small">
<a href="/Admin/Preorders">Összes előrendelés &rarr;</a> <a href="/Admin/PreOrders">Összes előrendelés &rarr;</a>
</div> </div>
</div> </div>
@ -277,7 +277,7 @@
if (alert.type === 'credit_exceeded') if (alert.type === 'credit_exceeded')
return '<a href="/Admin/CustomerCredit/Details/' + alert.customerId + '" class="btn btn-xs btn-outline-warning">Hitelkeret</a>'; return '<a href="/Admin/CustomerCredit/Details/' + alert.customerId + '" class="btn btn-xs btn-outline-warning">Hitelkeret</a>';
if (alert.type === 'old_preorder') if (alert.type === 'old_preorder')
return '<a href="/Admin/Preorders" class="btn btn-xs btn-outline-info">Előrendelések</a>'; return '<a href="/Admin/PreOrders" class="btn btn-xs btn-outline-info">Előrendelések</a>';
return ''; return '';
} }
@ -374,10 +374,10 @@
} }
// ── Pending preorders ──────────────────────────────────────────── // ── Pending preorders ────────────────────────────────────────────
if (data.pendingPreorders && data.pendingPreorders.length > 0) { if (data.pendingPreOrders && data.pendingPreOrders.length > 0) {
$('#fb-preorders-count').text(data.pendingPreorders.length); $('#fb-preorders-count').text(data.pendingPreOrders.length);
var $pb = $('#fb-preorders-body').empty(); var $pb = $('#fb-preorders-body').empty();
data.pendingPreorders.forEach(function (p) { data.pendingPreOrders.forEach(function (p) {
$pb.append( $pb.append(
'<tr>' + '<tr>' +
'<td>' + p.company + '</td>' + '<td>' + p.company + '</td>' +
@ -385,7 +385,7 @@
'<td class="text-center">' + p.itemCount + '</td>' + '<td class="text-center">' + p.itemCount + '</td>' +
'<td class="text-center">' + p.fulfilledCount + ' / ' + p.itemCount + '</td>' + '<td class="text-center">' + p.fulfilledCount + ' / ' + p.itemCount + '</td>' +
'<td class="text-center">' + preorderStatusBadge(p.status) + '</td>' + '<td class="text-center">' + preorderStatusBadge(p.status) + '</td>' +
'<td><a href="/Admin/Preorders/Details/' + p.id + '" class="btn btn-xs btn-outline-secondary">Részletek</a></td>' + '<td><a href="/Admin/PreOrders/Details/' + p.id + '" class="btn btn-xs btn-outline-secondary">Részletek</a></td>' +
'</tr>' '</tr>'
); );
}); });

View File

@ -37,7 +37,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
protected readonly IWorkContext _workContext; protected readonly IWorkContext _workContext;
protected readonly AICalculationService _aiCalculationService; protected readonly AICalculationService _aiCalculationService;
protected readonly FruitBankDbContext _fruitBankDbContext; protected readonly FruitBankDbContext _fruitBankDbContext;
protected readonly PreorderDbContext _preorderDbContext; protected readonly PreOrderDbContext _preorderDbContext;
#endregion #endregion
@ -55,7 +55,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
IWorkContext workContext, IWorkContext workContext,
AICalculationService aiCalculationService, AICalculationService aiCalculationService,
FruitBankDbContext fruitBankDbContext, FruitBankDbContext fruitBankDbContext,
PreorderDbContext preorderDbContext) PreOrderDbContext preorderDbContext)
{ {
_adminAreaSettings = adminAreaSettings; _adminAreaSettings = adminAreaSettings;
_commonModelFactory = commonModelFactory; _commonModelFactory = commonModelFactory;
@ -186,32 +186,32 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
var today = DateTime.Now.Date; var today = DateTime.Now.Date;
// ── Batch 1: parallel queries ───────────────────────────────────── // ── Batch 1: parallel queries ─────────────────────────────────────
// NOTE: PreorderItems is not a LinqToDB [Association] — never use LoadWith on it. // NOTE: PreOrderItems is not a LinqToDB [Association] — never use LoadWith on it.
// Preorders and items are loaded separately and joined in memory below. // PreOrders and items are loaded separately and joined in memory below.
var allOrdersTask = _fruitBankDbContext.OrderDtos.GetAll(true).ToListAsync(); var allOrdersTask = _fruitBankDbContext.OrderDtos.GetAll(true).ToListAsync();
var allCreditsTask = _fruitBankDbContext.CustomerCredits.GetAll().ToListAsync(); var allCreditsTask = _fruitBankDbContext.CustomerCredits.GetAll().ToListAsync();
var allPreordersTask = _preorderDbContext.Preorders.GetAll().ToListAsync(); var allPreOrdersTask = _preorderDbContext.PreOrders.GetAll().ToListAsync();
var unprocessedDocsTask = _fruitBankDbContext.ShippingDocuments.GetAllNotMeasured(true).ToListAsync(); var unprocessedDocsTask = _fruitBankDbContext.ShippingDocuments.GetAllNotMeasured(true).ToListAsync();
await Task.WhenAll(allOrdersTask, allCreditsTask, allPreordersTask, unprocessedDocsTask); await Task.WhenAll(allOrdersTask, allCreditsTask, allPreOrdersTask, unprocessedDocsTask);
var allOrders = await allOrdersTask; var allOrders = await allOrdersTask;
var credits = await allCreditsTask; var credits = await allCreditsTask;
var unprocessedDocs = await unprocessedDocsTask; var unprocessedDocs = await unprocessedDocsTask;
// Filter pending preorders in memory — LinqToDB cannot translate enum comparisons to SQL // Filter pending preorders in memory — LinqToDB cannot translate enum comparisons to SQL
var pendingStatuses = new[] { PreorderStatus.Pending, PreorderStatus.PartiallyFulfilled }; var pendingStatuses = new[] { PreOrderStatus.Pending, PreOrderStatus.PartiallyFulfilled };
var pendingPreorders = (await allPreordersTask) var pendingPreOrders = (await allPreOrdersTask)
.Where(p => pendingStatuses.Contains(p.Status)) .Where(p => pendingStatuses.Contains(p.Status))
.ToList(); .ToList();
// ── Batch 1b: preorder items for pending preorders only ──────────── // ── Batch 1b: preorder items for pending preorders only ────────────
var pendingPreorderIds = pendingPreorders.Select(p => p.Id).ToList(); var pendingPreOrderIds = pendingPreOrders.Select(p => p.Id).ToList();
var pendingItems = pendingPreorderIds.Any() var pendingItems = pendingPreOrderIds.Any()
? await _preorderDbContext.PreorderItems.GetAll() ? await _preorderDbContext.PreOrderItems.GetAll()
.Where(i => pendingPreorderIds.Contains(i.PreorderId)) .Where(i => pendingPreOrderIds.Contains(i.PreOrderId))
.ToListAsync() .ToListAsync()
: new List<PreorderItem>(); : new List<PreOrderItem>();
// ── Today's orders (in-memory filter, same pattern as AICalculationService) // ── Today's orders (in-memory filter, same pattern as AICalculationService)
var todaysOrders = allOrders var todaysOrders = allOrders
@ -249,8 +249,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
return $"#{customerId}"; return $"#{customerId}";
} }
// ── Preorder customer names (may not appear in allOrders) ────────── // ── PreOrder customer names (may not appear in allOrders) ──────────
var preorderCustomerIds = pendingPreorders var preorderCustomerIds = pendingPreOrders
.Select(p => p.CustomerId).Distinct() .Select(p => p.CustomerId).Distinct()
.Where(id => allOrders.All(o => o.CustomerId != id)) .Where(id => allOrders.All(o => o.CustomerId != id))
.ToList(); .ToList();
@ -267,7 +267,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
preorderCustomerLookup[c.Id] = !string.IsNullOrEmpty(c.Company) ? c.Company : c.Email; preorderCustomerLookup[c.Id] = !string.IsNullOrEmpty(c.Company) ? c.Company : c.Email;
} }
string PreorderCustomerName(int customerId) string PreOrderCustomerName(int customerId)
=> preorderCustomerLookup.TryGetValue(customerId, out var name) => preorderCustomerLookup.TryGetValue(customerId, out var name)
? name ? name
: CustomerName(customerId); : CustomerName(customerId);
@ -361,15 +361,15 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
// Pending preorders older than 7 days // Pending preorders older than 7 days
var sevenDaysAgo = DateTime.UtcNow.AddDays(-7); var sevenDaysAgo = DateTime.UtcNow.AddDays(-7);
foreach (var p in pendingPreorders.Where(p => foreach (var p in pendingPreOrders.Where(p =>
p.Status == PreorderStatus.Pending && p.Status == PreOrderStatus.Pending &&
p.CreatedOnUtc < sevenDaysAgo)) p.CreatedOnUtc < sevenDaysAgo))
{ {
alerts.Add(new alerts.Add(new
{ {
type = "old_preorder", type = "old_preorder",
preorderId = p.Id, preorderId = p.Id,
company = PreorderCustomerName(p.CustomerId), company = PreOrderCustomerName(p.CustomerId),
createdAt = p.CreatedOnUtc.ToLocalTime().ToString("yyyy.MM.dd"), createdAt = p.CreatedOnUtc.ToLocalTime().ToString("yyyy.MM.dd"),
message = "Régi, nyitott előrendelés" message = "Régi, nyitott előrendelés"
}); });
@ -404,20 +404,20 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
// ───────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────
// Section 4: Pending preorders (items joined in memory) // Section 4: Pending preorders (items joined in memory)
// ───────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────
var preorderRows = pendingPreorders var preorderRows = pendingPreOrders
.OrderBy(p => p.CreatedOnUtc) .OrderBy(p => p.CreatedOnUtc)
.Select(p => .Select(p =>
{ {
var items = pendingItems.Where(i => i.PreorderId == p.Id).ToList(); var items = pendingItems.Where(i => i.PreOrderId == p.Id).ToList();
return new return new
{ {
id = p.Id, id = p.Id,
customerId = p.CustomerId, customerId = p.CustomerId,
company = PreorderCustomerName(p.CustomerId), company = PreOrderCustomerName(p.CustomerId),
status = p.Status.ToString(), status = p.Status.ToString(),
createdAt = p.CreatedOnUtc.ToLocalTime().ToString("yyyy.MM.dd"), createdAt = p.CreatedOnUtc.ToLocalTime().ToString("yyyy.MM.dd"),
itemCount = items.Count, itemCount = items.Count,
fulfilledCount = items.Count(i => i.Status == PreorderItemStatus.Fulfilled) fulfilledCount = items.Count(i => i.Status == PreOrderItemStatus.Fulfilled)
}; };
}) })
.ToList(); .ToList();
@ -443,7 +443,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
pipeline, pipeline,
alerts, alerts,
creditStatus = creditRows, creditStatus = creditRows,
pendingPreorders = preorderRows, pendingPreOrders = preorderRows,
unprocessedDocs = docRows unprocessedDocs = docRows
}); });
} }

View File

@ -1076,7 +1076,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
[HttpGet] [HttpGet]
[CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)] [CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)]
public virtual async Task<IActionResult> PreorderProductSearchAutoComplete(string term) public virtual async Task<IActionResult> PreOrderProductSearchAutoComplete(string term)
{ {
if (string.IsNullOrWhiteSpace(term) || term.Length < 2) if (string.IsNullOrWhiteSpace(term) || term.Length < 2)
return Json(new List<object>()); return Json(new List<object>());
@ -1088,13 +1088,13 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
// Load preorder window attributes in two batch queries // Load preorder window attributes in two batch queries
var gaStart = await _dbContext.GenericAttributes.Table var gaStart = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product) .Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowStart && ga.Key == FruitBankConst.PreOrderWindowStart
&& ga.StoreId == store.Id) && ga.StoreId == store.Id)
.ToListAsync(); .ToListAsync();
var gaEnd = await _dbContext.GenericAttributes.Table var gaEnd = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product) .Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowEnd && ga.Key == FruitBankConst.PreOrderWindowEnd
&& ga.StoreId == store.Id) && ga.StoreId == store.Id)
.ToListAsync(); .ToListAsync();

View File

@ -41,7 +41,7 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
private readonly FileStorageService _fileStorageService; private readonly FileStorageService _fileStorageService;
private readonly FruitBankAttributeService _fruitBankAttributeService; private readonly FruitBankAttributeService _fruitBankAttributeService;
private readonly IStoreContext _storeContext; private readonly IStoreContext _storeContext;
private readonly PreorderConversionService _preorderConversionService; private readonly PreOrderConversionService _preorderConversionService;
public FileManagerController( public FileManagerController(
IPermissionService permissionService, IPermissionService permissionService,
@ -55,7 +55,7 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
FileStorageService fileStorageService, FileStorageService fileStorageService,
FruitBankAttributeService fruitBankAttributeService, FruitBankAttributeService fruitBankAttributeService,
IStoreContext storeContext, IStoreContext storeContext,
PreorderConversionService preorderConversionService) PreOrderConversionService preorderConversionService)
{ {
_permissionService = permissionService; _permissionService = permissionService;
_aiApiService = aiApiService; _aiApiService = aiApiService;
@ -1140,12 +1140,12 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
try try
{ {
await _preorderConversionService await _preorderConversionService
.ConvertPreordersForProductsAsync(productIdsWithIncoming, shippingDocument.Id); .ConvertPreOrdersForProductsAsync(productIdsWithIncoming, shippingDocument.Id);
} }
catch (Exception convEx) catch (Exception convEx)
{ {
Console.Error.WriteLine( Console.Error.WriteLine(
$"[PreorderConversion] Error during conversion for document #{shippingDocument.Id}: {convEx.Message}"); $"[PreOrderConversion] Error during conversion for document #{shippingDocument.Id}: {convEx.Message}");
} }
}); });
} }

View File

@ -16,36 +16,36 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers;
[AuthorizeAdmin] [AuthorizeAdmin]
[Area(AreaNames.ADMIN)] [Area(AreaNames.ADMIN)]
[AutoValidateAntiforgeryToken] [AutoValidateAntiforgeryToken]
public class PreorderAdminController : BasePluginController public class PreOrderAdminController : BasePluginController
{ {
private readonly IPermissionService _permissionService; private readonly IPermissionService _permissionService;
private readonly PreorderDbContext _preorderDbContext; private readonly PreOrderDbContext _preorderDbContext;
private readonly FruitBankDbContext _dbContext; private readonly FruitBankDbContext _dbContext;
private readonly ICustomerService _customerService; private readonly ICustomerService _customerService;
private readonly PreorderConversionService _preorderConversionService; private readonly PreOrderConversionService _preorderConversionService;
private static readonly Dictionary<PreorderStatus, string> StatusLabels = new() private static readonly Dictionary<PreOrderStatus, string> StatusLabels = new()
{ {
{ PreorderStatus.Pending, "Függőben" }, { PreOrderStatus.Pending, "Függőben" },
{ PreorderStatus.Confirmed, "Megerősítve" }, { PreOrderStatus.Confirmed, "Megerősítve" },
{ PreorderStatus.PartiallyFulfilled, "Részben teljesítve" }, { PreOrderStatus.PartiallyFulfilled, "Részben teljesítve" },
{ PreorderStatus.Cancelled, "Törölve" } { PreOrderStatus.Cancelled, "Törölve" }
}; };
private static readonly Dictionary<PreorderItemStatus, string> ItemStatusLabels = new() private static readonly Dictionary<PreOrderItemStatus, string> ItemStatusLabels = new()
{ {
{ PreorderItemStatus.Pending, "Függőben" }, { PreOrderItemStatus.Pending, "Függőben" },
{ PreorderItemStatus.Fulfilled, "Teljesítve" }, { PreOrderItemStatus.Fulfilled, "Teljesítve" },
{ PreorderItemStatus.PartiallyFulfilled, "Részben" }, { PreOrderItemStatus.PartiallyFulfilled, "Részben" },
{ PreorderItemStatus.Dropped, "Ejtve" } { PreOrderItemStatus.Dropped, "Ejtve" }
}; };
public PreorderAdminController( public PreOrderAdminController(
IPermissionService permissionService, IPermissionService permissionService,
PreorderDbContext preorderDbContext, PreOrderDbContext preorderDbContext,
FruitBankDbContext dbContext, FruitBankDbContext dbContext,
ICustomerService customerService, ICustomerService customerService,
PreorderConversionService preorderConversionService) PreOrderConversionService preorderConversionService)
{ {
_permissionService = permissionService; _permissionService = permissionService;
_preorderDbContext = preorderDbContext; _preorderDbContext = preorderDbContext;
@ -57,20 +57,20 @@ public class PreorderAdminController : BasePluginController
// ── LIST PAGE ───────────────────────────────────────────────────────────── // ── LIST PAGE ─────────────────────────────────────────────────────────────
[HttpGet] [HttpGet]
[Route("Admin/Preorders")] [Route("Admin/PreOrders")]
public async Task<IActionResult> List() public async Task<IActionResult> List()
{ {
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return AccessDeniedView(); return AccessDeniedView();
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Preorder/List.cshtml"); return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/PreOrder/List.cshtml");
} }
// ── DATATABLES SERVER-SIDE ──────────────────────────────────────────────── // ── DATATABLES SERVER-SIDE ────────────────────────────────────────────────
[HttpPost] [HttpPost]
[Route("Admin/Preorders/PreorderList")] [Route("Admin/PreOrders/PreOrderList")]
public async Task<IActionResult> PreorderList() public async Task<IActionResult> PreOrderList()
{ {
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return Forbid(); return Forbid();
@ -87,11 +87,11 @@ public class PreorderAdminController : BasePluginController
var statusFilter = Request.Form["statusFilter"].FirstOrDefault()?.Trim() ?? ""; var statusFilter = Request.Form["statusFilter"].FirstOrDefault()?.Trim() ?? "";
// 1. All preorders with items — two queries // 1. All preorders with items — two queries
var preorders = await _preorderDbContext.Preorders.GetAll(false).ToListAsync(); var preorders = await _preorderDbContext.PreOrders.GetAll(false).ToListAsync();
var allItems = await _preorderDbContext.PreorderItems.GetAll().ToListAsync(); var allItems = await _preorderDbContext.PreOrderItems.GetAll().ToListAsync();
var itemsByPreorder = allItems var itemsByPreOrder = allItems
.GroupBy(i => i.PreorderId) .GroupBy(i => i.PreOrderId)
.ToDictionary(g => g.Key, g => g.ToList()); .ToDictionary(g => g.Key, g => g.ToList());
// 2. Customers — batch // 2. Customers — batch
@ -111,7 +111,7 @@ public class PreorderAdminController : BasePluginController
var rows = preorders.Select(p => var rows = preorders.Select(p =>
{ {
customerById.TryGetValue(p.CustomerId, out var c); customerById.TryGetValue(p.CustomerId, out var c);
var items = itemsByPreorder.TryGetValue(p.Id, out var its) ? its : new(); var items = itemsByPreOrder.TryGetValue(p.Id, out var its) ? its : new();
// Derive status from quantities rather than relying on the enum read // Derive status from quantities rather than relying on the enum read
var fulfilledCount = items.Count(i => i.FulfilledQuantity > 0); var fulfilledCount = items.Count(i => i.FulfilledQuantity > 0);
@ -123,13 +123,13 @@ public class PreorderAdminController : BasePluginController
// otherwise infer from quantities // otherwise infer from quantities
var effectiveStatus = (int)p.Status != 0 var effectiveStatus = (int)p.Status != 0
? p.Status ? p.Status
: allFulfilled ? PreorderStatus.Confirmed : allFulfilled ? PreOrderStatus.Confirmed
: anyFulfilled ? PreorderStatus.PartiallyFulfilled : anyFulfilled ? PreOrderStatus.PartiallyFulfilled
: PreorderStatus.Pending; : PreOrderStatus.Pending;
return new PreorderListRow return new PreOrderListRow
{ {
PreorderId = p.Id, PreOrderId = p.Id,
CustomerId = p.CustomerId, CustomerId = p.CustomerId,
CustomerName = c != null ? $"{c.FirstName} {c.LastName}".Trim() : $"#{p.CustomerId}", CustomerName = c != null ? $"{c.FirstName} {c.LastName}".Trim() : $"#{p.CustomerId}",
CustomerEmail = c?.Email ?? string.Empty, CustomerEmail = c?.Email ?? string.Empty,
@ -146,7 +146,7 @@ public class PreorderAdminController : BasePluginController
int recordsTotal = rows.Count; int recordsTotal = rows.Count;
// 5. Filter by status // 5. Filter by status
if (!string.IsNullOrWhiteSpace(statusFilter) && Enum.TryParse<PreorderStatus>(statusFilter, out var statusEnum)) if (!string.IsNullOrWhiteSpace(statusFilter) && Enum.TryParse<PreOrderStatus>(statusFilter, out var statusEnum))
rows = rows.Where(r => r.Status == statusEnum).ToList(); rows = rows.Where(r => r.Status == statusEnum).ToList();
// 6. Global search // 6. Global search
@ -154,7 +154,7 @@ public class PreorderAdminController : BasePluginController
rows = rows.Where(r => rows = rows.Where(r =>
r.CustomerName.Contains(globalSearch, StringComparison.OrdinalIgnoreCase) || r.CustomerName.Contains(globalSearch, StringComparison.OrdinalIgnoreCase) ||
r.CustomerEmail.Contains(globalSearch, StringComparison.OrdinalIgnoreCase) || r.CustomerEmail.Contains(globalSearch, StringComparison.OrdinalIgnoreCase) ||
r.PreorderId.ToString().Contains(globalSearch) r.PreOrderId.ToString().Contains(globalSearch)
).ToList(); ).ToList();
int recordsFiltered = rows.Count; int recordsFiltered = rows.Count;
@ -178,17 +178,17 @@ public class PreorderAdminController : BasePluginController
// ── DETAIL PAGE ─────────────────────────────────────────────────────────── // ── DETAIL PAGE ───────────────────────────────────────────────────────────
[HttpGet] [HttpGet]
[Route("Admin/Preorders/Detail/{id:int}")] [Route("Admin/PreOrders/Detail/{id:int}")]
public async Task<IActionResult> Detail(int id) public async Task<IActionResult> Detail(int id)
{ {
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return AccessDeniedView(); return AccessDeniedView();
var preorder = await _preorderDbContext.Preorders.GetByIdAsync(id, loadRelations: false); var preorder = await _preorderDbContext.PreOrders.GetByIdAsync(id, loadRelations: false);
if (preorder == null) return NotFound(); if (preorder == null) return NotFound();
var items = await _preorderDbContext.PreorderItems var items = await _preorderDbContext.PreOrderItems
.GetAllByPreorderIdAsync(id) .GetAllByPreOrderIdAsync(id)
.ToListAsync(); .ToListAsync();
var customer = await _customerService.GetCustomerByIdAsync(preorder.CustomerId); var customer = await _customerService.GetCustomerByIdAsync(preorder.CustomerId);
@ -204,9 +204,9 @@ public class PreorderAdminController : BasePluginController
// Use preorder.OrderId directly — stored on the entity at conversion time // Use preorder.OrderId directly — stored on the entity at conversion time
int? linkedOrderId = preorder.OrderId; int? linkedOrderId = preorder.OrderId;
var model = new PreorderDetailModel var model = new PreOrderDetailModel
{ {
PreorderId = preorder.Id, PreOrderId = preorder.Id,
CustomerId = preorder.CustomerId, CustomerId = preorder.CustomerId,
CustomerName = customer != null ? $"{customer.FirstName} {customer.LastName}".Trim() : $"#{preorder.CustomerId}", CustomerName = customer != null ? $"{customer.FirstName} {customer.LastName}".Trim() : $"#{preorder.CustomerId}",
CustomerEmail = customer?.Email ?? string.Empty, CustomerEmail = customer?.Email ?? string.Empty,
@ -222,15 +222,15 @@ public class PreorderAdminController : BasePluginController
// Derive item status from quantities — enum reads unreliable in LinqToDB // Derive item status from quantities — enum reads unreliable in LinqToDB
var derivedStatus = i.FulfilledQuantity == 0 var derivedStatus = i.FulfilledQuantity == 0
? PreorderItemStatus.Pending ? PreOrderItemStatus.Pending
: i.FulfilledQuantity >= i.RequestedQuantity : i.FulfilledQuantity >= i.RequestedQuantity
? PreorderItemStatus.Fulfilled ? PreOrderItemStatus.Fulfilled
: PreorderItemStatus.PartiallyFulfilled; : PreOrderItemStatus.PartiallyFulfilled;
// If DB enum read as non-zero, prefer it; otherwise use derived // If DB enum read as non-zero, prefer it; otherwise use derived
var effectiveItemStatus = (int)i.Status != 0 ? i.Status : derivedStatus; var effectiveItemStatus = (int)i.Status != 0 ? i.Status : derivedStatus;
return new PreorderDetailItemRow return new PreOrderDetailItemRow
{ {
ItemId = i.Id, ItemId = i.Id,
ProductId = i.ProductId, ProductId = i.ProductId,
@ -245,14 +245,14 @@ public class PreorderAdminController : BasePluginController
}).ToList() }).ToList()
}; };
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Preorder/Detail.cshtml", model); return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/PreOrder/Detail.cshtml", model);
} }
// ── CREATE (admin phone order) ─────────────────────────────────────────── // ── CREATE (admin phone order) ───────────────────────────────────────────
[HttpPost] [HttpPost]
[Route("Admin/Preorders/CreatePreorder")] [Route("Admin/PreOrders/CreatePreOrder")]
public async Task<IActionResult> CreatePreorder( public async Task<IActionResult> CreatePreOrder(
int customerId, int customerId,
string deliveryDateTime, string deliveryDateTime,
string? customerNote, string? customerNote,
@ -288,7 +288,7 @@ public class PreorderAdminController : BasePluginController
.Select(g => g.StoreId).FirstOrDefaultAsync(); .Select(g => g.StoreId).FirstOrDefaultAsync();
storeId = gaStore > 0 ? gaStore : 1; storeId = gaStore > 0 ? gaStore : 1;
var preorder = new Preorder var preorder = new PreOrder
{ {
CustomerId = customerId, CustomerId = customerId,
StoreId = storeId, StoreId = storeId,
@ -296,13 +296,13 @@ public class PreorderAdminController : BasePluginController
CustomerNote = customerNote?.Trim() CustomerNote = customerNote?.Trim()
}; };
var items = new List<PreorderItem>(); var items = new List<PreOrderItem>();
foreach (var pi in productItems.Where(p => p.quantity > 0)) foreach (var pi in productItems.Where(p => p.quantity > 0))
{ {
var product = await _dbContext.Products.GetByIdAsync(pi.id); var product = await _dbContext.Products.GetByIdAsync(pi.id);
if (product == null || product.Deleted || !product.Published) continue; if (product == null || product.Deleted || !product.Published) continue;
items.Add(new PreorderItem items.Add(new PreOrderItem
{ {
ProductId = pi.id, ProductId = pi.id,
RequestedQuantity = pi.quantity, RequestedQuantity = pi.quantity,
@ -313,18 +313,18 @@ public class PreorderAdminController : BasePluginController
if (!items.Any()) if (!items.Any())
return Json(new { success = false, error = "Nincs érvényes termék az előrendelésben" }); return Json(new { success = false, error = "Nincs érvényes termék az előrendelésben" });
var saved = await _preorderDbContext.InsertPreorderAsync(preorder, items); var saved = await _preorderDbContext.InsertPreOrderAsync(preorder, items);
Console.WriteLine($"[Admin] Created preorder #{saved.Id} for customer #{customerId} " + Console.WriteLine($"[Admin] Created preorder #{saved.Id} for customer #{customerId} " +
$"by admin, {items.Count} items, delivery {deliveryDate:u}"); $"by admin, {items.Count} items, delivery {deliveryDate:u}");
// Immediately check if any items can be fulfilled from current stock — // Immediately check if any items can be fulfilled from current stock —
// same inline conversion as the customer-facing PlacePreorder endpoint. // same inline conversion as the customer-facing PlacePreOrder endpoint.
var productIds = items.Select(i => i.ProductId).Distinct().ToList(); var productIds = items.Select(i => i.ProductId).Distinct().ToList();
await _preorderConversionService.ConvertPreordersForProductsAsync(productIds, 0); await _preorderConversionService.ConvertPreOrdersForProductsAsync(productIds, 0);
// Re-read to pick up OrderId if conversion created a real order // Re-read to pick up OrderId if conversion created a real order
var refreshed = await _preorderDbContext.Preorders.GetByIdAsync(saved.Id); var refreshed = await _preorderDbContext.PreOrders.GetByIdAsync(saved.Id);
return Json(new { success = true, preorderId = saved.Id, orderId = refreshed?.OrderId }); return Json(new { success = true, preorderId = saved.Id, orderId = refreshed?.OrderId });
} }
@ -347,27 +347,27 @@ public class PreorderAdminController : BasePluginController
// ── CANCEL ─────────────────────────────────────────────────────────── // ── CANCEL ───────────────────────────────────────────────────────────
[HttpPost] [HttpPost]
[Route("Admin/Preorders/Cancel/{id:int}")] [Route("Admin/PreOrders/Cancel/{id:int}")]
public async Task<IActionResult> Cancel(int id) public async Task<IActionResult> Cancel(int id)
{ {
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return Json(new { success = false, error = "Access denied" }); return Json(new { success = false, error = "Access denied" });
var preorder = await _preorderDbContext.Preorders.GetByIdAsync(id); var preorder = await _preorderDbContext.PreOrders.GetByIdAsync(id);
if (preorder == null) if (preorder == null)
return Json(new { success = false, error = "Preorder not found" }); return Json(new { success = false, error = "PreOrder not found" });
if (preorder.Status != PreorderStatus.Pending) if (preorder.Status != PreOrderStatus.Pending)
return Json(new { success = false, error = "Only pending preorders can be cancelled" }); return Json(new { success = false, error = "Only pending preorders can be cancelled" });
await _preorderDbContext.CancelPreorderAsync(id); await _preorderDbContext.CancelPreOrderAsync(id);
return Json(new { success = true }); return Json(new { success = true });
} }
// ── DEMAND LIST ─────────────────────────────────────────────────────────── // ── DEMAND LIST ───────────────────────────────────────────────────────────
[HttpPost] [HttpPost]
[Route("Admin/Preorders/DemandList")] [Route("Admin/PreOrders/DemandList")]
public async Task<IActionResult> DemandList(bool openOnly = true) public async Task<IActionResult> DemandList(bool openOnly = true)
{ {
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
@ -380,24 +380,24 @@ public class PreorderAdminController : BasePluginController
openOnly = openOnlyParam != "false"; openOnly = openOnlyParam != "false";
// Fetch all preorder items + preorders in two queries // Fetch all preorder items + preorders in two queries
var allItems = await _preorderDbContext.PreorderItems.GetAll().ToListAsync(); var allItems = await _preorderDbContext.PreOrderItems.GetAll().ToListAsync();
var allPreorders = await _preorderDbContext.Preorders.GetAll(false).ToListAsync(); var allPreOrders = await _preorderDbContext.PreOrders.GetAll(false).ToListAsync();
// For "open only": include only items from preorders that still have // For "open only": include only items from preorders that still have
// unfulfilled demand (FulfilledQuantity < RequestedQuantity). // unfulfilled demand (FulfilledQuantity < RequestedQuantity).
// We use quantities rather than Status enum (enum reads unreliable). // We use quantities rather than Status enum (enum reads unreliable).
IEnumerable<PreorderItem> items = allItems; IEnumerable<PreOrderItem> items = allItems;
if (openOnly) if (openOnly)
{ {
// Open preorders: those where at least one item still needs fulfillment // Open preorders: those where at least one item still needs fulfillment
var openPreorderIds = allPreorders var openPreOrderIds = allPreOrders
.Where(p => allItems .Where(p => allItems
.Where(i => i.PreorderId == p.Id) .Where(i => i.PreOrderId == p.Id)
.Any(i => i.FulfilledQuantity < i.RequestedQuantity)) .Any(i => i.FulfilledQuantity < i.RequestedQuantity))
.Select(p => p.Id) .Select(p => p.Id)
.ToHashSet(); .ToHashSet();
items = allItems.Where(i => openPreorderIds.Contains(i.PreorderId)); items = allItems.Where(i => openPreOrderIds.Contains(i.PreOrderId));
} }
// Group by product // Group by product
@ -409,7 +409,7 @@ public class PreorderAdminController : BasePluginController
TotalRequested = g.Sum(i => i.RequestedQuantity), TotalRequested = g.Sum(i => i.RequestedQuantity),
TotalFulfilled = g.Sum(i => i.FulfilledQuantity), TotalFulfilled = g.Sum(i => i.FulfilledQuantity),
TotalUnfulfilled = g.Sum(i => i.RequestedQuantity - i.FulfilledQuantity), TotalUnfulfilled = g.Sum(i => i.RequestedQuantity - i.FulfilledQuantity),
PreorderCount = g.Select(i => i.PreorderId).Distinct().Count(), PreOrderCount = g.Select(i => i.PreOrderId).Distinct().Count(),
AvgUnitPrice = g.Where(i => i.UnitPriceInclTax > 0).Any() AvgUnitPrice = g.Where(i => i.UnitPriceInclTax > 0).Any()
? g.Where(i => i.UnitPriceInclTax > 0).Average(i => i.UnitPriceInclTax) ? g.Where(i => i.UnitPriceInclTax > 0).Average(i => i.UnitPriceInclTax)
: 0m : 0m
@ -429,7 +429,7 @@ public class PreorderAdminController : BasePluginController
var rows = grouped.Select(g => var rows = grouped.Select(g =>
{ {
productById.TryGetValue(g.ProductId, out var dto); productById.TryGetValue(g.ProductId, out var dto);
return new PreorderDemandRow return new PreOrderDemandRow
{ {
ProductId = g.ProductId, ProductId = g.ProductId,
ProductName = dto?.Name ?? $"Product #{g.ProductId}", ProductName = dto?.Name ?? $"Product #{g.ProductId}",
@ -438,7 +438,7 @@ public class PreorderAdminController : BasePluginController
TotalRequested = g.TotalRequested, TotalRequested = g.TotalRequested,
TotalFulfilled = g.TotalFulfilled, TotalFulfilled = g.TotalFulfilled,
TotalUnfulfilled = g.TotalUnfulfilled, TotalUnfulfilled = g.TotalUnfulfilled,
PreorderCount = g.PreorderCount, PreOrderCount = g.PreOrderCount,
AvgUnitPrice = Math.Round(g.AvgUnitPrice, 0) AvgUnitPrice = Math.Round(g.AvgUnitPrice, 0)
}; };
}).ToList(); }).ToList();

View File

@ -16,14 +16,14 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers;
[AuthorizeAdmin] [AuthorizeAdmin]
[Area(AreaNames.ADMIN)] [Area(AreaNames.ADMIN)]
[AutoValidateAntiforgeryToken] [AutoValidateAntiforgeryToken]
public class PreorderAvailabilityController : BasePluginController public class PreOrderAvailabilityController : BasePluginController
{ {
private readonly IPermissionService _permissionService; private readonly IPermissionService _permissionService;
private readonly FruitBankDbContext _dbContext; private readonly FruitBankDbContext _dbContext;
private readonly FruitBankAttributeService _fruitBankAttributeService; private readonly FruitBankAttributeService _fruitBankAttributeService;
private readonly IStoreContext _storeContext; private readonly IStoreContext _storeContext;
public PreorderAvailabilityController( public PreOrderAvailabilityController(
IPermissionService permissionService, IPermissionService permissionService,
FruitBankDbContext dbContext, FruitBankDbContext dbContext,
FruitBankAttributeService fruitBankAttributeService, FruitBankAttributeService fruitBankAttributeService,
@ -38,19 +38,19 @@ public class PreorderAvailabilityController : BasePluginController
// ── INDEX ───────────────────────────────────────────────────────────────── // ── INDEX ─────────────────────────────────────────────────────────────────
[HttpGet] [HttpGet]
[Route("Admin/PreorderAvailability")] [Route("Admin/PreOrderAvailability")]
public async Task<IActionResult> Index() public async Task<IActionResult> Index()
{ {
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return AccessDeniedView(); return AccessDeniedView();
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/PreorderAvailability/Index.cshtml"); return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/PreOrderAvailability/Index.cshtml");
} }
// ── ALL PRODUCTS — DataTables server-side ───────────────────────────────── // ── ALL PRODUCTS — DataTables server-side ─────────────────────────────────
[HttpPost] [HttpPost]
[Route("Admin/PreorderAvailability/ProductList")] [Route("Admin/PreOrderAvailability/ProductList")]
public async Task<IActionResult> ProductList() public async Task<IActionResult> ProductList()
{ {
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
@ -74,13 +74,13 @@ public class PreorderAvailabilityController : BasePluginController
// 2. All preorder window generic attributes — two queries, no N+1 // 2. All preorder window generic attributes — two queries, no N+1
var gaStart = await _dbContext.GenericAttributes.Table var gaStart = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product) .Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowStart && ga.Key == FruitBankConst.PreOrderWindowStart
&& ga.StoreId == storeId) && ga.StoreId == storeId)
.ToListAsync(); .ToListAsync();
var gaEnd = await _dbContext.GenericAttributes.Table var gaEnd = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product) .Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowEnd && ga.Key == FruitBankConst.PreOrderWindowEnd
&& ga.StoreId == storeId) && ga.StoreId == storeId)
.ToListAsync(); .ToListAsync();
@ -98,7 +98,7 @@ public class PreorderAvailabilityController : BasePluginController
var hasStart = startByProduct.ContainsKey(p.Id); var hasStart = startByProduct.ContainsKey(p.Id);
var hasEnd = endByProduct.ContainsKey(p.Id); var hasEnd = endByProduct.ContainsKey(p.Id);
return new PreorderAvailabilityRow return new PreOrderAvailabilityRow
{ {
ProductId = p.Id, ProductId = p.Id,
ProductName = p.Name, ProductName = p.Name,
@ -131,7 +131,7 @@ public class PreorderAvailabilityController : BasePluginController
// ── AVAILABLE TODAY — DataTables server-side ────────────────────────────── // ── AVAILABLE TODAY — DataTables server-side ──────────────────────────────
[HttpPost] [HttpPost]
[Route("Admin/PreorderAvailability/AvailableTodayList")] [Route("Admin/PreOrderAvailability/AvailableTodayList")]
public async Task<IActionResult> AvailableTodayList() public async Task<IActionResult> AvailableTodayList()
{ {
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
@ -153,13 +153,13 @@ public class PreorderAvailabilityController : BasePluginController
var gaStart = await _dbContext.GenericAttributes.Table var gaStart = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product) .Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowStart && ga.Key == FruitBankConst.PreOrderWindowStart
&& ga.StoreId == storeId) && ga.StoreId == storeId)
.ToListAsync(); .ToListAsync();
var gaEnd = await _dbContext.GenericAttributes.Table var gaEnd = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product) .Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowEnd && ga.Key == FruitBankConst.PreOrderWindowEnd
&& ga.StoreId == storeId) && ga.StoreId == storeId)
.ToListAsync(); .ToListAsync();
@ -179,7 +179,7 @@ public class PreorderAvailabilityController : BasePluginController
{ {
DateTime.TryParse(startByProduct[p.Id], out var ws); DateTime.TryParse(startByProduct[p.Id], out var ws);
DateTime.TryParse(endByProduct[p.Id], out var we); DateTime.TryParse(endByProduct[p.Id], out var we);
return new PreorderAvailabilityRow return new PreOrderAvailabilityRow
{ {
ProductId = p.Id, ProductId = p.Id,
ProductName = p.Name, ProductName = p.Name,
@ -201,7 +201,7 @@ public class PreorderAvailabilityController : BasePluginController
// ── SAVE WINDOW DATES for a product ─────────────────────────────────────── // ── SAVE WINDOW DATES for a product ───────────────────────────────────────
[HttpPost] [HttpPost]
[Route("Admin/PreorderAvailability/SaveWindow")] [Route("Admin/PreOrderAvailability/SaveWindow")]
public async Task<IActionResult> SaveWindow(int productId, string? windowStart, string? windowEnd) public async Task<IActionResult> SaveWindow(int productId, string? windowStart, string? windowEnd)
{ {
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
@ -215,13 +215,13 @@ public class PreorderAvailabilityController : BasePluginController
if (string.IsNullOrWhiteSpace(windowStart)) if (string.IsNullOrWhiteSpace(windowStart))
{ {
await _fruitBankAttributeService await _fruitBankAttributeService
.DeleteGenericAttributeAsync<Product>(productId, FruitBankConst.PreorderWindowStart, storeId); .DeleteGenericAttributeAsync<Product>(productId, FruitBankConst.PreOrderWindowStart, storeId);
} }
else if (DateTime.TryParse(windowStart, out var ws)) else if (DateTime.TryParse(windowStart, out var ws))
{ {
await _fruitBankAttributeService await _fruitBankAttributeService
.InsertOrUpdateGenericAttributeAsync<Product, DateTime>( .InsertOrUpdateGenericAttributeAsync<Product, DateTime>(
productId, FruitBankConst.PreorderWindowStart, ws.Date, storeId); productId, FruitBankConst.PreOrderWindowStart, ws.Date, storeId);
} }
else return Json(new { success = false, error = $"Invalid start date: {windowStart}" }); else return Json(new { success = false, error = $"Invalid start date: {windowStart}" });
@ -229,13 +229,13 @@ public class PreorderAvailabilityController : BasePluginController
if (string.IsNullOrWhiteSpace(windowEnd)) if (string.IsNullOrWhiteSpace(windowEnd))
{ {
await _fruitBankAttributeService await _fruitBankAttributeService
.DeleteGenericAttributeAsync<Product>(productId, FruitBankConst.PreorderWindowEnd, storeId); .DeleteGenericAttributeAsync<Product>(productId, FruitBankConst.PreOrderWindowEnd, storeId);
} }
else if (DateTime.TryParse(windowEnd, out var we)) else if (DateTime.TryParse(windowEnd, out var we))
{ {
await _fruitBankAttributeService await _fruitBankAttributeService
.InsertOrUpdateGenericAttributeAsync<Product, DateTime>( .InsertOrUpdateGenericAttributeAsync<Product, DateTime>(
productId, FruitBankConst.PreorderWindowEnd, we.Date, storeId); productId, FruitBankConst.PreOrderWindowEnd, we.Date, storeId);
} }
else return Json(new { success = false, error = $"Invalid end date: {windowEnd}" }); else return Json(new { success = false, error = $"Invalid end date: {windowEnd}" });

View File

@ -2,37 +2,37 @@ using FruitBank.Common.Enums;
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models; namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models;
public class PreorderListRow public class PreOrderListRow
{ {
public int PreorderId { get; set; } public int PreOrderId { get; set; }
public int CustomerId { get; set; } public int CustomerId { get; set; }
public string CustomerName { get; set; } = string.Empty; public string CustomerName { get; set; } = string.Empty;
public string CustomerEmail { get; set; } = string.Empty; public string CustomerEmail { get; set; } = string.Empty;
public string DateOfReceipt { get; set; } = string.Empty; // formatted public string DateOfReceipt { get; set; } = string.Empty; // formatted
public string CreatedOnUtc { get; set; } = string.Empty; // formatted public string CreatedOnUtc { get; set; } = string.Empty; // formatted
public PreorderStatus Status { get; set; } public PreOrderStatus Status { get; set; }
public string StatusLabel { get; set; } = string.Empty; public string StatusLabel { get; set; } = string.Empty;
public int ItemCount { get; set; } public int ItemCount { get; set; }
public int FulfilledCount { get; set; } public int FulfilledCount { get; set; }
public int? OrderId { get; set; } // linked real order, if created public int? OrderId { get; set; } // linked real order, if created
} }
public class PreorderDetailModel public class PreOrderDetailModel
{ {
public int PreorderId { get; set; } public int PreOrderId { get; set; }
public int CustomerId { get; set; } public int CustomerId { get; set; }
public string CustomerName { get; set; } = string.Empty; public string CustomerName { get; set; } = string.Empty;
public string CustomerEmail { get; set; } = string.Empty; public string CustomerEmail { get; set; } = string.Empty;
public string DateOfReceipt { get; set; } = string.Empty; public string DateOfReceipt { get; set; } = string.Empty;
public string CreatedOnUtc { get; set; } = string.Empty; public string CreatedOnUtc { get; set; } = string.Empty;
public string UpdatedOnUtc { get; set; } = string.Empty; public string UpdatedOnUtc { get; set; } = string.Empty;
public PreorderStatus Status { get; set; } public PreOrderStatus Status { get; set; }
public string? CustomerNote { get; set; } public string? CustomerNote { get; set; }
public int? OrderId { get; set; } public int? OrderId { get; set; }
public List<PreorderDetailItemRow> Items { get; set; } = new(); public List<PreOrderDetailItemRow> Items { get; set; } = new();
} }
public class PreorderDetailItemRow public class PreOrderDetailItemRow
{ {
public int ItemId { get; set; } public int ItemId { get; set; }
public int ProductId { get; set; } public int ProductId { get; set; }
@ -41,11 +41,11 @@ public class PreorderDetailItemRow
public int RequestedQuantity { get; set; } public int RequestedQuantity { get; set; }
public int FulfilledQuantity { get; set; } public int FulfilledQuantity { get; set; }
public decimal UnitPriceInclTax { get; set; } public decimal UnitPriceInclTax { get; set; }
public PreorderItemStatus Status { get; set; } public PreOrderItemStatus Status { get; set; }
public string StatusLabel { get; set; } = string.Empty; public string StatusLabel { get; set; } = string.Empty;
} }
public class PreorderDemandRow public class PreOrderDemandRow
{ {
public int ProductId { get; set; } public int ProductId { get; set; }
public string ProductName { get; set; } = string.Empty; public string ProductName { get; set; } = string.Empty;
@ -54,6 +54,6 @@ public class PreorderDemandRow
public int TotalRequested { get; set; } // sum of RequestedQuantity public int TotalRequested { get; set; } // sum of RequestedQuantity
public int TotalFulfilled { get; set; } // sum of FulfilledQuantity public int TotalFulfilled { get; set; } // sum of FulfilledQuantity
public int TotalUnfulfilled { get; set; } // TotalRequested - TotalFulfilled public int TotalUnfulfilled { get; set; } // TotalRequested - TotalFulfilled
public int PreorderCount { get; set; } // distinct preorders containing this product public int PreOrderCount { get; set; } // distinct preorders containing this product
public decimal AvgUnitPrice { get; set; } // average snapshot price public decimal AvgUnitPrice { get; set; } // average snapshot price
} }

View File

@ -1,6 +1,6 @@
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models; namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models;
public class PreorderAvailabilityRow public class PreOrderAvailabilityRow
{ {
public int ProductId { get; set; } public int ProductId { get; set; }
public string ProductName { get; set; } = string.Empty; public string ProductName { get; set; } = string.Empty;

View File

@ -1,23 +1,23 @@
@model Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.PreorderDetailModel @model Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.PreOrderDetailModel
@using FruitBank.Common.Enums @using FruitBank.Common.Enums
@{ @{
ViewBag.PageTitle = $"Előrendelés #{Model.PreorderId}"; ViewBag.PageTitle = $"Előrendelés #{Model.PreOrderId}";
Layout = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/_FruitBankAdminLayout.cshtml"; Layout = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/_FruitBankAdminLayout.cshtml";
var statusClass = Model.Status switch var statusClass = Model.Status switch
{ {
PreorderStatus.Confirmed => "po-status-confirmed", PreOrderStatus.Confirmed => "po-status-confirmed",
PreorderStatus.PartiallyFulfilled => "po-status-partial", PreOrderStatus.PartiallyFulfilled => "po-status-partial",
PreorderStatus.Cancelled => "po-status-cancelled", PreOrderStatus.Cancelled => "po-status-cancelled",
_ => "po-status-pending" _ => "po-status-pending"
}; };
var statusLabel = Model.Status switch var statusLabel = Model.Status switch
{ {
PreorderStatus.Confirmed => "Megerősítve", PreOrderStatus.Confirmed => "Megerősítve",
PreorderStatus.PartiallyFulfilled => "Részben teljesítve", PreOrderStatus.PartiallyFulfilled => "Részben teljesítve",
PreorderStatus.Cancelled => "Törölve", PreOrderStatus.Cancelled => "Törölve",
_ => "Függőben" _ => "Függőben"
}; };
} }
@ -46,14 +46,14 @@
</style> </style>
<!-- Back link --> <!-- Back link -->
<a href="/Admin/Preorders" class="btn btn-default btn-sm mb-3"> <a href="/Admin/PreOrders" class="btn btn-default btn-sm mb-3">
<i class="fas fa-arrow-left"></i> Vissza a listához <i class="fas fa-arrow-left"></i> Vissza a listához
</a> </a>
<div class="content-header clearfix"> <div class="content-header clearfix">
<h1 class="float-left"> <h1 class="float-left">
<i class="fas fa-calendar-plus" style="color:#2d7a3a;"></i> <i class="fas fa-calendar-plus" style="color:#2d7a3a;"></i>
Előrendelés <strong>#@Model.PreorderId</strong> Előrendelés <strong>#@Model.PreOrderId</strong>
<span class="@statusClass ml-2">@statusLabel</span> <span class="@statusClass ml-2">@statusLabel</span>
</h1> </h1>
<div class="float-right"> <div class="float-right">
@ -63,7 +63,7 @@
<i class="fas fa-external-link-alt"></i> Rendelés #@Model.OrderId <i class="fas fa-external-link-alt"></i> Rendelés #@Model.OrderId
</a> </a>
} }
@if (Model.Status == PreorderStatus.Pending) @if (Model.Status == PreOrderStatus.Pending)
{ {
<button id="cancelBtn" class="btn btn-danger btn-sm ml-2"> <button id="cancelBtn" class="btn btn-danger btn-sm ml-2">
<i class="fas fa-times"></i> Visszavonás <i class="fas fa-times"></i> Visszavonás
@ -112,10 +112,10 @@
<div class="card-header"> <div class="card-header">
<strong>Tételek (@Model.Items.Count)</strong> <strong>Tételek (@Model.Items.Count)</strong>
@{ @{
var fulfilled = Model.Items.Count(i => i.Status == PreorderItemStatus.Fulfilled); var fulfilled = Model.Items.Count(i => i.Status == PreOrderItemStatus.Fulfilled);
var partial = Model.Items.Count(i => i.Status == PreorderItemStatus.PartiallyFulfilled); var partial = Model.Items.Count(i => i.Status == PreOrderItemStatus.PartiallyFulfilled);
var dropped = Model.Items.Count(i => i.Status == PreorderItemStatus.Dropped); var dropped = Model.Items.Count(i => i.Status == PreOrderItemStatus.Dropped);
var pending = Model.Items.Count(i => i.Status == PreorderItemStatus.Pending); var pending = Model.Items.Count(i => i.Status == PreOrderItemStatus.Pending);
} }
<span class="ml-2 text-muted" style="font-size:13px;"> <span class="ml-2 text-muted" style="font-size:13px;">
@if (fulfilled > 0) { <span class="badge badge-success">@fulfilled teljesítve</span> } @if (fulfilled > 0) { <span class="badge badge-success">@fulfilled teljesítve</span> }
@ -142,9 +142,9 @@
{ {
var rowClass = item.Status switch var rowClass = item.Status switch
{ {
PreorderItemStatus.Fulfilled => "item-fulfilled", PreOrderItemStatus.Fulfilled => "item-fulfilled",
PreorderItemStatus.PartiallyFulfilled => "item-partial", PreOrderItemStatus.PartiallyFulfilled => "item-partial",
PreorderItemStatus.Dropped => "item-dropped", PreOrderItemStatus.Dropped => "item-dropped",
_ => "item-pending" _ => "item-pending"
}; };
var pct = item.RequestedQuantity > 0 var pct = item.RequestedQuantity > 0
@ -188,7 +188,7 @@
</tbody> </tbody>
@{ @{
var totalEstimated = Model.Items var totalEstimated = Model.Items
.Where(i => !i.IsMeasurable && (i.Status == PreorderItemStatus.Fulfilled || i.Status == PreorderItemStatus.PartiallyFulfilled)) .Where(i => !i.IsMeasurable && (i.Status == PreOrderItemStatus.Fulfilled || i.Status == PreOrderItemStatus.PartiallyFulfilled))
.Sum(i => i.UnitPriceInclTax * i.FulfilledQuantity); .Sum(i => i.UnitPriceInclTax * i.FulfilledQuantity);
} }
<tfoot> <tfoot>
@ -205,19 +205,19 @@
</div> </div>
</section> </section>
@if (Model.Status == PreorderStatus.Pending) @if (Model.Status == PreOrderStatus.Pending)
{ {
<script> <script>
$(function () { $(function () {
$('#cancelBtn').click(function () { $('#cancelBtn').click(function () {
if (!confirm('Biztosan visszavonod ezt az előrendelést? Ez a művelet nem visszafordítható.')) return; if (!confirm('Biztosan visszavonod ezt az előrendelést? Ez a művelet nem visszafordítható.')) return;
$.ajax({ $.ajax({
url : '/Admin/Preorders/Cancel/@Model.PreorderId', url : '/Admin/PreOrders/Cancel/@Model.PreOrderId',
type : 'POST', type : 'POST',
data : { __RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val() }, data : { __RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val() },
success: function (res) { success: function (res) {
if (res.success) { if (res.success) {
location.href = '/Admin/Preorders'; location.href = '/Admin/PreOrders';
} else { } else {
alert('Hiba: ' + (res.error || 'Ismeretlen hiba')); alert('Hiba: ' + (res.error || 'Ismeretlen hiba'));
} }

View File

@ -1,6 +1,6 @@
@{ @{
ViewBag.PageTitle = "Előrendelések"; ViewBag.PageTitle = "Előrendelések";
NopHtml.SetActiveMenuItemSystemName("Preorders.List"); NopHtml.SetActiveMenuItemSystemName("PreOrders.List");
Layout = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/_FruitBankAdminLayout.cshtml"; Layout = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/_FruitBankAdminLayout.cshtml";
} }
@ -194,7 +194,7 @@ $(function () {
return diff >= 0 && diff <= 4; return diff >= 0 && diff <= 4;
} }
// ── Preorder list grid ────────────────────────────────────────────────── // ── PreOrder list grid ──────────────────────────────────────────────────
var poTable = $('#po-grid').DataTable({ var poTable = $('#po-grid').DataTable({
serverSide: true, processing: true, pageLength: 25, serverSide: true, processing: true, pageLength: 25,
lengthMenu: [[25,50,100],[25,50,100]], order: [[3,'desc']], lengthMenu: [[25,50,100],[25,50,100]], order: [[3,'desc']],
@ -202,13 +202,13 @@ $(function () {
info:'_START__END_ / _TOTAL_ előrendelés', infoEmpty:'0 előrendelés', info:'_START__END_ / _TOTAL_ előrendelés', infoEmpty:'0 előrendelés',
infoFiltered:'(szűrve _MAX_-ból)', emptyTable:'Nincs előrendelés', zeroRecords:'Nincs találat', infoFiltered:'(szűrve _MAX_-ból)', emptyTable:'Nincs előrendelés', zeroRecords:'Nincs találat',
paginate:{first:'««',previous:'«',next:'»',last:'»»'} }, paginate:{first:'««',previous:'«',next:'»',last:'»»'} },
ajax: { url:'/Admin/Preorders/PreorderList', type:'POST', ajax: { url:'/Admin/PreOrders/PreOrderList', type:'POST',
data: function(d){ d.__RequestVerificationToken=_token; d.statusFilter=activeStatus; } }, data: function(d){ d.__RequestVerificationToken=_token; d.statusFilter=activeStatus; } },
createdRow: function(row, data) { createdRow: function(row, data) {
if (isUrgentRow(data)) $(row).addClass('po-urgent-row'); if (isUrgentRow(data)) $(row).addClass('po-urgent-row');
}, },
columns: [ columns: [
{ data:'PreorderId', name:'PreorderId', render:function(d){ return '<strong>#'+d+'</strong>'; } }, { data:'PreOrderId', name:'PreOrderId', render:function(d){ return '<strong>#'+d+'</strong>'; } },
{ data:'CustomerName', name:'CustomerName', render:function(d,t,row){ return '<div>'+d+'</div><small class="text-muted">'+row.CustomerEmail+'</small>'; } }, { data:'CustomerName', name:'CustomerName', render:function(d,t,row){ return '<div>'+d+'</div><small class="text-muted">'+row.CustomerEmail+'</small>'; } },
{ data:'DateOfReceipt',name:'DateOfReceipt',render:function(d,t,row){ { data:'DateOfReceipt',name:'DateOfReceipt',render:function(d,t,row){
var icon = '<i class="fas fa-calendar-day text-muted mr-1"></i>'; var icon = '<i class="fas fa-calendar-day text-muted mr-1"></i>';
@ -218,8 +218,8 @@ $(function () {
{ data:'CreatedOnUtc', name:'CreatedOnUtc', render:function(d){ return '<small>'+d+'</small>'; } }, { data:'CreatedOnUtc', name:'CreatedOnUtc', render:function(d){ return '<small>'+d+'</small>'; } },
{ data:'Status', name:'Status', orderable:false, render:function(d,t,row){ return statusBadge(row); } }, { data:'Status', name:'Status', orderable:false, render:function(d,t,row){ return statusBadge(row); } },
{ data:'ItemCount', orderable:false, className:'text-center', render:function(d,t,row){ return itemProgress(row); } }, { data:'ItemCount', orderable:false, className:'text-center', render:function(d,t,row){ return itemProgress(row); } },
{ data:'PreorderId', orderable:false, searchable:false, className:'text-center', width:'60px', { data:'PreOrderId', orderable:false, searchable:false, className:'text-center', width:'60px',
render:function(d){ return '<a href="/Admin/Preorders/Detail/'+d+'" class="btn btn-xs btn-default" title="Részletek"><i class="fas fa-eye"></i></a>'; } } render:function(d){ return '<a href="/Admin/PreOrders/Detail/'+d+'" class="btn btn-xs btn-default" title="Részletek"><i class="fas fa-eye"></i></a>'; } }
] ]
}); });
$(document).on('click','.po-filter',function(){ $(document).on('click','.po-filter',function(){
@ -235,7 +235,7 @@ $(function () {
info:'_START__END_ / _TOTAL_ termék', infoEmpty:'Nincs adat', info:'_START__END_ / _TOTAL_ termék', infoEmpty:'Nincs adat',
infoFiltered:'(szűrve _MAX_-ból)', emptyTable:'Nincs előrendelési igény', zeroRecords:'Nincs találat', infoFiltered:'(szűrve _MAX_-ból)', emptyTable:'Nincs előrendelési igény', zeroRecords:'Nincs találat',
paginate:{first:'««',previous:'«',next:'»',last:'»»'} }, paginate:{first:'««',previous:'«',next:'»',last:'»»'} },
ajax:{ url:'/Admin/Preorders/DemandList', type:'POST', ajax:{ url:'/Admin/PreOrders/DemandList', type:'POST',
data:function(d){ d.__RequestVerificationToken=_token; d.openOnly=demandOpenOnly?'true':'false'; }, data:function(d){ d.__RequestVerificationToken=_token; d.openOnly=demandOpenOnly?'true':'false'; },
dataSrc:function(json){ dataSrc:function(json){
var n=(json.data||[]).filter(function(r){return r.TotalUnfulfilled>0;}).length; var n=(json.data||[]).filter(function(r){return r.TotalUnfulfilled>0;}).length;
@ -253,7 +253,7 @@ $(function () {
var cls=pct===100?'bg-success':pct>0?'bg-warning':'bg-secondary'; var cls=pct===100?'bg-success':pct>0?'bg-warning':'bg-secondary';
return '<div>'+fmtQty(d)+'</div><div class="progress mt-1" style="height:4px;"><div class="progress-bar '+cls+'" style="width:'+pct+'%"></div></div>'; } }, return '<div>'+fmtQty(d)+'</div><div class="progress mt-1" style="height:4px;"><div class="progress-bar '+cls+'" style="width:'+pct+'%"></div></div>'; } },
{ data:'TotalUnfulfilled',orderable:false, className:'text-center', render:function(d){ return unfulfilledCell(d); } }, { data:'TotalUnfulfilled',orderable:false, className:'text-center', render:function(d){ return unfulfilledCell(d); } },
{ data:'PreorderCount', orderable:false, className:'text-center', render:function(d){ return '<span class="badge badge-secondary">'+d+'</span>'; } }, { data:'PreOrderCount', orderable:false, className:'text-center', render:function(d){ return '<span class="badge badge-secondary">'+d+'</span>'; } },
{ data:'AvgUnitPrice', orderable:false, className:'text-right', render:function(d){ return fmtPrice(d); } } { data:'AvgUnitPrice', orderable:false, className:'text-right', render:function(d){ return fmtPrice(d); } }
] ]
}); });
@ -270,7 +270,7 @@ $(function () {
demandTable.ajax.reload(); demandTable.ajax.reload();
}); });
// ── Create Order / Preorder Modal (mode-aware) ────────────────────────── // ── Create Order / PreOrder Modal (mode-aware) ──────────────────────────
var cpProducts = []; var cpProducts = [];
var cpMode = null; var cpMode = null;
var CP_CUTOFF = 4; // ≤4 days → order, >4 days → preorder var CP_CUTOFF = 4; // ≤4 days → order, >4 days → preorder
@ -332,7 +332,7 @@ $(function () {
if (!cpMode){ resp([]); return; } if (!cpMode){ resp([]); return; }
$.get(cpMode==='order' $.get(cpMode==='order'
?'/Admin/CustomOrder/ProductSearchAutoComplete' ?'/Admin/CustomOrder/ProductSearchAutoComplete'
:'/Admin/CustomOrder/PreorderProductSearchAutoComplete', :'/Admin/CustomOrder/PreOrderProductSearchAutoComplete',
{term:req.term}, resp); {term:req.term}, resp);
}, },
select:function(e,ui){ addCpProduct(ui.item); $('#cp-product-search').val(''); return false; } select:function(e,ui){ addCpProduct(ui.item); $('#cp-product-search').val(''); return false; }
@ -402,7 +402,7 @@ $(function () {
} }
}); });
} else { } else {
$.ajax({url:'/Admin/Preorders/CreatePreorder', type:'POST', $.ajax({url:'/Admin/PreOrders/CreatePreOrder', type:'POST',
data:{ customerId:$('#cp-customer-id').val(), deliveryDateTime:$('#cp-delivery').val(), data:{ customerId:$('#cp-customer-id').val(), deliveryDateTime:$('#cp-delivery').val(),
customerNote:$('#cp-note').val().trim(), productsJson:$('#cp-products-json').val(), customerNote:$('#cp-note').val().trim(), productsJson:$('#cp-products-json').val(),
__RequestVerificationToken:_token }, __RequestVerificationToken:_token },

View File

@ -1,6 +1,6 @@
@{ @{
ViewBag.PageTitle = "Előrendelés — termékelérhetőség"; ViewBag.PageTitle = "Előrendelés — termékelérhetőség";
NopHtml.SetActiveMenuItemSystemName("PreorderAvailability"); NopHtml.SetActiveMenuItemSystemName("PreOrderAvailability");
Layout = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/_FruitBankAdminLayout.cshtml"; Layout = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/_FruitBankAdminLayout.cshtml";
} }
@ -149,7 +149,7 @@ $(function () {
zeroRecords : 'Nincs találat' zeroRecords : 'Nincs találat'
}, },
ajax: { ajax: {
url : '/Admin/PreorderAvailability/ProductList', url : '/Admin/PreOrderAvailability/ProductList',
type: 'POST', type: 'POST',
data: function (d) { d.__RequestVerificationToken = _token; } data: function (d) { d.__RequestVerificationToken = _token; }
}, },
@ -197,7 +197,7 @@ $(function () {
else rowData.WindowEnd = newVal || null; else rowData.WindowEnd = newVal || null;
$.ajax({ $.ajax({
url : '/Admin/PreorderAvailability/SaveWindow', url : '/Admin/PreOrderAvailability/SaveWindow',
type : 'POST', type : 'POST',
data : { data : {
__RequestVerificationToken : _token, __RequestVerificationToken : _token,
@ -243,7 +243,7 @@ $(function () {
zeroRecords : 'Nincs találat' zeroRecords : 'Nincs találat'
}, },
ajax: { ajax: {
url : '/Admin/PreorderAvailability/AvailableTodayList', url : '/Admin/PreOrderAvailability/AvailableTodayList',
type: 'POST', type: 'POST',
data: function (d) { d.__RequestVerificationToken = _token; }, data: function (d) { d.__RequestVerificationToken = _token; },
dataSrc: function (json) { dataSrc: function (json) {

View File

@ -3,10 +3,10 @@ using Nop.Web.Framework.Components;
namespace Nop.Plugin.Misc.FruitBankPlugin.Components; namespace Nop.Plugin.Misc.FruitBankPlugin.Components;
public class CustomerPreorderNavViewComponent : NopViewComponent public class CustomerPreOrderNavViewComponent : NopViewComponent
{ {
public IViewComponentResult Invoke(string widgetZone, object additionalData) public IViewComponentResult Invoke(string widgetZone, object additionalData)
{ {
return View("~/Plugins/Misc.FruitBankPlugin/Views/CustomerPreorder/NavItem.cshtml"); return View("~/Plugins/Misc.FruitBankPlugin/Views/CustomerPreOrder/NavItem.cshtml");
} }
} }

View File

@ -5,20 +5,21 @@ using Nop.Core;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Services.Customers; using Nop.Services.Customers;
using Nop.Web.Framework.Controllers; using Nop.Web.Framework.Controllers;
using static Nop.Plugin.Misc.FruitBankPlugin.Controllers.CustomerPreOrderController;
namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers; namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers;
public class CustomerPreorderController : BasePluginController public class CustomerPreOrderController : BasePluginController
{ {
private readonly IWorkContext _workContext; private readonly IWorkContext _workContext;
private readonly ICustomerService _customerService; private readonly ICustomerService _customerService;
private readonly PreorderDbContext _preorderDbContext; private readonly PreOrderDbContext _preorderDbContext;
private readonly FruitBankDbContext _dbContext; private readonly FruitBankDbContext _dbContext;
public CustomerPreorderController( public CustomerPreOrderController(
IWorkContext workContext, IWorkContext workContext,
ICustomerService customerService, ICustomerService customerService,
PreorderDbContext preorderDbContext, PreOrderDbContext preorderDbContext,
FruitBankDbContext dbContext) FruitBankDbContext dbContext)
{ {
_workContext = workContext; _workContext = workContext;
@ -35,13 +36,13 @@ public class CustomerPreorderController : BasePluginController
return Challenge(); return Challenge();
// Load this customer's preorders, newest first // Load this customer's preorders, newest first
var preorders = await _preorderDbContext.Preorders var preorders = await _preorderDbContext.PreOrders
.GetAllByCustomerIdAsync(customer.Id, false) .GetAllByCustomerIdAsync(customer.Id, false)
.OrderByDescending(p => p.CreatedOnUtc) .OrderByDescending(p => p.CreatedOnUtc)
.ToListAsync(); .ToListAsync();
var allItems = await _preorderDbContext.PreorderItems.GetAll() var allItems = await _preorderDbContext.PreOrderItems.GetAll()
.Where(i => preorders.Select(p => p.Id).Contains(i.PreorderId)) .Where(i => preorders.Select(p => p.Id).Contains(i.PreOrderId))
.ToListAsync(); .ToListAsync();
// Resolve product names // Resolve product names
@ -54,7 +55,7 @@ public class CustomerPreorderController : BasePluginController
var rows = preorders.Select(p => var rows = preorders.Select(p =>
{ {
var items = allItems.Where(i => i.PreorderId == p.Id).ToList(); var items = allItems.Where(i => i.PreOrderId == p.Id).ToList();
// Derive status from quantities (enum reads unreliable in LinqToDB) // Derive status from quantities (enum reads unreliable in LinqToDB)
var allFulfilled = items.Any() && items.All(i => i.FulfilledQuantity >= i.RequestedQuantity); var allFulfilled = items.Any() && items.All(i => i.FulfilledQuantity >= i.RequestedQuantity);
@ -63,13 +64,13 @@ public class CustomerPreorderController : BasePluginController
i.RequestedQuantity > 0); i.RequestedQuantity > 0);
var effectiveStatus = (int)p.Status != 0 ? p.Status var effectiveStatus = (int)p.Status != 0 ? p.Status
: allFulfilled ? PreorderStatus.Confirmed : allFulfilled ? PreOrderStatus.Confirmed
: anyFulfilled ? PreorderStatus.PartiallyFulfilled : anyFulfilled ? PreOrderStatus.PartiallyFulfilled
: PreorderStatus.Pending; : PreOrderStatus.Pending;
return new CustomerPreorderRow return new CustomerPreOrderRow
{ {
PreorderId = p.Id, PreOrderId = p.Id,
OrderId = p.OrderId, OrderId = p.OrderId,
DateOfReceipt = p.DateOfReceipt, DateOfReceipt = p.DateOfReceipt,
CreatedOnUtc = p.CreatedOnUtc, CreatedOnUtc = p.CreatedOnUtc,
@ -78,7 +79,7 @@ public class CustomerPreorderController : BasePluginController
Items = items.Select(i => Items = items.Select(i =>
{ {
productById.TryGetValue(i.ProductId, out var dto); productById.TryGetValue(i.ProductId, out var dto);
return new CustomerPreorderItemRow return new CustomerPreOrderItemRow
{ {
ProductName = dto?.Name ?? $"Termék #{i.ProductId}", ProductName = dto?.Name ?? $"Termék #{i.ProductId}",
IsMeasurable = dto?.IsMeasurable ?? false, IsMeasurable = dto?.IsMeasurable ?? false,
@ -86,38 +87,38 @@ public class CustomerPreorderController : BasePluginController
FulfilledQuantity = i.FulfilledQuantity, FulfilledQuantity = i.FulfilledQuantity,
UnitPriceInclTax = i.UnitPriceInclTax, UnitPriceInclTax = i.UnitPriceInclTax,
Status = i.FulfilledQuantity == 0 Status = i.FulfilledQuantity == 0
? PreorderItemStatus.Pending ? PreOrderItemStatus.Pending
: i.FulfilledQuantity >= i.RequestedQuantity : i.FulfilledQuantity >= i.RequestedQuantity
? PreorderItemStatus.Fulfilled ? PreOrderItemStatus.Fulfilled
: PreorderItemStatus.PartiallyFulfilled : PreOrderItemStatus.PartiallyFulfilled
}; };
}).ToList() }).ToList()
}; };
}).ToList(); }).ToList();
return View("~/Plugins/Misc.FruitBankPlugin/Views/CustomerPreorder/List.cshtml", rows); return View("~/Plugins/Misc.FruitBankPlugin/Views/CustomerPreOrder/List.cshtml", rows);
} }
// ── Inner models ────────────────────────────────────────────────────────── // ── Inner models ──────────────────────────────────────────────────────────
public class CustomerPreorderRow public class CustomerPreOrderRow
{ {
public int PreorderId { get; set; } public int PreOrderId { get; set; }
public int? OrderId { get; set; } public int? OrderId { get; set; }
public DateTime DateOfReceipt { get; set; } public DateTime DateOfReceipt { get; set; }
public DateTime CreatedOnUtc { get; set; } public DateTime CreatedOnUtc { get; set; }
public PreorderStatus Status { get; set; } public PreOrderStatus Status { get; set; }
public string? CustomerNote { get; set; } public string? CustomerNote { get; set; }
public List<CustomerPreorderItemRow> Items { get; set; } = new(); public List<CustomerPreOrderItemRow> Items { get; set; } = new();
} }
public class CustomerPreorderItemRow public class CustomerPreOrderItemRow
{ {
public string ProductName { get; set; } = string.Empty; public string ProductName { get; set; } = string.Empty;
public bool IsMeasurable { get; set; } public bool IsMeasurable { get; set; }
public int RequestedQuantity { get; set; } public int RequestedQuantity { get; set; }
public int FulfilledQuantity { get; set; } public int FulfilledQuantity { get; set; }
public decimal UnitPriceInclTax { get; set; } public decimal UnitPriceInclTax { get; set; }
public PreorderItemStatus Status { get; set; } public PreOrderItemStatus Status { get; set; }
} }
} }

View File

@ -39,7 +39,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
ICustomerService customerService, ICustomerService customerService,
ICustomerRegistrationService customerRegistrationService, ICustomerRegistrationService customerRegistrationService,
ILocalizationService localizationService, ILocalizationService localizationService,
PreorderConversionService preorderConversionService, PreOrderConversionService preorderConversionService,
IEnumerable<IAcLogWriterBase> logWriters) IEnumerable<IAcLogWriterBase> logWriters)
: BasePluginController, IFruitBankDataControllerServer : BasePluginController, IFruitBankDataControllerServer
{ {
@ -187,7 +187,14 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
public async Task<CargoTruck> GetCargoTruckById(int id) public async Task<CargoTruck> GetCargoTruckById(int id)
{ {
_logger.Detail($"GetCargoTruckById invoked; id: {id}"); _logger.Detail($"GetCargoTruckById invoked; id: {id}");
return await ctx.CargoTrucks.GetByIdAsync(id); return await ctx.CargoTrucks.GetByIdAsync(id, true);
}
[SignalR(SignalRTags.GetCargoTrucksByCargoPartnerId)]
public async Task<List<CargoTruck>> GetCargoTrucksByCargoPartnerId(int cargoPartnerId)
{
_logger.Detail($"GetCargoTrucksByCargoPartnerId invoked; cargoPartnerId: {cargoPartnerId}");
return await ctx.CargoTrucks.GetByCargoPartnerId(cargoPartnerId, true).ToListAsync();
} }
[SignalR(SignalRTags.AddCargoTruck)] [SignalR(SignalRTags.AddCargoTruck)]
@ -198,7 +205,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
_logger.Detail($"AddCargoTruck invoked; id: {cargoTruck.Id}"); _logger.Detail($"AddCargoTruck invoked; id: {cargoTruck.Id}");
await ctx.CargoTrucks.InsertAsync(cargoTruck); await ctx.CargoTrucks.InsertAsync(cargoTruck);
return await ctx.CargoTrucks.GetByIdAsync(cargoTruck.Id); return await ctx.CargoTrucks.GetByIdAsync(cargoTruck.Id, true);
} }
[SignalR(SignalRTags.UpdateCargoTruck)] [SignalR(SignalRTags.UpdateCargoTruck)]
@ -209,7 +216,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
_logger.Detail($"UpdateCargoTruck invoked; id: {cargoTruck.Id}"); _logger.Detail($"UpdateCargoTruck invoked; id: {cargoTruck.Id}");
await ctx.CargoTrucks.UpdateAsync(cargoTruck); await ctx.CargoTrucks.UpdateAsync(cargoTruck);
return await ctx.CargoTrucks.GetByIdAsync(cargoTruck.Id); return await ctx.CargoTrucks.GetByIdAsync(cargoTruck.Id, true);
} }
[SignalR(SignalRTags.GetShippings)] [SignalR(SignalRTags.GetShippings)]
@ -360,7 +367,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
// (EventConsumer also fires this, double-call is idempotent) // (EventConsumer also fires this, double-call is idempotent)
if (shippingItem.QuantityOnDocument > oldItem.QuantityOnDocument) if (shippingItem.QuantityOnDocument > oldItem.QuantityOnDocument)
_ = Task.Run(async () => await preorderConversionService _ = Task.Run(async () => await preorderConversionService
.ConvertPreordersForProductsAsync( .ConvertPreOrdersForProductsAsync(
new List<int> { shippingItem.ProductId.Value }, new List<int> { shippingItem.ProductId.Value },
shippingItem.ShippingDocumentId)); shippingItem.ShippingDocumentId));
} }

View File

@ -28,14 +28,14 @@ public class OrderController : BasePluginController
private readonly ICustomerService _customerService; private readonly ICustomerService _customerService;
private readonly ILocalizationService _localizationService; private readonly ILocalizationService _localizationService;
private readonly FruitBankDbContext _dbContext; private readonly FruitBankDbContext _dbContext;
private readonly PreorderDbContext _preorderDbContext; private readonly PreOrderDbContext _preorderDbContext;
private readonly FruitBankAttributeService _fruitBankAttributeService; private readonly FruitBankAttributeService _fruitBankAttributeService;
private readonly CustomPriceCalculationService _customPriceCalculationService; private readonly CustomPriceCalculationService _customPriceCalculationService;
private readonly IShoppingCartService _shoppingCartService; private readonly IShoppingCartService _shoppingCartService;
private readonly IProductService _productService; private readonly IProductService _productService;
private readonly OpenAIApiService _aiApiService; private readonly OpenAIApiService _aiApiService;
private readonly CerebrasAPIService _cerebrasApiService; private readonly CerebrasAPIService _cerebrasApiService;
private readonly PreorderConversionService _preorderConversionService; private readonly PreOrderConversionService _preorderConversionService;
private const string PendingDeliveryKey = "OrderFlowPendingDeliveryDateTime"; private const string PendingDeliveryKey = "OrderFlowPendingDeliveryDateTime";
@ -45,14 +45,14 @@ public class OrderController : BasePluginController
ICustomerService customerService, ICustomerService customerService,
ILocalizationService localizationService, ILocalizationService localizationService,
FruitBankDbContext dbContext, FruitBankDbContext dbContext,
PreorderDbContext preorderDbContext, PreOrderDbContext preorderDbContext,
FruitBankAttributeService fruitBankAttributeService, FruitBankAttributeService fruitBankAttributeService,
IPriceCalculationService priceCalculationService, IPriceCalculationService priceCalculationService,
IShoppingCartService shoppingCartService, IShoppingCartService shoppingCartService,
IProductService productService, IProductService productService,
OpenAIApiService aiApiService, OpenAIApiService aiApiService,
CerebrasAPIService cerebrasApiService, CerebrasAPIService cerebrasApiService,
PreorderConversionService preorderConversionService) PreOrderConversionService preorderConversionService)
{ {
_workContext = workContext; _workContext = workContext;
_storeContext = storeContext; _storeContext = storeContext;
@ -105,7 +105,7 @@ public class OrderController : BasePluginController
// Quick Order: delivery needs current stock (before Thursday) // Quick Order: delivery needs current stock (before Thursday)
// OR goods already arrived (Thu-Sun) and delivery still this week // OR goods already arrived (Thu-Sun) and delivery still this week
// Preorder: delivery is Thursday+ but today is still Mon/Tue/Wed (goods not yet here) // PreOrder: delivery is Thursday+ but today is still Mon/Tue/Wed (goods not yet here)
return (deliveryBeforeThursday || (isLateWeek && deliveryThisWeek)) return (deliveryBeforeThursday || (isLateWeek && deliveryThisWeek))
? "quickorder" ? "quickorder"
: "preorder"; : "preorder";
@ -216,10 +216,10 @@ public class OrderController : BasePluginController
} }
} }
// ── PRODUCTS — Preorder flow (curated window list) ──────────────────────── // ── PRODUCTS — PreOrder flow (curated window list) ────────────────────────
[HttpGet] [HttpGet]
public async Task<IActionResult> GetPreorderProducts() public async Task<IActionResult> GetPreOrderProducts()
{ {
var customer = await _workContext.GetCurrentCustomerAsync(); var customer = await _workContext.GetCurrentCustomerAsync();
if (await _customerService.IsGuestAsync(customer)) if (await _customerService.IsGuestAsync(customer))
@ -232,12 +232,12 @@ public class OrderController : BasePluginController
var gaStart = await _dbContext.GenericAttributes.Table var gaStart = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product) .Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowStart && ga.Key == FruitBankConst.PreOrderWindowStart
&& ga.StoreId == store.Id) && ga.StoreId == store.Id)
.ToListAsync(); .ToListAsync();
var gaEnd = await _dbContext.GenericAttributes.Table var gaEnd = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product) .Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowEnd && ga.Key == FruitBankConst.PreOrderWindowEnd
&& ga.StoreId == store.Id) && ga.StoreId == store.Id)
.ToListAsync(); .ToListAsync();
@ -403,10 +403,10 @@ public class OrderController : BasePluginController
return Json(new { success = true, cartItems = await GetCartItemsJson(customer, store) }); return Json(new { success = true, cartItems = await GetCartItemsJson(customer, store) });
} }
// ── PLACE PREORDER (Preorder flow) ──────────────────────────────────────── // ── PLACE PREORDER (PreOrder flow) ────────────────────────────────────────
[HttpPost] [HttpPost]
public async Task<IActionResult> PlacePreorder([FromBody] PlacePreorderRequest request) public async Task<IActionResult> PlacePreOrder([FromBody] PlacePreOrderRequest request)
{ {
var customer = await _workContext.GetCurrentCustomerAsync(); var customer = await _workContext.GetCurrentCustomerAsync();
if (await _customerService.IsGuestAsync(customer)) if (await _customerService.IsGuestAsync(customer))
@ -422,7 +422,7 @@ public class OrderController : BasePluginController
{ {
var store = await _storeContext.GetCurrentStoreAsync(); var store = await _storeContext.GetCurrentStoreAsync();
var preorder = new Preorder var preorder = new PreOrder
{ {
CustomerId = customer.Id, CustomerId = customer.Id,
StoreId = store.Id, StoreId = store.Id,
@ -430,7 +430,7 @@ public class OrderController : BasePluginController
CustomerNote = request.CustomerNote?.Trim() CustomerNote = request.CustomerNote?.Trim()
}; };
var items = new List<PreorderItem>(); var items = new List<PreOrderItem>();
foreach (var req in request.Items.Where(i => i.Quantity > 0)) foreach (var req in request.Items.Where(i => i.Quantity > 0))
{ {
var product = await _dbContext.Products.GetByIdAsync(req.ProductId); var product = await _dbContext.Products.GetByIdAsync(req.ProductId);
@ -444,7 +444,7 @@ public class OrderController : BasePluginController
unitPrice = pr.finalPrice; unitPrice = pr.finalPrice;
} }
items.Add(new PreorderItem items.Add(new PreOrderItem
{ {
ProductId = req.ProductId, ProductId = req.ProductId,
RequestedQuantity = req.Quantity, RequestedQuantity = req.Quantity,
@ -455,7 +455,7 @@ public class OrderController : BasePluginController
if (!items.Any()) if (!items.Any())
return Json(new { success = false, message = "Nincs érvényes termék az előrendelésben" }); return Json(new { success = false, message = "Nincs érvényes termék az előrendelésben" });
var saved = await _preorderDbContext.InsertPreorderAsync(preorder, items); var saved = await _preorderDbContext.InsertPreOrderAsync(preorder, items);
// Clean up the pending datetime attribute // Clean up the pending datetime attribute
await _fruitBankAttributeService await _fruitBankAttributeService
@ -466,12 +466,12 @@ public class OrderController : BasePluginController
// Awaited inline (not fire-and-forget) so we can return the order ID if one is created. // Awaited inline (not fire-and-forget) so we can return the order ID if one is created.
// shippingDocumentId = 0 signals this was triggered at preorder placement, not by a document. // shippingDocumentId = 0 signals this was triggered at preorder placement, not by a document.
var productIds = items.Select(i => i.ProductId).Distinct().ToList(); var productIds = items.Select(i => i.ProductId).Distinct().ToList();
await _preorderConversionService.ConvertPreordersForProductsAsync(productIds, 0); await _preorderConversionService.ConvertPreOrdersForProductsAsync(productIds, 0);
// Re-read to pick up OrderId if conversion created a real order // Re-read to pick up OrderId if conversion created a real order
var refreshed = await _preorderDbContext.Preorders.GetByIdAsync(saved.Id); var refreshed = await _preorderDbContext.PreOrders.GetByIdAsync(saved.Id);
Console.WriteLine($"[OrderFlow] PlacePreorder #{saved.Id} — orderId={refreshed?.OrderId}"); Console.WriteLine($"[OrderFlow] PlacePreOrder #{saved.Id} — orderId={refreshed?.OrderId}");
return Json(new return Json(new
{ {
@ -607,14 +607,14 @@ OUTPUT FORMAT: [{""product"": ""narancs"", ""quantity"": 100}]";
// ── Inner models ────────────────────────────────────────────────────────── // ── Inner models ──────────────────────────────────────────────────────────
public class PlacePreorderRequest public class PlacePreOrderRequest
{ {
public string? DeliveryDateTime { get; set; } public string? DeliveryDateTime { get; set; }
public string? CustomerNote { get; set; } public string? CustomerNote { get; set; }
public List<PreorderItemRequest> Items { get; set; } = new(); public List<PreOrderItemRequest> Items { get; set; } = new();
} }
public class PreorderItemRequest public class PreOrderItemRequest
{ {
public int ProductId { get; set; } public int ProductId { get; set; }
public int Quantity { get; set; } public int Quantity { get; set; }

View File

@ -10,31 +10,32 @@ using Nop.Services.Catalog;
using Nop.Services.Customers; using Nop.Services.Customers;
using Nop.Services.Localization; using Nop.Services.Localization;
using Nop.Web.Framework.Controllers; using Nop.Web.Framework.Controllers;
using static Nop.Plugin.Misc.FruitBankPlugin.Controllers.OrderController;
namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers; namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers;
[AutoValidateAntiforgeryToken] [AutoValidateAntiforgeryToken]
public class PreorderController : BasePluginController public class PreOrderController : BasePluginController
{ {
private readonly IWorkContext _workContext; private readonly IWorkContext _workContext;
private readonly IStoreContext _storeContext; private readonly IStoreContext _storeContext;
private readonly ICustomerService _customerService; private readonly ICustomerService _customerService;
private readonly ILocalizationService _localizationService; private readonly ILocalizationService _localizationService;
private readonly FruitBankDbContext _dbContext; private readonly FruitBankDbContext _dbContext;
private readonly PreorderDbContext _preorderDbContext; private readonly PreOrderDbContext _preorderDbContext;
private readonly FruitBankAttributeService _fruitBankAttributeService; private readonly FruitBankAttributeService _fruitBankAttributeService;
private readonly CustomPriceCalculationService _customPriceCalculationService; private readonly CustomPriceCalculationService _customPriceCalculationService;
private const string PendingDeliveryDateTimeKey = "PreorderPendingDeliveryDateTime"; private const string PendingDeliveryDateTimeKey = "PreOrderPendingDeliveryDateTime";
private const string Prefix = "Plugins.Misc.FruitBankPlugin.Preorder."; private const string Prefix = "Plugins.Misc.FruitBankPlugin.PreOrder.";
public PreorderController( public PreOrderController(
IWorkContext workContext, IWorkContext workContext,
IStoreContext storeContext, IStoreContext storeContext,
ICustomerService customerService, ICustomerService customerService,
ILocalizationService localizationService, ILocalizationService localizationService,
FruitBankDbContext dbContext, FruitBankDbContext dbContext,
PreorderDbContext preorderDbContext, PreOrderDbContext preorderDbContext,
FruitBankAttributeService fruitBankAttributeService, FruitBankAttributeService fruitBankAttributeService,
IPriceCalculationService priceCalculationService) IPriceCalculationService priceCalculationService)
{ {
@ -60,7 +61,7 @@ public class PreorderController : BasePluginController
if (await _customerService.IsGuestAsync(customer)) if (await _customerService.IsGuestAsync(customer))
return Challenge(); return Challenge();
return View("~/Plugins/Misc.FruitBankPlugin/Views/Preorder/Index.cshtml"); return View("~/Plugins/Misc.FruitBankPlugin/Views/PreOrder/Index.cshtml");
} }
// ── GET SAVED DELIVERY DATETIME (page restore) ──────────────────────────── // ── GET SAVED DELIVERY DATETIME (page restore) ────────────────────────────
@ -129,13 +130,13 @@ public class PreorderController : BasePluginController
// Load preorder window generic attributes — two queries, no N+1 // Load preorder window generic attributes — two queries, no N+1
var gaStart = await _dbContext.GenericAttributes.Table var gaStart = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product) .Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowStart && ga.Key == FruitBankConst.PreOrderWindowStart
&& ga.StoreId == store.Id) && ga.StoreId == store.Id)
.ToListAsync(); .ToListAsync();
var gaEnd = await _dbContext.GenericAttributes.Table var gaEnd = await _dbContext.GenericAttributes.Table
.Where(ga => ga.KeyGroup == nameof(Product) .Where(ga => ga.KeyGroup == nameof(Product)
&& ga.Key == FruitBankConst.PreorderWindowEnd && ga.Key == FruitBankConst.PreOrderWindowEnd
&& ga.StoreId == store.Id) && ga.StoreId == store.Id)
.ToListAsync(); .ToListAsync();
@ -192,7 +193,7 @@ public class PreorderController : BasePluginController
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"[Preorder] GetAvailableProducts error: {ex.Message}"); Console.WriteLine($"[PreOrder] GetAvailableProducts error: {ex.Message}");
return Json(new { success = false, message = $"Hiba: {ex.Message}" }); return Json(new { success = false, message = $"Hiba: {ex.Message}" });
} }
} }
@ -200,7 +201,7 @@ public class PreorderController : BasePluginController
// ── PLACE PREORDER ──────────────────────────────────────────────────────── // ── PLACE PREORDER ────────────────────────────────────────────────────────
[HttpPost] [HttpPost]
public async Task<IActionResult> PlacePreorder([FromBody] PlacePreorderRequest request) public async Task<IActionResult> PlacePreOrder([FromBody] PlacePreOrderRequest request)
{ {
var customer = await _workContext.GetCurrentCustomerAsync(); var customer = await _workContext.GetCurrentCustomerAsync();
if (await _customerService.IsGuestAsync(customer)) if (await _customerService.IsGuestAsync(customer))
@ -216,7 +217,7 @@ public class PreorderController : BasePluginController
{ {
var store = await _storeContext.GetCurrentStoreAsync(); var store = await _storeContext.GetCurrentStoreAsync();
var preorder = new Preorder var preorder = new PreOrder
{ {
CustomerId = customer.Id, CustomerId = customer.Id,
StoreId = store.Id, StoreId = store.Id,
@ -224,7 +225,7 @@ public class PreorderController : BasePluginController
CustomerNote = request.CustomerNote?.Trim() CustomerNote = request.CustomerNote?.Trim()
}; };
var items = new List<PreorderItem>(); var items = new List<PreOrderItem>();
foreach (var req in request.Items.Where(i => i.Quantity > 0)) foreach (var req in request.Items.Where(i => i.Quantity > 0))
{ {
var product = await _dbContext.Products.GetByIdAsync(req.ProductId); var product = await _dbContext.Products.GetByIdAsync(req.ProductId);
@ -239,7 +240,7 @@ public class PreorderController : BasePluginController
unitPrice = pr.finalPrice; unitPrice = pr.finalPrice;
} }
items.Add(new PreorderItem items.Add(new PreOrderItem
{ {
ProductId = req.ProductId, ProductId = req.ProductId,
RequestedQuantity = req.Quantity, RequestedQuantity = req.Quantity,
@ -250,14 +251,14 @@ public class PreorderController : BasePluginController
if (!items.Any()) if (!items.Any())
return Json(new { success = false, message = await L("NoValidItems") }); return Json(new { success = false, message = await L("NoValidItems") });
var saved = await _preorderDbContext.InsertPreorderAsync(preorder, items); var saved = await _preorderDbContext.InsertPreOrderAsync(preorder, items);
// Clean up the pending delivery datetime attribute // Clean up the pending delivery datetime attribute
await _fruitBankAttributeService await _fruitBankAttributeService
.DeleteGenericAttributeAsync<Nop.Core.Domain.Customers.Customer>( .DeleteGenericAttributeAsync<Nop.Core.Domain.Customers.Customer>(
customer.Id, PendingDeliveryDateTimeKey, store.Id); customer.Id, PendingDeliveryDateTimeKey, store.Id);
Console.WriteLine($"[Preorder] Placed #{saved.Id} — customer #{customer.Id}, {items.Count} items, delivery {deliveryDateTime:u}"); Console.WriteLine($"[PreOrder] Placed #{saved.Id} — customer #{customer.Id}, {items.Count} items, delivery {deliveryDateTime:u}");
return Json(new return Json(new
{ {
@ -268,21 +269,21 @@ public class PreorderController : BasePluginController
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"[Preorder] PlacePreorder error: {ex.Message}"); Console.WriteLine($"[PreOrder] PlacePreOrder error: {ex.Message}");
return Json(new { success = false, message = $"Hiba: {ex.Message}" }); return Json(new { success = false, message = $"Hiba: {ex.Message}" });
} }
} }
// ── INNER MODELS ────────────────────────────────────────────────────────── // ── INNER MODELS ──────────────────────────────────────────────────────────
public class PlacePreorderRequest public class PlacePreOrderRequest
{ {
public string? DeliveryDateTime { get; set; } public string? DeliveryDateTime { get; set; }
public string? CustomerNote { get; set; } public string? CustomerNote { get; set; }
public List<PreorderItemRequest> Items { get; set; } = new(); public List<PreOrderItemRequest> Items { get; set; } = new();
} }
public class PreorderItemRequest public class PreOrderItemRequest
{ {
public int ProductId { get; set; } public int ProductId { get; set; }
public int Quantity { get; set; } public int Quantity { get; set; }

View File

@ -1,4 +1,5 @@
using FruitBank.Common.Entities; using FruitBank.Common.Entities;
using LinqToDB;
using Mango.Nop.Data.Repositories; using Mango.Nop.Data.Repositories;
using Nop.Core.Caching; using Nop.Core.Caching;
using Nop.Core.Configuration; using Nop.Core.Configuration;
@ -18,9 +19,9 @@ public class CargoTruckDbTable : MgDbTableBase<CargoTruck>
public IQueryable<CargoTruck> GetAll(bool loadRelations) public IQueryable<CargoTruck> GetAll(bool loadRelations)
{ {
return GetAll(); return loadRelations ? GetAll().LoadWith(sd => sd.CargoPartner) : GetAll();
//return loadRelations ? GetAll().LoadWith(sd => sd.CargoTrucks) : GetAll();
} }
public Task<CargoTruck> GetByIdAsync(int id, bool loadRelations) => GetAll(loadRelations).FirstOrDefaultAsync(p => p.Id == id); public Task<CargoTruck> GetByIdAsync(int id, bool loadRelations) => GetAll(loadRelations).FirstOrDefaultAsync(p => p.Id == id);
public IQueryable<CargoTruck> GetByCargoPartnerId(int cargoPartnerId, bool loadRelations) => GetAll(loadRelations).Where(p => p.CargoPartnerId == cargoPartnerId);
} }

View File

@ -4,7 +4,7 @@ using Nop.Data;
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer.Interfaces; namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer.Interfaces;
public interface IPreorderDbSet<TDbTable> : IMgDbTableBase where TDbTable : IRepository<Preorder> public interface IPreOrderDbSet<TDbTable> : IMgDbTableBase where TDbTable : IRepository<PreOrder>
{ {
public TDbTable Preorders { get; set; } public TDbTable PreOrders { get; set; }
} }

View File

@ -4,7 +4,7 @@ using Nop.Data;
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer.Interfaces; namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer.Interfaces;
public interface IPreorderItemDbSet<TDbTable> : IMgDbTableBase where TDbTable : IRepository<PreorderItem> public interface IPreOrderItemDbSet<TDbTable> : IMgDbTableBase where TDbTable : IRepository<PreOrderItem>
{ {
public TDbTable PreorderItems { get; set; } public TDbTable PreOrderItems { get; set; }
} }

View File

@ -15,14 +15,14 @@ using Nop.Plugin.Misc.FruitBankPlugin.Services;
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
public class PreorderDbContext : public class PreOrderDbContext :
IPreorderDbSet<PreorderDbTable>, IPreOrderDbSet<PreOrderDbTable>,
IPreorderItemDbSet<PreorderItemDbTable> IPreOrderItemDbSet<PreOrderItemDbTable>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
public PreorderDbTable Preorders { get; set; } public PreOrderDbTable PreOrders { get; set; }
public PreorderItemDbTable PreorderItems { get; set; } public PreOrderItemDbTable PreOrderItems { get; set; }
// Read-only access to related NopCommerce repositories needed during conversion // Read-only access to related NopCommerce repositories needed during conversion
public IRepository<Customer> Customers { get; set; } public IRepository<Customer> Customers { get; set; }
@ -30,112 +30,112 @@ public class PreorderDbContext :
public IRepository<Order> Orders { get; set; } public IRepository<Order> Orders { get; set; }
public IRepository<OrderItem> OrderItems { get; set; } public IRepository<OrderItem> OrderItems { get; set; }
public PreorderDbContext( public PreOrderDbContext(
PreorderDbTable preorderDbTable, PreOrderDbTable preorderDbTable,
PreorderItemDbTable preorderItemDbTable, PreOrderItemDbTable preorderItemDbTable,
IRepository<Customer> customerRepository, IRepository<Customer> customerRepository,
IRepository<Product> productRepository, IRepository<Product> productRepository,
IRepository<Order> orderRepository, IRepository<Order> orderRepository,
IRepository<OrderItem> orderItemRepository, IRepository<OrderItem> orderItemRepository,
IEnumerable<IAcLogWriterBase> logWriters) IEnumerable<IAcLogWriterBase> logWriters)
{ {
Preorders = preorderDbTable; PreOrders = preorderDbTable;
PreorderItems = preorderItemDbTable; PreOrderItems = preorderItemDbTable;
Customers = customerRepository; Customers = customerRepository;
Products = productRepository; Products = productRepository;
Orders = orderRepository; Orders = orderRepository;
OrderItems = orderItemRepository; OrderItems = orderItemRepository;
_logger = new Logger<PreorderDbContext>(logWriters.ToArray()); _logger = new Logger<PreOrderDbContext>(logWriters.ToArray());
} }
/// <summary> /// <summary>
/// Insert a complete preorder with all its items in one operation. /// Insert a complete preorder with all its items in one operation.
/// Returns the saved preorder (with Id populated). /// Returns the saved preorder (with Id populated).
/// </summary> /// </summary>
public async Task<Preorder> InsertPreorderAsync(Preorder preorder, IList<PreorderItem> items) public async Task<PreOrder> InsertPreOrderAsync(PreOrder preorder, IList<PreOrderItem> items)
{ {
preorder.CreatedOnUtc = DateTime.UtcNow; preorder.CreatedOnUtc = DateTime.UtcNow;
preorder.UpdatedOnUtc = DateTime.UtcNow; preorder.UpdatedOnUtc = DateTime.UtcNow;
preorder.Status = PreorderStatus.Pending; preorder.Status = PreOrderStatus.Pending;
await Preorders.InsertAsync(preorder); await PreOrders.InsertAsync(preorder);
foreach (var item in items) foreach (var item in items)
{ {
item.PreorderId = preorder.Id; item.PreOrderId = preorder.Id;
item.FulfilledQuantity = 0; item.FulfilledQuantity = 0;
item.Status = PreorderItemStatus.Pending; item.Status = PreOrderItemStatus.Pending;
await PreorderItems.InsertAsync(item); await PreOrderItems.InsertAsync(item);
} }
_logger.Info($"PreorderDbContext: inserted Preorder #{preorder.Id} with {items.Count} items for customer #{preorder.CustomerId}"); _logger.Info($"PreOrderDbContext: inserted PreOrder #{preorder.Id} with {items.Count} items for customer #{preorder.CustomerId}");
return preorder; return preorder;
} }
/// <summary> /// <summary>
/// Returns all pending preorder items for a set of productIds, ordered by PreorderId (FCFS). /// Returns all pending preorder items for a set of productIds, ordered by PreOrderId (FCFS).
/// Used by PreorderConversionService after IncomingQuantity is written. /// Used by PreOrderConversionService after IncomingQuantity is written.
/// </summary> /// </summary>
public async Task<List<PreorderItem>> GetPendingItemsForProductsAsync(IList<int> productIds) public async Task<List<PreOrderItem>> GetPendingItemsForProductsAsync(IList<int> productIds)
{ {
// Fetch all items for these products first, then filter by status in memory // Fetch all items for these products first, then filter by status in memory
// LinqToDB cannot translate enum comparisons to SQL in this codebase // LinqToDB cannot translate enum comparisons to SQL in this codebase
var all = await PreorderItems.Table var all = await PreOrderItems.Table
.Where(i => productIds.Contains(i.ProductId)) .Where(i => productIds.Contains(i.ProductId))
.OrderBy(i => i.PreorderId) .OrderBy(i => i.PreOrderId)
.ToListAsync(); .ToListAsync();
return all.Where(i => return all.Where(i =>
i.Status == PreorderItemStatus.Pending || i.Status == PreOrderItemStatus.Pending ||
i.Status == PreorderItemStatus.PartiallyFulfilled) i.Status == PreOrderItemStatus.PartiallyFulfilled)
.ToList(); .ToList();
} }
/// <summary> /// <summary>
/// After conversion: check if all items in a preorder are resolved and update the preorder's status. /// After conversion: check if all items in a preorder are resolved and update the preorder's status.
/// </summary> /// </summary>
public async Task RefreshPreorderStatusAsync(int preorderId) public async Task RefreshPreOrderStatusAsync(int preorderId)
{ {
var preorder = await Preorders.GetByIdAsync(preorderId); var preorder = await PreOrders.GetByIdAsync(preorderId);
if (preorder == null) return; if (preorder == null) return;
var items = await PreorderItems.GetAllByPreorderIdAsync(preorderId).ToListAsync(); var items = await PreOrderItems.GetAllByPreOrderIdAsync(preorderId).ToListAsync();
var hasDropped = items.Any(i => i.Status == PreorderItemStatus.Dropped); var hasDropped = items.Any(i => i.Status == PreOrderItemStatus.Dropped);
var hasPartial = items.Any(i => i.Status == PreorderItemStatus.PartiallyFulfilled); var hasPartial = items.Any(i => i.Status == PreOrderItemStatus.PartiallyFulfilled);
var hasPending = items.Any(i => i.Status == PreorderItemStatus.Pending); var hasPending = items.Any(i => i.Status == PreOrderItemStatus.Pending);
var allFulfilled = items.All(i => i.Status == PreorderItemStatus.Fulfilled); var allFulfilled = items.All(i => i.Status == PreOrderItemStatus.Fulfilled);
preorder.Status = (hasDropped || hasPartial) && !hasPending ? PreorderStatus.PartiallyFulfilled preorder.Status = (hasDropped || hasPartial) && !hasPending ? PreOrderStatus.PartiallyFulfilled
: allFulfilled ? PreorderStatus.Confirmed : allFulfilled ? PreOrderStatus.Confirmed
: PreorderStatus.Pending; : PreOrderStatus.Pending;
preorder.UpdatedOnUtc = DateTime.UtcNow; preorder.UpdatedOnUtc = DateTime.UtcNow;
await Preorders.UpdateAsync(preorder); await PreOrders.UpdateAsync(preorder);
_logger.Info($"PreorderDbContext: Preorder #{preorderId} status → {preorder.Status}"); _logger.Info($"PreOrderDbContext: PreOrder #{preorderId} status → {preorder.Status}");
} }
/// <summary> /// <summary>
/// Mark a preorder as cancelled (customer or admin action). /// Mark a preorder as cancelled (customer or admin action).
/// </summary> /// </summary>
public async Task CancelPreorderAsync(int preorderId) public async Task CancelPreOrderAsync(int preorderId)
{ {
var preorder = await Preorders.GetByIdAsync(preorderId); var preorder = await PreOrders.GetByIdAsync(preorderId);
if (preorder == null) return; if (preorder == null) return;
preorder.Status = PreorderStatus.Cancelled; preorder.Status = PreOrderStatus.Cancelled;
preorder.UpdatedOnUtc = DateTime.UtcNow; preorder.UpdatedOnUtc = DateTime.UtcNow;
await Preorders.UpdateAsync(preorder); await PreOrders.UpdateAsync(preorder);
var items = await PreorderItems.GetAllByPreorderIdAsync(preorderId).ToListAsync(); var items = await PreOrderItems.GetAllByPreOrderIdAsync(preorderId).ToListAsync();
var cancellableStatuses = new[] { PreorderItemStatus.Pending, PreorderItemStatus.PartiallyFulfilled }; var cancellableStatuses = new[] { PreOrderItemStatus.Pending, PreOrderItemStatus.PartiallyFulfilled };
foreach (var item in items.Where(i => cancellableStatuses.Contains(i.Status))) foreach (var item in items.Where(i => cancellableStatuses.Contains(i.Status)))
{ {
item.Status = PreorderItemStatus.Dropped; item.Status = PreOrderItemStatus.Dropped;
await PreorderItems.UpdateAsync(item); await PreOrderItems.UpdateAsync(item);
} }
_logger.Info($"PreorderDbContext: Preorder #{preorderId} cancelled"); _logger.Info($"PreOrderDbContext: PreOrder #{preorderId} cancelled");
} }
} }

View File

@ -10,9 +10,9 @@ using Nop.Data;
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
public class PreorderDbTable : MgDbTableBase<Preorder> public class PreOrderDbTable : MgDbTableBase<PreOrder>
{ {
public PreorderDbTable( public PreOrderDbTable(
IEventPublisher eventPublisher, IEventPublisher eventPublisher,
INopDataProvider dataProvider, INopDataProvider dataProvider,
IShortTermCacheManager shortTermCacheManager, IShortTermCacheManager shortTermCacheManager,
@ -22,23 +22,23 @@ public class PreorderDbTable : MgDbTableBase<Preorder>
{ {
} }
public IQueryable<Preorder> GetAll(bool loadRelations) public IQueryable<PreOrder> GetAll(bool loadRelations)
{ {
return loadRelations return loadRelations
? GetAll() ? GetAll()
.LoadWith(p => p.PreorderItems) .LoadWith(p => p.PreOrderItems)
: GetAll(); : GetAll();
} }
public Task<Preorder?> GetByIdAsync(int id, bool loadRelations) public Task<PreOrder?> GetByIdAsync(int id, bool loadRelations)
=> GetAll(loadRelations).FirstOrDefaultAsync(p => p.Id == id); => GetAll(loadRelations).FirstOrDefaultAsync(p => p.Id == id);
public IQueryable<Preorder> GetAllByCustomerIdAsync(int customerId, bool loadRelations) public IQueryable<PreOrder> GetAllByCustomerIdAsync(int customerId, bool loadRelations)
=> GetAll(loadRelations).Where(p => p.CustomerId == customerId); => GetAll(loadRelations).Where(p => p.CustomerId == customerId);
public IQueryable<Preorder> GetAllPendingAsync(bool loadRelations) public IQueryable<PreOrder> GetAllPendingAsync(bool loadRelations)
{ {
var pendingStatuses = new[] { PreorderStatus.Pending, PreorderStatus.PartiallyFulfilled }; var pendingStatuses = new[] { PreOrderStatus.Pending, PreOrderStatus.PartiallyFulfilled };
return GetAll(loadRelations).Where(p => pendingStatuses.Contains(p.Status)); return GetAll(loadRelations).Where(p => pendingStatuses.Contains(p.Status));
} }
} }

View File

@ -10,9 +10,9 @@ using Nop.Data;
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
public class PreorderItemDbTable : MgDbTableBase<PreorderItem> public class PreOrderItemDbTable : MgDbTableBase<PreOrderItem>
{ {
public PreorderItemDbTable( public PreOrderItemDbTable(
IEventPublisher eventPublisher, IEventPublisher eventPublisher,
INopDataProvider dataProvider, INopDataProvider dataProvider,
IShortTermCacheManager shortTermCacheManager, IShortTermCacheManager shortTermCacheManager,
@ -22,21 +22,21 @@ public class PreorderItemDbTable : MgDbTableBase<PreorderItem>
{ {
} }
public IQueryable<PreorderItem> GetAllByPreorderIdAsync(int preorderId) public IQueryable<PreOrderItem> GetAllByPreOrderIdAsync(int preorderId)
=> GetAll().Where(i => i.PreorderId == preorderId); => GetAll().Where(i => i.PreOrderId == preorderId);
public IQueryable<PreorderItem> GetAllByProductIdAsync(int productId) public IQueryable<PreOrderItem> GetAllByProductIdAsync(int productId)
=> GetAll().Where(i => i.ProductId == productId); => GetAll().Where(i => i.ProductId == productId);
/// <summary> /// <summary>
/// All pending/partially-fulfilled items for a product, ordered by their parent preorder's /// All pending/partially-fulfilled items for a product, ordered by their parent preorder's
/// CreatedOnUtc for first-come-first-served allocation. /// CreatedOnUtc for first-come-first-served allocation.
/// </summary> /// </summary>
public IQueryable<PreorderItem> GetPendingByProductIdOrderedAsync(int productId) public IQueryable<PreOrderItem> GetPendingByProductIdOrderedAsync(int productId)
{ {
var pendingStatuses = new[] { PreorderItemStatus.Pending, PreorderItemStatus.PartiallyFulfilled }; var pendingStatuses = new[] { PreOrderItemStatus.Pending, PreOrderItemStatus.PartiallyFulfilled };
return GetAll() return GetAll()
.Where(i => i.ProductId == productId && pendingStatuses.Contains(i.Status)) .Where(i => i.ProductId == productId && pendingStatuses.Contains(i.Status))
.OrderBy(i => i.PreorderId); .OrderBy(i => i.PreOrderId);
} }
} }

View File

@ -38,11 +38,11 @@ public class FruitBankEventConsumer :
private readonly FruitBankDbContext _ctx; private readonly FruitBankDbContext _ctx;
private readonly MeasurementService _measurementService; private readonly MeasurementService _measurementService;
private readonly FruitBankAttributeService _fruitBankAttributeService; private readonly FruitBankAttributeService _fruitBankAttributeService;
private readonly PreorderConversionService _preorderConversionService; private readonly PreOrderConversionService _preorderConversionService;
private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IServiceScopeFactory _serviceScopeFactory;
public FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBankDbContext ctx, MeasurementService measurementService, public FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBankDbContext ctx, MeasurementService measurementService,
FruitBankAttributeService fruitBankAttributeService, PreorderConversionService preorderConversionService, FruitBankAttributeService fruitBankAttributeService, PreOrderConversionService preorderConversionService,
IServiceScopeFactory serviceScopeFactory, IEnumerable<IAcLogWriterBase> logWriters) : base(ctx, httpContextAcc, logWriters) IServiceScopeFactory serviceScopeFactory, IEnumerable<IAcLogWriterBase> logWriters) : base(ctx, httpContextAcc, logWriters)
{ {
_ctx = ctx; _ctx = ctx;
@ -212,9 +212,9 @@ public class FruitBankEventConsumer :
System.Transactions.TransactionScopeOption.Suppress, System.Transactions.TransactionScopeOption.Suppress,
System.Transactions.TransactionScopeAsyncFlowOption.Enabled); System.Transactions.TransactionScopeAsyncFlowOption.Enabled);
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
var conversion = scope.ServiceProvider.GetRequiredService<PreorderConversionService>(); var conversion = scope.ServiceProvider.GetRequiredService<PreOrderConversionService>();
try { await conversion.ConvertPreordersForProductsAsync(new List<int> { item.ProductId.Value }, item.ShippingDocumentId); } try { await conversion.ConvertPreOrdersForProductsAsync(new List<int> { item.ProductId.Value }, item.ShippingDocumentId); }
catch (Exception ex) { Logger.Error($"[FruitBankEventConsumer] Preorder conversion failed for ProductId={item.ProductId}: {ex.Message}", ex); } catch (Exception ex) { Logger.Error($"[FruitBankEventConsumer] PreOrder conversion failed for ProductId={item.ProductId}: {ex.Message}", ex); }
}); });
} }
} }
@ -238,9 +238,9 @@ public class FruitBankEventConsumer :
System.Transactions.TransactionScopeOption.Suppress, System.Transactions.TransactionScopeOption.Suppress,
System.Transactions.TransactionScopeAsyncFlowOption.Enabled); System.Transactions.TransactionScopeAsyncFlowOption.Enabled);
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
var conversion = scope.ServiceProvider.GetRequiredService<PreorderConversionService>(); var conversion = scope.ServiceProvider.GetRequiredService<PreOrderConversionService>();
try { await conversion.ConvertPreordersForProductsAsync(new List<int> { shippingItem.ProductId.Value }, shippingItem.ShippingDocumentId); } try { await conversion.ConvertPreOrdersForProductsAsync(new List<int> { shippingItem.ProductId.Value }, shippingItem.ShippingDocumentId); }
catch (Exception ex) { Logger.Error($"[FruitBankEventConsumer] Preorder conversion failed for ProductId={shippingItem.ProductId}: {ex.Message}", ex); } catch (Exception ex) { Logger.Error($"[FruitBankEventConsumer] PreOrder conversion failed for ProductId={shippingItem.ProductId}: {ex.Message}", ex); }
}); });
} }
} }

View File

@ -18,12 +18,12 @@ namespace Nop.Plugin.Misc.FruitBankPlugin
public static class FruitBankPluginConst public static class FruitBankPluginConst
{ {
/// <summary> /// <summary>
/// Preorders whose DateOfReceipt is further than this many days in the future /// PreOrders whose DateOfReceipt is further than this many days in the future
/// are NOT converted at the current conversion run. /// are NOT converted at the current conversion run.
/// Based on the bi-weekly truck cycle (~3-4 days between arrivals): /// Based on the bi-weekly truck cycle (~3-4 days between arrivals):
/// if delivery is more than 4 days away, the next truck will arrive before /// if delivery is more than 4 days away, the next truck will arrive before
/// that delivery date, and its document processing will be the correct trigger. /// that delivery date, and its document processing will be the correct trigger.
/// </summary> /// </summary>
public const int PreorderConversionWindowDays = 4; public const int PreOrderConversionWindowDays = 4;
} }
} }

View File

@ -228,92 +228,92 @@ namespace Nop.Plugin.Misc.FruitBankPlugin
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.QuickOrder.InvalidProductOrQuantity", "Invalid product or quantity", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.QuickOrder.InvalidProductOrQuantity", "Invalid product or quantity", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.QuickOrder.InvalidProductOrQuantity", "\u00c9rv\u00e9nytelen term\u00e9k vagy mennyis\u00e9g", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.QuickOrder.InvalidProductOrQuantity", "\u00c9rv\u00e9nytelen term\u00e9k vagy mennyis\u00e9g", hu);
// ── Preorder page ─────────────────────────────────────────────────── // ── PreOrder page ───────────────────────────────────────────────────
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.PageTitle", "Preorder", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.PageTitle", "PreOrder", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.PageTitle", "El\u0151rendel\u00e9s", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.PageTitle", "El\u0151rendel\u00e9s", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.MenuLabel", "Preorder", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.MenuLabel", "PreOrder", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.MenuLabel", "El\u0151rendel\u00e9s", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.MenuLabel", "El\u0151rendel\u00e9s", hu);
// Delivery step // Delivery step
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Title", "When do you want to receive your preorder?", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Title", "When do you want to receive your preorder?", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Title", "Mikor k\u00e9red a rendel\u00e9st?", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Title", "Mikor k\u00e9red a rendel\u00e9st?", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Subtitle", "Choose a delivery day and time (we\u2019ll confirm availability)", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Subtitle", "Choose a delivery day and time (we\u2019ll confirm availability)", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Subtitle", "V\u00e1lassz sz\u00e1ll\u00edt\u00e1si napot \u00e9s id\u0151pontot (az el\u00e9rhet\u0151s\u00e9get meger\u0151s\u00edtj\u00fck)", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Subtitle", "V\u00e1lassz sz\u00e1ll\u00edt\u00e1si napot \u00e9s id\u0151pontot (az el\u00e9rhet\u0151s\u00e9get meger\u0151s\u00edtj\u00fck)", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.DayLabel", "Delivery day", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.DayLabel", "Delivery day", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.DayLabel", "K\u00edv\u00e1nt sz\u00e1ll\u00edt\u00e1si nap", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.DayLabel", "K\u00edv\u00e1nt sz\u00e1ll\u00edt\u00e1si nap", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.TimeLabel", "Delivery time", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.TimeLabel", "Delivery time", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.TimeLabel", "K\u00edv\u00e1nt id\u0151pont", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.TimeLabel", "K\u00edv\u00e1nt id\u0151pont", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.TimeHint", "Choose an exact time", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.TimeHint", "Choose an exact time", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.TimeHint", "V\u00e1lassz pontos id\u0151pontot", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.TimeHint", "V\u00e1lassz pontos id\u0151pontot", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ConfirmButton", "Show available products", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ConfirmButton", "Show available products", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ConfirmButton", "El\u00e9rhet\u0151 term\u00e9kek mutat\u00e1sa", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ConfirmButton", "El\u00e9rhet\u0151 term\u00e9kek mutat\u00e1sa", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ChangeLabel", "Delivery:", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ChangeLabel", "Delivery:", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ChangeLabel", "Sz\u00e1ll\u00edt\u00e1s:", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ChangeLabel", "Sz\u00e1ll\u00edt\u00e1s:", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ChangeButton", "Change", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ChangeButton", "Change", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ChangeButton", "M\u00f3dos\u00edt\u00e1s", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ChangeButton", "M\u00f3dos\u00edt\u00e1s", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Today", "Today", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Today", "Today", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Today", "Ma", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Today", "Ma", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Tomorrow", "Tomorrow", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Tomorrow", "Tomorrow", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Tomorrow", "Holnap", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Tomorrow", "Holnap", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Saving", "Saving...", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Saving", "Saving...", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Saving", "Ment\u00e9s...", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Saving", "Ment\u00e9s...", hu);
// Products // Products
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.InfoBanner", "Preorders are wishes \u2014 we will confirm availability when the shipment arrives and notify you of any changes.", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.InfoBanner", "PreOrders are wishes \u2014 we will confirm availability when the shipment arrives and notify you of any changes.", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.InfoBanner", "Az el\u0151rendel\u00e9s egy k\u00edv\u00e1ns\u00e1glista \u2014 az áruk meger\u0151s\u00edt\u00e9se a sz\u00e1ll\u00edtm\u00e1ny be\u00e9rkez\u00e9sekor t\u00f6rt\u00e9nik, \u00e9s az esetleges v\u00e1ltoz\u00e1sokr\u00f3l \u00e9rtes\u00edt\u00fcnk.", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.InfoBanner", "Az el\u0151rendel\u00e9s egy k\u00edv\u00e1ns\u00e1glista \u2014 az áruk meger\u0151s\u00edt\u00e9se a sz\u00e1ll\u00edtm\u00e1ny be\u00e9rkez\u00e9sekor t\u00f6rt\u00e9nik, \u00e9s az esetleges v\u00e1ltoz\u00e1sokr\u00f3l \u00e9rtes\u00edt\u00fcnk.", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.LoadingProducts", "Loading available products...", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.LoadingProducts", "Loading available products...", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.LoadingProducts", "El\u00e9rhet\u0151 term\u00e9kek bet\u00f6lt\u00e9se...", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.LoadingProducts", "El\u00e9rhet\u0151 term\u00e9kek bet\u00f6lt\u00e9se...", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NoProductsAvailable", "No products are currently available for preorder. Please check back later.", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NoProductsAvailable", "No products are currently available for preorder. Please check back later.", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NoProductsAvailable", "Jelenleg nincs el\u0151rendelhet\u0151 term\u00e9k. K\u00e9rj\u00fck, l\u00e1togass vissza k\u00e9s\u0151bb.", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NoProductsAvailable", "Jelenleg nincs el\u0151rendelhet\u0151 term\u00e9k. K\u00e9rj\u00fck, l\u00e1togass vissza k\u00e9s\u0151bb.", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.ProductsLabel", "Available for preorder \u2014 set quantities:", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.ProductsLabel", "Available for preorder \u2014 set quantities:", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.ProductsLabel", "El\u0151rendelhet\u0151 term\u00e9kek \u2014 add meg a mennyis\u00e9geket:", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.ProductsLabel", "El\u0151rendelhet\u0151 term\u00e9kek \u2014 add meg a mennyis\u00e9geket:", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.MeasurableBadge", "Requires weighing", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.MeasurableBadge", "Requires weighing", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.MeasurableBadge", "S\u00falym\u00e9r\u00e9st ig\u00e9nyel", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.MeasurableBadge", "S\u00falym\u00e9r\u00e9st ig\u00e9nyel", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.PricePerPiece", "Ft/pcs", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.PricePerPiece", "Ft/pcs", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.PricePerPiece", "Ft/db", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.PricePerPiece", "Ft/db", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.PieceUnit", "pcs", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.PieceUnit", "pcs", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.PieceUnit", "db", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.PieceUnit", "db", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.StockLabel", "Incoming stock:", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.StockLabel", "Incoming stock:", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.StockLabel", "V\u00e1rhat\u00f3 k\u00e9szlet:", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.StockLabel", "V\u00e1rhat\u00f3 k\u00e9szlet:", hu);
// Note + submit // Note + submit
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NoteLabel", "Additional note (optional)", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NoteLabel", "Additional note (optional)", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NoteLabel", "Megjegyz\u00e9s (nem k\u00f6telez\u0151)", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NoteLabel", "Megjegyz\u00e9s (nem k\u00f6telez\u0151)", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NotePlaceholder", "Any special requests or notes for this preorder...", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NotePlaceholder", "Any special requests or notes for this preorder...", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NotePlaceholder", "Esetleges megjegyz\u00e9sek az el\u0151rendel\u00e9ssel kapcsolatban...", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NotePlaceholder", "Esetleges megjegyz\u00e9sek az el\u0151rendel\u00e9ssel kapcsolatban...", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SelectionNone", "No products selected yet", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SelectionNone", "No products selected yet", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SelectionNone", "M\u00e9g nincs kiv\u00e1lasztott term\u00e9k", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SelectionNone", "M\u00e9g nincs kiv\u00e1lasztott term\u00e9k", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SelectionItems", "product(s) selected", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SelectionItems", "product(s) selected", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SelectionItems", "term\u00e9k kiv\u00e1lasztva", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SelectionItems", "term\u00e9k kiv\u00e1lasztva", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SubmitButton", "Place preorder", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SubmitButton", "Place preorder", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SubmitButton", "El\u0151rendel\u00e9s lead\u00e1sa", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SubmitButton", "El\u0151rendel\u00e9s lead\u00e1sa", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.Submitting", "Placing preorder...", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.Submitting", "Placing preorder...", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.Submitting", "El\u0151rendel\u00e9s ment\u00e9se...", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.Submitting", "El\u0151rendel\u00e9s ment\u00e9se...", hu);
// Summary panel // Summary panel
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SummaryTitle", "Your preorder", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SummaryTitle", "Your preorder", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SummaryTitle", "El\u0151rendel\u00e9sed", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SummaryTitle", "El\u0151rendel\u00e9sed", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SummaryEmpty", "Set quantities above to build your preorder.", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SummaryEmpty", "Set quantities above to build your preorder.", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SummaryEmpty", "Add meg a mennyis\u00e9geket a term\u00e9kekn\u00e9l.", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SummaryEmpty", "Add meg a mennyis\u00e9geket a term\u00e9kekn\u00e9l.", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SummaryNote", "Prices for weighed items will be finalised after measurement. Preorder quantities may change depending on actual shipment.", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SummaryNote", "Prices for weighed items will be finalised after measurement. PreOrder quantities may change depending on actual shipment.", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SummaryNote", "A s\u00falym\u00e9r\u00e9st ig\u00e9nyl\u0151 t\u00e9teleikn\u00e9l az \u00e1r a m\u00e9r\u00e9s ut\u00e1n v\u00e9glegesedik. A mennyis\u00e9gek a t\u00e9nyleges sz\u00e1ll\u00edtm\u00e1nyt\u00f3l f\u00fcgg\u0151en v\u00e1ltozhatnak.", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SummaryNote", "A s\u00falym\u00e9r\u00e9st ig\u00e9nyl\u0151 t\u00e9teleikn\u00e9l az \u00e1r a m\u00e9r\u00e9s ut\u00e1n v\u00e9glegesedik. A mennyis\u00e9gek a t\u00e9nyleges sz\u00e1ll\u00edtm\u00e1nyt\u00f3l f\u00fcgg\u0151en v\u00e1ltozhatnak.", hu);
// Success + errors // Success + errors
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SuccessTitle", "Preorder placed!", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SuccessTitle", "PreOrder placed!", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SuccessTitle", "El\u0151rendel\u00e9s leadva!", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SuccessTitle", "El\u0151rendel\u00e9s leadva!", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SuccessMessage", "Your preorder #{0} has been received. We will notify you when the shipment arrives.", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SuccessMessage", "Your preorder #{0} has been received. We will notify you when the shipment arrives.", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.SuccessMessage", "#{0} sz\u00e1m\u00fa el\u0151rendel\u00e9sed be\u00e9rkezett. A sz\u00e1ll\u00edtm\u00e1ny meger\u0151s\u00edt\u00e9sekor \u00e9rtes\u00edt\u00fcnk.", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.SuccessMessage", "#{0} sz\u00e1m\u00fa el\u0151rendel\u00e9sed be\u00e9rkezett. A sz\u00e1ll\u00edtm\u00e1ny meger\u0151s\u00edt\u00e9sekor \u00e9rtes\u00edt\u00fcnk.", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.BackToHome", "Back to home", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.BackToHome", "Back to home", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.BackToHome", "Vissza a f\u0151oldalra", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.BackToHome", "Vissza a f\u0151oldalra", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.ErrorPrefix", "Error: ", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.ErrorPrefix", "Error: ", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.ErrorPrefix", "Hiba: ", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.ErrorPrefix", "Hiba: ", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NotLoggedIn", "Not logged in", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NotLoggedIn", "Not logged in", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NotLoggedIn", "Nincs bejelentkezve", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NotLoggedIn", "Nincs bejelentkezve", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NoItemsSelected", "No items selected", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NoItemsSelected", "No items selected", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NoItemsSelected", "Nincs kiv\u00e1lasztott term\u00e9k", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NoItemsSelected", "Nincs kiv\u00e1lasztott term\u00e9k", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NoValidItems", "No valid items in preorder", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NoValidItems", "No valid items in preorder", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NoValidItems", "Nincs \u00e9rv\u00e9nyes term\u00e9k az el\u0151rendel\u00e9sben", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NoValidItems", "Nincs \u00e9rv\u00e9nyes term\u00e9k az el\u0151rendel\u00e9sben", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NoDeliveryDateTimeProvided", "No delivery date/time provided", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NoDeliveryDateTimeProvided", "No delivery date/time provided", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.NoDeliveryDateTimeProvided", "Nincs sz\u00e1ll\u00edt\u00e1si id\u0151pont megadva", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.NoDeliveryDateTimeProvided", "Nincs sz\u00e1ll\u00edt\u00e1si id\u0151pont megadva", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.InvalidDeliveryDateTime", "Invalid delivery date/time format", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.InvalidDeliveryDateTime", "Invalid delivery date/time format", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.InvalidDeliveryDateTime", "\u00c9rv\u00e9nytelen sz\u00e1ll\u00edt\u00e1si d\u00e1tum/id\u0151 form\u00e1tum", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.InvalidDeliveryDateTime", "\u00c9rv\u00e9nytelen sz\u00e1ll\u00edt\u00e1si d\u00e1tum/id\u0151 form\u00e1tum", hu);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.PlacedSuccessfully", "Preorder placed successfully", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.PlacedSuccessfully", "PreOrder placed successfully", en);
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.Preorder.PlacedSuccessfully", "El\u0151rendel\u00e9s sikeresen leadva", hu); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.PreOrder.PlacedSuccessfully", "El\u0151rendel\u00e9s sikeresen leadva", hu);
// ── Customer Credit ──────────────────────────────────────────────────── // ── Customer Credit ────────────────────────────────────────────────────
await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.CustomerCredit.PageTitle", "Customer Credit Management", en); await _localizationService.AddOrUpdateLocaleResourceAsync("Plugins.Misc.FruitBankPlugin.CustomerCredit.PageTitle", "Customer Credit Management", en);
@ -464,7 +464,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin
} }
else if (widgetZone == PublicWidgetZones.AccountNavigationAfter) else if (widgetZone == PublicWidgetZones.AccountNavigationAfter)
{ {
return typeof(CustomerPreorderNavViewComponent); return typeof(CustomerPreOrderNavViewComponent);
} }
} }

View File

@ -94,9 +94,9 @@ public class PluginNopStartup : INopStartup
services.AddScoped<StockTakingItemDbTable>(); services.AddScoped<StockTakingItemDbTable>();
services.AddScoped<StockTakingItemPalletDbTable>(); services.AddScoped<StockTakingItemPalletDbTable>();
services.AddScoped<CustomerCreditDbTable>(); services.AddScoped<CustomerCreditDbTable>();
services.AddScoped<PreorderDbTable>(); services.AddScoped<PreOrderDbTable>();
services.AddScoped<PreorderItemDbTable>(); services.AddScoped<PreOrderItemDbTable>();
services.AddScoped<PreorderDbContext>(); services.AddScoped<PreOrderDbContext>();
services.AddScoped<StockTakingDbContext>(); services.AddScoped<StockTakingDbContext>();
services.AddScoped<FruitBankDbContext>(); services.AddScoped<FruitBankDbContext>();
@ -151,7 +151,7 @@ public class PluginNopStartup : INopStartup
services.AddScoped<PdfToImageService>(); services.AddScoped<PdfToImageService>();
services.AddScoped<FruitBankNotificationService>(); services.AddScoped<FruitBankNotificationService>();
services.AddScoped<FruitBankOrderItemService>(); services.AddScoped<FruitBankOrderItemService>();
services.AddScoped<PreorderConversionService>(); services.AddScoped<PreOrderConversionService>();
services.AddSingleton<IFileStorageProvider>(sp => services.AddSingleton<IFileStorageProvider>(sp =>
new LocalFileStorageProvider() // Uses default wwwroot/uploads new LocalFileStorageProvider() // Uses default wwwroot/uploads
// Or specify custom path: // Or specify custom path:

View File

@ -197,57 +197,57 @@ public class RouteProvider : IRouteProvider
pattern: "Admin/CustomerCredit/UpdateCreditLimit", pattern: "Admin/CustomerCredit/UpdateCreditLimit",
defaults: new { controller = "CustomerCredit", action = "UpdateCreditLimit", area = AreaNames.ADMIN }); defaults: new { controller = "CustomerCredit", action = "UpdateCreditLimit", area = AreaNames.ADMIN });
// ── Admin: Preorder list ─────────────────────────────────────────── // ── Admin: PreOrder list ───────────────────────────────────────────
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Preorders.List", name: "Plugin.FruitBank.PreOrders.List",
pattern: "Admin/Preorders", pattern: "Admin/PreOrders",
defaults: new { controller = "PreorderAdmin", action = "List", area = AreaNames.ADMIN }); defaults: new { controller = "PreOrderAdmin", action = "List", area = AreaNames.ADMIN });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Preorders.PreorderList", name: "Plugin.FruitBank.PreOrders.PreOrderList",
pattern: "Admin/Preorders/PreorderList", pattern: "Admin/PreOrders/PreOrderList",
defaults: new { controller = "PreorderAdmin", action = "PreorderList", area = AreaNames.ADMIN }); defaults: new { controller = "PreOrderAdmin", action = "PreOrderList", area = AreaNames.ADMIN });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Preorders.Detail", name: "Plugin.FruitBank.PreOrders.Detail",
pattern: "Admin/Preorders/Detail/{id:int}", pattern: "Admin/PreOrders/Detail/{id:int}",
defaults: new { controller = "PreorderAdmin", action = "Detail", area = AreaNames.ADMIN }); defaults: new { controller = "PreOrderAdmin", action = "Detail", area = AreaNames.ADMIN });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Preorders.Cancel", name: "Plugin.FruitBank.PreOrders.Cancel",
pattern: "Admin/Preorders/Cancel/{id:int}", pattern: "Admin/PreOrders/Cancel/{id:int}",
defaults: new { controller = "PreorderAdmin", action = "Cancel", area = AreaNames.ADMIN }); defaults: new { controller = "PreOrderAdmin", action = "Cancel", area = AreaNames.ADMIN });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Preorders.CreatePreorder", name: "Plugin.FruitBank.PreOrders.CreatePreOrder",
pattern: "Admin/Preorders/CreatePreorder", pattern: "Admin/PreOrders/CreatePreOrder",
defaults: new { controller = "PreorderAdmin", action = "CreatePreorder", area = AreaNames.ADMIN }); defaults: new { controller = "PreOrderAdmin", action = "CreatePreOrder", area = AreaNames.ADMIN });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Preorders.DemandList", name: "Plugin.FruitBank.PreOrders.DemandList",
pattern: "Admin/Preorders/DemandList", pattern: "Admin/PreOrders/DemandList",
defaults: new { controller = "PreorderAdmin", action = "DemandList", area = AreaNames.ADMIN }); defaults: new { controller = "PreOrderAdmin", action = "DemandList", area = AreaNames.ADMIN });
// ── Admin: Preorder availability ───────────────────────────────── // ── Admin: PreOrder availability ─────────────────────────────────
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.PreorderAvailability.Index", name: "Plugin.FruitBank.PreOrderAvailability.Index",
pattern: "Admin/PreorderAvailability", pattern: "Admin/PreOrderAvailability",
defaults: new { controller = "PreorderAvailability", action = "Index", area = AreaNames.ADMIN }); defaults: new { controller = "PreOrderAvailability", action = "Index", area = AreaNames.ADMIN });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.PreorderAvailability.ProductList", name: "Plugin.FruitBank.PreOrderAvailability.ProductList",
pattern: "Admin/PreorderAvailability/ProductList", pattern: "Admin/PreOrderAvailability/ProductList",
defaults: new { controller = "PreorderAvailability", action = "ProductList", area = AreaNames.ADMIN }); defaults: new { controller = "PreOrderAvailability", action = "ProductList", area = AreaNames.ADMIN });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.PreorderAvailability.AvailableTodayList", name: "Plugin.FruitBank.PreOrderAvailability.AvailableTodayList",
pattern: "Admin/PreorderAvailability/AvailableTodayList", pattern: "Admin/PreOrderAvailability/AvailableTodayList",
defaults: new { controller = "PreorderAvailability", action = "AvailableTodayList", area = AreaNames.ADMIN }); defaults: new { controller = "PreOrderAvailability", action = "AvailableTodayList", area = AreaNames.ADMIN });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.PreorderAvailability.SaveWindow", name: "Plugin.FruitBank.PreOrderAvailability.SaveWindow",
pattern: "Admin/PreorderAvailability/SaveWindow", pattern: "Admin/PreOrderAvailability/SaveWindow",
defaults: new { controller = "PreorderAvailability", action = "SaveWindow", area = AreaNames.ADMIN }); defaults: new { controller = "PreOrderAvailability", action = "SaveWindow", area = AreaNames.ADMIN });
// ── Public: Unified Order flow ───────────────────────────────────────── // ── Public: Unified Order flow ─────────────────────────────────────────
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
@ -271,9 +271,9 @@ public class RouteProvider : IRouteProvider
defaults: new { controller = "Order", action = "GetAllProducts" }); defaults: new { controller = "Order", action = "GetAllProducts" });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Order.GetPreorderProducts", name: "Plugin.FruitBank.Order.GetPreOrderProducts",
pattern: "rendeles/elozetes-termekek", pattern: "rendeles/elozetes-termekek",
defaults: new { controller = "Order", action = "GetPreorderProducts" }); defaults: new { controller = "Order", action = "GetPreOrderProducts" });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Order.SearchProducts", name: "Plugin.FruitBank.Order.SearchProducts",
@ -296,9 +296,9 @@ public class RouteProvider : IRouteProvider
defaults: new { controller = "Order", action = "GetCartItems" }); defaults: new { controller = "Order", action = "GetCartItems" });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Order.PlacePreorder", name: "Plugin.FruitBank.Order.PlacePreOrder",
pattern: "rendeles/elozetes-leadás", pattern: "rendeles/elozetes-leadás",
defaults: new { controller = "Order", action = "PlacePreorder" }); defaults: new { controller = "Order", action = "PlacePreOrder" });
// ── Public: Help page ─────────────────────────────────────────────────── // ── Public: Help page ───────────────────────────────────────────────────
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
@ -308,35 +308,35 @@ public class RouteProvider : IRouteProvider
// ── Public: Customer preorder list ─────────────────────────────────────────── // ── Public: Customer preorder list ───────────────────────────────────────────
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.CustomerPreorder.List", name: "Plugin.FruitBank.CustomerPreOrder.List",
pattern: "fiokom/elorerendeles-aim", pattern: "fiokom/elorerendeles-aim",
defaults: new { controller = "CustomerPreorder", action = "List" }); defaults: new { controller = "CustomerPreOrder", action = "List" });
// ── Public: Preorder (legacy, kept for backward compat) ─────────────── // ── Public: PreOrder (legacy, kept for backward compat) ───────────────
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Preorder.Index", name: "Plugin.FruitBank.PreOrder.Index",
pattern: "elozetes-rendeles", pattern: "elozetes-rendeles",
defaults: new { controller = "Preorder", action = "Index" }); defaults: new { controller = "PreOrder", action = "Index" });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Preorder.GetDeliveryDateTime", name: "Plugin.FruitBank.PreOrder.GetDeliveryDateTime",
pattern: "elozetes-rendeles/szallitas-idopont", pattern: "elozetes-rendeles/szallitas-idopont",
defaults: new { controller = "Preorder", action = "GetDeliveryDateTime" }); defaults: new { controller = "PreOrder", action = "GetDeliveryDateTime" });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Preorder.SetDeliveryDateTime", name: "Plugin.FruitBank.PreOrder.SetDeliveryDateTime",
pattern: "elozetes-rendeles/szallitas-idopont-beallitas", pattern: "elozetes-rendeles/szallitas-idopont-beallitas",
defaults: new { controller = "Preorder", action = "SetDeliveryDateTime" }); defaults: new { controller = "PreOrder", action = "SetDeliveryDateTime" });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Preorder.GetAvailableProducts", name: "Plugin.FruitBank.PreOrder.GetAvailableProducts",
pattern: "elozetes-rendeles/termekek", pattern: "elozetes-rendeles/termekek",
defaults: new { controller = "Preorder", action = "GetAvailableProducts" }); defaults: new { controller = "PreOrder", action = "GetAvailableProducts" });
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Preorder.PlacePreorder", name: "Plugin.FruitBank.PreOrder.PlacePreOrder",
pattern: "elozetes-rendeles/leadás", pattern: "elozetes-rendeles/leadás",
defaults: new { controller = "Preorder", action = "PlacePreorder" }); defaults: new { controller = "PreOrder", action = "PlacePreOrder" });
// ── Public: Quick Order ────────────────────────────────────────────── // ── Public: Quick Order ──────────────────────────────────────────────
endpointRouteBuilder.MapControllerRoute( endpointRouteBuilder.MapControllerRoute(

View File

@ -2,142 +2,142 @@
<Language Name="English" IsDefault="false" IsRightToLeft="false"> <Language Name="English" IsDefault="false" IsRightToLeft="false">
<!-- ═══════════════════════════════════════════════════════════ <!-- ═══════════════════════════════════════════════════════════
Preorder page — Plugins.Misc.FruitBankPlugin.Preorder.* PreOrder page — Plugins.Misc.FruitBankPlugin.PreOrder.*
Import: Admin > Configuration > Languages > [English] > Import resources Import: Admin > Configuration > Languages > [English] > Import resources
═══════════════════════════════════════════════════════════ --> ═══════════════════════════════════════════════════════════ -->
<!-- General --> <!-- General -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.PageTitle"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.PageTitle">
<Value><![CDATA[Preorder]]></Value> <Value><![CDATA[PreOrder]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.MenuLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.MenuLabel">
<Value><![CDATA[Preorder]]></Value> <Value><![CDATA[PreOrder]]></Value>
</LocaleResource> </LocaleResource>
<!-- Delivery step --> <!-- Delivery step -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Title"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Title">
<Value><![CDATA[When do you want to receive your preorder?]]></Value> <Value><![CDATA[When do you want to receive your preorder?]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Subtitle"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Subtitle">
<Value><![CDATA[Choose a delivery day and time (we'll confirm availability)]]></Value> <Value><![CDATA[Choose a delivery day and time (we'll confirm availability)]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.DayLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.DayLabel">
<Value><![CDATA[Delivery day]]></Value> <Value><![CDATA[Delivery day]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.TimeLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.TimeLabel">
<Value><![CDATA[Delivery time]]></Value> <Value><![CDATA[Delivery time]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.TimeHint"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.TimeHint">
<Value><![CDATA[Choose an exact time]]></Value> <Value><![CDATA[Choose an exact time]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ConfirmButton"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ConfirmButton">
<Value><![CDATA[Show available products]]></Value> <Value><![CDATA[Show available products]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ChangeLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ChangeLabel">
<Value><![CDATA[Delivery:]]></Value> <Value><![CDATA[Delivery:]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ChangeButton"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ChangeButton">
<Value><![CDATA[Change]]></Value> <Value><![CDATA[Change]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Today"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Today">
<Value><![CDATA[Today]]></Value> <Value><![CDATA[Today]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Tomorrow"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Tomorrow">
<Value><![CDATA[Tomorrow]]></Value> <Value><![CDATA[Tomorrow]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Saving"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Saving">
<Value><![CDATA[Saving...]]></Value> <Value><![CDATA[Saving...]]></Value>
</LocaleResource> </LocaleResource>
<!-- Product list --> <!-- Product list -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.InfoBanner"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.InfoBanner">
<Value><![CDATA[Preorders are wishes — we will confirm availability when the shipment arrives and notify you of any changes.]]></Value> <Value><![CDATA[PreOrders are wishes — we will confirm availability when the shipment arrives and notify you of any changes.]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.LoadingProducts"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.LoadingProducts">
<Value><![CDATA[Loading available products...]]></Value> <Value><![CDATA[Loading available products...]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NoProductsAvailable"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NoProductsAvailable">
<Value><![CDATA[No products are currently available for preorder. Please check back later.]]></Value> <Value><![CDATA[No products are currently available for preorder. Please check back later.]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.ProductsLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.ProductsLabel">
<Value><![CDATA[Available for preorder — set quantities:]]></Value> <Value><![CDATA[Available for preorder — set quantities:]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.MeasurableBadge"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.MeasurableBadge">
<Value><![CDATA[Requires weighing]]></Value> <Value><![CDATA[Requires weighing]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.PricePerPiece"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.PricePerPiece">
<Value><![CDATA[Ft/pcs]]></Value> <Value><![CDATA[Ft/pcs]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.PieceUnit"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.PieceUnit">
<Value><![CDATA[pcs]]></Value> <Value><![CDATA[pcs]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.StockLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.StockLabel">
<Value><![CDATA[Incoming stock:]]></Value> <Value><![CDATA[Incoming stock:]]></Value>
</LocaleResource> </LocaleResource>
<!-- Note and submit --> <!-- Note and submit -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NoteLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NoteLabel">
<Value><![CDATA[Additional note (optional)]]></Value> <Value><![CDATA[Additional note (optional)]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NotePlaceholder"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NotePlaceholder">
<Value><![CDATA[Any special requests or notes for this preorder...]]></Value> <Value><![CDATA[Any special requests or notes for this preorder...]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SelectionNone"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SelectionNone">
<Value><![CDATA[No products selected yet]]></Value> <Value><![CDATA[No products selected yet]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SelectionItems"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SelectionItems">
<Value><![CDATA[product(s) selected]]></Value> <Value><![CDATA[product(s) selected]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SubmitButton"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SubmitButton">
<Value><![CDATA[Place preorder]]></Value> <Value><![CDATA[Place preorder]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.Submitting"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.Submitting">
<Value><![CDATA[Placing preorder...]]></Value> <Value><![CDATA[Placing preorder...]]></Value>
</LocaleResource> </LocaleResource>
<!-- Summary panel --> <!-- Summary panel -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SummaryTitle"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SummaryTitle">
<Value><![CDATA[Your preorder]]></Value> <Value><![CDATA[Your preorder]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SummaryEmpty"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SummaryEmpty">
<Value><![CDATA[Set quantities above to build your preorder.]]></Value> <Value><![CDATA[Set quantities above to build your preorder.]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SummaryNote"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SummaryNote">
<Value><![CDATA[Prices for weighed items will be finalised after measurement. Preorder quantities may change depending on actual shipment.]]></Value> <Value><![CDATA[Prices for weighed items will be finalised after measurement. PreOrder quantities may change depending on actual shipment.]]></Value>
</LocaleResource> </LocaleResource>
<!-- Success --> <!-- Success -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SuccessTitle"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SuccessTitle">
<Value><![CDATA[Preorder placed!]]></Value> <Value><![CDATA[PreOrder placed!]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SuccessMessage"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SuccessMessage">
<Value><![CDATA[Your preorder #{0} has been received. We will notify you when the shipment arrives.]]></Value> <Value><![CDATA[Your preorder #{0} has been received. We will notify you when the shipment arrives.]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.BackToHome"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.BackToHome">
<Value><![CDATA[Back to home]]></Value> <Value><![CDATA[Back to home]]></Value>
</LocaleResource> </LocaleResource>
<!-- Error messages --> <!-- Error messages -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.ErrorPrefix"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.ErrorPrefix">
<Value><![CDATA[Error: ]]></Value> <Value><![CDATA[Error: ]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NotLoggedIn"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NotLoggedIn">
<Value><![CDATA[Not logged in]]></Value> <Value><![CDATA[Not logged in]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NoItemsSelected"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NoItemsSelected">
<Value><![CDATA[No items selected]]></Value> <Value><![CDATA[No items selected]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NoValidItems"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NoValidItems">
<Value><![CDATA[No valid items in preorder]]></Value> <Value><![CDATA[No valid items in preorder]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NoDeliveryDateTimeProvided"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NoDeliveryDateTimeProvided">
<Value><![CDATA[No delivery date/time provided]]></Value> <Value><![CDATA[No delivery date/time provided]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.InvalidDeliveryDateTime"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.InvalidDeliveryDateTime">
<Value><![CDATA[Invalid delivery date/time format]]></Value> <Value><![CDATA[Invalid delivery date/time format]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.PlacedSuccessfully"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.PlacedSuccessfully">
<Value><![CDATA[Preorder placed successfully]]></Value> <Value><![CDATA[PreOrder placed successfully]]></Value>
</LocaleResource> </LocaleResource>
</Language> </Language>

View File

@ -2,141 +2,141 @@
<Language Name="Hungarian" IsDefault="false" IsRightToLeft="false"> <Language Name="Hungarian" IsDefault="false" IsRightToLeft="false">
<!-- ═══════════════════════════════════════════════════════════ <!-- ═══════════════════════════════════════════════════════════
Előrendelés oldal — Plugins.Misc.FruitBankPlugin.Preorder.* Előrendelés oldal — Plugins.Misc.FruitBankPlugin.PreOrder.*
Import: Admin > Konfiguráció > Nyelvek > [Magyar] > Erőforrások importálása Import: Admin > Konfiguráció > Nyelvek > [Magyar] > Erőforrások importálása
═══════════════════════════════════════════════════════════ --> ═══════════════════════════════════════════════════════════ -->
<!-- Általános --> <!-- Általános -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.PageTitle"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.PageTitle">
<Value><![CDATA[Előrendelés]]></Value> <Value><![CDATA[Előrendelés]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.MenuLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.MenuLabel">
<Value><![CDATA[Előrendelés]]></Value> <Value><![CDATA[Előrendelés]]></Value>
</LocaleResource> </LocaleResource>
<!-- Szállítási időpont lépés --> <!-- Szállítási időpont lépés -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Title"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Title">
<Value><![CDATA[Mikor kéred a rendelést?]]></Value> <Value><![CDATA[Mikor kéred a rendelést?]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Subtitle"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Subtitle">
<Value><![CDATA[Válassz szállítási napot és időpontot (az elérhetőséget megerősítjük)]]></Value> <Value><![CDATA[Válassz szállítási napot és időpontot (az elérhetőséget megerősítjük)]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.DayLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.DayLabel">
<Value><![CDATA[Kívánt szállítási nap]]></Value> <Value><![CDATA[Kívánt szállítási nap]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.TimeLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.TimeLabel">
<Value><![CDATA[Kívánt időpont]]></Value> <Value><![CDATA[Kívánt időpont]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.TimeHint"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.TimeHint">
<Value><![CDATA[Válassz pontos időpontot]]></Value> <Value><![CDATA[Válassz pontos időpontot]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ConfirmButton"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ConfirmButton">
<Value><![CDATA[Elérhető termékek mutatása]]></Value> <Value><![CDATA[Elérhető termékek mutatása]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ChangeLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ChangeLabel">
<Value><![CDATA[Szállítás:]]></Value> <Value><![CDATA[Szállítás:]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ChangeButton"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ChangeButton">
<Value><![CDATA[Módosítás]]></Value> <Value><![CDATA[Módosítás]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Today"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Today">
<Value><![CDATA[Ma]]></Value> <Value><![CDATA[Ma]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Tomorrow"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Tomorrow">
<Value><![CDATA[Holnap]]></Value> <Value><![CDATA[Holnap]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Saving"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Saving">
<Value><![CDATA[Mentés...]]></Value> <Value><![CDATA[Mentés...]]></Value>
</LocaleResource> </LocaleResource>
<!-- Terméklista --> <!-- Terméklista -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.InfoBanner"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.InfoBanner">
<Value><![CDATA[Az előrendelés egy kívánságlista — az áruk megerősítése a szállítmány beérkezésekor történik, és az esetleges változásokról értesítünk.]]></Value> <Value><![CDATA[Az előrendelés egy kívánságlista — az áruk megerősítése a szállítmány beérkezésekor történik, és az esetleges változásokról értesítünk.]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.LoadingProducts"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.LoadingProducts">
<Value><![CDATA[Elérhető termékek betöltése...]]></Value> <Value><![CDATA[Elérhető termékek betöltése...]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NoProductsAvailable"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NoProductsAvailable">
<Value><![CDATA[Jelenleg nincs előrendelhető termék. Kérjük, látogass vissza később.]]></Value> <Value><![CDATA[Jelenleg nincs előrendelhető termék. Kérjük, látogass vissza később.]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.ProductsLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.ProductsLabel">
<Value><![CDATA[Előrendelhető termékek — add meg a mennyiségeket:]]></Value> <Value><![CDATA[Előrendelhető termékek — add meg a mennyiségeket:]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.MeasurableBadge"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.MeasurableBadge">
<Value><![CDATA[Súlymérést igényel]]></Value> <Value><![CDATA[Súlymérést igényel]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.PricePerPiece"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.PricePerPiece">
<Value><![CDATA[Ft/db]]></Value> <Value><![CDATA[Ft/db]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.PieceUnit"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.PieceUnit">
<Value><![CDATA[db]]></Value> <Value><![CDATA[db]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.StockLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.StockLabel">
<Value><![CDATA[Várható készlet:]]></Value> <Value><![CDATA[Várható készlet:]]></Value>
</LocaleResource> </LocaleResource>
<!-- Megjegyzés és leadás --> <!-- Megjegyzés és leadás -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NoteLabel"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NoteLabel">
<Value><![CDATA[Megjegyzés (nem kötelező)]]></Value> <Value><![CDATA[Megjegyzés (nem kötelező)]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NotePlaceholder"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NotePlaceholder">
<Value><![CDATA[Esetleges megjegyzések az előrendeléssel kapcsolatban...]]></Value> <Value><![CDATA[Esetleges megjegyzések az előrendeléssel kapcsolatban...]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SelectionNone"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SelectionNone">
<Value><![CDATA[Még nincs kiválasztott termék]]></Value> <Value><![CDATA[Még nincs kiválasztott termék]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SelectionItems"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SelectionItems">
<Value><![CDATA[termék kiválasztva]]></Value> <Value><![CDATA[termék kiválasztva]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SubmitButton"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SubmitButton">
<Value><![CDATA[Előrendelés leadása]]></Value> <Value><![CDATA[Előrendelés leadása]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.Submitting"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.Submitting">
<Value><![CDATA[Előrendelés mentése...]]></Value> <Value><![CDATA[Előrendelés mentése...]]></Value>
</LocaleResource> </LocaleResource>
<!-- Összefoglaló panel --> <!-- Összefoglaló panel -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SummaryTitle"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SummaryTitle">
<Value><![CDATA[Előrendelésed]]></Value> <Value><![CDATA[Előrendelésed]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SummaryEmpty"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SummaryEmpty">
<Value><![CDATA[Add meg a mennyiségeket a termékeknél.]]></Value> <Value><![CDATA[Add meg a mennyiségeket a termékeknél.]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SummaryNote"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SummaryNote">
<Value><![CDATA[A súlymérést igénylő tételeknél az ár a mérés után véglegesedik. A mennyiségek a tényleges szállítmánytól függően változhatnak.]]></Value> <Value><![CDATA[A súlymérést igénylő tételeknél az ár a mérés után véglegesedik. A mennyiségek a tényleges szállítmánytól függően változhatnak.]]></Value>
</LocaleResource> </LocaleResource>
<!-- Sikeres leadás --> <!-- Sikeres leadás -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SuccessTitle"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SuccessTitle">
<Value><![CDATA[Előrendelés leadva!]]></Value> <Value><![CDATA[Előrendelés leadva!]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.SuccessMessage"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.SuccessMessage">
<Value><![CDATA[#{0} számú előrendelésed beérkezett. A szállítmány megerősítésekor értesítünk.]]></Value> <Value><![CDATA[#{0} számú előrendelésed beérkezett. A szállítmány megerősítésekor értesítünk.]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.BackToHome"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.BackToHome">
<Value><![CDATA[Vissza a főoldalra]]></Value> <Value><![CDATA[Vissza a főoldalra]]></Value>
</LocaleResource> </LocaleResource>
<!-- Hibaüzenetek --> <!-- Hibaüzenetek -->
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.ErrorPrefix"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.ErrorPrefix">
<Value><![CDATA[Hiba: ]]></Value> <Value><![CDATA[Hiba: ]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NotLoggedIn"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NotLoggedIn">
<Value><![CDATA[Nincs bejelentkezve]]></Value> <Value><![CDATA[Nincs bejelentkezve]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NoItemsSelected"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NoItemsSelected">
<Value><![CDATA[Nincs kiválasztott termék]]></Value> <Value><![CDATA[Nincs kiválasztott termék]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NoValidItems"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NoValidItems">
<Value><![CDATA[Nincs érvényes termék az előrendelésben]]></Value> <Value><![CDATA[Nincs érvényes termék az előrendelésben]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.NoDeliveryDateTimeProvided"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.NoDeliveryDateTimeProvided">
<Value><![CDATA[Nincs szállítási időpont megadva]]></Value> <Value><![CDATA[Nincs szállítási időpont megadva]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.InvalidDeliveryDateTime"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.InvalidDeliveryDateTime">
<Value><![CDATA[Érvénytelen szállítási dátum/idő formátum]]></Value> <Value><![CDATA[Érvénytelen szállítási dátum/idő formátum]]></Value>
</LocaleResource> </LocaleResource>
<LocaleResource Name="Plugins.Misc.FruitBankPlugin.Preorder.PlacedSuccessfully"> <LocaleResource Name="Plugins.Misc.FruitBankPlugin.PreOrder.PlacedSuccessfully">
<Value><![CDATA[Előrendelés sikeresen leadva]]></Value> <Value><![CDATA[Előrendelés sikeresen leadva]]></Value>
</LocaleResource> </LocaleResource>

View File

@ -44,8 +44,8 @@ public partial class NameCompatibility : INameCompatibility
{ typeof(StockTakingItemPallet), FruitBankConstClient.StockTakingItemPalletDbTableName}, { typeof(StockTakingItemPallet), FruitBankConstClient.StockTakingItemPalletDbTableName},
{ typeof(CustomerCredit), FruitBankConstClient.CustomerCreditDbTableName}, { typeof(CustomerCredit), FruitBankConstClient.CustomerCreditDbTableName},
{ typeof(Preorder), FruitBankConstClient.PreOrderDbTableName}, { typeof(PreOrder), FruitBankConstClient.PreOrderDbTableName},
{ typeof(PreorderItem), FruitBankConstClient.PreOrderItemDbTableName}, { typeof(PreOrderItem), FruitBankConstClient.PreOrderItemDbTableName},
{ typeof(CargoPartner), FruitBankConstClient.CargoPartnerDbTableName}, { typeof(CargoPartner), FruitBankConstClient.CargoPartnerDbTableName},
{ typeof(CargoTruck), FruitBankConstClient.CargoTruckDbTableName}, { typeof(CargoTruck), FruitBankConstClient.CargoTruckDbTableName},

View File

@ -224,13 +224,13 @@
<None Update="Areas\Admin\Views\Order\List.cshtml"> <None Update="Areas\Admin\Views\Order\List.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Areas\Admin\Views\PreorderAvailability\Index.cshtml"> <None Update="Areas\Admin\Views\PreOrderAvailability\Index.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Areas\Admin\Views\Preorder\Detail.cshtml"> <None Update="Areas\Admin\Views\PreOrder\Detail.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Areas\Admin\Views\Preorder\List.cshtml"> <None Update="Areas\Admin\Views\PreOrder\List.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Areas\Admin\Views\Product\List.cshtml"> <None Update="Areas\Admin\Views\Product\List.cshtml">
@ -677,10 +677,10 @@
<None Update="Views\CustomerCreditWidget.cshtml"> <None Update="Views\CustomerCreditWidget.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Views\CustomerPreorder\List.cshtml"> <None Update="Views\CustomerPreOrder\List.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Views\CustomerPreorder\NavItem.cshtml"> <None Update="Views\CustomerPreOrder\NavItem.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="Views\Help\Index.cshtml"> <None Update="Views\Help\Index.cshtml">
@ -689,7 +689,7 @@
<None Update="Views\Order\Index.cshtml"> <None Update="Views\Order\Index.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Views\Preorder\Index.cshtml"> <None Update="Views\PreOrder\Index.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Views\ProductAIListWidget.cshtml"> <None Update="Views\ProductAIListWidget.cshtml">

View File

@ -260,10 +260,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
var preorderAvailabilityMenuItem = new AdminMenuItem var preorderAvailabilityMenuItem = new AdminMenuItem
{ {
Visible = true, Visible = true,
SystemName = "PreorderAvailability", SystemName = "PreOrderAvailability",
Title = "Előrendelés — elérhetőség", Title = "Előrendelés — elérhetőség",
IconClass = "fas fa-calendar-check", IconClass = "fas fa-calendar-check",
Url = _adminMenu.GetMenuItemUrl("PreorderAvailability", "Index") Url = _adminMenu.GetMenuItemUrl("PreOrderAvailability", "Index")
}; };
//shippingConfigurationItem.ChildNodes.Insert(4, preorderAvailabilityMenuItem); //shippingConfigurationItem.ChildNodes.Insert(4, preorderAvailabilityMenuItem);
@ -271,10 +271,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
var preorderListMenuItem = new AdminMenuItem var preorderListMenuItem = new AdminMenuItem
{ {
Visible = true, Visible = true,
SystemName = "Preorders.List", SystemName = "PreOrders.List",
Title = "Előrendelések", Title = "Előrendelések",
IconClass = "fas fa-calendar-plus", IconClass = "fas fa-calendar-plus",
Url = _adminMenu.GetMenuItemUrl("PreorderAdmin", "List") Url = _adminMenu.GetMenuItemUrl("PreOrderAdmin", "List")
}; };
var preordersRootMenuItem = new AdminMenuItem var preordersRootMenuItem = new AdminMenuItem
@ -282,7 +282,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
Visible = true, Visible = true,
SystemName = "FruitBank", SystemName = "FruitBank",
Title = "Előrendelés", Title = "Előrendelés",
//Title = await _localizationService.GetResourceAsync("Plugins.Misc.FruitBankPlugin.Menu.Preorders"), // You can localize this with await _localizationService.GetResourceAsync("...") //Title = await _localizationService.GetResourceAsync("Plugins.Misc.FruitBankPlugin.Menu.PreOrders"), // You can localize this with await _localizationService.GetResourceAsync("...")
IconClass = "fas fa-heart", IconClass = "fas fa-heart",
//Url = _adminMenu.GetMenuItemUrl("Shipping", "List") //Url = _adminMenu.GetMenuItemUrl("Shipping", "List")
//ChildNodes = [shippingsListMenuItem, createShippingMenuItem, editShippingMenuItem] //ChildNodes = [shippingsListMenuItem, createShippingMenuItem, editShippingMenuItem]

View File

@ -20,7 +20,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services;
/// <summary> /// <summary>
/// Shared service for creating, adding and removing order items. /// Shared service for creating, adding and removing order items.
/// Extracted from CustomOrderController so the same logic can be reused /// Extracted from CustomOrderController so the same logic can be reused
/// by PreorderConversionService without duplication. /// by PreOrderConversionService without duplication.
/// </summary> /// </summary>
public class FruitBankOrderItemService public class FruitBankOrderItemService
{ {

View File

@ -10,13 +10,13 @@ using Nop.Services.Orders;
namespace Nop.Plugin.Misc.FruitBankPlugin.Services; namespace Nop.Plugin.Misc.FruitBankPlugin.Services;
/// <summary> /// <summary>
/// Extension methods for product replacement logic on PreorderConversionService. /// Extension methods for product replacement logic on PreOrderConversionService.
/// ///
/// SETUP REQUIRED: Add this as a partial class by: /// SETUP REQUIRED: Add this as a partial class by:
/// 1. Add `partial` keyword to PreorderConversionService class declaration /// 1. Add `partial` keyword to PreOrderConversionService class declaration
/// 2. This file uses the same injected fields — no extra DI needed /// 2. This file uses the same injected fields — no extra DI needed
/// </summary> /// </summary>
public partial class PreorderConversionService public partial class PreOrderConversionService
{ {
// ── Product replacement ─────────────────────────────────────────────────── // ── Product replacement ───────────────────────────────────────────────────
@ -127,32 +127,32 @@ public partial class PreorderConversionService
// ── Swap preorder items ─────────────────────────────────────────────── // ── Swap preorder items ───────────────────────────────────────────────
if (affectedOrderIds.Any()) if (affectedOrderIds.Any())
{ {
var preorders = (await _preorderDbContext.Preorders.GetAll(false).ToListAsync()) var preorders = (await _preorderDbContext.PreOrders.GetAll(false).ToListAsync())
.Where(p => p.OrderId.HasValue && affectedOrderIds.Contains(p.OrderId.Value)) .Where(p => p.OrderId.HasValue && affectedOrderIds.Contains(p.OrderId.Value))
.ToList(); .ToList();
foreach (var preorder in preorders) foreach (var preorder in preorders)
{ {
var piList = await _preorderDbContext.PreorderItems var piList = await _preorderDbContext.PreOrderItems
.GetAllByPreorderIdAsync(preorder.Id) .GetAllByPreOrderIdAsync(preorder.Id)
.ToListAsync(); .ToListAsync();
foreach (var pi in piList.Where(i => i.ProductId == oldProductId)) foreach (var pi in piList.Where(i => i.ProductId == oldProductId))
{ {
pi.ProductId = newProductId; pi.ProductId = newProductId;
await _preorderDbContext.PreorderItems.UpdateAsync(pi); await _preorderDbContext.PreOrderItems.UpdateAsync(pi);
} }
} }
} }
// ── Trigger conversion for new product ──────────────────────────────── // ── Trigger conversion for new product ────────────────────────────────
// New product may now have pending preorders that can be fulfilled // New product may now have pending preorders that can be fulfilled
await ConvertPreordersForProductsAsync( await ConvertPreOrdersForProductsAsync(
new List<int> { newProductId }, new List<int> { newProductId },
oldItem.ShippingDocumentId); oldItem.ShippingDocumentId);
// TODO: SignalR notification to admin hub // TODO: SignalR notification to admin hub
// TODO: SendPreorderProductReplacedNotificationAsync per affected customer // TODO: SendPreOrderProductReplacedNotificationAsync per affected customer
Console.WriteLine($"[ReplaceShippingItemProduct] Complete: " + Console.WriteLine($"[ReplaceShippingItemProduct] Complete: " +
$"{affectedOrderIds.Count} orders swapped, budget remaining={replacementBudget}"); $"{affectedOrderIds.Count} orders swapped, budget remaining={replacementBudget}");
@ -161,7 +161,7 @@ public partial class PreorderConversionService
private async Task<List<OrderDto>> GetAffectedOpenOrdersAsync(int oldProductId) private async Task<List<OrderDto>> GetAffectedOpenOrdersAsync(int oldProductId)
{ {
// Orders that are referenced by a preorder AND contain the old product // Orders that are referenced by a preorder AND contain the old product
var preorderOrderIds = (await _preorderDbContext.Preorders.GetAll(false).ToListAsync()) var preorderOrderIds = (await _preorderDbContext.PreOrders.GetAll(false).ToListAsync())
.Where(p => p.OrderId.HasValue) .Where(p => p.OrderId.HasValue)
.Select(p => p.OrderId!.Value) .Select(p => p.OrderId!.Value)
.ToHashSet(); .ToHashSet();

View File

@ -24,17 +24,17 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services;
/// Called once per shipping document save, after all IncomingQuantity /// Called once per shipping document save, after all IncomingQuantity
/// attributes have been written for that document's product set. /// attributes have been written for that document's product set.
/// ///
/// Allocation strategy: first-come-first-served by PreorderId (insertion order). /// Allocation strategy: first-come-first-served by PreOrderId (insertion order).
/// ///
/// Multi-document design: /// Multi-document design:
/// - Preorder.OrderId tracks the linked real order once created. /// - PreOrder.OrderId tracks the linked real order once created.
/// - First partial fulfillment → creates the order, saves OrderId on Preorder. /// - First partial fulfillment → creates the order, saves OrderId on PreOrder.
/// - Subsequent documents → appends only newly-fulfilled items to that same order. /// - Subsequent documents → appends only newly-fulfilled items to that same order.
/// - Dropped items are recorded in an order note but never become OrderItems. /// - Dropped items are recorded in an order note but never become OrderItems.
/// </summary> /// </summary>
public partial class PreorderConversionService public partial class PreOrderConversionService
{ {
private readonly PreorderDbContext _preorderDbContext; private readonly PreOrderDbContext _preorderDbContext;
private readonly FruitBankDbContext _dbContext; private readonly FruitBankDbContext _dbContext;
private readonly ICustomerService _customerService; private readonly ICustomerService _customerService;
private readonly IProductService _productService; private readonly IProductService _productService;
@ -45,8 +45,8 @@ public partial class PreorderConversionService
private readonly FruitBankOrderItemService _orderItemService; private readonly FruitBankOrderItemService _orderItemService;
private readonly IStoreContext _storeContext; private readonly IStoreContext _storeContext;
public PreorderConversionService( public PreOrderConversionService(
PreorderDbContext preorderDbContext, PreOrderDbContext preorderDbContext,
FruitBankDbContext dbContext, FruitBankDbContext dbContext,
ICustomerService customerService, ICustomerService customerService,
IProductService productService, IProductService productService,
@ -71,54 +71,54 @@ public partial class PreorderConversionService
// ── Entry point ─────────────────────────────────────────────────────────── // ── Entry point ───────────────────────────────────────────────────────────
public async Task ConvertPreordersForProductsAsync(IList<int> productIds, int shippingDocumentId) public async Task ConvertPreOrdersForProductsAsync(IList<int> productIds, int shippingDocumentId)
{ {
Console.WriteLine($"[PreorderConversion] Starting for {productIds.Count} products, shippingDocumentId={shippingDocumentId}"); Console.WriteLine($"[PreOrderConversion] Starting for {productIds.Count} products, shippingDocumentId={shippingDocumentId}");
// Always sweep expired preorders first — any preorder whose DateOfReceipt // Always sweep expired preorders first — any preorder whose DateOfReceipt
// is in the past is closed regardless of stock, before we allocate anything // is in the past is closed regardless of stock, before we allocate anything
await SweepExpiredPreordersAsync(); await SweepExpiredPreOrdersAsync();
var pendingItems = await _preorderDbContext.GetPendingItemsForProductsAsync(productIds); var pendingItems = await _preorderDbContext.GetPendingItemsForProductsAsync(productIds);
if (!pendingItems.Any()) if (!pendingItems.Any())
{ {
Console.WriteLine("[PreorderConversion] No pending preorder items — done."); Console.WriteLine("[PreOrderConversion] No pending preorder items — done.");
return; return;
} }
// Filter out preorders whose delivery date is more than PreorderConversionWindowDays // Filter out preorders whose delivery date is more than PreOrderConversionWindowDays
// (4 days) away. With bi-weekly trucks, a delivery that far out will be served // (4 days) away. With bi-weekly trucks, a delivery that far out will be served
// by the next truck's document — converting now would steal stock from // by the next truck's document — converting now would steal stock from
// earlier deliveries that legitimately need it. // earlier deliveries that legitimately need it.
var conversionCutoff = DateTime.UtcNow.Date.AddDays(FruitBankPluginConst.PreorderConversionWindowDays); var conversionCutoff = DateTime.UtcNow.Date.AddDays(FruitBankPluginConst.PreOrderConversionWindowDays);
var pendingPreorderIds = pendingItems.Select(i => i.PreorderId).Distinct().ToList(); var pendingPreOrderIds = pendingItems.Select(i => i.PreOrderId).Distinct().ToList();
var parentPreorders = await _preorderDbContext.Preorders var parentPreOrders = await _preorderDbContext.PreOrders
.GetAll(false) .GetAll(false)
.Where(p => pendingPreorderIds.Contains(p.Id)) .Where(p => pendingPreOrderIds.Contains(p.Id))
.ToListAsync(); .ToListAsync();
var eligiblePreorderIds = parentPreorders var eligiblePreOrderIds = parentPreOrders
.Where(p => p.DateOfReceipt.Date <= conversionCutoff) .Where(p => p.DateOfReceipt.Date <= conversionCutoff)
.Select(p => p.Id) .Select(p => p.Id)
.ToHashSet(); .ToHashSet();
pendingItems = pendingItems.Where(i => eligiblePreorderIds.Contains(i.PreorderId)).ToList(); pendingItems = pendingItems.Where(i => eligiblePreOrderIds.Contains(i.PreOrderId)).ToList();
if (!pendingItems.Any()) if (!pendingItems.Any())
{ {
Console.WriteLine($"[PreorderConversion] All pending preorders are beyond the " + Console.WriteLine($"[PreOrderConversion] All pending preorders are beyond the " +
$"{FruitBankPluginConst.PreorderConversionWindowDays}-day window — skipped."); $"{FruitBankPluginConst.PreOrderConversionWindowDays}-day window — skipped.");
return; return;
} }
Console.WriteLine($"[PreorderConversion] {pendingItems.Count} items eligible " + Console.WriteLine($"[PreOrderConversion] {pendingItems.Count} items eligible " +
$"(within {FruitBankPluginConst.PreorderConversionWindowDays}-day window)."); $"(within {FruitBankPluginConst.PreOrderConversionWindowDays}-day window).");
var incomingPool = await BuildIncomingQuantityPoolAsync(productIds); var incomingPool = await BuildIncomingQuantityPoolAsync(productIds);
// Track which items were newly resolved in THIS run, grouped by preorder // Track which items were newly resolved in THIS run, grouped by preorder
// Key: preorderId Value: list of items whose status changed in this run // Key: preorderId Value: list of items whose status changed in this run
var newlyResolvedByPreorder = new Dictionary<int, List<PreorderItem>>(); var newlyResolvedByPreOrder = new Dictionary<int, List<PreOrderItem>>();
foreach (var item in pendingItems) foreach (var item in pendingItems)
{ {
@ -138,49 +138,49 @@ public partial class PreorderConversionService
incomingPool[item.ProductId] -= fulfill; incomingPool[item.ProductId] -= fulfill;
item.Status = item.FulfilledQuantity >= item.RequestedQuantity item.Status = item.FulfilledQuantity >= item.RequestedQuantity
? PreorderItemStatus.Fulfilled ? PreOrderItemStatus.Fulfilled
: item.FulfilledQuantity > 0 : item.FulfilledQuantity > 0
? PreorderItemStatus.PartiallyFulfilled ? PreOrderItemStatus.PartiallyFulfilled
: PreorderItemStatus.Dropped; : PreOrderItemStatus.Dropped;
await _preorderDbContext.PreorderItems.UpdateAsync(item); await _preorderDbContext.PreOrderItems.UpdateAsync(item);
} }
// Only track this item if something actually changed this run // Only track this item if something actually changed this run
// (i.e. it gained fulfilled quantity or got dropped) // (i.e. it gained fulfilled quantity or got dropped)
var gainedQuantity = item.FulfilledQuantity - prevFulfilled; var gainedQuantity = item.FulfilledQuantity - prevFulfilled;
bool wasDropped = item.Status == PreorderItemStatus.Dropped && prevFulfilled == 0; bool wasDropped = item.Status == PreOrderItemStatus.Dropped && prevFulfilled == 0;
if (gainedQuantity > 0 || wasDropped) if (gainedQuantity > 0 || wasDropped)
{ {
if (!newlyResolvedByPreorder.ContainsKey(item.PreorderId)) if (!newlyResolvedByPreOrder.ContainsKey(item.PreOrderId))
newlyResolvedByPreorder[item.PreorderId] = new List<PreorderItem>(); newlyResolvedByPreOrder[item.PreOrderId] = new List<PreOrderItem>();
newlyResolvedByPreorder[item.PreorderId].Add(item); newlyResolvedByPreOrder[item.PreOrderId].Add(item);
} }
Console.WriteLine($"[PreorderConversion] Item #{item.Id} (product {item.ProductId}): " + Console.WriteLine($"[PreOrderConversion] Item #{item.Id} (product {item.ProductId}): " +
$"requested={item.RequestedQuantity}, fulfilled={item.FulfilledQuantity}, " + $"requested={item.RequestedQuantity}, fulfilled={item.FulfilledQuantity}, " +
$"gained={item.FulfilledQuantity - prevFulfilled}, status={item.Status}"); $"gained={item.FulfilledQuantity - prevFulfilled}, status={item.Status}");
} }
// Process each affected preorder // Process each affected preorder
foreach (var (preorderId, changedItems) in newlyResolvedByPreorder) foreach (var (preorderId, changedItems) in newlyResolvedByPreOrder)
{ {
await _preorderDbContext.RefreshPreorderStatusAsync(preorderId); await _preorderDbContext.RefreshPreOrderStatusAsync(preorderId);
var preorder = await _preorderDbContext.Preorders.GetByIdAsync(preorderId); var preorder = await _preorderDbContext.PreOrders.GetByIdAsync(preorderId);
if (preorder == null) continue; if (preorder == null) continue;
// Items newly gaining fulfilled quantity in this run // Items newly gaining fulfilled quantity in this run
var newlyFulfilled = changedItems var newlyFulfilled = changedItems
.Where(i => i.FulfilledQuantity - 0 > 0 && .Where(i => i.FulfilledQuantity - 0 > 0 &&
(i.Status == PreorderItemStatus.Fulfilled || (i.Status == PreOrderItemStatus.Fulfilled ||
i.Status == PreorderItemStatus.PartiallyFulfilled)) i.Status == PreOrderItemStatus.PartiallyFulfilled))
.ToList(); .ToList();
// Items dropped in this run (no stock at all) // Items dropped in this run (no stock at all)
var newlyDropped = changedItems var newlyDropped = changedItems
.Where(i => i.Status == PreorderItemStatus.Dropped) .Where(i => i.Status == PreOrderItemStatus.Dropped)
.ToList(); .ToList();
if (preorder.OrderId == null) if (preorder.OrderId == null)
@ -198,7 +198,7 @@ public partial class PreorderConversionService
} }
} }
Console.WriteLine($"[PreorderConversion] Done. {newlyResolvedByPreorder.Count} preorders affected."); Console.WriteLine($"[PreOrderConversion] Done. {newlyResolvedByPreOrder.Count} preorders affected.");
} }
// ── Expiry sweep ─────────────────────────────────────────────────────────── // ── Expiry sweep ───────────────────────────────────────────────────────────
@ -210,71 +210,71 @@ public partial class PreorderConversionService
/// quantities already made it into a real order). /// quantities already made it into a real order).
/// Called at the start of every conversion run. /// Called at the start of every conversion run.
/// </summary> /// </summary>
private async Task SweepExpiredPreordersAsync() private async Task SweepExpiredPreOrdersAsync()
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var activePreorderStatuses = new[] { PreorderStatus.Pending, PreorderStatus.PartiallyFulfilled }; var activePreOrderStatuses = new[] { PreOrderStatus.Pending, PreOrderStatus.PartiallyFulfilled };
// Find preorders that are past their receipt date — fetch by date only, // Find preorders that are past their receipt date — fetch by date only,
// then filter by status in memory (LinqToDB can't translate enum comparisons) // then filter by status in memory (LinqToDB can't translate enum comparisons)
var expiredPreorders = (await _preorderDbContext.Preorders var expiredPreOrders = (await _preorderDbContext.PreOrders
.GetAll(false) .GetAll(false)
.Where(p => p.DateOfReceipt < now) .Where(p => p.DateOfReceipt < now)
.ToListAsync()) .ToListAsync())
.Where(p => p.Status == PreorderStatus.Pending || .Where(p => p.Status == PreOrderStatus.Pending ||
p.Status == PreorderStatus.PartiallyFulfilled) p.Status == PreOrderStatus.PartiallyFulfilled)
.ToList(); .ToList();
if (!expiredPreorders.Any()) return; if (!expiredPreOrders.Any()) return;
Console.WriteLine($"[PreorderConversion] Sweeping {expiredPreorders.Count} expired preorders"); Console.WriteLine($"[PreOrderConversion] Sweeping {expiredPreOrders.Count} expired preorders");
foreach (var preorder in expiredPreorders) foreach (var preorder in expiredPreOrders)
{ {
var items = await _preorderDbContext.PreorderItems var items = await _preorderDbContext.PreOrderItems
.GetAllByPreorderIdAsync(preorder.Id) .GetAllByPreOrderIdAsync(preorder.Id)
.ToListAsync(); .ToListAsync();
// Drop only the items that were never fulfilled — already-fulfilled // Drop only the items that were never fulfilled — already-fulfilled
// items stay as-is since they are already on a real order // items stay as-is since they are already on a real order
var stillPending = items.Where(i => i.Status == PreorderItemStatus.Pending).ToList(); var stillPending = items.Where(i => i.Status == PreOrderItemStatus.Pending).ToList();
foreach (var item in stillPending) foreach (var item in stillPending)
{ {
item.Status = PreorderItemStatus.Dropped; item.Status = PreOrderItemStatus.Dropped;
await _preorderDbContext.PreorderItems.UpdateAsync(item); await _preorderDbContext.PreOrderItems.UpdateAsync(item);
} }
// Recalculate header status // Recalculate header status
await _preorderDbContext.RefreshPreorderStatusAsync(preorder.Id); await _preorderDbContext.RefreshPreOrderStatusAsync(preorder.Id);
var hadAnyFulfillment = items.Any(i => var hadAnyFulfillment = items.Any(i =>
i.Status == PreorderItemStatus.Fulfilled || i.Status == PreOrderItemStatus.Fulfilled ||
i.Status == PreorderItemStatus.PartiallyFulfilled); i.Status == PreOrderItemStatus.PartiallyFulfilled);
Console.WriteLine($"[PreorderConversion] Expired preorder #{preorder.Id}: " + Console.WriteLine($"[PreOrderConversion] Expired preorder #{preorder.Id}: " +
$"{stillPending.Count} items dropped, " + $"{stillPending.Count} items dropped, " +
$"hadFulfillment={hadAnyFulfillment}, orderId={preorder.OrderId}"); $"hadFulfillment={hadAnyFulfillment}, orderId={preorder.OrderId}");
// TODO: Send expiry notification if nothing was ever fulfilled // TODO: Send expiry notification if nothing was ever fulfilled
// (fully unfulfilled preorders — customer should be notified) // (fully unfulfilled preorders — customer should be notified)
// if (!hadAnyFulfillment) // if (!hadAnyFulfillment)
// await _fruitBankNotificationService.SendPreorderExpiredNotificationAsync(preorder); // await _fruitBankNotificationService.SendPreOrderExpiredNotificationAsync(preorder);
} }
} }
// ── Create new order (first document that fulfills anything) ────────────── // ── Create new order (first document that fulfills anything) ──────────────
private async Task CreateOrderAsync( private async Task CreateOrderAsync(
Preorder preorder, PreOrder preorder,
List<PreorderItem> fulfilledItems, List<PreOrderItem> fulfilledItems,
List<PreorderItem> droppedItems, List<PreOrderItem> droppedItems,
int shippingDocumentId) int shippingDocumentId)
{ {
var customer = await _customerService.GetCustomerByIdAsync(preorder.CustomerId); var customer = await _customerService.GetCustomerByIdAsync(preorder.CustomerId);
if (customer == null) if (customer == null)
{ {
Console.WriteLine($"[PreorderConversion] Customer {preorder.CustomerId} not found — skipping order creation for preorder #{preorder.Id}"); Console.WriteLine($"[PreOrderConversion] Customer {preorder.CustomerId} not found — skipping order creation for preorder #{preorder.Id}");
return; return;
} }
@ -289,7 +289,7 @@ public partial class PreorderConversionService
if (billingAddressId == 0) if (billingAddressId == 0)
{ {
Console.WriteLine($"[PreorderConversion] No billing address for customer {customer.Id} — skipping for preorder #{preorder.Id}"); Console.WriteLine($"[PreOrderConversion] No billing address for customer {customer.Id} — skipping for preorder #{preorder.Id}");
return; return;
} }
@ -351,10 +351,10 @@ public partial class PreorderConversionService
order.CustomOrderNumber = order.Id.ToString(); order.CustomOrderNumber = order.Id.ToString();
await _dbContext.Orders.UpdateAsync(order); await _dbContext.Orders.UpdateAsync(order);
// Save OrderId back on the Preorder so future documents can find it // Save OrderId back on the PreOrder so future documents can find it
preorder.OrderId = order.Id; preorder.OrderId = order.Id;
preorder.UpdatedOnUtc = DateTime.UtcNow; preorder.UpdatedOnUtc = DateTime.UtcNow;
await _preorderDbContext.Preorders.UpdateAsync(preorder); await _preorderDbContext.PreOrders.UpdateAsync(preorder);
// DateOfReceipt generic attribute // DateOfReceipt generic attribute
await _dbContext.GenericAttributes.InsertAsync(new Nop.Core.Domain.Common.GenericAttribute await _dbContext.GenericAttributes.InsertAsync(new Nop.Core.Domain.Common.GenericAttribute
@ -370,26 +370,26 @@ public partial class PreorderConversionService
// Fire event so existing handlers (EventConsumer etc.) run // Fire event so existing handlers (EventConsumer etc.) run
await _eventPublisher.PublishAsync(new OrderPlacedEvent(order)); await _eventPublisher.PublishAsync(new OrderPlacedEvent(order));
// TODO: Send "FruitBank.PreorderConverted.CustomerNotification" email // TODO: Send "FruitBank.PreOrderConverted.CustomerNotification" email
// summarising fulfilled items, dropped items, order ID, DateOfReceipt // summarising fulfilled items, dropped items, order ID, DateOfReceipt
// await _fruitBankNotificationService.SendPreorderConvertedNotificationAsync(order, preorder, fulfilledItems, droppedItems); // await _fruitBankNotificationService.SendPreOrderConvertedNotificationAsync(order, preorder, fulfilledItems, droppedItems);
Console.WriteLine($"[PreorderConversion] Created Order #{order.Id} from Preorder #{preorder.Id} — " + Console.WriteLine($"[PreOrderConversion] Created Order #{order.Id} from PreOrder #{preorder.Id} — " +
$"{fulfilledItems.Count} fulfilled, {droppedItems.Count} dropped, total {orderTotal:N0} Ft"); $"{fulfilledItems.Count} fulfilled, {droppedItems.Count} dropped, total {orderTotal:N0} Ft");
} }
// ── Append to existing order (subsequent documents) ─────────────────────── // ── Append to existing order (subsequent documents) ───────────────────────
private async Task AppendItemsToOrderAsync( private async Task AppendItemsToOrderAsync(
Preorder preorder, PreOrder preorder,
List<PreorderItem> newlyFulfilled, List<PreOrderItem> newlyFulfilled,
List<PreorderItem> newlyDropped, List<PreOrderItem> newlyDropped,
int shippingDocumentId) int shippingDocumentId)
{ {
var order = await _dbContext.Orders.GetByIdAsync(preorder.OrderId!.Value); var order = await _dbContext.Orders.GetByIdAsync(preorder.OrderId!.Value);
if (order == null) if (order == null)
{ {
Console.WriteLine($"[PreorderConversion] Preorder #{preorder.Id} references Order #{preorder.OrderId} which no longer exists — creating fresh"); Console.WriteLine($"[PreOrderConversion] PreOrder #{preorder.Id} references Order #{preorder.OrderId} which no longer exists — creating fresh");
preorder.OrderId = null; preorder.OrderId = null;
await CreateOrderAsync(preorder, newlyFulfilled, newlyDropped, shippingDocumentId); await CreateOrderAsync(preorder, newlyFulfilled, newlyDropped, shippingDocumentId);
return; return;
@ -397,7 +397,7 @@ public partial class PreorderConversionService
if (!newlyFulfilled.Any() && !newlyDropped.Any()) if (!newlyFulfilled.Any() && !newlyDropped.Any())
{ {
Console.WriteLine($"[PreorderConversion] Preorder #{preorder.Id}: no new items to append to Order #{order.Id}"); Console.WriteLine($"[PreOrderConversion] PreOrder #{preorder.Id}: no new items to append to Order #{order.Id}");
return; return;
} }
@ -422,16 +422,16 @@ public partial class PreorderConversionService
await InsertOrderNoteAsync(order.Id, preorder.Id, shippingDocumentId, newlyFulfilled, newlyDropped); await InsertOrderNoteAsync(order.Id, preorder.Id, shippingDocumentId, newlyFulfilled, newlyDropped);
// TODO: Send update notification email (same template as initial, but framed as an update) // TODO: Send update notification email (same template as initial, but framed as an update)
// await _fruitBankNotificationService.SendPreorderConvertedNotificationAsync(order, preorder, newlyFulfilled, newlyDropped); // await _fruitBankNotificationService.SendPreOrderConvertedNotificationAsync(order, preorder, newlyFulfilled, newlyDropped);
Console.WriteLine($"[PreorderConversion] Appended {newlyFulfilled.Count} items to Order #{order.Id} " + Console.WriteLine($"[PreOrderConversion] Appended {newlyFulfilled.Count} items to Order #{order.Id} " +
$"from Preorder #{preorder.Id} via document #{shippingDocumentId}. " + $"from PreOrder #{preorder.Id} via document #{shippingDocumentId}. " +
$"New total: {newTotal:N0} Ft"); $"New total: {newTotal:N0} Ft");
} }
// ── Shared helpers ──────────────────────────────────────────────────────── // ── Shared helpers ────────────────────────────────────────────────────────
private async Task InsertOrderItemsAsync(Order order, List<PreorderItem> items) private async Task InsertOrderItemsAsync(Order order, List<PreOrderItem> items)
{ {
foreach (var item in items) foreach (var item in items)
{ {
@ -475,13 +475,13 @@ public partial class PreorderConversionService
product, product,
-item.FulfilledQuantity, -item.FulfilledQuantity,
string.Empty, string.Empty,
$"Előrendelés #{item.PreorderId} — rendelés #{order.Id} létrehozása"); $"Előrendelés #{item.PreOrderId} — rendelés #{order.Id} létrehozása");
} }
} }
private async Task InsertOrderNoteAsync( private async Task InsertOrderNoteAsync(
int orderId, int preorderId, int shippingDocumentId, int orderId, int preorderId, int shippingDocumentId,
List<PreorderItem> fulfilled, List<PreorderItem> dropped) List<PreOrderItem> fulfilled, List<PreOrderItem> dropped)
{ {
var fulfilledDesc = fulfilled.Any() var fulfilledDesc = fulfilled.Any()
? $"Teljesített: {string.Join(", ", fulfilled.Select(i => $"#{i.ProductId} ({i.FulfilledQuantity} db)"))}" ? $"Teljesített: {string.Join(", ", fulfilled.Select(i => $"#{i.ProductId} ({i.FulfilledQuantity} db)"))}"
@ -521,10 +521,10 @@ public partial class PreorderConversionService
await _fruitBankAttributeService await _fruitBankAttributeService
.InsertOrUpdateGenericAttributeAsync<Product, int>( .InsertOrUpdateGenericAttributeAsync<Product, int>(
productId, nameof(IIncomingQuantity.IncomingQuantity), updated, storeId); productId, nameof(IIncomingQuantity.IncomingQuantity), updated, storeId);
Console.WriteLine($"[PreorderConversion] SyncIncomingQty product #{productId}: {current}+({delta})={updated}"); Console.WriteLine($"[PreOrderConversion] SyncIncomingQty product #{productId}: {current}+({delta})={updated}");
} }
private async Task<decimal> CalculateTotalAsync(List<PreorderItem> items) private async Task<decimal> CalculateTotalAsync(List<PreOrderItem> items)
{ {
var total = 0m; var total = 0m;
foreach (var item in items) foreach (var item in items)
@ -549,17 +549,17 @@ public partial class PreorderConversionService
p => p.Id, p => p.Id,
p => p.AvailableQuantity); p => p.AvailableQuantity);
var activeItemStatuses = new[] { PreorderItemStatus.Fulfilled, PreorderItemStatus.PartiallyFulfilled }; var activeItemStatuses = new[] { PreOrderItemStatus.Fulfilled, PreOrderItemStatus.PartiallyFulfilled };
// 2. Subtract quantities already committed to preorders in previous runs // 2. Subtract quantities already committed to preorders in previous runs
// Fetch by productId only, filter by status in memory // Fetch by productId only, filter by status in memory
var allCommittedItems = await _preorderDbContext.PreorderItems.Table var allCommittedItems = await _preorderDbContext.PreOrderItems.Table
.Where(i => productIds.Contains(i.ProductId)) .Where(i => productIds.Contains(i.ProductId))
.ToListAsync(); .ToListAsync();
var alreadyAllocated = allCommittedItems var alreadyAllocated = allCommittedItems
.Where(i => i.Status == PreorderItemStatus.Fulfilled || .Where(i => i.Status == PreOrderItemStatus.Fulfilled ||
i.Status == PreorderItemStatus.PartiallyFulfilled) i.Status == PreOrderItemStatus.PartiallyFulfilled)
.GroupBy(i => i.ProductId) .GroupBy(i => i.ProductId)
.Select(g => new { ProductId = g.Key, Allocated = g.Sum(i => i.FulfilledQuantity) }) .Select(g => new { ProductId = g.Key, Allocated = g.Sum(i => i.FulfilledQuantity) })
.ToList(); .ToList();

View File

@ -1,6 +1,6 @@
@using FruitBank.Common.Enums @using FruitBank.Common.Enums
@using Nop.Plugin.Misc.FruitBankPlugin.Controllers @using Nop.Plugin.Misc.FruitBankPlugin.Controllers
@model List<CustomerPreorderController.CustomerPreorderRow> @model List<CustomerPreOrderController.CustomerPreOrderRow>
@{ @{
Layout = "_ColumnsTwo"; Layout = "_ColumnsTwo";
@ -28,23 +28,23 @@
{ {
var statusClass = preorder.Status switch var statusClass = preorder.Status switch
{ {
PreorderStatus.Confirmed => "po-status-confirmed", PreOrderStatus.Confirmed => "po-status-confirmed",
PreorderStatus.PartiallyFulfilled => "po-status-partial", PreOrderStatus.PartiallyFulfilled => "po-status-partial",
PreorderStatus.Cancelled => "po-status-cancelled", PreOrderStatus.Cancelled => "po-status-cancelled",
_ => "po-status-pending" _ => "po-status-pending"
}; };
var statusLabel = preorder.Status switch var statusLabel = preorder.Status switch
{ {
PreorderStatus.Confirmed => "Megerősítve", PreOrderStatus.Confirmed => "Megerősítve",
PreorderStatus.PartiallyFulfilled => "Részben teljesítve", PreOrderStatus.PartiallyFulfilled => "Részben teljesítve",
PreorderStatus.Cancelled => "Törölve / Lejárt", PreOrderStatus.Cancelled => "Törölve / Lejárt",
_ => "Függőben" _ => "Függőben"
}; };
<div class="po-customer-card"> <div class="po-customer-card">
<div class="po-card-header"> <div class="po-card-header">
<div class="po-card-meta"> <div class="po-card-meta">
<span class="po-card-id">#@preorder.PreorderId előrendelés</span> <span class="po-card-id">#@preorder.PreOrderId előrendelés</span>
<span class="po-card-date"> <span class="po-card-date">
<i class="fa fa-calendar"></i> <i class="fa fa-calendar"></i>
Kért szállítás: <strong>@preorder.DateOfReceipt.ToLocalTime().ToString("yyyy. MM. dd. HH:mm")</strong> Kért szállítás: <strong>@preorder.DateOfReceipt.ToLocalTime().ToString("yyyy. MM. dd. HH:mm")</strong>
@ -89,16 +89,16 @@
{ {
var itemStatusLabel = item.Status switch var itemStatusLabel = item.Status switch
{ {
PreorderItemStatus.Fulfilled => "✓ Teljesítve", PreOrderItemStatus.Fulfilled => "✓ Teljesítve",
PreorderItemStatus.PartiallyFulfilled => "◑ Részben", PreOrderItemStatus.PartiallyFulfilled => "◑ Részben",
PreorderItemStatus.Dropped => "✕ Ejtve", PreOrderItemStatus.Dropped => "✕ Ejtve",
_ => "⏳ Vár" _ => "⏳ Vár"
}; };
var itemStatusClass = item.Status switch var itemStatusClass = item.Status switch
{ {
PreorderItemStatus.Fulfilled => "item-fulfilled", PreOrderItemStatus.Fulfilled => "item-fulfilled",
PreorderItemStatus.PartiallyFulfilled => "item-partial", PreOrderItemStatus.PartiallyFulfilled => "item-partial",
PreorderItemStatus.Dropped => "item-dropped", PreOrderItemStatus.Dropped => "item-dropped",
_ => "item-pending" _ => "item-pending"
}; };
var unitPrice = item.IsMeasurable var unitPrice = item.IsMeasurable
@ -147,7 +147,7 @@
.no-data p { margin-bottom: 16px; font-size: 15px; } .no-data p { margin-bottom: 16px; font-size: 15px; }
/* ── Preorder card ────────────────────────────────────────────── */ /* ── PreOrder card ────────────────────────────────────────────── */
.po-customer-card { .po-customer-card {
background: #fff; background: #fff;
border: 1px solid #dde8da; border: 1px solid #dde8da;

View File

@ -1,5 +1,5 @@
<li class="customer-navigation-item @(Context.Request.Path.Value?.Contains("elorerendeles") == true ? "active" : "")"> <li class="customer-navigation-item @(Context.Request.Path.Value?.Contains("elorerendeles") == true ? "active" : "")">
<a href="@Url.Action("List", "CustomerPreorder")"> <a href="@Url.Action("List", "CustomerPreOrder")">
Előrendeléseim Előrendeléseim
</a> </a>
</li> </li>

View File

@ -499,7 +499,7 @@
<i class="fa fa-chevron-down"></i> <i class="fa fa-chevron-down"></i>
</button> </button>
<div class="help-faq-a"> <div class="help-faq-a">
A <a href="@Url.Action("List", "CustomerPreorder")" style="color:#2d7a3a;font-weight:600;">Saját fiók → Előrendeléseim</a> oldalon látod az összes leadott előrendelést, azok állapotát és a létrejött rendelésekre mutató hivatkozást. A <a href="@Url.Action("List", "CustomerPreOrder")" style="color:#2d7a3a;font-weight:600;">Saját fiók → Előrendeléseim</a> oldalon látod az összes leadott előrendelést, azok állapotát és a létrejött rendelésekre mutató hivatkozást.
</div> </div>
</div> </div>
</div> </div>

View File

@ -177,7 +177,7 @@
</div><!-- /#sectionQuickOrder --> </div><!-- /#sectionQuickOrder -->
<!-- ══ PREORDER SECTION ══════════════════════════════════════════════ --> <!-- ══ PREORDER SECTION ══════════════════════════════════════════════ -->
<div id="sectionPreorder" style="display:none;"> <div id="sectionPreOrder" style="display:none;">
<div class="po-info-banner"> <div class="po-info-banner">
<i class="fa fa-info-circle"></i> <i class="fa fa-info-circle"></i>
Az előrendelés egy kívánságlista — az áruk megerősítése a szállítmány beérkezésekor történik, és az esetleges változásokról értesítünk. Az előrendelés egy kívánságlista — az áruk megerősítése a szállítmány beérkezésekor történik, és az esetleges változásokról értesítünk.
@ -211,7 +211,7 @@
<div class="po-submit-row"> <div class="po-submit-row">
<div id="poSelectionSummary" class="po-selection-summary">Még nincs kiválasztott termék</div> <div id="poSelectionSummary" class="po-selection-summary">Még nincs kiválasztott termék</div>
<button type="button" id="submitPreorderBtn" class="po-submit-btn" disabled> <button type="button" id="submitPreOrderBtn" class="po-submit-btn" disabled>
<i class="fa fa-paper-plane"></i> Előrendelés leadása <i class="fa fa-paper-plane"></i> Előrendelés leadása
</button> </button>
</div> </div>
@ -237,11 +237,11 @@
</div> </div>
</div> </div>
</div> </div>
</div><!-- /#sectionPreorder --> </div><!-- /#sectionPreOrder -->
</div><!-- /#mainContent --> </div><!-- /#mainContent -->
<!-- ── Preorder success state ────────────────────────────────────────── --> <!-- ── PreOrder success state ────────────────────────────────────────── -->
<div id="successState" style="display:none;" class="po-success-state"> <div id="successState" style="display:none;" class="po-success-state">
<div class="po-success-icon"><i class="fa fa-check-circle"></i></div> <div class="po-success-icon"><i class="fa fa-check-circle"></i></div>
<h2>Előrendelés leadva!</h2> <h2>Előrendelés leadva!</h2>
@ -408,7 +408,7 @@
} }
@@keyframes fadeIn { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:translateY(0); } } @@keyframes fadeIn { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:translateY(0); } }
/* ── Preorder stock label variant ──────────────────────────── */ /* ── PreOrder stock label variant ──────────────────────────── */
.pc-stock.stock-preorder { .pc-stock.stock-preorder {
color: #c87500; color: #c87500;
font-style: italic; font-style: italic;
@ -500,7 +500,7 @@
var selectedDayLabel = null; var selectedDayLabel = null;
var currentFlowType = null; // "quickorder" | "preorder" var currentFlowType = null; // "quickorder" | "preorder"
// Preorder state // PreOrder state
var poProducts = []; var poProducts = [];
var poQuantities = {}; var poQuantities = {};
@ -547,7 +547,7 @@
$('#recordBtn').click(startRecording); $('#recordBtn').click(startRecording);
$('#stopBtn').click(function () { stopRecording(false); }); $('#stopBtn').click(function () { stopRecording(false); });
$('#submitPreorderBtn').click(submitPreorder); $('#submitPreOrderBtn').click(submitPreOrder);
// Restore saved datetime // Restore saved datetime
$.ajax({ $.ajax({
@ -674,12 +674,12 @@
if (flowType === 'quickorder') { if (flowType === 'quickorder') {
$('#sectionQuickOrder').show(); $('#sectionQuickOrder').show();
$('#sectionPreorder').hide(); $('#sectionPreOrder').hide();
loadAllProducts(); loadAllProducts();
} else { } else {
$('#sectionPreorder').show(); $('#sectionPreOrder').show();
$('#sectionQuickOrder').hide(); $('#sectionQuickOrder').hide();
loadPreorderProducts(); loadPreOrderProducts();
} }
} }
@ -942,21 +942,21 @@
// PREORDER FLOW // PREORDER FLOW
// ══════════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════════
function loadPreorderProducts() { function loadPreOrderProducts() {
$('#poLoadingState').show(); $('#poNoProducts, #poProductSection').hide(); $('#poLoadingState').show(); $('#poNoProducts, #poProductSection').hide();
$.ajax({ url: '@Url.Action("GetPreorderProducts", "Order")', type: 'GET', $.ajax({ url: '@Url.Action("GetPreOrderProducts", "Order")', type: 'GET',
success: function (r) { success: function (r) {
$('#poLoadingState').hide(); $('#poLoadingState').hide();
if (!r.success || !r.products || r.products.length === 0) { $('#poNoProducts').show(); return; } if (!r.success || !r.products || r.products.length === 0) { $('#poNoProducts').show(); return; }
poProducts = r.products; poQuantities = {}; poProducts = r.products; poQuantities = {};
renderPreorderProducts(); $('#poProductSection').show(); renderPreOrderProducts(); $('#poProductSection').show();
}, },
error: function () { $('#poLoadingState').hide(); $('#poNoProducts').show(); } error: function () { $('#poLoadingState').hide(); $('#poNoProducts').show(); }
}); });
} }
function renderPreorderProducts() { function renderPreOrderProducts() {
var grid = $('#poProductGrid').empty(); var grid = $('#poProductGrid').empty();
$.each(poProducts, function (_, p) { $.each(poProducts, function (_, p) {
poQuantities[p.id] = 0; poQuantities[p.id] = 0;
@ -1004,7 +1004,7 @@
var selected = poProducts.filter(function (p) { return (poQuantities[p.id] || 0) > 0; }); var selected = poProducts.filter(function (p) { return (poQuantities[p.id] || 0) > 0; });
var count = selected.length; var count = selected.length;
$('#poItemCountBadge').text(count); $('#poItemCountBadge').text(count);
$('#submitPreorderBtn').prop('disabled', count === 0); $('#submitPreOrderBtn').prop('disabled', count === 0);
$('#poSelectionSummary').text(count === 0 ? 'Még nincs kiválasztott termék' : count + ' termék kiválasztva'); $('#poSelectionSummary').text(count === 0 ? 'Még nincs kiválasztott termék' : count + ' termék kiválasztva');
if (count === 0) { $('#poSummaryEmpty').show(); $('#poSummaryList, #poSummaryNote').hide(); return; } if (count === 0) { $('#poSummaryEmpty').show(); $('#poSummaryList, #poSummaryNote').hide(); return; }
@ -1022,14 +1022,14 @@
if (hasMeasurable) $('#poSummaryNote').show(); else $('#poSummaryNote').hide(); if (hasMeasurable) $('#poSummaryNote').show(); else $('#poSummaryNote').hide();
} }
function submitPreorder() { function submitPreOrder() {
var selected = poProducts.filter(function (p) { return (poQuantities[p.id] || 0) > 0; }); var selected = poProducts.filter(function (p) { return (poQuantities[p.id] || 0) > 0; });
if (!selected.length) return; if (!selected.length) return;
var btn = $('#submitPreorderBtn').prop('disabled', true) var btn = $('#submitPreOrderBtn').prop('disabled', true)
.html('<i class="fa fa-spinner fa-spin"></i> Előrendelés mentése...'); .html('<i class="fa fa-spinner fa-spin"></i> Előrendelés mentése...');
$.ajax({ $.ajax({
url : '@Url.Action("PlacePreorder", "Order")', url : '@Url.Action("PlacePreOrder", "Order")',
type : 'POST', type : 'POST',
contentType: 'application/json', contentType: 'application/json',
data : JSON.stringify({ data : JSON.stringify({

View File

@ -1,7 +1,7 @@
@using System.Text.Encodings.Web @using System.Text.Encodings.Web
@{ @{
Layout = "_Root"; Layout = "_Root";
ViewBag.Title = T("Plugins.Misc.FruitBankPlugin.Preorder.PageTitle").Text; ViewBag.Title = T("Plugins.Misc.FruitBankPlugin.PreOrder.PageTitle").Text;
} }
<link rel="stylesheet" href="~/Plugins/Misc.FruitBankPlugin/css/quick-order.css" /> <link rel="stylesheet" href="~/Plugins/Misc.FruitBankPlugin/css/quick-order.css" />
@ -14,25 +14,25 @@
<div class="ds-header"> <div class="ds-header">
<i class="fa fa-calendar"></i> <i class="fa fa-calendar"></i>
<div> <div>
<div class="ds-title">@T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Title")</div> <div class="ds-title">@T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Title")</div>
<div class="ds-subtitle">@T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Subtitle")</div> <div class="ds-subtitle">@T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Subtitle")</div>
</div> </div>
</div> </div>
<div class="ds-body"> <div class="ds-body">
<div class="ds-section-label">@T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.DayLabel")</div> <div class="ds-section-label">@T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.DayLabel")</div>
<div class="ds-day-buttons" id="dayButtons"></div> <div class="ds-day-buttons" id="dayButtons"></div>
<div class="ds-section-label" style="margin-top:20px;"> <div class="ds-section-label" style="margin-top:20px;">
@T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.TimeLabel") @T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.TimeLabel")
</div> </div>
<div class="ds-time-wrapper"> <div class="ds-time-wrapper">
<input type="time" id="deliveryTimePicker" class="ds-time-input" value="08:00" min="05:00" max="22:00" /> <input type="time" id="deliveryTimePicker" class="ds-time-input" value="08:00" min="05:00" max="22:00" />
<span class="ds-time-hint">@T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.TimeHint")</span> <span class="ds-time-hint">@T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.TimeHint")</span>
</div> </div>
</div> </div>
<div class="ds-footer"> <div class="ds-footer">
<button type="button" class="ds-confirm-btn" id="deliveryConfirmBtn" disabled> <button type="button" class="ds-confirm-btn" id="deliveryConfirmBtn" disabled>
<i class="fa fa-arrow-right"></i> @T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ConfirmButton") <i class="fa fa-arrow-right"></i> @T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ConfirmButton")
</button> </button>
</div> </div>
</div> </div>
@ -40,10 +40,10 @@
<!-- ── Delivery chip ────────────────────────────────────────────────── --> <!-- ── Delivery chip ────────────────────────────────────────────────── -->
<div id="deliveryChip" class="qo-delivery-chip" style="display:none;"> <div id="deliveryChip" class="qo-delivery-chip" style="display:none;">
<i class="fa fa-calendar-check-o"></i> <i class="fa fa-calendar-check-o"></i>
<span class="dc-label">@T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ChangeLabel")</span> <span class="dc-label">@T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ChangeLabel")</span>
<strong id="deliveryChipText"></strong> <strong id="deliveryChipText"></strong>
<button type="button" class="dc-change-btn" id="deliveryChangeBtn"> <button type="button" class="dc-change-btn" id="deliveryChangeBtn">
<i class="fa fa-pencil"></i> @T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ChangeButton") <i class="fa fa-pencil"></i> @T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ChangeButton")
</button> </button>
</div> </div>
@ -53,7 +53,7 @@
<!-- Info banner --> <!-- Info banner -->
<div class="po-info-banner"> <div class="po-info-banner">
<i class="fa fa-info-circle"></i> <i class="fa fa-info-circle"></i>
@T("Plugins.Misc.FruitBankPlugin.Preorder.InfoBanner") @T("Plugins.Misc.FruitBankPlugin.PreOrder.InfoBanner")
</div> </div>
<div class="qo-layout"> <div class="qo-layout">
@ -64,20 +64,20 @@
<!-- Loading --> <!-- Loading -->
<div id="productsLoadingState" class="products-empty-state"> <div id="productsLoadingState" class="products-empty-state">
<i class="fa fa-spinner fa-spin"></i> <i class="fa fa-spinner fa-spin"></i>
<p>@T("Plugins.Misc.FruitBankPlugin.Preorder.LoadingProducts")</p> <p>@T("Plugins.Misc.FruitBankPlugin.PreOrder.LoadingProducts")</p>
</div> </div>
<!-- No products --> <!-- No products -->
<div id="noProductsCard" class="no-results-card" style="display:none;"> <div id="noProductsCard" class="no-results-card" style="display:none;">
<i class="fa fa-calendar-times-o"></i> <i class="fa fa-calendar-times-o"></i>
<p>@T("Plugins.Misc.FruitBankPlugin.Preorder.NoProductsAvailable")</p> <p>@T("Plugins.Misc.FruitBankPlugin.PreOrder.NoProductsAvailable")</p>
</div> </div>
<!-- Product grid --> <!-- Product grid -->
<div id="productSection" style="display:none;"> <div id="productSection" style="display:none;">
<div class="matches-label"> <div class="matches-label">
<i class="fa fa-cubes"></i> <i class="fa fa-cubes"></i>
<span>@T("Plugins.Misc.FruitBankPlugin.Preorder.ProductsLabel")</span> <span>@T("Plugins.Misc.FruitBankPlugin.PreOrder.ProductsLabel")</span>
</div> </div>
<div id="productGrid" class="product-grid"></div> <div id="productGrid" class="product-grid"></div>
@ -85,19 +85,19 @@
<div class="po-note-section"> <div class="po-note-section">
<label class="po-note-label" for="customerNote"> <label class="po-note-label" for="customerNote">
<i class="fa fa-comment-o"></i> <i class="fa fa-comment-o"></i>
@T("Plugins.Misc.FruitBankPlugin.Preorder.NoteLabel") @T("Plugins.Misc.FruitBankPlugin.PreOrder.NoteLabel")
</label> </label>
<textarea id="customerNote" class="po-note-input" <textarea id="customerNote" class="po-note-input"
placeholder="@T("Plugins.Misc.FruitBankPlugin.Preorder.NotePlaceholder")" placeholder="@T("Plugins.Misc.FruitBankPlugin.PreOrder.NotePlaceholder")"
rows="3" maxlength="1000"></textarea> rows="3" maxlength="1000"></textarea>
</div> </div>
<!-- Submit --> <!-- Submit -->
<div class="po-submit-row"> <div class="po-submit-row">
<div id="selectionSummary" class="po-selection-summary"></div> <div id="selectionSummary" class="po-selection-summary"></div>
<button type="button" id="submitPreorderBtn" class="po-submit-btn" disabled> <button type="button" id="submitPreOrderBtn" class="po-submit-btn" disabled>
<i class="fa fa-paper-plane"></i> <i class="fa fa-paper-plane"></i>
@T("Plugins.Misc.FruitBankPlugin.Preorder.SubmitButton") @T("Plugins.Misc.FruitBankPlugin.PreOrder.SubmitButton")
</button> </button>
</div> </div>
</div> </div>
@ -108,13 +108,13 @@
<div class="qo-cart-panel"> <div class="qo-cart-panel">
<div class="qo-section-title"> <div class="qo-section-title">
<i class="fa fa-list-ul"></i> <i class="fa fa-list-ul"></i>
@T("Plugins.Misc.FruitBankPlugin.Preorder.SummaryTitle") @T("Plugins.Misc.FruitBankPlugin.PreOrder.SummaryTitle")
<span id="itemCountBadge" class="cart-count-badge">0</span> <span id="itemCountBadge" class="cart-count-badge">0</span>
</div> </div>
<div id="summaryEmpty" class="cart-empty"> <div id="summaryEmpty" class="cart-empty">
<i class="fa fa-list-ul"></i> <i class="fa fa-list-ul"></i>
<p>@T("Plugins.Misc.FruitBankPlugin.Preorder.SummaryEmpty")</p> <p>@T("Plugins.Misc.FruitBankPlugin.PreOrder.SummaryEmpty")</p>
</div> </div>
<div id="summaryList" class="cart-items-list" style="display:none;"></div> <div id="summaryList" class="cart-items-list" style="display:none;"></div>
@ -122,7 +122,7 @@
<div id="summaryNote" class="cart-total-row" style="display:none;"> <div id="summaryNote" class="cart-total-row" style="display:none;">
<div class="cart-total-note"> <div class="cart-total-note">
<i class="fa fa-info-circle"></i> <i class="fa fa-info-circle"></i>
<small>@T("Plugins.Misc.FruitBankPlugin.Preorder.SummaryNote")</small> <small>@T("Plugins.Misc.FruitBankPlugin.PreOrder.SummaryNote")</small>
</div> </div>
</div> </div>
</div> </div>
@ -133,10 +133,10 @@
<!-- ── SUCCESS STATE ─────────────────────────────────────────────────── --> <!-- ── SUCCESS STATE ─────────────────────────────────────────────────── -->
<div id="successState" style="display:none;" class="po-success-state"> <div id="successState" style="display:none;" class="po-success-state">
<div class="po-success-icon"><i class="fa fa-check-circle"></i></div> <div class="po-success-icon"><i class="fa fa-check-circle"></i></div>
<h2>@T("Plugins.Misc.FruitBankPlugin.Preorder.SuccessTitle")</h2> <h2>@T("Plugins.Misc.FruitBankPlugin.PreOrder.SuccessTitle")</h2>
<p id="successMessage"></p> <p id="successMessage"></p>
<a href="@Url.RouteUrl("Homepage")" class="po-back-btn"> <a href="@Url.RouteUrl("Homepage")" class="po-back-btn">
<i class="fa fa-home"></i> @T("Plugins.Misc.FruitBankPlugin.Preorder.BackToHome") <i class="fa fa-home"></i> @T("Plugins.Misc.FruitBankPlugin.PreOrder.BackToHome")
</a> </a>
</div> </div>
@ -146,19 +146,19 @@
<script asp-location="Footer"> <script asp-location="Footer">
var poStr = { var poStr = {
dsToday : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Today").Text))', dsToday : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Today").Text))',
dsTomorrow : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Tomorrow").Text))', dsTomorrow : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Tomorrow").Text))',
dsSaving : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.Saving").Text))', dsSaving : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.Saving").Text))',
dsConfirm : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.DeliveryStep.ConfirmButton").Text))', dsConfirm : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.DeliveryStep.ConfirmButton").Text))',
measurable : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.MeasurableBadge").Text))', measurable : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.MeasurableBadge").Text))',
pricePerPc : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.PricePerPiece").Text))', pricePerPc : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.PricePerPiece").Text))',
pieceUnit : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.PieceUnit").Text))', pieceUnit : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.PieceUnit").Text))',
stockLabel : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.StockLabel").Text))', stockLabel : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.StockLabel").Text))',
selNone : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.SelectionNone").Text))', selNone : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.SelectionNone").Text))',
selItems : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.SelectionItems").Text))', selItems : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.SelectionItems").Text))',
submitting : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.Submitting").Text))', submitting : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.Submitting").Text))',
successMsg : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.SuccessMessage").Text))', successMsg : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.SuccessMessage").Text))',
errorPfx : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.ErrorPrefix").Text))', errorPfx : '@Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.ErrorPrefix").Text))',
huDayNames : ['vas\u00e1rnap','h\u00e9tf\u0151','kedd','szerda','cs\u00fct\u00f6rt\u00f6k','p\u00e9ntek','szombat'] huDayNames : ['vas\u00e1rnap','h\u00e9tf\u0151','kedd','szerda','cs\u00fct\u00f6rt\u00f6k','p\u00e9ntek','szombat']
}; };
</script> </script>
@ -196,11 +196,11 @@ var poStr = {
$('#deliveryStep').show(); $('#deliveryStep').show();
}); });
$('#submitPreorderBtn').click(submitPreorder); $('#submitPreOrderBtn').click(submitPreOrder);
// Restore saved delivery datetime if revisiting // Restore saved delivery datetime if revisiting
$.ajax({ $.ajax({
url: '@Url.Action("GetDeliveryDateTime", "Preorder")', url: '@Url.Action("GetDeliveryDateTime", "PreOrder")',
type: 'GET', type: 'GET',
success: function (result) { success: function (result) {
if (!result.success || !result.hasValue) return; if (!result.success || !result.hasValue) return;
@ -251,7 +251,7 @@ var poStr = {
var deliveryDateTime = selectedDeliveryDate + 'T' + selectedDeliveryTime; var deliveryDateTime = selectedDeliveryDate + 'T' + selectedDeliveryTime;
$.ajax({ $.ajax({
url : '@Url.Action("SetDeliveryDateTime", "Preorder")', url : '@Url.Action("SetDeliveryDateTime", "PreOrder")',
type: 'POST', type: 'POST',
data: { data: {
deliveryDateTime: deliveryDateTime, deliveryDateTime: deliveryDateTime,
@ -288,7 +288,7 @@ var poStr = {
$('#productSection').hide(); $('#productSection').hide();
$.ajax({ $.ajax({
url : '@Url.Action("GetAvailableProducts", "Preorder")', url : '@Url.Action("GetAvailableProducts", "PreOrder")',
type: 'GET', type: 'GET',
success: function (result) { success: function (result) {
$('#productsLoadingState').hide(); $('#productsLoadingState').hide();
@ -369,7 +369,7 @@ var poStr = {
var count = selectedItems.length; var count = selectedItems.length;
$('#itemCountBadge').text(count); $('#itemCountBadge').text(count);
$('#submitPreorderBtn').prop('disabled', count === 0); $('#submitPreOrderBtn').prop('disabled', count === 0);
// Selection summary text // Selection summary text
if (count === 0) { if (count === 0) {
@ -413,11 +413,11 @@ var poStr = {
// ── Submit ──────────────────────────────────────────────────────────────── // ── Submit ────────────────────────────────────────────────────────────────
function submitPreorder() { function submitPreOrder() {
var selectedItems = products.filter(function (p) { return (quantities[p.id] || 0) > 0; }); var selectedItems = products.filter(function (p) { return (quantities[p.id] || 0) > 0; });
if (!selectedItems.length) return; if (!selectedItems.length) return;
var btn = $('#submitPreorderBtn'); var btn = $('#submitPreOrderBtn');
btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> ' + poStr.submitting); btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> ' + poStr.submitting);
var payload = { var payload = {
@ -429,7 +429,7 @@ var poStr = {
}; };
$.ajax({ $.ajax({
url : '@Url.Action("PlacePreorder", "Preorder")', url : '@Url.Action("PlacePreOrder", "PreOrder")',
type : 'POST', type : 'POST',
contentType: 'application/json', contentType: 'application/json',
data : JSON.stringify(payload), data : JSON.stringify(payload),
@ -442,13 +442,13 @@ var poStr = {
} else { } else {
alert(poStr.errorPfx + (result.message || '')); alert(poStr.errorPfx + (result.message || ''));
btn.prop('disabled', false) btn.prop('disabled', false)
.html('<i class="fa fa-paper-plane"></i> @Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.SubmitButton").Text))'); .html('<i class="fa fa-paper-plane"></i> @Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrder.SubmitButton").Text))');
} }
}, },
error: function () { error: function () {
alert(poStr.errorPfx); alert(poStr.errorPfx);
btn.prop('disabled', false) btn.prop('disabled', false)
.html('<i class="fa fa-paper-plane"></i> @Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.Preorder.SubmitButton").Text))'); .html('<i class="fa fa-paper-plane"></i> @Html.Raw(JavaScriptEncoder.Default.Encode(T("Plugins.Misc.FruitBankPlugin.PreOrderPreOrder.SubmitButton").Text))');
} }
}); });
} }

View File

@ -1,5 +1,5 @@
/* /*
* Preorder page supplemental styles * PreOrder page supplemental styles
* Inherits all base styles from quick-order.css * Inherits all base styles from quick-order.css
*/ */

File diff suppressed because one or more lines are too long