This commit is contained in:
Adam 2025-11-06 10:02:34 +01:00
commit c561c14359
6 changed files with 362 additions and 41 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
using AyCode.Core.Loggers;
using AyCode.Core.Extensions;
using AyCode.Core.Loggers;
using AyCode.Services.SignalRs;
using DocumentFormat.OpenXml.Office2010.Excel;
using FruitBank.Common.Dtos;
@ -17,6 +18,7 @@ using Nop.Core;
using Nop.Core.Domain.Customers;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Factories;
using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Services.Customers;
using Nop.Services.Localization;
using Nop.Web.Framework.Controllers;
@ -27,6 +29,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
//https://linq2db.github.io/articles/sql/Join-Operators.html
public class FruitBankDataController(
FruitBankDbContext ctx,
MeasurementService measurementService,
IWorkContext workContext,
ICustomerService customerService,
ICustomerRegistrationService customerRegistrationService,
@ -36,6 +39,11 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
{
private readonly ILogger _logger = new Logger<FruitBankDataController>(logWriters.ToArray());
[SignalR(SignalRTags.ProcessAndSaveFullShippingJson)]
public async Task<List<Partner>> ProcessAndSaveFullShippingJson(string fullShippingJson, int customerId)
{
return await measurementService.ProcessAndSaveFullShippingJson(fullShippingJson, customerId);
}
[SignalR(SignalRTags.GetMeasuringModels)]
public Task<List<MeasuringModel>> GetMeasuringModels()
@ -127,6 +135,17 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
return shippingItem;
}
[SignalR(SignalRTags.AddShippingItem)]
public async Task<ShippingItem> AddShippingItem(ShippingItem shippingItem)
{
ArgumentNullException.ThrowIfNull(shippingItem);
_logger.Detail($"AddShippingItem invoked; id: {shippingItem.Id}");
if (!await ctx.AddShippingItemAsync(shippingItem)) return null;
return await ctx.ShippingItems.GetByIdAsync(shippingItem.Id, shippingItem.ShippingDocument != null);
}
[SignalR(SignalRTags.UpdateShippingItem)]
public async Task<ShippingItem> UpdateShippingItem(ShippingItem shippingItem)
{
@ -218,6 +237,17 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
return await ctx.ShippingDocuments.GetByIdAsync(id, true);
}
[SignalR(SignalRTags.AddShippingDocument)]
public async Task<ShippingDocument> AddShippingDocument(ShippingDocument shippingDocument)
{
ArgumentNullException.ThrowIfNull(shippingDocument);
_logger.Detail($"AddShippingDocument invoked; id: {shippingDocument.Id}");
await ctx.ShippingDocuments.InsertAsync(shippingDocument);
return await ctx.ShippingDocuments.GetByIdAsync(shippingDocument.Id, shippingDocument.Shipping != null || shippingDocument.Partner != null);
}
[SignalR(SignalRTags.UpdateShippingDocument)]
public async Task<ShippingDocument> UpdateShippingDocument(ShippingDocument shippingDocument)
{

View File

@ -152,6 +152,25 @@ public class FruitBankDbContext : MgDbContextBase,
//public async Task<MeasuringAttributeValues?> GetMeasuringAttributeValuesByProductIdAsync(int productId)
// => await _fruitBankAttributeService.GetMeasuringAttributeValuesAsync<Product>(productId);
//public async Task<List<Partner>> ProcessAndSaveFullShippingDocumentJson(string fullShippingDocumentJson)
//{
// var partners = fullShippingDocumentJson.JsonTo<List<Partner>>();
// if (partners != null)
// {
// foreach (var partner in partners)
// {
// //await Partners.InsertAsync(partner);
// if (partner.ShippingDocuments == null) continue;
// foreach (var shippingDocument in partner.ShippingDocuments)
// {
// await ShippingDocuments.InsertAsync(shippingDocument);
// }
// }
// }
//}
public async Task DeleteShippingSafeAsync(Shipping shipping)
{
await TransactionSafeAsync(async _ =>
@ -186,6 +205,14 @@ public class FruitBankDbContext : MgDbContextBase,
Logger.Error("shippingItem.IsMeasurable && !shippingItem.IsValidMeasuringValues()");
return Task.FromResult(false);
}
public Task<bool> AddShippingItemSafeAsync(ShippingItem shippingItem)
=> TransactionSafeAsync(async _ => await AddShippingItemAsync(shippingItem));
public async Task<bool> AddShippingItemAsync(ShippingItem shippingItem)
{
await ShippingItems.InsertAsync(shippingItem);
return true;
}
public Task<bool> UpdateShippingItemSafeAsync(ShippingItem shippingItem)
=> TransactionSafeAsync(async _ => await UpdateShippingItemAsync(shippingItem));
@ -268,7 +295,7 @@ public class FruitBankDbContext : MgDbContextBase,
}
//if (productIdUnchanged || !dbShippingItem.IsMeasured) return true;
if (!productIdChanged && (shippingItem.IsMeasured || !dbShippingItem.IsMeasured)) return true;
if (!dbShippingItem.ProductId.HasValue || (!productIdChanged && (shippingItem.IsMeasured || !dbShippingItem.IsMeasured))) return true;
productDto = await ProductDtos.GetByIdAsync(dbShippingItem.ProductId!.Value, true);
@ -460,6 +487,22 @@ public class FruitBankDbContext : MgDbContextBase,
return orderDto;
}
public async Task<bool> SetOrderStatusToPendingSafeAsync(Order order)
=> await TransactionSafeAsync(async _ => await SetOrderStatusToPendingAsync(order));
public async Task<bool> SetOrderStatusToPendingAsync(Order order)
{
if (order.OrderStatus == OrderStatus.Pending) return true;
var prevOrderStatus = order.OrderStatus;
order.OrderStatus = OrderStatus.Pending;
await Orders.UpdateAsync(order, false);
await _eventPublisher.PublishAsync(new OrderStatusChangedEvent(order, prevOrderStatus));
return true;
}
public Task DeleteOrderItemConstraintsSafeAsync(OrderItem orderItem, bool publishEvent = false)
{
return TransactionSafeAsync(async _ =>

View File

@ -27,10 +27,12 @@
<PackageReference Include="Microsoft.AspNetCore.SignalR.Common" Version="9.0.10" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.Json" Version="9.0.10" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />
<!--<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />-->
<PackageReference Include="PdfPig" Version="0.1.11" />
<PackageReference Include="PdfPig.Rendering.Skia" Version="0.1.11.5" />
<PackageReference Include="SendGrid" Version="9.29.3" />
<PackageReference Include="System.Memory.Data" Version="9.0.10" />
<PackageReference Include="Tesseract" Version="5.2.0" />
<PackageReference Include="TesseractOCR" Version="5.5.1" />
</ItemGroup>

View File

@ -1,14 +1,21 @@
using AyCode.Core.Loggers;
using AyCode.Core.Extensions;
using AyCode.Core.Loggers;
using AyCode.Services.Server.SignalRs;
using FruitBank.Common.Dtos;
using FruitBank.Common.Entities;
using FruitBank.Common.Interfaces;
using FruitBank.Common.Server.Services.SignalRs;
using FruitBank.Common.Services;
using Mango.Nop.Core.Extensions;
using Mango.Nop.Core.Loggers;
using Microsoft.CodeAnalysis.Operations;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Orders;
using Nop.Core.Events;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Services.Catalog;
using Nop.Services.Events;
namespace Nop.Plugin.Misc.FruitBankPlugin.Services;
@ -16,18 +23,21 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services;
public class MeasurementService : MeasurementServiceBase<Logger>, IMeasurementService
{
private readonly FruitBankDbContext _dbContext;
private readonly IEventPublisher _eventPublisher;
private readonly FruitBankAttributeService _fruitBankAttributeService;
private readonly SignalRSendToClientService _signalRSendToClientService;
private readonly CustomPriceCalculationService _customPriceCalculationService;
public MeasurementService(FruitBankDbContext dbContext, SignalRSendToClientService signalRSendToClientService, FruitBankAttributeService fruitBankAttributeService,
IPriceCalculationService customPriceCalculationService, IEnumerable<IAcLogWriterBase> logWriters) : base(new Logger<MeasurementService>(logWriters.ToArray()))
IPriceCalculationService customPriceCalculationService, IEventPublisher eventPublisher, IEnumerable<IAcLogWriterBase> logWriters) : base(new Logger<MeasurementService>(logWriters.ToArray()))
{
_dbContext = dbContext;
_eventPublisher = eventPublisher;
_fruitBankAttributeService = fruitBankAttributeService;
_signalRSendToClientService = signalRSendToClientService;
_customPriceCalculationService = (CustomPriceCalculationService)customPriceCalculationService;
}
public async Task DeleteOrderItemConstraintsAsync(int orderItemId) => await DeleteOrderItemConstraintsAsync(await _dbContext.OrderItems.GetByIdAsync(orderItemId));
public async Task DeleteOrderItemConstraintsAsync(OrderItem orderItem)
@ -56,4 +66,82 @@ public class MeasurementService : MeasurementServiceBase<Logger>, IMeasurementSe
await _customPriceCalculationService.CheckAndUpdateOrderTotalPrice(order);
}
}
public async Task<bool> OrderItemMeasuringReset(int orderItemId)
=> await OrderItemMeasuringReset(await _dbContext.OrderItems.GetByIdAsync(orderItemId));
public async Task<bool> OrderItemMeasuringReset(OrderItem orderItem)
{
var order = await _dbContext.Orders.GetByIdAsync(orderItem.OrderId);
var orderItemPallets = await _dbContext.OrderItemPallets.GetAllByOrderItemId(orderItem.Id, false).ToListAsync();
var result = await _dbContext.TransactionSafeAsync(async _ =>
{
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Order, int>(order.Id, nameof(IOrderDto.RevisorId), 0);
foreach (var orderItemPallet in orderItemPallets)
{
orderItemPallet.RevisorId = 0;
await _dbContext.OrderItemPallets.UpdateAsync(orderItemPallet);
}
if (order.OrderStatus == OrderStatus.Complete)
await _dbContext.SetOrderStatusToPendingAsync(order);
return true;
});
if (!result) return result;
foreach (var orderItemPallet in orderItemPallets)
await _signalRSendToClientService.SendOrderItemPalletChanged(orderItemPallet);
await _signalRSendToClientService.SendOrderChanged(await _dbContext.OrderDtos.GetByIdAsync(orderItem.OrderId, true));
return result;
}
public async Task<List<Partner>?> ProcessAndSaveFullShippingJson(string fullShippingJson, int customerId)
{
var partners = fullShippingJson.JsonTo<List<Partner>>();
if (partners == null || partners.Count == 0) return partners;
var a = partners.SelectMany(x => x.ShippingDocuments?.SelectMany(sd => sd.ShippingItems?.Where(si => si.ProductId.GetValueOrDefault(0) > 0).Select(si => si.ProductId!.Value) ?? []) ?? []).ToHashSet();
var productDtosById = await _dbContext.ProductDtos.GetAllByIds(a, false, false).ToDictionaryAsync(k => k.Id, v => v);
var result = await _dbContext.TransactionSafeAsync(async _ =>
{
foreach (var partner in partners)
{
//await _dbContext.Partners.InsertAsync(partner, false);
if (partner.ShippingDocuments == null) continue;
foreach (var shippingDocument in partner.ShippingDocuments)
{
//shippingDocument.PartnerId = 0;
await _dbContext.ShippingDocuments.InsertAsync(shippingDocument, false);
if (shippingDocument.ShippingItems == null) continue;
foreach (var shippingItem in shippingDocument.ShippingItems)
{
if (shippingItem.ProductId != null && productDtosById.TryGetValue(shippingItem.ProductId.Value, out var productDto))
{
shippingItem.Name = productDto.Name;
shippingItem.IsMeasurable = productDto.IsMeasurable;
//TODO: Update Product Incoming attribute! - J.
}
else shippingItem.ProductId = null;
shippingItem.ShippingDocumentId = shippingDocument.Id;
await _dbContext.ShippingItems.InsertAsync(shippingItem, false);
}
}
}
return true;
});
return result ? partners : null;
}
}

