This commit is contained in:
Loretta 2025-10-19 13:58:57 +02:00
commit cc4f5a3f8c
7 changed files with 1550 additions and 640 deletions

View File

@ -5,19 +5,30 @@ using FruitBank.Common.Interfaces;
using FruitBank.Common.Server.Interfaces;
using FruitBank.Common.SignalRs;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using Nop.Core.Domain.Tax;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Factories;
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
using Nop.Services.Catalog;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.Messages;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Security;
using Nop.Web.Areas.Admin.Controllers;
using Nop.Web.Areas.Admin.Factories;
using Nop.Web.Areas.Admin.Models.Orders;
using Nop.Web.Framework;
using Nop.Web.Framework.Mvc.Filters;
using System.Text.Json.Serialization;
using System.Xml;
using System.Xml.Serialization;
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
{
@ -31,9 +42,11 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
private readonly IPermissionService _permissionService;
private readonly IGenericAttributeService _genericAttributeService;
private readonly INotificationService _notificationService;
private readonly ICustomerService _customerService;
private readonly IProductService _productService;
// ... other dependencies
public CustomOrderController(IOrderService orderService, IOrderModelFactory orderModelFactory, ICustomOrderSignalREndpointServer customOrderSignalREndpoint, IPermissionService permissionService, IGenericAttributeService genericAttributeService, INotificationService notificationService)
public CustomOrderController(IOrderService orderService, IOrderModelFactory orderModelFactory, ICustomOrderSignalREndpointServer customOrderSignalREndpoint, IPermissionService permissionService, IGenericAttributeService genericAttributeService, INotificationService notificationService, ICustomerService customerService, IProductService productService)
{
_orderService = orderService;
_orderModelFactory = orderModelFactory as CustomOrderModelFactory;
@ -41,6 +54,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
_permissionService = permissionService;
_genericAttributeService = genericAttributeService;
_notificationService = notificationService;
_customerService = customerService;
_productService = productService;
// ... initialize other deps
}
@ -134,6 +149,377 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
return RedirectToAction("Edit", "Order", new { id = model.OrderId });
}
[HttpPost]
//[CheckPermission(StandardPermission.Orders.ORDERS_CREATE)]
public virtual async Task<IActionResult> Create(int customerId, string orderProductsJson)
{
if (!await _permissionService.AuthorizeAsync(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE))
return AccessDeniedView();
// Validate customer
var customer = await _customerService.GetCustomerByIdAsync(customerId);
if (customer == null)
return RedirectToAction("List");
// Parse products
var orderProducts = string.IsNullOrEmpty(orderProductsJson)
? new List<OrderProductItem>()
: JsonConvert.DeserializeObject<List<OrderProductItem>>(orderProductsJson);
// Create order
var order = new Order
{
OrderGuid = Guid.NewGuid(),
CustomOrderNumber = "",
CustomerId = customerId,
CustomerLanguageId = customer.LanguageId ?? 1,
CustomerTaxDisplayType = TaxDisplayType.IncludingTax,
CustomerIp = string.Empty,
OrderStatusId = (int)OrderStatus.Pending,
PaymentStatusId = (int)PaymentStatus.Pending,
ShippingStatusId = (int)ShippingStatus.ShippingNotRequired,
CreatedOnUtc = DateTime.UtcNow,
BillingAddressId = customer.BillingAddressId ?? 0,
ShippingAddressId = customer.ShippingAddressId
};
await _orderService.InsertOrderAsync(order);
// Add order items
foreach (var item in orderProducts)
{
var product = await _productService.GetProductByIdAsync(item.Id);
if (product != null)
{
var orderItem = new OrderItem
{
OrderId = order.Id,
ProductId = item.Id,
Quantity = item.Quantity,
UnitPriceInclTax = item.Price,
UnitPriceExclTax = item.Price,
PriceInclTax = item.Price * item.Quantity,
PriceExclTax = item.Price * item.Quantity,
OriginalProductCost = product.ProductCost,
AttributeDescription = string.Empty,
AttributesXml = string.Empty,
DiscountAmountInclTax = 0,
DiscountAmountExclTax = 0
};
await _orderService.InsertOrderItemAsync(orderItem);
}
}
return RedirectToAction("Edit", new { id = order.Id });
}
public class OrderProductItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Sku { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
//private static OrderItem CreateOrderItem(ProductToAuctionMapping productToAuction, Order order, decimal orderTotal)
//{
// return new OrderItem
// {
// ProductId = productToAuction.ProductId,
// OrderId = order.Id,
// OrderItemGuid = Guid.NewGuid(),
// PriceExclTax = orderTotal,
// PriceInclTax = orderTotal,
// UnitPriceExclTax = orderTotal,
// UnitPriceInclTax = orderTotal,
// Quantity = productToAuction.ProductAmount,
// };
//}
//private static Order CreateOrder(ProductToAuctionMapping productToAuction, decimal orderTotal, Customer customer, Address billingAddress, int storeId, Dictionary<string, object> customValues)
//{
// return new Order
// {
// BillingAddressId = billingAddress.Id,
// CreatedOnUtc = DateTime.UtcNow,
// CurrencyRate = 1,
// CustomOrderNumber = productToAuction.AuctionId + "/" + productToAuction.SortIndex,
// CustomValuesXml = SerializeCustomValuesToXml(customValues),
// CustomerCurrencyCode = "HUF",
// CustomerId = productToAuction.WinnerCustomerId,
// CustomerLanguageId = 2,
// CustomerTaxDisplayType = TaxDisplayType.IncludingTax,
// OrderGuid = Guid.NewGuid(),
// OrderStatus = OrderStatus.Pending,
// OrderTotal = orderTotal,
// PaymentStatus = PaymentStatus.Pending,
// PaymentMethodSystemName = "Payments.CheckMoneyOrder",
// ShippingStatus = ShippingStatus.ShippingNotRequired,
// StoreId = storeId,
// VatNumber = customer.VatNumber,
// CustomerIp = customer.LastIpAddress,
// OrderSubtotalExclTax = orderTotal,
// OrderSubtotalInclTax = orderTotal
// };
//}
private static OrderNote CreateOrderNote(Order order, string note)
{
return new OrderNote
{
CreatedOnUtc = order.CreatedOnUtc,
DisplayToCustomer = true,
OrderId = order.Id,
Note = note
};
}
private static string SerializeCustomValuesToXml(Dictionary<string, object> sourceDictionary)
{
ArgumentNullException.ThrowIfNull(sourceDictionary);
if (!sourceDictionary.Any())
return null;
var ds = new DictionarySerializer(sourceDictionary);
var xs = new XmlSerializer(typeof(DictionarySerializer));
using var textWriter = new StringWriter();
using (var xmlWriter = XmlWriter.Create(textWriter))
{
xs.Serialize(xmlWriter, ds);
}
var result = textWriter.ToString();
return result;
}
[HttpGet] // Change from [HttpPost] to [HttpGet]
[CheckPermission(StandardPermission.Customers.CUSTOMERS_VIEW)]
public virtual async Task<IActionResult> CustomerSearchAutoComplete(string term)
{
if (string.IsNullOrWhiteSpace(term) || term.Length < 2)
return Json(new List<object>());
const int maxResults = 15;
// Search by email (contains)
var customersByEmail = await _customerService.GetAllCustomersAsync(
email: term,
pageIndex: 0,
pageSize: maxResults);
// Search by first name (contains)
var customersByFirstName = await _customerService.GetAllCustomersAsync(
firstName: term,
pageIndex: 0,
pageSize: maxResults);
// Search by last name (contains)
var customersByLastName = await _customerService.GetAllCustomersAsync(
lastName: term,
pageIndex: 0,
pageSize: maxResults);
// Combine and deduplicate results
var allCustomers = customersByEmail
.Union(customersByFirstName)
.Union(customersByLastName)
.DistinctBy(c => c.Id)
.Take(maxResults)
.ToList();
var result = new List<object>();
foreach (var customer in allCustomers)
{
var fullName = await _customerService.GetCustomerFullNameAsync(customer);
var displayText = !string.IsNullOrEmpty(customer.Email)
? $"{customer.Email} ({fullName})"
: fullName;
result.Add(new
{
label = displayText,
value = customer.Id
});
}
return Json(result);
}
[HttpGet]
[CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)]
public virtual async Task<IActionResult> ProductSearchAutoComplete(string term)
{
if (string.IsNullOrWhiteSpace(term) || term.Length < 2)
return Json(new List<object>());
const int maxResults = 15;
// Search products by name or SKU
var products = await _productService.SearchProductsAsync(
keywords: term,
pageIndex: 0,
pageSize: maxResults);
var result = new List<object>();
foreach (var product in products)
{
result.Add(new
{
label = product.Name,
value = product.Id,
sku = product.Sku,
price = product.Price
});
}
return Json(result);
}
//[HttpPost]
//public async Task<IActionResult> CreateInvoice(int orderId)
//{
// try
// {
// var order = await _orderService.GetOrderByIdAsync(orderId);
// if (order == null)
// return Json(new { success = false, message = "Order not found" });
// var billingAddress = await _customerService.GetCustomerBillingAddressAsync(order.Customer);
// if (billingAddress == null)
// return Json(new { success = false, message = "Billing address not found" });
// var country = await _countryService.GetCountryByAddressAsync(billingAddress);
// var countryCode = country?.TwoLetterIsoCode ?? "HU";
// // Create invoice request
// var invoiceRequest = new InvoiceCreateRequest
// {
// VevoNev = $"{billingAddress.FirstName} {billingAddress.LastName}",
// VevoIrsz = billingAddress.ZipPostalCode ?? "",
// VevoTelep = billingAddress.City ?? "",
// VevoOrszag = countryCode,
// VevoUtcaHsz = $"{billingAddress.Address1} {billingAddress.Address2}".Trim(),
// SzamlatombID = 1, // Configure this based on your setup
// SzamlaKelte = DateTime.Now,
// TeljesitesKelte = DateTime.Now,
// Hatarido = DateTime.Now.AddDays(15), // 15 days payment term
// Devizanem = order.CustomerCurrencyCode,
// FizetesiMod = order.PaymentMethodSystemName,
// Felretett = false,
// Proforma = false,
// Email = billingAddress.Email,
// Telefon = billingAddress.PhoneNumber
// };
// // Add order items
// var orderItems = await _orderService.GetOrderItemsAsync(order.Id);
// foreach (var item in orderItems)
// {
// var product = await _productService.GetProductByIdAsync(item.ProductId);
// invoiceRequest.AddItem(new InvoiceItem
// {
// TetelNev = product?.Name ?? "Product",
// AfaSzoveg = "27%", // Configure VAT rate as needed
// Brutto = true,
// EgysegAr = item.UnitPriceInclTax,
// Mennyiseg = item.Quantity,
// MennyisegEgyseg = "db",
// CikkSzam = product?.Sku
// });
// }
// // Create invoice via API
// var response = await _innVoiceApiService.CreateInvoiceAsync(invoiceRequest);
// if (response.IsSuccess)
// {
// // TODO: Save invoice details to your database for future reference
// // You might want to create a custom table to store:
// // - OrderId
// // - InnVoice TableId
// // - Invoice Number
// // - PDF URL
// // - Created Date
// return Json(new
// {
// success = true,
// message = "Invoice created successfully",
// data = new
// {
// tableId = response.TableId,
// invoiceNumber = response.Sorszam,
// sorszam = response.Sorszam,
// printUrl = response.PrintUrl
// }
// });
// }
// else
// {
// return Json(new
// {
// success = false,
// message = $"InnVoice API Error: {response.Message}"
// });
// }
// }
// catch (Exception ex)
// {
// return Json(new
// {
// success = false,
// message = $"Error: {ex.Message}"
// });
// }
//}
//[HttpGet]
//public async Task<IActionResult> GetInvoiceStatus(int orderId)
//{
// try
// {
// // TODO: Retrieve invoice details from your database
// // This is a placeholder - you need to implement actual storage/retrieval
// // Example: var invoiceData = await _yourInvoiceService.GetByOrderIdAsync(orderId);
// // if (invoiceData != null)
// // {
// // return Json(new
// // {
// // success = true,
// // data = new
// // {
// // tableId = invoiceData.TableId,
// // invoiceNumber = invoiceData.InvoiceNumber,
// // sorszam = invoiceData.InvoiceNumber,
// // printUrl = invoiceData.PrintUrl
// // }
// // });
// // }
// return Json(new
// {
// success = false,
// message = "No invoice found for this order"
// });
// }
// catch (Exception ex)
// {
// return Json(new
// {
// success = false,
// message = $"Error: {ex.Message}"
// });
// }
//}
}
}

