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]
|
[HttpPost]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
[CheckPermission(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE)]
|
[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
|
try
|
||||||
{
|
{
|
||||||
_logger.Info($"SplitOrder - OrderId: {orderId} - STARTED");
|
_logger.Info($"SplitOrder - OrderId: {orderId}, Mode: {mode}, OrderItemIds: {orderItemIds} - STARTED");
|
||||||
|
|
||||||
var order = await _orderService.GetOrderByIdAsync(orderId);
|
var order = await _orderService.GetOrderByIdAsync(orderId);
|
||||||
if (order == null || order.Deleted)
|
if (order == null || order.Deleted)
|
||||||
|
|
@ -1496,44 +1496,73 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY CHECK: Don't allow splitting if measuring hasn't started
|
// REMOVED: NotStarted check - we allow splitting at any stage except Audited
|
||||||
if (orderDto.MeasuringStatus == MeasuringStatus.NotStarted)
|
// Manual mode is always available, audit mode is controlled by the UI
|
||||||
{
|
|
||||||
_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!"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Info($"SplitOrder - OrderDto found, separating items. Total items: {orderDto.OrderItemDtos.Count}, MeasuringStatus: {orderDto.MeasuringStatus}");
|
_logger.Info($"SplitOrder - OrderDto found, separating items. Total items: {orderDto.OrderItemDtos.Count}, MeasuringStatus: {orderDto.MeasuringStatus}");
|
||||||
|
|
||||||
// Separate audited and non-audited items
|
List<OrderItemDto> itemsToMove;
|
||||||
var auditedItems = orderDto.OrderItemDtos.Where(oi => oi.IsAudited).ToList();
|
|
||||||
var nonAuditedItems = orderDto.OrderItemDtos.Where(oi => !oi.IsAudited).ToList();
|
|
||||||
|
|
||||||
_logger.Info($"SplitOrder - Audited items: {auditedItems.Count}, Non-audited items: {nonAuditedItems.Count}");
|
if (mode == "manual")
|
||||||
|
|
||||||
if (nonAuditedItems.Count == 0)
|
|
||||||
{
|
{
|
||||||
_logger.Warning($"SplitOrder - No non-audited items in order {orderId}");
|
// Manual mode - use provided order item IDs
|
||||||
|
if (string.IsNullOrWhiteSpace(orderItemIds))
|
||||||
|
{
|
||||||
|
_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}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
_logger.Warning($"SplitOrder - No not-started items in order {orderId}");
|
||||||
return Json(new
|
return Json(new
|
||||||
{
|
{
|
||||||
success = false,
|
success = false,
|
||||||
message = "Nincs nem auditált termék a rendelésben. Szétválasztás nem szükséges."
|
message = "Nincs nem elindított termék a rendelésben. Szétválasztás nem szükséges."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auditedItems.Count == 0)
|
if (startedItems.Count == 0)
|
||||||
{
|
{
|
||||||
_logger.Warning($"SplitOrder - All items are non-audited in order {orderId}");
|
_logger.Warning($"SplitOrder - All items are not-started in order {orderId}");
|
||||||
return Json(new
|
return Json(new
|
||||||
{
|
{
|
||||||
success = false,
|
success = false,
|
||||||
message = "Minden termék nem auditált. Szétválasztás nem szükséges."
|
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");
|
_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();
|
var admin = await _workContext.GetCurrentCustomerAsync();
|
||||||
|
|
||||||
_logger.Info($"SplitOrder - Customer: {customer?.Id}, Store: {store?.Id}, Admin: {admin?.Id}");
|
_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
|
var newOrder = new Order
|
||||||
{
|
{
|
||||||
OrderGuid = Guid.NewGuid(),
|
OrderGuid = Guid.NewGuid(),
|
||||||
|
|
@ -1582,10 +1611,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
|
|
||||||
var orderItemsToMove = new List<OrderItem>();
|
var orderItemsToMove = new List<OrderItem>();
|
||||||
|
|
||||||
// Find order items to move based on non-audited OrderItemDtos
|
// Find order items to move based on itemsToMove DTOs
|
||||||
foreach (var nonAuditedDto in nonAuditedItems)
|
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)
|
if (orderItemToMove != null)
|
||||||
{
|
{
|
||||||
orderItemsToMove.Add(orderItemToMove);
|
orderItemsToMove.Add(orderItemToMove);
|
||||||
|
|
@ -1594,7 +1623,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
|
|
||||||
_logger.Info($"SplitOrder - Found {orderItemsToMove.Count} items to move");
|
_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)
|
foreach (var orderItem in orderItemsToMove)
|
||||||
{
|
{
|
||||||
_logger.Info($"SplitOrder - Processing order item {orderItem.Id}");
|
_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");
|
_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
|
// Add notes to both orders
|
||||||
await InsertOrderNoteAsync(
|
await InsertOrderNoteAsync(
|
||||||
order.Id,
|
order.Id,
|
||||||
false,
|
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(
|
await InsertOrderNoteAsync(
|
||||||
newOrder.Id,
|
newOrder.Id,
|
||||||
false,
|
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
|
// Send notifications
|
||||||
_logger.Info($"SplitOrder - Sending notifications");
|
_logger.Info($"SplitOrder - Sending notifications");
|
||||||
|
|
@ -1706,7 +1737,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
success = true,
|
success = true,
|
||||||
message = "Rendelés sikeresen szétválasztva",
|
message = "Rendelés sikeresen szétválasztva",
|
||||||
newOrderId = newOrder.Id,
|
newOrderId = newOrder.Id,
|
||||||
originalOrderId = order.Id
|
originalOrderId = order.Id,
|
||||||
|
movedItemsCount = orderItemsToMove.Count
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
|
||||||
private readonly IWorkContext _workContext;
|
private readonly IWorkContext _workContext;
|
||||||
private readonly FileStorageService _fileStorageService;
|
private readonly FileStorageService _fileStorageService;
|
||||||
private readonly FruitBankAttributeService _fruitBankAttributeService;
|
private readonly FruitBankAttributeService _fruitBankAttributeService;
|
||||||
|
private readonly IStoreContext _storeContext;
|
||||||
|
|
||||||
public FileManagerController(
|
public FileManagerController(
|
||||||
IPermissionService permissionService,
|
IPermissionService permissionService,
|
||||||
|
|
@ -51,7 +52,8 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
|
||||||
PdfToImageService pdfToImageService,
|
PdfToImageService pdfToImageService,
|
||||||
IWorkContext workContext,
|
IWorkContext workContext,
|
||||||
FileStorageService fileStorageService,
|
FileStorageService fileStorageService,
|
||||||
FruitBankAttributeService fruitBankAttributeService)
|
FruitBankAttributeService fruitBankAttributeService,
|
||||||
|
IStoreContext storeContext)
|
||||||
{
|
{
|
||||||
_permissionService = permissionService;
|
_permissionService = permissionService;
|
||||||
_aiApiService = aiApiService;
|
_aiApiService = aiApiService;
|
||||||
|
|
@ -63,6 +65,7 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
|
||||||
_workContext = workContext;
|
_workContext = workContext;
|
||||||
_fileStorageService = fileStorageService;
|
_fileStorageService = fileStorageService;
|
||||||
_fruitBankAttributeService = fruitBankAttributeService;
|
_fruitBankAttributeService = fruitBankAttributeService;
|
||||||
|
_storeContext = storeContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -1028,7 +1031,7 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
|
||||||
foreach (var itemDto in request.ShippingItems)
|
foreach (var itemDto in request.ShippingItems)
|
||||||
{
|
{
|
||||||
var productDto = await _dbContext.ProductDtos.GetByIdAsync(itemDto.ProductId ?? 0, true);
|
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;
|
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
|
//everything done, let's update the genericattribute "IncomingQuantity" of the product
|
||||||
foreach (var item in shippingDocument.ShippingItems.Where(x => x.ProductId != null))
|
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>(
|
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, int>(
|
||||||
item.ProductId.Value,
|
item.ProductId.Value,
|
||||||
"IncomingQuantity",
|
"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)
|
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;
|
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>
|
/// <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.";
|
"If you can't find information of any of these, return null value for that field.";
|
||||||
|
|
||||||
//here I can start preparing the file entity
|
//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)
|
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());
|
Console.WriteLine(pdfText.ToString());
|
||||||
|
|
||||||
// Analyze PDF with AI to extract structured data
|
// Analyze PDF with AI to extract structured data
|
||||||
var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(
|
//var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(
|
||||||
pdfText.ToString(),
|
// 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."
|
// "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)
|
//// Parse AI response (assuming it returns JSON)
|
||||||
var extractedData = ParseShippingDocumentAIResponse(aiAnalysis);
|
//var extractedData = ParseShippingDocumentAIResponse(aiAnalysis);
|
||||||
|
|
||||||
//TODO: Save document record to database
|
//TODO: Save document record to database
|
||||||
Console.WriteLine("AI Analysis Result:");
|
//Console.WriteLine("AI Analysis Result:");
|
||||||
Console.WriteLine(extractedData.RecipientName);
|
//Console.WriteLine(extractedData.RecipientName);
|
||||||
Console.WriteLine(extractedData.SenderName);
|
//Console.WriteLine(extractedData.SenderName);
|
||||||
Console.WriteLine(extractedData.InvoiceNumber);
|
//Console.WriteLine(extractedData.InvoiceNumber);
|
||||||
Console.WriteLine(extractedData.TotalAmount);
|
//Console.WriteLine(extractedData.TotalAmount);
|
||||||
Console.WriteLine(extractedData.ItemCount);
|
//Console.WriteLine(extractedData.ItemCount);
|
||||||
Console.WriteLine(extractedData.Notes);
|
//Console.WriteLine(extractedData.Notes);
|
||||||
|
|
||||||
// var savedDocument = await _documentService.InsertDocumentAsync(document);
|
// 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
|
var documentId = 1; // Replace with: savedDocument.Id
|
||||||
|
|
||||||
// Return structured document model
|
// Return structured document model
|
||||||
var documentModel = new ShippingDocumentModel
|
var documentModel = new ShippingDocumentModel();
|
||||||
{
|
//{
|
||||||
Id = documentId,
|
// Id = documentId,
|
||||||
ShippingId = shippingId,
|
// ShippingId = shippingId,
|
||||||
FileName = file.FileName,
|
// FileName = file.FileName,
|
||||||
FilePath = $"/uploads/shippingDocuments/{fileName}",
|
// FilePath = $"/uploads/shippingDocuments/{fileName}",
|
||||||
FileSize = (int)(file.Length / 1024),
|
// FileSize = (int)(file.Length / 1024),
|
||||||
DocumentDate = extractedData.DocumentDate,
|
// DocumentDate = extractedData.DocumentDate,
|
||||||
RecipientName = extractedData.RecipientName,
|
// RecipientName = extractedData.RecipientName,
|
||||||
SenderName = extractedData.SenderName,
|
// SenderName = extractedData.SenderName,
|
||||||
InvoiceNumber = extractedData.InvoiceNumber,
|
// InvoiceNumber = extractedData.InvoiceNumber,
|
||||||
TotalAmount = extractedData.TotalAmount,
|
// TotalAmount = extractedData.TotalAmount,
|
||||||
ItemCount = extractedData.ItemCount,
|
// ItemCount = extractedData.ItemCount,
|
||||||
Notes = extractedData.Notes,
|
// Notes = extractedData.Notes,
|
||||||
RawAIAnalysis = aiAnalysis // Store the raw AI response for debugging
|
// RawAIAnalysis = aiAnalysis // Store the raw AI response for debugging
|
||||||
};
|
//};
|
||||||
|
|
||||||
return Json(new
|
return Json(new
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
//https://linq2db.github.io/articles/sql/Join-Operators.html
|
//https://linq2db.github.io/articles/sql/Join-Operators.html
|
||||||
public class FruitBankDataController(
|
public class FruitBankDataController(
|
||||||
FruitBankDbContext ctx,
|
FruitBankDbContext ctx,
|
||||||
|
FruitBankAttributeService fruitBankAttributeService,
|
||||||
MeasurementService measurementService,
|
MeasurementService measurementService,
|
||||||
IWorkContext workContext,
|
IWorkContext workContext,
|
||||||
ICustomerService customerService,
|
ICustomerService customerService,
|
||||||
|
|
@ -326,7 +327,13 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
|
||||||
_logger.Detail($"AddOrUpdateMeasuredShippingItemPallet invoked; {shippingItemPallet}");
|
_logger.Detail($"AddOrUpdateMeasuredShippingItemPallet invoked; {shippingItemPallet}");
|
||||||
|
|
||||||
if (!await ctx.AddOrUpdateShippingItemPalletSafeAsync(shippingItemPallet)) return null;
|
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)]
|
[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.Domains.DataLayer;
|
||||||
using Nop.Plugin.Misc.FruitBankPlugin.Models;
|
using Nop.Plugin.Misc.FruitBankPlugin.Models;
|
||||||
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
|
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
|
||||||
|
using Nop.Services.Attributes;
|
||||||
using Nop.Services.Catalog;
|
using Nop.Services.Catalog;
|
||||||
using Nop.Services.Common;
|
using Nop.Services.Common;
|
||||||
|
using Nop.Services.Customers;
|
||||||
using Nop.Services.Events;
|
using Nop.Services.Events;
|
||||||
using Nop.Services.Localization;
|
using Nop.Services.Localization;
|
||||||
using Nop.Services.Orders;
|
using Nop.Services.Orders;
|
||||||
|
|
@ -24,6 +26,7 @@ using Nop.Web.Framework.Events;
|
||||||
using Nop.Web.Framework.Menu;
|
using Nop.Web.Framework.Menu;
|
||||||
using Nop.Web.Models.Sitemap;
|
using Nop.Web.Models.Sitemap;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
{
|
{
|
||||||
|
|
@ -42,6 +45,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
private readonly IAddressService _addressService;
|
private readonly IAddressService _addressService;
|
||||||
private readonly FruitBankAttributeService _fruitBankAttributeService;
|
private readonly FruitBankAttributeService _fruitBankAttributeService;
|
||||||
private readonly FruitBankDbContext _dbContext;
|
private readonly FruitBankDbContext _dbContext;
|
||||||
|
private readonly IAttributeParser<CustomerAttribute, CustomerAttributeValue> _attributeParser;
|
||||||
|
private readonly ICustomerService _customerService;
|
||||||
|
|
||||||
public EventConsumer(
|
public EventConsumer(
|
||||||
IGenericAttributeService genericAttributeService,
|
IGenericAttributeService genericAttributeService,
|
||||||
|
|
@ -57,7 +62,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
IHttpContextAccessor httpContextAccessor,
|
IHttpContextAccessor httpContextAccessor,
|
||||||
IAddressService addressService,
|
IAddressService addressService,
|
||||||
FruitBankAttributeService fruitBankAttributeService,
|
FruitBankAttributeService fruitBankAttributeService,
|
||||||
FruitBankDbContext dbContext) : base(pluginManager)
|
FruitBankDbContext dbContext,
|
||||||
|
IAttributeParser<CustomerAttribute, CustomerAttributeValue> attributeParser,
|
||||||
|
ICustomerService customerService
|
||||||
|
) : base(pluginManager)
|
||||||
{
|
{
|
||||||
_genericAttributeService = genericAttributeService;
|
_genericAttributeService = genericAttributeService;
|
||||||
_productService = productService;
|
_productService = productService;
|
||||||
|
|
@ -72,6 +80,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
_addressService = addressService;
|
_addressService = addressService;
|
||||||
_fruitBankAttributeService = fruitBankAttributeService;
|
_fruitBankAttributeService = fruitBankAttributeService;
|
||||||
_dbContext = dbContext;
|
_dbContext = dbContext;
|
||||||
|
_attributeParser = attributeParser;
|
||||||
|
_customerService = customerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string PluginSystemName => "Misc.FruitBankPlugin";
|
protected override string PluginSystemName => "Misc.FruitBankPlugin";
|
||||||
|
|
@ -320,7 +330,19 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
|
|
||||||
private async Task SynchronizeTaxInformationAsync(Customer customer)
|
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))
|
if (!string.IsNullOrWhiteSpace(taxId) && string.IsNullOrWhiteSpace(customer.VatNumber))
|
||||||
{
|
{
|
||||||
|
|
@ -333,6 +355,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
"TaxId",
|
"TaxId",
|
||||||
customer.VatNumber);
|
customer.VatNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
_dbContext.Customers.Update(customer, false);
|
_dbContext.Customers.Update(customer, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,4 +150,54 @@ public class MeasurementService : MeasurementServiceBase<Logger>, IMeasurementSe
|
||||||
|
|
||||||
return result ? partners : null;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,14 +58,14 @@
|
||||||
class="btn btn-danger btn-block"
|
class="btn btn-danger btn-block"
|
||||||
data-toggle="modal"
|
data-toggle="modal"
|
||||||
data-target="#splitOrderModal"
|
data-target="#splitOrderModal"
|
||||||
@if(Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.Audited ||
|
@if (Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.Audited)
|
||||||
Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.NotStarted)
|
|
||||||
{
|
{
|
||||||
<text>disabled</text>
|
<text>disabled</text>
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
<i class="fas fa-cut"></i> Rendelés szétválasztása
|
<i class="fas fa-cut"></i> Rendelés szétválasztása
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -215,7 +215,7 @@
|
||||||
|
|
||||||
<!-- Split Order Modal -->
|
<!-- Split Order Modal -->
|
||||||
<div class="modal fade" id="splitOrderModal" tabindex="-1" role="dialog" aria-labelledby="splitOrderModalLabel" aria-hidden="true">
|
<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-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="splitOrderModalLabel">
|
<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.
|
<strong>Ez a rendelés már auditált!</strong> Auditált rendelések nem választhatók szét.
|
||||||
</div>
|
</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
|
else
|
||||||
{
|
{
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
<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>
|
</div>
|
||||||
|
|
||||||
<h6>Auditált termékek (maradnak ebben a rendelésben):</h6>
|
<!-- 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>
|
||||||
|
|
||||||
|
<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">
|
<ul id="auditedItemsList" class="list-group mb-3">
|
||||||
<!-- Will be populated by JavaScript -->
|
<!-- Will be populated by JavaScript -->
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
<h6>Nem auditált termékek (új rendelésbe kerülnek):</h6>
|
<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">
|
<ul id="nonAuditedItemsList" class="list-group mb-3">
|
||||||
<!-- Will be populated by JavaScript -->
|
<!-- Will be populated by JavaScript -->
|
||||||
</ul>
|
</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;">
|
<div id="splitOrderStatus" class="alert" style="display: none; margin-top: 15px;">
|
||||||
|
|
@ -267,8 +355,7 @@
|
||||||
<i class="fa fa-times"></i> Mégse
|
<i class="fa fa-times"></i> Mégse
|
||||||
</button>
|
</button>
|
||||||
<button type="button" id="splitOrderBtn" class="btn btn-danger"
|
<button type="button" id="splitOrderBtn" class="btn btn-danger"
|
||||||
@if (Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.Audited ||
|
@if (Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.Audited)
|
||||||
Model.OrderDto.MeasuringStatus == FruitBank.Common.Enums.MeasuringStatus.NotStarted)
|
|
||||||
{
|
{
|
||||||
<text>disabled</text>
|
<text>disabled</text>
|
||||||
}
|
}
|
||||||
|
|
@ -301,23 +388,33 @@
|
||||||
$('#@Html.IdFor(m => m.DateOfReceipt)').attr('data-val', 'false');
|
$('#@Html.IdFor(m => m.DateOfReceipt)').attr('data-val', 'false');
|
||||||
|
|
||||||
// Save Order Attributes
|
// Save Order Attributes
|
||||||
$("#saveAttributesBtn").click(function (e) {
|
// Save Order Attributes
|
||||||
|
$("#saveAttributesBtn").on("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
console.log("Save Attributes button clicked"); // Debug log
|
||||||
|
|
||||||
|
var orderId = "@Model.OrderId";
|
||||||
|
var dateOfReceipt = $("#@Html.IdFor(m => m.DateOfReceipt)").val();
|
||||||
|
|
||||||
|
console.log("OrderId:", orderId, "DateOfReceipt:", dateOfReceipt); // Debug log
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
url: "@Url.Action("SaveOrderAttributes", "CustomOrder")",
|
url: "@Url.Action("SaveOrderAttributes", "CustomOrder")",
|
||||||
data: {
|
data: {
|
||||||
orderId: "@Model.OrderId",
|
orderId: orderId,
|
||||||
isMeasurable: $("#@Html.IdFor(m => m.IsMeasurable)").is(":checked"),
|
isMeasurable: $("#@Html.IdFor(m => m.IsMeasurable)").is(":checked"),
|
||||||
dateOfReceipt: $("#@Html.IdFor(m => m.DateOfReceipt)").val(),
|
dateOfReceipt: dateOfReceipt,
|
||||||
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val()
|
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val()
|
||||||
},
|
},
|
||||||
success: function () {
|
success: function (response) {
|
||||||
|
console.log("Save success:", response); // Debug log
|
||||||
alert("Attributes saved successfully");
|
alert("Attributes saved successfully");
|
||||||
},
|
},
|
||||||
error: function () {
|
error: function (xhr, status, error) {
|
||||||
|
console.error("Save error:", xhr, status, error); // Debug log
|
||||||
alert("Error saving attributes");
|
alert("Error saving attributes");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -823,81 +920,226 @@
|
||||||
var splitOrderUrl = '@Url.Action("SplitOrder", "CustomOrder")';
|
var splitOrderUrl = '@Url.Action("SplitOrder", "CustomOrder")';
|
||||||
var measuringStatus = @((int)Model.OrderDto.MeasuringStatus);
|
var measuringStatus = @((int)Model.OrderDto.MeasuringStatus);
|
||||||
var isAudited = measuringStatus === @((int)FruitBank.Common.Enums.MeasuringStatus.Audited);
|
var isAudited = measuringStatus === @((int)FruitBank.Common.Enums.MeasuringStatus.Audited);
|
||||||
var isNotStarted = measuringStatus === @((int)FruitBank.Common.Enums.MeasuringStatus.NotStarted);
|
|
||||||
|
|
||||||
// Populate split order modal when opened
|
// Store all order items data with their measuring status
|
||||||
$('#splitOrderModal').on('show.bs.modal', function () {
|
var allOrderItems = [];
|
||||||
// Skip populating lists if order is audited or not started
|
|
||||||
if (isAudited || isNotStarted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var auditedItems = [];
|
|
||||||
var nonAuditedItems = [];
|
|
||||||
|
|
||||||
@foreach (var orderItem in Model.OrderDto.OrderItemDtos)
|
@foreach (var orderItem in Model.OrderDto.OrderItemDtos)
|
||||||
{
|
|
||||||
@if (orderItem.IsAudited)
|
|
||||||
{
|
{
|
||||||
<text>
|
<text>
|
||||||
auditedItems.push({
|
allOrderItems.push({
|
||||||
id: @orderItem.Id,
|
id: @orderItem.Id,
|
||||||
name: '@Html.Raw(orderItem.ProductName)',
|
name: '@Html.Raw(orderItem.ProductName)',
|
||||||
quantity: @orderItem.Quantity
|
quantity: @orderItem.Quantity,
|
||||||
|
isAudited: @orderItem.IsAudited.ToString().ToLower(),
|
||||||
|
measuringStatus: @((int)orderItem.MeasuringStatus)
|
||||||
});
|
});
|
||||||
</text>
|
</text>
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
<text>
|
|
||||||
nonAuditedItems.push({
|
|
||||||
id: @orderItem.Id,
|
|
||||||
name: '@Html.Raw(orderItem.ProductName)',
|
|
||||||
quantity: @orderItem.Quantity
|
|
||||||
});
|
|
||||||
</text>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate audited items list
|
// Check if audit mode should be available
|
||||||
var auditedListHtml = '';
|
// Audit mode only makes sense if there are some started/audited items
|
||||||
if (auditedItems.length > 0) {
|
var hasStartedItems = allOrderItems.some(item => item.measuringStatus > 0); // MeasuringStatus.NotStarted = 0
|
||||||
auditedItems.forEach(function(item) {
|
var hasNotStartedItems = allOrderItems.some(item => item.measuringStatus === 0);
|
||||||
auditedListHtml += '<li class="list-group-item">' +
|
var canUseAuditMode = hasStartedItems && hasNotStartedItems;
|
||||||
'<i class="fas fa-check-circle text-success"></i> ' +
|
|
||||||
item.name + ' - ' + item.quantity + ' db</li>';
|
// 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 if order is audited
|
||||||
|
if (isAudited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
} 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
|
// Populate not started items list
|
||||||
var nonAuditedListHtml = '';
|
var notStartedListHtml = '';
|
||||||
if (nonAuditedItems.length > 0) {
|
if (notStartedItems.length > 0) {
|
||||||
nonAuditedItems.forEach(function(item) {
|
notStartedItems.forEach(function(item) {
|
||||||
nonAuditedListHtml += '<li class="list-group-item">' +
|
notStartedListHtml += '<li class="list-group-item">' +
|
||||||
'<i class="fas fa-times-circle text-danger"></i> ' +
|
'<i class="fas fa-times-circle text-danger"></i> ' +
|
||||||
item.name + ' - ' + item.quantity + ' db</li>';
|
item.name + ' - ' + item.quantity + ' db</li>';
|
||||||
});
|
});
|
||||||
} else {
|
} 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
|
validateSplitButton();
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
$('#splitOrderBtn').prop('disabled', false);
|
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 {
|
||||||
|
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
|
// Split Order Button
|
||||||
$("#splitOrderBtn").click(function (e) {
|
$("#splitOrderBtn").click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -909,12 +1151,36 @@
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNotStarted) {
|
var mode = $('input[name="splitMode"]:checked').val();
|
||||||
showSplitOrderStatus("Ez a rendelés még nem lett elkezdve, nem választható szét!", "danger");
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!confirm('Biztosan szét szeretné választani ezt a rendelést? Ez a művelet nem vonható vissza!')) {
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -928,6 +1194,8 @@
|
||||||
url: splitOrderUrl,
|
url: splitOrderUrl,
|
||||||
data: {
|
data: {
|
||||||
orderId: @Model.OrderId,
|
orderId: @Model.OrderId,
|
||||||
|
mode: mode,
|
||||||
|
orderItemIds: mode === 'manual' ? itemsToMove.join(',') : '',
|
||||||
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val()
|
__RequestVerificationToken: $('input[name="__RequestVerificationToken"]').val()
|
||||||
},
|
},
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
|
|
@ -979,10 +1247,9 @@
|
||||||
// Clear split order status when modal is closed
|
// Clear split order status when modal is closed
|
||||||
$('#splitOrderModal').on('hidden.bs.modal', function () {
|
$('#splitOrderModal').on('hidden.bs.modal', function () {
|
||||||
$("#splitOrderStatus").hide();
|
$("#splitOrderStatus").hide();
|
||||||
if (!isAudited && !isNotStarted) {
|
if (!isAudited) {
|
||||||
$("#splitOrderBtn").prop('disabled', false).html('<i class="fas fa-cut"></i> Rendelés szétválasztása');
|
$("#splitOrderBtn").prop('disabled', false).html('<i class="fas fa-cut"></i> Rendelés szétválasztása');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue