diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs index aa29b8a..f29beeb 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderController.cs @@ -483,8 +483,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers 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); + //var orderDto = await _dbContext.OrderDtos.GetByIdAsync(order.Id, true); + //await _sendToClient.SendMeasuringNotification("Módosult a rendelés, mérjétek újra!", orderDto); return true; }); diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs index 28be353..c4409f4 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs @@ -15,7 +15,9 @@ using Nop.Web.Framework; using Nop.Web.Framework.Mvc.Filters; using System.Text; using System.Text.Json; +using AyCode.Core.Consts; using UglyToad.PdfPig.DocumentLayoutAnalysis.TextExtractor; +using AyCode.Core.Extensions; namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers { @@ -68,6 +70,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers testGridModel2.Configuration = new GridConfiguration(); testGridModel2.Configuration.ShowChildGridsAsTabs = true; testGridModel2.ChildGrids = new List(); + var childGrid1 = new TestGridModel { GridName = "TestGrid", @@ -88,7 +91,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers }; testGridModel2.ChildGrids.Add(childGrid2); - testPageModel.Grids.Add(testGridModel2); var testGridModel = new TestGridModel(); @@ -124,8 +126,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers // Request model for deserialization public class LoadChildGridRequest { - public int ContextId { get; set; } // The actual row ID from data.Id - public TestGridModel ChildModel { get; set; } // The full child grid model + public int ContextId { get; set; } // The actual row ID from data.Id + public TestGridModel ChildModel { get; set; } // The full child grid model } @@ -226,24 +228,22 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers } - [HttpPost] + [HttpPost] [RequestSizeLimit(10485760)] // 10MB [RequestFormLimits(MultipartBodyLengthLimit = 10485760)] public async Task UploadFile(List files, int shippingDocumentId, int? partnerId) { - //an empty shippingdocument created by the user var shippingDocument = await _dbContext.ShippingDocuments.GetByIdAsync(shippingDocumentId); + + var shippingDocumentAnalysisResult = new ShippingDocumentAnalysisResult + { + Partner = new Partner(), + ShippingDocument = shippingDocument, + ShippingItems = [] + }; - - - ShippingDocumentAnalysisResult shippingDocumentAnalysisResult = new ShippingDocumentAnalysisResult(); - shippingDocumentAnalysisResult.Partner = new Partner(); - shippingDocumentAnalysisResult.ShippingDocument = shippingDocument; - shippingDocumentAnalysisResult.ShippingItems = new List(); - shippingDocumentAnalysisResult.ShippingDocument.ShippingDocumentToFiles = new List(); - - + shippingDocumentAnalysisResult.ShippingDocument.ShippingDocumentToFiles = []; //checks // - files exist @@ -264,73 +264,67 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers } var filesList = new List(); - - + //iteratation 1: iterate documents to determine their type by AI - - for (int i = 0; i < files.Count; i++) + var responseRawPdfTexts = string.Empty; + foreach (var currentFile in files) { - - var fileName = files[i].FileName; - var fileSize = files[i].Length; + var fileName = currentFile.FileName; + var fileSize = currentFile.Length; var dbFile = new Files(); - string pdfText = ""; + var pdfText = ""; - _logger.Detail($"Received file: {fileName} for Document ID: {shippingDocumentId}, content type: {files[i].ContentType}"); + _logger.Detail($"Received file: {fileName} for Document ID: {shippingDocumentId}, content type: {currentFile.ContentType}"); - if (!files[i].ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase) && !files[i].ContentType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase)) + if (!currentFile.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase) && !currentFile.ContentType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase)) return Json(new { success = false, errorMessage = "Only PDF or jpg files are allowed" }); // Validate file size (max 20MB) - if (files[i].Length > 20 * 1024 * 1024) + if (currentFile.Length > 20 * 1024 * 1024) return Json(new { success = false, errorMessage = "File size must be less than 10MB" }); // - get text extracted from pdf // Validate file type (PDF only) //if (file.Length > 0 && file.ContentType == "application/pdf") - if (files[i].Length > 0) + + if (currentFile.Length > 0) { - if (files[i].ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)) + if (currentFile.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)) { try { // Open the PDF from the IFormFile's stream directly in memory - using (var stream = files[i].OpenReadStream()) - using (var pdf = UglyToad.PdfPig.PdfDocument.Open(stream)) + await using var stream = currentFile.OpenReadStream(); + using var pdf = UglyToad.PdfPig.PdfDocument.Open(stream); + // Now you can analyze the PDF content + + foreach (var page in pdf.GetPages()) + pdfText += ContentOrderTextExtractor.GetText(page); + + //still nothing? let's send it to AI + if (string.IsNullOrWhiteSpace(pdfText)) { - // Now you can analyze the PDF content - - foreach (var page in pdf.GetPages()) + try { - // Extract text from each page - pdfText += ContentOrderTextExtractor.GetText(page); - + // ✅ 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); } - //still nothing? let's send it to AI - if (string.IsNullOrWhiteSpace(pdfText)) + catch (Exception aiEx) { - try - { - // ✅ Use the service we implemented earlier - pdfText = await _openAIApiService.AnalyzePdfAsync(stream, files[i].FileName, "Please extract all readable text from this PDF."); - } - catch (Exception aiEx) - { - Console.Error.WriteLine($"OpenAI Assistants API failed: {aiEx.Message}"); - return StatusCode(500, $"Failed to process PDF: {aiEx.Message}"); - } + Console.Error.WriteLine($"OpenAI Assistants API failed: {aiEx.Message}"); + return StatusCode(500, $"Failed to process PDF: {aiEx.Message}"); } - - // For demonstration, let's just log the extracted text - _logger.Detail($"Extracted text from {files[i].FileName}: {pdfText}"); - } + + // For demonstration, let's just log the extracted text + _logger.Detail($"Extracted text from {currentFile.FileName}: {pdfText}"); } catch (Exception ex) { // Handle potential exceptions during PDF processing - Console.Error.WriteLine($"Error processing PDF file {files[i].FileName}: {ex.Message}"); + Console.Error.WriteLine($"Error processing PDF file {currentFile.FileName}: {ex.Message}"); return StatusCode(500, $"Error processing PDF file: {ex.Message}"); } } @@ -339,80 +333,107 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers try { // Open the Image from the IFormFile's stream directly in memory - using (var stream = files[i].OpenReadStream()) + await using var stream = currentFile.OpenReadStream(); + try { - - try - { - // ✅ Use the service we implemented earlier - pdfText = await _openAIApiService.AnalyzePdfAsync(stream, files[i].FileName, "Please extract all readable text from this image."); - } - catch (Exception aiEx) - { - Console.Error.WriteLine($"OpenAI Assistants API failed: {aiEx.Message}"); - return StatusCode(500, $"Failed to process PDF: {aiEx.Message}"); - } - - // For demonstration, let's just log the extracted text - _logger.Detail($"Extracted text from {files[i].FileName}: {pdfText}"); - + // ✅ Use the service we implemented earlier + pdfText = await _openAIApiService.AnalyzePdfAsync(stream, currentFile.FileName, "Please extract all readable text from this image."); + } + catch (Exception aiEx) + { + Console.Error.WriteLine($"OpenAI Assistants API failed: {aiEx.Message}"); + return StatusCode(500, $"Failed to process PDF: {aiEx.Message}"); } - } + // For demonstration, let's just log the extracted text + _logger.Detail($"Extracted text from {currentFile.FileName}: {pdfText}"); + } catch (Exception ex) { // Handle potential exceptions during PDF processing - Console.Error.WriteLine($"Error processing PDF file {files[i].FileName}: {ex.Message}"); + Console.Error.WriteLine($"Error processing PDF file {currentFile.FileName}: {ex.Message}"); return StatusCode(500, $"Error processing PDF file: {ex.Message}"); } } + //we should have some kind of text now _logger.Detail(pdfText); } - 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)}" + - "If you can't find information of any of these, return null value for that field."; + //ORIGINAL!!! - J. + shippingDocumentAnalysisResult = await ProcessRawText(shippingDocumentId, partnerId, pdfText, shippingDocumentAnalysisResult, filesList, dbFile); + await SaveDocumentToFileSystem(currentFile, shippingDocumentId, $"{Guid.NewGuid()}_{currentFile.FileName}");//shippingDocumentAnalysisResult.Partner.Name); - //here I can start preparing the file entity - var metaAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText.ToString(), analysisPrompt); + //Ez rész a catch végéig nekem teszt! - J + //try + //{ + // responseRawPdfTexts += $"{AcEnv.NL}{AcEnv.NL}{currentFile.Name}:{AcEnv.NL}{pdfText}"; - var extractedMetaData = ParseMetaDataAIResponse(metaAnalyzis); - bool transactionSuccess = await SaveFileInfoToDB(shippingDocumentId, shippingDocumentAnalysisResult, filesList, dbFile, pdfText, extractedMetaData); + // var transactionSuccess = await SaveFileInfoToDb(shippingDocumentId, shippingDocumentAnalysisResult, filesList, dbFile, pdfText); + // if (!transactionSuccess) + // { + // _logger.Error($"(transactionSuccess == false)"); + // return BadRequest($"Error saving file! RawText:{AcEnv.NL}{pdfText}"); + // } - if (!transactionSuccess) _logger.Error($"(transactionSuccess == false)"); + // if (await SaveDocumentToFileSystem(currentFile, shippingDocumentId, $"{Guid.NewGuid()}_{currentFile.FileName}")) + // { + // //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 + // - determine the items listed in the documents by AI + // try to fill all shippingitems information from the iteration + + + return Ok($"Files for Shipping Document ID {shippingDocumentId} were uploaded successfully!{AcEnv.NL}{AcEnv.NL}{responseRawPdfTexts}"); + } + + private async Task ProcessRawText(int shippingDocumentId, int? partnerId, string pdfText, ShippingDocumentAnalysisResult shippingDocumentAnalysisResult, List filesList, Files dbFile) + { + var transactionSuccess = await SaveFileInfoToDb(shippingDocumentId, shippingDocumentAnalysisResult, filesList, dbFile, pdfText); + if (!transactionSuccess) _logger.Error($"(transactionSuccess == false)"); + + // - IF WE DON'T HAVE PARTNERID ALREADY: read partner information + // (check if all 3 refers to the same partner) + // save partner information to partners table { Id, Name, TaxId, CertificationNumber, PostalCode, Country, State, County, City, Street } + if (partnerId == null) + { + //find partner in DB... there is a serious chance that the partner Name and Taxid determines the partner + + var partners = await _dbContext.Partners.GetAll().ToListAsync(); + foreach (var dbpartner in partners.Where(dbpartner => pdfText.Contains(dbpartner.Name) || (dbpartner.TaxId != null && pdfText.Contains(dbpartner.TaxId)))) + { + partnerId = dbpartner.Id; + _logger.Detail($"Found existing partner in DB: {dbpartner.Name} (ID: {dbpartner.Id})"); + break; + } - // - IF WE DON'T HAVE PARTNERID ALREADY: read partner information - // (check if all 3 refers to the same partner) - // save partner information to partners table { Id, Name, TaxId, CertificationNumber, PostalCode, Country, State, County, City, Street } if (partnerId == null) { + _logger.Detail("No existing partner found in DB, proceeding to extract partner info via AI."); - //find partner in DB... there is a serious chance that the partner Name and Taxid determines the partner + // string partnerAnalysisPrompt = "You are an agent of Fruitbank, helping to analyze pdf content. Determine which partner of Fruitbank sent this document, extract their data and return them as JSON: name, taxId, certificationNumber, postalCode, country, state, county, city, street. " + + //"If you can't find information of any of these, return null value for that field."; - var partners = await _dbContext.Partners.GetAll().ToListAsync(); - foreach (var dbpartner in partners) - { - if (pdfText.Contains(dbpartner.Name) || (dbpartner.TaxId != null && pdfText.Contains(dbpartner.TaxId))) - { - partnerId = dbpartner.Id; - _logger.Detail($"Found existing partner in DB: {dbpartner.Name} (ID: {dbpartner.Id})"); - break; - } - } + // //here I can start preparing the file entity + // var partnerAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText.ToString(), partnerAnalysisPrompt); - if (partnerId == null) - { - _logger.Detail("No existing partner found in DB, proceeding to extract partner info via AI."); - - // string partnerAnalysisPrompt = "You are an agent of Fruitbank, helping to analyze pdf content. Determine which partner of Fruitbank sent this document, extract their data and return them as JSON: name, taxId, certificationNumber, postalCode, country, state, county, city, street. " + - //"If you can't find information of any of these, return null value for that field."; - - // //here I can start preparing the file entity - // var partnerAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText.ToString(), partnerAnalysisPrompt); - - var partnerPrompt = @"Extract the partner/company information from the following text and return ONLY a valid JSON object with these exact fields: + var partnerPrompt = @"Extract the partner/company information from the following text and return ONLY a valid JSON object with these exact fields: { ""Name"": ""company name"", ""TaxId"": ""tax identification number"", @@ -430,57 +451,49 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers Text to analyze: " + pdfText; - var partnerResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( - pdfText, - partnerPrompt - ); + var partnerResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( + pdfText, + partnerPrompt + ); - shippingDocumentAnalysisResult.Partner = JsonSerializer.Deserialize(CleanJsonResponse(partnerResponse), - new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - - - if (shippingDocumentAnalysisResult.Partner.Name != null) - { - _logger.Detail("AI Analysis Partner Result:"); - _logger.Detail(shippingDocumentAnalysisResult.Partner.Name); - } - - if (shippingDocumentAnalysisResult.Partner.TaxId != null) - { - _logger.Detail(shippingDocumentAnalysisResult.Partner.TaxId); - } - - if (shippingDocumentAnalysisResult.Partner.Country != null) - { - - _logger.Detail(shippingDocumentAnalysisResult.Partner.Country); - } - - if (shippingDocumentAnalysisResult.Partner.State != null) - { - _logger.Detail(shippingDocumentAnalysisResult.Partner.State); - } - - if (shippingDocumentAnalysisResult.Partner != null) - { - shippingDocumentAnalysisResult.Partner = shippingDocumentAnalysisResult.Partner; - } + shippingDocumentAnalysisResult.Partner = JsonSerializer.Deserialize(CleanJsonResponse(partnerResponse), + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + if (shippingDocumentAnalysisResult.Partner.Name != null) + { + _logger.Detail("AI Analysis Partner Result:"); + _logger.Detail(shippingDocumentAnalysisResult.Partner.Name); } + if (shippingDocumentAnalysisResult.Partner.TaxId != null) + { + _logger.Detail(shippingDocumentAnalysisResult.Partner.TaxId); + } + + if (shippingDocumentAnalysisResult.Partner.Country != null) + { + + _logger.Detail(shippingDocumentAnalysisResult.Partner.Country); + } + + if (shippingDocumentAnalysisResult.Partner.State != null) + { + _logger.Detail(shippingDocumentAnalysisResult.Partner.State); + } + + if (shippingDocumentAnalysisResult.Partner != null) + { + shippingDocumentAnalysisResult.Partner = shippingDocumentAnalysisResult.Partner; + } } + } - - //shortcut - - - try - { - - - // Step 2: Extract Shipping Document Information - var shippingDocPrompt = @"Extract the shipping document information from the following text and return ONLY a valid JSON object with these exact fields: + //shortcut + try + { + // Step 2: Extract Shipping Document Information + var shippingDocPrompt = @"Extract the shipping document information from the following text and return ONLY a valid JSON object with these exact fields: { ""DocumentIdNumber"": ""document ID or reference number if exists, otherwise empty string"", ""ShippingDate"": ""shipping date in ISO format (yyyy-MM-dd)"", @@ -493,16 +506,16 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers Text to analyze: " + pdfText; - var shippingDocResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( - pdfText, - shippingDocPrompt - ); + var shippingDocResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( + pdfText, + shippingDocPrompt + ); - var shippingDocData = JsonSerializer.Deserialize(CleanJsonResponse(shippingDocResponse), - new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + var shippingDocData = JsonSerializer.Deserialize(CleanJsonResponse(shippingDocResponse), + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - // Step 3: Extract Shipping Items - var itemsPrompt = @"Extract all shipping items (fruits, vegetables, or products) from the following text and return ONLY a valid JSON array with objects having these exact fields: + // Step 3: Extract Shipping Items + var itemsPrompt = @"Extract all shipping items (fruits, vegetables, or products) from the following text and return ONLY a valid JSON array with objects having these exact fields: [ { ""Name"": ""product name"", @@ -518,152 +531,170 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers Text to analyze: " + pdfText; - var itemsResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( - pdfText, - itemsPrompt - ); + var itemsResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( + pdfText, + itemsPrompt + ); - var items = JsonSerializer.Deserialize>(CleanJsonResponse(itemsResponse), - new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new List(); + var items = JsonSerializer.Deserialize>(CleanJsonResponse(itemsResponse), + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new List(); - // Prepare result - var result = new ShippingDocumentAnalysisResult + // Prepare result + var result = new ShippingDocumentAnalysisResult + { + Partner = shippingDocumentAnalysisResult.Partner, + ShippingDocument = new ShippingDocument { - Partner = shippingDocumentAnalysisResult.Partner, - ShippingDocument = new ShippingDocument - { - DocumentIdNumber = shippingDocData.DocumentIdNumber, - ShippingDate = shippingDocData.ShippingDate, - Country = shippingDocData.Country, - TotalPallets = shippingDocData.TotalPallets - }, - ShippingItems = items - }; + DocumentIdNumber = shippingDocData.DocumentIdNumber, + ShippingDate = shippingDocData.ShippingDate, + Country = shippingDocData.Country, + TotalPallets = shippingDocData.TotalPallets + }, + ShippingItems = items + }; - //return Ok(result); - } - catch (JsonException ex) - { - return BadRequest($"Failed to parse AI response: {ex.Message}"); - } - - - // - save the documents to file system - wwwroot/uploads/orders/order-{orderId}/fileId-documentId.pdf - // where documentId is the number or id IN the document - try - { - // Create upload directory if it doesn't exist - var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "orders", "order" + shippingDocumentId.ToString(), "documents"); - Directory.CreateDirectory(uploadsPath); - - // Generate unique filename - fileName = $"{Guid.NewGuid()}_{files[i].FileName}"; - var filePath = Path.Combine(uploadsPath, fileName); - - // Save file - using (var stream = new FileStream(filePath, FileMode.Create)) - { - await files[i].CopyToAsync(stream); - } - - 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."); - } - - - // - create a list of documents to read information and to save the document's information to DB - // - INSIDE ITERATION: - // - IF SHIPPINGDOCUMENT: read shipping information - // save document information into DB {Id, PartnerId, ShippingId, DocumentIdNumber, ShippingDate, Country, TotalPallets, IsAllMeasured} - // - IF Orderdocument: try reading shipping information and financial information - // save document information into DB {Id, PartnerId, ShippingId, DocumentIdNumber, ShippingDate, Country, TotalPallets, IsAllMeasured} - // - IF Invoice: try reading financial information - // save document information into DB {financial db table not created yet} - // - save the documents to Files Table - { name, extension, type, rawtext } - - //try - //{ - - // // Analyze PDF with AI to extract structured data - // var lastPrompt = "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."; - - // var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( - // pdfText, - // lastPrompt - // ); - - // // Parse AI response (assuming it returns JSON) - // var extractedData = ParseShippingDocumentAIResponse(aiAnalysis); - - // //TODO: Save document record to database - // _logger.Detail("AI Analysis Result:"); - // _logger.Detail(extractedData.RecipientName); - // _logger.Detail(extractedData.SenderName); - // _logger.Detail(extractedData.InvoiceNumber); - // _logger.Detail(extractedData.TotalAmount.ToString()); - // _logger.Detail(extractedData.ItemCount.ToString()); - // _logger.Detail(extractedData.Notes); - - - // var documentId = 1; // Replace with: savedDocument.Id - - // // Return structured document model - // var documentModel = new ShippingDocumentModel - // { - // Id = documentId, - // ShippingId = shippingDocumentId, - // FileName = file.FileName, - // FilePath = $"/uploads/shippingDocuments/{fileName}", - // FileSize = (int)(file.Length / 1024), - // DocumentDate = extractedData.DocumentDate, - // RecipientName = extractedData.RecipientName, - // SenderName = extractedData.SenderName, - // InvoiceNumber = extractedData.InvoiceNumber, - // TotalAmount = extractedData.TotalAmount, - // ItemCount = extractedData.ItemCount, - // Notes = extractedData.Notes, - // RawAIAnalysis = aiAnalysis // Store the raw AI response for debugging - // }; - - - - // // var savedDocument = await _documentService.InsertDocumentAsync(document); - - // // Mock saved document ID - - - // //return Json(new - // //{ - // // success = true, - // // document = documentModel - // //}); - - //} - //catch (Exception ex) - //{ - // _logger.Error($"Error uploading file: {ex.Message}", ex); - // //return Json(new { success = false, errorMessage = ex.Message }); - // return BadRequest("No files were uploaded."); - //} + result.ShippingDocument.ShippingDocumentToFiles = shippingDocumentAnalysisResult.ShippingDocument.ShippingDocumentToFiles; + return result; + //return Ok(result); + } + catch (JsonException ex) + { + _logger.Error($"ProcessRawText ERROR; {ex.Message}", ex); } + return null; + + // - create a list of documents to read information and to save the document's information to DB + // - INSIDE ITERATION: + // - IF SHIPPINGDOCUMENT: read shipping information + // save document information into DB {Id, PartnerId, ShippingId, DocumentIdNumber, ShippingDate, Country, TotalPallets, IsAllMeasured} + // - IF Orderdocument: try reading shipping information and financial information + // save document information into DB {Id, PartnerId, ShippingId, DocumentIdNumber, ShippingDate, Country, TotalPallets, IsAllMeasured} + // - IF Invoice: try reading financial information + // save document information into DB {financial db table not created yet} + // - save the documents to Files Table - { name, extension, type, rawtext } + + //try + //{ + + // // Analyze PDF with AI to extract structured data + // var lastPrompt = "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."; + + // var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( + // pdfText, + // lastPrompt + // ); + + // // Parse AI response (assuming it returns JSON) + // var extractedData = ParseShippingDocumentAIResponse(aiAnalysis); + + // //TODO: Save document record to database + // _logger.Detail("AI Analysis Result:"); + // _logger.Detail(extractedData.RecipientName); + // _logger.Detail(extractedData.SenderName); + // _logger.Detail(extractedData.InvoiceNumber); + // _logger.Detail(extractedData.TotalAmount.ToString()); + // _logger.Detail(extractedData.ItemCount.ToString()); + // _logger.Detail(extractedData.Notes); - //iteration 2: iterate documents again - // - determine the items listed in the documents by AI - // try to fill all shippingitems information from the iteration + // var documentId = 1; // Replace with: savedDocument.Id + + // // Return structured document model + // var documentModel = new ShippingDocumentModel + // { + // Id = documentId, + // ShippingId = shippingDocumentId, + // FileName = file.FileName, + // FilePath = $"/uploads/shippingDocuments/{fileName}", + // FileSize = (int)(file.Length / 1024), + // DocumentDate = extractedData.DocumentDate, + // RecipientName = extractedData.RecipientName, + // SenderName = extractedData.SenderName, + // InvoiceNumber = extractedData.InvoiceNumber, + // TotalAmount = extractedData.TotalAmount, + // ItemCount = extractedData.ItemCount, + // Notes = extractedData.Notes, + // RawAIAnalysis = aiAnalysis // Store the raw AI response for debugging + // }; - return Ok($"Files for Shipping Document ID {shippingDocumentId} were uploaded successfully!"); + + // // var savedDocument = await _documentService.InsertDocumentAsync(document); + + // // Mock saved document ID + + + // //return Json(new + // //{ + // // success = true, + // // document = documentModel + // //}); + + //} + //catch (Exception ex) + //{ + // _logger.Error($"Error uploading file: {ex.Message}", ex); + // //return Json(new { success = false, errorMessage = ex.Message }); + // return BadRequest("No files were uploaded."); + //} } - private async Task SaveFileInfoToDB(int shippingDocumentId, ShippingDocumentAnalysisResult shippingDocumentAnalysisResult, List filesList, Files dbFile, string pdfText, ExtractedDocumentMetaData extractedMetaData) + /// + /// Save the documents to file system - wwwroot/uploads/orders/order-{orderId}/fileId-documentId.pdf where documentId is the number or id IN the document + /// + /// + /// + /// + /// + private async Task SaveDocumentToFileSystem(IFormFile file, int shippingDocumentId, string fileName) + { + try + { + // Create upload directory if it doesn't exist + var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "orders", "order" + shippingDocumentId, "documents"); + Directory.CreateDirectory(uploadsPath); + + // Generate unique filename + //var fileName = $"{Guid.NewGuid()}_{file.FileName}"; + var filePath = Path.Combine(uploadsPath, fileName); + + // Save file + await using var stream = new FileStream(filePath, FileMode.Create); + await file.CopyToAsync(stream); + + return true; + } + catch (Exception ex) + { + _logger.Error($"Error saving file: {ex.Message}", ex); + //return Json(new { success = false, errorMessage = ex.Message }); + } + + return false; + } + + private async Task GetDocumentTypeFromRawText(string pdfText) + { + const 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)}" + + "If you can't find information of any of these, return null value for that field."; + + //here I can start preparing the file entity + var metaAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText, analysisPrompt); + + return ParseMetaDataAIResponse(metaAnalyzis); + } + + private async Task SaveFileInfoToDb(int shippingDocumentId, ShippingDocumentAnalysisResult shippingDocumentAnalysisResult, List filesList, Files dbFile, string pdfText) + { + var extractedMetaData = await GetDocumentTypeFromRawText(pdfText); + return await SaveFileInfoToDb(shippingDocumentId, shippingDocumentAnalysisResult, filesList, dbFile, pdfText, extractedMetaData); + } + + private async Task SaveFileInfoToDb(int shippingDocumentId, ShippingDocumentAnalysisResult shippingDocumentAnalysisResult, List filesList, Files dbFile, string pdfText, ExtractedDocumentMetaData extractedMetaData) { if (extractedMetaData.DocumentNumber != null) { @@ -672,7 +703,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers dbFile.FileName = extractedMetaData.DocumentNumber; } - var transactionSuccess = await _dbContext.TransactionSafeAsync(async _ => + return await _dbContext.TransactionSafeAsync(async _ => { await _dbContext.Files.InsertAsync(dbFile); filesList.Add(dbFile); @@ -689,7 +720,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers await _dbContext.ShippingDocumentToFiles.InsertAsync(shippingDocumentToFiles); return true; }); - return transactionSuccess; } @@ -746,7 +776,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers { public string RawText { get; set; } } - public class ShippingDocumentAnalysisResult { public Partner Partner { get; set; } @@ -762,15 +791,14 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers public int TotalPallets { get; set; } } - - private ExtractedDocumentData ParseShippingDocumentAIResponse(string aiResponse) + private static ExtractedDocumentData ParseShippingDocumentAIResponse(string aiResponse) { try { // Try to parse as JSON first - var data = System.Text.Json.JsonSerializer.Deserialize( + var data = JsonSerializer.Deserialize( aiResponse, - new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true } + new JsonSerializerOptions { PropertyNameCaseInsensitive = true } ); return data ?? new ExtractedDocumentData(); } @@ -784,15 +812,12 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers } } - private ExtractedDocumentMetaData ParseMetaDataAIResponse(string aiResponse) + private static ExtractedDocumentMetaData ParseMetaDataAIResponse(string aiResponse) { try { // Try to parse as JSON first - var data = System.Text.Json.JsonSerializer.Deserialize( - aiResponse, - new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true } - ); + var data = JsonSerializer.Deserialize(aiResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); return data ?? new ExtractedDocumentMetaData(); } catch @@ -806,14 +831,14 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers } } - private ExtractedPartnerData ParsePartnerDataAIResponse(string aiResponse) + private static ExtractedPartnerData ParsePartnerDataAIResponse(string aiResponse) { try { // Try to parse as JSON first - var data = System.Text.Json.JsonSerializer.Deserialize( + var data = JsonSerializer.Deserialize( aiResponse, - new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true } + new JsonSerializerOptions { PropertyNameCaseInsensitive = true } ); return data ?? new ExtractedPartnerData(); } @@ -887,13 +912,11 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers var fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", filePath.TrimStart('/')); - if (System.IO.File.Exists(fullPath)) - { - System.IO.File.Delete(fullPath); - return Json(new { success = true }); - } + if (!System.IO.File.Exists(fullPath)) return Json(new { success = false, message = "File not found" }); + + System.IO.File.Delete(fullPath); + return Json(new { success = true }); - return Json(new { success = false, message = "File not found" }); } catch (Exception ex) { @@ -901,6 +924,98 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers } } + public void GetJson() + { + var partner = new Partner(); + var jsonResolver = new IgnoreAndRenamePropertySerializerContractResolver(); + + jsonResolver.IgnoreProperty(typeof(Partner), nameof(partner.Name)); + jsonResolver.IgnoreProperty(typeof(Partner), "Title"); + jsonResolver.IgnoreProperty(typeof(Partner), "Title"); + + jsonResolver.IncludesProperty(typeof(Partner), "Title"); + + jsonResolver.RenameProperty(typeof(Partner), "FirstName", "firstName"); + + var serializerSettings = new Newtonsoft.Json.JsonSerializerSettings(); + serializerSettings.ContractResolver = jsonResolver; + + var json = Newtonsoft.Json.JsonConvert.SerializeObject(partner, serializerSettings); + } + + 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