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 Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Azure; using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; using Nop.Plugin.Misc.FruitBankPlugin.Services; using Nop.Services.Security; using Nop.Web.Areas.Admin.Controllers; using Nop.Web.Framework; using Nop.Web.Framework.Mvc.Filters; using System.Text; using UglyToad.PdfPig.DocumentLayoutAnalysis.TextExtractor; 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 ILogger _logger; public ManagementPageController(IPermissionService permissionService, FruitBankDbContext fruitBankDbContext, AICalculationService aiCalculationService, OpenAIApiService openAIApiService, IEnumerable logWriters) { _logger = new Logger(logWriters.ToArray()); _permissionService = permissionService; _dbContext = fruitBankDbContext; _aiCalculationService = aiCalculationService; _openAIApiService = openAIApiService; } public async Task Test() { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); 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] [RequestSizeLimit(10485760)] // 10MB [RequestFormLimits(MultipartBodyLengthLimit = 10485760)] public async Task UploadFile(List files, int shippingDocumentId, int? partnerId) { var shippingDocument = await _dbContext.ShippingDocuments.GetByIdAsync(shippingDocumentId); //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 var partner = await _dbContext.Partners.GetByIdAsync(partnerId.Value); } var filesList = new List(); //iteratation 1: iterate documents to determine their type by AI foreach (var file in files) { var fileName = file.FileName; var fileSize = file.Length; var dbFile = new Files(); string pdfText = ""; _logger.Detail($"Received file: {fileName} for Document ID: {shippingDocumentId}, content type: {file.ContentType}"); if (!file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase) && !file.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 (file.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 (file.Length > 0) { if (file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)){ try { // Open the PDF from the IFormFile's stream directly in memory using (var stream = file.OpenReadStream()) using (var pdf = UglyToad.PdfPig.PdfDocument.Open(stream)) { // Now you can analyze the PDF content foreach (var page in pdf.GetPages()) { // Extract text from each page 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, file.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}"); } } // For demonstration, let's just log the extracted text _logger.Detail($"Extracted text from {file.FileName}: {pdfText}"); } } catch (Exception ex) { // Handle potential exceptions during PDF processing Console.Error.WriteLine($"Error processing PDF file {file.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 using (var stream = file.OpenReadStream()) { try { // ✅ Use the service we implemented earlier pdfText = await _openAIApiService.AnalyzePdfAsync(stream, file.FileName, "Please extract all readable text from this 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 {file.FileName}: {pdfText}"); } } catch (Exception ex) { // Handle potential exceptions during PDF processing Console.Error.WriteLine($"Error processing PDF file {file.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."; //here I can start preparing the file entity var metaAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText.ToString(), analysisPrompt); var extractedMetaData = ParseMetaDataAIResponse(metaAnalyzis); if (extractedMetaData.DocumentNumber != null) { dbFile.RawText = pdfText; dbFile.FileExtension = "pdf"; dbFile.FileName = extractedMetaData.DocumentNumber; } var transactionSuccess = 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()); await _dbContext.ShippingDocumentToFiles.InsertAsync(shippingDocumentToFiles); return true; }); 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) { string partnerAnalysisPrompt = "Extract the sender information from this document, 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 extractedPartnerData = ParsePartnerDataAIResponse(partnerAnalyzis); if (extractedPartnerData.Name != null) { _logger.Detail("AI Analysis Partner Result:"); _logger.Detail(extractedPartnerData.Name); } if (extractedPartnerData.TaxId != null) { _logger.Detail(extractedPartnerData.TaxId); } if (extractedPartnerData.Country != null) { _logger.Detail(extractedPartnerData.Country); } if (extractedPartnerData.State != null) { _logger.Detail(extractedPartnerData.State); } } // - 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()}_{file.FileName}"; var filePath = Path.Combine(uploadsPath, fileName); // Save file using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } } 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 aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( pdfText.ToString(), "You work for FruitBank. Extract the following information from this shipping document and return as JSON: documentDate, recipientName, senderName, invoiceNumber, totalAmount, itemCount, notes. If a field is not found, return null for that field." ); // Parse AI response (assuming it returns JSON) 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."); } } //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!"); } //[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 ExtractedDocumentData ParseShippingDocumentAIResponse(string aiResponse) { try { // Try to parse as JSON first var data = System.Text.Json.JsonSerializer.Deserialize( aiResponse, new System.Text.Json.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 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 } ); 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 ExtractedPartnerData ParsePartnerDataAIResponse(string aiResponse) { try { // Try to parse as JSON first var data = System.Text.Json.JsonSerializer.Deserialize( aiResponse, new System.Text.Json.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)) { System.IO.File.Delete(fullPath); return Json(new { success = true }); } return Json(new { success = false, message = "File not found" }); } catch (Exception ex) { return Json(new { success = false, message = ex.Message }); } } } public class UploadModel { public int ShippingDocumentId { get; set; } public List Files { get; set; } } }