using AyCode.Core.Extensions; using AyCode.Core.Loggers; using AyCode.Services.Server.SignalRs; using AyCode.Services.SignalRs; using FruitBank.Common.Dtos; using FruitBank.Common.Entities; using FruitBank.Common.Interfaces; using FruitBank.Common.Server.Interfaces; using FruitBank.Common.Server.Services.SignalRs; using FruitBank.Common.SignalRs; using Mango.Nop.Core.Extensions; using Mango.Nop.Core.Loggers; using MessagePack.Resolvers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using Newtonsoft.Json; using Nop.Core; 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.Core.Events; using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Order; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; using Nop.Plugin.Misc.FruitBankPlugin.Factories; using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders; using Nop.Plugin.Misc.FruitBankPlugin.Services; using Nop.Services.Catalog; using Nop.Services.Common; using Nop.Services.Customers; using Nop.Services.ExportImport; using Nop.Services.Helpers; using Nop.Services.Localization; using Nop.Services.Logging; using Nop.Services.Messages; using Nop.Services.Orders; using Nop.Services.Payments; using Nop.Services.Plugins; using Nop.Services.Security; using Nop.Services.Tax; 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.Controllers; using Nop.Web.Framework.Mvc.Filters; using System.Text; using System.Text.Json.Serialization; using System.Xml; using System.Xml.Serialization; namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers { [Area(AreaNames.ADMIN)] [AuthorizeAdmin] public class CustomOrderController : BaseAdminController, ICustomOrderSignalREndpointServer { private readonly FruitBankDbContext _dbContext; private readonly SignalRSendToClientService _sendToClient; private readonly IOrderService _orderService; private readonly CustomOrderModelFactory _orderModelFactory; private readonly ICustomOrderSignalREndpointServer _customOrderSignalREndpoint; private readonly IPermissionService _permissionService; private readonly IGenericAttributeService _genericAttributeService; private readonly INotificationService _notificationService; private readonly ICustomerService _customerService; private readonly IProductService _productService; private readonly IStoreContext _storeContext; private readonly IWorkContext _workContext; private readonly IPriceCalculationService _priceCalculationService; protected readonly IEventPublisher _eventPublisher; protected readonly ILocalizationService _localizationService; protected readonly ICustomerActivityService _customerActivityService; protected readonly IExportManager _exportManager; protected readonly IGiftCardService _giftCardService; protected readonly IImportManager _importManager; protected readonly IDateTimeHelper _dateTimeHelper; protected readonly ITaxService _taxService; protected readonly MeasurementService _measurementService; private static readonly char[] _separator = [',']; // ... other dependencies private readonly Mango.Nop.Core.Loggers.ILogger _logger; protected virtual async ValueTask HasAccessToOrderAsync(Order order) { return order != null && await HasAccessToOrderAsync(order.Id); } protected virtual async Task HasAccessToOrderAsync(int orderId) { if (orderId == 0) return false; var currentVendor = await _workContext.GetCurrentVendorAsync(); if (currentVendor == null) //not a vendor; has access return true; var vendorId = currentVendor.Id; var hasVendorProducts = (await _orderService.GetOrderItemsAsync(orderId, vendorId: vendorId)).Any(); return hasVendorProducts; } public CustomOrderController(FruitBankDbContext fruitBankDbContext, SignalRSendToClientService sendToClient, IOrderService orderService, IPriceCalculationService priceCalculationService, IOrderModelFactory orderModelFactory, ICustomOrderSignalREndpointServer customOrderSignalREndpoint, IPermissionService permissionService, IGenericAttributeService genericAttributeService, INotificationService notificationService, ICustomerService customerService, IProductService productService, IEnumerable logWriters, IStoreContext storeContext, IWorkContext workContext, IEventPublisher eventPublisher, ILocalizationService localizationService, ICustomerActivityService customerActivityService, IExportManager exportManager, IGiftCardService giftCardService, IImportManager importManager, IDateTimeHelper dateTimeHelper, ITaxService taxService, MeasurementService measurementService) { _logger = new Logger(logWriters.ToArray()); _dbContext = fruitBankDbContext; _sendToClient = sendToClient; _orderService = orderService; _orderModelFactory = orderModelFactory as CustomOrderModelFactory; _customOrderSignalREndpoint = customOrderSignalREndpoint; _permissionService = permissionService; _genericAttributeService = genericAttributeService; _notificationService = notificationService; _customerService = customerService; _productService = productService; _storeContext = storeContext; _workContext = workContext; _priceCalculationService = priceCalculationService; _eventPublisher = eventPublisher; _localizationService = localizationService; _customerActivityService = customerActivityService; _exportManager = exportManager; _giftCardService = giftCardService; _importManager = importManager; _dateTimeHelper = dateTimeHelper; _taxService = taxService; _measurementService = measurementService; // ... initialize other deps } #region CustomOrderSignalREndpoint [NonAction] public Task> GetAllOrderDtos() => _customOrderSignalREndpoint.GetAllOrderDtos(); [NonAction] public Task GetOrderDtoById(int orderId) => _customOrderSignalREndpoint.GetOrderDtoById(orderId); [NonAction] public Task> GetPendingOrderDtos() => _customOrderSignalREndpoint.GetPendingOrderDtos(); [NonAction] public Task> GetPendingOrderDtosForMeasuring() => _customOrderSignalREndpoint.GetPendingOrderDtosForMeasuring(); [NonAction] public Task StartMeasuring(int orderId, int userId) => _customOrderSignalREndpoint.StartMeasuring(orderId, userId); [NonAction] public Task SetOrderStatusToComplete(int orderId, int revisorId) => _customOrderSignalREndpoint.SetOrderStatusToComplete(orderId, revisorId); [NonAction] public Task> GetAllOrderDtoByIds(int[] orderIds) => _customOrderSignalREndpoint.GetAllOrderDtoByIds(orderIds); [NonAction] public Task> GetAllOrderItemDtos() => _customOrderSignalREndpoint.GetAllOrderItemDtos(); [NonAction] public Task> GetAllOrderDtoByProductId(int productId) => _customOrderSignalREndpoint.GetAllOrderDtoByProductId(productId); [NonAction] public Task GetOrderItemDtoById(int orderItemId) => _customOrderSignalREndpoint.GetOrderItemDtoById(orderItemId); [NonAction] public Task> GetAllOrderItemDtoByOrderId(int orderId) => _customOrderSignalREndpoint.GetAllOrderItemDtoByOrderId(orderId); [NonAction] public Task> GetAllOrderItemDtoByProductId(int productId) => _customOrderSignalREndpoint.GetAllOrderItemDtoByProductId(productId); [NonAction] public Task> GetAllOrderItemPallets() => _customOrderSignalREndpoint.GetAllOrderItemPallets(); [NonAction] public Task GetOrderItemPalletById(int orderItemPalletId) => _customOrderSignalREndpoint.GetOrderItemPalletById(orderItemPalletId); [NonAction] public Task> GetAllOrderItemPalletByOrderItemId(int orderItemId) => _customOrderSignalREndpoint.GetAllOrderItemPalletByOrderItemId(orderItemId); [NonAction] public Task> GetAllOrderItemPalletByOrderId(int orderId) => _customOrderSignalREndpoint.GetAllOrderItemPalletByOrderId(orderId); [NonAction] public Task> GetAllOrderItemPalletByProductId(int productId) => _customOrderSignalREndpoint.GetAllOrderItemPalletByProductId(productId); [NonAction] public Task AddOrUpdateMeasuredOrderItemPallet(OrderItemPallet orderItemPallet) => _customOrderSignalREndpoint.AddOrUpdateMeasuredOrderItemPallet(orderItemPallet); #endregion CustomOrderSignalREndpoint [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)] public virtual async Task List(List orderStatuses = null, List paymentStatuses = null, List shippingStatuses = null) { //prepare model var model = await _orderModelFactory.PrepareOrderSearchModelAsync(new OrderSearchModelExtended { OrderStatusIds = orderStatuses, PaymentStatusIds = paymentStatuses, ShippingStatusIds = shippingStatuses, AvailablePageSizes = "20,50,100,500", }); return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/List.cshtml", model); } [HttpPost] [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)] public async Task OrderList(OrderSearchModelExtended searchModel) { //prepare model var orderListModel = await GetOrderListModelByFilter(searchModel); //var orderListModel = new OrderListModel(); var valami = Json(orderListModel); Console.WriteLine(valami); return valami; } [HttpPost, ActionName("List")] [FormValueRequired("go-to-order-by-number")] [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)] public virtual async Task GoToOrderId(OrderSearchModel model) { var order = await _orderService.GetOrderByCustomOrderNumberAsync(model.GoDirectlyToCustomOrderNumber); if (order == null) return await List(); return RedirectToAction("Edit", new { id = order.Id }); } [HttpGet] //[Route("Edit/{id}")] [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)] public virtual async Task Edit(int id) { //try to get an order with the specified id var order = await _orderService.GetOrderByIdAsync(id); if (order == null || order.Deleted) return RedirectToAction("List"); //a vendor does not have access to this functionality if (await _workContext.GetCurrentVendorAsync() != null) return RedirectToAction("List"); //prepare model var model = await _orderModelFactory.PrepareOrderModelExtendedAsync(null, order); return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/Edit.cshtml", model); } //public async Task GetOrderListModelByFilter(OrderSearchModelExtended searchModel) //{ // //return _customOrderService. // var orderListModel = await _orderModelFactory.PrepareOrderListModelExtendedAsync(searchModel); // _logger.Detail($"Total: {orderListModel.RecordsTotal}, Data Count: {orderListModel.Data.Count()}"); // foreach (var item in orderListModel.Data.Take(3)) // { // _logger.Detail($"Order: {item.Id}, {item.CustomOrderNumber}"); // } // return orderListModel; //} public async Task GetOrderListModelByFilter(OrderSearchModelExtended searchModel) { var sortColumnIndex = Request.Form["order[0][column]"].FirstOrDefault(); var sortDirection = Request.Form["order[0][dir]"].FirstOrDefault(); if (!string.IsNullOrEmpty(sortColumnIndex)) { // Get the column name from the column index var columnName = Request.Form[$"columns[{sortColumnIndex}][data]"].FirstOrDefault(); searchModel.SortColumn = columnName; searchModel.SortColumnDirection = sortDirection; // "asc" or "desc" } // Get the paginated data var orderListModel = await _orderModelFactory.PrepareOrderListModelExtendedAsync(searchModel); _logger.Detail($"Total: {orderListModel.RecordsTotal}, Data Count: {orderListModel.Data.Count()}"); // Apply sorting if specified if (!string.IsNullOrEmpty(searchModel.SortColumn) && orderListModel.Data.Any()) { var sortedData = orderListModel.Data.AsQueryable(); sortedData = searchModel.SortColumn.ToLowerInvariant() switch { "id" => searchModel.SortColumnDirection == "asc" ? sortedData.OrderBy(o => o.Id) : sortedData.OrderByDescending(o => o.Id), "customercompany" => searchModel.SortColumnDirection == "asc" ? sortedData.OrderBy(o => o.CustomerCompany) : sortedData.OrderByDescending(o => o.CustomerCompany), "customordernumber" => searchModel.SortColumnDirection == "asc" ? sortedData.OrderBy(o => o.CustomOrderNumber) : sortedData.OrderByDescending(o => o.CustomOrderNumber), "ordertotal" => searchModel.SortColumnDirection == "asc" ? sortedData.OrderBy(o => o.OrderTotal) : sortedData.OrderByDescending(o => o.OrderTotal), "createdon" => searchModel.SortColumnDirection == "asc" ? sortedData.OrderBy(o => o.CreatedOn) : sortedData.OrderByDescending(o => o.CreatedOn), "orderstatusid" => searchModel.SortColumnDirection == "asc" ? sortedData.OrderBy(o => o.OrderStatusId) : sortedData.OrderByDescending(o => o.OrderStatusId), "paymentstatusid" => searchModel.SortColumnDirection == "asc" ? sortedData.OrderBy(o => o.PaymentStatusId) : sortedData.OrderByDescending(o => o.PaymentStatusId), "shippingstatusid" => searchModel.SortColumnDirection == "asc" ? sortedData.OrderBy(o => o.ShippingStatusId) : sortedData.OrderByDescending(o => o.ShippingStatusId), _ => sortedData }; orderListModel.Data = sortedData.ToList(); orderListModel.RecordsTotal = orderListModel.Data.Count(); //orderListModel.Draw = searchModel.Draw; Console.WriteLine($"Sorted Data Count: {orderListModel.Data.Count()}"); Console.WriteLine($"Total Records: {orderListModel.RecordsTotal}"); Console.WriteLine($"Filtered Records: {orderListModel.RecordsFiltered}"); Console.WriteLine($"Draw: {orderListModel.Draw}"); _logger.Detail($"Sorted by {searchModel.SortColumn} {searchModel.SortColumnDirection}"); } foreach (var item in orderListModel.Data.Take(3)) { _logger.Detail($"Order: {item.Id}, {item.CustomOrderNumber}"); } return orderListModel; } public virtual IActionResult Test() { // Your custom logic here // This will use your custom List.cshtml view return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/Test.cshtml"); } //[HttpPost] //[CheckPermission(Nop.Services.Security.StandardPermission.Orders.ORDERS_VIEW)] //public virtual async Task OrderList(OrderSearchModel searchModel) //{ // //prepare model // var model = await _orderModelFactory.PrepareOrderListModelAsync(searchModel); // return Json(model); //} [HttpPost] [ValidateAntiForgeryToken] public async Task SaveOrderAttributes(OrderAttributesModel model) { if (!ModelState.IsValid) { // reload order page with errors return RedirectToAction("Edit", "Order", new { id = model.OrderId }); } var order = await _orderService.GetOrderByIdAsync(model.OrderId); if (order == null) return RedirectToAction("List", "Order"); //TODO: A FruitBankAttributeService-t használjuk és akkor az OrderDto lehet lekérni és beadni a SaveAttribute-ba! - J. // store attributes in GenericAttribute table //await _genericAttributeService.SaveAttributeAsync(order, nameof(IMeasurable.IsMeasurable), model.IsMeasurable, _storeContext.GetCurrentStore().Id); await _genericAttributeService.SaveAttributeAsync(order, nameof(IOrderDto.DateOfReceipt), model.DateOfReceipt, _storeContext.GetCurrentStore().Id); var orderDto = await _dbContext.OrderDtos.GetByIdAsync(model.OrderId, true); await _sendToClient.SendOrderChanged(orderDto); _notificationService.SuccessNotification("Custom attributes saved successfully."); return RedirectToAction("Edit", "Order", new { id = model.OrderId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task AllowRevision(OrderRevisionModel model) { if (!ModelState.IsValid) { // reload order page with errors return RedirectToAction("Edit", "Order", new { id = model.OrderId }); } var order = await _orderService.GetOrderByIdAsync(model.OrderId); if (order == null) return RedirectToAction("List", "Order"); //MeasurementService.OrderItemMeasuringReset //Todo: ezt orderitiemnként kéne kirakni?? - Á. var valami = await _measurementService.OrderItemMeasuringReset(model.OrderItemId); return RedirectToAction("Edit", "Order", new { id = model.OrderId }); } [HttpPost] //[CheckPermission(StandardPermission.Orders.ORDERS_CREATE)] public virtual async Task 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"); var billingAddress = await _customerService.GetCustomerBillingAddressAsync(customer); if (billingAddress == null) { //let's see if he has any address at all var addresses = await _customerService.GetAddressesByCustomerIdAsync(customer.Id); if (addresses != null && addresses.Count > 0) { //set the first one as billing billingAddress = addresses[0]; customer.BillingAddressId = billingAddress.Id; await _customerService.UpdateCustomerAsync(customer); } else { //no address at all, cannot create order _logger.Error($"Cannot create order for customer {customer.Id}, no billing address found."); return RedirectToAction("List"); } } //var currency = await _workContext.GetWorkingCurrencyAsync(); //customer.CurrencyId = currency.Id; // Parse products var orderProducts = string.IsNullOrEmpty(orderProductsJson) ? [] : JsonConvert.DeserializeObject>(orderProductsJson); // Create order var order = new Order { OrderGuid = Guid.NewGuid(), CustomOrderNumber = "", CustomerId = customerId, CustomerLanguageId = customer.LanguageId ?? 1, CustomerTaxDisplayType = TaxDisplayType.IncludingTax, CustomerIp = string.Empty, OrderStatus = OrderStatus.Pending, PaymentStatus = PaymentStatus.Pending, ShippingStatus = ShippingStatus.ShippingNotRequired, CreatedOnUtc = DateTime.UtcNow, BillingAddressId = customer.BillingAddressId ?? 0, ShippingAddressId = customer.ShippingAddressId, PaymentMethodSystemName = "Payments.CheckMoneyOrder", // Default payment method CustomerCurrencyCode = "HUF", // TODO: GET Default currency - A. }; //var productDtosById = await _dbContext.ProductDtos.GetAllByIds(orderProducts.Select(op => op.Id)).ToDictionaryAsync(p => p.Id, prodDto => prodDto); var store = _storeContext.GetCurrentStore(); var productDtosByOrderItemId = await _dbContext.ProductDtos.GetAllByIds(orderProducts.Select(x => x.Id).ToArray()).ToDictionaryAsync(k => k.Id, v => v); var transactionSuccess = await _dbContext.TransactionSafeAsync(async _ => { await _orderService.InsertOrderAsync(order); order.OrderTotal = 0; foreach (var item in orderProducts) { var product = await _productService.GetProductByIdAsync(item.Id); if (product == null) { var errorText = $"product == null; productId: {item.Id};"; _logger.Error($"{errorText}"); throw new Exception($"{errorText}"); } var productDto = productDtosByOrderItemId[item.Id]; var isMeasurable = productDto.IsMeasurable; if ((product.StockQuantity + productDto.IncomingQuantity) - item.Quantity < 0) { var errorText = $"((product.StockQuantity + productDto.IncomingQuantity) - item.Quantity < 0); productId: {product.Id}; (product.StockQuantity + productDto.IncomingQuantity) - item.Quantity: {(product.StockQuantity + productDto.IncomingQuantity) - item.Quantity}"; _logger.Error($"{errorText}"); throw new Exception($"{errorText}"); } var orderItem = new OrderItem { OrderId = order.Id, ProductId = item.Id, Quantity = item.Quantity, UnitPriceInclTax = item.Price, UnitPriceExclTax = item.Price, PriceInclTax = isMeasurable ? 0 : item.Price * item.Quantity, PriceExclTax = isMeasurable ? 0 : item.Price * item.Quantity, OriginalProductCost = product.ProductCost, AttributeDescription = string.Empty, AttributesXml = string.Empty, DiscountAmountInclTax = 0, DiscountAmountExclTax = 0 }; order.OrderTotal += orderItem.PriceInclTax; await _orderService.InsertOrderItemAsync(orderItem); //await _productService.AddStockQuantityHistoryEntryAsync(product, -orderItem.Quantity, product.StockQuantity, 1); await _productService.AdjustInventoryAsync(product, -orderItem.Quantity, orderItem.AttributesXml, ""); //await _productService.BookReservedInventoryAsync(product, 1, item.Quantity, ""); } order.CustomOrderNumber = order.Id.ToString(); order.OrderSubtotalInclTax = order.OrderTotal; order.OrderSubtotalExclTax = order.OrderTotal; order.OrderSubTotalDiscountInclTax = order.OrderTotal; order.OrderSubTotalDiscountExclTax = order.OrderTotal; await _orderService.UpdateOrderAsync(order); //var orderDto = await _dbContext.OrderDtos.GetByIdAsync(order.Id, true); //await _sendToClient.SendMeasuringNotification("Módosult a rendelés, mérjétek újra!", orderDto); return true; }); if (transactionSuccess) return RedirectToAction("Edit", "Order", new { id = order.Id }); _logger.Error($"(transactionSuccess == false)"); return RedirectToAction("Error", 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; } public override string ToString() { return $"{nameof(OrderProductItem)} [Id: {Id}; Name: {Name}; Sku: {Sku}; Quantity: {Quantity}; Price: {Price}]"; } } //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 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 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 CustomerSearchAutoComplete(string term) { if (string.IsNullOrWhiteSpace(term) || term.Length < 2) return Json(new List()); 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); var customersByCompanyName = await _customerService.GetAllCustomersAsync( company: term, pageIndex: 0, pageSize: maxResults); // Combine and deduplicate results var allCustomers = customersByEmail .Union(customersByFirstName) .Union(customersByLastName) .Union(customersByCompanyName) .DistinctBy(c => c.Id) .Take(maxResults) .ToList(); var result = new List(); foreach (var customer in allCustomers) { var fullName = await _customerService.GetCustomerFullNameAsync(customer); var company = customer.Company; if (string.IsNullOrEmpty(fullName)) fullName = "[No name]"; if (string.IsNullOrEmpty(company)) company = "[No company]"; string fullText = $"{company} ({fullName}), {customer.Email}"; //var displayText = !string.IsNullOrEmpty(customer.Email) // ? $"{customer.Email}, {customer.Company} ({fullName})" // : fullName; result.Add(new { label = fullText, value = customer.Id }); } return Json(result); } [HttpGet] [CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)] public virtual async Task ProductSearchAutoComplete(string term) { if (string.IsNullOrWhiteSpace(term) || term.Length < 2) return Json(new List()); 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(); var productDtosById = await _dbContext.ProductDtos.GetAllByIds(products.Select(p => p.Id)).ToDictionaryAsync(k => k.Id, v => v); foreach (var product in products) { var productDto = productDtosById[product.Id]; result.Add(new { label = $"{product.Name} [RENDELHETŐ: {(product.StockQuantity + productDto.IncomingQuantity)}] [ÁR: {product.Price}]", value = product.Id, sku = product.Sku, price = product.Price, stockQuantity = product.StockQuantity, incomingQuantity = productDto.IncomingQuantity, }); } return Json(result); } //[HttpPost] //public async Task 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 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}" // }); // } //} //THE REST #region Export / Import [HttpPost, ActionName("ExportXml")] [FormValueRequired("exportxml-all")] [CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)] public virtual async Task ExportXmlAll(OrderSearchModelExtended model) { var startDateValue = model.StartDate == null ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync()); var endDateValue = model.EndDate == null ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync()).AddDays(1); //a vendor should have access only to his products var currentVendor = await _workContext.GetCurrentVendorAsync(); if (currentVendor != null) { model.VendorId = currentVendor.Id; } var orderStatusIds = model.OrderStatusIds != null && !model.OrderStatusIds.Contains(0) ? model.OrderStatusIds.ToList() : null; var paymentStatusIds = model.PaymentStatusIds != null && !model.PaymentStatusIds.Contains(0) ? model.PaymentStatusIds.ToList() : null; var shippingStatusIds = model.ShippingStatusIds != null && !model.ShippingStatusIds.Contains(0) ? model.ShippingStatusIds.ToList() : null; var filterByProductId = 0; var product = await _productService.GetProductByIdAsync(model.ProductId); if (product != null && (currentVendor == null || product.VendorId == currentVendor.Id)) filterByProductId = model.ProductId; //load orders var orders = await _orderService.SearchOrdersAsync(storeId: model.StoreId, vendorId: model.VendorId, productId: filterByProductId, warehouseId: model.WarehouseId, paymentMethodSystemName: model.PaymentMethodSystemName, createdFromUtc: startDateValue, createdToUtc: endDateValue, osIds: orderStatusIds, psIds: paymentStatusIds, ssIds: shippingStatusIds, billingPhone: model.BillingPhone, billingEmail: model.BillingEmail, billingLastName: model.BillingLastName, billingCountryId: model.BillingCountryId, orderNotes: model.OrderNotes); //ensure that we at least one order selected if (!orders.Any()) { _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Orders.NoOrders")); return RedirectToAction("List"); } try { var xml = await _exportManager.ExportOrdersToXmlAsync(orders); return File(Encoding.UTF8.GetBytes(xml), MimeTypes.ApplicationXml, "orders.xml"); } catch (Exception exc) { await _notificationService.ErrorNotificationAsync(exc); return RedirectToAction("List"); } } [HttpPost] [CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)] public virtual async Task ExportXmlSelected(string selectedIds) { var orders = new List(); if (selectedIds != null) { var ids = selectedIds .Split(_separator, StringSplitOptions.RemoveEmptyEntries) .Select(x => Convert.ToInt32(x)) .ToArray(); orders.AddRange(await (await _orderService.GetOrdersByIdsAsync(ids)) .WhereAwait(HasAccessToOrderAsync).ToListAsync()); } try { var xml = await _exportManager.ExportOrdersToXmlAsync(orders); return File(Encoding.UTF8.GetBytes(xml), MimeTypes.ApplicationXml, "orders.xml"); } catch (Exception exc) { await _notificationService.ErrorNotificationAsync(exc); return RedirectToAction("List"); } } [HttpPost, ActionName("ExportExcel")] [FormValueRequired("exportexcel-all")] [CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)] public virtual async Task ExportExcelAll(OrderSearchModelExtended model) { var startDateValue = model.StartDate == null ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync()); var endDateValue = model.EndDate == null ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync()).AddDays(1); //a vendor should have access only to his products var currentVendor = await _workContext.GetCurrentVendorAsync(); if (currentVendor != null) { model.VendorId = currentVendor.Id; } var orderStatusIds = model.OrderStatusIds != null && !model.OrderStatusIds.Contains(0) ? model.OrderStatusIds.ToList() : null; var paymentStatusIds = model.PaymentStatusIds != null && !model.PaymentStatusIds.Contains(0) ? model.PaymentStatusIds.ToList() : null; var shippingStatusIds = model.ShippingStatusIds != null && !model.ShippingStatusIds.Contains(0) ? model.ShippingStatusIds.ToList() : null; var filterByProductId = 0; var product = await _productService.GetProductByIdAsync(model.ProductId); if (product != null && (currentVendor == null || product.VendorId == currentVendor.Id)) filterByProductId = model.ProductId; //load orders var orders = await _orderService.SearchOrdersAsync(storeId: model.StoreId, vendorId: model.VendorId, productId: filterByProductId, warehouseId: model.WarehouseId, paymentMethodSystemName: model.PaymentMethodSystemName, createdFromUtc: startDateValue, createdToUtc: endDateValue, osIds: orderStatusIds, psIds: paymentStatusIds, ssIds: shippingStatusIds, billingPhone: model.BillingPhone, billingEmail: model.BillingEmail, billingLastName: model.BillingLastName, billingCountryId: model.BillingCountryId, orderNotes: model.OrderNotes); //ensure that we at least one order selected if (!orders.Any()) { _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Orders.NoOrders")); return RedirectToAction("List"); } try { var bytes = await _exportManager.ExportOrdersToXlsxAsync(orders); return File(bytes, MimeTypes.TextXlsx, "orders.xlsx"); } catch (Exception exc) { await _notificationService.ErrorNotificationAsync(exc); return RedirectToAction("List"); } } [HttpPost] [CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)] public virtual async Task ExportExcelSelected(string selectedIds) { var orders = new List(); if (selectedIds != null) { var ids = selectedIds .Split(_separator, StringSplitOptions.RemoveEmptyEntries) .Select(x => Convert.ToInt32(x)) .ToArray(); orders.AddRange(await (await _orderService.GetOrdersByIdsAsync(ids)).WhereAwait(HasAccessToOrderAsync).ToListAsync()); } try { var bytes = await _exportManager.ExportOrdersToXlsxAsync(orders); return File(bytes, MimeTypes.TextXlsx, "orders.xlsx"); } catch (Exception exc) { await _notificationService.ErrorNotificationAsync(exc); return RedirectToAction("List"); } } [HttpPost] [CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)] public virtual async Task ImportFromXlsx(IFormFile importexcelfile) { //a vendor cannot import orders if (await _workContext.GetCurrentVendorAsync() != null) return AccessDeniedView(); try { if (importexcelfile != null && importexcelfile.Length > 0) { await _importManager.ImportOrdersFromXlsxAsync(importexcelfile.OpenReadStream()); } else { _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Common.UploadFile")); return RedirectToAction("List"); } _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Orders.Imported")); return RedirectToAction("List"); } catch (Exception exc) { await _notificationService.ErrorNotificationAsync(exc); return RedirectToAction("List"); } } #endregion [HttpPost] [ValidateAntiForgeryToken] public async Task SendOrderNotification(int orderId, string message) { try { if (string.IsNullOrWhiteSpace(message)) { return Json(new { success = false, message = "Az üzenet nem lehet üres" }); } var orderDto = await _dbContext.OrderDtos.GetByIdAsync(orderId, true); await _sendToClient.SendMeasuringNotification(message, orderDto); return Json(new { success = true, message = "Üzenet sikeresen elküldve" }); } catch (Exception ex) { _logger.Error($"Error sending notification for order {orderId}", ex); return Json(new { success = false, message = $"Hiba történt: {ex.Message}" }); } } [HttpPost] //[IgnoreAntiforgeryToken] [ValidateAntiForgeryToken] public async Task FruitBankAddProductToOrder(int orderId, string productsJson) { try { _logger.Info($"AddProductToOrder - OrderId: {orderId}, ProductsJson: {productsJson}"); if (!await _permissionService.AuthorizeAsync(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE)) { return Json(new { success = false, message = "Access denied" }); } if (string.IsNullOrEmpty(productsJson)) { return Json(new { success = false, message = "No products data received" }); } var order = await _orderService.GetOrderByIdAsync(orderId); if (order == null || order.Deleted) { return Json(new { success = false, message = "Order not found" }); } // Deserialize products var products = JsonConvert.DeserializeObject>(productsJson); if (products == null || !products.Any()) { return Json(new { success = false, message = "No products to add" }); } var productDtosByOrderItemId = await _dbContext.ProductDtos.GetAllByIds(products.Select(x => x.Id).ToArray()).ToDictionaryAsync(k => k.Id, v => v); var customer = await _customerService.GetCustomerByIdAsync(order.CustomerId); var store = await _storeContext.GetCurrentStoreAsync(); var admin = await _workContext.GetCurrentCustomerAsync(); string errorMessage = ""; var transactionSuccess = await _dbContext.TransactionSafeAsync(async _ => { // Add each product to the order foreach (var productModel in products) { var product = await _productService.GetProductByIdAsync(productModel.Id); if (product == null) { _logger.Warning($"Product with ID {productModel.Id} not found"); continue; } // Validate stock var stockQuantity = await _productService.GetTotalStockQuantityAsync(product); var productDto = productDtosByOrderItemId[productModel.Id]; var isMeasurable = productDto.IsMeasurable; //if (stockQuantity < productModel.Quantity) //{ // return Json(new // { // success = false, // message = $"Product '{product.Name}' has insufficient stock. Available: {stockQuantity}, Requested: {productModel.Quantity}" // }); //} if ((product.StockQuantity + productDto.IncomingQuantity) - productModel.Quantity < 0) { errorMessage = $"Nem elérhető készleten!"; var errorText = $"((product.StockQuantity + productDto.IncomingQuantity) - item.Quantity < 0); productId: {product.Id}; (product.StockQuantity + productDto.IncomingQuantity) - item.Quantity: {(product.StockQuantity + productDto.IncomingQuantity) - productModel.Quantity}"; _logger.Error($"{errorText}"); throw new Exception($"{errorText}"); } // Get or calculate price var unitPrice = productModel.Price > 0 ? productModel.Price : (await _priceCalculationService.GetFinalPriceAsync(product, customer, store)).finalPrice; // Calculate tax var (unitPriceInclTaxValue, _) = await _taxService.GetProductPriceAsync(product, unitPrice, true, customer); var (unitPriceExclTaxValue, _) = await _taxService.GetProductPriceAsync(product, unitPrice, false, customer); // Create order item var orderItem = new OrderItem { OrderItemGuid = Guid.NewGuid(), OrderId = order.Id, ProductId = product.Id, UnitPriceInclTax = unitPriceInclTaxValue, UnitPriceExclTax = unitPriceExclTaxValue, PriceInclTax = unitPriceInclTaxValue * productModel.Quantity, PriceExclTax = unitPriceExclTaxValue * productModel.Quantity, OriginalProductCost = await _priceCalculationService.GetProductCostAsync(product, null), Quantity = productModel.Quantity, DiscountAmountInclTax = decimal.Zero, DiscountAmountExclTax = decimal.Zero, DownloadCount = 0, IsDownloadActivated = false, LicenseDownloadId = 0, ItemWeight = product.Weight * productModel.Quantity, RentalStartDateUtc = null, RentalEndDateUtc = null }; await _orderService.InsertOrderItemAsync(orderItem); // Adjust inventory await _productService.AdjustInventoryAsync( product, -productModel.Quantity, orderItem.AttributesXml, string.Format(await _localizationService.GetResourceAsync("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id)); } // Update order totals var orderSubtotalInclTax = decimal.Zero; var orderSubtotalExclTax = decimal.Zero; var orderItems = await _orderService.GetOrderItemsAsync(order.Id); foreach (var item in orderItems) { orderSubtotalInclTax += item.PriceInclTax; orderSubtotalExclTax += item.PriceExclTax; } order.OrderSubtotalInclTax = orderSubtotalInclTax; order.OrderSubtotalExclTax = orderSubtotalExclTax; order.OrderTotal = orderSubtotalInclTax + order.OrderShippingInclTax + order.PaymentMethodAdditionalFeeInclTax - order.OrderDiscount; await _orderService.UpdateOrderAsync(order); // Add order note await _orderService.InsertOrderNoteAsync(new OrderNote { OrderId = order.Id, Note = $"Products added to order by {admin.FirstName} {admin.LastName}, (Id: {admin.Id})", DisplayToCustomer = false, CreatedOnUtc = DateTime.UtcNow }); return true; }); if(transactionSuccess) { _logger.Info($"Successfully added {products.Count} products to order {orderId}"); return Json(new { success = true, message = "Products added successfully" }); } else { return Json(new { success = false, message = errorMessage }); } } catch (Exception ex) { _logger.Error($"Error adding products to order {orderId}, {ex.Message}"); return Json(new { success = false, message = $"Error: {ex.Message}" }); } } // Helper model for deserialization public class AddProductModel { 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; } public int StockQuantity { get; set; } public int IncomingQuantity { get; set; } } } }