EKÁER: add warning/error distinction, status, and filters
- Validation now distinguishes blocking errors from warnings; only errors block submission, warnings are shown but allow progress. - Introduced EkaerSeverity, EkaerValidationMessage, and severity-aware extensions for validation results. - Extended EkaerHistory.Status with GeneratedWithWarning and SentWithMissingData; added EkaerStatusExtensions for status categorization. - Added EkaerHistoryFilter for server-side filtering; updated SignalR and controller interfaces accordingly. - UI now displays error/warning messages with color/icons, supports new status tabs/filters, and restricts XML copying to submittable records. - Refactored validation and error handling to use new severity logic; updated comments and minor code for clarity.
This commit is contained in:
parent
9383041504
commit
1e41632599
|
|
@ -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)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,19 +55,55 @@ public sealed class EkaerHistory: MgEntityBase, ITimeStampInfo
|
|||
public DateTime Modified { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>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).</summary>
|
||||
/// <summary>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 <see cref="EkaerStatusExtensions"/>
|
||||
/// 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.</summary>
|
||||
public enum EkaerStatus
|
||||
{
|
||||
/// <summary>Automatikusan létrejött, még nem volt Generate.</summary>
|
||||
Pending = 0,
|
||||
/// <summary>A tradeCard XML legenerálva és valid — küldhető.</summary>
|
||||
Generated = 1,
|
||||
/// <summary>A Generate validációs hibákkal zárult (ErrorText) — a forrásadat javítandó.</summary>
|
||||
/// <summary>A Generate BLOKKOLÓ (error) hibákkal zárult (ErrorText) — a forrásadat javítandó, nem küldhető.</summary>
|
||||
ValidationError = 2,
|
||||
/// <summary>NAV által befogadva (EkaerNumber + SentDate töltve).</summary>
|
||||
/// <summary>NAV által befogadva (EkaerNumber + SentDate töltve), hiánytalanul.</summary>
|
||||
Sent = 3,
|
||||
/// <summary>A NAV-hívás hibával zárult (ErrorText) — újraküldhető.</summary>
|
||||
SendError = 4,
|
||||
/// <summary>Legenerálva és KÜLDHETŐ, de warninggal (pl. hiányzó rendszám, ami a felrakodás megkezdéséig pótolható).</summary>
|
||||
GeneratedWithWarning = 5,
|
||||
/// <summary>NAV-hoz beküldve, de PÓTLÁSRA VÁR (pl. a rendszám a felrakodás megkezdéséig megadandó).</summary>
|
||||
SentWithMissingData = 6,
|
||||
}
|
||||
|
||||
/// <summary>Állapot-kategóriák — a tárolás egy mező (Status), a csoportosítás ezekkel (külön oszlop nélkül).</summary>
|
||||
public static class EkaerStatusExtensions
|
||||
{
|
||||
/// <summary>Blokkoló hibás állapot (a bejelentés nem küldhető / a NAV-hívás bukott).</summary>
|
||||
public static bool IsError(this EkaerStatus status) => status is EkaerStatus.ValidationError or EkaerStatus.SendError;
|
||||
|
||||
/// <summary>Beküldhető: legenerálva, blokkoló hiba nélkül (warninggal is) — innen mehet a NAV-hoz.</summary>
|
||||
public static bool IsSubmittable(this EkaerStatus status) => status is EkaerStatus.Generated or EkaerStatus.GeneratedWithWarning;
|
||||
|
||||
/// <summary>A NAV-nál van (akár hiányosan).</summary>
|
||||
public static bool IsSent(this EkaerStatus status) => status is EkaerStatus.Sent or EkaerStatus.SentWithMissingData;
|
||||
|
||||
/// <summary>Elküldve, de pótlásra vár (hiányos adat — pl. rendszám).</summary>
|
||||
public static bool NeedsCompletion(this EkaerStatus status) => status is EkaerStatus.SentWithMissingData;
|
||||
}
|
||||
|
||||
/// <summary>EkaerHistory-lekérdezés szűrő a tabokhoz. <see cref="All"/> = 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.</summary>
|
||||
public enum EkaerHistoryFilter
|
||||
{
|
||||
/// <summary>Minden rekord (nincs szűrés).</summary>
|
||||
All = 0,
|
||||
/// <summary>Beküldésre váró: minden, ami még NINCS a NAV-nál (Pending/Generated/GeneratedWithWarning/ValidationError/SendError).</summary>
|
||||
ToSubmit,
|
||||
/// <summary>Elküldött és hiánytalan (<see cref="EkaerStatus.Sent"/>).</summary>
|
||||
Sent,
|
||||
/// <summary>Elküldve, de pótlásra vár (<see cref="EkaerStatus.SentWithMissingData"/>).</summary>
|
||||
NeedsCompletion,
|
||||
}
|
||||
|
||||
//public sealed class EkaerHistoryShipping : EkaerHistoryBase
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public interface IFruitBankDataControllerCommon
|
|||
#endregion PartnerDepot
|
||||
|
||||
#region EkaerHistory
|
||||
public Task<List<EkaerHistory>?> GetEkaerHistories();
|
||||
public Task<List<EkaerHistory>?> GetEkaerHistories(EkaerHistoryFilter filter);
|
||||
public Task<EkaerHistory?> GetEkaerHistoryById(int id);
|
||||
public Task<List<EkaerHistory>?> GetEkaerHistoriesByForeignKey(int foreignKey);
|
||||
public Task<EkaerHistory?> AddEkaerHistory(EkaerHistory ekaerHistory);
|
||||
|
|
|
|||
|
|
@ -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). *@
|
||||
<div class="text-danger" style="padding:0 12px 8px 12px; font-size:0.85rem; white-space:pre-line;">@ErrorText</div>
|
||||
@* A generálás/küldés üzenetei SORONKÉNT, a saját súlyosságukkal színezve: error → piros, warning → arany/sárga. *@
|
||||
<div style="padding:0 12px 8px 12px; font-size:0.85rem;">
|
||||
@foreach (var line in ErrorMessageLines)
|
||||
{
|
||||
<div style="color:@(line.IsError ? "#dc3545" : "#caa000");">@line.Text</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<MgGridWithInfoPanel ShowInfoPanel="false">
|
||||
|
|
@ -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<TradeCardItemType> TradeCardItems = [];
|
||||
|
||||
private LoggerClient<GridEkaerDetail> _logger;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
<GridEkaerHistoryBase @ref="Grid"
|
||||
DataSource="EkaerHistories"
|
||||
ParentDataItem="ParentDataItem"
|
||||
Filter="Filter"
|
||||
AutoSaveLayoutName="GridEkaerHistory"
|
||||
SignalRClient="FruitBankSignalRClient"
|
||||
Logger="_logger"
|
||||
|
|
@ -35,12 +36,14 @@
|
|||
<DxGridDataColumn FieldName="@nameof(EkaerHistory.SentDate)" DisplayFormat="yyyy.MM.dd HH:mm" />
|
||||
@* Audit: a value-számításhoz alkalmazott árfolyam (HUF feladónál 1). ReadOnly — generáláskor töltődik. *@
|
||||
<DxGridDataColumn FieldName="@nameof(EkaerHistory.ConversionRate)" Caption="Árfolyam" ReadOnly="true" DisplayFormat="0.00##" />
|
||||
<DxGridDataColumn FieldName="@nameof(EkaerHistory.ErrorText)" Caption="Hiba" ReadOnly="true">
|
||||
@* Üzenet-oszlop: warningot ÉS errort is mutat. Ikon a súlyosság szerint: ⛔ blokkoló hiba, ⚠️ warning (pótlandó). *@
|
||||
<DxGridDataColumn FieldName="@nameof(EkaerHistory.ErrorText)" Caption="Üzenet" ReadOnly="true">
|
||||
<CellDisplayTemplate>
|
||||
@if (!string.IsNullOrWhiteSpace(context.DisplayText))
|
||||
{
|
||||
var isError = ((EkaerHistory)context.DataItem).Status.IsError();
|
||||
<span title="@context.DisplayText">
|
||||
<span aria-hidden="true" style="font-size:1.2em; margin-right:3px;">⚠️</span>@context.DisplayText
|
||||
<span aria-hidden="true" style="font-size:1.2em; margin-right:3px;">@(isError ? "⛔" : "⚠️")</span>@context.DisplayText
|
||||
</span>
|
||||
}
|
||||
</CellDisplayTemplate>
|
||||
|
|
@ -137,6 +140,9 @@
|
|||
|
||||
[Parameter] public IId<int>? ParentDataItem { get; set; }
|
||||
|
||||
/// <summary>Szerver-oldali szűrő (a tabok adják): ToSubmit / Sent / NeedsCompletion. Default All = minden.</summary>
|
||||
[Parameter] public EkaerHistoryFilter Filter { get; set; } = EkaerHistoryFilter.All;
|
||||
|
||||
public bool IsMasterGrid => ParentDataItem == null;
|
||||
|
||||
private LoggerClient<GridEkaerHistory> _logger;
|
||||
|
|
@ -172,14 +178,13 @@
|
|||
|
||||
private readonly HashSet<int> _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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ public class GridEkaerHistoryBase : FruitBankGridBase<EkaerHistory>, IGrid
|
|||
private bool _isFirstInitializeParameterCore;
|
||||
private bool _isFirstInitializeParameters;
|
||||
|
||||
/// <summary>Szerver-oldali szűrő a master-grid lekérdezéséhez (a tabok adják át). All = minden.</summary>
|
||||
[Parameter] public EkaerHistoryFilter Filter { get; set; } = EkaerHistoryFilter.All;
|
||||
|
||||
public GridEkaerHistoryBase() : base()
|
||||
{
|
||||
//GetAllMessageTag = SignalRTags.GetEkaerHistories;
|
||||
|
|
@ -25,7 +28,11 @@ public class GridEkaerHistoryBase : FruitBankGridBase<EkaerHistory>, 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];
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
|
||||
<DxTabs ActiveTabIndexChanged="(i) => OnActiveTabChanged(i)" RenderMode="TabsRenderMode.OnDemand" AllowTabReorder="true">
|
||||
<DxTabPage Text="Beküldésre váró">
|
||||
@* TODO: "váró vs. elküldött" szűrés — az EkaerHistory-ban még nincs állapot-mező. *@
|
||||
<GridEkaerHistory @ref="gridEkaerHistoryPending"></GridEkaerHistory>
|
||||
<GridEkaerHistory @ref="gridEkaerHistoryPending" Filter="EkaerHistoryFilter.ToSubmit"></GridEkaerHistory>
|
||||
</DxTabPage>
|
||||
<DxTabPage Text="Elküldött">
|
||||
<GridEkaerHistory @ref="gridEkaerHistorySent"></GridEkaerHistory>
|
||||
<GridEkaerHistory @ref="gridEkaerHistorySent" Filter="EkaerHistoryFilter.Sent"></GridEkaerHistory>
|
||||
</DxTabPage>
|
||||
<DxTabPage Text="Pótlásra váró">
|
||||
<GridEkaerHistory @ref="gridEkaerHistoryNeedsCompletion" Filter="EkaerHistoryFilter.NeedsCompletion"></GridEkaerHistory>
|
||||
</DxTabPage>
|
||||
</DxTabs>
|
||||
</DxLoadingPanel>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
|||
#endregion PartnerDepot
|
||||
|
||||
#region EkaerHistory
|
||||
public Task<List<EkaerHistory>?> GetEkaerHistories() => GetAllAsync<List<EkaerHistory>>(SignalRTags.GetEkaerHistories);
|
||||
public Task<List<EkaerHistory>?> GetEkaerHistories(EkaerHistoryFilter filter) => GetAllAsync<List<EkaerHistory>>(SignalRTags.GetEkaerHistories, [filter]);
|
||||
public Task<EkaerHistory?> GetEkaerHistoryById(int id) => GetByIdAsync<EkaerHistory?>(SignalRTags.GetEkaerHistoryById, id);
|
||||
public Task<List<EkaerHistory>?> GetEkaerHistoriesByForeignKey(int foreignKey) => GetAllAsync<List<EkaerHistory>>(SignalRTags.GetEkaerHistoriesByForeignKey, [foreignKey]);
|
||||
public Task<EkaerHistory?> AddEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.AddEkaerHistory, ekaerHistory);
|
||||
|
|
|
|||
Loading…
Reference in New Issue