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:
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user