using DevExtreme.AspNet.Data; using DevExtreme.AspNet.Mvc; using FruitBank.Common.Entities; 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.Helpers; using Nop.Plugin.Misc.FruitBankPlugin.Services; using Nop.Services.Messages; using Nop.Services.Security; using Nop.Web.Areas.Admin.Controllers; using Nop.Web.Areas.Admin.Models.Orders; using Nop.Web.Framework; using Nop.Web.Framework.Models; using Nop.Web.Framework.Models.Extensions; using Nop.Web.Framework.Mvc.Filters; using System.Text; namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers { [Area(AreaNames.ADMIN)] [AuthorizeAdmin] public class ShippingController : BaseAdminController { private readonly IPermissionService _permissionService; protected readonly INotificationService _notificationService; protected readonly ShippingItemDbTable _shippingItemDbTable; protected readonly ShippingDbTable _shippingDbTable; protected readonly ShippingDocumentDbTable _shippingDocumentDbTable; protected readonly FruitBankDbContext _dbContext; protected readonly AICalculationService _aiCalculationService; public ShippingController(IPermissionService permissionService, INotificationService notificationService, ShippingItemDbTable shippingItemDbTable, ShippingDbTable shippingDbTable, ShippingDocumentDbTable shippingDocumentDbTable, FruitBankDbContext dbContext, AICalculationService aICalculationService) { _permissionService = permissionService; _notificationService = notificationService; _shippingItemDbTable = shippingItemDbTable; _shippingDbTable = shippingDbTable; _shippingDocumentDbTable = shippingDocumentDbTable; _dbContext = dbContext; //_shippingModelFactory = shippingModelFactory; _aiCalculationService = aICalculationService; } [HttpGet] public async Task List() { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); // Create model and load data var model = new ShippingListModel(); // TODO: Replace with your actual service call // model.ShippingList = await _shippingService.GetAllShippingsAsync(); // Mock data for now model.ShippingList = _dbContext.Shippings.GetAll(true).ToList(); var valami = model; //model. = await _dbContext.GetShippingDocumentsByShippingIdAsync(shippingId); return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipping/List.cshtml", model); } [HttpPost] public async Task List(ShippingListModel model) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); // Apply filters to mock data model.ShippingList = _shippingDbTable.GetAll().ToList(); return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipping/List.cshtml", model); } [HttpGet] public async Task ShippingDocumentList() { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); // Create model and load data //var model = _shippingDocumentDbTable.GetAll().ToList(); var model = _dbContext.ShippingDocuments.GetAll(true).ToList(); // TODO: Replace with your actual service call // model.ShippingList = await _shippingService.GetAllShippingsAsync(); // Mock data for now return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipping/List.cshtml", 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 View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipping/List.cshtml", model); } //TODO IMPLEMENT DEVEXTREME FILETRING AND LOADING //[HttpGet] //public async Task GetDocumentByShippingId(int shippingId, DataSourceLoadOptions loadOptions) //{ // // 1. Get the IQueryable from your service. // // This is the key step. It's more efficient to work with IQueryable // // so that filtering, sorting, and paging are done by the database. // var documentQuery = await _dbContext.GetShippingDocumentsByShippingIdAsync(shippingId); // // 2. Process the data using DevExtreme.AspNet.Data. // // This is where all the magic happens. The Load method takes care of // // applying all the grid's client-side settings (sorting, filtering, // // grouping, paging) to your data query. // var loadResult = DataSourceLoader.Load(documentQuery, loadOptions); // // 3. Return the result in the format DevExtreme expects. // return Json(loadResult); //} [HttpGet] public async Task GetShippingDocumentsByShippingId(int shippingId) { var result = await _dbContext.GetShippingDocumentsByShippingIdAsync(shippingId); // 3. Return the result in the format DevExtreme expects. return Json(result); } [HttpPost] public async Task Delete(int id) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, message = "Access denied" }); try { // TODO: Implement actual deletion return Json(new { success = true, message = "Shipment deleted successfully" }); } catch (Exception ex) { return Json(new { success = false, message = ex.Message }); } } [HttpGet] public async Task Create() { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); var model = new CreateShippingModel { ShippingDate = DateTime.Now }; return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipping/Create.cshtml", model); } [HttpPost] public async Task Create(CreateShippingModel model) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); if (!ModelState.IsValid) { return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipping/Create.cshtml", model); } string licencePlate = ""; if (model.LicencePlate.Length > 3) { licencePlate = model.LicencePlate?.Trim().ToUpper() ?? string.Empty; } try { var shipment = new Shipping { LicencePlate = licencePlate, ShippingDate = model.ShippingDate, IsAllMeasured = false, }; await _shippingDbTable.InsertAsync(shipment); _notificationService.SuccessNotification($"Shipment created successfully. You can now upload documents. Shipping Id: {shipment.Id}"); // Redirect to Edit action where user can upload files return RedirectToAction("Edit", new { id = shipment.Id }); } catch (Exception ex) { _notificationService.ErrorNotification($"Error creating shipment: {ex.Message}"); return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipping/Create.cshtml", model); } } [HttpGet] public async Task Edit(int id) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); try { // TODO: Load shipment from database var shipment = await _shippingDbTable.GetByIdAsync(id); if (shipment == null) return RedirectToAction("List"); // TODO: Load existing documents // var documents = await _documentService.GetShipmentDocumentsAsync(id); // For now, create a mock model var model = new EditShippingModel { Id = shipment.Id, ShippingDate = shipment.ShippingDate, // Replace with: shipment.ShipmentDate LicencePlate = shipment.LicencePlate, // Replace with: shipment.TrackingNumber ExistingDocuments = new List() // Replace with: documents.Select(d => new ShipmentDocumentModel { ... }).ToList() }; return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipping/Edit.cshtml", model); } catch (Exception ex) { _notificationService.ErrorNotification($"Error loading shipment: {ex.Message}"); return RedirectToAction("List"); } } [HttpPost] public async Task Edit(EditShippingModel model) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return AccessDeniedView(); if (!ModelState.IsValid) { // Reload existing documents if validation fails // model.ExistingDocuments = await LoadExistingDocuments(model.Id); return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipping/Edit.cshtml", model); } try { // TODO: Update shipment in database // var shipment = await _shipmentService.GetShipmentByIdAsync(model.Id); // shipment.Name = model.ShipmentName; // shipment.Description = model.Description; // shipment.ShipmentDate = model.ShipmentDate; // shipment.TrackingNumber = model.TrackingNumber; // await _shipmentService.UpdateShippingAsync(shipment); _notificationService.SuccessNotification("Shipment updated successfully"); return RedirectToAction("Edit", new { id = model.Id }); } catch (Exception ex) { _notificationService.ErrorNotification($"Error updating shipment: {ex.Message}"); return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Shipping/Edit.cshtml", model); } } [HttpPost] [RequestSizeLimit(10485760)] // 10MB [RequestFormLimits(MultipartBodyLengthLimit = 10485760)] public async Task UploadFile(IFormFile file, int shippingId) { try { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, errorMessage = "Access denied" }); if (file == null || file.Length == 0) return Json(new { success = false, errorMessage = "No file selected" }); // Validate file type (PDF only) if (!file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)) return Json(new { success = false, errorMessage = "Only PDF files are allowed" }); // Validate file size (max 10MB) if (file.Length > 10 * 1024 * 1024) return Json(new { success = false, errorMessage = "File size must be less than 10MB" }); // Create upload directory if it doesn't exist var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "shippingDocuments"); Directory.CreateDirectory(uploadsPath); // Generate unique filename var 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); } // Extract text with PdfPig var pdfText = new StringBuilder(); using (var pdf = UglyToad.PdfPig.PdfDocument.Open(filePath)) { foreach (var page in pdf.GetPages()) { pdfText.AppendLine(page.Text); } } // Log extracted text for debugging Console.WriteLine("Extracted PDF text:"); Console.WriteLine(pdfText.ToString()); // Analyze PDF with AI to extract structured data var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( pdfText.ToString(), "Extract the following information from this shipping document and return as JSON: documentDate, recipientName, senderName, invoiceNumber, totalAmount, itemCount, notes. If a field is not found, return null for that field." ); // Parse AI response (assuming it returns JSON) var extractedData = ParseShippingDocumentAIResponse(aiAnalysis); //TODO: Save document record to database Console.WriteLine("AI Analysis Result:"); Console.WriteLine(extractedData.RecipientName); Console.WriteLine(extractedData.SenderName); Console.WriteLine(extractedData.InvoiceNumber); Console.WriteLine(extractedData.TotalAmount); Console.WriteLine(extractedData.ItemCount); Console.WriteLine(extractedData.Notes); // var savedDocument = await _documentService.InsertDocumentAsync(document); // Mock saved document ID var documentId = 1; // Replace with: savedDocument.Id // Return structured document model var documentModel = new ShippingDocumentModel { Id = documentId, ShippingId = shippingId, 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 Json(new { success = true, document = documentModel }); } catch (Exception ex) { Console.WriteLine($"Error uploading file: {ex}"); return Json(new { success = false, errorMessage = ex.Message }); } } 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}" }; } } // 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 }); } } [HttpPost] public async Task DeleteDocument(int documentId) { try { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new DocumentOperationResult { Success = false, Message = "Access denied" }); // TODO: Implement document deletion // var document = await _documentService.GetDocumentByIdAsync(documentId); // if (document == null) // return Json(new DocumentOperationResult { Success = false, Message = "Document not found" }); // Delete physical file // var fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", document.FilePath.TrimStart('/')); // if (System.IO.File.Exists(fullPath)) // System.IO.File.Delete(fullPath); // Delete database record // await _documentService.DeleteDocumentAsync(document); return Json(new DocumentOperationResult { Success = true, Message = "Document deleted successfully", DocumentId = documentId }); } catch (Exception ex) { return Json(new DocumentOperationResult { Success = false, Message = ex.Message }); } } // Helper method for loading existing documents (to be implemented) // private async Task> LoadExistingDocuments(int shipmentId) // { // var documents = await _documentService.GetShipmentDocumentsAsync(shipmentId); // return documents.Select(d => new ShipmentDocumentModel // { // Id = d.Id, // ShipmentId = d.ShipmentId, // FileName = d.FileName, // FilePath = d.FilePath, // FileSize = d.FileSize, // UploadDate = d.UploadDate, // ContentType = d.ContentType, // IsActive = d.IsActive // }).ToList(); // } } }