Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml

963 lines
35 KiB
Plaintext

@model OrderSearchModel
@inject IStoreService storeService
@using FruitBank.Common.Interfaces
@using Nop.Plugin.Misc.FruitBankPlugin.Models
@using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders
@using Nop.Services.Stores
@using Nop.Web.Areas.Admin.Components
@using Nop.Web.Areas.Admin.Models.Orders
@using Nop.Web.Framework.Infrastructure
@using Nop.Web.Framework.Models.DataTables
@using static Nop.Services.Common.NopLinksDefaults
@{
//page title
ViewBag.PageTitle = T("Admin.Orders").Text;
//active menu item (system name)
NopHtml.SetActiveMenuItemSystemName("Orders");
}
@{
const string hideSearchBlockAttributeName = "OrdersPage.HideSearchBlock";
var hideSearchBlock = await genericAttributeService.GetAttributeAsync<bool>(await workContext.GetCurrentCustomerAsync(), hideSearchBlockAttributeName);
}
@if (Model.LicenseCheckModel.BlockPages != true)
{
<form asp-controller="Order" asp-action="List" method="post">
<div class="content-header clearfix">
<h1 class="float-left">
@T("Admin.Orders")
</h1>
<div class="float-right">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#create-order-window">
<i class="fas fa-plus"></i>
@T("Admin.Common.AddNew")
</button>
<div class="btn-group">
<button type="button" class="btn btn-success">
<i class="fas fa-download"></i>
@T("Admin.Common.Export")
</button>
<button type="button" class="btn btn-success dropdown-toggle dropdown-icon" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">&nbsp;</span>
</button>
<ul class="dropdown-menu" role="menu">
<li class="dropdown-item">
<button asp-action="ExportXml" type="submit" name="exportxml-all">
<i class="far fa-file-code"></i>
@T("Admin.Common.ExportToXml.All")
</button>
</li>
<li class="dropdown-item">
<button type="button" id="exportxml-selected">
<i class="far fa-file-code"></i>
@T("Admin.Common.ExportToXml.Selected")
</button>
</li>
<li class="dropdown-divider"></li>
<li class="dropdown-item">
<button asp-action="ExportExcel" type="submit" name="exportexcel-all">
<i class="far fa-file-excel"></i>
@T("Admin.Common.ExportToExcel.All")
</button>
</li>
<li class="dropdown-item">
<button type="button" id="exportexcel-selected">
<i class="far fa-file-excel"></i>
@T("Admin.Common.ExportToExcel.Selected")
</button>
</li>
</ul>
</div>
<button type="button" name="importexcel" class="btn bg-olive" data-toggle="modal" data-target="#importexcel-window">
<i class="fas fa-upload"></i>
@T("Admin.Common.Import")
</button>
<div class="btn-group">
<button type="button" class="btn btn-info">
<i class="far fa-file-pdf"></i>
@T("Admin.Orders.PdfInvoices")
</button>
<button type="button" class="btn btn-info dropdown-toggle dropdown-icon" data-toggle="dropdown" aria-expanded="false">
<span class="sr-only">&nbsp;</span>
</button>
<ul class="dropdown-menu" role="menu">
<li class="dropdown-item">
<button asp-action="PdfInvoice" type="submit" name="pdf-invoice-all">
@T("Admin.Orders.PdfInvoices.All")
</button>
</li>
<li class="dropdown-item">
<button type="button" id="pdf-invoice-selected">
@T("Admin.Orders.PdfInvoices.Selected")
</button>
</li>
</ul>
</div>
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.OrderListButtons, additionalData = Model })
</div>
</div>
<section class="content">
<div class="container-fluid">
<div class="form-horizontal">
<div class="cards-group">
<div class="card card-default card-search">
<div class="card-body">
<div class="row search-row @(!hideSearchBlock ? "opened" : "")" data-hideAttribute="@hideSearchBlockAttributeName">
<div class="search-text">@T("Admin.Common.Search")</div>
<div class="icon-search"><i class="fas fa-magnifying-glass" aria-hidden="true"></i></div>
<div class="icon-collapse"><i class="far fa-angle-@(!hideSearchBlock ? "up" : "down")" aria-hidden="true"></i></div>
</div>
<div class="search-body @(hideSearchBlock ? "closed" : "")">
<div class="row">
<div class="col-md-5">
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="StartDate" />
</div>
<div class="col-md-8">
<nop-editor asp-for="StartDate" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="EndDate" />
</div>
<div class="col-md-8">
<nop-editor asp-for="EndDate" />
</div>
</div>
<div class="form-group row" @(Model.AvailableWarehouses.SelectionIsNotPossible() ? Html.Raw("style=\"display:none\"") : null)>
<div class="col-md-4">
<nop-label asp-for="WarehouseId" />
</div>
<div class="col-md-8">
<nop-select asp-for="WarehouseId" asp-items="Model.AvailableWarehouses" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="ProductId" />
</div>
<div class="col-md-8">
<input type="text" id="search-product-name" autocomplete="off" class="form-control" />
<span id="search-product-friendly-name"></span>
<button type="button" id="search-product-clear" class="btn bg-gray" style="display: none; margin-top: 5px;">@T("Admin.Common.Clear")</button>
<input asp-for="ProductId" autocomplete="off" style="display: none;" />
<script>
$(function() {
$('#search-product-name').autocomplete({
delay: 500,
minLength: 3,
source: '@Url.Action("SearchAutoComplete", "SearchComplete")',
select: function(event, ui) {
$('#@Html.IdFor(model => model.ProductId)').val(ui.item.productid);
$('#search-product-friendly-name').text(ui.item.label);
$('#search-product-clear').show();
return false;
}
});
//remove button
$('#search-product-clear').click(function() {
$('#@Html.IdFor(model => model.ProductId)').val('0');
$('#search-product-friendly-name').text('');
$('#search-product-clear').hide();
return false;
});
});
</script>
</div>
</div>
<div class="form-group row" @(Model.IsLoggedInAsVendor? Html.Raw("style='display: none;'") : null)>
<div class="col-md-4">
<nop-label asp-for="OrderStatusIds" />
</div>
<div class="col-md-8">
<nop-select asp-for="OrderStatusIds" asp-items="Model.AvailableOrderStatuses" asp-multiple="true" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="PaymentStatusIds" />
</div>
<div class="col-md-8">
<nop-select asp-for="PaymentStatusIds" asp-items="Model.AvailablePaymentStatuses" asp-multiple="true" />
</div>
</div>
<div class="form-group row" @(Model.IsLoggedInAsVendor? Html.Raw("style='display: none;'") : null)>
<div class="col-md-4">
<nop-label asp-for="ShippingStatusIds" />
</div>
<div class="col-md-8">
<nop-select asp-for="ShippingStatusIds" asp-items="Model.AvailableShippingStatuses" asp-multiple="true" />
</div>
</div>
</div>
<div class="col-md-7">
<div class="form-group row" @(Model.HideStoresList? Html.Raw("style=\"display:none\"") : null)>
<div class="col-md-4">
<nop-label asp-for="StoreId" />
</div>
<div class="col-md-8">
<nop-select asp-for="StoreId" asp-items="Model.AvailableStores" />
</div>
</div>
<div class="form-group row" @(Model.AvailableVendors.SelectionIsNotPossible() || Model.IsLoggedInAsVendor ? Html.Raw("style='display: none;'") : null)>
<div class="col-md-4">
<nop-label asp-for="VendorId" />
</div>
<div class="col-md-8">
<nop-select asp-for="VendorId" asp-items="Model.AvailableVendors" />
</div>
</div>
@if (Model.BillingPhoneEnabled)
{
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="BillingPhone" />
</div>
<div class="col-md-8">
<nop-editor asp-for="BillingPhone" />
</div>
</div>
}
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="BillingEmail" />
</div>
<div class="col-md-8">
<nop-editor asp-for="BillingEmail" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="BillingLastName" />
</div>
<div class="col-md-8">
<nop-editor asp-for="BillingLastName" />
</div>
</div>
<div class="form-group row" @(Model.AvailableCountries.SelectionIsNotPossible() ? Html.Raw("style=\"display:none\"") : null)>
<div class="col-md-4">
<nop-label asp-for="BillingCountryId" />
</div>
<div class="col-md-8">
<nop-select asp-for="BillingCountryId" asp-items="Model.AvailableCountries" />
</div>
</div>
<div class="form-group row" @(Model.AvailablePaymentMethods.SelectionIsNotPossible() || Model.IsLoggedInAsVendor ? Html.Raw("style='display: none;'") : null)>
<div class="col-md-4">
<nop-label asp-for="PaymentMethodSystemName" />
</div>
<div class="col-md-8">
<nop-select asp-for="PaymentMethodSystemName" asp-items="Model.AvailablePaymentMethods" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="OrderNotes" />
</div>
<div class="col-md-8">
<nop-editor asp-for="OrderNotes" />
</div>
</div>
<div class="form-group row">
<div class="col-md-4">
<nop-label asp-for="GoDirectlyToCustomOrderNumber" />
</div>
<div class="col-md-8">
<div class="input-group input-group-short">
<nop-editor asp-for="GoDirectlyToCustomOrderNumber" />
<span class="input-group-append">
<button type="submit" id="go-to-order-by-number" name="go-to-order-by-number" class="btn btn-info btn-flat">
@T("Admin.Common.Go")
</button>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="text-center col-12">
<button type="button" id="search-orders" class="btn btn-primary btn-search">
<i class="fas fa-magnifying-glass"></i>
@T("Admin.Common.Search")
</button>
</div>
</div>
</div>
</div>
</div>
<div class="card card-default">
<div class="card-body">
<nop-doc-reference asp-string-resource="@T("Admin.Documentation.Reference.Orders", Docs.Orders + Utm.OnAdmin)" />
@{
var gridModel = new DataTablesModel
{
Name = "orders-grid",
UrlRead = new DataUrl("OrderList", "CustomOrder", null),
SearchButtonId = "search-orders",
Length = Model.PageSize,
LengthMenu = Model.AvailablePageSizes,
FooterCallback = !Model.IsLoggedInAsVendor ? "ordersfootercallback" : null,
FooterColumns = !Model.IsLoggedInAsVendor ? 10 : 0,
Filters = new List<FilterParameter>
{
new FilterParameter(nameof(Model.StartDate), typeof(DateTime?)),
new FilterParameter(nameof(Model.EndDate), typeof(DateTime?)),
new FilterParameter(nameof(Model.OrderStatusIds)),
new FilterParameter(nameof(Model.PaymentStatusIds)),
new FilterParameter(nameof(Model.ShippingStatusIds)),
new FilterParameter(nameof(Model.StoreId)),
new FilterParameter(nameof(Model.VendorId)),
new FilterParameter(nameof(Model.WarehouseId)),
new FilterParameter(nameof(Model.BillingEmail)),
new FilterParameter(nameof(Model.BillingPhone)),
new FilterParameter(nameof(Model.BillingLastName)),
new FilterParameter(nameof(Model.BillingCountryId)),
new FilterParameter(nameof(Model.PaymentMethodSystemName)),
new FilterParameter(nameof(Model.ProductId)),
new FilterParameter(nameof(Model.OrderNotes))
}
};
gridModel.ColumnCollection = new List<ColumnProperty>
{
new ColumnProperty(nameof(OrderModel.Id))
{
IsMasterCheckBox = true,
Render = new RenderCheckBox("checkbox_orders"),
ClassName = NopColumnClassDefaults.CenterAll,
Width = "50"
},
new ColumnProperty(nameof(OrderModel.CustomOrderNumber))
{
Title = T("Admin.Orders.Fields.CustomOrderNumber").Text,
Width = "80"
}
};
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.CustomerCompany))
{
Title = T("Admin.Orders.Fields.Customer").Text,
//Render = new RenderCustom("renderColumnCustomer")
});
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.IsMeasured))
{
Title = T("Admin.Orders.Fields.IsMeasured").Text,
Width = "100",
Render = new RenderCustom("renderColumnIsMeasurable"),
ClassName = NopColumnClassDefaults.CenterAll
});
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(IOrderDto.DateOfReceipt))
{
Title = T("Admin.Orders.Fields.PickupDate").Text,
Width = "100",
Render = new RenderCustom("renderColumnPickupDateAndTime"),
ClassName = NopColumnClassDefaults.CenterAll
});
//a vendor does not have access to this functionality
if (!Model.IsLoggedInAsVendor)
{
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.OrderStatus))
{
Title = T("Admin.Orders.Fields.OrderStatus").Text,
Width = "100",
Render = new RenderCustom("renderColumnOrderStatus")
});
}
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.PaymentStatus))
{
Title = T("Admin.Orders.Fields.PaymentStatus").Text,
Width = "150"
});
//a vendor does not have access to this functionality
if (!Model.IsLoggedInAsVendor)
{
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.ShippingStatus))
{
Title = T("Admin.Orders.Fields.ShippingStatus").Text,
Width = "150"
});
}
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.StoreName))
{
Title = T("Admin.Orders.Fields.Store").Text,
Width = "100",
Visible = (await storeService.GetAllStoresAsync()).Count > 1
});
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.CreatedOn))
{
Title = T("Admin.Orders.Fields.CreatedOn").Text,
Width = "120",
Render = new RenderDate()
});
//a vendor does not have access to this functionality
if (!Model.IsLoggedInAsVendor)
{
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.OrderTotal))
{
Title = T("Admin.Orders.Fields.OrderTotal").Text,
Width = "100",
});
}
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModel.Id))
{
Title = T("Admin.Common.View").Text,
Width = "50",
ClassName = NopColumnClassDefaults.Button,
Render = new RenderButtonView(new DataUrl("~/Admin/Order/Edit"))
});
var orderSummaryColumnNumber = 8;
}
@await Html.PartialAsync("Table", gridModel)
<script>
function renderColumnOrderStatus(data, type, row, meta) {
try {
console.log("OrderStatus render - data:", data, "row:", row);
if (!row.OrderStatusId) {
console.error("OrderStatusId is missing from row data");
return data || '';
}
var color;
switch (row.OrderStatusId) {
case 10: color = 'yellow'; break;
case 20: color = 'blue'; break;
case 30: color = 'green'; break;
case 40: color = 'red'; break;
default: color = 'gray';
}
return '<span class="grid-report-item ' + color + '">' + data + '</span>';
} catch (e) {
console.error("Error in renderColumnOrderStatus:", e);
return data || '';
}
}
function renderColumnCustomer(data, type, row, meta) {
console.log("Hello World 2");
var link = '@Url.Content("~/Admin/Customer/Edit/")' + row.CustomerId;
var textRenderer = $.fn.dataTable.render.text().display;
return `${textRenderer(row.Company)} <br /><a href="${link}">${data}</a > `;
}
function renderColumnIsMeasurable(data, type, row, meta) {
if(data === true) {
return '<span class="badge badge-warning" disabled>Yes</span>';
}
return '<span class="badge badge-secondary" disabled>No</span>';
}
function renderColumnPickupDateAndTime(data, type, row, meta) {
if (!data) return '<span>-</span>';
const date = new Date(data);
// Format: "Oct 18, 2025, Friday 14:30"
const options = {
year: 'numeric',
month: 'short',
day: 'numeric',
weekday: 'long',
hour: '2-digit',
minute: '2-digit',
hour12: false // Use true for 12-hour format with AM/PM
};
const formatted = date.toLocaleString('en-US', options);
return `<span>${formatted}</span>`;
}
$(function() {
$("#@Html.IdFor(model => model.GoDirectlyToCustomOrderNumber)").keydown(
function(event) {
if (event.keyCode === 13) {
$("#go-to-order-by-number").trigger("click");
return false;
}
});
});
function ordersfootercallback(tfoot, data, start, end, display) {
//update order totals summary
var postData = {
StartDate: $('#@Html.IdFor(model => model.StartDate)').val(),
EndDate: $('#@Html.IdFor(model => model.EndDate)').val(),
OrderStatusIds: $('#@Html.IdFor(model => model.OrderStatusIds)').val(),
PaymentStatusIds: $('#@Html.IdFor(model => model.PaymentStatusIds)').val(),
ShippingStatusIds: $('#@Html.IdFor(model => model.ShippingStatusIds)').val(),
StoreId: $('#@Html.IdFor(model => model.StoreId)').val(),
VendorId: $('#@Html.IdFor(model => model.VendorId)').val(),
WarehouseId: $('#@Html.IdFor(model => model.WarehouseId)').val(),
BillingEmail: $('#@Html.IdFor(model => model.BillingEmail)').val(),
BillingPhone: $('#@Html.IdFor(model => model.BillingPhone)').val(),
BillingLastName: $('#@Html.IdFor(model => model.BillingLastName)').val(),
BillingCountryId: $('#@Html.IdFor(model => model.BillingCountryId)').val(),
PaymentMethodSystemName: $('#@Html.IdFor(model => model.PaymentMethodSystemName)').val(),
ProductId: $('#@Html.IdFor(model => model.ProductId)').val(),
OrderNotes: $('#@Html.IdFor(model => model.OrderNotes)').val()
};
addAntiForgeryToken(postData);
$.ajax({
cache: false,
type: "POST",
url: "@(Url.Action("ReportAggregates", "Order"))",
data: postData,
success: function (data, textStatus, jqXHR) {
if (data) {
for (var key in data) {
var reportSummary = '<div><strong>@T("Admin.Orders.Report.Summary").Text</strong></div>' +
'<div>@T("Admin.Orders.Report.Profit").Text <span>' + data['AggregatorProfit'] +'</span></div>' +
'<div>@T("Admin.Orders.Report.Shipping").Text <span>' + data['AggregatorShipping'] + '</span></div>' +
'<div>@T("Admin.Orders.Report.Tax").Text <span>' + data['AggregatorTax'] + '</span></div>' +
'<div>@T("Admin.Orders.Report.Total").Text <span>' + data['AggregatorTotal'] + '</span></div>'
var orderTotalsColumn = $('#orders-grid').DataTable().column(@(orderSummaryColumnNumber));
$(orderTotalsColumn.footer()).html(reportSummary);
}
}
}
});
}
</script>
</div>
</div>
</div>
</div>
</div>
</section>
</form>
}
<script>
$(function() {
var displayModal = @((Model.LicenseCheckModel.DisplayWarning == true || Model.LicenseCheckModel?.BlockPages == true).ToString().ToLower());
if (displayModal){
$('#license-window').modal({
backdrop: 'static',
keyboard: false
});
$('#license-window').on('shown.bs.modal', function (event) {
var modalCloseEl = $(this).find('button.close');
var closeTextEl = $('span', modalCloseEl);
var startFrom = 5;
closeTextEl.text(startFrom);
const timer = setInterval(function() {
if (startFrom-- > 0)
closeTextEl.text(startFrom);
}, 1000);
setTimeout(function() {
closeTextEl.html('&times;');
modalCloseEl.on('click', function() {
$('#license-window').modal('hide')
});
clearInterval(timer);
}, startFrom*1000);
});
}
});
</script>
<div id="license-window" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
@Html.Raw(Model.LicenseCheckModel?.WarningText)
</div>
</div>
</div>
@*export selected (XML). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@
<form asp-controller="Order" asp-action="ExportXmlSelected" method="post" id="export-xml-selected-form">
<input type="hidden" id="selectedIds" name="selectedIds" value="" />
</form>
<script>
$(function() {
$('#exportxml-selected').click(function (e) {
e.preventDefault();
var ids = selectedIds.join(",");
if (!ids) {
$('#exportXmlSelected-info').text("@T("Admin.Orders.NoOrders")");
$("#exportXmlSelected").trigger("click");
}
else {
$('#export-xml-selected-form #selectedIds').val(ids);
$('#export-xml-selected-form').submit();
updateTable('#orders-grid');
}
return false;
});
});
</script>
<nop-alert asp-alert-id="exportXmlSelected" />
@*export selected (Excel). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@
<form asp-controller="Order" asp-action="ExportExcelSelected" method="post" id="export-excel-selected-form">
<input type="hidden" id="selectedIds" name="selectedIds" value="" />
</form>
<script>
$(function() {
$('#exportexcel-selected').click(function (e) {
e.preventDefault();
var ids = selectedIds.join(",");
if (!ids) {
$('#exportExcelSelected-info').text("@T("Admin.Orders.NoOrders")");
$("#exportExcelSelected").trigger("click");
}
else {
$('#export-excel-selected-form #selectedIds').val(ids);
$('#export-excel-selected-form').submit();
updateTable('#orders-grid');
}
return false;
});
});
</script>
<nop-alert asp-alert-id="exportExcelSelected" />
@*Print packaging slips selected (XML). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@
<form asp-controller="Order" asp-action="PdfInvoiceSelected" method="post" id="pdf-invoice-selected-form">
<input type="hidden" id="selectedIds" name="selectedIds" value="" />
</form>
<script>
$(function() {
$('#pdf-invoice-selected').click(function (e) {
e.preventDefault();
var ids = selectedIds.join(",");
if (!ids) {
$('#pdfInvoiceSelected-info').text("@T("Admin.Orders.NoOrders")");
$("#pdfInvoiceSelected").trigger("click");
}
else {
$('#pdf-invoice-selected-form #selectedIds').val(ids);
$('#pdf-invoice-selected-form').submit();
updateTable('#orders-grid');
}
return false;
});
});
</script>
<nop-alert asp-alert-id="pdfInvoiceSelected" />
@*import orders form*@
<div id="importexcel-window" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="importexcel-window-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="importexcel-window-title">@T("Admin.Common.ImportFromExcel")</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<form asp-controller="Order" asp-action="ImportFromXlsx" method="post" enctype="multipart/form-data">
<div class="form-horizontal">
<div class="modal-body">
<ul class="common-list">
<li>
<em>@T("Admin.Orders.List.ImportFromExcelTip")</em>
</li>
<li>
<em>@T("Admin.Common.ImportFromExcel.ManyRecordsWarning")</em>
</li>
</ul>
<div class="form-group row">
<div class="col-md-2">
<div class="label-wrapper">
<label class="col-form-label">
@T("Admin.Common.ExcelFile")
</label>
</div>
</div>
<div class="col-md-10">
<input type="file" id="importexcelfile" name="importexcelfile" class="form-control" />
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">
@T("Admin.Common.ImportFromExcel")
</button>
</div>
</div>
</form>
</div>
</div>
</div>
@*create new order form*@
<div id="create-order-window" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="create-order-window-title">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="create-order-window-title">@T("Admin.Orders.AddNew")</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<form asp-controller="CustomOrder" asp-action="Create" method="post" id="create-order-form">
<div class="form-horizontal">
<div class="modal-body">
<!-- Customer Selection -->
<div class="form-group row">
<div class="col-md-3">
<div class="label-wrapper">
<label class="col-form-label">
@T("Admin.Orders.Fields.Customer")
</label>
</div>
</div>
<div class="col-md-9">
<input type="text" id="create-order-customer-search" autocomplete="off" class="form-control" placeholder="Type customer name or email..." />
<span id="create-order-customer-name" class="mt-2 d-inline-block"></span>
<input type="hidden" id="create-order-customer-id" name="customerId" value="" />
<span class="field-validation-error" id="create-order-customer-error" style="display:none;">Please select a customer</span>
</div>
</div>
<!-- Product Selection -->
<div class="form-group row" id="product-search-section" style="display:none;">
<div class="col-md-3">
<div class="label-wrapper">
<label class="col-form-label">
@T("Admin.Orders.Fields.Product")
</label>
</div>
</div>
<div class="col-md-9">
<input type="text" id="create-order-product-search" autocomplete="off" class="form-control" placeholder="Type product name or SKU..." />
</div>
</div>
<!-- Selected Products List -->
<div id="selected-products-section" style="display:none;">
<div class="form-group row">
<div class="col-md-12">
<label class="col-form-label"><strong>Selected Products:</strong></label>
<div class="table-responsive">
<table class="table table-sm table-bordered" id="selected-products-table">
<thead>
<tr>
<th>Product</th>
<th style="width: 100px;">Quantity</th>
<th style="width: 120px;">Price</th>
<th style="width: 50px;"></th>
</tr>
</thead>
<tbody id="selected-products-body">
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Hidden input for products JSON -->
<input type="hidden" id="order-products-json" name="orderProductsJson" value="" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
@T("Admin.Common.Cancel")
</button>
<button type="submit" class="btn btn-primary" id="create-order-submit">
@T("Admin.Common.Create")
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<style>
/* Fix z-index for autocomplete dropdown in modal */
.ui-autocomplete {
z-index: 1060 !important;
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
}
#selected-products-table input[type="number"] {
width: 80px;
}
#selected-products-table input[type="text"] {
width: 100px;
}
</style>
<script>
$(function() {
var selectedProducts = [];
// Customer autocomplete
$('#create-order-customer-search').autocomplete({
delay: 500,
minLength: 2,
source: '@Url.Action("CustomerSearchAutoComplete", "CustomOrder")',
select: function(event, ui) {
$('#create-order-customer-id').val(ui.item.value);
$('#create-order-customer-name').html('<strong>' + ui.item.label + '</strong>');
$('#create-order-customer-search').val('');
$('#create-order-customer-error').hide();
// Show product search section after customer is selected
$('#product-search-section').slideDown();
$('#create-order-product-search').focus();
return false;
}
});
// Product autocomplete
$('#create-order-product-search').autocomplete({
delay: 500,
minLength: 2,
source: '@Url.Action("ProductSearchAutoComplete", "CustomOrder")',
select: function(event, ui) {
addProduct(ui.item);
$('#create-order-product-search').val('');
return false;
}
});
// Add product to selected list
function addProduct(product) {
var existingProduct = selectedProducts.find(p => p.id === product.value);
if (existingProduct) {
alert('This product is already added to the order.');
return;
}
var productItem = {
id: product.value,
name: product.label,
sku: product.sku || '',
quantity: 1,
stockQuantity : product.stockQuantity,
price: product.price || 0
};
selectedProducts.push(productItem);
renderProductsList();
updateProductsJson();
}
// Render products list
function renderProductsList() {
var tbody = $('#selected-products-body');
tbody.empty();
if (selectedProducts.length === 0) {
$('#selected-products-section').hide();
return;
}
$('#selected-products-section').show();
selectedProducts.forEach(function(product, index) {
var row = $('<tr>');
var nameCell = $('<td>').html(
'<strong>' + product.name + '</strong>' +
(product.sku ? '<br><small>SKU: ' + product.sku + '</small>' : '')
);
var quantityCell = $('<td>').html(
'<input type="number" class="form-control form-control-sm" min="1" max="' + product.stockQuantity + '" value="' + product.quantity + '" data-index="' + index + '" />'
);
var priceCell = $('<td>').html(
'<input type="text" class="form-control form-control-sm" value="' + product.price + '" data-index="' + index + '" />'
);
var removeCell = $('<td class="text-center">').html(
'<button type="button" class="btn btn-sm btn-danger" data-index="' + index + '"><i class="fas fa-trash"></i></button>'
);
row.append(nameCell).append(quantityCell).append(priceCell).append(removeCell);
tbody.append(row);
});
}
// Update quantity
$(document).on('change', '#selected-products-body input[type="number"]', function() {
var index = $(this).data('index');
var newQuantity = parseInt($(this).val());
if (newQuantity > 0) {
selectedProducts[index].quantity = newQuantity;
updateProductsJson();
} else {
$(this).val(selectedProducts[index].quantity);
}
});
// Update price
$(document).on('change', '#selected-products-body input[type="text"]', function() {
var index = $(this).data('index');
var newPrice = parseFloat($(this).val());
if (!isNaN(newPrice) && newPrice >= 0) {
selectedProducts[index].price = newPrice;
updateProductsJson();
} else {
$(this).val(selectedProducts[index].price);
}
});
// Remove product
$(document).on('click', '#selected-products-body button[data-index]', function() {
var index = $(this).data('index');
selectedProducts.splice(index, 1);
renderProductsList();
updateProductsJson();
});
// Update hidden JSON field
function updateProductsJson() {
$('#order-products-json').val(JSON.stringify(selectedProducts));
}
// Validate form submission
$('#create-order-form').on('submit', function(e) {
var customerId = $('#create-order-customer-id').val();
if (!customerId || customerId === '0' || customerId === '') {
e.preventDefault();
$('#create-order-customer-error').show();
return false;
}
return true;
});
// Clear error when typing
$('#create-order-customer-search').on('input', function() {
$('#create-order-customer-error').hide();
});
// Reset form when modal is closed
$('#create-order-window').on('hidden.bs.modal', function () {
$('#create-order-customer-search').val('');
$('#create-order-customer-id').val('');
$('#create-order-customer-name').html('');
$('#create-order-customer-error').hide();
$('#create-order-product-search').val('');
$('#product-search-section').hide();
selectedProducts = [];
renderProductsList();
updateProductsJson();
});
});
</script>