From f048b999cf3be9ec8fd6db9ccbb31fafe276511c Mon Sep 17 00:00:00 2001 From: Loretta Date: Sat, 1 Nov 2025 19:43:21 +0100 Subject: [PATCH 1/3] =?UTF-8?q?AI=20szop=C3=A1s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ManagementPageController.cs | 196 +++++++++++++++--- .../Nop.Plugin.Misc.FruitBankPlugin.csproj | 2 + .../Services/OpenAIApiService.cs | 32 ++- 3 files changed, 195 insertions(+), 35 deletions(-) diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs index 9092246..8b63095 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs @@ -15,9 +15,11 @@ using Nop.Web.Framework; using Nop.Web.Framework.Mvc.Filters; using System.Text; using System.Text.Json; +//using AutoGen.Core; using AyCode.Core.Consts; using UglyToad.PdfPig.DocumentLayoutAnalysis.TextExtractor; using AyCode.Core.Extensions; +//using AutoGen.Gemini; namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers { @@ -309,8 +311,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers try { // ✅ Use the service we implemented earlier - //pdfText = await _openAIApiService.AnalyzePdfAsync(stream, currentFile.FileName, "Please extract all readable text from this PDF."); - pdfText = await _openAIApiService.AnalyzePdfAsync(stream, currentFile.FileName, DefaultFullPrompt); + pdfText = await _openAIApiService.AnalyzePdfAsync(stream, currentFile.FileName, "Please extract all readable text from this PDF."); + //pdfText = await _openAIApiService.AnalyzePdfAsync(stream, currentFile.FileName, DefaultFullPrompt); } catch (Exception aiEx) { @@ -365,35 +367,87 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers shippingDocumentAnalysisResult = await ProcessRawText(shippingDocumentId, partnerId, pdfText, shippingDocumentAnalysisResult, filesList, dbFile); await SaveDocumentToFileSystem(currentFile, shippingDocumentId, $"{Guid.NewGuid()}_{currentFile.FileName}");//shippingDocumentAnalysisResult.Partner.Name); - //Ez rész a catch végéig nekem teszt! - J - //try - //{ - // responseRawPdfTexts += $"{AcEnv.NL}{AcEnv.NL}{currentFile.Name}:{AcEnv.NL}{pdfText}"; + return Ok($"Files for Shipping Document ID {shippingDocumentId} were uploaded successfully!{AcEnv.NL}{AcEnv.NL}{responseRawPdfTexts}"); - // var transactionSuccess = await SaveFileInfoToDb(shippingDocumentId, shippingDocumentAnalysisResult, filesList, dbFile, pdfText); - // if (!transactionSuccess) - // { - // _logger.Error($"(transactionSuccess == false)"); - // return BadRequest($"Error saving file! RawText:{AcEnv.NL}{pdfText}"); - // } + //Ez rész a catch végéig nekem teszt! - J. + try + { + //responseRawPdfTexts += $"{AcEnv.NL}{AcEnv.NL}{currentFile.Name}:{AcEnv.NL}{pdfText}"; - // if (await SaveDocumentToFileSystem(currentFile, shippingDocumentId, $"{Guid.NewGuid()}_{currentFile.FileName}")) - // { - // //var partnerResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( - // // pdfText, - // // $"{DefaultFullPrompt}" - // //); + var transactionSuccess = await SaveFileInfoToDb(shippingDocumentId, shippingDocumentAnalysisResult, filesList, dbFile, pdfText); + if (!transactionSuccess) + { + _logger.Error($"(transactionSuccess == false)"); + return BadRequest($"Error saving file! RawText:{AcEnv.NL}{pdfText}"); + } - // //responseRawPdfTexts += $"{AcEnv.NL}{AcEnv.NL}{AcEnv.NL}Response json:{AcEnv.NL}{partnerResponse}"; - // } - // //return Ok(shippingDocumentAnalysisResult); - //} - //catch (Exception ex) - //{ - // _logger.Error($"Error saving file: {ex.Message}", ex); - // //return Json(new { success = false, errorMessage = ex.Message }); - // return BadRequest("No files were uploaded."); - //} + if (await SaveDocumentToFileSystem(currentFile, shippingDocumentId, $"{Guid.NewGuid()}_{currentFile.FileName}")) + { + await using var stream = currentFile.OpenReadStream(); + + try + { + await _openAIApiService.CleanupAllVectorStoresAsync(); + await _openAIApiService.CleanupAllAssistantsAsync(); + pdfText = await _openAIApiService.AnalyzePdfAsync(stream, currentFile.FileName, "nyerd ki az összes adatot könnyen értelmezhető .txt formátumba!"); + + var projectID = "864202751803"; //"AIzaSyBAk5dyHnFrt8Upvqb2ICZmxws0Cf1de9M";//Environment.GetEnvironmentVariable("AIzaSyBAk5dyHnFrt8Upvqb2ICZmxws0Cf1de9M"); + var apiKey = "AIzaSyBAk5dyHnFrt8Upvqb2ICZmxws0Cf1de9M"; + + if (projectID is not null) + { + //var geminiAgent = new GeminiChatAgent( + // name: "gemini", + // model: "gemini-2.5-flash", + // //location: "us-east4", + // //project: projectID, + // apiKey: apiKey, + // systemMessage: "Fruitbank egy zöldséges nagyker, ahol a bejövő szállítmányokat kezelik.") + // .RegisterMessageConnector() + // .RegisterPrintMessage(); + + ////var imagePath = Path.Combine("resource", "images", "background.png"); + ////var image = await File.ReadAllBytesAsync(imagePath); + //var imageMessage = new ImageMessage(Role.User, BinaryData.FromStream(stream, "application/pdf")); + + //var reply = await geminiAgent.SendAsync(DefaultFullPrompt, [imageMessage]); + //pdfText = reply.From; + + //pdfText = "Feladó (Expediteur): Victor i Merce, S.L., Mercabarna, Pab.F-6035-6036, 08040 Barcelona, Spanyolország, CIF: ESB61478095.\r\n- Szállítmányozó (Transporteur): FRUIT BANK KFT, Rippl-Rónai utca 18, 1068 Budapest, Magyarország, CIF: HU14902170.\r\n- Címzett (Destinataire): FRUIT BANK KFT, Rippl-Rónai utca 18, 1068 Budapest, Magyarország, CIF: HU14902170.\r\n- Szállítás helye és ideje (Lugar y fecha de carga): 08040 Barcelona, Barcelona (Spanyolország), 2025. október 30.\r\n- Áru részletei (Mercancia): 140 TTE.MADURO G, összesen 840,00 kg bruttó tömeg.\r\n- Szállítás kifizetése a célállomáson (Transporte a payer par): TRANSPORTE A PAGAR EN DESTINO.\r\n\r\n\r\n\r\n"; + + responseRawPdfTexts += $"{AcEnv.NL}{AcEnv.NL}{currentFile.Name}:{AcEnv.NL}{pdfText}"; + + //pdfText = await _openAIApiService.GetSimpleResponseAsync("You are an assistant of FRUITBANK that extracts data from text. The user provides a text, and asks you questions about it. You extract the information without futher explanation.", + pdfText = await _openAIApiService.GetSimpleResponseAsync(DefaultFullPrompt, pdfText); + + responseRawPdfTexts += $"{AcEnv.NL}{AcEnv.NL}{currentFile.Name}:{AcEnv.NL}{pdfText}"; + } + else + { + Console.WriteLine("Please set GCP_VERTEX_PROJECT_ID environment variable."); + } + } + catch (Exception aiEx) + { + Console.Error.WriteLine($"OpenAI Assistants API failed: {aiEx.Message}"); + return StatusCode(500, $"Failed to process PDF: {aiEx.Message}"); + } + + //var partnerResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( + // pdfText, + // $"{DefaultFullPrompt}" + //); + + //responseRawPdfTexts += $"{AcEnv.NL}{AcEnv.NL}{AcEnv.NL}Response json:{AcEnv.NL}{partnerResponse}"; + } + //return Ok(shippingDocumentAnalysisResult); + } + catch (Exception ex) + { + _logger.Error($"Error saving file: {ex.Message}", ex); + //return Json(new { success = false, errorMessage = ex.Message }); + return BadRequest("No files were uploaded."); + } } //iteration 2: iterate documents again @@ -945,9 +999,9 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers } private const string DefaultFullPrompt = $"Role:\r\nYou are an AI data extraction assistant for Fruitbank, a " + - $"fruit and vegetable wholesale company. Your task is to analyze a list of extracted pdf " + - $"documents (delivery notes, invoices, or order confirmations) and extract structured information about " + - $"the shipment and its items.\r\n\r\n🎯 Goal:\r\nRead the provided extracted pdf text and extract all shipment " + + $"fruit and vegetable wholesale company. Your task is to analyze a " + + $"provided text (delivery notes, invoices, or order confirmations) and extract structured information about " + + $"the shipment and its items.\r\n\r\n🎯 Goal:\r\nRead the provided text and extract all shipment " + $"details and items according to the data model below.\r\n Generate the complete JSON output following this " + $"structure.\r\n\r\n🧩 Data Models:\r\n\r\npublic " + $"class Partner\r\n{{\r\n " + @@ -1005,7 +1059,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers $"(e.g. sequential: 1, 2, 3…).\r\n\r\n- When a field is missing or unclear, return it as an empty " + $"string or 0 (depending on type).\r\nDo not omit any fields.\r\n\r\n- " + $"All dates must be in ISO 8601 format (yyyy-MM-dd).\r\n\r\n🧭 Instructions to the AI\r\n" + - $"1. Analyze the provided PDF files one by one carefully.\r\n" + + $"1. Analyze the provided text one by one carefully.\r\n" + $"2. Identify the Partner/Company details, " + $"document identifiers, and each shipment item.\r\n" + $"3. FruitBank is not a partner! Always look for THE OTHER partner on the document. \r\n " + @@ -1016,7 +1070,83 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers $"és a zöldség vagy gyümölcs nevét fordítsd le magyarra!\r\n" + $"7. A ShippingDocument-et tedd bele a Partner entitásba!\r\n" + $"8. ShippingItem-eket tedd bele a ShippingDocument-be!\r\n" + - $"9. Do not assume or modify any data, if you don't find a value, return null, if you find a value, keep it unmodified."; + $"9. Do not assume or modify any data, if you don't find a value, return null, if you find a value, keep it unmodified.\r\n" + + $"10. Magyarázat nélkül válaszolj!"; + //+ $"\r\n\r\n" + $"***POVIDED TEXT***:"; + + //private const string DefaultFullPrompt = $"Role:\r\nYou are an AI data extraction assistant for Fruitbank, a " + + // $"fruit and vegetable wholesale company. Your task is to analyze a list of extracted pdf " + + // $"documents (delivery notes, invoices, or order confirmations) and extract structured information about " + + // $"the shipment and its items.\r\n\r\n🎯 Goal:\r\nRead the provided extracted pdf text and extract all shipment " + + // $"details and items according to the data model below.\r\n Generate the complete JSON output following this " + + // $"structure.\r\n\r\n🧩 Data Models:\r\n\r\npublic " + + // $"class Partner\r\n{{\r\n " + + // $"/// \r\n /// Partner entity primary key\r\n /// \r\n " + + // $"public int Id {{ get; set; }}\r\n " + + // $"/// \r\n /// Partner company name\r\n /// \r\n " + + // $"public string Name {{ get; set; }}\r\n " + + // $"/// \r\n /// Partner company TaxId\r\n /// \r\n " + + // $"public string TaxId {{ get; set; }}\r\n /// \r\n " + + // $"/// Partner company Certification if exists\r\n /// \r\n " + + // $"public string CertificationNumber {{ get; set; }}\r\n /// \r\n " + + // $"/// Partner company address PostalCode\r\n /// \r\n " + + // $"public string PostalCode {{ get; set; }}\r\n /// \r\n " + + // $"/// Partner company address Country\r\n /// \r\n " + + // $"public string Country {{ get; set; }}\r\n /// \r\n " + + // $"/// Partner company address State if exists\r\n /// \r\n " + + // $"public string State {{ get; set; }}\r\n /// \r\n " + + // $"/// Partner company address County if exists\r\n /// \r\n " + + // $"public string County {{ get; set; }}\r\n /// \r\n " + + // $"/// Partner company address City\r\n /// \r\n " + + // $"public string City {{ get; set; }}\r\n /// \r\n " + + // $"/// Partner company address Street\r\n /// \r\n " + + // $"public string Street {{ get; set; }}\r\n\t/// \r\n " + + // $"/// Entities of ShippingDocument\r\n /// \r\n\tpublic List " + + // $"ShippingDocuments {{ get; set; }}\t\r\n}}\r\n\r\npublic class ShippingDocument\r\n{{\r\n " + + // $"/// \r\n /// ShippingItem entity primary key\r\n /// \r\n " + + // $"public int Id {{ get; set; }}\r\n /// \r\n /// Partner entity primary key\r\n " + + // $"/// \r\n public int PartnerId {{ get; set; }}\t\r\n\t/// \r\n " + + // $"/// Entities of ShippingItem\r\n /// \r\n\t" + + // $"public List ShippingItems {{ get; set; }}\r\n /// \r\n " + + // $"/// DocumentIdNumber if exists\r\n /// \r\n public string DocumentIdNumber {{ get; set; }}\r\n " + + // $"/// \r\n /// \r\n /// \r\n public DateTime ShippingDate {{ get; set; }}\r\n " + + // $"/// \r\n /// Shipping pickup Contry of origin\r\n /// \r\n " + + // $"public string Country {{ get; set; }}\r\n\t/// \r\n /// Sum of ShippingItem pallets\r\n " + + // $"/// \r\n public int TotalPallets {{ get; set; }}\r\n\t/// \r\n " + + // $"/// Filename of pdf\r\n /// \r\n\tpublic string PdfFileName {{ get; set; }}\r\n}}\r\n\r\n" + + // $"public class ShippingItem\r\n{{\r\n /// \r\n /// ShippingItem entity primary key\r\n /// " + + // $"\r\n public int Id {{ get; set; }}\r\n /// \r\n /// " + + // $"ShippingDocument entity primary key\r\n /// \r\n " + + // $"public int ShippingDocumentId {{ get; set; }}\r\n /// " + + // $"\r\n /// Name of the fruit or vegitable\r\n /// \r\n " + + // $"public string Name {{ get; set; }}\r\n\t/// \r\n /// Translated Name to Hungarian\r\n " + + // $"/// \r\n public string HungarianName {{ get; set; }}\r\n /// \r\n " + + // $"/// Pallets of fruit or vegitable item\r\n /// \r\n " + + // $"public int PalletsOnDocument {{ get; set; }}\r\n /// \r\n " + + // $"/// Quantity of fruit or vegitable item\r\n /// \r\n " + + // $"public int QuantityOnDocument {{ get; set; }}\r\n /// \r\n " + + // $"/// Net weight in kg. of fruit or vegitable item\r\n /// \r\n " + + // $"public double NetWeightOnDocument {{ get; set; }}\r\n /// \r\n " + + // $"/// Gross weight in kg. of fruit or vegitable item\r\n /// \r\n " + + // $"public double GrossWeightOnDocument {{ get; set; }}\r\n}}\r\n\r\n🧾 Output Requirements\r\n- " + + // $"Output must be a single valid JSON object containing:\r\n- One Partner object\r\n- " + + // $"One ShippingDocument object\r\n- A list of all related ShippingItem objects\r\n\r\n- " + + // $"Primary keys (Partner.Id, ShippingDocument.Id, ShippingItem.Id) should be auto-generated integers " + + // $"(e.g. sequential: 1, 2, 3…).\r\n\r\n- When a field is missing or unclear, return it as an empty " + + // $"string or 0 (depending on type).\r\nDo not omit any fields.\r\n\r\n- " + + // $"All dates must be in ISO 8601 format (yyyy-MM-dd).\r\n\r\n🧭 Instructions to the AI\r\n" + + // $"1. Analyze the provided PDF files one by one carefully.\r\n" + + // $"2. Identify the Partner/Company details, " + + // $"document identifiers, and each shipment item.\r\n" + + // $"3. FruitBank is not a partner! Always look for THE OTHER partner on the document. \r\n " + + // $"4. Generate a complete hierarchical JSON of ALL received documents in ONE JSON structure according to the " + + // $"data model above.\r\n5. Do not include any explanations or text outside the JSON output. " + + // $"Only return the structured JSON.\r\n" + + // $"6. A teljes ShippingItem.Name-et tedd bele a ShippingItem.HungarianName-be " + + // $"és a zöldség vagy gyümölcs nevét fordítsd le magyarra!\r\n" + + // $"7. A ShippingDocument-et tedd bele a Partner entitásba!\r\n" + + // $"8. ShippingItem-eket tedd bele a ShippingDocument-be!\r\n" + + // $"9. Do not assume or modify any data, if you don't find a value, return null, if you find a value, keep it unmodified."; } public class UploadModel diff --git a/Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.FruitBankPlugin.csproj b/Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.FruitBankPlugin.csproj index f9e70c8..1bb38be 100644 --- a/Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.FruitBankPlugin.csproj +++ b/Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.FruitBankPlugin.csproj @@ -27,10 +27,12 @@ + + diff --git a/Nop.Plugin.Misc.AIPlugin/Services/OpenAIApiService.cs b/Nop.Plugin.Misc.AIPlugin/Services/OpenAIApiService.cs index e94d49f..70802ee 100644 --- a/Nop.Plugin.Misc.AIPlugin/Services/OpenAIApiService.cs +++ b/Nop.Plugin.Misc.AIPlugin/Services/OpenAIApiService.cs @@ -312,6 +312,34 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services _assistantId ??= await FindOrCreateAssistantAsync("PDF and Image Analyzer Assistant"); } + public async Task CleanupAllVectorStoresAsync() + { + Console.WriteLine("Cleaning up all existing vector stores..."); + var listRequest = new HttpRequestMessage(HttpMethod.Get, $"{BaseUrl}/vector_stores"); + listRequest.Headers.Add("OpenAI-Beta", "assistants=v2"); + + var response = await _httpClient.SendAsync(listRequest); + if (response.IsSuccessStatusCode) + { + using var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); + var vectorStores = json.RootElement.GetProperty("data"); + + foreach (var vectorStore in vectorStores.EnumerateArray()) + { + var id = vectorStore.GetProperty("id").GetString(); + var name = vectorStore.TryGetProperty("name", out var nameElement) && nameElement.ValueKind != JsonValueKind.Null + ? nameElement.GetString() + : "Unnamed"; + + var deleteRequest = new HttpRequestMessage(HttpMethod.Delete, $"{BaseUrl}/vector_stores/{id}"); + deleteRequest.Headers.Add("OpenAI-Beta", "assistants=v2"); + await _httpClient.SendAsync(deleteRequest); + + Console.WriteLine($"Deleted vector store: {name} ({id})"); + } + Console.WriteLine("Vector store cleanup complete!"); + } + } //TEMPORARY: Cleanup all assistants (for testing purposes) - A. public async Task CleanupAllAssistantsAsync() { @@ -347,8 +375,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services public async Task AnalyzePdfAsync(Stream file, string fileName, string userPrompt) { - await EnsureAssistantAndVectorStoreAsync(); + var fileId = await UploadFileAsync(file, fileName); var isImage = IsImageFile(fileName); @@ -640,7 +668,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services var assistantBody = new { name = name, - instructions = "You are an assistant that analyzes uploaded files. When you receive an image, analyze and describe what you see in the image in detail. When you receive a PDF or text document, use the file_search tool to find and analyze relevant information. Always respond directly to the user's question about the file they uploaded.", + instructions = "You are an assistant that analyzes uploaded files. When you receive an image, analyze and describe what you see in the image in detail. When you receive a PDF or text document, use the file_search tool to find and analyze relevant information. Always respond directly according to the user's instructions about the current file they upload.", model = "gpt-4o", tools = new[] { new { type = "file_search" } } }; From 5fad9008972362e208ef9db813aa6c4b6d34a42c Mon Sep 17 00:00:00 2001 From: Loretta Date: Mon, 3 Nov 2025 06:44:45 +0100 Subject: [PATCH 2/3] Implement OrderItemMeasuringReset to MeasurementService --- .../Domains/DataLayer/FruitBankDbContext.cs | 16 +++++++ .../Services/MeasurementService.cs | 43 +++++++++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/Nop.Plugin.Misc.AIPlugin/Domains/DataLayer/FruitBankDbContext.cs b/Nop.Plugin.Misc.AIPlugin/Domains/DataLayer/FruitBankDbContext.cs index b60b8ad..bef2367 100644 --- a/Nop.Plugin.Misc.AIPlugin/Domains/DataLayer/FruitBankDbContext.cs +++ b/Nop.Plugin.Misc.AIPlugin/Domains/DataLayer/FruitBankDbContext.cs @@ -460,6 +460,22 @@ public class FruitBankDbContext : MgDbContextBase, return orderDto; } + public async Task SetOrderStatusToPendingSafeAsync(Order order) + => await TransactionSafeAsync(async _ => await SetOrderStatusToPendingAsync(order)); + + public async Task SetOrderStatusToPendingAsync(Order order) + { + if (order.OrderStatus == OrderStatus.Pending) return true; + + var prevOrderStatus = order.OrderStatus; + order.OrderStatus = OrderStatus.Pending; + + await Orders.UpdateAsync(order, false); + await _eventPublisher.PublishAsync(new OrderStatusChangedEvent(order, prevOrderStatus)); + + return true; + } + public Task DeleteOrderItemConstraintsSafeAsync(OrderItem orderItem, bool publishEvent = false) { return TransactionSafeAsync(async _ => diff --git a/Nop.Plugin.Misc.AIPlugin/Services/MeasurementService.cs b/Nop.Plugin.Misc.AIPlugin/Services/MeasurementService.cs index 518e5f3..10fe2dd 100644 --- a/Nop.Plugin.Misc.AIPlugin/Services/MeasurementService.cs +++ b/Nop.Plugin.Misc.AIPlugin/Services/MeasurementService.cs @@ -1,5 +1,7 @@ using AyCode.Core.Loggers; +using AyCode.Services.Server.SignalRs; using FruitBank.Common.Dtos; +using FruitBank.Common.Entities; using FruitBank.Common.Interfaces; using FruitBank.Common.Server.Services.SignalRs; using FruitBank.Common.Services; @@ -7,8 +9,10 @@ using Mango.Nop.Core.Extensions; using Mango.Nop.Core.Loggers; using Nop.Core.Domain.Catalog; using Nop.Core.Domain.Orders; +using Nop.Core.Events; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; using Nop.Services.Catalog; +using Nop.Services.Events; namespace Nop.Plugin.Misc.FruitBankPlugin.Services; @@ -16,18 +20,20 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services; public class MeasurementService : MeasurementServiceBase, IMeasurementService { private readonly FruitBankDbContext _dbContext; + private readonly IEventPublisher _eventPublisher; private readonly SignalRSendToClientService _signalRSendToClientService; private readonly CustomPriceCalculationService _customPriceCalculationService; - public MeasurementService(FruitBankDbContext dbContext, SignalRSendToClientService signalRSendToClientService, FruitBankAttributeService fruitBankAttributeService, - IPriceCalculationService customPriceCalculationService, IEnumerable logWriters) : base(new Logger(logWriters.ToArray())) + public MeasurementService(FruitBankDbContext dbContext, SignalRSendToClientService signalRSendToClientService, FruitBankAttributeService fruitBankAttributeService, + IPriceCalculationService customPriceCalculationService, IEventPublisher eventPublisher, IEnumerable logWriters) : base(new Logger(logWriters.ToArray())) { _dbContext = dbContext; + _eventPublisher = eventPublisher; _signalRSendToClientService = signalRSendToClientService; _customPriceCalculationService = (CustomPriceCalculationService)customPriceCalculationService; } - + public async Task DeleteOrderItemConstraintsAsync(int orderItemId) => await DeleteOrderItemConstraintsAsync(await _dbContext.OrderItems.GetByIdAsync(orderItemId)); public async Task DeleteOrderItemConstraintsAsync(OrderItem orderItem) @@ -56,4 +62,35 @@ public class MeasurementService : MeasurementServiceBase, IMeasurementSe await _customPriceCalculationService.CheckAndUpdateOrderTotalPrice(order); } } + + public async Task OrderItemMeasuringReset(int orderItemId) + => await OrderItemMeasuringReset(await _dbContext.OrderItems.GetByIdAsync(orderItemId)); + + public async Task OrderItemMeasuringReset(OrderItem orderItem) + { + var order = await _dbContext.Orders.GetByIdAsync(orderItem.OrderId); + var orderItemPallets = await _dbContext.OrderItemPallets.GetAllByOrderItemId(orderItem.Id, false).ToListAsync(); + + var result = await _dbContext.TransactionSafeAsync(async _ => + { + foreach (var orderItemPallet in orderItemPallets) + { + orderItemPallet.RevisorId = 0; + await _dbContext.OrderItemPallets.UpdateAsync(orderItemPallet); + } + + if (order.OrderStatus == OrderStatus.Complete) + await _dbContext.SetOrderStatusToPendingAsync(order); + + return true; + }); + + if (!result) return result; + + foreach (var orderItemPallet in orderItemPallets) + await _signalRSendToClientService.SendOrderItemPalletChanged(orderItemPallet); + + await _signalRSendToClientService.SendOrderChanged(await _dbContext.OrderDtos.GetByIdAsync(orderItem.OrderId, true)); + return result; + } } \ No newline at end of file From 91c11ffc7973787f6d7572b3877becab50d2b65c Mon Sep 17 00:00:00 2001 From: Loretta Date: Tue, 4 Nov 2025 15:31:49 +0100 Subject: [PATCH 3/3] improvements, fixes, etc... --- .../Controllers/FruitBankDataController.cs | 32 ++++++++++- .../Domains/DataLayer/FruitBankDbContext.cs | 29 +++++++++- .../Services/MeasurementService.cs | 55 ++++++++++++++++++- 3 files changed, 112 insertions(+), 4 deletions(-) diff --git a/Nop.Plugin.Misc.AIPlugin/Controllers/FruitBankDataController.cs b/Nop.Plugin.Misc.AIPlugin/Controllers/FruitBankDataController.cs index d7f16e7..e66ec5d 100644 --- a/Nop.Plugin.Misc.AIPlugin/Controllers/FruitBankDataController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Controllers/FruitBankDataController.cs @@ -1,4 +1,5 @@ -using AyCode.Core.Loggers; +using AyCode.Core.Extensions; +using AyCode.Core.Loggers; using AyCode.Services.SignalRs; using DocumentFormat.OpenXml.Office2010.Excel; using FruitBank.Common.Dtos; @@ -17,6 +18,7 @@ using Nop.Core; using Nop.Core.Domain.Customers; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; using Nop.Plugin.Misc.FruitBankPlugin.Factories; +using Nop.Plugin.Misc.FruitBankPlugin.Services; using Nop.Services.Customers; using Nop.Services.Localization; using Nop.Web.Framework.Controllers; @@ -27,6 +29,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers //https://linq2db.github.io/articles/sql/Join-Operators.html public class FruitBankDataController( FruitBankDbContext ctx, + MeasurementService measurementService, IWorkContext workContext, ICustomerService customerService, ICustomerRegistrationService customerRegistrationService, @@ -36,6 +39,11 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers { private readonly ILogger _logger = new Logger(logWriters.ToArray()); + [SignalR(SignalRTags.ProcessAndSaveFullShippingJson)] + public async Task> ProcessAndSaveFullShippingJson(string fullShippingJson, int customerId) + { + return await measurementService.ProcessAndSaveFullShippingJson(fullShippingJson, customerId); + } [SignalR(SignalRTags.GetMeasuringModels)] public Task> GetMeasuringModels() @@ -127,6 +135,17 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers return shippingItem; } + [SignalR(SignalRTags.AddShippingItem)] + public async Task AddShippingItem(ShippingItem shippingItem) + { + ArgumentNullException.ThrowIfNull(shippingItem); + + _logger.Detail($"AddShippingItem invoked; id: {shippingItem.Id}"); + + if (!await ctx.AddShippingItemAsync(shippingItem)) return null; + return await ctx.ShippingItems.GetByIdAsync(shippingItem.Id, shippingItem.ShippingDocument != null); + } + [SignalR(SignalRTags.UpdateShippingItem)] public async Task UpdateShippingItem(ShippingItem shippingItem) { @@ -218,6 +237,17 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers return await ctx.ShippingDocuments.GetByIdAsync(id, true); } + [SignalR(SignalRTags.AddShippingDocument)] + public async Task AddShippingDocument(ShippingDocument shippingDocument) + { + ArgumentNullException.ThrowIfNull(shippingDocument); + + _logger.Detail($"AddShippingDocument invoked; id: {shippingDocument.Id}"); + + await ctx.ShippingDocuments.InsertAsync(shippingDocument); + return await ctx.ShippingDocuments.GetByIdAsync(shippingDocument.Id, shippingDocument.Shipping != null || shippingDocument.Partner != null); + } + [SignalR(SignalRTags.UpdateShippingDocument)] public async Task UpdateShippingDocument(ShippingDocument shippingDocument) { diff --git a/Nop.Plugin.Misc.AIPlugin/Domains/DataLayer/FruitBankDbContext.cs b/Nop.Plugin.Misc.AIPlugin/Domains/DataLayer/FruitBankDbContext.cs index bef2367..1d2311b 100644 --- a/Nop.Plugin.Misc.AIPlugin/Domains/DataLayer/FruitBankDbContext.cs +++ b/Nop.Plugin.Misc.AIPlugin/Domains/DataLayer/FruitBankDbContext.cs @@ -152,6 +152,25 @@ public class FruitBankDbContext : MgDbContextBase, //public async Task GetMeasuringAttributeValuesByProductIdAsync(int productId) // => await _fruitBankAttributeService.GetMeasuringAttributeValuesAsync(productId); + //public async Task> ProcessAndSaveFullShippingDocumentJson(string fullShippingDocumentJson) + //{ + // var partners = fullShippingDocumentJson.JsonTo>(); + // if (partners != null) + // { + // foreach (var partner in partners) + // { + // //await Partners.InsertAsync(partner); + // if (partner.ShippingDocuments == null) continue; + + // foreach (var shippingDocument in partner.ShippingDocuments) + // { + // await ShippingDocuments.InsertAsync(shippingDocument); + + // } + // } + // } + //} + public async Task DeleteShippingSafeAsync(Shipping shipping) { await TransactionSafeAsync(async _ => @@ -186,6 +205,14 @@ public class FruitBankDbContext : MgDbContextBase, Logger.Error("shippingItem.IsMeasurable && !shippingItem.IsValidMeasuringValues()"); return Task.FromResult(false); } + public Task AddShippingItemSafeAsync(ShippingItem shippingItem) + => TransactionSafeAsync(async _ => await AddShippingItemAsync(shippingItem)); + + public async Task AddShippingItemAsync(ShippingItem shippingItem) + { + await ShippingItems.InsertAsync(shippingItem); + return true; + } public Task UpdateShippingItemSafeAsync(ShippingItem shippingItem) => TransactionSafeAsync(async _ => await UpdateShippingItemAsync(shippingItem)); @@ -268,7 +295,7 @@ public class FruitBankDbContext : MgDbContextBase, } //if (productIdUnchanged || !dbShippingItem.IsMeasured) return true; - if (!productIdChanged && (shippingItem.IsMeasured || !dbShippingItem.IsMeasured)) return true; + if (!dbShippingItem.ProductId.HasValue || (!productIdChanged && (shippingItem.IsMeasured || !dbShippingItem.IsMeasured))) return true; productDto = await ProductDtos.GetByIdAsync(dbShippingItem.ProductId!.Value, true); diff --git a/Nop.Plugin.Misc.AIPlugin/Services/MeasurementService.cs b/Nop.Plugin.Misc.AIPlugin/Services/MeasurementService.cs index 10fe2dd..c21fabe 100644 --- a/Nop.Plugin.Misc.AIPlugin/Services/MeasurementService.cs +++ b/Nop.Plugin.Misc.AIPlugin/Services/MeasurementService.cs @@ -1,4 +1,5 @@ -using AyCode.Core.Loggers; +using AyCode.Core.Extensions; +using AyCode.Core.Loggers; using AyCode.Services.Server.SignalRs; using FruitBank.Common.Dtos; using FruitBank.Common.Entities; @@ -7,7 +8,9 @@ using FruitBank.Common.Server.Services.SignalRs; using FruitBank.Common.Services; using Mango.Nop.Core.Extensions; using Mango.Nop.Core.Loggers; +using Microsoft.CodeAnalysis.Operations; using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; using Nop.Core.Domain.Orders; using Nop.Core.Events; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; @@ -21,6 +24,7 @@ public class MeasurementService : MeasurementServiceBase, IMeasurementSe { private readonly FruitBankDbContext _dbContext; private readonly IEventPublisher _eventPublisher; + private readonly FruitBankAttributeService _fruitBankAttributeService; private readonly SignalRSendToClientService _signalRSendToClientService; private readonly CustomPriceCalculationService _customPriceCalculationService; @@ -29,11 +33,11 @@ public class MeasurementService : MeasurementServiceBase, IMeasurementSe { _dbContext = dbContext; _eventPublisher = eventPublisher; + _fruitBankAttributeService = fruitBankAttributeService; _signalRSendToClientService = signalRSendToClientService; _customPriceCalculationService = (CustomPriceCalculationService)customPriceCalculationService; } - public async Task DeleteOrderItemConstraintsAsync(int orderItemId) => await DeleteOrderItemConstraintsAsync(await _dbContext.OrderItems.GetByIdAsync(orderItemId)); public async Task DeleteOrderItemConstraintsAsync(OrderItem orderItem) @@ -73,6 +77,8 @@ public class MeasurementService : MeasurementServiceBase, IMeasurementSe var result = await _dbContext.TransactionSafeAsync(async _ => { + await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync(order.Id, nameof(IOrderDto.RevisorId), 0); + foreach (var orderItemPallet in orderItemPallets) { orderItemPallet.RevisorId = 0; @@ -93,4 +99,49 @@ public class MeasurementService : MeasurementServiceBase, IMeasurementSe await _signalRSendToClientService.SendOrderChanged(await _dbContext.OrderDtos.GetByIdAsync(orderItem.OrderId, true)); return result; } + + public async Task?> ProcessAndSaveFullShippingJson(string fullShippingJson, int customerId) + { + var partners = fullShippingJson.JsonTo>(); + if (partners == null || partners.Count == 0) return partners; + + var a = partners.SelectMany(x => x.ShippingDocuments?.SelectMany(sd => sd.ShippingItems?.Where(si => si.ProductId.GetValueOrDefault(0) > 0).Select(si => si.ProductId!.Value) ?? []) ?? []).ToHashSet(); + var productDtosById = await _dbContext.ProductDtos.GetAllByIds(a, false, false).ToDictionaryAsync(k => k.Id, v => v); + + var result = await _dbContext.TransactionSafeAsync(async _ => + { + foreach (var partner in partners) + { + //await _dbContext.Partners.InsertAsync(partner, false); + if (partner.ShippingDocuments == null) continue; + + foreach (var shippingDocument in partner.ShippingDocuments) + { + //shippingDocument.PartnerId = 0; + await _dbContext.ShippingDocuments.InsertAsync(shippingDocument, false); + + if (shippingDocument.ShippingItems == null) continue; + + foreach (var shippingItem in shippingDocument.ShippingItems) + { + if (shippingItem.ProductId != null && productDtosById.TryGetValue(shippingItem.ProductId.Value, out var productDto)) + { + shippingItem.Name = productDto.Name; + shippingItem.IsMeasurable = productDto.IsMeasurable; + + //TODO: Update Product Incoming attribute! - J. + } + else shippingItem.ProductId = null; + + shippingItem.ShippingDocumentId = shippingDocument.Id; + await _dbContext.ShippingItems.InsertAsync(shippingItem, false); + } + } + } + + return true; + }); + + return result ? partners : null; + } } \ No newline at end of file