Compare commits
4 Commits
9b2e34f7b5
...
1b8d2a6aa0
| Author | SHA1 | Date |
|---|---|---|
|
|
1b8d2a6aa0 | |
|
|
1468aa651c | |
|
|
2a8dfeaca9 | |
|
|
8020131c0e |
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Topic Codes — Nop.Plugin.Misc.FruitBankPlugin (`MGFBANKPLUG`)
|
||||||
|
|
||||||
|
Per-repo topic registry for this plugin, per the **per-repo extension convention** in `AyCode.Core/.github/skills/docs-check/references/TOPIC_CODES.md`. Lists ONLY this repo's own topic codes; inherited (ACCORE / Mango.Nop) topics are reached through `@repo.own-dep-repos`.
|
||||||
|
|
||||||
|
**Foundational conventions are defined once at the framework layer — not restated here:**
|
||||||
|
- ID format, type codes (`I`/`T`/`B`/`C`), ID rules, Status vocabulary, archival, registry-maintenance workflow → `AyCode.Core/.github/skills/docs-check/references/TOPIC_CODES.md`
|
||||||
|
- `<PREFIX>` (this repo = `MGFBANKPLUG`, from the `@repo.prefix` in `.github/copilot-instructions.md`) + `<RAND>` spec → `AyCode.Core/.github/REPO_PREFIXES.md`
|
||||||
|
|
||||||
|
## This repo's own topic codes
|
||||||
|
|
||||||
|
| Code | Topic | Scope | Docs location |
|
||||||
|
|--------|-----------|------------------------------------------------------------------------------------------------|------------------|
|
||||||
|
| `PREO` | PRE-ORDER | Customer advance orders placed before stock arrives, and their conversion into real nopCommerce orders when an inbound shipping document confirms incoming stock. | `docs/PREORDER/` |
|
||||||
|
|
@ -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 →</a>
|
<a href="/Admin/PreOrders">Összes előrendelés →</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>'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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}" });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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'));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
@ -137,6 +137,87 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
return await ctx.Partners.GetByIdAsync(partner.Id, partner.ShippingDocuments != null);
|
return await ctx.Partners.GetByIdAsync(partner.Id, partner.ShippingDocuments != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SignalR(SignalRTags.GetCargoPartners)]
|
||||||
|
public async Task<List<CargoPartner>> GetCargoPartners()
|
||||||
|
{
|
||||||
|
_logger.Detail($"GetCargoPartners invoked");
|
||||||
|
|
||||||
|
return await ctx.CargoPartners.GetAll().ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SignalR(SignalRTags.GetCargoPartnerById)]
|
||||||
|
public async Task<CargoPartner> GetCargoPartnerById(int id)
|
||||||
|
{
|
||||||
|
_logger.Detail($"GetCargoPartnerById invoked; id: {id}");
|
||||||
|
return await ctx.CargoPartners.GetByIdAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SignalR(SignalRTags.AddCargoPartner)]
|
||||||
|
public async Task<CargoPartner> AddCargoPartner(CargoPartner cargoPartner)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(cargoPartner);
|
||||||
|
|
||||||
|
_logger.Detail($"AddCargoPartner invoked; id: {cargoPartner.Id}");
|
||||||
|
|
||||||
|
await ctx.CargoPartners.InsertAsync(cargoPartner);
|
||||||
|
return await ctx.CargoPartners.GetByIdAsync(cargoPartner.Id, cargoPartner.CargoTrucks != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SignalR(SignalRTags.UpdateCargoPartner)]
|
||||||
|
public async Task<CargoPartner> UpdateCargoPartner(CargoPartner cargoPartner)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(cargoPartner);
|
||||||
|
|
||||||
|
_logger.Detail($"UpdateCargoPartner invoked; id: {cargoPartner.Id}");
|
||||||
|
|
||||||
|
await ctx.CargoPartners.UpdateAsync(cargoPartner);
|
||||||
|
return await ctx.CargoPartners.GetByIdAsync(cargoPartner.Id, cargoPartner.CargoTrucks != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[SignalR(SignalRTags.GetCargoTrucks)]
|
||||||
|
public async Task<List<CargoTruck>> GetCargoTrucks()
|
||||||
|
{
|
||||||
|
_logger.Detail($"GetCargoTrucks invoked");
|
||||||
|
|
||||||
|
return await ctx.CargoTrucks.GetAll().ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SignalR(SignalRTags.GetCargoTruckById)]
|
||||||
|
public async Task<CargoTruck> GetCargoTruckById(int id)
|
||||||
|
{
|
||||||
|
_logger.Detail($"GetCargoTruckById invoked; id: {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)]
|
||||||
|
public async Task<CargoTruck> AddCargoTruck(CargoTruck cargoTruck)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(cargoTruck);
|
||||||
|
|
||||||
|
_logger.Detail($"AddCargoTruck invoked; id: {cargoTruck.Id}");
|
||||||
|
|
||||||
|
await ctx.CargoTrucks.InsertAsync(cargoTruck);
|
||||||
|
return await ctx.CargoTrucks.GetByIdAsync(cargoTruck.Id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SignalR(SignalRTags.UpdateCargoTruck)]
|
||||||
|
public async Task<CargoTruck> UpdateCargoTruck(CargoTruck cargoTruck)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(cargoTruck);
|
||||||
|
|
||||||
|
_logger.Detail($"UpdateCargoTruck invoked; id: {cargoTruck.Id}");
|
||||||
|
|
||||||
|
await ctx.CargoTrucks.UpdateAsync(cargoTruck);
|
||||||
|
return await ctx.CargoTrucks.GetByIdAsync(cargoTruck.Id, true);
|
||||||
|
}
|
||||||
|
|
||||||
[SignalR(SignalRTags.GetShippings)]
|
[SignalR(SignalRTags.GetShippings)]
|
||||||
public async Task<List<Shipping>> GetShippings()
|
public async Task<List<Shipping>> GetShippings()
|
||||||
|
|
@ -286,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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
using LinqToDB;
|
||||||
|
using Mango.Nop.Data.Repositories;
|
||||||
|
using Nop.Core.Caching;
|
||||||
|
using Nop.Core.Configuration;
|
||||||
|
using Nop.Core.Events;
|
||||||
|
using Nop.Data;
|
||||||
|
|
||||||
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
|
||||||
|
|
||||||
|
public class CargoPartnerDbTable : MgDbTableBase<CargoPartner>
|
||||||
|
{
|
||||||
|
public CargoPartnerDbTable(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings)
|
||||||
|
: base(eventPublisher, dataProvider, shortTermCacheManager, staticCacheManager, appSettings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IOrderedQueryable<CargoPartner> GetAll() => base.GetAll().OrderBy(p => p.Name);
|
||||||
|
|
||||||
|
public IQueryable<CargoPartner> GetAll(bool loadRelations)
|
||||||
|
{
|
||||||
|
return loadRelations ? GetAll().LoadWith(sd => sd.CargoTrucks) : GetAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<CargoPartner> GetByIdAsync(int id, bool loadRelations) => GetAll(loadRelations).FirstOrDefaultAsync(p => p.Id == id);
|
||||||
|
|
||||||
|
public Task<CargoPartner> GetByNameAsync(string name, bool loadRelations) => GetAll(loadRelations).FirstOrDefaultAsync(p => p.Name == name);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
|
using LinqToDB;
|
||||||
|
using Mango.Nop.Data.Repositories;
|
||||||
|
using Nop.Core.Caching;
|
||||||
|
using Nop.Core.Configuration;
|
||||||
|
using Nop.Core.Events;
|
||||||
|
using Nop.Data;
|
||||||
|
|
||||||
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
|
||||||
|
|
||||||
|
public class CargoTruckDbTable : MgDbTableBase<CargoTruck>
|
||||||
|
{
|
||||||
|
public CargoTruckDbTable(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings)
|
||||||
|
: base(eventPublisher, dataProvider, shortTermCacheManager, staticCacheManager, appSettings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IOrderedQueryable<CargoTruck> GetAll() => base.GetAll().OrderBy(p => p.CargoPartnerId);
|
||||||
|
|
||||||
|
public IQueryable<CargoTruck> GetAll(bool loadRelations)
|
||||||
|
{
|
||||||
|
return loadRelations ? GetAll().LoadWith(sd => sd.CargoPartner) : GetAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -55,6 +55,9 @@ public class FruitBankDbContext : MgDbContextBase,
|
||||||
public OrderItemDtoDbTable OrderItemDtos { get; set; }
|
public OrderItemDtoDbTable OrderItemDtos { get; set; }
|
||||||
|
|
||||||
public PartnerDbTable Partners { get; set; }
|
public PartnerDbTable Partners { get; set; }
|
||||||
|
public CargoPartnerDbTable CargoPartners { get; set; }
|
||||||
|
public CargoTruckDbTable CargoTrucks{ get; set; }
|
||||||
|
|
||||||
public ShippingDbTable Shippings { get; set; }
|
public ShippingDbTable Shippings { get; set; }
|
||||||
public ShippingDocumentDbTable ShippingDocuments { get; set; }
|
public ShippingDocumentDbTable ShippingDocuments { get; set; }
|
||||||
public ShippingItemDbTable ShippingItems { get; set; }
|
public ShippingItemDbTable ShippingItems { get; set; }
|
||||||
|
|
@ -78,7 +81,7 @@ public class FruitBankDbContext : MgDbContextBase,
|
||||||
public IRepository<StockQuantityHistoryExt> StockQuantityHistoriesExt { get; set; }
|
public IRepository<StockQuantityHistoryExt> StockQuantityHistoriesExt { get; set; }
|
||||||
|
|
||||||
public FruitBankDbContext(INopDataProvider dataProvider, ILockService lockService, FruitBankAttributeService fruitBankAttributeService, IStoreContext storeContext,
|
public FruitBankDbContext(INopDataProvider dataProvider, ILockService lockService, FruitBankAttributeService fruitBankAttributeService, IStoreContext storeContext,
|
||||||
PartnerDbTable partnerDbTable, ShippingDbTable shippingDbTable, ShippingDocumentDbTable shippingDocumentDbTable, ShippingItemDbTable shippingItemDbTable,
|
CargoPartnerDbTable cargoPartnerDbTable, CargoTruckDbTable cargoTruckDbTable, PartnerDbTable partnerDbTable, ShippingDbTable shippingDbTable, ShippingDocumentDbTable shippingDocumentDbTable, ShippingItemDbTable shippingItemDbTable,
|
||||||
ShippingItemPalletDbTable shippingItemPalletDbTable, FilesDbTable filesDbTable, ShippingDocumentToFilesDbTable shippingDocumentToFilesDbTable,
|
ShippingItemPalletDbTable shippingItemPalletDbTable, FilesDbTable filesDbTable, ShippingDocumentToFilesDbTable shippingDocumentToFilesDbTable,
|
||||||
ProductDtoDbTable productDtoDbTable, OrderDtoDbTable orderDtoDbTable, OrderItemDtoDbTable orderItemDtoDbTable, OrderItemPalletDbTable orderItemPalletDbTable,
|
ProductDtoDbTable productDtoDbTable, OrderDtoDbTable orderDtoDbTable, OrderItemDtoDbTable orderItemDtoDbTable, OrderItemPalletDbTable orderItemPalletDbTable,
|
||||||
StockQuantityHistoryDtoDbTable stockQuantityHistoryDtos, CustomerCreditDbTable customerCreditDbTable,
|
StockQuantityHistoryDtoDbTable stockQuantityHistoryDtos, CustomerCreditDbTable customerCreditDbTable,
|
||||||
|
|
@ -105,6 +108,8 @@ public class FruitBankDbContext : MgDbContextBase,
|
||||||
|
|
||||||
Files = filesDbTable;
|
Files = filesDbTable;
|
||||||
Partners = partnerDbTable;
|
Partners = partnerDbTable;
|
||||||
|
CargoPartners = cargoPartnerDbTable;
|
||||||
|
CargoTrucks = cargoTruckDbTable;
|
||||||
|
|
||||||
ProductDtos = productDtoDbTable;
|
ProductDtos = productDtoDbTable;
|
||||||
|
|
||||||
|
|
@ -225,8 +230,7 @@ public class FruitBankDbContext : MgDbContextBase,
|
||||||
return Task.FromResult(false);
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> AddShippingItemSafeAsync(ShippingItem shippingItem)
|
public Task<bool> AddShippingItemSafeAsync(ShippingItem shippingItem) => TransactionSafeAsync(async _ => await AddShippingItemAsync(shippingItem));
|
||||||
=> TransactionSafeAsync(async _ => await AddShippingItemAsync(shippingItem));
|
|
||||||
|
|
||||||
public async Task<bool> AddShippingItemAsync(ShippingItem shippingItem)
|
public async Task<bool> AddShippingItemAsync(ShippingItem shippingItem)
|
||||||
{
|
{
|
||||||
|
|
@ -251,8 +255,7 @@ public class FruitBankDbContext : MgDbContextBase,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> UpdateShippingItemSafeAsync(ShippingItem shippingItem)
|
public Task<bool> UpdateShippingItemSafeAsync(ShippingItem shippingItem) => TransactionSafeAsync(async _ => await UpdateShippingItemAsync(shippingItem));
|
||||||
=> TransactionSafeAsync(async _ => await UpdateShippingItemAsync(shippingItem));
|
|
||||||
|
|
||||||
public async Task<bool> UpdateShippingItemAsync(ShippingItem shippingItem)
|
public async Task<bool> UpdateShippingItemAsync(ShippingItem shippingItem)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,7 @@ public class PartnerDbTable : MgDbTableBase<Partner>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IOrderedQueryable<Partner> GetAll()
|
public override IOrderedQueryable<Partner> GetAll() => base.GetAll().OrderBy(p => p.Name);
|
||||||
=> base.GetAll().OrderBy(p => p.Name);
|
|
||||||
|
|
||||||
public IQueryable<Partner> GetAll(bool loadRelations)
|
public IQueryable<Partner> GetAll(bool loadRelations)
|
||||||
{
|
{
|
||||||
|
|
@ -29,10 +28,7 @@ public class PartnerDbTable : MgDbTableBase<Partner>
|
||||||
: GetAll();
|
: GetAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Partner> GetByIdAsync(int id, bool loadRelations)
|
public Task<Partner> GetByIdAsync(int id, bool loadRelations) => GetAll(loadRelations).FirstOrDefaultAsync(p => p.Id == id);
|
||||||
=> GetAll(loadRelations).FirstOrDefaultAsync(p => p.Id == id);
|
|
||||||
|
|
||||||
|
public Task<Partner> GetByNameAsync(string name, bool loadRelations) => GetAll(loadRelations).FirstOrDefaultAsync(p => p.Name == name);
|
||||||
public Task<Partner> GetByIdNameAsync(string name, bool loadRelations)
|
|
||||||
=> GetAll(loadRelations).FirstOrDefaultAsync(p => p.Name == name);
|
|
||||||
}
|
}
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,9 @@ public class PluginNopStartup : INopStartup
|
||||||
services.AddScoped<OrderItemPalletDbTable>();
|
services.AddScoped<OrderItemPalletDbTable>();
|
||||||
|
|
||||||
services.AddScoped<PartnerDbTable>();
|
services.AddScoped<PartnerDbTable>();
|
||||||
|
services.AddScoped<CargoPartnerDbTable>();
|
||||||
|
services.AddScoped<CargoTruckDbTable>();
|
||||||
|
|
||||||
services.AddScoped<ShippingDbTable>();
|
services.AddScoped<ShippingDbTable>();
|
||||||
services.AddScoped<ShippingDocumentDbTable>();
|
services.AddScoped<ShippingDocumentDbTable>();
|
||||||
services.AddScoped<ShippingItemDbTable>();
|
services.AddScoped<ShippingItemDbTable>();
|
||||||
|
|
@ -91,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>();
|
||||||
|
|
@ -148,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:
|
||||||
|
|
@ -184,7 +187,7 @@ public class PluginNopStartup : INopStartup
|
||||||
.AddAcBinaryProtocol(opts =>
|
.AddAcBinaryProtocol(opts =>
|
||||||
{
|
{
|
||||||
opts.ProtocolMode = BinaryProtocolMode.AsyncSegment;
|
opts.ProtocolMode = BinaryProtocolMode.AsyncSegment;
|
||||||
opts.FlushPolicy = FlushPolicy.DoubleBuffered;
|
opts.FlushPolicy = FlushPolicy.Coalesced;
|
||||||
|
|
||||||
// Explicit AcLogger instance (name-based category, matches the previous setup).
|
// Explicit AcLogger instance (name-based category, matches the previous setup).
|
||||||
// If omitted, the options extension falls back to ILogger<AcBinaryHubProtocol> from DI.
|
// If omitted, the options extension falls back to ILogger<AcBinaryHubProtocol> from DI.
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,13 @@ public partial class NameCompatibility : INameCompatibility
|
||||||
{ typeof(StockTaking), FruitBankConstClient.StockTakingDbTableName},
|
{ typeof(StockTaking), FruitBankConstClient.StockTakingDbTableName},
|
||||||
{ typeof(StockTakingItem), FruitBankConstClient.StockTakingItemDbTableName},
|
{ typeof(StockTakingItem), FruitBankConstClient.StockTakingItemDbTableName},
|
||||||
{ 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(CargoTruck), FruitBankConstClient.CargoTruckDbTableName},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
> For FruitBankHybridApp domain rules see the FruitBankHybridApp solution's `.github/copilot-instructions.md`
|
> For FruitBankHybridApp domain rules see the FruitBankHybridApp solution's `.github/copilot-instructions.md`
|
||||||
> For core measurement system rules and common domain traps, see: `../../../../FruitBankHybridApp/FruitBank.Common/docs/GLOSSARY.md`
|
> For core measurement system rules and common domain traps, see: `../../../../FruitBankHybridApp/FruitBank.Common/docs/GLOSSARY.md`
|
||||||
|
|
||||||
Server-side nopCommerce plugin for FruitBank, a fruit and vegetable wholesaler. Manages supplier inbound delivery (receiving), warehouse weighing (net/gross/pallet/tare weights), outbound order measurement, inventory stocktaking, AI services, and Billingo invoicing. Runs inside nopCommerce 4.80.9 (**net9.0**).
|
Server-side nopCommerce plugin for FruitBank, a fruit and vegetable wholesaler. Manages supplier inbound delivery (receiving), warehouse weighing (net/gross/pallet/tare weights), outbound order measurement, customer pre-orders, inventory stocktaking, AI services, and Billingo invoicing. Runs inside nopCommerce 4.80.9 (**net9.0**).
|
||||||
|
|
||||||
Project file: `Nop.Plugin.Misc.FruitBankPlugin.csproj`
|
Project file: `Nop.Plugin.Misc.FruitBankPlugin.csproj`
|
||||||
|
|
||||||
|
|
@ -25,6 +25,7 @@ Project file: `Nop.Plugin.Misc.FruitBankPlugin.csproj`
|
||||||
| `docs/AI_SERVICES.md` | OpenAI, Cerebras, Replicate providers, FruitBankSettings, file storage, PDF conversion, audio transcription |
|
| `docs/AI_SERVICES.md` | OpenAI, Cerebras, Replicate providers, FruitBankSettings, file storage, PDF conversion, audio transcription |
|
||||||
| `docs/DATA_LAYER.md` | FruitBankDbContext, StockTakingDbContext, DbTable repositories, entity mapping, FruitBankEventConsumer |
|
| `docs/DATA_LAYER.md` | FruitBankDbContext, StockTakingDbContext, DbTable repositories, entity mapping, FruitBankEventConsumer |
|
||||||
| `docs/SIGNALR/README.md` | SignalR endpoints, FruitBankDataController, InnVoice/Billingo integration, FruitBankAttributeService |
|
| `docs/SIGNALR/README.md` | SignalR endpoints, FruitBankDataController, InnVoice/Billingo integration, FruitBankAttributeService |
|
||||||
|
| `docs/PREORDER/README.md` | Customer pre-order workflow — placement, FCFS allocation on incoming stock, conversion to real orders, expiry |
|
||||||
|
|
||||||
## Folder Structure
|
## Folder Structure
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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))');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
> Part of `Nop.Plugin.Misc.FruitBankPlugin`. See `README.md` for project overview.
|
> Part of `Nop.Plugin.Misc.FruitBankPlugin`. See `README.md` for project overview.
|
||||||
> For entity definitions, properties, and relationships see `docs/SCHEMA.md` (authoritative TOON schema).
|
> For entity definitions, properties, and relationships see `docs/SCHEMA.md` (authoritative TOON schema).
|
||||||
> For measurement workflows see `docs/MEASUREMENT.md`.
|
> For measurement workflows see `docs/MEASUREMENT.md`.
|
||||||
|
> For the customer pre-order workflow see `docs/PREORDER/README.md`.
|
||||||
> For data layer details see `docs/DATA_LAYER.md`.
|
> For data layer details see `docs/DATA_LAYER.md`.
|
||||||
> For core measurement system rules and common domain traps, see: `../../../../../FruitBankHybridApp/FruitBank.Common/docs/GLOSSARY.md`
|
> For core measurement system rules and common domain traps, see: `../../../../../FruitBankHybridApp/FruitBank.Common/docs/GLOSSARY.md`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
> Part of `Nop.Plugin.Misc.FruitBankPlugin`. See `README.md` for project overview.
|
> Part of `Nop.Plugin.Misc.FruitBankPlugin`. See `README.md` for project overview.
|
||||||
> For entity model see `docs/DOMAIN_MODEL.md`.
|
> For entity model see `docs/DOMAIN_MODEL.md`.
|
||||||
> For SignalR endpoints see `docs/SIGNALR/README.md`.
|
> For SignalR endpoints see `docs/SIGNALR/README.md`.
|
||||||
|
> For the customer pre-order workflow (advance orders converted on stock arrival) see `docs/PREORDER/README.md`.
|
||||||
> For core measurement system rules and common domain traps, see: `../../../../../FruitBankHybridApp/FruitBank.Common/docs/GLOSSARY.md`
|
> For core measurement system rules and common domain traps, see: `../../../../../FruitBankHybridApp/FruitBank.Common/docs/GLOSSARY.md`
|
||||||
|
|
||||||
Three parallel measurement workflows share the same weight formula and `MeasuringStatus` lifecycle.
|
Three parallel measurement workflows share the same weight formula and `MeasuringStatus` lifecycle.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# PRE-ORDER — Known Issues
|
||||||
|
|
||||||
|
> Companion to [`README.md`](README.md). Topic `PREO`, prefix `MGFBANKPLUG` → entry IDs `MGFBANKPLUG-PREO-I-<RAND>` (issue) / `-B-` (confirmed bug).
|
||||||
|
> ID format, Status vocabulary, type codes and archival are **not restated here** — see `../../.github/TOPIC_CODES.md` (which points to the framework registry). Entry template: follow `AyCode.Core/.github/META_ISSUES.md`.
|
||||||
|
|
||||||
|
Scope: bugs / inconsistent or observable edge-case behaviour in the pre-order subsystem — placement, allocation/conversion, conversion window, expiry sweep, order create/append, product replacement, status derivation, pricing.
|
||||||
|
|
||||||
|
## Active entries
|
||||||
|
|
||||||
|
*(None yet — to be filled from the code review as needed.)*
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# PRE-ORDER — TODO
|
||||||
|
|
||||||
|
> Companion to [`README.md`](README.md). Topic `PREO`, prefix `MGFBANKPLUG` → entry IDs `MGFBANKPLUG-PREO-T-<RAND>`.
|
||||||
|
> Priority legend, Status vocabulary and archival are **not restated here** — see `../../.github/TOPIC_CODES.md` (which points to the framework registry). Entry template: follow `AyCode.Core/.github/META_TODO.md`.
|
||||||
|
|
||||||
|
Scope: planned work / refactors / optimizations for the pre-order subsystem.
|
||||||
|
|
||||||
|
## Active entries
|
||||||
|
|
||||||
|
*(None yet — to be filled from the code review as needed.)*
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
# Pre-Order Workflow
|
||||||
|
|
||||||
|
> Part of `Nop.Plugin.Misc.FruitBankPlugin`. See `../README.md` for the docs index, `../../README.md` for project overview.
|
||||||
|
> For entity definitions (`PreOrder`, `PreOrderItem`, `PreOrderStatus`, `PreOrderItemStatus`) see `../SCHEMA.md` (authoritative TOON schema).
|
||||||
|
> For the entity-hierarchy overview see `../DOMAIN_MODEL.md`; for the inbound shipping flow that triggers conversion see `../MEASUREMENT.md` (Workflow 1).
|
||||||
|
> Known issues / planned work: [`PREORDER_ISSUES.md`](PREORDER_ISSUES.md) / [`PREORDER_TODO.md`](PREORDER_TODO.md).
|
||||||
|
|
||||||
|
Customers place **pre-orders** for products that are not yet physically in stock. When a supplier shipping document confirms incoming stock, pending pre-orders are allocated against that stock and converted into real nopCommerce orders. This is the fourth domain workflow alongside the three measurement workflows (shipping, order, stocktaking).
|
||||||
|
|
||||||
|
## Entity Hierarchy
|
||||||
|
|
||||||
|
```
|
||||||
|
PreOrder (fbPreOrder) ← customer advance-order header
|
||||||
|
└─ PreOrderItem (fbPreOrderItem) ← requested product line [1:N]
|
||||||
|
|
||||||
|
PreOrder.OrderId ─▶ Order ← real nopCommerce order, created on first fulfilment [N:1, nullable]
|
||||||
|
```
|
||||||
|
|
||||||
|
Both entities live in `FruitBank.Common/Entities/` (shared with FruitBankHybridApp), inherit `MgEntityBase`, and use LinqToDB.
|
||||||
|
|
||||||
|
## Status Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
PreOrderStatus: Pending(0) → Confirmed(10) / PartiallyFulfilled(20) / Cancelled(30)
|
||||||
|
PreOrderItemStatus: Pending(0) → Fulfilled(10) / PartiallyFulfilled(20) / Dropped(30)
|
||||||
|
```
|
||||||
|
|
||||||
|
Header status is **derived from item states** by `PreOrderDbContext.RefreshPreOrderStatusAsync`:
|
||||||
|
|
||||||
|
| Condition | PreOrder.Status |
|
||||||
|
|---|---|
|
||||||
|
| all items `Fulfilled` | `Confirmed` |
|
||||||
|
| any `Dropped`/`PartiallyFulfilled` AND none `Pending` | `PartiallyFulfilled` |
|
||||||
|
| otherwise | `Pending` |
|
||||||
|
| customer/admin cancel | `Cancelled` (set explicitly; active items → `Dropped`) |
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### 1. Placement
|
||||||
|
|
||||||
|
`PreOrderController.PlacePreOrder` (public), also `OrderController` and `PreOrderAdminController` (admin):
|
||||||
|
|
||||||
|
- Available products are filtered by the per-product pre-order window (`PreOrderWindowStart` / `PreOrderWindowEnd` product GenericAttributes).
|
||||||
|
- Unit price is captured at placement into `PreOrderItem.UnitPriceInclTax` (price lock).
|
||||||
|
- `PreOrderDbContext.InsertPreOrderAsync` saves the header (`Status = Pending`) and items (`FulfilledQuantity = 0`, `Status = Pending`).
|
||||||
|
|
||||||
|
### 2. Conversion (on incoming stock)
|
||||||
|
|
||||||
|
`PreOrderConversionService.ConvertPreOrdersForProductsAsync(productIds, shippingDocumentId)`:
|
||||||
|
|
||||||
|
1. **Expiry sweep** first (see §4).
|
||||||
|
2. Fetch `Pending`/`PartiallyFulfilled` items for the products (FCFS-ordered by `PreOrderId`).
|
||||||
|
3. **Conversion-window filter** — only preorders whose `DateOfReceipt` is within `PreOrderConversionWindowDays` (4 days) are eligible; farther-out deliveries wait for a later document.
|
||||||
|
4. Build the incoming-quantity pool (`AvailableQuantity` minus quantities already committed to preorders).
|
||||||
|
5. Allocate FCFS: per item `fulfill = min(RequestedQuantity − FulfilledQuantity, available)`; set item status `Fulfilled` / `PartiallyFulfilled`.
|
||||||
|
6. **First fulfilment** for a preorder → `CreateOrderAsync` (creates the nopCommerce `Order`, saves `PreOrder.OrderId`). **Subsequent documents** → `AppendItemsToOrderAsync` (adds the newly-fulfilled items to the same order).
|
||||||
|
7. Dropped items are recorded in an order note (never become order items).
|
||||||
|
|
||||||
|
### 3. Triggers
|
||||||
|
|
||||||
|
| Source | When |
|
||||||
|
|---|---|
|
||||||
|
| `FruitBankEventConsumer` (`EntityInserted/UpdatedEvent<ShippingItem>`) | shipping item with a matched product + quantity (background `Task.Run`) |
|
||||||
|
| `FruitBankDataController.UpdateShippingItem` | shipping-item quantity increase |
|
||||||
|
| `FileManagerController` | shipping-document processing (batch over the document's products) |
|
||||||
|
| `OrderController` / `PreOrderAdminController` | manual / immediate-from-stock |
|
||||||
|
|
||||||
|
`IncomingQuantity` is kept in sync by `FruitBankDataController.AddShippingItem` / `UpdateShippingItem` → `PreOrderConversionService.SyncIncomingQuantityAsync`. Product replacement on a shipping item is handled by `ReplaceShippingItemProductAsync` (`PreOrderConversionService.Replace.cs`), which moves stock/incoming, swaps order items, repoints preorder items, and re-triggers conversion for the new product.
|
||||||
|
|
||||||
|
### 4. Expiry
|
||||||
|
|
||||||
|
`SweepExpiredPreordersAsync` runs at the start of every conversion run: for preorders past `DateOfReceipt` still holding `Pending`/`PartiallyFulfilled` items, any still-`Pending` item becomes `Dropped` and the header status is refreshed. Already-fulfilled quantities stay (they are already on a real order).
|
||||||
|
|
||||||
|
## Pricing
|
||||||
|
|
||||||
|
| Product | Order-item price on conversion |
|
||||||
|
|---|---|
|
||||||
|
| Non-measurable | `UnitPriceInclTax × FulfilledQuantity` (locked at placement) |
|
||||||
|
| Measurable | `0` at conversion — weighed afterwards (see `../MEASUREMENT.md` Workflow 2) |
|
||||||
|
|
||||||
|
## Key Source
|
||||||
|
|
||||||
|
| Type | Location |
|
||||||
|
|---|---|
|
||||||
|
| `PreOrderConversionService` (+ `.Replace` partial) | `Services/` |
|
||||||
|
| `PreOrderDbContext` (`InsertPreOrderAsync`, `RefreshPreOrderStatusAsync`, `CancelPreOrderAsync`) | `Domains/DataLayer/` |
|
||||||
|
| `PreOrderDbTable` / `PreOrderItemDbTable` (+ `IPreOrderDbSet` / `IPreOrderItemDbSet`) | `Domains/DataLayer/` |
|
||||||
|
| `PreOrderController` / `CustomerPreOrderController` | `Controllers/` |
|
||||||
|
| `PreOrderAdminController` / `PreOrderAvailabilityController` | `Areas/Admin/Controllers/` |
|
||||||
|
| `PreOrder` / `PreOrderItem` entities | `FruitBank.Common/Entities/` (FruitBankHybridApp solution) |
|
||||||
|
|
@ -13,6 +13,7 @@ Topic documentation for the FruitBank-specific NopCommerce plugin (Layer 3 — c
|
||||||
## Topic folders
|
## Topic folders
|
||||||
|
|
||||||
- [`SIGNALR/`](SIGNALR/README.md) — SignalR endpoints exposed by this plugin (project-specific variant)
|
- [`SIGNALR/`](SIGNALR/README.md) — SignalR endpoints exposed by this plugin (project-specific variant)
|
||||||
|
- [`PREORDER/`](PREORDER/README.md) — Customer pre-order workflow: advance orders converted to real orders on incoming stock (+ `PREORDER_ISSUES.md` / `PREORDER_TODO.md`)
|
||||||
|
|
||||||
## Navigation
|
## Navigation
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue