A fájlhiba ne vigye el a készletet, a raklapszám pedig legyen számított

A "|" karakter a beszállítói PDF nevében (Macről feltöltve érvényes) elhasalt a
Windows-szerveren, és ezzel a mentés NÉMÁN, RÉSZLEGESEN futott le: a dokumentum és a
tételek mentve maradtak, de az IncomingQuantity és az előrendelés-konverzió sosem
futott le, mert a fájlírás kivétele a külső catch-be ugrott.

Két külön javítás:
- a feltöltött fájl neve tisztítva (Path.GetInvalidFileNameChars), egy helyen, így a
  dokumentumra mentett PdfFileName és a lemezre írt név nem csúszhat szét;
- a fájlmentés SAJÁT try/catch-et kapott: a PDF melléklet, a készlet-frissítés a
  lényeg. A válasz fileSaved/warning mezőben jelzi a részleges sikert, hogy ne
  vesszen el némán. A "|" csak a kiváltó eset volt — tele lemez vagy zárolt fájl
  ugyanezt okozta volna.

A raklapszám mostantól a rekeszszámból SZÁRMAZTATOTT (rekesz / rekesz-per-raklap,
FELFELÉ kerekítve: 160 rekesz 120-as aránynál 1,33 → fizikailag 2 raklap), és
kikerült az AI feladatai közül. A szállítóleveleken gyakran nincs is rajta, az AI
pedig rendre eltévesztette. A mező — a súllyal ellentétben — NEM zárolt, de a
mennyiség módosításakor újraszámolódik: ha az AI a mennyiséget tévesztette, a
javítás után a raklapszámnak követnie kell. Hiányzó rekesz/raklapnál a mező piros,
az érték 1, és a címke elvisz a termékhez.

A számítás sorrendben MEGELŐZI a súlyt, mert a bruttó képlet a raklapszámot
fogyasztja — így a jobb raklapszám a súlyt is javítja.

Mellette: a selectProduct eddig csak a nevet és az árat vette át a termékről, a
törzsadatot nem — termékváltás után a RÉGI termék tárájával és átlagsúlyával
számoltunk tovább, épp akkor rontva el, amikor az admin egy téves párosítást javít.

⚠️ A számított érték itt a PalletsOnDocument-be megy, tehát felülírja a "dokumentum
nyers állítása" mezőt. A 0.0.8.1+ ágon ez a tört ShippingItem.Pallets mezőbe való
(ADR 0004-M9F3), és a PalletsOnDocument visszakapja az eredeti szerepét.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 11:31:26 +02:00
co-authored by Claude Opus 5
parent d37763ab8a
commit 6419b44605
4 changed files with 225 additions and 56 deletions
@@ -1170,6 +1170,9 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
isMeasurable = productDto.IsMeasurable,
tare = productDto.Tare,
averageWeight = productDto.AverageWeight,
// Ebből származtatja a felület a raklapszámot (rekesz / rekesz-per-raklap, felfelé
// kerekítve) — az AI-nak már nem feladata kiolvasni. 0 = nincs kitöltve, a felület jelzi.
cratesPerPallet = productDto.CratesPerPallet,
});
}
}
@@ -388,6 +388,9 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
isMeasurable = item.ProductDto?.IsMeasurable ?? false,
tare = item.ProductDto?.Tare ?? 0d,
averageWeight = item.ProductDto?.AverageWeight ?? 0d,
// A kliens ebből számolja a raklapszámot (rekesz / rekesz-per-raklap, felfelé
// kerekítve). 0 = nincs kitöltve → a felület pirossal jelzi, és 1-et használ.
cratesPerPallet = item.ProductDto?.CratesPerPallet ?? 0,
palletsOnDocument = item.PalletsOnDocument,
quantityOnDocument = item.QuantityOnDocument,
netWeightOnDocument = item.NetWeightOnDocument,
@@ -662,7 +665,11 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
ProductId = productDto.Id,
NameOnDocument = deserializedProduct.name,
IsMeasurable = productDto.IsMeasurable,
UnitPriceOnDocument = deserializedProduct.unitCost
UnitPriceOnDocument = deserializedProduct.unitCost,
// A kiolvasás-ellenőrző felület ebből számol súlyt nem mérendő terméknél. Enélkül a payloadban
// tare = 0 és averageWeight = 0 menne ki, és a kliens nem tudna számolni — csak azt látná, hogy
// „nincs termék-törzsadat".
ProductDto = productDto
};
}
@@ -860,6 +867,28 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
return cleanedPartnerName;
}
/// <summary>
/// A feltöltött fájl nevét fájlrendszer-biztossá teszi: a tiltott karaktereket aláhúzásra cseréli.
/// <para>
/// Beszállítói PDF-ek nevében rendszeresen előfordul a <c>|</c> (pl. „Orderconfirmation | 153321.pdf"),
/// ami Windowson tiltott — a lemezre írás <c>IOException</c>-t dob. A tiltott karakterek listáját nem
/// írjuk le kézzel, hanem a futtató platformtól kérdezzük (<see cref="Path.GetInvalidFileNameChars"/>),
/// így nem marad ki egy sem.
/// </para>
/// </summary>
private static string SanitizeFileName(string rawFileName)
{
if (string.IsNullOrWhiteSpace(rawFileName))
return rawFileName;
// Teljes névvel: a `Path` kétértelmű a DocumentFormat.OpenXml.Vml.Path miatt.
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
var sanitized = new string(rawFileName.Select(c => invalidChars.Contains(c) ? '_' : c).ToArray()).Trim();
// Elvi eset: ha a tisztítás után semmi nem marad, adjunk nevet — a mentés a névre épít.
return sanitized.Length == 0 ? "file" : sanitized;
}
/// <summary>
/// Cleans and normalizes partner name from AI response
/// </summary>
@@ -1024,6 +1053,13 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
var partner = await _dbContext.Partners.GetByIdAsync(request.PartnerId.Value);
// A feltöltött fájl neve tisztítva: a beszállítói PDF-ek nevében előfordulnak Windows-on
// TILTOTT karakterek (| \ / : * ? " < >), és ilyenkor a lemezre írás kivételt dob.
// Egy helyen tisztítjuk, és MINDENHOL ezt használjuk — a dokumentumra mentett PdfFileName
// és a lemezre írt fájlnév így nem csúszhat szét. Ez azért is fontos, mert a blob
// visszakeresése a tárolt névből építi az útvonalat (FileStorageService).
var safeOriginalFileName = SanitizeFileName(originalFile?.FileName);
// Create the shipping document entity
var shippingDocument = new ShippingDocument
{
@@ -1031,7 +1067,7 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
PartnerId = request.PartnerId.Value,
Country = partner?.Country,
TotalPallets = request.TotalPallets,
PdfFileName = originalFile.FileName,
PdfFileName = safeOriginalFileName,
ShippingDate = DateTime.UtcNow,
ShippingItems = new List<ShippingItem>()
};
@@ -1056,17 +1092,36 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
// nettó = rekesz × átlagsúly (a repo NET_WEIGHT szabályával konzisztensen)
//
// A bejövő EKÁER-bejelentés a GrossWeightOnDocument-et olvassa.
// A raklapszám a rekeszszámból SZÁRMAZTATOTT, nem az AI olvassa ki: a szállítóleveleken
// gyakran nincs is rajta, az AI pedig tapasztalat szerint rendre eltéveszti. A számítás
// FELFELÉ kerekít — 160 rekesz 120-as rekesz/raklap aránynál 1,33 raklap, ami fizikailag
// KETTŐ. Az admin a kiolvasás-ellenőrzőn felülírhatja (a súllyal ellentétben nincs zárolva),
// de a mennyiség módosításakor újraszámolódik: ha az AI a mennyiséget tévesztette el, a
// javítás után a raklapszámnak is követnie kell.
//
// ⚠️ Ez ITT a PalletsOnDocument-be ír, tehát felülírja a "dokumentum nyers állítása" mezőt.
// A 0.0.8.1+ ágon ez az érték a tört ShippingItem.Pallets mezőbe való (ADR 0004-M9F3),
// és a PalletsOnDocument visszakapja az eredeti szerepét.
var palletsOnDocument = itemDto.PalletsOnDocument;
var cratesPerPallet = productDto?.CratesPerPallet ?? 0;
if (cratesPerPallet > 0 && itemDto.QuantityOnDocument > 0)
palletsOnDocument = (int)Math.Ceiling((double)itemDto.QuantityOnDocument / cratesPerPallet);
var grossWeightOnDocument = itemDto.GrossWeightOnDocument;
var netWeightOnDocument = itemDto.NetWeightOnDocument;
// NEM MÉRENDŐ terméknél MINDIG számolunk, akkor is, ha az AI adott súlyt: a nem mérendő áru
// súlya a törzsadatból következik (rekesz × átlagsúly), és az megbízhatóbb, mint egy
// szállítólevélről kiolvasott szám. A kliens ugyanezt teszi, hogy a mentett érték egyezzen
// azzal, amit az admin a felületen látott.
if (productDto is { IsMeasurable: false })
{
if (grossWeightOnDocument <= 0)
grossWeightOnDocument = itemDto.PalletsOnDocument * FruitBankConstClient.PalletWeightKg
+ itemDto.QuantityOnDocument * (productDto.Tare + productDto.AverageWeight);
grossWeightOnDocument = palletsOnDocument * FruitBankConstClient.PalletWeightKg
+ itemDto.QuantityOnDocument * (productDto.Tare + productDto.AverageWeight);
if (netWeightOnDocument <= 0)
netWeightOnDocument = itemDto.QuantityOnDocument * productDto.AverageWeight;
netWeightOnDocument = itemDto.QuantityOnDocument * productDto.AverageWeight;
}
var shippingItem = new ShippingItem
@@ -1075,7 +1130,7 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
HungarianName = itemDto.HungarianName,
NameOnDocument = itemDto.NameOnDocument,
ProductId = itemDto.ProductId,
PalletsOnDocument = itemDto.PalletsOnDocument,
PalletsOnDocument = palletsOnDocument,
QuantityOnDocument = itemDto.QuantityOnDocument,
NetWeightOnDocument = netWeightOnDocument,
GrossWeightOnDocument = grossWeightOnDocument,
@@ -1097,37 +1152,56 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
Console.WriteLine($"✓ Shipping document saved - ID: {shippingDocument.Id}, Document ID: {shippingDocument.DocumentIdNumber}");
// ✅ STEP 2: NOW save the original file (only if document saved successfully)
//
// ⚠️ SAJÁT try/catch, szándékosan: a PDF melléklet, a készlet-frissítés (STEP 3) viszont a
// lényeg. Korábban a fájlírás hibája a KÜLSŐ catch-be ugrott, és ezzel a dokumentum + tételek
// már mentve maradtak, de az IncomingQuantity és az előrendelés-konverzió SOSEM futott le —
// némán, részlegesen elhasalt mentés. (A kiváltó eset a fájlnévben lévő "|" volt, de a lemez
// betelése vagy egy zárolt fájl ugyanezt okozná.) A fájlhiba ezért itt megáll: naplózunk,
// és a válaszban jelezzük, de a mentés maradéka lefut.
string fileWarning = null;
if (originalFile != null && originalFile.Length > 0)
{
var currentUser = await _workContext.GetCurrentCustomerAsync();
using (var fileStream = originalFile.OpenReadStream())
try
{
var savedFile = await _fileStorageService.SaveFileAsync(
fileStream: fileStream,
fileName: originalFile.FileName,
userId: currentUser.Id,
featureName: "ShippingDocumentProcessing",
entityType: "ShippingDocuments",
entityId: shippingDocument.Id, // ✅ Now we have the real ID!
rawText: extractedText,
checkForDuplicates: true
);
var currentUser = await _workContext.GetCurrentCustomerAsync();
// Create mapping between ShippingDocument and File
var mapping = new ShippingDocumentToFiles
using (var fileStream = originalFile.OpenReadStream())
{
FilesId = savedFile.Id,
ShippingDocumentId = shippingDocument.Id,
DocumentTypeId = (int)DocumentType.ShippingDocument,
Created = DateTime.UtcNow,
Modified = DateTime.UtcNow
};
var savedFile = await _fileStorageService.SaveFileAsync(
fileStream: fileStream,
fileName: safeOriginalFileName,
userId: currentUser.Id,
featureName: "ShippingDocumentProcessing",
entityType: "ShippingDocuments",
entityId: shippingDocument.Id, // ✅ Now we have the real ID!
rawText: extractedText,
checkForDuplicates: true
);
await _dbContext.ShippingDocumentToFiles.InsertAsync(mapping);
// Create mapping between ShippingDocument and File
var mapping = new ShippingDocumentToFiles
{
FilesId = savedFile.Id,
ShippingDocumentId = shippingDocument.Id,
DocumentTypeId = (int)DocumentType.ShippingDocument,
Created = DateTime.UtcNow,
Modified = DateTime.UtcNow
};
await _dbContext.ShippingDocumentToFiles.InsertAsync(mapping);
Console.WriteLine($"✓ Original file saved - File ID: {savedFile.Id}, Filename: {savedFile.FileName}");
Console.WriteLine($"✓ Original file saved - File ID: {savedFile.Id}, Filename: {savedFile.FileName}");
}
}
catch (Exception fileEx)
{
fileWarning = $"A dokumentum és a tételek mentve, de a PDF csatolása nem sikerült: {fileEx.Message}";
Console.Error.WriteLine(
$"[SaveShippingDocument] A fájl mentése nem sikerült (dokumentum #{shippingDocument.Id}, fájl: '{safeOriginalFileName}'). "
+ $"A dokumentum és a tételek mentve, a készlet-frissítés folytatódik. {fileEx}");
}
}
else
@@ -1185,7 +1259,11 @@ namespace Nop.Plugin.Misc.FruitBank.Controllers
return Json(new
{
success = true,
message = "Shipping document and file saved successfully",
message = fileWarning ?? "Shipping document and file saved successfully",
// A hívó megkülönböztetheti a teljes sikert attól, amikor a mentés lefutott, de a PDF
// nem csatolódott — enélkül a részleges siker némán elveszne.
fileSaved = fileWarning == null,
warning = fileWarning,
shippingDocumentId = shippingDocument.Id,
documentId = shippingDocument.DocumentIdNumber,
itemCount = shippingDocument.ShippingItems.Count
@@ -803,8 +803,12 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
$"/// DocumentIdNumber if exists\r\n /// </summary>\r\n public string DocumentIdNumber {{ get; set; }}\r\n " +
$"/// <summary>\r\n /// \r\n /// </summary>\r\n public DateTime ShippingDate {{ get; set; }}\r\n " +
$"/// <summary>\r\n /// Shipping pickup Contry of origin\r\n /// </summary>\r\n " +
$"public string Country {{ get; set; }}\r\n\t/// <summary>\r\n /// Sum of ShippingItem pallets\r\n " +
$"/// </summary>\r\n public int TotalPallets {{ get; set; }}\r\n\t/// <summary>\r\n " +
// A TotalPallets és a ShippingItem.PalletsOnDocument KIKERÜLT a promptból:
// a raklapszámot a rekeszszámból származtatjuk (rekesz / rekesz-per-raklap,
// felfelé kerekítve), mert a szállítóleveleken gyakran nincs is rajta, és az
// AI rendre eltévesztette. A TotalPallets amúgy is felülíródott a tételek
// összegével, tehát az AI válaszát ott eddig is eldobtuk.
$"public string Country {{ get; set; }}\r\n\t/// <summary>\r\n " +
$"/// Filename of pdf\r\n /// </summary>\r\n\tpublic string PdfFileName {{ get; set; }}\r\n}}\r\n\r\n" +
$"public class ShippingItem\r\n{{\r\n /// <summary>\r\n /// ShippingItem entity primary key\r\n /// " +
$"</summary>\r\n public int Id {{ get; set; }}\r\n /// <summary>\r\n /// " +
@@ -813,8 +817,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
$"<summary>\r\n /// Name of the fruit or vegitable\r\n /// </summary>\r\n " +
$"public string Name {{ get; set; }}\r\n\t/// <summary>\r\n /// Translated Name to Hungarian\r\n " +
$"/// </summary>\r\n public string HungarianName {{ get; set; }}\r\n /// <summary>\r\n " +
$"/// Pallets of fruit or vegitable item\r\n /// </summary>\r\n " +
$"public int PalletsOnDocument {{ get; set; }}\r\n /// <summary>\r\n " +
$"/// Quantity of fruit or vegitable item\r\n /// </summary>\r\n " +
$"public int QuantityOnDocument {{ get; set; }}\r\n /// <summary>\r\n " +
$"/// Net weight in kg. of fruit or vegitable item\r\n /// </summary>\r\n " +
@@ -630,6 +630,11 @@
// ─── Shipping Item Cards ───
function renderShippingItems() {
// A raklapszám és a nem mérendő tételek súlya is számított — a renderelés előtt frissítjük, így a
// kiolvasás utáni ELSŐ megjelenítéskor is a helyes érték látszik, nem 0.
// A raklap MEGELŐZI a súlyt: a bruttó képlet a raklapszámot fogyasztja.
shippingItems.forEach(function (item) { recalcPalletsFor(item); recalcWeightsFor(item); });
const container = document.getElementById('shippingItemsContainer');
container.innerHTML = '';
@@ -684,8 +689,8 @@
<input type="text" class="form-control form-control-sm" value="${escapeHtml(item.nameOnDocument || '')}" data-field="nameOnDocument" data-index="${index}">
</div>
<div>
<label>Raklapok</label>
<input type="number" class="form-control form-control-sm" value="${item.palletsOnDocument || 0}" data-field="palletsOnDocument" data-index="${index}">
<label>Raklapok ${palletsHint(item)}</label>
<input type="number" class="form-control form-control-sm ${palletsClass(item)}" value="${item.palletsOnDocument || 1}" data-field="palletsOnDocument" data-index="${index}">
</div>
<div>
<label>Mennyiség</label>
@@ -693,11 +698,11 @@
</div>
<div>
<label>Nettó súly (kg) ${weightHint(item, item.netWeightOnDocument)}</label>
<input type="number" step="0.01" class="form-control form-control-sm ${weightClass(item, item.netWeightOnDocument)}" value="${item.netWeightOnDocument ? item.netWeightOnDocument.toFixed(2) : '0.00'}" data-field="netWeightOnDocument" data-index="${index}">
<input type="number" step="0.01" class="form-control form-control-sm ${weightClass(item, item.netWeightOnDocument)}" value="${item.netWeightOnDocument ? item.netWeightOnDocument.toFixed(2) : '0.00'}" data-field="netWeightOnDocument" data-index="${index}" ${weightReadonly(item)}>
</div>
<div>
<label>Bruttó súly (kg) ${weightHint(item, item.grossWeightOnDocument)}</label>
<input type="number" step="0.01" class="form-control form-control-sm ${weightClass(item, item.grossWeightOnDocument)}" value="${item.grossWeightOnDocument ? item.grossWeightOnDocument.toFixed(2) : '0.00'}" data-field="grossWeightOnDocument" data-index="${index}">
<input type="number" step="0.01" class="form-control form-control-sm ${weightClass(item, item.grossWeightOnDocument)}" value="${item.grossWeightOnDocument ? item.grossWeightOnDocument.toFixed(2) : '0.00'}" data-field="grossWeightOnDocument" data-index="${index}" ${weightReadonly(item)}>
</div>
<div>
<label>Egységár</label>
@@ -720,7 +725,13 @@
}
shippingItems[idx][field] = value;
if (field === 'palletsOnDocument') updateTotalPallets();
// A MENNYISÉG módosítása újraszámolja a raklapszámot — és a súly SORRENDBEN utána jön,
// mert a bruttó képlet a raklapszámot fogyasztja. Az admin kézi raklap-értéke ilyenkor
// szándékosan felülíródik (lásd recalcPalletsFor).
if (field === 'quantityOnDocument') recalcPalletsFor(shippingItems[idx]);
if (['palletsOnDocument', 'quantityOnDocument'].includes(field)) updateTotalPallets();
// A súlyok azonnal, MENTÉS ELŐTT álljanak elő — az admin eddig csak mentés után látta őket.
if (['palletsOnDocument', 'quantityOnDocument', 'netWeightOnDocument', 'grossWeightOnDocument'].includes(field))
@@ -746,6 +757,14 @@
return (item.isMeasurable && !(value > 0)) ? 'is-invalid border-danger' : '';
}
/* Nem mérendő terméknél a súly MINDIG számított, tehát a kézi szerkesztés értelmetlen: a következő
újraszámolás úgyis felülírná. Ilyenkor csak akkor engedjük szerkeszteni, ha nincs termék-törzsadat,
amiből számolni lehetne. */
function weightReadonly(item) {
var hasMasterData = (item.tare || 0) > 0 || (item.averageWeight || 0) > 0;
return (!item.isMeasurable && hasMasterData) ? 'readonly' : '';
}
function weightHint(item, value) {
if (item.isMeasurable)
return !(value > 0)
@@ -755,24 +774,84 @@
return (value > 0) ? '<small class="text-muted">(számított)</small>' : '';
}
/* A raklapszám a rekeszszámból SZÁRMAZTATOTT — az AI-nak már nem is feladata kiolvasni. A szállítóleveleken
gyakran nincs is rajta, és a kiolvasott érték rendre hibás volt.
FELFELÉ kerekítünk: 160 rekesz 120-as rekesz/raklap aránynál 1,33 raklap, ami fizikailag KETTŐ.
A súllyal ellentétben ez a mező NEM zárolt — javaslat, az admin felülírhatja. De ha a MENNYISÉGET
módosítja, újraszámolódik: az AI a mennyiséget is eltévesztheti, és a javítás után a raklapszámnak
követnie kell. Ezért nincs "kézzel felülírva" jelzés. */
function recalcPalletsFor(item) {
if (!item) return false;
var perPallet = item.cratesPerPallet || 0;
var crates = item.quantityOnDocument || 0;
if (perPallet <= 0 || crates <= 0) return false; // nincs törzsadat → marad, amit a felület jelez
var pallets = Math.ceil(crates / perPallet);
var changed = item.palletsOnDocument !== pallets;
item.palletsOnDocument = pallets;
return changed;
}
/* Hiányzó rekesz/raklap: a mező piros, az érték 1, és a címke elvisz a termékhez, hogy pótolható legyen. */
function palletsClass(item) {
return ((item.cratesPerPallet || 0) > 0) ? '' : 'is-invalid border-danger';
}
function palletsHint(item) {
if ((item.cratesPerPallet || 0) > 0)
return '<small class="text-muted">(számított)</small>';
var link = item.productId
? ` <a href="/Admin/Product/Edit/${item.productId}" target="_blank" rel="noopener">termék</a>`
: '';
return `<span class="text-danger" title="A terméken nincs kitöltve a rekesz/raklap érték, ezért a raklapszám nem számolható. Addig 1 az érték.">⚠ nincs rekesz/raklap</span>${link}`;
}
/* Egyetlen tétel súlyainak beállítása. Visszaadja, hogy változott-e valami (a hívó dönt az újrarajzolásról).
NEM MÉRENDŐ terméknél MINDIG felülírjuk — akkor is, ha az AI adott súlyt. A nem mérendő áru súlya a
törzsadatból következik (rekesz × átlagsúly), és az megbízhatóbb, mint egy szállítólevélről kiolvasott szám.
Ezért ezek a mezők nem is szerkeszthetők nem mérendő terméknél: minden változtatást azonnal felülírnánk.
MÉRENDŐ terméknél soha nem nyúlunk hozzájuk: ott a dokumentum súlya szerződéses adat. */
function recalcWeightsFor(item) {
if (!item || item.isMeasurable) return false;
var tare = item.tare || 0;
var avg = item.averageWeight || 0;
if (tare <= 0 && avg <= 0) return false; // nincs termék-törzsadat, nincs miből
var crates = item.quantityOnDocument || 0;
var pallets = item.palletsOnDocument || 0;
var gross = Math.round((pallets * PALLET_WEIGHT_KG + crates * (tare + avg)) * 100) / 100;
var net = Math.round((crates * avg) * 100) / 100;
var changed = item.grossWeightOnDocument !== gross || item.netWeightOnDocument !== net;
item.grossWeightOnDocument = gross;
item.netWeightOnDocument = net;
return changed;
}
function recalcWeights(idx) {
var item = shippingItems[idx];
if (!item || item.isMeasurable) { renderShippingItems(); return; }
var tare = item.tare || 0;
var avg = item.averageWeight || 0;
var crates = item.quantityOnDocument || 0;
if (tare <= 0 && avg <= 0) { renderShippingItems(); return; } // nincs termék-törzsadat, nincs miből
if (!(item.grossWeightOnDocument > 0))
item.grossWeightOnDocument = Math.round(((item.palletsOnDocument || 0) * PALLET_WEIGHT_KG + crates * (tare + avg)) * 100) / 100;
if (!(item.netWeightOnDocument > 0))
item.netWeightOnDocument = Math.round((crates * avg) * 100) / 100;
recalcWeightsFor(shippingItems[idx]);
renderShippingItems();
}
/* Az ÖSSZES tételre — kiolvasás után és termékváltáskor kell, mert a mező-változás kezelője ilyenkor nem fut le.
Enélkül a frissen kiolvasott tétel 0 súllyal állna, amíg az admin hozzá nem nyúl valamelyik mezőhöz. */
function recalcAllWeights() {
var changed = false;
shippingItems.forEach(function (item) { if (recalcWeightsFor(item)) changed = true; });
return changed;
}
function addNewShippingItem() {
shippingItems.push({
name: '', hungarianName: '', nameOnDocument: '', productId: null,
@@ -910,10 +989,17 @@
function selectProduct(product, itemIndex) {
const name = product.label.split('[')[0].trim();
const prev = shippingItems[itemIndex].productId;
/* A TERMÉK-TÖRZSADATOT is át kell venni, nem csak a nevet és az árat: a súly- és a raklap-számítás
ebből dolgozik. Enélkül termékváltás után a RÉGI termék tárájával / átlagsúlyával / rekesz-per-raklap
értékével számoltunk volna tovább — épp akkor rontva el, amikor az admin egy téves párosítást javít. */
shippingItems[itemIndex] = {
...shippingItems[itemIndex],
productId: product.value, name, hungarianName: name,
unitPriceOnDocument: product.price || 0
unitPriceOnDocument: product.price || 0,
isMeasurable: product.isMeasurable || false,
tare: product.tare || 0,
averageWeight: product.averageWeight || 0,
cratesPerPallet: product.cratesPerPallet || 0
};
renderShippingItems();
showMessage(prev ? `Termék módosítva: ${name}` : `Termék párosítva: ${name}`, prev ? 'info' : 'success');