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:
Adam 2026-02-23 17:06:21 +01:00
parent 889c368a46
commit 98b1ba9b22
9 changed files with 791 additions and 448 deletions

View File

@ -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)

View File

@ -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
);
}

View File

@ -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)

View File

@ -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
{

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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>