View File

@ -186,6 +186,20 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
return Json(model);
}
[HttpGet]
public async Task<IActionResult> GetAllPartners()
{
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return AccessDeniedView();
// Mock data for now
var model = await _dbContext.Partners.GetAll().ToListAsync();
var valami = model;
//model. = await _dbContext.GetShippingDocumentsByShippingIdAsync(shippingId);
return Json(model);
}
[HttpPost]
[RequestSizeLimit(10485760)] // 10MB
@ -240,7 +254,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
//if (file.Length > 0 && file.ContentType == "application/pdf")
if (file.Length > 0)
{
if (!file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)){
if (file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)){
try
{
// Open the PDF from the IFormFile's stream directly in memory
@ -282,18 +296,18 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
return StatusCode(500, $"Error processing PDF file: {ex.Message}");
}
}
else
else //read from image
{
try
{
// Open the PDF from the IFormFile's stream directly in memory
// Open the Image from the IFormFile's stream directly in memory
using (var stream = file.OpenReadStream())
{
try
{
// ✅ Use the service we implemented earlier
pdfText = await _openAIApiService.AnalyzePdfAsync(stream, file.FileName, "Please extract all readable text from this PDF.");
pdfText = await _openAIApiService.AnalyzePdfAsync(stream, file.FileName, "Please extract all readable text from this image.");
}
catch (Exception aiEx)
{
@ -319,7 +333,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
}
string analysisPrompt = "Extract the document identification number from this document, determine the type of the " +
"document IN ENGLISH from the available list, and return them as JSON: documentNumber, documentType. " +
$"Available filetypes: {nameof(DocumentType.Invoice)}, {nameof(DocumentType.ShippingDocument)} , {nameof(DocumentType.OrderConfirmation)}, {nameof(DocumentType.Unknown)}" +
@ -342,9 +355,11 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
ShippingDocumentToFiles shippingDocumentToFiles = new ShippingDocumentToFiles
{
ShippingDocumentId = shippingDocumentId,
FilesId = dbFile.Id
FilesId = dbFile.Id,
DocumentType = extractedMetaData.DocumentType != null ? (DocumentType)Enum.Parse(typeof(DocumentType), extractedMetaData.DocumentType) : DocumentType.Unknown
};
Console.WriteLine(shippingDocumentToFiles.DocumentType);
await _dbContext.ShippingDocumentToFiles.InsertAsync(shippingDocumentToFiles);
// - IF WE DON'T HAVE PARTNERID ALREADY: read partner information
@ -425,7 +440,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
// Analyze PDF with AI to extract structured data
var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(
pdfText.ToString(),
"Extract the following information from this shipping document and return as JSON: documentDate, recipientName, senderName, invoiceNumber, totalAmount, itemCount, notes. If a field is not found, return null for that field."
"You work for FruitBank. Extract the following information from this shipping document and return as JSON: documentDate, recipientName, senderName, invoiceNumber, totalAmount, itemCount, notes. If a field is not found, return null for that field."
);
// Parse AI response (assuming it returns JSON)

View File

@ -12,680 +12,950 @@
@using static Nop.Services.Common.NopLinksDefaults
@{
//page title
ViewBag.PageTitle = T("Admin.Orders").Text;
//active menu item (system name)
NopHtml.SetActiveMenuItemSystemName("Orders");
//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);
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">
<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>
<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);
<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;
}
});
$('#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>
//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 card-default">
<div class="card-body">
<nop-doc-reference asp-string-resource="@T("Admin.Documentation.Reference.Orders", Docs.Orders + Utm.OnAdmin)" />
<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.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
});
@{
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.IsMeasurable))
{
Title = T("Admin.Orders.Fields.ToBeMeasured").Text,
Width = "100",
Render = new RenderCustom("renderColumnIsMeasurable"),
ClassName = NopColumnClassDefaults.CenterAll
});
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"),
Render = new RenderDate(),
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.CustomerEmail))
{
Title = T("Admin.Orders.Fields.Customer").Text,
Render = new RenderCustom("renderColumnCustomer")
});
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)
//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.CustomerEmail))
{
Title = T("Admin.Orders.Fields.Customer").Text,
Render = new RenderCustom("renderColumnCustomer")
});
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);
<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 || '';
}
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 || '';
}
}
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.CustomerFullName)} <br /><a href="${link}">${data}</a > `;
}
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.CustomerFullName)} <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 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>';
function renderColumnPickupDateAndTime(data, type, row, meta) {
const date = new Date(data);
var formattedDate = "";
if(data != null) formattedDate = data;//.toString();//('YYYY-MM-DD HH:mm');
// 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
};
return `<span>${formattedDate}</span>`;
}
const formatted = date.toLocaleString('en-US', options);
$(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);
return `<span>${formatted}</span>`;
}
$.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>
$(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
});
$(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);
$('#license-window').on('shown.bs.modal', function (event) {
var modalCloseEl = $(this).find('button.close');
var closeTextEl = $('span', modalCloseEl);
var startFrom = 5;
closeTextEl.text(startFrom);
var startFrom = 5;
closeTextEl.text(startFrom);
const timer = setInterval(function() {
if (startFrom-- > 0)
closeTextEl.text(startFrom);
}, 1000);
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);
});
}
});
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 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="" />
<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;
});
});
$(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="" />
<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;
});
});
$(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="" />
<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;
});
});
$(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 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,
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" 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>

View File

@ -24,14 +24,22 @@
})
.Columns(c => {
c.Add().DataField("Id").AllowEditing(false);
c.Add().DataField("Partner.Name").AllowEditing(false);
c.Add().DataField("PartnerId")
.AllowEditing(true)
.Lookup(lookup => lookup
.DataSource(d => d.Mvc().Controller("ManagementPage").LoadAction("GetAllPartners").Key("Id"))
.ValueExpr("Id")
.DisplayExpr("Name")
)
.EditCellTemplate(new TemplateName("DropDownBoxTemplate"))
.Width(150);
c.Add()
.Caption("Items in order")
.DataType(GridColumnDataType.Number)
.CalculateCellValue("calculateItemsCount").AllowEditing(false);
c.Add().DataField("PartnerId");
@* c.Add().DataField("PartnerId"); *@
c.Add().DataField("DocumentIdNumber");
c.Add().DataField("IsAllMeasured");
c.Add().DataField("IsAllMeasured").AllowEditing(false);
c.Add()
.Caption("Completed")
.DataType(GridColumnDataType.Boolean)
@ -54,8 +62,10 @@
);
});
})
.MasterDetail(md => md.Enabled(true).Template(new TemplateName("masterDetailTemplate")))
)
.MasterDetail(md => md.Enabled(true).Template(new TemplateName("masterDetailTemplate"))
)
)
</div>
@using (Html.DevExtreme().NamedTemplate("masterDetailTemplate"))
@ -108,6 +118,40 @@
}
}
@using(Html.DevExtreme().NamedTemplate("DropDownBoxTemplate")) {
@(Html.DevExtreme().DropDownBox()
.DataSource(d => d.Mvc().Controller("ManagementPage").LoadAction("GetAllPartners").Key("Id"))
.Value(new JS("value"))
.ValueExpr("Id")
.InputAttr("aria-label", "Partner")
.DisplayExpr("Name")
.DropDownOptions(options => options.Width(500))
.Option("setValue", new JS("setValue"))
.ContentTemplate(new TemplateName("ContentTemplate"))
)
}
@using(Html.DevExtreme().NamedTemplate("ContentTemplate")) {
@(Html.DevExtreme().DataGrid()
.DataSource(d => d.Mvc().Controller("ManagementPage").LoadAction("GetAllPartners").Key("Id"))
.RemoteOperations(true)
.Height(250)
.Columns(c => {
c.Add().DataField("Name");
c.Add().DataField("Country");
c.Add().DataField("TaxId");
})
.Scrolling(s => s.Mode(GridScrollingMode.Virtual))
.HoverStateEnabled(true)
.Selection(s => s.Mode(SelectionMode.Single))
.SelectedRowKeys(new JS("component.option('value') !== undefined && component.option('value') !== null ? [component.option('value')] : []"))
.FocusedRowEnabled(true)
.FocusedRowKey(new JS("component.option('value')"))
.OnContextMenuPreparing("function(e) { e.items = [] }")
.OnSelectionChanged("function(e) { onPartnerSelectionChanged(e, component) }")
)
}
<script>
// Store the parent grid model as JSON
var parentGridModel = @Html.Raw(Json.Serialize(Model));
@ -180,10 +224,6 @@
});
}
function onSelectionChanged(data) {
let dataGrid = $("#orderDataGridContainer").dxDataGrid("instance");
dataGrid.option("toolbar.items[1].options.disabled", !data.selectedRowsData.length);
}
function onRowExpanded(e) {
// Trigger loading of first tab when row expands
@ -196,4 +236,20 @@
}
}
}
function onInitNewRow(e) {
// Replace this with actual default values and db insert
}
function onPartnerSelectionChanged(e, dropDownBoxComponent) {
var selectedRowKey = e.selectedRowKeys[0];
if (e.selectedRowKeys.length > 0) {
dropDownBoxComponent.option('value', selectedRowKey);
dropDownBoxComponent.close();
}
}
</script>

View File

@ -131,6 +131,11 @@ public class RouteProvider : IRouteProvider
name: "Plugin.FruitBank.Admin.Orders.SaveOrderAttributes",
pattern: "Admin/CustomOrder/SaveOrderAttributes",
defaults: new { controller = "CustomOrder", action = "SaveOrderAttributes", area = AreaNames.ADMIN });
endpointRouteBuilder.MapControllerRoute(
name: "Plugin.FruitBank.Admin.Orders.CustomerSearchAutoComplete",
pattern: "Admin/CustomOrder/CustomerSearchAutoComplete",
defaults: new { controller = "CustomOrder", action = "CustomerSearchAutoComplete", area = AreaNames.ADMIN });
}
/// <summary>

View File

@ -1,8 +1,10 @@
using System.Linq;
using FruitBank.Common.Interfaces;
using FruitBank.Common.Interfaces;
using Microsoft.AspNetCore.Http;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Orders;
using Nop.Core.Events;
using Nop.Plugin.Misc.FruitBankPlugin.Models;
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
using Nop.Services.Catalog;
using Nop.Services.Common;
@ -13,10 +15,11 @@ using Nop.Services.Plugins;
using Nop.Web.Framework.Events;
using Nop.Web.Framework.Menu;
using Nop.Web.Models.Sitemap;
using System.Linq;
namespace Nop.Plugin.Misc.FruitBankPlugin.Services
{
public class EventConsumer : BaseAdminMenuCreatedEventConsumer, IConsumer<OrderPlacedEvent>, IConsumer<AdminMenuCreatedEvent>
public class EventConsumer : BaseAdminMenuCreatedEventConsumer, IConsumer<OrderPlacedEvent>, IConsumer<EntityUpdatedEvent<Order>>, IConsumer<AdminMenuCreatedEvent>
{
private readonly IGenericAttributeService _genericAttributeService;
private readonly IProductService _productService;
@ -27,6 +30,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
private readonly IStoreContext _storeContext;
private readonly IAdminMenu _adminMenu;
private readonly ILocalizationService _localizationService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly FruitBankAttributeService _fruitBankAttributeService;
public EventConsumer(
IGenericAttributeService genericAttributeService,
@ -38,7 +43,9 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
IWorkContext workContext,
IStoreContext storeContext,
IAdminMenu adminMenu,
ILocalizationService localizationService) : base(pluginManager)
ILocalizationService localizationService,
IHttpContextAccessor httpContextAccessor,
FruitBankAttributeService fruitBankAttributeService) : base(pluginManager)
{
_genericAttributeService = genericAttributeService;
_productService = productService;
@ -49,6 +56,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
_storeContext = storeContext;
_adminMenu = adminMenu;
_localizationService = localizationService;
_httpContextAccessor = httpContextAccessor;
_fruitBankAttributeService = fruitBankAttributeService;
}
protected override string PluginSystemName => "Misc.FruitBankPlugin";
@ -120,6 +129,39 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
}
}
public async Task HandleEventAsync(EntityUpdatedEvent<Order> eventMessage)
{
await SaveOrderCustomAttributesAsync(eventMessage.Entity);
}
private async Task SaveOrderCustomAttributesAsync(Order order)
{
if (order == null) return;
var form = _httpContextAccessor.HttpContext?.Request?.Form;
if (form == null || form.Count == 0) return;
if (form.ContainsKey(nameof(IMeasurable.IsMeasurable)))
{
var isMeasurable = form[nameof(IMeasurable.IsMeasurable)].ToString().Contains("true");
//var isMeasurable = CommonHelper.To<bool>(form[nameof(IMeasurable.IsMeasurable)].ToString());
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Order, bool>(order.Id, nameof(IMeasurable.IsMeasurable), isMeasurable);
}
if (form.ContainsKey(nameof(IOrderDto.DateOfReceipt)))
{
var dateOfReceipt = form[nameof(IOrderDto.DateOfReceipt)];
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Order, DateTime>(order.Id, nameof(IOrderDto.DateOfReceipt), DateTime.Parse(dateOfReceipt));
}
}
public async Task HandleEventAsync(AdminMenuCreatedEvent eventMessage)
{
var rootNode = eventMessage.RootMenuItem;

View File

@ -1,6 +1,43 @@
@using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders
@model OrderAttributesModel
<!-- InnVoice Invoice Section -->
<div class="card card-default mb-3">
<div class="card-header">
<i class="fas fa-file-invoice"></i>
InnVoice Invoice Management
</div>
<div class="card-body">
<div class="form-group row">
<div class="col-md-12">
<div id="invoiceStatus" class="alert alert-info" style="display: none;">
<i class="fas fa-info-circle"></i> <span id="invoiceStatusMessage"></span>
</div>
<div id="invoiceDetails" style="display: none;">
<p><strong>Invoice Number:</strong> <span id="invoiceNumber"></span></p>
<p><strong>Table ID:</strong> <span id="invoiceTableId"></span></p>
<p>
<a id="invoicePdfLink" href="#" target="_blank" class="btn btn-sm btn-info">
<i class="fas fa-file-pdf"></i> View PDF
</a>
</p>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-md-12 text-right">
<button type="button" id="createInvoiceBtn" class="btn btn-success">
<i class="fas fa-file-invoice-dollar"></i> Create & Upload Invoice
</button>
<button type="button" id="checkInvoiceBtn" class="btn btn-secondary" style="display: none;">
<i class="fas fa-sync"></i> Check Invoice Status
</button>
</div>
</div>
</div>
</div>
<!-- Custom Order Attributes Section -->
<div class="card card-default">
<div class="card-header">
<i class="fas fa-tags"></i>
@ -16,7 +53,6 @@
<span asp-validation-for="IsMeasurable"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-label asp-for="DateOfReceipt" />
@ -26,7 +62,6 @@
<span asp-validation-for="DateOfReceipt"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-12 text-right">
<button type="button" id="saveAttributesBtn" class="btn btn-primary">
@ -39,6 +74,7 @@
<script>
$(document).ready(function () {
// Save Order Attributes
$("#saveAttributesBtn").click(function () {
$.ajax({
type: "POST",
@ -46,7 +82,7 @@
data: {
orderId: "@Model.OrderId",
isMeasurable: $("#@Html.IdFor(m => m.IsMeasurable)").is(":checked"),
pickupDateTimeUtc: $("#@Html.IdFor(m => m.DateOfReceipt)").val(),
dateOfReceipt: $("#@Html.IdFor(m => m.DateOfReceipt)").val(),
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val()
},
success: function () {
@ -57,6 +93,106 @@
}
});
});
});
</script>
// Create Invoice
$("#createInvoiceBtn").click(function () {
var btn = $(this);
btn.prop("disabled", true).html('<i class="fas fa-spinner fa-spin"></i> Creating Invoice...');
showInvoiceStatus("Creating invoice, please wait...", "info");
$.ajax({
type: "POST",
url: "@Url.Action("CreateInvoice", "InnVoice")",
data: {
orderId: "@Model.OrderId",
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val()
},
success: function (response) {
btn.prop("disabled", false).html('<i class="fas fa-file-invoice-dollar"></i> Create & Upload Invoice');
if (response.success) {
showInvoiceStatus("Invoice created successfully!", "success");
displayInvoiceDetails(response.data);
$("#checkInvoiceBtn").show();
} else {
showInvoiceStatus("Error: " + response.message, "danger");
}
},
error: function (xhr) {
btn.prop("disabled", false).html('<i class="fas fa-file-invoice-dollar"></i> Create & Upload Invoice');
var errorMessage = "Error creating invoice";
if (xhr.responseJSON && xhr.responseJSON.message) {
errorMessage = xhr.responseJSON.message;
}
showInvoiceStatus(errorMessage, "danger");
}
});
});
// Check Invoice Status
$("#checkInvoiceBtn").click(function () {
var btn = $(this);
btn.prop("disabled", true).html('<i class="fas fa-spinner fa-spin"></i> Checking...');
$.ajax({
type: "GET",
url: "@Url.Action("GetInvoiceStatus", "InnVoice")",
data: {
orderId: "@Model.OrderId"
},
success: function (response) {
btn.prop("disabled", false).html('<i class="fas fa-sync"></i> Check Invoice Status');
if (response.success && response.data) {
displayInvoiceDetails(response.data);
showInvoiceStatus("Invoice details loaded", "success");
} else {
showInvoiceStatus("No invoice found for this order", "warning");
}
},
error: function () {
btn.prop("disabled", false).html('<i class="fas fa-sync"></i> Check Invoice Status');
showInvoiceStatus("Error checking invoice status", "danger");
}
});
});
function showInvoiceStatus(message, type) {
var statusDiv = $("#invoiceStatus");
statusDiv.removeClass("alert-info alert-success alert-warning alert-danger")
.addClass("alert-" + type);
$("#invoiceStatusMessage").text(message);
statusDiv.show();
}
function displayInvoiceDetails(data) {
$("#invoiceNumber").text(data.invoiceNumber || data.sorszam || "N/A");
$("#invoiceTableId").text(data.tableId || "N/A");
if (data.printUrl) {
$("#invoicePdfLink").attr("href", data.printUrl).show();
} else {
$("#invoicePdfLink").hide();
}
$("#invoiceDetails").show();
}
// Check if invoice exists on page load
$.ajax({
type: "GET",
url: "@Url.Action("GetInvoiceStatus", "InnVoice")",
data: {
orderId: "@Model.OrderId"
},
success: function (response) {
if (response.success && response.data) {
displayInvoiceDetails(response.data);
$("#checkInvoiceBtn").show();
}
}
});
});
</script>