Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Preorder/Detail.cshtml

484 lines
25 KiB
Plaintext

@model Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.PreOrderDetailModel
@using FruitBank.Common.Enums
@{
ViewBag.PageTitle = $"Előrendelés #{Model.PreOrderId}";
Layout = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/_FruitBankAdminLayout.cshtml";
var statusClass = Model.Status switch
{
PreOrderStatus.Confirmed => "po-status-confirmed",
PreOrderStatus.PartiallyFulfilled => "po-status-partial",
PreOrderStatus.Cancelled => "po-status-cancelled",
_ => "po-status-pending"
};
var statusLabel = Model.Status switch
{
PreOrderStatus.Confirmed => "Megerősítve",
PreOrderStatus.PartiallyFulfilled => "Részben teljesítve",
PreOrderStatus.Cancelled => "Törölve",
_ => "Függőben"
};
var canEdit = Model.Status != PreOrderStatus.Cancelled;
}
@Html.AntiForgeryToken()
<style>
.po-status-pending { background:#fff3cd; color:#856404; border-radius:6px; padding:4px 12px; font-weight:700; display:inline-block; }
.po-status-confirmed { background:#d4edda; color:#155724; border-radius:6px; padding:4px 12px; font-weight:700; display:inline-block; }
.po-status-partial { background:#fff8ee; color:#c87500; border-radius:6px; padding:4px 12px; font-weight:700; display:inline-block; }
.po-status-cancelled { background:#f8d7da; color:#721c24; border-radius:6px; padding:4px 12px; font-weight:700; display:inline-block; }
.po-meta-card { background:#fff; border:1px solid #dde8da; border-radius:8px; padding:16px 20px; margin-bottom:20px; }
.po-meta-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); gap:16px; }
.po-meta-item .label { font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.5px; color:#6b7c6e; margin-bottom:4px; }
.po-meta-item .value { font-size:15px; color:#1a3c22; font-weight:600; }
.item-fulfilled { background:#eaf7ee; }
.item-partial { background:#fffbf0; }
.item-dropped { background:#fdf0f0; color:#888; }
.qty-bar-wrap { width:100px; display:inline-block; vertical-align:middle; }
.qty-bar { height:6px; background:#dde8da; border-radius:3px; overflow:hidden; display:inline-block; width:100%; }
.qty-bar-fill { height:100%; border-radius:3px; }
/* Click-to-edit */
.editable-cell { cursor:pointer; border-radius:4px; padding:2px 6px; transition:background .15s; }
.editable-cell:hover { background:#f0f8f1; outline:1px dashed #2d7a3a; }
.edit-input { width:90px; }
.btn-swap { padding:2px 7px; font-size:11px; }
.ui-autocomplete { z-index:1060 !important; max-height:220px; overflow-y:auto; overflow-x:hidden; }
</style>
<a href="/Admin/PreOrders" class="btn btn-default btn-sm mb-3">
<i class="fas fa-arrow-left"></i> Vissza a listához
</a>
<div class="content-header clearfix">
<h1 class="float-left">
<i class="fas fa-calendar-plus" style="color:#2d7a3a;"></i>
Előrendelés <strong>#@Model.PreOrderId</strong>
<span class="@statusClass ml-2">@statusLabel</span>
</h1>
<div class="float-right d-flex align-items-center" style="gap:8px;">
@if (Model.OrderId.HasValue)
{
<a href="/Admin/Order/Edit/@Model.OrderId" class="btn btn-success btn-sm" target="_blank">
<i class="fas fa-external-link-alt"></i> Rendelés #@Model.OrderId
</a>
}
@if (canEdit)
{
<button id="convertNowBtn" class="btn btn-warning btn-sm">
<i class="fas fa-bolt"></i> Konvertálás rendeléssé
</button>
<button id="editHeaderBtn" class="btn btn-default btn-sm">
<i class="fas fa-pencil-alt"></i> Szerkesztés
</button>
}
@if (Model.Status == PreOrderStatus.Pending)
{
<button id="cancelBtn" class="btn btn-danger btn-sm">
<i class="fas fa-times"></i> Visszavonás
</button>
}
</div>
</div>
<section class="content">
<div class="container-fluid">
<!-- ── Meta card ─────────────────────────────────────────────────── -->
<div class="po-meta-card">
<!-- Read-only view -->
<div id="headerView">
<div class="po-meta-grid">
<div class="po-meta-item">
<div class="label">Ügyfél</div>
<div class="value"><a href="/Admin/Customer/Edit/@Model.CustomerId">@Model.CustomerName</a></div>
<small class="text-muted">@Model.CustomerEmail</small>
</div>
<div class="po-meta-item">
<div class="label">Kért szállítási időpont</div>
<div class="value"><i class="fas fa-calendar-day text-muted mr-1"></i>@Model.DateOfReceipt</div>
</div>
<div class="po-meta-item">
<div class="label">Leadva</div>
<div class="value">@Model.CreatedOnUtc</div>
</div>
<div class="po-meta-item">
<div class="label">Utoljára frissítve</div>
<div class="value">@Model.UpdatedOnUtc</div>
</div>
@if (!string.IsNullOrWhiteSpace(Model.CustomerNote))
{
<div class="po-meta-item" style="grid-column:1/-1;">
<div class="label">Ügyfél megjegyzése</div>
<div class="value" style="font-weight:400;font-size:14px;color:#444;">@Model.CustomerNote</div>
</div>
}
</div>
</div>
<!-- Edit form -->
<div id="headerEdit" style="display:none;">
<div class="form-row align-items-end">
<div class="col-md-3">
<label class="small font-weight-bold text-muted text-uppercase">Kért szállítási időpont</label>
<input type="datetime-local" id="editDelivery" class="form-control" value="@Model.DateOfReceiptRaw" />
</div>
<div class="col-md-6">
<label class="small font-weight-bold text-muted text-uppercase">Ügyfél megjegyzése</label>
<input type="text" id="editNote" class="form-control" value="@Model.CustomerNote"
maxlength="1000" placeholder="Megjegyzés..." />
</div>
<div class="col-md-3 d-flex" style="gap:8px;">
<button id="saveHeaderBtn" class="btn btn-success btn-sm flex-grow-1">
<i class="fas fa-save"></i> Mentés
</button>
<button id="cancelHeaderBtn" class="btn btn-secondary btn-sm">Mégse</button>
</div>
</div>
</div>
</div>
<!-- ── Items table ────────────────────────────────────────────────── -->
<div class="card card-default">
<div class="card-header">
<strong>Tételek (@Model.Items.Count)</strong>
@{
var fulfilled = Model.Items.Count(i => i.Status == PreOrderItemStatus.Fulfilled);
var partial = Model.Items.Count(i => i.Status == PreOrderItemStatus.PartiallyFulfilled);
var dropped = Model.Items.Count(i => i.Status == PreOrderItemStatus.Dropped);
var pending = Model.Items.Count(i => i.Status == PreOrderItemStatus.Pending);
}
<span class="ml-2 text-muted" style="font-size:13px;">
@if (fulfilled > 0) { <span class="badge badge-success">@fulfilled teljesítve</span> }
@if (partial > 0) { <span class="badge badge-warning ml-1">@partial részben</span> }
@if (dropped > 0) { <span class="badge badge-danger ml-1">@dropped ejtve</span> }
@if (pending > 0) { <span class="badge badge-secondary ml-1">@pending függőben</span> }
</span>
</div>
<div class="card-body p-0">
<table class="table table-bordered table-hover table-sm m-0">
<thead>
<tr>
<th>Termék</th>
<th width="90" class="text-center">Kérve</th>
<th width="90" class="text-center">Teljesítve</th>
<th width="150">Teljesítés</th>
<th width="130" class="text-right">Egységár</th>
<th width="130" class="text-right">Becsült ár</th>
<th width="110" class="text-center">Állapot</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Items)
{
var rowClass = item.Status switch
{
PreOrderItemStatus.Fulfilled => "item-fulfilled",
PreOrderItemStatus.PartiallyFulfilled => "item-partial",
PreOrderItemStatus.Dropped => "item-dropped",
_ => ""
};
var pct = item.RequestedQuantity > 0
? (int)Math.Round((double)item.FulfilledQuantity / item.RequestedQuantity * 100)
: 0;
var barColor = pct == 100 ? "#2d7a3a" : pct > 0 ? "#f4a236" : "#dc3545";
var estimatedPrice = item.IsMeasurable
? "—"
: (item.UnitPriceInclTax * item.FulfilledQuantity).ToString("N0") + " Ft";
var unitPriceStr = item.IsMeasurable ? "súlymérés" : item.UnitPriceInclTax.ToString("N0") + " Ft";
var isLocked = item.FulfilledQuantity > 0;
<tr class="@rowClass" data-item-id="@item.ItemId">
<!-- Product + optional swap -->
<td>
<a href="/Admin/Product/Edit/@item.ProductId" target="_blank">@item.ProductName</a>
@if (item.IsMeasurable)
{
<span class="badge badge-light ml-1" title="Súlymérést igényel">⚖️</span>
}
@if (canEdit && !isLocked)
{
<button class="btn btn-default btn-swap ml-1 swap-product-btn"
data-item-id="@item.ItemId"
data-product-id="@item.ProductId"
title="Termék cseréje">
<i class="fas fa-exchange-alt"></i>
</button>
}
@if (isLocked)
{
<i class="fas fa-lock text-muted ml-1" title="Már teljesített" style="font-size:11px;"></i>
}
</td>
<!-- Requested qty — click-to-edit if pending -->
<td class="text-center">
@if (canEdit && !isLocked)
{
<span class="editable-cell qty-display" data-item-id="@item.ItemId">@item.RequestedQuantity db</span>
<span class="qty-edit-wrap" style="display:none;">
<input type="number" class="form-control form-control-sm edit-input qty-input"
min="1" value="@item.RequestedQuantity" data-item-id="@item.ItemId" />
</span>
}
else
{
@item.RequestedQuantity<text> db</text>
}
</td>
<!-- Fulfilled — always read-only -->
<td class="text-center"><strong>@item.FulfilledQuantity db</strong></td>
<!-- Progress bar -->
<td>
<div class="qty-bar-wrap">
<div class="qty-bar">
<div class="qty-bar-fill" style="width:@pct%;background:@barColor;"></div>
</div>
</div>
<small class="ml-1">@pct%</small>
</td>
<!-- Unit price — click-to-edit if pending and not measurable -->
<td class="text-right">
@if (canEdit && !isLocked && !item.IsMeasurable)
{
<span class="editable-cell price-display" data-item-id="@item.ItemId">@unitPriceStr</span>
<span class="price-edit-wrap" style="display:none;">
<input type="number" class="form-control form-control-sm edit-input price-input"
min="0" value="@item.UnitPriceInclTax" data-item-id="@item.ItemId" />
</span>
}
else
{
@unitPriceStr
}
</td>
<td class="text-right">@estimatedPrice</td>
<td class="text-center">
<span class="po-status-@item.Status.ToString().ToLower()" style="font-size:11px;padding:2px 6px;">
@item.StatusLabel
</span>
</td>
</tr>
}
</tbody>
@{
var totalEstimated = Model.Items
.Where(i => !i.IsMeasurable &&
(i.Status == PreOrderItemStatus.Fulfilled || i.Status == PreOrderItemStatus.PartiallyFulfilled))
.Sum(i => i.UnitPriceInclTax * i.FulfilledQuantity);
}
<tfoot>
<tr>
<td colspan="5" class="text-right"><strong>Becsült összeg:</strong></td>
<td class="text-right"><strong>@totalEstimated.ToString("N0") Ft</strong></td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</section>
<!-- ── Product swap modal ──────────────────────────────────────────────── -->
<div id="swapProductModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header" style="background:#2d7a3a;color:#fff;">
<h5 class="modal-title"><i class="fas fa-exchange-alt"></i> Termék cseréje</h5>
<button type="button" class="close" data-dismiss="modal" style="color:#fff;"><span>&times;</span></button>
</div>
<div class="modal-body">
<p class="text-muted" style="font-size:13px;">Csak az előrendelési ablakban elérhető termékek jelennek meg.</p>
<input type="text" id="swapProductSearch" autocomplete="off" class="form-control"
placeholder="Termék neve vagy SKU..." />
<div id="swapSelectedProduct" class="mt-2" style="display:none;">
<span class="badge badge-success p-2" id="swapSelectedName"></span>
<span class="ml-2 text-muted" id="swapNewPrice"></span>
</div>
<input type="hidden" id="swapNewProductId" />
<input type="hidden" id="swapItemId" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Mégse</button>
<button type="button" id="confirmSwapBtn" class="btn btn-primary" disabled>
<i class="fas fa-check"></i> Csere megerősítése
</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
var _token = $('input[name="__RequestVerificationToken"]').val();
var preorderId = @Model.PreOrderId;
// ── Header edit toggle ────────────────────────────────────────────────
$('#editHeaderBtn').click(function () {
$('#headerView').hide(); $('#headerEdit').show(); $(this).hide();
});
$('#cancelHeaderBtn').click(function () {
$('#headerEdit').hide(); $('#headerView').show(); $('#editHeaderBtn').show();
});
$('#saveHeaderBtn').click(function () {
var $btn = $(this).prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
$.post('/Admin/PreOrders/UpdateHeader/' + preorderId, {
deliveryDateTime: $('#editDelivery').val(),
customerNote : $('#editNote').val(),
__RequestVerificationToken: _token
}, function (r) {
if (r.success) { location.reload(); }
else {
alert('Hiba: ' + (r.error || 'Ismeretlen hiba'));
$btn.prop('disabled', false).html('<i class="fas fa-save"></i> Mentés');
}
});
});
// ── Click-to-edit: quantity ───────────────────────────────────────────
$(document).on('click', '.qty-display', function () {
var id = $(this).data('item-id');
$(this).hide();
$('[data-item-id="' + id + '"].qty-input').closest('.qty-edit-wrap').show()
.find('.qty-input').focus().select();
});
$(document).on('keydown blur', '.qty-input', function (e) {
if (e.type === 'keydown' && e.key === 'Escape') { cancelEdit($(this).data('item-id'), 'qty'); return; }
if (e.type === 'keydown' && e.key !== 'Enter') return;
saveItemField($(this).data('item-id'));
});
// ── Click-to-edit: price ──────────────────────────────────────────────
$(document).on('click', '.price-display', function () {
var id = $(this).data('item-id');
$(this).hide();
$(this).siblings('.price-edit-wrap').show().find('.price-input').focus().select();
});
$(document).on('keydown blur', '.price-input', function (e) {
if (e.type === 'keydown' && e.key === 'Escape') { cancelEdit($(this).data('item-id'), 'price'); return; }
if (e.type === 'keydown' && e.key !== 'Enter') return;
saveItemField($(this).data('item-id'));
});
function cancelEdit(id, type) {
if (type === 'qty') {
$('[data-item-id="' + id + '"].qty-display').show();
$('[data-item-id="' + id + '"].qty-input').closest('.qty-edit-wrap').hide();
} else {
$('[data-item-id="' + id + '"].price-display').show();
$('[data-item-id="' + id + '"].price-input').closest('.price-edit-wrap').hide();
}
}
function saveItemField(id) {
var qty = parseInt($('[data-item-id="' + id + '"].qty-input').val()) || 1;
var price = parseFloat($('[data-item-id="' + id + '"].price-input').val()) || 0;
$.post('/Admin/PreOrders/UpdateItem/' + id, {
quantity: qty, unitPrice: price, __RequestVerificationToken: _token
}, function (r) {
if (r.success) {
$('[data-item-id="' + id + '"].qty-display').text(qty + ' db').show();
$('[data-item-id="' + id + '"].qty-input').closest('.qty-edit-wrap').hide();
$('[data-item-id="' + id + '"].price-display').text(Math.round(price).toLocaleString('hu-HU') + ' Ft').show();
$('[data-item-id="' + id + '"].price-input').closest('.price-edit-wrap').hide();
} else {
alert('Hiba: ' + (r.error || 'Ismeretlen hiba'));
}
});
}
// ── Product swap ──────────────────────────────────────────────────────
$(document).on('click', '.swap-product-btn', function () {
$('#swapItemId').val($(this).data('item-id'));
$('#swapNewProductId').val('');
$('#swapProductSearch, #swapSelectedName, #swapNewPrice').val('').text('');
$('#swapSelectedProduct').hide();
$('#confirmSwapBtn').prop('disabled', true);
$('#swapProductModal').modal('show');
});
$('#swapProductSearch').autocomplete({
delay: 400, minLength: 2,
source: '/Admin/CustomOrder/PreorderProductSearchAutoComplete',
select: function (e, ui) {
$('#swapNewProductId').val(ui.item.value);
$('#swapSelectedName').text(ui.item.label);
$('#swapSelectedProduct').show();
$('#confirmSwapBtn').prop('disabled', false);
$('#swapProductSearch').val('');
return false;
}
});
$('#swapProductModal').on('hidden.bs.modal', function () {
$('#swapNewProductId').val('');
$('#swapSelectedProduct').hide();
$('#confirmSwapBtn').prop('disabled', true);
});
$('#confirmSwapBtn').click(function () {
var $btn = $(this).prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
$.post('/Admin/PreOrders/ReplaceItemProduct/' + $('#swapItemId').val(), {
newProductId: $('#swapNewProductId').val(),
__RequestVerificationToken: _token
}, function (r) {
if (r.success) {
$('#swapProductModal').modal('hide');
location.reload();
} else {
alert('Hiba: ' + (r.error || 'Ismeretlen hiba'));
$btn.prop('disabled', false).html('<i class="fas fa-check"></i> Csere megerősítése');
}
});
});
// ── Manual conversion ─────────────────────────────────────────────────
$('#convertNowBtn').click(function () {
if (!confirm('Azonnali konvertálást indítasz — a függőben lévő tételeket megpróbálja rendeléssé alakítani a jelenlegi készlet alapján. Folytatod?')) return;
var $btn = $(this).prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Konvertálás...');
$.post('/Admin/PreOrders/ConvertNow/' + preorderId, {
__RequestVerificationToken: _token
}, function (r) {
if (r.success) {
alert(r.orderId
? 'Sikeresen konvertálva! Rendelés: #' + r.orderId
: 'A konvertálás lefutott, de nincs elegendő készlet.');
location.reload();
} else {
alert('Hiba: ' + (r.error || 'Ismeretlen hiba'));
$btn.prop('disabled', false).html('<i class="fas fa-bolt"></i> Konvertálás rendeléssé');
}
});
});
// ── Cancel preorder ───────────────────────────────────────────────────
@if (Model.Status == PreOrderStatus.Pending)
{
<text>
$('#cancelBtn').click(function () {
if (!confirm('Biztosan visszavonod ezt az előrendelést? Ez a művelet nem visszafordítható.')) return;
$.post('/Admin/PreOrders/Cancel/@Model.PreOrderId', {
__RequestVerificationToken: _token
}, function (r) {
if (r.success) { location.href = '/Admin/PreOrders'; }
else { alert('Hiba: ' + (r.error || 'Ismeretlen hiba')); }
});
});
</text>
}
});
</script>