View File

@ -312,6 +312,34 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
_assistantId ??= await FindOrCreateAssistantAsync("PDF and Image Analyzer Assistant");
}
public async Task CleanupAllVectorStoresAsync()
{
Console.WriteLine("Cleaning up all existing vector stores...");
var listRequest = new HttpRequestMessage(HttpMethod.Get, $"{BaseUrl}/vector_stores");
listRequest.Headers.Add("OpenAI-Beta", "assistants=v2");
var response = await _httpClient.SendAsync(listRequest);
if (response.IsSuccessStatusCode)
{
using var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
var vectorStores = json.RootElement.GetProperty("data");
foreach (var vectorStore in vectorStores.EnumerateArray())
{
var id = vectorStore.GetProperty("id").GetString();
var name = vectorStore.TryGetProperty("name", out var nameElement) && nameElement.ValueKind != JsonValueKind.Null
? nameElement.GetString()
: "Unnamed";
var deleteRequest = new HttpRequestMessage(HttpMethod.Delete, $"{BaseUrl}/vector_stores/{id}");
deleteRequest.Headers.Add("OpenAI-Beta", "assistants=v2");
await _httpClient.SendAsync(deleteRequest);
Console.WriteLine($"Deleted vector store: {name} ({id})");
}
Console.WriteLine("Vector store cleanup complete!");
}
}
//TEMPORARY: Cleanup all assistants (for testing purposes) - A.
public async Task CleanupAllAssistantsAsync()
{
@ -347,8 +375,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
public async Task<string?> AnalyzePdfAsync(Stream file, string fileName, string userPrompt)
{
await EnsureAssistantAndVectorStoreAsync();
var fileId = await UploadFileAsync(file, fileName);
var isImage = IsImageFile(fileName);
@ -640,7 +668,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
var assistantBody = new
{
name = name,
instructions = "You are an assistant that analyzes uploaded files. When you receive an image, analyze and describe what you see in the image in detail. When you receive a PDF or text document, use the file_search tool to find and analyze relevant information. Always respond directly to the user's question about the file they uploaded.",
instructions = "You are an assistant that analyzes uploaded files. When you receive an image, analyze and describe what you see in the image in detail. When you receive a PDF or text document, use the file_search tool to find and analyze relevant information. Always respond directly according to the user's instructions about the current file they upload.",
model = "gpt-4o",
tools = new[] { new { type = "file_search" } }
};