From d87823bb41314f64893812e8e5ef7dcdc71e7041 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 15 Oct 2025 15:26:52 +0200 Subject: [PATCH 1/3] Create Order --- .../Controllers/CustomOrderController.cs | 95 ++++++++++++++++++- .../Controllers/ManagementPageController.cs | 23 ++++- .../Areas/Admin/Views/Order/List.cshtml | 70 ++++++++++++++ .../ShippingDocumentGridComponent.cshtml | 74 +++++++++++++-- .../Infrastructure/RouteProvider.cs | 5 + .../Services/EventConsumer.cs | 49 +++++++++- .../Views/OrderAttributes.cshtml | 2 +- 7 files changed, 298 insertions(+), 20 deletions(-) diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs index 1291b4c..b696788 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs @@ -5,10 +5,14 @@ using FruitBank.Common.Server.Interfaces; using FruitBank.Common.SignalRs; using Microsoft.AspNetCore.Mvc; 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; using Nop.Services.Common; +using Nop.Services.Customers; using Nop.Services.Messages; using Nop.Services.Orders; using Nop.Services.Security; @@ -30,9 +34,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers private readonly IPermissionService _permissionService; private readonly IGenericAttributeService _genericAttributeService; private readonly INotificationService _notificationService; + private readonly ICustomerService _customerService; // ... 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) { _orderService = orderService; _orderModelFactory = orderModelFactory as CustomOrderModelFactory; @@ -40,6 +45,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers _permissionService = permissionService; _genericAttributeService = genericAttributeService; _notificationService = notificationService; + _customerService = customerService; // ... initialize other deps } @@ -131,6 +137,93 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers return RedirectToAction("Edit", "Order", new { id = model.OrderId }); } + [HttpPost] + public virtual async Task Create(int customerId) + { + if (!await _permissionService.AuthorizeAsync(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE)) + return AccessDeniedView(); + + // Validate customer exists + var customer = await _customerService.GetCustomerByIdAsync(customerId); + if (customer == null) + return RedirectToAction("List"); + + // Create new empty order + var order = new Order + { + OrderGuid = Guid.NewGuid(), + CustomerId = customerId, + CustomerLanguageId = customer.LanguageId ?? 1, + CustomerTaxDisplayType = (TaxDisplayType)customer.TaxDisplayType, + 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); + + // Redirect to edit page + return RedirectToAction("Edit", new { id = order.Id }); + } + + + [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); + + // Combine and deduplicate results + var allCustomers = customersByEmail + .Union(customersByFirstName) + .Union(customersByLastName) + .DistinctBy(c => c.Id) + .Take(maxResults) + .ToList(); + + var result = new List(); + 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); + } + } } diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs index dbfaf58..1acca9b 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs @@ -186,6 +186,20 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers return Json(model); } + [HttpGet] + public async Task 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 @@ -286,14 +300,14 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers { 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,10 +355,10 @@ 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 }; - await _dbContext.ShippingDocumentToFiles.InsertAsync(shippingDocumentToFiles); // - IF WE DON'T HAVE PARTNERID ALREADY: read partner information // (check if all 3 refers to the same partner) diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml index 0540362..cdf1a37 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml @@ -30,6 +30,10 @@ @T("Admin.Orders")
+
+
@@ -677,3 +682,68 @@
+@*create new order form*@ + + + + + \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/ShippingDocumentGridComponent.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/ShippingDocumentGridComponent.cshtml index f3a41a6..6b76abb 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/ShippingDocumentGridComponent.cshtml +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/ShippingDocumentGridComponent.cshtml @@ -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")) + ) +) + @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) }") + ) +} + \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs b/Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs index c37686b..ebd5829 100644 --- a/Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs +++ b/Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs @@ -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 }); } /// diff --git a/Nop.Plugin.Misc.AIPlugin/Services/EventConsumer.cs b/Nop.Plugin.Misc.AIPlugin/Services/EventConsumer.cs index e2bf14a..0b11407 100644 --- a/Nop.Plugin.Misc.AIPlugin/Services/EventConsumer.cs +++ b/Nop.Plugin.Misc.AIPlugin/Services/EventConsumer.cs @@ -1,8 +1,9 @@ -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.Services.Catalog; using Nop.Services.Common; @@ -13,10 +14,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, IConsumer + public class EventConsumer : BaseAdminMenuCreatedEventConsumer, IConsumer, IConsumer>, IConsumer { private readonly IGenericAttributeService _genericAttributeService; private readonly IProductService _productService; @@ -27,6 +29,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 +42,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 +55,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 +128,39 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services } } + public async Task HandleEventAsync(EntityUpdatedEvent 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(form[nameof(IMeasurable.IsMeasurable)].ToString()); + + + await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync(order.Id, nameof(IMeasurable.IsMeasurable), isMeasurable); + } + + if (form.ContainsKey(nameof(IOrderDto.DateOfReceipt))) + { + var dateOfReceipt = form[nameof(IOrderDto.DateOfReceipt)]; + + await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync(order.Id, nameof(IOrderDto.DateOfReceipt), DateTime.Parse(dateOfReceipt)); + } + + } + + + public async Task HandleEventAsync(AdminMenuCreatedEvent eventMessage) { var rootNode = eventMessage.RootMenuItem; diff --git a/Nop.Plugin.Misc.AIPlugin/Views/OrderAttributes.cshtml b/Nop.Plugin.Misc.AIPlugin/Views/OrderAttributes.cshtml index 96e8e4e..b3dcd1d 100644 --- a/Nop.Plugin.Misc.AIPlugin/Views/OrderAttributes.cshtml +++ b/Nop.Plugin.Misc.AIPlugin/Views/OrderAttributes.cshtml @@ -45,7 +45,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 () { From 192904558dee99027bc28bbd9015c5380196a3b3 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 18 Oct 2025 18:35:45 +0200 Subject: [PATCH 2/3] merge --- .../Areas/Admin/Views/Order/List.cshtml | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml index cdf1a37..5784df9 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml @@ -3,6 +3,7 @@ @inject IStoreService storeService @using FruitBank.Common.Interfaces @using Nop.Plugin.Misc.FruitBankPlugin.Models +@using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders @using Nop.Services.Stores @using Nop.Web.Areas.Admin.Components @using Nop.Web.Areas.Admin.Models.Orders @@ -344,13 +345,13 @@ 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, @@ -460,8 +461,24 @@ } function renderColumnPickupDateAndTime(data, type, row, meta) { + if (!data) return '-'; - return `${data}`; + const date = new Date(data); + + // Format: "Oct 18, 2025, Friday 14:30" + const options = { + year: 'numeric', + month: 'short', + day: 'numeric', + weekday: 'long', + hour: '2-digit', + minute: '2-digit', + hour12: false // Use true for 12-hour format with AM/PM + }; + + const formatted = date.toLocaleString('en-US', options); + + return `${formatted}`; } $(function() { From 0bee8979b7659bfd665722112cc209df1d4797fb Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 19 Oct 2025 13:57:55 +0200 Subject: [PATCH 3/3] Documenntype? OrderId? --- .../Controllers/CustomOrderController.cs | 305 +++++++++++++++++- .../Controllers/ManagementPageController.cs | 8 +- .../Areas/Admin/Views/Order/List.cshtml | 219 ++++++++++++- .../Views/OrderAttributes.cshtml | 144 ++++++++- 4 files changed, 651 insertions(+), 25 deletions(-) diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs index df4306e..7881a9e 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs @@ -5,6 +5,8 @@ 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; @@ -12,16 +14,21 @@ 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 { @@ -36,9 +43,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers 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, ICustomerService customerService) + public CustomOrderController(IOrderService orderService, IOrderModelFactory orderModelFactory, ICustomOrderSignalREndpointServer customOrderSignalREndpoint, IPermissionService permissionService, IGenericAttributeService genericAttributeService, INotificationService notificationService, ICustomerService customerService, IProductService productService) { _orderService = orderService; _orderModelFactory = orderModelFactory as CustomOrderModelFactory; @@ -47,6 +55,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers _genericAttributeService = genericAttributeService; _notificationService = notificationService; _customerService = customerService; + _productService = productService; // ... initialize other deps } @@ -141,23 +150,30 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers } [HttpPost] - public virtual async Task Create(int customerId) + //[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 exists + // Validate customer var customer = await _customerService.GetCustomerByIdAsync(customerId); if (customer == null) return RedirectToAction("List"); - // Create new empty order + // Parse products + var orderProducts = string.IsNullOrEmpty(orderProductsJson) + ? new List() + : JsonConvert.DeserializeObject>(orderProductsJson); + + // Create order var order = new Order { OrderGuid = Guid.NewGuid(), + CustomOrderNumber = "", CustomerId = customerId, CustomerLanguageId = customer.LanguageId ?? 1, - CustomerTaxDisplayType = (TaxDisplayType)customer.TaxDisplayType, + CustomerTaxDisplayType = TaxDisplayType.IncludingTax, CustomerIp = string.Empty, OrderStatusId = (int)OrderStatus.Pending, PaymentStatusId = (int)PaymentStatus.Pending, @@ -169,10 +185,117 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers await _orderService.InsertOrderAsync(order); - // Redirect to edit page + // 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 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)] @@ -227,6 +350,176 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers 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(); + 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 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}" + // }); + // } + //} + } } diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs index 1acca9b..be958ef 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs @@ -254,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 @@ -296,7 +296,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers return StatusCode(500, $"Error processing PDF file: {ex.Message}"); } } - else + else //read from image { try { @@ -359,6 +359,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers 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 // (check if all 3 refers to the same partner) @@ -438,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) diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml index 5784df9..b8d4ee6 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/List.cshtml @@ -701,15 +701,16 @@ @*create new order form*@