911 lines
41 KiB
C#
911 lines
41 KiB
C#
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 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 CustomPriceCalculationService _customPriceCalculationService;
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
public ManagementPageController(IPermissionService permissionService, IPriceCalculationService customPriceCalculationService, FruitBankDbContext fruitBankDbContext, AICalculationService aiCalculationService, OpenAIApiService openAIApiService, IEnumerable<IAcLogWriterBase> logWriters)
|
|
{
|
|
_logger = new Logger<ManagementPageController>(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<IActionResult> Test()
|
|
{
|
|
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
|
|
return AccessDeniedView();
|
|
|
|
//await OrderTotalsFix();
|
|
|
|
var testPageModel = new TestPageModel();
|
|
testPageModel.Grids = new List<TestGridModel>();
|
|
|
|
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<TestGridModel>();
|
|
var childGrid1 = new TestGridModel
|
|
{
|
|
GridName = "TestGrid",
|
|
ViewComponentName = "TestGridComponent",
|
|
ViewComponentLocation = "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/TestGridComponent.cshtml",
|
|
ParentGridId = testGridModel2.Id,
|
|
ChildGrids = new List<TestGridModel>()
|
|
};
|
|
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<TestGridModel>()
|
|
};
|
|
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<string, int>();
|
|
|
|
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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> ProcessShippingDocument(int shippingDocumentId)
|
|
{
|
|
return Ok("Ok");
|
|
}
|
|
|
|
|
|
[HttpPost]
|
|
[RequestSizeLimit(10485760)] // 10MB
|
|
[RequestFormLimits(MultipartBodyLengthLimit = 10485760)]
|
|
public async Task<IActionResult> UploadFile(List<IFormFile> files, int shippingDocumentId, int? partnerId)
|
|
{
|
|
|
|
//an empty shippingdocument created by the user
|
|
var shippingDocument = await _dbContext.ShippingDocuments.GetByIdAsync(shippingDocumentId);
|
|
|
|
|
|
|
|
ShippingDocumentAnalysisResult shippingDocumentAnalysisResult = new ShippingDocumentAnalysisResult();
|
|
shippingDocumentAnalysisResult.Partner = new Partner();
|
|
shippingDocumentAnalysisResult.ShippingDocument = shippingDocument;
|
|
shippingDocumentAnalysisResult.ShippingItems = new List<ShippingItem>();
|
|
shippingDocumentAnalysisResult.ShippingDocument.ShippingDocumentToFiles = new List<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<Files>();
|
|
|
|
|
|
//iteratation 1: iterate documents to determine their type by AI
|
|
|
|
|
|
for (int i = 0; i < files.Count; i++)
|
|
{
|
|
|
|
var fileName = files[i].FileName;
|
|
var fileSize = files[i].Length;
|
|
var dbFile = new Files();
|
|
string pdfText = "";
|
|
|
|
_logger.Detail($"Received file: {fileName} for Document ID: {shippingDocumentId}, content type: {files[i].ContentType}");
|
|
|
|
if (!files[i].ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase) && !files[i].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)
|
|
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 (files[i].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))
|
|
{
|
|
// 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, 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}");
|
|
}
|
|
}
|
|
|
|
// For demonstration, let's just log the extracted text
|
|
_logger.Detail($"Extracted text from {files[i].FileName}: {pdfText}");
|
|
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Handle potential exceptions during PDF processing
|
|
Console.Error.WriteLine($"Error processing PDF file {files[i].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 = files[i].OpenReadStream())
|
|
{
|
|
|
|
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}");
|
|
|
|
}
|
|
}
|
|
|
|
catch (Exception ex)
|
|
{
|
|
// Handle potential exceptions during PDF processing
|
|
Console.Error.WriteLine($"Error processing PDF file {files[i].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);
|
|
bool transactionSuccess = await SaveFileInfoToDB(shippingDocumentId, shippingDocumentAnalysisResult, filesList, dbFile, pdfText, extractedMetaData);
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
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<Partner>(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<ShippingDocumentDto>(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<List<ShippingItem>>(CleanJsonResponse(itemsResponse),
|
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new List<ShippingItem>();
|
|
|
|
// 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
|
|
};
|
|
|
|
//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.");
|
|
//}
|
|
|
|
}
|
|
|
|
|
|
|
|
//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!");
|
|
}
|
|
|
|
private async Task<bool> SaveFileInfoToDB(int shippingDocumentId, ShippingDocumentAnalysisResult shippingDocumentAnalysisResult, List<Files> filesList, Files dbFile, string pdfText, ExtractedDocumentMetaData extractedMetaData)
|
|
{
|
|
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());
|
|
shippingDocumentAnalysisResult.ShippingDocument.ShippingDocumentToFiles.Add(shippingDocumentToFiles);
|
|
await _dbContext.ShippingDocumentToFiles.InsertAsync(shippingDocumentToFiles);
|
|
return true;
|
|
});
|
|
return transactionSuccess;
|
|
}
|
|
|
|
|
|
//[HttpPost]
|
|
//public async Task<IActionResult> UploadFile(List<IFormFile> 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<ShippingItem> 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 ExtractedDocumentData ParseShippingDocumentAIResponse(string aiResponse)
|
|
{
|
|
try
|
|
{
|
|
// Try to parse as JSON first
|
|
var data = System.Text.Json.JsonSerializer.Deserialize<ExtractedDocumentData>(
|
|
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<ExtractedDocumentMetaData>(
|
|
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<ExtractedPartnerData>(
|
|
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<IActionResult> 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<IFormFile> Files { get; set; }
|
|
}
|
|
} |