diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 85101802..e6cc8352 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -68,7 +68,8 @@ "Bash(grep -c -a \"ConversionRate\" FruitBank.Common.Server.dll)", "Bash(grep -c -a \"EurHufRate\" FruitBank.Common.dll FruitBank.Common.Server.dll)", "Bash(ls -la --time-style=+%H:%M FruitBank.Common.dll FruitBank.Common.Server.dll)", - "Bash(find \"H:\\\\\\\\Applications\\\\\\\\Mango\" -name \"Order.cs\" -path \"*/Domain/Orders/*\" 2>&1 | head -3)" + "Bash(find \"H:\\\\\\\\Applications\\\\\\\\Mango\" -name \"Order.cs\" -path \"*/Domain/Orders/*\" 2>&1 | head -3)", + "Bash(f=/h/Applications/Aycode/Source/AyCode.Blazor/AyCode.Blazor.Components/Components/Grids/MgGridBase.razor; echo \"FILE exists: $\\(test -f $f && echo yes\\)\"; echo \"=== DxGrid nyitó tag + környéke ===\"; grep -n \"DxGrid\\\\|CustomizeDataRowFilter\\\\|CustomizeElement\\\\|@attributes\\\\|CustomizeEditModel\" \"$f\" 2>/dev/null | head -15)" ] } } diff --git a/FruitBank.Common.Server/Services/Ekaer/FruitBankEkaerService.cs b/FruitBank.Common.Server/Services/Ekaer/FruitBankEkaerService.cs index 3c1004e3..1f9334f0 100644 --- a/FruitBank.Common.Server/Services/Ekaer/FruitBankEkaerService.cs +++ b/FruitBank.Common.Server/Services/Ekaer/FruitBankEkaerService.cs @@ -72,12 +72,20 @@ public sealed class FruitBankEkaerService : IFruitBankEkaerService private EkaerHistory Finalize(EkaerHistory ekaerHistory, TradeCardType tradeCard, string? currency) { var operation = new TradeCardOperationType { Index = 1, Operation = OperationType.Create, TradeCard = tradeCard }; - var errors = _validator.Validate(operation); + var messages = _validator.Validate(operation); ekaerHistory.XmlDoc = NavXmlHelper.Serialize(tradeCard); ekaerHistory.ConversionRate = EkaerValueCalculator.ResolveRateToHuf(currency, _settings.EurHufRate); - ekaerHistory.Status = errors.Count == 0 ? EkaerStatus.Generated : EkaerStatus.ValidationError; - ekaerHistory.ErrorText = errors.Count == 0 ? null : string.Join(Environment.NewLine, errors.Select(e => e.ErrorMessage)); + // Blokkoló hiba → ValidationError (nem küldhető); csak warning → GeneratedWithWarning (küldhető, de pótlandó); + // semmi → Generated. Az üzeneteket súlyossággal prefixeljük, hogy a detail-nézet megkülönböztesse őket. + ekaerHistory.Status = messages.HasErrors() ? EkaerStatus.ValidationError + : messages.HasWarnings() ? EkaerStatus.GeneratedWithWarning + : EkaerStatus.Generated; + // Error-ok elöl, warningok hátul (a detail-nézet soronként színez); a prefix [Error]/[Warning] hordozza a szintet. + ekaerHistory.ErrorText = messages.Count == 0 ? null + : string.Join(Environment.NewLine, messages + .OrderByDescending(m => m.GetSeverity()) + .Select(m => $"[{m.GetSeverity()}] {m.ErrorMessage}")); return ekaerHistory; } } diff --git a/FruitBank.Common/Entities/EkaerHistory.cs b/FruitBank.Common/Entities/EkaerHistory.cs index 204c3a81..d2113409 100644 --- a/FruitBank.Common/Entities/EkaerHistory.cs +++ b/FruitBank.Common/Entities/EkaerHistory.cs @@ -55,19 +55,55 @@ public sealed class EkaerHistory: MgEntityBase, ITimeStampInfo public DateTime Modified { get; set; } } -/// Az EKÁER-bejelentés életciklus-állapota. Append-only: új érték a végére, meglévő értéke nem változhat (DB-ben int-ként tárolt). +/// Az EKÁER-bejelentés életciklus + kimenet állapota. Append-only: új érték a végére, meglévő értéke nem +/// változhat (DB-ben int-ként tárolt). Az állapot egyetlen mező; a kategorizálás a +/// segédekkel megy (külön oszlop nélkül) — a folyamat lineáris, ezért a single-enum állapotgép a tiszta illeszkedés. public enum EkaerStatus { /// Automatikusan létrejött, még nem volt Generate. Pending = 0, /// A tradeCard XML legenerálva és valid — küldhető. Generated = 1, - /// A Generate validációs hibákkal zárult (ErrorText) — a forrásadat javítandó. + /// A Generate BLOKKOLÓ (error) hibákkal zárult (ErrorText) — a forrásadat javítandó, nem küldhető. ValidationError = 2, - /// NAV által befogadva (EkaerNumber + SentDate töltve). + /// NAV által befogadva (EkaerNumber + SentDate töltve), hiánytalanul. Sent = 3, /// A NAV-hívás hibával zárult (ErrorText) — újraküldhető. SendError = 4, + /// Legenerálva és KÜLDHETŐ, de warninggal (pl. hiányzó rendszám, ami a felrakodás megkezdéséig pótolható). + GeneratedWithWarning = 5, + /// NAV-hoz beküldve, de PÓTLÁSRA VÁR (pl. a rendszám a felrakodás megkezdéséig megadandó). + SentWithMissingData = 6, +} + +/// Állapot-kategóriák — a tárolás egy mező (Status), a csoportosítás ezekkel (külön oszlop nélkül). +public static class EkaerStatusExtensions +{ + /// Blokkoló hibás állapot (a bejelentés nem küldhető / a NAV-hívás bukott). + public static bool IsError(this EkaerStatus status) => status is EkaerStatus.ValidationError or EkaerStatus.SendError; + + /// Beküldhető: legenerálva, blokkoló hiba nélkül (warninggal is) — innen mehet a NAV-hoz. + public static bool IsSubmittable(this EkaerStatus status) => status is EkaerStatus.Generated or EkaerStatus.GeneratedWithWarning; + + /// A NAV-nál van (akár hiányosan). + public static bool IsSent(this EkaerStatus status) => status is EkaerStatus.Sent or EkaerStatus.SentWithMissingData; + + /// Elküldve, de pótlásra vár (hiányos adat — pl. rendszám). + public static bool NeedsCompletion(this EkaerStatus status) => status is EkaerStatus.SentWithMissingData; +} + +/// EkaerHistory-lekérdezés szűrő a tabokhoz. = 0 (minden) — szándékosan explicit, NEM nullable, +/// hogy ne kelljen null-t kezelni sehol; a default érték (0) automatikusan „minden", ami biztonságos. +public enum EkaerHistoryFilter +{ + /// Minden rekord (nincs szűrés). + All = 0, + /// Beküldésre váró: minden, ami még NINCS a NAV-nál (Pending/Generated/GeneratedWithWarning/ValidationError/SendError). + ToSubmit, + /// Elküldött és hiánytalan (). + Sent, + /// Elküldve, de pótlásra vár (). + NeedsCompletion, } //public sealed class EkaerHistoryShipping : EkaerHistoryBase diff --git a/FruitBank.Common/Interfaces/IFruitBankDataControllerCommon.cs b/FruitBank.Common/Interfaces/IFruitBankDataControllerCommon.cs index 9c7f89bc..4152eb0c 100644 --- a/FruitBank.Common/Interfaces/IFruitBankDataControllerCommon.cs +++ b/FruitBank.Common/Interfaces/IFruitBankDataControllerCommon.cs @@ -29,7 +29,7 @@ public interface IFruitBankDataControllerCommon #endregion PartnerDepot #region EkaerHistory - public Task?> GetEkaerHistories(); + public Task?> GetEkaerHistories(EkaerHistoryFilter filter); public Task GetEkaerHistoryById(int id); public Task?> GetEkaerHistoriesByForeignKey(int foreignKey); public Task AddEkaerHistory(EkaerHistory ekaerHistory); diff --git a/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerDetail.razor b/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerDetail.razor index f6a2e487..072627fa 100644 --- a/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerDetail.razor +++ b/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerDetail.razor @@ -30,8 +30,13 @@ @if (!string.IsNullOrWhiteSpace(ErrorText)) { - @* A generálás/küldés hibái egyben olvashatóan — soronként (a validátor NewLine-nal fűzi). *@ -
@ErrorText
+ @* A generálás/küldés üzenetei SORONKÉNT, a saját súlyosságukkal színezve: error → piros, warning → arany/sárga. *@ +
+ @foreach (var line in ErrorMessageLines) + { +
@line.Text
+ } +
} @@ -67,6 +72,25 @@ private string? ErrorText => (ParentDataItem as EkaerHistory)?.ErrorText; + // Soronkénti üzenet + súlyosság az [Error]/[Warning] prefixből (a service így fűzi). Prefix nélküli (pl. config-hiba) → error. + private IEnumerable<(bool IsError, string Text)> ErrorMessageLines + { + get + { + if (string.IsNullOrWhiteSpace(ErrorText)) yield break; + + foreach (var raw in ErrorText.Split('\n')) + { + var line = raw.Trim(); + if (line.Length == 0) continue; + + if (line.StartsWith("[Warning]")) yield return (false, line["[Warning]".Length..].TrimStart()); + else if (line.StartsWith("[Error]")) yield return (true, line["[Error]".Length..].TrimStart()); + else yield return (true, line); + } + } + } + private AcObservableCollection TradeCardItems = []; private LoggerClient _logger; diff --git a/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistory.razor b/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistory.razor index 0d67a925..b8861425 100644 --- a/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistory.razor +++ b/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistory.razor @@ -19,6 +19,7 @@ @* Audit: a value-számításhoz alkalmazott árfolyam (HUF feladónál 1). ReadOnly — generáláskor töltődik. *@ - + @* Üzenet-oszlop: warningot ÉS errort is mutat. Ikon a súlyosság szerint: ⛔ blokkoló hiba, ⚠️ warning (pótlandó). *@ + @if (!string.IsNullOrWhiteSpace(context.DisplayText)) { + var isError = ((EkaerHistory)context.DataItem).Status.IsError(); - @context.DisplayText + @context.DisplayText } @@ -137,6 +140,9 @@ [Parameter] public IId? ParentDataItem { get; set; } + /// Szerver-oldali szűrő (a tabok adják): ToSubmit / Sent / NeedsCompletion. Default All = minden. + [Parameter] public EkaerHistoryFilter Filter { get; set; } = EkaerHistoryFilter.All; + public bool IsMasterGrid => ParentDataItem == null; private LoggerClient _logger; @@ -172,14 +178,13 @@ private readonly HashSet _generatingIds = []; - // Elküldött bejelentést nem generálunk újra némán (az már a NAV-nál van — módosítás külön művelet lesz). - // Bejövő és kimenő (Order) is generálható; a kimenőnél a fuvar-adat bekötéséig a validátor jelzi a hiányt. + // Elküldött (NAV-nál lévő) bejelentést nem generálunk újra némán; minden más állapot újragenerálható. private bool CanGenerate(EkaerHistory ekaerHistory) - => ekaerHistory.Status != EkaerStatus.Sent && !_generatingIds.Contains(ekaerHistory.Id); + => !ekaerHistory.Status.IsSent() && !_generatingIds.Contains(ekaerHistory.Id); - // Hibás (validációs hibás) bejelentés XML-jét nem adjuk a user kezébe — ne kerülhessen kézzel a NAV-hoz. + // Csak BLOKKOLÓ hiba (ValidationError) esetén NEM másolható; a warning (pl. hiányzó rendszám) küldhető → másolható. private static bool CanCopy(EkaerHistory ekaerHistory) - => !string.IsNullOrWhiteSpace(ekaerHistory.XmlDoc) && string.IsNullOrWhiteSpace(ekaerHistory.ErrorText); + => ekaerHistory.Status.IsSubmittable() && !string.IsNullOrWhiteSpace(ekaerHistory.XmlDoc); private async Task OnGenerateClick(EkaerHistory ekaerHistory) { diff --git a/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistoryBase.cs b/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistoryBase.cs index e63824f8..c3b27be1 100644 --- a/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistoryBase.cs +++ b/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistoryBase.cs @@ -12,6 +12,9 @@ public class GridEkaerHistoryBase : FruitBankGridBase, IGrid private bool _isFirstInitializeParameterCore; private bool _isFirstInitializeParameters; + /// Szerver-oldali szűrő a master-grid lekérdezéséhez (a tabok adják át). All = minden. + [Parameter] public EkaerHistoryFilter Filter { get; set; } = EkaerHistoryFilter.All; + public GridEkaerHistoryBase() : base() { //GetAllMessageTag = SignalRTags.GetEkaerHistories; @@ -25,7 +28,11 @@ public class GridEkaerHistoryBase : FruitBankGridBase, IGrid { if (GetAllMessageTag > 0) return; - if (IsMasterGrid) GetAllMessageTag = SignalRTags.GetEkaerHistories; + if (IsMasterGrid) + { + GetAllMessageTag = SignalRTags.GetEkaerHistories; + ContextIds = [Filter]; // szerver-oldali szűrő a tabokhoz (All = minden) — a GetEkaerHistories(filter) param-ja + } else { if (ContextIds == null || ContextIds.Length == 0) ContextIds = [ParentDataItem!.Id]; diff --git a/FruitBankHybrid.Shared/Pages/Ekaer.razor b/FruitBankHybrid.Shared/Pages/Ekaer.razor index 7f4f6534..a4ce3750 100644 --- a/FruitBankHybrid.Shared/Pages/Ekaer.razor +++ b/FruitBankHybrid.Shared/Pages/Ekaer.razor @@ -1,4 +1,5 @@ @page "/Ekaer" +@using FruitBank.Common.Entities @using FruitBankHybrid.Shared.Components @using FruitBankHybrid.Shared.Components.Grids.Ekaers @using FruitBankHybrid.Shared.Databases @@ -16,11 +17,13 @@ - @* TODO: "váró vs. elküldött" szűrés — az EkaerHistory-ban még nincs állapot-mező. *@ - + - + + + + diff --git a/FruitBankHybrid.Shared/Pages/Ekaer.razor.cs b/FruitBankHybrid.Shared/Pages/Ekaer.razor.cs index a9861584..d7c2ecef 100644 --- a/FruitBankHybrid.Shared/Pages/Ekaer.razor.cs +++ b/FruitBankHybrid.Shared/Pages/Ekaer.razor.cs @@ -15,6 +15,7 @@ public partial class Ekaer : ComponentBase private GridEkaerHistory gridEkaerHistoryPending; private GridEkaerHistory gridEkaerHistorySent; + private GridEkaerHistory gridEkaerHistoryNeedsCompletion; private ILogger _logger = null!; public int ActiveTabIndex; diff --git a/FruitBankHybrid.Shared/Services/SignalRs/FruitBankSignalRClient.cs b/FruitBankHybrid.Shared/Services/SignalRs/FruitBankSignalRClient.cs index 2f85f620..a620ea2a 100644 --- a/FruitBankHybrid.Shared/Services/SignalRs/FruitBankSignalRClient.cs +++ b/FruitBankHybrid.Shared/Services/SignalRs/FruitBankSignalRClient.cs @@ -74,7 +74,7 @@ namespace FruitBankHybrid.Shared.Services.SignalRs #endregion PartnerDepot #region EkaerHistory - public Task?> GetEkaerHistories() => GetAllAsync>(SignalRTags.GetEkaerHistories); + public Task?> GetEkaerHistories(EkaerHistoryFilter filter) => GetAllAsync>(SignalRTags.GetEkaerHistories, [filter]); public Task GetEkaerHistoryById(int id) => GetByIdAsync(SignalRTags.GetEkaerHistoryById, id); public Task?> GetEkaerHistoriesByForeignKey(int foreignKey) => GetAllAsync>(SignalRTags.GetEkaerHistoriesByForeignKey, [foreignKey]); public Task AddEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.AddEkaerHistory, ekaerHistory);