using AyCode.Core.Loggers; using DevExpress.Pdf.Native; using FruitBank.Common; using FruitBank.Common.Entities; using Mango.Nop.Core.Loggers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; using Nop.Plugin.Misc.FruitBankPlugin.Services; using Nop.Services.Catalog; using Nop.Services.Security; using Nop.Web.Areas.Admin.Controllers; 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 { [Area(AreaNames.ADMIN)] [AuthorizeAdmin] public class ManagementPageController : BaseAdminController { private readonly IPermissionService _permissionService; protected readonly FruitBankDbContext _dbContext; protected readonly AICalculationService _aiCalculationService; protected readonly OpenAIApiService _openAIApiService; private readonly CustomPriceCalculationService _customPriceCalculationService; private readonly ILogger _logger; public ManagementPageController(IPermissionService permissionService, IPriceCalculationService customPriceCalculationService, FruitBankDbContext fruitBankDbContext, AICalculationService aiCalculationService, OpenAIApiService openAIApiService, IEnumerable logWriters) { _logger = new Logger(logWriters.ToArray()); _permissionService = permissionService; _dbContext = fruitBankDbContext; _aiCalculationService = aiCalculationService; _openAIApiService = openAIApiService; _customPriceCalculationService = customPriceCalculationService as CustomPriceCalculationService; } private async Task OrderTotalsFix() { //var orders = await _dbContext.Orders.Table.ToListAsync(); //foreach (var order in orders) //{ // await _customPriceCalculationService.CheckAndUpdateOrderTotalPrice(order); //} } public async Task Test() { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); //await OrderTotalsFix(); var testPageModel = new TestPageModel(); testPageModel.Grids = new List(); var testGridModel2 = new TestGridModel(); testGridModel2.GridName = "Orders"; testGridModel2.ViewComponentName = "ShippingDocumentGridComponent"; testGridModel2.ViewComponentLocation = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/ShippingDocumentGridComponent.cshtml"; testGridModel2.Configuration = new GridConfiguration(); testGridModel2.Configuration.ShowChildGridsAsTabs = true; testGridModel2.ChildGrids = new List(); var childGrid1 = new TestGridModel { GridName = "TestGrid", ViewComponentName = "TestGridComponent", ViewComponentLocation = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/TestGridComponent.cshtml", ParentGridId = testGridModel2.Id, ChildGrids = new List() }; testGridModel2.ChildGrids.Add(childGrid1); var childGrid2 = new TestGridModel { GridName = "Files", ViewComponentName = "FileUploadGridComponent", ViewComponentLocation = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/FileUploadGridComponent.cshtml", ParentGridId = testGridModel2.Id, ChildGrids = new List() }; testGridModel2.ChildGrids.Add(childGrid2); testPageModel.Grids.Add(testGridModel2); var testGridModel = new TestGridModel(); testGridModel.GridName = "Shipping"; testGridModel.ViewComponentName = "ShippingGridComponent"; testPageModel.Grids.Add(testGridModel); var testGridModel3 = new TestGridModel(); testGridModel3.GridName = "Partners"; testGridModel3.ViewComponentName = "PartnersGridComponent"; testPageModel.Grids.Add(testGridModel3); //testGridModel.ViewComponentLocation = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Components/Views/_ShippingGridComponent.cshtml"; return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/Test.cshtml", testPageModel); } [HttpPost] public IActionResult LoadChildGrid([FromBody] LoadChildGridRequest request) { // request.contextId is the actual row ID (data.Id from DevExtreme) // request.childModel is the full TestGridModel object // Add the context ID to the model's DataContext if (request.ChildModel.DataContext == null) request.ChildModel.DataContext = new Dictionary(); request.ChildModel.DataContext["contextId"] = request.ContextId; // Invoke the view component with the full model return ViewComponent(request.ChildModel.ViewComponentName, request.ChildModel); } // 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 } [HttpGet] public async Task GetShippings() { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); // Mock data for now var model = _dbContext.Shippings.GetAll(true).OrderByDescending(s => s.Created).ToList(); var valami = model; //model. = await _dbContext.GetShippingDocumentsByShippingIdAsync(shippingId); return Json(model); } [HttpGet] public async Task GetShippingDocuments() { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); // Mock data for now var model = await _dbContext.ShippingDocuments.GetAll(true).OrderByDescending(sd => sd.Created).ToListAsync(); var valami = model; //model. = await _dbContext.GetShippingDocumentsByShippingIdAsync(shippingId); return Json(model); } [HttpPost] public async Task ShippingDocumentList(int shippingId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); // Apply filters to mock data var model = await _dbContext.GetShippingDocumentsByShippingIdAsync(shippingId); return Json(model); } [HttpGet] public async Task GetPartners() { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); // Mock data for now var model = await _dbContext.Partners.GetAll().OrderByDescending(p => p.Created).ToListAsync(); var valami = model; //model. = await _dbContext.GetShippingDocumentsByShippingIdAsync(shippingId); return Json(model); } [HttpGet] public async Task GetShippingItemsByShippingDocumentId(int shippingDocumentId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); // Mock data for now var model = await _dbContext.ShippingItems.GetAll(true).Where(sd => sd.ShippingDocumentId == shippingDocumentId).OrderByDescending(sd => sd.Created).ToListAsync(); var valami = model; //model. = await _dbContext.GetShippingDocumentsByShippingIdAsync(shippingId); return Json(model); } [HttpGet] public async Task GetShippingDocumentsByShippingDocumentId(int shippingDocumentId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); // Mock data for now var model = await _dbContext.ShippingDocumentToFiles.GetAll().Where(f => f.ShippingDocumentId == shippingDocumentId).OrderByDescending(sd => sd.Created).ToListAsync(); var valami = model; //model. = await _dbContext.GetShippingDocumentsByShippingIdAsync(shippingId); 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] public async Task ProcessShippingDocument(int shippingDocumentId) { var shippingDocument = await _dbContext.ShippingDocuments.GetByIdAsync(shippingDocumentId); return Ok("Ok"); } [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.ShippingDocument.ShippingDocumentToFiles = []; //checks // - files exist if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, errorMessage = "Access denied" }); // - permissions if (files == null || files.Count == 0) return Json(new { success = false, errorMessage = "No file selected" }); // - do we have partnerId - if so, we already have at least one doecument uploaded earlier if (partnerId.HasValue) { _logger.Debug($"Associated with Partner ID: {partnerId.Value}"); //let's get the partner shippingDocumentAnalysisResult.Partner = await _dbContext.Partners.GetByIdAsync(partnerId.Value); } var filesList = new List(); //iteratation 1: iterate documents to determine their type by AI var responseRawPdfTexts = string.Empty; foreach (var currentFile in files) { var fileName = currentFile.FileName; var fileSize = currentFile.Length; var dbFile = new Files(); var pdfText = ""; _logger.Detail($"Received file: {fileName} for Document ID: {shippingDocumentId}, content type: {currentFile.ContentType}"); 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 (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 (currentFile.Length > 0) { if (currentFile.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)) { try { // Open the PDF from the IFormFile's stream directly in memory 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)) { 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); } 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 {currentFile.FileName}: {ex.Message}"); return StatusCode(500, $"Error processing PDF file: {ex.Message}"); } } else //read from image { try { // Open the Image from the IFormFile's stream directly in memory await using var stream = currentFile.OpenReadStream(); try { // ✅ 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 {currentFile.FileName}: {ex.Message}"); return StatusCode(500, $"Error processing PDF file: {ex.Message}"); } } //we should have some kind of text now _logger.Detail(pdfText); } //ORIGINAL!!! - J. shippingDocumentAnalysisResult = await ProcessRawText(shippingDocumentId, partnerId, pdfText, shippingDocumentAnalysisResult, filesList, dbFile); await SaveDocumentToFileSystem(currentFile, shippingDocumentId, $"{Guid.NewGuid()}_{currentFile.FileName}");//shippingDocumentAnalysisResult.Partner.Name); return Ok($"Files for Shipping Document ID {shippingDocumentId} were uploaded successfully!{AcEnv.NL}{AcEnv.NL}{responseRawPdfTexts}"); //Ez rész a catch végéig nekem teszt! - J. try { //responseRawPdfTexts += $"{AcEnv.NL}{AcEnv.NL}{currentFile.Name}:{AcEnv.NL}{pdfText}"; 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 (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 // - 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 (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: { ""Name"": ""company name"", ""TaxId"": ""tax identification number"", ""CertificationNumber"": ""certification number if exists, otherwise empty string"", ""PostalCode"": ""postal code"", ""Country"": ""country name"", ""State"": ""state if exists, otherwise empty string"", ""County"": ""county if exists, otherwise empty string"", ""City"": ""city name"", ""Street"": ""street address"" } If a field is not found in the text, use an empty string. Return ONLY the JSON object, no additional text or explanation. Text to analyze: " + pdfText; 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; } } } //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)"", ""Country"": ""country of origin for pickup"", ""TotalPallets"": number_of_total_pallets } If a field is not found, use empty string for text fields, current date for ShippingDate, and 0 for TotalPallets. Return ONLY the JSON object, no additional text or explanation. Text to analyze: " + pdfText; var shippingDocResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( pdfText, shippingDocPrompt ); 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: [ { ""Name"": ""product name"", ""PalletsOnDocument"": number_of_pallets, ""QuantityOnDocument"": quantity_count, ""NetWeightOnDocument"": net_weight_in_kg, ""GrossWeightOnDocument"": gross_weight_in_kg } ] If a numeric field is not found, use 0. Return ONLY the JSON array, no additional text or explanation. Text to analyze: " + pdfText; var itemsResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( pdfText, itemsPrompt ); var items = JsonSerializer.Deserialize>(CleanJsonResponse(itemsResponse), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new List(); // Prepare result var result = new ShippingDocumentAnalysisResult { Partner = shippingDocumentAnalysisResult.Partner, ShippingDocument = new ShippingDocument { DocumentIdNumber = shippingDocData.DocumentIdNumber, ShippingDate = shippingDocData.ShippingDate, Country = shippingDocData.Country, TotalPallets = shippingDocData.TotalPallets }, ShippingItems = items }; 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); // 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."); //} } /// /// 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) { dbFile.RawText = pdfText; dbFile.FileExtension = "pdf"; dbFile.FileName = extractedMetaData.DocumentNumber; } return await _dbContext.TransactionSafeAsync(async _ => { await _dbContext.Files.InsertAsync(dbFile); filesList.Add(dbFile); var shippingDocumentToFiles = new ShippingDocumentToFiles { ShippingDocumentId = shippingDocumentId, FilesId = dbFile.Id, DocumentType = extractedMetaData.DocumentType != null ? (DocumentType)Enum.Parse(typeof(DocumentType), extractedMetaData.DocumentType) : DocumentType.Unknown }; _logger.Detail(shippingDocumentToFiles.DocumentType.ToString()); shippingDocumentAnalysisResult.ShippingDocument.ShippingDocumentToFiles.Add(shippingDocumentToFiles); await _dbContext.ShippingDocumentToFiles.InsertAsync(shippingDocumentToFiles); return true; }); } //[HttpPost] //public async Task UploadFile(List files, int shippingDocumentId) //{ // if (files == null || files.Count == 0) // { // return BadRequest("No files were uploaded."); // } // foreach (var file in files) // { // // Here, you would implement your logic to save the file. // // For example, you can save the file to a specific folder on the server // // or a cloud storage service like Azure Blob Storage. // // You would use the 'shippingDocumentId' to associate the file with the correct entity in your database. // // Example: Get file details // var fileName = file.FileName; // var fileSize = file.Length; // // Log or process the file and its associated ID. // // For demonstration, let's just return a success message. // _logger.Detail($"Received file: {fileName} for Document ID: {shippingDocumentId}"); // } // // Return a success response. The DevExtreme FileUploader expects a 200 OK status. // return Ok(new { message = "Files uploaded successfully." }); //} private string CleanJsonResponse(string response) { // Remove markdown code blocks if present response = response.Trim(); if (response.StartsWith("```json")) { response = response.Substring(7); } else if (response.StartsWith("```")) { response = response.Substring(3); } if (response.EndsWith("```")) { response = response.Substring(0, response.Length - 3); } return response.Trim(); } public class ShippingDocumentAnalysisRequest { public string RawText { get; set; } } public class ShippingDocumentAnalysisResult { public Partner Partner { get; set; } public ShippingDocument ShippingDocument { get; set; } public List ShippingItems { get; set; } } public class ShippingDocumentDto { public string DocumentIdNumber { get; set; } public DateTime ShippingDate { get; set; } public string Country { get; set; } public int TotalPallets { get; set; } } private static ExtractedDocumentData ParseShippingDocumentAIResponse(string aiResponse) { try { // Try to parse as JSON first var data = JsonSerializer.Deserialize( aiResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true } ); return data ?? new ExtractedDocumentData(); } catch { // If JSON parsing fails, return empty data with the raw response in notes return new ExtractedDocumentData { Notes = $"AI Analysis (raw): {aiResponse}" }; } } private static ExtractedDocumentMetaData ParseMetaDataAIResponse(string aiResponse) { try { // Try to parse as JSON first var data = JsonSerializer.Deserialize(aiResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); return data ?? new ExtractedDocumentMetaData(); } catch { // If JSON parsing fails, return empty data with the raw response in notes return new ExtractedDocumentMetaData { DocumentNumber = $"Unknown", DocumentType = "Unknown" }; } } private static ExtractedPartnerData ParsePartnerDataAIResponse(string aiResponse) { try { // Try to parse as JSON first var data = JsonSerializer.Deserialize( aiResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true } ); return data ?? new ExtractedPartnerData(); } catch { // If JSON parsing fails, return empty data with the raw response in notes return new ExtractedPartnerData { Name = "Unknown", CertificationNumber = "Unknown", TaxId = "Unknown", PostalCode = "Unknown", Country = "Unknown", State = "Unknown", County = "Unknown", City = "Unknown", Street = "Unknown", }; } } private class ExtractedDocumentMetaData { public string DocumentNumber { get; set; } public string DocumentType { get; set; } } private class ExtractedPartnerData { //Name, TaxId, CertificationNumber, PostalCode, Country, State, County, City, Street, public string Name { get; set; } public string TaxId { get; set; } public string CertificationNumber { get; set; } public string PostalCode { get; set; } public string Country { get; set; } public string State { get; set; } public string County { get; set; } public string City { get; set; } public string Street { get; set; } } // Helper class for extracted data private class ExtractedDocumentData { public DateTime? DocumentDate { get; set; } public string RecipientName { get; set; } public string SenderName { get; set; } public string InvoiceNumber { get; set; } public decimal? TotalAmount { get; set; } public int? ItemCount { get; set; } public string Notes { get; set; } } [HttpPost] public async Task DeleteUploadedFile(string filePath) { try { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, message = "Access denied" }); if (string.IsNullOrEmpty(filePath)) return Json(new { success = false, message = "Invalid file path" }); // TODO: Delete document record from database first // var document = await _documentService.GetDocumentByFilePathAsync(filePath); // if (document != null) // await _documentService.DeleteDocumentAsync(document); var fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", filePath.TrimStart('/')); 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 }); } catch (Exception ex) { return Json(new { success = false, message = ex.Message }); } } 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 " + $"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 " + $"/// \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 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 " + $"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.\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 { public int ShippingDocumentId { get; set; } public List Files { get; set; } } }