Rendelések feldarabolása mérési státusz alapján, és szabad szétválasztás, deign update, adószám bug-fix, szállítás alatt mező automatikus kitöltése AI által termékben, átlagsúly kiszámítása és beírása termékbe bevételezéskor. AI üdvözlő szöveg alapvető elemzéssel.
This commit is contained in:
parent
889c368a46
commit
98b1ba9b22
|
|
@ -1454,11 +1454,11 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[CheckPermission(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE)]
|
||||
public async Task<IActionResult> SplitOrder(int orderId)
|
||||
public async Task<IActionResult> SplitOrder(int orderId, string mode = "audit", string orderItemIds = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info($"SplitOrder - OrderId: {orderId} - STARTED");
|
||||
_logger.Info($"SplitOrder - OrderId: {orderId}, Mode: {mode}, OrderItemIds: {orderItemIds} - STARTED");
|
||||
|
||||
var order = await _orderService.GetOrderByIdAsync(orderId);
|
||||
if (order == null || order.Deleted)
|
||||
|
|
@ -1496,43 +1496,72 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
});
|
||||
}
|
||||
|
||||
// SAFETY CHECK: Don't allow splitting if measuring hasn't started
|
||||
if (orderDto.MeasuringStatus == MeasuringStatus.NotStarted)
|
||||
{
|
||||
_logger.Warning($"SplitOrder - Cannot split order {orderId} that hasn't been started");
|
||||
return Json(new
|
||||
{
|
||||
success = false,
|
||||
message = "Ez a rendelés még nem lett elkezdve, nem választható szét!"
|
||||
});
|
||||
}
|
||||
// REMOVED: NotStarted check - we allow splitting at any stage except Audited
|
||||
// Manual mode is always available, audit mode is controlled by the UI
|
||||
|
||||
_logger.Info($"SplitOrder - OrderDto found, separating items. Total items: {orderDto.OrderItemDtos.Count}, MeasuringStatus: {orderDto.MeasuringStatus}");
|
||||
|
||||
// Separate audited and non-audited items
|
||||
var auditedItems = orderDto.OrderItemDtos.Where(oi => oi.IsAudited).ToList();
|
||||
var nonAuditedItems = orderDto.OrderItemDtos.Where(oi => !oi.IsAudited).ToList();
|
||||
List<OrderItemDto> itemsToMove;
|
||||
|
||||
_logger.Info($"SplitOrder - Audited items: {auditedItems.Count}, Non-audited items: {nonAuditedItems.Count}");
|
||||
|
||||
if (nonAuditedItems.Count == 0)
|
||||
if (mode == "manual")
|
||||
{
|
||||
_logger.Warning($"SplitOrder - No non-audited items in order {orderId}");
|
||||
return Json(new
|
||||
// Manual mode - use provided order item IDs
|
||||
if (string.IsNullOrWhiteSpace(orderItemIds))
|
||||
{
|
||||
success = false,
|
||||
message = "Nincs nem auditált termék a rendelésben. Szétválasztás nem szükséges."
|
||||
});
|
||||
_logger.Warning($"SplitOrder - Manual mode selected but no order item IDs provided");
|
||||
return Json(new { success = false, message = "Nem lettek termékek kiválasztva" });
|
||||
}
|
||||
|
||||
var selectedIds = orderItemIds.Split(',')
|
||||
.Where(id => !string.IsNullOrWhiteSpace(id))
|
||||
.Select(id => int.Parse(id.Trim()))
|
||||
.ToList();
|
||||
|
||||
if (selectedIds.Count == 0)
|
||||
{
|
||||
_logger.Warning($"SplitOrder - No valid order item IDs provided");
|
||||
return Json(new { success = false, message = "Nem lettek érvényes termékek kiválasztva" });
|
||||
}
|
||||
|
||||
if (selectedIds.Count == orderDto.OrderItemDtos.Count)
|
||||
{
|
||||
_logger.Warning($"SplitOrder - All items selected for move");
|
||||
return Json(new { success = false, message = "Legalább egy terméknek maradnia kell az eredeti rendelésben" });
|
||||
}
|
||||
|
||||
itemsToMove = orderDto.OrderItemDtos.Where(oi => selectedIds.Contains(oi.Id)).ToList();
|
||||
|
||||
_logger.Info($"SplitOrder - Manual mode: {itemsToMove.Count} items selected to move out of {orderDto.OrderItemDtos.Count}");
|
||||
}
|
||||
|
||||
if (auditedItems.Count == 0)
|
||||
else
|
||||
{
|
||||
_logger.Warning($"SplitOrder - All items are non-audited in order {orderId}");
|
||||
return Json(new
|
||||
// Audit mode - separate by measuring status (started vs not started)
|
||||
// Items with MeasuringStatus > NotStarted stay in original order
|
||||
// Items with MeasuringStatus = NotStarted move to new order
|
||||
var startedItems = orderDto.OrderItemDtos.Where(oi => oi.MeasuringStatus > MeasuringStatus.NotStarted).ToList();
|
||||
itemsToMove = orderDto.OrderItemDtos.Where(oi => oi.MeasuringStatus == MeasuringStatus.NotStarted).ToList();
|
||||
|
||||
_logger.Info($"SplitOrder - Audit mode: Started/Audited items: {startedItems.Count}, Not started items: {itemsToMove.Count}");
|
||||
|
||||
if (itemsToMove.Count == 0)
|
||||
{
|
||||
success = false,
|
||||
message = "Minden termék nem auditált. Szétválasztás nem szükséges."
|
||||
});
|
||||
_logger.Warning($"SplitOrder - No not-started items in order {orderId}");
|
||||
return Json(new
|
||||
{
|
||||
success = false,
|
||||
message = "Nincs nem elindított termék a rendelésben. Szétválasztás nem szükséges."
|
||||
});
|
||||
}
|
||||
|
||||
if (startedItems.Count == 0)
|
||||
{
|
||||
_logger.Warning($"SplitOrder - All items are not-started in order {orderId}");
|
||||
return Json(new
|
||||
{
|
||||
success = false,
|
||||
message = "Minden termék még nem lett elindítva. Használja a kézi módot."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info($"SplitOrder - Getting customer, store, and admin");
|
||||
|
|
@ -1542,9 +1571,9 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
var admin = await _workContext.GetCurrentCustomerAsync();
|
||||
|
||||
_logger.Info($"SplitOrder - Customer: {customer?.Id}, Store: {store?.Id}, Admin: {admin?.Id}");
|
||||
_logger.Info($"SplitOrder - Creating new order (no transaction)");
|
||||
_logger.Info($"SplitOrder - Creating new order");
|
||||
|
||||
// Create new order for non-audited items
|
||||
// Create new order for items to move
|
||||
var newOrder = new Order
|
||||
{
|
||||
OrderGuid = Guid.NewGuid(),
|
||||
|
|
@ -1582,10 +1611,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
|
||||
var orderItemsToMove = new List<OrderItem>();
|
||||
|
||||
// Find order items to move based on non-audited OrderItemDtos
|
||||
foreach (var nonAuditedDto in nonAuditedItems)
|
||||
// Find order items to move based on itemsToMove DTOs
|
||||
foreach (var itemDto in itemsToMove)
|
||||
{
|
||||
var orderItemToMove = originalOrderItems.FirstOrDefault(oi => oi.Id == nonAuditedDto.Id);
|
||||
var orderItemToMove = originalOrderItems.FirstOrDefault(oi => oi.Id == itemDto.Id);
|
||||
if (orderItemToMove != null)
|
||||
{
|
||||
orderItemsToMove.Add(orderItemToMove);
|
||||
|
|
@ -1594,7 +1623,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
|
||||
_logger.Info($"SplitOrder - Found {orderItemsToMove.Count} items to move");
|
||||
|
||||
// Move non-audited items to new order
|
||||
// Move items to new order
|
||||
foreach (var orderItem in orderItemsToMove)
|
||||
{
|
||||
_logger.Info($"SplitOrder - Processing order item {orderItem.Id}");
|
||||
|
|
@ -1675,20 +1704,22 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
|
||||
_logger.Info($"SplitOrder - Adding order notes");
|
||||
|
||||
var splitModeText = mode == "manual" ? "kézi kiválasztással" : "mérési státusz alapján";
|
||||
|
||||
// Add notes to both orders
|
||||
await InsertOrderNoteAsync(
|
||||
order.Id,
|
||||
false,
|
||||
$"Rendelés szétválasztva. Nem auditált termékek átkerültek a #{newOrder.Id} rendelésbe. Művelet végrehajtója: {admin.FirstName} {admin.LastName} (ID: {admin.Id})"
|
||||
$"* Rendelés szétválasztva ({splitModeText}). {orderItemsToMove.Count} termék átkerült a #{newOrder.Id} rendelésbe. Művelet végrehajtója: {admin.FirstName} {admin.LastName} (ID: {admin.Id})"
|
||||
);
|
||||
|
||||
await InsertOrderNoteAsync(
|
||||
newOrder.Id,
|
||||
false,
|
||||
$"Új rendelés létrehozva a #{order.Id} rendelés szétválasztásával. Nem auditált termékek. Művelet végrehajtója: {admin.FirstName} {admin.LastName} (ID: {admin.Id})"
|
||||
$"* Új rendelés létrehozva a #{order.Id} rendelés szétválasztásával ({splitModeText}). {orderItemsToMove.Count} termék. Művelet végrehajtója: {admin.FirstName} {admin.LastName} (ID: {admin.Id})"
|
||||
);
|
||||
|
||||
_logger.Info($"Order {orderId} split successfully. New order created: {newOrder.Id}. Moved {orderItemsToMove.Count} items.");
|
||||
_logger.Info($"Order {orderId} split successfully using {mode} mode. New order created: {newOrder.Id}. Moved {orderItemsToMove.Count} items.");
|
||||
|
||||
// Send notifications
|
||||
_logger.Info($"SplitOrder - Sending notifications");
|
||||
|
|
@ -1706,7 +1737,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
success = true,
|
||||
message = "Rendelés sikeresen szétválasztva",
|
||||
newOrderId = newOrder.Id,
|
||||
originalOrderId = order.Id
|
||||
originalOrderId = order.Id,
|
||||
movedItemsCount = orderItemsToMove.Count
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
|
|||
private readonly IWorkContext _workContext;
|
||||
private readonly FileStorageService _fileStorageService;
|
||||
private readonly FruitBankAttributeService _fruitBankAttributeService;
|
||||
private readonly IStoreContext _storeContext;
|
||||
|
||||
public FileManagerController(
|
||||
IPermissionService permissionService,
|
||||
|
|
@ -51,7 +52,8 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
|
|||
PdfToImageService pdfToImageService,
|
||||
IWorkContext workContext,
|
||||
FileStorageService fileStorageService,
|
||||
FruitBankAttributeService fruitBankAttributeService)
|
||||
FruitBankAttributeService fruitBankAttributeService,
|
||||
IStoreContext storeContext)
|
||||
{
|
||||
_permissionService = permissionService;
|
||||
_aiApiService = aiApiService;
|
||||
|
|
@ -63,6 +65,7 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
|
|||
_workContext = workContext;
|
||||
_fileStorageService = fileStorageService;
|
||||
_fruitBankAttributeService = fruitBankAttributeService;
|
||||
_storeContext = storeContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1028,7 +1031,7 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
|
|||
foreach (var itemDto in request.ShippingItems)
|
||||
{
|
||||
var productDto = await _dbContext.ProductDtos.GetByIdAsync(itemDto.ProductId ?? 0, true);
|
||||
if (productDto != null && string.IsNullOrEmpty(itemDto.Name))
|
||||
if (productDto != null && !string.IsNullOrEmpty(itemDto.Name))
|
||||
{
|
||||
itemDto.IsMeasurable = productDto.IsMeasurable;
|
||||
}
|
||||
|
|
@ -1101,10 +1104,20 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
|
|||
//everything done, let's update the genericattribute "IncomingQuantity" of the product
|
||||
foreach (var item in shippingDocument.ShippingItems.Where(x => x.ProductId != null))
|
||||
{
|
||||
//var stockWeight = double.Round(await _fruitBankAttributeService.GetGenericAttributeValueAsync<Product, double>(product.Id, nameof(IMeasuringNetWeight.NetWeight), storeId), 1);
|
||||
var alreadyIncomingQuantityAttribute = await _fruitBankAttributeService.GetGenericAttributeValueAsync<Product, int>(item.ProductId.Value, nameof(IIncomingQuantity.IncomingQuantity), _storeContext.GetCurrentStore().Id);
|
||||
int alreadyIncomingQuantity = 0;
|
||||
if (alreadyIncomingQuantityAttribute != null)
|
||||
{
|
||||
Console.WriteLine($"Existing IncomingQuantity for Product ID {item.ProductId.Value}: {alreadyIncomingQuantityAttribute}");
|
||||
alreadyIncomingQuantity = Convert.ToInt32(alreadyIncomingQuantityAttribute);
|
||||
}
|
||||
var newIncomingQuantity = alreadyIncomingQuantity + item.QuantityOnDocument;
|
||||
|
||||
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, int>(
|
||||
item.ProductId.Value,
|
||||
"IncomingQuantity",
|
||||
item.QuantityOnDocument
|
||||
newIncomingQuantity, _storeContext.GetCurrentStore().Id
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -460,240 +460,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
|
||||
private async Task<ShippingDocumentAnalysisResult> ProcessRawText(int shippingDocumentId, int? partnerId, string pdfText, ShippingDocumentAnalysisResult shippingDocumentAnalysisResult, List<Files> 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<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
|
||||
};
|
||||
|
||||
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.");
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -738,9 +505,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
"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);
|
||||
//var metaAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText, analysisPrompt);
|
||||
|
||||
return ParseMetaDataAIResponse(metaAnalyzis);
|
||||
//return ParseMetaDataAIResponse(metaAnalyzis);
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<bool> SaveFileInfoToDb(int shippingDocumentId, ShippingDocumentAnalysisResult shippingDocumentAnalysisResult, List<Files> filesList, Files dbFile, string pdfText)
|
||||
|
|
|
|||
|
|
@ -329,22 +329,22 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
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."
|
||||
);
|
||||
//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);
|
||||
//// 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);
|
||||
//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);
|
||||
|
||||
|
|
@ -352,22 +352,22 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
|||
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
|
||||
};
|
||||
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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
|||
//https://linq2db.github.io/articles/sql/Join-Operators.html
|
||||
public class FruitBankDataController(
|
||||
FruitBankDbContext ctx,
|
||||
FruitBankAttributeService fruitBankAttributeService,
|
||||
MeasurementService measurementService,
|
||||
IWorkContext workContext,
|
||||
ICustomerService customerService,
|
||||
|
|
@ -326,7 +327,13 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
|||
_logger.Detail($"AddOrUpdateMeasuredShippingItemPallet invoked; {shippingItemPallet}");
|
||||
|
||||
if (!await ctx.AddOrUpdateShippingItemPalletSafeAsync(shippingItemPallet)) return null;
|
||||
return await ctx.ShippingItemPallets.GetByIdAsync(shippingItemPallet.Id, false);
|
||||
|
||||
var savedShippingItemPallet = await ctx.ShippingItemPallets.GetByIdAsync(shippingItemPallet.Id, true);
|
||||
//update average weight and quantity on Product
|
||||
await measurementService.CalculateAndSetAverageWeight(savedShippingItemPallet);
|
||||
|
||||
//return await ctx.ShippingItemPallets.GetByIdAsync(shippingItemPallet.Id, false);
|
||||
return shippingItemPallet;
|
||||
}
|
||||
|
||||
[SignalR(SignalRTags.GetShippingDocuments)]
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -14,8 +14,10 @@ using Nop.Core.Events;
|
|||
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Models;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
|
||||
using Nop.Services.Attributes;
|
||||
using Nop.Services.Catalog;
|
||||
using Nop.Services.Common;
|
||||
using Nop.Services.Customers;
|
||||
using Nop.Services.Events;
|
||||
using Nop.Services.Localization;
|
||||
using Nop.Services.Orders;
|
||||
|
|
@ -24,6 +26,7 @@ using Nop.Web.Framework.Events;
|
|||
using Nop.Web.Framework.Menu;
|
||||
using Nop.Web.Models.Sitemap;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
{
|
||||
|
|
@ -42,6 +45,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
|||
private readonly IAddressService _addressService;
|
||||
private readonly FruitBankAttributeService _fruitBankAttributeService;
|
||||
private readonly FruitBankDbContext _dbContext;
|
||||
private readonly IAttributeParser<CustomerAttribute, CustomerAttributeValue> _attributeParser;
|
||||
private readonly ICustomerService _customerService;
|
||||
|
||||
public EventConsumer(
|
||||
IGenericAttributeService genericAttributeService,
|
||||
|
|
@ -57,7 +62,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
|||
IHttpContextAccessor httpContextAccessor,
|
||||
IAddressService addressService,
|
||||
FruitBankAttributeService fruitBankAttributeService,
|
||||
FruitBankDbContext dbContext) : base(pluginManager)
|
||||
FruitBankDbContext dbContext,
|
||||
IAttributeParser<CustomerAttribute, CustomerAttributeValue> attributeParser,
|
||||
ICustomerService customerService
|
||||
) : base(pluginManager)
|
||||
{
|
||||
_genericAttributeService = genericAttributeService;
|
||||
_productService = productService;
|
||||
|
|
@ -72,6 +80,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
|||
_addressService = addressService;
|
||||
_fruitBankAttributeService = fruitBankAttributeService;
|
||||
_dbContext = dbContext;
|
||||
_attributeParser = attributeParser;
|
||||
_customerService = customerService;
|
||||
}
|
||||
|
||||
protected override string PluginSystemName => "Misc.FruitBankPlugin";
|
||||
|
|
@ -320,7 +330,19 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
|||
|
||||
private async Task SynchronizeTaxInformationAsync(Customer customer)
|
||||
{
|
||||
var taxId = await _fruitBankAttributeService.GetGenericAttributeValueAsync<Customer, string>(customer.Id, "TaxId");
|
||||
string attributesXml = customer.CustomCustomerAttributesXML;
|
||||
|
||||
string taxId = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(attributesXml))
|
||||
{
|
||||
var doc = XDocument.Parse(attributesXml);
|
||||
taxId = doc.Descendants("CustomerAttribute")
|
||||
.FirstOrDefault(el => (int?)el.Attribute("ID") == 1) // your TaxId attribute ID
|
||||
?.Descendants("Value")
|
||||
.FirstOrDefault()
|
||||
?.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(taxId) && string.IsNullOrWhiteSpace(customer.VatNumber))
|
||||
{
|
||||
|
|
@ -333,6 +355,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
|||
"TaxId",
|
||||
customer.VatNumber);
|
||||
}
|
||||
|
||||
_dbContext.Customers.Update(customer, false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class MeasurementService : MeasurementServiceBase<Logger>, IMeasurementSe
|
|||
private readonly IEventPublisher _eventPublisher;
|
||||
private readonly FruitBankAttributeService _fruitBankAttributeService;
|
||||
private readonly SignalRSendToClientService _signalRSendToClientService;
|
||||
private readonly CustomPriceCalculationService _customPriceCalculationService;
|
||||
private readonly CustomPriceCalculationService _customPriceCalculationService;
|
||||
|
||||
public MeasurementService(FruitBankDbContext dbContext, SignalRSendToClientService signalRSendToClientService, FruitBankAttributeService fruitBankAttributeService,
|
||||
IPriceCalculationService customPriceCalculationService, IEventPublisher eventPublisher, IEnumerable<IAcLogWriterBase> logWriters) : base(new Logger<MeasurementService>(logWriters.ToArray()))
|
||||
|
|
@ -150,4 +150,54 @@ public class MeasurementService : MeasurementServiceBase<Logger>, IMeasurementSe
|
|||
|
||||
return result ? partners : null;
|
||||
}
|
||||
|
||||
public async Task CalculateAndSetAverageWeight(ShippingItemPallet shippingItemPallet)
|
||||
{
|
||||
var productDto = await _dbContext.ProductDtos.GetByIdAsync(shippingItemPallet.ShippingItem.ProductId);
|
||||
if (productDto != null)
|
||||
{
|
||||
double averageWeight = 0;
|
||||
|
||||
if (shippingItemPallet.IsValidMeasuringValues(shippingItemPallet.ShippingItem.IsMeasurable))
|
||||
{
|
||||
//this is only for the current pallet, the average weight should be calculated from all pallets of the product,
|
||||
//so we check if there are any pallets with valid measuring values and calculate the average weight from them,
|
||||
//otherwise we use the current pallet's weight as the average weight
|
||||
|
||||
var allPalletsInThisShippingItem = shippingItemPallet.ShippingItem.ShippingItemPallets;
|
||||
|
||||
//we need an object to store the netweight and trayquantity for average calculation
|
||||
var validPalletsForAverageCalculation = new List<(double NetWeight, int TrayQuantity)>();
|
||||
|
||||
|
||||
//get all pallets with valid measuring values
|
||||
for (int i = 0; i < allPalletsInThisShippingItem.Count; i++)
|
||||
{
|
||||
if (allPalletsInThisShippingItem[i].IsValidMeasuringValues(shippingItemPallet.ShippingItem.IsMeasurable))
|
||||
{
|
||||
validPalletsForAverageCalculation.Add((allPalletsInThisShippingItem[i].NetWeight, allPalletsInThisShippingItem[i].TrayQuantity));
|
||||
}
|
||||
}
|
||||
|
||||
//add current pallet to the valid pallets if it has valid measuring values
|
||||
|
||||
validPalletsForAverageCalculation.Add(new(shippingItemPallet.NetWeight, shippingItemPallet.TrayQuantity));
|
||||
|
||||
//calculate the average weight from the valid pallets
|
||||
var totalNetWeight = validPalletsForAverageCalculation.Sum(x => x.NetWeight);
|
||||
var totalTrayQuantity = validPalletsForAverageCalculation.Sum(x => x.TrayQuantity);
|
||||
|
||||
var TotalAverageWeight = totalTrayQuantity > 0 ? totalNetWeight / totalTrayQuantity : 0;
|
||||
|
||||
}
|
||||
await SetProductAverageWeight(productDto, averageWeight);
|
||||
await _dbContext.ProductDtos.UpdateAsync(productDto);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task SetProductAverageWeight(ProductDto productDto, double averageWeight)
|
||||
{
|
||||
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<ProductDto, double>(productDto.Id, nameof(IProductDto.AverageWeight), averageWeight);
|
||||
}
|
||||
}
|
||||
|
|
@ -54,18 +54,18 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button type="button"
|
||||
class="btn btn-danger btn-block"
|
||||
data-toggle="modal"
|
||||
data-target="#splitOrderModal"
|
||||
@if(Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.Audited ||
|
||||
Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.NotStarted)
|
||||
{
|
||||
<text>disabled</text>
|
||||
}>
|
||||
<button type="button"
|
||||
class="btn btn-danger btn-block"
|
||||
data-toggle="modal"
|
||||
data-target="#splitOrderModal"
|
||||
@if (Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.Audited)
|
||||
{
|
||||
<text>disabled</text>
|
||||
}
|
||||
>
|
||||
<i class="fas fa-cut"></i> Rendelés szétválasztása
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -215,7 +215,7 @@
|
|||
|
||||
<!-- Split Order Modal -->
|
||||
<div class="modal fade" id="splitOrderModal" tabindex="-1" role="dialog" aria-labelledby="splitOrderModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="splitOrderModalLabel">
|
||||
|
|
@ -233,29 +233,117 @@
|
|||
<strong>Ez a rendelés már auditált!</strong> Auditált rendelések nem választhatók szét.
|
||||
</div>
|
||||
}
|
||||
else if (Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.NotStarted)
|
||||
{
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Ez a rendelés még nem lett elkezdve!</strong> Csak elkezdett rendelések választhatók szét.
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Figyelem!</strong> Ez a művelet az auditált és nem auditált termékeket külön rendelésekbe választja szét.
|
||||
<strong>Figyelem!</strong> Ez a művelet a kiválasztott termékeket egy új rendelésbe helyezi át.
|
||||
</div>
|
||||
|
||||
<h6>Auditált termékek (maradnak ebben a rendelésben):</h6>
|
||||
<ul id="auditedItemsList" class="list-group mb-3">
|
||||
<!-- Will be populated by JavaScript -->
|
||||
</ul>
|
||||
<!-- Mode Selection -->
|
||||
<div class="form-group">
|
||||
<label><strong>Válassza ki a szétválasztás módját:</strong></label>
|
||||
<div class="btn-group btn-group-toggle d-flex" data-toggle="buttons">
|
||||
<label class="btn btn-outline-primary flex-fill" id="auditModeButton">
|
||||
<input type="radio" name="splitMode" id="splitModeAudit" value="audit">
|
||||
<i class="fas fa-check-circle"></i> Audit státusz alapján
|
||||
</label>
|
||||
<label class="btn btn-outline-primary active flex-fill">
|
||||
<input type="radio" name="splitMode" id="splitModeManual" value="manual" checked>
|
||||
<i class="fas fa-hand-pointer"></i> Kézi kiválasztás
|
||||
</label>
|
||||
</div>
|
||||
<small class="form-text text-muted" id="splitModeDescription">
|
||||
Válassza ki, mely termékeket szeretné átmozgatni az új rendelésbe.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<h6>Nem auditált termékek (új rendelésbe kerülnek):</h6>
|
||||
<ul id="nonAuditedItemsList" class="list-group mb-3">
|
||||
<!-- Will be populated by JavaScript -->
|
||||
</ul>
|
||||
<hr />
|
||||
|
||||
<!-- Audit Mode View -->
|
||||
<div id="auditModeView" style="display: none;">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6><i class="fas fa-check-circle text-success"></i> Auditált/Elindított termékek (maradnak):</h6>
|
||||
<ul id="auditedItemsList" class="list-group mb-3">
|
||||
<!-- Will be populated by JavaScript -->
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6><i class="fas fa-times-circle text-danger"></i> Nem elindított termékek (új rendelés):</h6>
|
||||
<ul id="nonAuditedItemsList" class="list-group mb-3">
|
||||
<!-- Will be populated by JavaScript -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Manual Selection Mode View -->
|
||||
<div id="manualModeView">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6><i class="fas fa-box text-primary"></i> Maradnak az eredeti rendelésben:</h6>
|
||||
<ul id="remainingItemsList" class="list-group mb-3">
|
||||
<!-- Will be populated by JavaScript -->
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6><i class="fas fa-arrow-right text-warning"></i> Átkerülnek az új rendelésbe:</h6>
|
||||
<ul id="movingItemsList" class="list-group mb-3">
|
||||
<!-- Will be populated by JavaScript -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h6><i class="fas fa-list"></i> Válassza ki az átmozgatandó termékeket:</h6>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 50px;">
|
||||
<input type="checkbox" id="selectAllItems" title="Összes kijelölése" />
|
||||
</th>
|
||||
<th>Termék</th>
|
||||
<th>Mennyiség</th>
|
||||
<th>Státusz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="manualSelectionTable">
|
||||
@foreach (var orderItem in Model.OrderDto.OrderItemDtos)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
class="item-checkbox"
|
||||
data-item-id="@orderItem.Id"
|
||||
data-item-name="@orderItem.ProductName"
|
||||
data-item-quantity="@orderItem.Quantity"
|
||||
data-item-audited="@orderItem.IsAudited.ToString().ToLower()" />
|
||||
</td>
|
||||
<td>@orderItem.ProductName</td>
|
||||
<td>@orderItem.Quantity db</td>
|
||||
<td>
|
||||
@if (orderItem.IsAudited)
|
||||
{
|
||||
<span class="badge badge-success">
|
||||
<i class="fas fa-check"></i> Auditált
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge badge-warning">
|
||||
<i class="fas fa-clock"></i> Nem auditált
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div id="splitOrderStatus" class="alert" style="display: none; margin-top: 15px;">
|
||||
|
|
@ -267,8 +355,7 @@
|
|||
<i class="fa fa-times"></i> Mégse
|
||||
</button>
|
||||
<button type="button" id="splitOrderBtn" class="btn btn-danger"
|
||||
@if (Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.Audited ||
|
||||
Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.NotStarted)
|
||||
@if (Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.Audited)
|
||||
{
|
||||
<text>disabled</text>
|
||||
}
|
||||
|
|
@ -301,30 +388,40 @@
|
|||
$('#@Html.IdFor(m => m.DateOfReceipt)').attr('data-val', 'false');
|
||||
|
||||
// Save Order Attributes
|
||||
$("#saveAttributesBtn").click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Save Order Attributes
|
||||
$("#saveAttributesBtn").on("click", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "@Url.Action("SaveOrderAttributes", "CustomOrder")",
|
||||
data: {
|
||||
orderId: "@Model.OrderId",
|
||||
isMeasurable: $("#@Html.IdFor(m => m.IsMeasurable)").is(":checked"),
|
||||
dateOfReceipt: $("#@Html.IdFor(m => m.DateOfReceipt)").val(),
|
||||
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val()
|
||||
},
|
||||
success: function () {
|
||||
alert("Attributes saved successfully");
|
||||
},
|
||||
error: function () {
|
||||
alert("Error saving attributes");
|
||||
}
|
||||
});
|
||||
console.log("Save Attributes button clicked"); // Debug log
|
||||
|
||||
return false;
|
||||
var orderId = "@Model.OrderId";
|
||||
var dateOfReceipt = $("#@Html.IdFor(m => m.DateOfReceipt)").val();
|
||||
|
||||
console.log("OrderId:", orderId, "DateOfReceipt:", dateOfReceipt); // Debug log
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "@Url.Action("SaveOrderAttributes", "CustomOrder")",
|
||||
data: {
|
||||
orderId: orderId,
|
||||
isMeasurable: $("#@Html.IdFor(m => m.IsMeasurable)").is(":checked"),
|
||||
dateOfReceipt: dateOfReceipt,
|
||||
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val()
|
||||
},
|
||||
success: function (response) {
|
||||
console.log("Save success:", response); // Debug log
|
||||
alert("Attributes saved successfully");
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error("Save error:", xhr, status, error); // Debug log
|
||||
alert("Error saving attributes");
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// Allow Revision Button (in modal)
|
||||
$("#allowRevisionBtn").click(function (e) {
|
||||
e.preventDefault();
|
||||
|
|
@ -818,86 +915,231 @@
|
|||
$("#orderNoteText").val("");
|
||||
});
|
||||
|
||||
// ========== SPLIT ORDER MANAGEMENT ==========
|
||||
// ========== SPLIT ORDER MANAGEMENT ==========
|
||||
|
||||
var splitOrderUrl = '@Url.Action("SplitOrder", "CustomOrder")';
|
||||
var measuringStatus = @((int)Model.OrderDto.MeasuringStatus);
|
||||
var isAudited = measuringStatus === @((int)FruitBank.Common.Enums.MeasuringStatus.Audited);
|
||||
var isNotStarted = measuringStatus === @((int)FruitBank.Common.Enums.MeasuringStatus.NotStarted);
|
||||
|
||||
// Store all order items data with their measuring status
|
||||
var allOrderItems = [];
|
||||
|
||||
@foreach (var orderItem in Model.OrderDto.OrderItemDtos)
|
||||
{
|
||||
<text>
|
||||
allOrderItems.push({
|
||||
id: @orderItem.Id,
|
||||
name: '@Html.Raw(orderItem.ProductName)',
|
||||
quantity: @orderItem.Quantity,
|
||||
isAudited: @orderItem.IsAudited.ToString().ToLower(),
|
||||
measuringStatus: @((int)orderItem.MeasuringStatus)
|
||||
});
|
||||
</text>
|
||||
}
|
||||
|
||||
// Check if audit mode should be available
|
||||
// Audit mode only makes sense if there are some started/audited items
|
||||
var hasStartedItems = allOrderItems.some(item => item.measuringStatus > 0); // MeasuringStatus.NotStarted = 0
|
||||
var hasNotStartedItems = allOrderItems.some(item => item.measuringStatus === 0);
|
||||
var canUseAuditMode = hasStartedItems && hasNotStartedItems;
|
||||
|
||||
// Mode switching
|
||||
$('input[name="splitMode"]').change(function() {
|
||||
var mode = $(this).val();
|
||||
|
||||
if (mode === 'audit') {
|
||||
$('#auditModeView').show();
|
||||
$('#manualModeView').hide();
|
||||
$('#splitModeDescription').text('A már elindított/auditált termékek maradnak az eredeti rendelésben, a nem elindítottak új rendelésbe kerülnek.');
|
||||
populateAuditMode();
|
||||
} else {
|
||||
$('#auditModeView').hide();
|
||||
$('#manualModeView').show();
|
||||
$('#splitModeDescription').text('Válassza ki, mely termékeket szeretné átmozgatni az új rendelésbe.');
|
||||
updateManualModeLists();
|
||||
}
|
||||
|
||||
validateSplitButton();
|
||||
});
|
||||
|
||||
// Populate split order modal when opened
|
||||
$('#splitOrderModal').on('show.bs.modal', function () {
|
||||
// Skip populating lists if order is audited or not started
|
||||
if (isAudited || isNotStarted) {
|
||||
// Skip if order is audited
|
||||
if (isAudited) {
|
||||
return;
|
||||
}
|
||||
|
||||
var auditedItems = [];
|
||||
var nonAuditedItems = [];
|
||||
// Determine which mode to show by default
|
||||
if (canUseAuditMode) {
|
||||
// If we have both started and not started items, default to audit mode
|
||||
$('#splitModeAudit').prop('checked', true).parent().addClass('active');
|
||||
$('#splitModeManual').prop('checked', false).parent().removeClass('active');
|
||||
$('#auditModeView').show();
|
||||
$('#manualModeView').hide();
|
||||
$('#splitModeDescription').text('A már elindított/auditált termékek maradnak az eredeti rendelésben, a nem elindítottak új rendelésbe kerülnek.');
|
||||
populateAuditMode();
|
||||
|
||||
@foreach (var orderItem in Model.OrderDto.OrderItemDtos)
|
||||
{
|
||||
@if (orderItem.IsAudited)
|
||||
{
|
||||
<text>
|
||||
auditedItems.push({
|
||||
id: @orderItem.Id,
|
||||
name: '@Html.Raw(orderItem.ProductName)',
|
||||
quantity: @orderItem.Quantity
|
||||
});
|
||||
</text>
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>
|
||||
nonAuditedItems.push({
|
||||
id: @orderItem.Id,
|
||||
name: '@Html.Raw(orderItem.ProductName)',
|
||||
quantity: @orderItem.Quantity
|
||||
});
|
||||
</text>
|
||||
}
|
||||
// Enable audit mode button
|
||||
$('#auditModeButton').removeClass('disabled').prop('disabled', false);
|
||||
} else {
|
||||
// If all items are not started, only manual mode makes sense
|
||||
$('#splitModeManual').prop('checked', true).parent().addClass('active');
|
||||
$('#splitModeAudit').prop('checked', false).parent().removeClass('active');
|
||||
$('#auditModeView').hide();
|
||||
$('#manualModeView').show();
|
||||
$('#splitModeDescription').text('Válassza ki, mely termékeket szeretné átmozgatni az új rendelésbe.');
|
||||
updateManualModeLists();
|
||||
|
||||
// Disable and show tooltip for audit mode button
|
||||
$('#auditModeButton').addClass('disabled').prop('disabled', true);
|
||||
$('#auditModeButton').attr('title', 'Audit mód csak akkor elérhető, ha vannak már elindított mérések');
|
||||
}
|
||||
|
||||
// Populate audited items list
|
||||
var auditedListHtml = '';
|
||||
if (auditedItems.length > 0) {
|
||||
auditedItems.forEach(function(item) {
|
||||
auditedListHtml += '<li class="list-group-item">' +
|
||||
'<i class="fas fa-check-circle text-success"></i> ' +
|
||||
item.name + ' - ' + item.quantity + ' db</li>';
|
||||
// Uncheck all manual selections
|
||||
$('.item-checkbox').prop('checked', false);
|
||||
$('#selectAllItems').prop('checked', false);
|
||||
});
|
||||
|
||||
function populateAuditMode() {
|
||||
// In audit mode, we split by measuring status (started vs not started)
|
||||
var startedItems = allOrderItems.filter(item => item.measuringStatus > 0);
|
||||
var notStartedItems = allOrderItems.filter(item => item.measuringStatus === 0);
|
||||
|
||||
// Populate started/audited items list
|
||||
var startedListHtml = '';
|
||||
if (startedItems.length > 0) {
|
||||
startedItems.forEach(function(item) {
|
||||
var statusBadge = item.isAudited
|
||||
? '<span class="badge badge-success badge-sm ml-2">Auditált</span>'
|
||||
: '<span class="badge badge-info badge-sm ml-2">Elindítva</span>';
|
||||
startedListHtml += '<li class="list-group-item d-flex justify-content-between align-items-center">' +
|
||||
'<span><i class="fas fa-check-circle text-success"></i> ' +
|
||||
item.name + ' - ' + item.quantity + ' db</span>' +
|
||||
statusBadge +
|
||||
'</li>';
|
||||
});
|
||||
} else {
|
||||
auditedListHtml = '<li class="list-group-item text-muted">Nincsenek auditált termékek</li>';
|
||||
startedListHtml = '<li class="list-group-item text-muted">Nincsenek elindított termékek</li>';
|
||||
}
|
||||
$('#auditedItemsList').html(auditedListHtml);
|
||||
$('#auditedItemsList').html(startedListHtml);
|
||||
|
||||
// Populate non-audited items list
|
||||
var nonAuditedListHtml = '';
|
||||
if (nonAuditedItems.length > 0) {
|
||||
nonAuditedItems.forEach(function(item) {
|
||||
nonAuditedListHtml += '<li class="list-group-item">' +
|
||||
// Populate not started items list
|
||||
var notStartedListHtml = '';
|
||||
if (notStartedItems.length > 0) {
|
||||
notStartedItems.forEach(function(item) {
|
||||
notStartedListHtml += '<li class="list-group-item">' +
|
||||
'<i class="fas fa-times-circle text-danger"></i> ' +
|
||||
item.name + ' - ' + item.quantity + ' db</li>';
|
||||
});
|
||||
} else {
|
||||
nonAuditedListHtml = '<li class="list-group-item text-muted">Nincsenek nem auditált termékek</li>';
|
||||
notStartedListHtml = '<li class="list-group-item text-muted">Minden termék el lett indítva</li>';
|
||||
}
|
||||
$('#nonAuditedItemsList').html(nonAuditedListHtml);
|
||||
$('#nonAuditedItemsList').html(notStartedListHtml);
|
||||
|
||||
// Disable split button if no non-audited items or all items audited
|
||||
if (nonAuditedItems.length === 0 || auditedItems.length === 0) {
|
||||
$('#splitOrderBtn').prop('disabled', true);
|
||||
if (nonAuditedItems.length === 0) {
|
||||
showSplitOrderStatus("Nincs szétválasztható termék (minden termék auditált)", "warning");
|
||||
} else if (auditedItems.length === 0) {
|
||||
showSplitOrderStatus("Nincs szétválasztható termék (minden termék nem auditált)", "warning");
|
||||
validateSplitButton();
|
||||
}
|
||||
|
||||
function updateManualModeLists() {
|
||||
var selectedItems = [];
|
||||
var remainingItems = [];
|
||||
|
||||
$('.item-checkbox').each(function() {
|
||||
var checkbox = $(this);
|
||||
var item = {
|
||||
id: checkbox.data('item-id'),
|
||||
name: checkbox.data('item-name'),
|
||||
quantity: checkbox.data('item-quantity'),
|
||||
isAudited: checkbox.data('item-audited')
|
||||
};
|
||||
|
||||
if (checkbox.is(':checked')) {
|
||||
selectedItems.push(item);
|
||||
} else {
|
||||
remainingItems.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
// Populate remaining items list
|
||||
var remainingListHtml = '';
|
||||
if (remainingItems.length > 0) {
|
||||
remainingItems.forEach(function(item) {
|
||||
var badge = item.isAudited
|
||||
? '<span class="badge badge-success badge-sm">Auditált</span>'
|
||||
: '<span class="badge badge-warning badge-sm">Nem auditált</span>';
|
||||
remainingListHtml += '<li class="list-group-item d-flex justify-content-between align-items-center">' +
|
||||
'<span><i class="fas fa-box text-primary"></i> ' + item.name + ' - ' + item.quantity + ' db</span>' +
|
||||
badge +
|
||||
'</li>';
|
||||
});
|
||||
} else {
|
||||
$('#splitOrderBtn').prop('disabled', false);
|
||||
remainingListHtml = '<li class="list-group-item text-muted">Minden termék átkerül</li>';
|
||||
}
|
||||
$('#remainingItemsList').html(remainingListHtml);
|
||||
|
||||
// Populate moving items list
|
||||
var movingListHtml = '';
|
||||
if (selectedItems.length > 0) {
|
||||
selectedItems.forEach(function(item) {
|
||||
var badge = item.isAudited
|
||||
? '<span class="badge badge-success badge-sm">Auditált</span>'
|
||||
: '<span class="badge badge-warning badge-sm">Nem auditált</span>';
|
||||
movingListHtml += '<li class="list-group-item d-flex justify-content-between align-items-center">' +
|
||||
'<span><i class="fas fa-arrow-right text-warning"></i> ' + item.name + ' - ' + item.quantity + ' db</span>' +
|
||||
badge +
|
||||
'</li>';
|
||||
});
|
||||
} else {
|
||||
movingListHtml = '<li class="list-group-item text-muted">Nincs kiválasztott termék</li>';
|
||||
}
|
||||
$('#movingItemsList').html(movingListHtml);
|
||||
|
||||
validateSplitButton();
|
||||
}
|
||||
|
||||
// Handle individual checkbox changes
|
||||
$('.item-checkbox').change(function() {
|
||||
updateManualModeLists();
|
||||
});
|
||||
|
||||
// Handle select all checkbox
|
||||
$('#selectAllItems').change(function() {
|
||||
var isChecked = $(this).is(':checked');
|
||||
$('.item-checkbox').prop('checked', isChecked);
|
||||
updateManualModeLists();
|
||||
});
|
||||
|
||||
function validateSplitButton() {
|
||||
var mode = $('input[name="splitMode"]:checked').val();
|
||||
var shouldDisable = false;
|
||||
var message = '';
|
||||
|
||||
if (mode === 'audit') {
|
||||
if (!canUseAuditMode) {
|
||||
shouldDisable = true;
|
||||
message = 'Audit mód nem elérhető - nincs elindított mérés';
|
||||
}
|
||||
} else { // manual mode
|
||||
var selectedCount = $('.item-checkbox:checked').length;
|
||||
var totalCount = $('.item-checkbox').length;
|
||||
|
||||
if (selectedCount === 0) {
|
||||
shouldDisable = true;
|
||||
message = 'Válasszon ki legalább egy terméket';
|
||||
} else if (selectedCount === totalCount) {
|
||||
shouldDisable = true;
|
||||
message = 'Legalább egy terméknek maradnia kell az eredeti rendelésben';
|
||||
}
|
||||
}
|
||||
|
||||
$('#splitOrderBtn').prop('disabled', shouldDisable);
|
||||
|
||||
if (shouldDisable && message) {
|
||||
showSplitOrderStatus(message, 'warning');
|
||||
} else {
|
||||
$('#splitOrderStatus').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Split Order Button
|
||||
$("#splitOrderBtn").click(function (e) {
|
||||
e.preventDefault();
|
||||
|
|
@ -909,12 +1151,36 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
if (isNotStarted) {
|
||||
showSplitOrderStatus("Ez a rendelés még nem lett elkezdve, nem választható szét!", "danger");
|
||||
return false;
|
||||
var mode = $('input[name="splitMode"]:checked').val();
|
||||
var itemsToMove = [];
|
||||
|
||||
if (mode === 'manual') {
|
||||
// Get selected items
|
||||
$('.item-checkbox:checked').each(function() {
|
||||
itemsToMove.push(parseInt($(this).data('item-id')));
|
||||
});
|
||||
|
||||
if (itemsToMove.length === 0) {
|
||||
showSplitOrderStatus("Válasszon ki legalább egy terméket!", "warning");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemsToMove.length === allOrderItems.length) {
|
||||
showSplitOrderStatus("Legalább egy terméknek maradnia kell az eredeti rendelésben!", "warning");
|
||||
return false;
|
||||
}
|
||||
} else { // audit mode
|
||||
if (!canUseAuditMode) {
|
||||
showSplitOrderStatus("Audit mód nem elérhető - nincs elindított mérés!", "danger");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!confirm('Biztosan szét szeretné választani ezt a rendelést? Ez a művelet nem vonható vissza!')) {
|
||||
var confirmMessage = mode === 'audit'
|
||||
? 'Biztosan szét szeretné választani ezt a rendelést mérési státusz alapján? (Elindított vs. Nem elindított)'
|
||||
: 'Biztosan át szeretné mozgatni a kiválasztott termékeket egy új rendelésbe?';
|
||||
|
||||
if (!confirm(confirmMessage + '\n\nEz a művelet nem vonható vissza!')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -928,6 +1194,8 @@
|
|||
url: splitOrderUrl,
|
||||
data: {
|
||||
orderId: @Model.OrderId,
|
||||
mode: mode,
|
||||
orderItemIds: mode === 'manual' ? itemsToMove.join(',') : '',
|
||||
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val()
|
||||
},
|
||||
dataType: 'json',
|
||||
|
|
@ -979,10 +1247,9 @@
|
|||
// Clear split order status when modal is closed
|
||||
$('#splitOrderModal').on('hidden.bs.modal', function () {
|
||||
$("#splitOrderStatus").hide();
|
||||
if (!isAudited && !isNotStarted) {
|
||||
if (!isAudited) {
|
||||
$("#splitOrderBtn").prop('disabled', false).html('<i class="fas fa-cut"></i> Rendelés szétválasztása');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue