Merge branch 'FruitBank_v0.0.8.2' of https://git.aycode.com/Adam/FruitBankHybridApp into FruitBank_v0.0.8.2

This commit is contained in:
2026-08-21 15:10:37 +02:00
6 changed files with 538 additions and 313 deletions
+2
View File
@@ -21,3 +21,5 @@ The protocol names operations abstractly. Map them to Claude Code tools:
- **edit** -> `Edit`, `Write`
- **shell** -> `Bash`, `PowerShell`
- **clock** -> `PowerShell`: `Get-Date -Format "yyyy-MM-dd HH:mm"`
A literal backslash in a tool argument is built from a character code (`[char]92`, `chr(92)`), never typed.
@@ -10,9 +10,10 @@
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
@inject FruitBankSignalRClient FruitBankSignalRClient
@inject IJSRuntime JS
@* Alapból a generált kártya, mert a user azzal dolgozik — a NAV verziója a referencia. A NAV-opció akkor is
LÁTSZIK, ha még nincs lekérdezve, csak tiltva: így kiderül, hogy van hova frissíteni. *@
VÁLASZTHATÓ, ha még nincs lekérdezve (lásd HasNavSource): az „Állapot frissítése" gomb azon az ágon áll. *@
<div style="padding:8px 12px 0 12px;">
<DxRadioGroup TData="SourceOption"
TValue="DetailSource"
@@ -92,16 +93,31 @@
@* A fenti választó dönti el, melyik kártya XML-je látszik — a fejléc és a tétel-grid ugyanazt mutatja,
tehát a detail egészében EGY dolgot mutat egyszerre. Így teljes szélességben olvasható. *@
<div style="padding:8px 12px; display:flex; align-items:center; gap:12px;">
<DxButton Text="@(_refreshing ? "Frissítés…" : "Állapot frissítése")"
@* A letöltés MINDKÉT forráshoz kell — a generált kártya és a NAV-é is audit-anyag —, ezért a
választótól függetlenül itt áll, és azt menti, ami épp látszik. A frissítésnek viszont csak a
NAV-ágon van tárgya, ott is áll egyedül. *@
<DxButton Text="Letöltés"
SizeMode="SizeMode.Small"
RenderStyle="ButtonRenderStyle.Secondary"
Enabled="@CanRefreshNavState"
Attributes="@(new Dictionary<string, object> { ["title"] = CanRefreshNavState ? "A NAV-nál lévő aktuális állapot visszaolvasása" : "Csak beküldött bejelentésnek van NAV-oldali állapota." })"
Click="OnRefreshNavStateClick" />
Enabled="@CanDownload"
Attributes="@(new Dictionary<string, object> { ["title"] = CanDownload ? "A megjelenített XML mentése fájlba" : "Ehhez a forráshoz még nincs XML." })"
Click="OnDownloadClick" />
@if (!string.IsNullOrWhiteSpace(_refreshMessage))
@if (_source == DetailSource.Nav)
{
<span style="font-size:0.85rem; color:@(_refreshFailed ? "#dc3545" : "#6c757d");">@_refreshMessage</span>
<DxButton Text="@(_refreshing ? "Frissítés…" : "Állapot frissítése")"
SizeMode="SizeMode.Small"
RenderStyle="ButtonRenderStyle.Secondary"
Enabled="@CanRefreshNavState"
Attributes="@(new Dictionary<string, object> { ["title"] = CanRefreshNavState ? "A NAV-nál lévő aktuális állapot visszaolvasása" : "Csak beküldött bejelentésnek van NAV-oldali állapota." })"
Click="OnRefreshNavStateClick" />
@* A visszajelzés a frissítésé, ezért vele együtt tűnik el: a generált ágon egy „Frissítve."
a Letöltés mellett arra vonatkozna, amit nem is az töltött be. *@
@if (!string.IsNullOrWhiteSpace(_refreshMessage))
{
<span style="font-size:0.85rem; color:@(_refreshFailed ? "#dc3545" : "#6c757d");">@_refreshMessage</span>
}
}
</div>
@@ -133,11 +149,16 @@
/// <summary>Publikus, mert a DevExpress a mezőneveket reflexióval olvassa ki.</summary>
public sealed record SourceOption(DetailSource Value, string Text, bool Enabled);
/// <summary>A NAV-verzió csak akkor választható, ha már van — de akkor is látszik, hogy létezik ez az ág.</summary>
/// <summary>Van-e NAV-ág: már lekérdeztük, VAGY van EKÁER-szám, tehát lekérdezhető.</summary>
/// <remarks>Nem elég a meglévő pillanatképre kötni: a frissítés gombja ezen az ágon áll, tehát tiltott
/// opció mögött elérhetetlen lenne — épp azokon a sorokon, amiknek a NAV-állapota még sosem került le
/// (beküldés a <c>NavXmlDoc</c> oszlop előttről, vagy a befogadás utáni best-effort lekérdezés bukása).</remarks>
private bool HasNavSource => _navCard != null || !string.IsNullOrWhiteSpace(History?.EkaerNumber);
private SourceOption[] SourceOptions =>
[
new(DetailSource.Generated, "Generált", true),
new(DetailSource.Nav, "NAV-nál lévő", _navCard != null),
new(DetailSource.Nav, "NAV-nál lévő", HasNavSource),
];
/// <summary>Alapból a generált: a user azzal dolgozik (generál, javít, beküld) — a NAV verziója a referencia.</summary>
@@ -167,6 +188,12 @@
? "A bejelentés még nincs beküldve."
: "Még nincs lekérdezve — nyomd meg az „Állapot frissítése” gombot.";
/// <summary>Van-e mit letölteni: a megjelenített forrásnak van tartalma.</summary>
/// <remarks>A státusz szándékosan NEM kapu. A fájl itt megjelenítés és audit — ami a képernyőn olvasható,
/// annak a mentését letiltani következetlen —, és a NAV-ág státusza definíció szerint nem beküldhető,
/// tehát beküldhetőségre kötve a NAV-oldali letöltés sosem aktiválódna.</remarks>
private bool CanDownload => !string.IsNullOrWhiteSpace(SelectedXml);
/// <summary>NAV-oldali állapota csak beküldött bejelentésnek van — EKÁER-szám nélkül nincs mit lekérdezni.</summary>
private bool CanRefreshNavState => !_refreshing && !string.IsNullOrWhiteSpace(History?.EkaerNumber);
@@ -194,7 +221,11 @@
else
{
// A rács a saját példányát mutatja, ezért abba írjuk vissza — különben a fül a régit mutatná.
// A NavStatus is kell: az a rács „NAV állapot" OSZLOPA, tehát a hiánya nem csak itt látszana.
// Csak ez a két mező jön át — a frissítés a NAV-oldali pillanatképet hozza, a saját státuszunkat
// és az általunk generált kártyát nem érinti; ezért NEM a rács ApplyServerState-je való ide.
History.NavXmlDoc = refreshed.NavXmlDoc;
History.NavStatus = refreshed.NavStatus;
_navCard = Deserialize<TradeCardInfoType>(History.NavXmlDoc);
if (_source == DetailSource.Nav) RefreshItems();
@@ -216,6 +247,32 @@
}
}
/// <summary>A megjelenített XML mentése fájlba.</summary>
/// <remarks>A fájlnév hordozza a forrást is: a két kártya ugyanahhoz a sorhoz tartozik, azonos néven a
/// letöltési mappában megkülönböztethetetlen lenne.</remarks>
private async Task OnDownloadClick()
{
if (History is null || !CanDownload) return;
// Fájlnév: "yyyy.MM.dd-Partner-forrás.xml" — a partnernévből az érvénytelen fájlnév-karaktereket kicseréljük.
var datePart = History.ShippingDate?.ToString("yyyy.MM.dd") ?? "nincs-datum";
var partner = string.IsNullOrWhiteSpace(History.Partner) ? "partner" : History.Partner;
var invalid = System.IO.Path.GetInvalidFileNameChars();
var safePartner = new string(partner.Select(c => invalid.Contains(c) ? '_' : c).ToArray()).Trim();
if (string.IsNullOrWhiteSpace(safePartner)) safePartner = "partner";
var sourcePart = _source == DetailSource.Nav ? "nav" : "generalt";
var fileName = $"{datePart}-{safePartner}-{sourcePart}.xml";
try
{
await JS.InvokeVoidAsync("downloadFileFromText", fileName, SelectedXml, "application/xml");
}
catch (Exception ex)
{
_logger.Error($"XML letöltése sikertelen; EkaerHistory.Id: {History.Id}; forrás: {_source}", ex);
}
}
// 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
{
@@ -256,8 +313,8 @@
_generatedCard = Deserialize<TradeCardType>(History?.XmlDoc);
_navCard = Deserialize<TradeCardInfoType>(History?.NavXmlDoc);
// A választó eltűnhet a talpunk alól (pl. új sorra váltunk, aminek nincs NAV-verziója).
if (_source == DetailSource.Nav && _navCard is null) _source = DetailSource.Generated;
// A választó eltűnhet a talpunk alól (pl. új sorra váltunk, aminek nincs NAV-ága).
if (_source == DetailSource.Nav && !HasNavSource) _source = DetailSource.Generated;
RefreshItems();
}
@@ -14,18 +14,19 @@
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
@inject FruitBankSignalRClient FruitBankSignalRClient
@inject IJSRuntime JS
@inject IDialogService DialogService
<MgGridWithInfoPanel ShowInfoPanel="@IsMasterGrid">
<GridContent>
@* Az AutoSaveLayoutName fülönként külön: a három fül UGYANAZ a komponens, más Filter-rel, és a mentett
DevExpress-layout egészben tárolódik — rendezés, szélesség, szűrő és oszlop-láthatóság együtt. Közös
néven az egyik fülön beállított rendezés a másikon is előjön.
A komment NEM mehet az attribútum-listába: a Razor ott nem vágja ki, hanem attribútum-névként adja
tovább, és a renderelés InvalidCharacterError-ral elszáll — fordítási hiba NÉLKÜL. *@
<GridEkaerHistoryBase @ref="Grid"
DataSource="EkaerHistories"
ParentDataItem="ParentDataItem"
Filter="Filter"
@* Fülönként külön layout-név: a három fül UGYANAZ a komponens, más Filter-rel, és a mentett
DevExpress-layout egészben tárolódik — rendezés, szélesség, szűrő és oszlop-láthatóság
együtt. Közös néven az egyik fülön beállított rendezés a másikon is előjön. *@
AutoSaveLayoutName="@($"GridEkaerHistory_{Filter}")"
SignalRClient="FruitBankSignalRClient"
Logger="_logger"
@@ -79,22 +80,22 @@
<DxGridDataColumn FieldName="Created" ReadOnly="true" DisplayFormat="yyyy.MM.dd HH:mm" />
<DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="yyyy.MM.dd HH:mm" />
<DxGridDataColumn Caption="Műveletek" Width="290" AllowSort="false" AllowGroup="false">
@* EGY akció-oszlop, a TARTALMA fülfüggő. Nem fülönkénti oszlopok láthatósággal: az oszlop-
láthatóságot a felhasználó állítja a Column Chooserrel, és a mentett layout vissza is tölti —
kódból ugyanazt a kapcsolót fogni ütközés. A tartalom nem mentődik, nincs mivel ütköznie. *@
<DxGridDataColumn Caption="Műveletek" Width="@(ShowOpenAtNavActions ? "290" : "200")" AllowSort="false" AllowGroup="false">
<CellDisplayTemplate>
@{
var row = (EkaerHistory)context.DataItem;
}
@if (ShowToSubmitActions)
{
<DxButton Text="Generate"
SizeMode="SizeMode.Small"
RenderStyle="ButtonRenderStyle.Primary"
Enabled="@(CanGenerate(row))"
Attributes="@(new Dictionary<string, object> { ["title"] = row.ErrorText ?? "EKÁER XML generálása és validálása" })"
Click="async () => await OnGenerateClick(row)" />
<DxButton Text="Letöltés"
SizeMode="SizeMode.Small"
RenderStyle="ButtonRenderStyle.Secondary"
Enabled="@(CanDownload(row))"
Attributes="@(new Dictionary<string, object> { ["title"] = CanDownload(row) ? "A generált XML letöltése fájlként (kézi NAV-beadáshoz)" : "Hibás bejelentés nem tölthető le — javítsd az adatokat és generáld újra." })"
Click="async () => await OnDownloadClick(row)" />
<DxButton Text="Beküldés"
SizeMode="SizeMode.Small"
RenderStyle="ButtonRenderStyle.Success"
@@ -102,6 +103,29 @@
Attributes="@(new Dictionary<string, object> { ["title"] = CanSend(row) ? "A legenerált bejelentés beküldése a NAV-nak" : "Csak legenerált, hibátlan bejelentés küldhető be." })"
Click="async () => await OnSendClick(row)" />
}
@* A NAV-nál nyitva álló ág gombjai. TILTVA állnak, amíg a műveletük nincs bekötve — azért
vannak mégis kint, hogy a folyamat végigolvasható legyen a rácsról (lásd NotWiredYet).
A „Lezárt" fülön nincs gomb: ott a folyamat véget ért, a `correction` pedig nyitott
üzleti kérdés — egy gomb róla döntést ábrázolna, ami nem született meg.
A letöltés a detail XML-fülére költözött, a tárgya mellé. *@
@if (ShowOpenAtNavActions)
{
<DxButton Text="Generate"
SizeMode="SizeMode.Small"
RenderStyle="ButtonRenderStyle.Primary"
Enabled="false"
Attributes="@(new Dictionary<string, object> { ["title"] = NotWiredYet("A kártya újragenerálása modify-ként, a NAV menetlevél- és tétel-id-jeivel", "1. lépés") })" />
<DxButton Text="Módosítás"
SizeMode="SizeMode.Small"
RenderStyle="ButtonRenderStyle.Secondary"
Enabled="false"
Attributes="@(new Dictionary<string, object> { ["title"] = NotWiredYet("A módosítás beküldése a NAV-nak (pl. rendszám-pótlás), az EKÁER-szám megtartásával", "1. lépés") })" />
<DxButton Text="Lezárás"
SizeMode="SizeMode.Small"
RenderStyle="ButtonRenderStyle.Success"
Enabled="false"
Attributes="@(new Dictionary<string, object> { ["title"] = NotWiredYet("A bejelentés véglegesítése a NAV-nál (finalize), az érkezési idővel", "24. lépés") })" />
}
</CellDisplayTemplate>
</DxGridDataColumn>
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
@@ -119,24 +143,31 @@
engedélyezett: Status / EKÁER szám / SentDate kézi rögzítéséhez. *@
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" EnableNew="false" EnableEdit="true" EnableDelete="false">
<ToolbarItemsExtended>
@* A dátumválasztó editor csak Template-ben mehet toolbarba; a gomb natív DxToolbarItem,
hogy a toolbar stílusát/igazítását kapja (lásd: "Ai process..." minta a GridShippingDocument-ben). *@
@* dxbl-toolbar-edit: a DevExpress téma saját opt-in osztálya editor-t hordozó toolbar-itemre —
ettől kapja a beépített középre igazítást és a toolbarba simuló editor-stílust. *@
<DxToolbarItem BeginGroup="true" CssClass="dxbl-toolbar-edit ekaer-create-from-date-item">
<Template Context="toolbarItemContext">
<DxDateEdit @bind-Date="CreateFromDate"
Format="yyyy.MM.dd"
MinDate="@DateTime.Today.AddMonths(-3)"
MaxDate="@DateTime.Today"
CssClass="ekaer-create-from-date" />
</Template>
</DxToolbarItem>
<DxToolbarItem Text="EKÁER sorok létrehozása"
Enabled="@(!_creatingMissing)"
Tooltip="A választott dátumtól kezdődő, rekord nélküli szállítólevelekre/rendelésekre Pending sort hoz létre (idempotens)."
Click="OnCreateMissingClick">
</DxToolbarItem>
@* A sor-létrehozó gomb + dátumválasztó CSAK a „Beküldésre váró" fülön: a művelet Pending
sorokat gyárt, azok pedig definíció szerint oda kerülnek — a másik két fülön az eredménye
nem ott jelenne meg, ahol megnyomták. A toolbar többi gombja (Column Chooser, layout,
export, reload) mindhárom fülön kell, ezért csak ez a két item szűkül. *@
@if (ShowToSubmitActions)
{
@* A dátumválasztó editor csak Template-ben mehet toolbarba; a gomb natív DxToolbarItem,
hogy a toolbar stílusát/igazítását kapja (lásd: "Ai process..." minta a GridShippingDocument-ben). *@
@* dxbl-toolbar-edit: a DevExpress téma saját opt-in osztálya editor-t hordozó toolbar-itemre —
ettől kapja a beépített középre igazítást és a toolbarba simuló editor-stílust. *@
<DxToolbarItem BeginGroup="true" CssClass="dxbl-toolbar-edit ekaer-create-from-date-item">
<Template Context="toolbarItemContext">
<DxDateEdit @bind-Date="CreateFromDate"
Format="yyyy.MM.dd"
MinDate="@DateTime.Today.AddMonths(-3)"
MaxDate="@DateTime.Today"
CssClass="ekaer-create-from-date" />
</Template>
</DxToolbarItem>
<DxToolbarItem Text="EKÁER sorok létrehozása"
Enabled="@(!_creatingMissing)"
Tooltip="A választott dátumtól kezdődő, rekord nélküli szállítólevelekre/rendelésekre Pending sort hoz létre (idempotens)."
Click="OnCreateMissingClick">
</DxToolbarItem>
}
</ToolbarItemsExtended>
</MgGridToolbarTemplate>
}
@@ -162,6 +193,25 @@
/// <summary>Szerver-oldali szűrő (a tabok adják): ToSubmit / Sent / NeedsCompletion. Default All = minden.</summary>
[Parameter] public EkaerHistoryFilter Filter { get; set; } = EkaerHistoryFilter.All;
/// <summary>A beküldés előtti ág tartalma: az akció-oszlop gombjai és a sor-létrehozó toolbar-itemek.</summary>
/// <remarks>A <c>[Flags]</c> enum <c>All = 0</c> értéke miatt <c>HasFlag</c> itt nem használható — mindig
/// true-t adna —, ezért pontos érték-egyezés.</remarks>
private bool ShowToSubmitActions => Filter is EkaerHistoryFilter.All or EkaerHistoryFilter.ToSubmit;
/// <summary>A NAV-nál nyitva álló ág tartalma: a pótlás és a véglegesítés gombjai.</summary>
private bool ShowOpenAtNavActions => Filter is EkaerHistoryFilter.All or EkaerHistoryFilter.OpenAtNav;
/// <summary>Még be nem kötött művelet tooltipje: mit fog csinálni, és melyik lépésre vár.</summary>
/// <remarks>
/// Ezek a gombok a folyamat ÁBRÁZOLÁSÁÉRT állnak kint tiltva — hogy a rácsról végigolvasható legyen, mi
/// következik egy bejelentésen. Az <c>ADR 0005-H2Q6</c> ezzel szemben azt mondta ki, hogy gomb a művelete
/// nélkül nem kerül ki; a kinttartás felhasználói döntés (2026-08-20), és ez a rekord felülvizsgálandó.
/// A bekötés horgonya ez a segéd: minden hívási helye egy-egy hátralévő lépés
/// (<c>FBANKAPP-EKAER-T-R9D4</c>), és amint a művelet megvan, a hívás helyén tűnik el.
/// </remarks>
private static string NotWiredYet(string whatItWillDo, string step)
=> $"{whatItWillDo} — még nincs bekötve (R9D4 {step}).";
/// <summary>A szülő (oldal) értesítése, ha egy művelet státuszt válthatott (generálás / kézi mentés / jövőbeni beküldés) —
/// hogy a fül-számlálók azonnal frissüljenek, mert a sor átkerülhet másik tabra.</summary>
[Parameter] public EventCallback OnDataChanged { get; set; }
@@ -212,13 +262,9 @@
private bool CanGenerate(EkaerHistory ekaerHistory)
=> !ekaerHistory.Status.IsSent() && !_generatingIds.Contains(ekaerHistory.Id);
// Csak BLOKKOLÓ hiba (ValidationError) esetén NEM tölthető le; a warning (pl. hiányzó rendszám) küldhető → letölthető.
private static bool CanDownload(EkaerHistory ekaerHistory)
=> ekaerHistory.Status.IsSubmittable() && !string.IsNullOrWhiteSpace(ekaerHistory.XmlDoc);
private readonly HashSet<int> _sendingIds = [];
// Ugyanaz az előfeltétel, mint a letöltésnél (legenerálva, blokkoló hiba nélkül) + nincs épp folyamatban küldés.
// Legenerálva, blokkoló hiba nélkül (a warning — pl. hiányzó rendszám — küldhető), és nincs folyamatban küldés.
private bool CanSend(EkaerHistory ekaerHistory)
=> ekaerHistory.Status.IsSubmittable() && !string.IsNullOrWhiteSpace(ekaerHistory.XmlDoc) && !_sendingIds.Contains(ekaerHistory.Id);
@@ -360,26 +406,4 @@
if (result?.Messages is { Count: > 0 } skipped)
await DialogService.ShowMessageBoxAsync($"EKÁER — kihagyott tételek ({skipped.Count})", skipped, MessageBoxRenderStyle.Warning);
}
private async Task OnDownloadClick(EkaerHistory ekaerHistory)
{
if (!CanDownload(ekaerHistory)) return;
// Fájlnév: "yyyy.MM.dd-Partner.xml" — a partnernévből az érvénytelen fájlnév-karaktereket kicseréljük.
var datePart = ekaerHistory.ShippingDate?.ToString("yyyy.MM.dd") ?? "nincs-datum";
var partner = string.IsNullOrWhiteSpace(ekaerHistory.Partner) ? "partner" : ekaerHistory.Partner;
var invalid = System.IO.Path.GetInvalidFileNameChars();
var safePartner = new string(partner.Select(c => invalid.Contains(c) ? '_' : c).ToArray()).Trim();
if (string.IsNullOrWhiteSpace(safePartner)) safePartner = "partner";
var fileName = $"{datePart}-{safePartner}.xml";
try
{
await JS.InvokeVoidAsync("downloadFileFromText", fileName, ekaerHistory.XmlDoc, "application/xml");
}
catch (Exception ex)
{
_logger.Error($"XmlDoc letöltése sikertelen; EkaerHistory.Id: {ekaerHistory.Id}", ex);
}
}
}
File diff suppressed because one or more lines are too long
@@ -1,250 +1,378 @@
# ADR 0005-H2Q6: Az EKÁER fogyasztói oldala — a bejelentés vezérlése a beküldés után
# ADR 0005-H2Q6: The consumer side of EKÁER — steering the declaration after submission
## Status
Proposed (2026-08-18, rev. 2026-08-18, 2026-08-19, 2026-08-20)
Proposed (2026-08-18, rev. 2026-08-18, 2026-08-19, 2026-08-20, 2026-08-21)
> Gyűjtő-rekord: a fogyasztói EKÁER-folyamat döntéseit tartja. Új döntés **revízióként** kerül bele, dátumozott
> `Rev.` sorral — nem új fájlként, mert a folyamat egy egészként érthető.
> Collector record: it holds the decisions of the consumer-side EKÁER process. A new decision enters as a
> **revision**, on a dated `Rev.` line — not as a new file, because the process is understood as one whole.
>
> Rev. 2026-08-18: a rács felülete — a detail tabjai, a gombok helye és a fülönkénti layout. A NAV-műveletekre
> vonatkozó döntések változatlanok.
> Rev. 2026-08-18: the grid's surface — the detail's tabs, where the buttons sit, and the per-tab layout. The
> decisions about the NAV operations are unchanged.
>
> Rev. 2026-08-19: a hiányzó rendszám kapuja feloldva a `modify` használható, a menetlevél- és tétel-id a
> `queryTradeCards` `tcn`-es ágából jön, így a pótlás nem cseréli le a bejelentés számát. Ezzel együtt: a
> NAV-oldali állapot tárolása egyetlen mezőben, és a két kártya egymás mellett az XML-tabon.
> Rev. 2026-08-19: the gate on the missing licence plate is lifted — `modify` is usable, the delivery-plan and
> item ids come from the `tcn` branch of `queryTradeCards`, so completing the data does not replace the
> declaration's number. With it: the NAV-side state stored in a single field, and the two cards side by side on
> the XML tab.
>
> Rev. 2026-08-20: a NAV státusza saját oszlopot kap, a detail EGY kártyát mutat egyszerre — a két XML egymás
> mellől egy forrás-választó alá került —, és a módosítás indoka az eltérésből származik. Az előző revízió
> XML-tabra vonatkozó része ezzel felülírva.
> Rev. 2026-08-20: NAV's status gets its own column, the detail shows ONE card at a time — the two XMLs moved
> from side by side under one source selector — and the modification's reason is derived from the difference.
> The previous revision's XML-tab part is overridden by this. The grid's surface landed, and two decisions fell
> with it: the NAV source is selectable even before it has been fetched, and the unwired buttons stand out,
> disabled.
>
> Rev. 2026-08-21: `EkaerStatus` splits into two axes — lifecycle and outcome — because today's single field
> says two things, and that is exactly what blocks the modification path. The split goes BEFORE `modify` is
> wired.
## Context
A NAV-protokollt a keret adja (`AyCode.Services/Nav/Ekaer/`): transport, auth, tradeCard-séma, az öt művelet.
Ez a rekord arról szól, amit **a termék** csinál egy bejelentéssel — mikor, mi alapján, kinek a kezdeményezésére.
The framework supplies the NAV protocol (`AyCode.Services/Nav/Ekaer/`): transport, auth, the tradeCard schema,
the five operations. This record is about what **the product** does with a declaration — when, on what basis, on
whose initiative.
**Ahol ma megállunk.** A folyamat `create`-ig tart: a kapu sort hoz létre → generálás + NAV-előellenőrzés →
beküldés. A beküldés a NAV-nál `P` (Prepare) állapotot hoz létre, nem végállapotot.
**Where we stop today.** The process runs to `create`: the gate creates a row → generation + NAV pre-flight
check → submission. The submission creates a `P` (Prepare) state at NAV, not a final state.
**A NAV életciklusa** (`AyCode.Services/docs/NAV/EKAER_OPERATIONS.md`): `P → S → F`, ahol az `F` a `finalize`
művelettel áll elő; ami **időben nem véglegesedik, az `I` (Inactive)** lesz. Következmény: **minden eddig
beküldött bejelentésünk lejárat felé tart**, és erről ma semmilyen jelzésünk nincs.
**NAV's lifecycle** (`AyCode.Services/docs/NAV/EKAER_OPERATIONS.md`): `P → S → F`, where `F` is produced by the
`finalize` operation; whatever **is not finalized in time becomes `I` (Inactive)**. Consequence: **every
declaration we have submitted so far is heading for expiry**, and today nothing tells us about it.
**Amit tegnap rögzítettünk.** Az `EkaerStatus` megkapta a `Finalized = 7` értéket, és a rács három füle
`Beküldésre váró` / `Pótlásra/Véglegesítésre vár` / `Lezárt`. A harmadik fül **konstrukció szerint üres**:
semmi nem állít `Finalized`-et. A rekord ezt az ígéretet váltja be.
**What we settled yesterday.** `EkaerStatus` received the value `Finalized = 7`, and the grid's three tabs are
`Beküldésre váró` / `Pótlásra/Véglegesítésre vár` / `Lezárt` (awaiting submission / awaiting completion or
finalization / closed). The third tab is **empty by construction**: nothing sets `Finalized`. This record
redeems that promise.
**Mért tények a NAV teszt-rendszeréről** (2026-08-17/18/19, `AyCode.Services.Tests` sandbox-suite):
**Measured facts from NAV's test system** (2026-08-17/18/19, the `AyCode.Services.Tests` sandbox suite):
- a `finalize` a `tcn`-t kéri, és az **érkezési időt a művelet** `arrivalDate` / `arrivalDateOnly` mezőjében
(2.0-tól nem a tradeCard-ban);
- a `delete` indoklást is kér (`statusChangeModReasonText`), különben `TC_MOD_REASON_MISSING`;
- a `modify` **használható**, de hármat egyszerre kér (2026-08-19): tételenként `itemOperation`-t, a módosított
tételhez a NAV saját `id`-ját, és a menetlevélhez ugyanígy a sajátját. Bármelyik hiányzik`INVALID_INPUT`;
- **mindkét id a `queryTradeCards` `tcn`-es ágából olvasható ki**: a válasz a teljes kártyát adja vissza,
menetlevéllel és tétellel, a párosítás pedig a saját `externalId`-nkon megy;
- a **tétel-lista nélküli, fejrész-only `modify`** nem alternatíva: a NAV boríték-szinten `OPERATION_FAILED`-del
felel, azaz a kérést fel sem dolgozza. A tételeket el kell küldeni;
- a `tcn`-es lekérdezés **nem késik**: a frissen létrehozott kártyát azonnal visszaadta (két futás) — szemben a
dátum-tartományos listázással, ami korábban késve frissült;
- a frissen létrehozott kártya azonnal **`S` (Start)** állapottal jött vissza, nem `P`-vel.
- `finalize` asks for the `tcn`, and for the **arrival time on the operation**, in its `arrivalDate` /
`arrivalDateOnly` field (since 2.0 not in the tradeCard);
- `delete` also asks for a reason (`statusChangeModReasonText`), otherwise `TC_MOD_REASON_MISSING`;
- `modify` **is usable**, but asks for three things at once (2026-08-19): an `itemOperation` per item, NAV's own
`id` for the modified item, and likewise its own for the delivery plan. Any one missing`INVALID_INPUT`;
- **both ids are readable from the `tcn` branch of `queryTradeCards`**: the response returns the whole card, with
the delivery plan and the items, and the pairing runs on our own `externalId`;
- the **header-only `modify`, without the item list**, is not an alternative: NAV answers with an
envelope-level `OPERATION_FAILED`, meaning it does not even process the request. The items must be sent;
- the `tcn` query **does not lag**: it returned the freshly created card immediately (two runs) — unlike the
date-range listing, which used to refresh late;
- the freshly created card came back immediately in state **`S` (Start)**, not `P`.
**Egy megdőlt előfeltevés.** A keret doksija — és nyomában e rekord első változata — azt állította, hogy a
tétel-id nem szerezhető meg. Nem hiányzott: soha nem kértük el. Aki a `queryTradeCards`-ot összefoglaló-
lekérdezésnek hiszi, ugyanide jut újra.
**A premise that proved false.** The framework's docs — and following them, this record's first version — claimed
the item id could not be obtained. It was not missing: we never asked for it. Anyone who takes `queryTradeCards`
for a summary query arrives here again.
**A rács mai állapota** (rev. 2026-08-18). A három fül **ugyanaz a komponens**, csak más `Filter` paraméterrel,
és mindhárom `AutoSaveLayoutName="GridEkaerHistory"` néven ment — egyetlen tárolt layouton osztoznak. A mentett
layout a DevExpress `GridPersistentLayout`-ja **egészben**, szűrés nélkül (`MgGridBase`), tehát az **oszlop-
láthatóságot is tartalmazza**, amit ráadásul a felhasználó a Column Chooserrel maga állít. Ennek két látható
következménye volt: egy fülön beállított oszlop-szűrő mindhárom fülön megjelent, és a rács üresnek látszott.
**One field, two meanings.** `EkaerStatus` says at once where the declaration stands (`Sent`, `Finalized`) and
what became of our last operation (`GeneratedWithWarning`, `ValidationError`). The two are independent, and
fusing them causes measurable harm: modification requires regenerating the card, but generation overwrites the
status, and with it takes away the fact that the declaration is at NAV. The resulting row is `IsSubmittable()`,
so the next submission would create a SECOND declaration for the same carriage. The
`FmEkaerServiceBase.ThrowIfAlreadyAtNav` guard is the scar of that squeeze — it exists because one field cannot
preserve two things at once.
**Hatókörön kívül:** az `EkaerStatus` és a státusz-kategóriák keretbe költöztetése.
**The grid's state today** (rev. 2026-08-18). The three tabs are **the same component**, only with a different
`Filter` parameter, and all three saved under the name `AutoSaveLayoutName="GridEkaerHistory"` — they share a
single stored layout. The saved layout is DevExpress's `GridPersistentLayout` **as a whole**, unfiltered
(`MgGridBase`), so it **also contains column visibility**, which the user sets with the Column Chooser on top of
that. This had two visible consequences: a column filter set on one tab appeared on all three, and the grid
looked empty.
**Out of scope:** moving `EkaerStatus` and the status categories into the framework.
## Decision
> **An ADR records reasoning, not the state, and binds nothing — `BASE:ARTIFACT_ROLES`.**
**A bejelentést a felhasználó zárja le, a rácsból, soronként.** A véglegesítés nem automatikus: a lerakodás
ténye a rendszerben nem keletkezik magától, és a `finalize` visszafordíthatatlan (utána csak `correction`,
pótlék ellenében).
**The user closes the declaration, from the grid, row by row.** Finalization is not automatic: the fact of
unloading does not arise in the system by itself, and `finalize` is irreversible (after it, only `correction`,
against a surcharge).
**Az érkezési idő a meglévő domain-adatból jön:** bejövőnél a `Shipping.MeasuredDate`, kimenőnél az
`Order.DateOfReceipt`. Mindkettő **közelítés** a tényleges lerakodáshoz, és tudatosan az: a mérés a lerakodás
után történik, a `DateOfReceipt` pedig az átvétel napja. Kitalált értéket (a gombnyomás ideje, a rekord
létrehozása) nem küldünk — hamis adat kerülne a bejelentésbe.
**The arrival time comes from existing domain data:** inbound the `Shipping.MeasuredDate`, outbound the
`Order.DateOfReceipt`. Both are an **approximation** of the actual unloading, deliberately so: weighing happens
after unloading, and `DateOfReceipt` is the day of receipt. We do not send an invented value (the moment the
button was pressed, the record's creation) — that would put false data into the declaration.
**A hiányzó rendszámot `modify`-jal pótoljuk** (rev. 2026-08-19). A `SentWithMissingData` állapotú bejelentés
nem véglegesíthető, amíg a rendszám hiányzik. A pótlás menete: `queryTradeCards` a `tcn`-re → a menetlevél- és
tétel-id-k átvezetése a generált kártyára az `externalId`-k mentén`modify` a rendszámmal és a mezőnkénti
indokkal.
**We complete the missing licence plate with `modify`** (rev. 2026-08-19). A declaration in `SentWithMissingData`
cannot be finalized while the plate is missing. The route: `queryTradeCards` on the `tcn` → carrying the
delivery-plan and item ids onto the generated card along the `externalId`s`modify` with the plate and the
per-field reason.
Ez azért ez az ág, mert **a bejelentés száma megmarad**: a `delete` + újra `create` új `tcn`-t adna, amit a
fuvarlevélre addigra már ráírtak.
This branch is chosen because **the declaration's number survives**: `delete` plus a fresh `create` would give a
new `tcn`, which by then has been written onto the waybill.
**A NAV-oldali állapotot eltároljuk, egyetlen mezőben** (rev. 2026-08-19). Az `FmEkaerHistoryBase` kap egy
`NavXmlDoc`-ot: a lekérdezett kártya, a saját generált modellünkbe deszerializálva és onnan XML-be visszaírva
(`NavXmlHelper.Serialize<T>`). Mentés **befogadás után, best-effort** — ha a lekérdezés bukik, a beküldés attól
még megtörtént, plusz kézi frissítés a rácsból, mert a NAV állapota nélkülünk is változik.
**We store the NAV-side state, in a single field** (rev. 2026-08-19). `FmEkaerHistoryBase` gets a `NavXmlDoc`:
the queried card, deserialized into our own generated model and written back from there into XML
(`NavXmlHelper.Serialize<T>`). Saved **after acceptance, best-effort** — if the query fails, the submission still
happened — plus a manual refresh from the grid, because NAV's state changes without us.
**Egy mező elég, mert a `modify` sosem ebből dolgozik.** A módosítás egyetlen művelet, ami maga kérdezi le az
id-ket közvetlenül a küldés előtt: nincs hívó, aki elavultat adhatna át, és ha a lekérdezés bukik, a módosítás
el sem indul. Külön dátum-mező sem kell — a pillanatkép a NAV `insDate`/`modDate` mezőit hordozza, tehát maga
datálja magát. A tárolt példány szerepe ezért megjelenítés és audit, nem a művelet bemenete.
**One field is enough, because `modify` never works from it.** Modification is a single operation that queries
the ids itself, directly before sending: there is no caller who could hand over a stale one, and if the query
fails the modification does not even start. No separate date field is needed either — the snapshot carries NAV's
`insDate` / `modDate` fields, so it dates itself. The stored copy therefore serves display and audit, not the
operation's input.
**A NAV státusza mégis külön oszlopot kap** (rev. 2026-08-20). A `NavXmlDoc` hordozza, de a rács nem tud
XML-ből szűrni és rendezni — márpedig a lejárt bejelentés (`I`) megkeresése épp ilyen lekérdezés. Ez az a küszöb,
amit az előző revízió kimondott: amint a rácsnak kell, oszlop legyen. A `NavStatus` ugyanabból a válaszból
íródik, mint a `NavXmlDoc`, ugyanabban a két lépésben.
**NAV's status nevertheless gets its own column** (rev. 2026-08-20). `NavXmlDoc` carries it, but the grid cannot
filter and sort from XML — and finding the expired declaration (`I`) is exactly that kind of query. This is the
threshold the previous revision stated: the moment the grid needs it, it becomes a column. `NavStatus` is written
from the same response as `NavXmlDoc`, in the same two steps.
**A NAV betűjét tároljuk, nem a mi státuszunkra képezve** (`P`/`S`/`F`/`I`/`D`). A megfeleltetés az `EkaerStatus`-ra
alant nyitott kérdés; leképezni most annyi lenne, mint egy tisztázatlan feltevést beírni az adatbázisba. A nyers
érték viszont **magát a kérdést válaszolja meg** éles adatból.
**We store NAV's letter, not mapped onto our own status** (`P`/`S`/`F`/`I`/`D`). The correspondence to
`EkaerStatus` is an open question below; mapping it now would amount to writing an unclarified assumption into
the database. The raw value, by contrast, **answers that very question** from live data.
**A módosítás indokát az eltérés adja meg, mezőnként** (rev. 2026-08-20). A NAV minden megváltoztatott mezőhöz
külön indok-szöveget kér. Ezt nem állandó szöveggel töltjük: a küldendő kártyát a `NavXmlDoc`-ban tárolt
NAV-verzióhoz mérjük, és **csak az eltérő mezők** kapnak indokot. A generálás ezt magától nem tudná — a kártya
minden módosítás előtt teljesen újraépül a domain-adatból, nem foltozzuk —, a NAV verziója viszont ott van.
**The modification's reason is given by the difference, per field** (rev. 2026-08-20). NAV asks for a separate
reason text for every changed field. We do not fill it with a constant text: we measure the card to be sent
against the NAV version stored in `NavXmlDoc`, and **only the differing fields** get a reason. Generation could
not know this on its own — the card is fully rebuilt from domain data before every modification, we do not patch
it — but NAV's version is there.
**A mezőkészlet a sémáé, nem a miénk**: fejrészben `orderNumber`, `plateNumber`+`country`,
`modByCarrierEnabled`, `carrier`, `carrierText` (import + kockázatos árunál `unloadLocation.vatNumber/name`),
tételszinten `value`, `weight`, `productVtsz`, `productName`. Dátum nincs közte, ezért a „minden dátum eltér"
zaj fel sem merül. Az összevetés **normalizálva** történik (a rendszám a keret normalizálóival), a `weight` és a
`value` **értékként**, a tétel-mezők tételenként az `itemExternalId`-n párosítva — ugyanazon a kulcson, amin az
id-ket is átvezetjük.
**The field set is the schema's, not ours**: in the header `orderNumber`, `plateNumber`+`country`,
`modByCarrierEnabled`, `carrier`, `carrierText` (with import plus risky goods, `unloadLocation.vatNumber/name`),
at item level `value`, `weight`, `productVtsz`, `productName`. There is no date among them, so the "every date
differs" noise does not even arise. The comparison runs **normalized** (the plate through the framework's
normalizers), `weight` and `value` **as values**, the item fields per item paired on `itemExternalId` — the same
key on which we carry the ids over.
**Nem töltünk „biztos, ami biztos" indokot a nem változott mezőkre.** Az egy hivatalos bejelentésben olyat
állítana, ami nem történt meg, és azt ellenőr olvassa. A kihagyott mező ára ehhez képest kicsi: a NAV
`TC_MOD_REASON_MISSING`-gel elutasítja a módosítást, a bejelentés érintetlen marad, és a hiba azonnal látszik.
**We do not fill a "just in case" reason on unchanged fields.** In an official declaration that would state
something that did not happen, and an inspector reads it. The price of an omitted field is small by comparison:
NAV rejects the modification with `TC_MOD_REASON_MISSING`, the declaration stays untouched, and the error is
visible immediately.
**Nyitva hagyott kérdések**, amikre ez a rekord szándékosan nem ad választ:
**Questions left open**, which this record deliberately does not answer:
- **`Inactive` felismerése.** A lejárt bejelentésről csak a `queryTradeCards` szól. Kézi seprés a rácsból,
ütemezett futás, vagy egyáltalán nem követjük — eldöntendő. Enélkül a „Pótlásra/Véglegesítésre vár" fül
olyan sorokat gyűjt, amiket a NAV már lejártatott.
- **`correction`.** Lezárt bejelentés utólagos javítása pótlékkal jár (mezőtípusonként), exportra pedig nem
alkalmazható. Használjuk-e valaha, vagy a hibás lezárt bejelentés marad úgy — üzleti döntés.
- **Az indok-szövegek tartalma.** Az, hogy MELYIK mező kap indokot, eldőlt (fent); hogy MIT írunk bele,
mezőnként egy-egy rögzített mondat lesz (Rendszám pótlása.") — a szövegek maguk még nincsenek kimondva.
Korlát: 200 karakter (`SimpleText200Type`), és a mezőbe a különbségből képzett mondat való, nem maga a
különbség.
- **Recognizing `Inactive`.** Only `queryTradeCards` tells us about an expired declaration. A manual sweep from
the grid, a scheduled run, or not tracking it at all — to be decided. Without it the
„Pótlásra/Véglegesítésre vár" tab collects rows NAV has already expired.
- **`correction`.** Amending a closed declaration carries a surcharge (per field type), and it cannot be applied
to export. Whether we ever use it, or a wrong closed declaration simply stays that way — a business decision.
- **The content of the reason texts.** WHICH field gets a reason is settled (above); WHAT we write into it will
be one fixed sentence per field ("Rendszám pótlása.") — the texts themselves are not stated yet. Constraint:
200 characters (`SimpleText200Type`), and the field wants a sentence formed from the difference, not the
difference itself.
### A rács felülete (rev. 2026-08-18)
### The grid's surface (rev. 2026-08-18)
**A detail két tabot kap: a tételeket és a nyers XML-t.** A generált kártya ma csak letölthető, megnézni nem
lehet — a saját tabján olvashatóvá válik. A **letöltés gombja odaköltözik**, és kikerül a sor-műveletek közül:
a gomb a tárgya mellett álljon. Ára, hogy a letöltés két kattintás lesz egy helyett.
**The detail gets two tabs: the items and the raw XML.** Today the generated card can only be downloaded, not
looked at — on its own tab it becomes readable. The **download button moves there**, and leaves the row actions:
the button should stand next to its subject. The price is that downloading becomes two clicks instead of one.
**A detail EGY kártyát mutat egyszerre, és egy választó dönti el, melyiket** (rev. 2026-08-20). A tetején
vízszintes rádió`Generált` / `NAV-nál lévő` —, és a fejléc, a tétel-grid és az XML-tab **mind ugyanazt
követi**. Alapból a generált: a felhasználó azzal dolgozik (generál, javít, beküld), a NAV verziója a referencia.
A NAV-opció akkor is látszik, ha még nincs lekérdezve, csak tiltva — így kiderül, hogy van hova frissíteni.
**The detail shows ONE card at a time, and a selector decides which** (rev. 2026-08-20). At the top a horizontal
radio`Generált` / `NAV-nál lévő` (generated / the one at NAV) — and the header, the item grid and the XML tab
**all follow the same one**. The generated one by default: the user works with that (generates, fixes, submits),
NAV's version is the reference. The NAV option is **selectable** even before it has been fetched. The previous
form — visible but disabled — failed on the query button: that button stands on this branch, so behind a disabled
option it would be unreachable, exactly on the rows whose NAV state has never been fetched. Selectability is
conditioned on the EKÁER number: that says whether there is anything to query.
Ez **felülírja az előző revízió két-hasábos XML-tabját**. Az ok: a választó úgyis eldönti, „mit nézek", és két
fél szélességű hasábban a hosszú XML olvashatatlan volt. Ára, hogy az egymás melletti összevetés elvész —
váltogatni lehet, de az eltérés nem egy pillantás. A mező-szintű összehasonlítás amúgy is a modellen történne,
nem a szövegen: a kettő nem ugyanaz a dokumentum-fajta (a miénk `manageTradeCards` kérés, a NAV-é
`queryTradeCards` válasz), szövegesen összevetve minden sor eltérésnek látszana.
This **overrides the previous revision's two-column XML tab**. The reason: the selector decides "what am I
looking at" anyway, and in two half-width columns the long XML was unreadable. The price is that side-by-side
comparison is lost — one can switch back and forth, but the difference is not a single glance. Field-level
comparison would happen on the model rather than the text in any case: the two are not the same kind of document
(ours is a `manageTradeCards` request, NAV's a `queryTradeCards` response), and compared as text every line would
look like a difference.
**Ami a soron látszik, az nem kerül a fejlécbe.** Az EKÁER-szám és a NAV-státusz oszlop a rácsban; a detail
fejlécében ugyanaz a tény a forrás-választótól függően jelent volna meg és tűnt volna el.
**What is visible on the row does not go into the header.** The EKÁER number and the NAV status are columns in
the grid; in the detail's header the same fact would have appeared and disappeared depending on the source
selector.
**Egy „Műveletek" oszlop marad, és a fül választja meg a TARTALMÁT** — nem fülönkénti oszlopok láthatósággal.
Az oszlop-láthatóság ugyanis kétszeresen sem a kódé: a felhasználó állítja a Column Chooserrel, és a mentett
layout vissza is tölti. A tartalom viszont nem oszlop: nem mentődik, és nincs mivel ütköznie. Mellékhaszon,
hogy az egyszer leírt gomb több fülön is megjelenhet — a Generate-nek épp erre van szüksége.
**One "Műveletek" (actions) column stays, and the tab chooses its CONTENT** — not per-tab columns with
visibility. Column visibility is doubly not the code's: the user sets it with the Column Chooser, and the saved
layout loads it back. Content, by contrast, is not a column: it is not saved, and there is nothing for it to
collide with. A side benefit is that a button written once can appear on several tabs — which is exactly what
Generate needs.
**Fülönként külön `AutoSaveLayoutName`**, a `Filter`-ből származtatva (`GridEkaerHistory_{Filter}`). A három
fül más oszlopokat és más tartalmat mutat; közös szélességnek, rendezésnek és szűrőnek nincs értelme. A meglévő
mentett layoutok ezzel elárvulnak — mindenki egyszer alapértelmezett nézetet kap, és a beragadt szűrő eltűnik.
**A separate `AutoSaveLayoutName` per tab**, derived from the `Filter` (`GridEkaerHistory_{Filter}`). The three
tabs show different columns and different content; a shared width, sort order and filter make no sense. The
existing saved layouts are orphaned by this — everyone gets the default view once, and the stuck filter
disappears.
**A sor-létrehozó gomb és a dátumválasztó csak az első fülön.** A művelet `Pending` sorokat gyárt, azok pedig
definíció szerint oda kerülnek; a „Lezárt" fülön értelmetlen, a középsőn félrevezető. Ez template-tartalom,
nem oszlop, tehát a láthatósági ütközés itt fel sem merül.
**The row-creating button and the date picker only on the first tab.** The operation manufactures `Pending` rows,
and those by definition land there; on the „Lezárt" tab it is meaningless, on the middle one misleading. This is
template content, not a column, so the visibility collision does not even arise here.
**A Generate a második fülön is kell** — a rendszám megérkezése után újra kell generálni, mielőtt a módosítás
kimegy. Ez két dolgot von maga után: a szabály `!IsSent()`-ről `IsOpenAtNav()`-ra vált (lezárt bejelentésre
továbbra sem), és a generált eredmény ott már **nem `create`, hanem `modify`**vagyis a kártyára a lekérdezett
menetlevél- és tétel-id-k is rákerülnek.
**Generate is needed on the second tab too** — after the plate arrives the card must be regenerated before the
modification goes out. This entails two things: the rule switches from `!IsSent()` to `IsOpenAtNav()` (still not
for a closed declaration), and the generated result there is **no longer a `create` but a `modify`**that is,
the queried delivery-plan and item ids go onto the card as well.
**A Módosítás és a Lezárás gomb a műveletével együtt érkezik.** Gomb, ami mögött nincs működő út, rosszabb a
hiányzó gombnál.
**The Módosítás and Lezárás buttons arrive together with their operation.** A button with no working path behind
it is worse than a missing button.
**The user overrode this**: the unwired buttons **stand out, disabled**. The reason is that the process should be
readable end to end from the grid — that what comes next on a declaration should be visible on the row, not only
in the documentation. Of the objection above, the false affordance falls away with `Enabled="false"`, and the
tooltip names the remaining step. What remains as a risk: the surface promises a finished process whose NAV-side
half does not work yet.
### The two axes of the status (rev. 2026-08-21)
**`EkaerStatus` splits in two: lifecycle and outcome.** This is not a new model — today's eight values **decompose
onto it without remainder**, so it is the statement of what is already there. `GeneratedWithWarning` and
`SentWithMissingData` are literally compound names: lifecycle plus outcome in one word.
| Lifecycle | Outcome | Today | What to do |
|---|---|---|---|
| `NotSent` | `None` | `Pending` | Generate |
| `NotSent` | `Ok` | `Generated` | submit |
| `NotSent` | `Warning` | `GeneratedWithWarning` | submit |
| `NotSent` | `Error` | `ValidationError`, `SendError` | fix + Generate |
| `Sent` | `Ok` | `Sent` | finalize |
| `Sent` | `Warning` | `SentWithMissingData` | Generate + modify |
| `Sent` | `Error` | — no value for it | fix + Generate + modify |
| `Finalized` | `Ok` | `Finalized` | — |
The eighth row is empty in today's column: for a failed modification there is **no state today**. Without it the
modify branch cannot be represented.
**The lifecycle has three values, not four.** `Modified` came up and fell: modification can happen several times,
and a modified row belongs where a submitted one does — at NAV, awaiting finalization. What can be entered
repeatedly and changes nothing about what is allowed is not a state. If the fact that we touched it is needed,
that is a date or a counter.
**`ValidationError` and `SendError` land in one cell.** The lifecycle gives them their meaning: `NotSent` +
`Error` = it never went out; `Sent` + `Error` = the modification failed, the declaration is intact at NAV.
Distinguishing them does nothing today either — `CanSend` is false for both, so both require regeneration — and
the "why" stays in `ErrorText`, with a per-line `[Error]` / `[Warning]` prefix.
**Two rules that must be stated, or the model does not hold:**
- **The outcome is the result of the LAST OPERATION, not the card's validity.** Submitting a card generated as
`Ok` can fail: the row becomes `NotSent` + `Error` while the card is valid. This is correct — what matters is
what to do — but anyone reading it as "the card's validity" misreads it.
- **`Lifecycle == Sent``EkaerNumber != null`.** The model's single redundancy; the submission writes both. As
an invariant it is fine, unstated it drifts apart, and from then on nobody knows which one governs.
**`NavStatus` stays where it is** (`P`/`S`/`F`/`I`/`D`), and from now on **the disagreement between the two is
information too**: if our lifecycle is `Sent` and NAV's is `I`, that is the expiry nothing reports today.
**The zero default stays correct:** `NotSent = 0` and `None = 0`, so a freshly inserted row is exactly today's
`Pending`.
**Where the decision lives.** `EkaerStatus` sits in `FruitMasterErp.Core`, so this is the wholesale layer's
decision. The record stays here nonetheless: the layer's pull-up is in progress, and the records belonging to it
move together at the end.
**Settled before implementation:**
- **`StatusId` stays**, and a second column joins it for the other axis. The alternative — a new name for each,
with `StatusId` retired — would make a row that missed the backfill impossible to misread; that safety is
given up for the cheaper migration. The consequence is that the backfill has to be **complete**, not merely
attempted: a missed row reads as a valid state rather than as an error.
- **No wipe.** Regenerating the rows is not the difficulty — the mapping is. `EkaerHistoryMapping.ForeignKey`
carries its meaning implicitly, and the source id ranges overlap, so a stale row resolves to a *different*
source record instead of failing (`MGFBANKPLUG-EKAER-I-D8V3`). A regenerated set would land in exactly that.
The backfill keeps the existing rows, and the database step waits.
**Still open:**
- **Which axis `StatusId` carries** — the lifecycle, leaving the outcome to the new column, or the reverse.
- **`EkaerHistoryFilter`.** With the tabs keyed on the lifecycle, the flags enum may simply be the lifecycle enum;
whether the wire contract keeps a separate type is undecided.
## Consequences
**Positive:**
- A bejelentés a NAV szerinti végállapotba jut (`F`), nem lejáratba (`I`).
- A „Lezárt" fül tartalmat kap, és a „Pótlásra/Véglegesítésre vár" számlálója valódi teendőt mutat.
- A `finalize` bekötésével kiderül, hány eddigi bejelentés lezárható még, és hány veszett el.
- A hiányzó rendszám utólag pótolható, és a bejelentés `tcn`-je megmarad (rev. 2026-08-19).
- The declaration reaches NAV's final state (`F`), not expiry (`I`).
- The „Lezárt" tab gets content, and the „Pótlásra/Véglegesítésre vár" counter shows real work.
- Wiring `finalize` reveals how many past declarations can still be closed, and how many were lost.
- The missing licence plate can be completed afterwards, and the declaration's `tcn` survives (rev. 2026-08-19).
- The modification path opens: a row at NAV can be regenerated without losing the fact that it is at NAV.
- The tab filter simplifies — the three tabs are purely a lifecycle question, an `IN` on a single column;
`EkaerHistoryDbTable`'s precomputed `StatusId` arrays and the `StatusIdsWhere` machinery can go.
- Later operations bring no new vocabulary: `Lifecycle × Outcome` yields the cells by itself.
**Negative:**
- A bejelentett érkezési idő közelítés; egy NAV-ellenőrzésnél ezt vállalni kell.
- A kézi trigger azt jelenti, hogy **elmaradhat** — a lejárat kockázata a felhasználón marad, amíg a `D`
kérdés (lejárat-figyelés) nyitva van.
- `Finalized` után az adat csak pótlékkal javítható; a korai véglegesítés drágább, mint a késői.
- A `modify` **két körös** (rev. 2026-08-19): minden módosítás előtt le kell kérdezni a NAV állapotát az
id-kért. Az elhagyott lekérdezés `INVALID_INPUT`-ot ad, nem csendes hibát.
- The reported arrival time is an approximation; at a NAV inspection that has to be owned.
- The manual trigger means it **can be skipped** — the risk of expiry stays with the user while the `D` question
(expiry watch) is open.
- After `Finalized` the data can only be amended against a surcharge; finalizing early costs more than late.
- `modify` is **two-round** (rev. 2026-08-19): before every modification NAV's state must be queried for the ids.
An omitted query gives `INVALID_INPUT`, not a silent failure.
- The status split is a **schema change**: two columns plus a backfill, retiring the old `StatusId`. The mapping
of the eight values is total, so the backfill is closed — but the database has to be touched.
- The change reaches every consumer of `EkaerStatus` and its partitions, across three repos: the enum and its
helpers, the entity, both server-side services, the query table, the controller's endpoints, the grid and the
tests. Measure the set before starting — it is larger than the enum's own file suggests, and
`EkaerHistoryDbTable` carries the densest use. Every piece gets smaller, but it has to happen in one pass.
- One redundancy remains (`Sent``EkaerNumber`), to be held as an invariant rather than derived.
**Follow-ups required:**
- A rács felülete, ami még hátravan: a letöltés áthelyezése az XML-fülre és ki a sor-műveletek közül, az egy
akció-oszlop tabtól függő tartalommal, a fülönkénti `AutoSaveLayoutName`, és a toolbar szűkítése az első
fülre. Ez a csomag a hiányzó-rendszám ágtól **független**.
- `modify` a NAV-határon: `queryTradeCards` a `tcn`-re, a menetlevél- és tétel-id-k átvezetése az
`externalId`-k mentén, az eltérő mezők indokolása a `NavXmlDoc`-hoz mérve, majd a `modify`; a rács gombja és
a dialógus. A lekérdezés a művelet RÉSZE, nem külön hívható lépés — az id-k így nem kerülhetnek kívülről a
kérésbe.
- A lejárt bejelentés megkeresése a rácsban: a `NavStatus` oszlop már hordozza az `I`-t, de a
„Pótlásra/Véglegesítésre vár" fül nem különbözteti meg. Ez az `Inactive` nyitott kérdés második fele —
az elsőt (honnan tudjuk meg) a lekérdezés megválaszolta.
- `finalize` művelet a NAV-határon (`FmEkaerNavGateway`), a `tcn` + `arrivalDate` alakkal; a rács gombja és a
megerősítő dialógus.
- Az EKÁER-írások tranzakció-lefedettsége: a törlő végpont (`EkaerHistory` + mappingek) és a `finalize`
státusz-írása `TransactionSafeAsync`-be. A `DATA_LAYER.md` tranzakció-állítása ezzel együtt pontosítandó.
- Az `EkaerStatus``TradeCardStatusType` megfeleltetés kimondása a dokumentációban. A feltételezett
`Sent` = NAV `P` **mérésre szorul**: a teszt-rendszeren a frissen létrehozott kártya azonnal `S`-sel jött
vissza. A `NavStatus` oszlop élesben ezt találgatás nélkül megmutatja — a megfeleltetés innentől leolvasható,
nem feltételezendő.
- A státusz-életciklus leírása a `Nop.Plugin.Misc.AIPlugin/docs/EKAER/README.md`-ben ma a `Sent`-nél megáll —
a véglegesítés landolásakor ott kell aktuális ténnyé válnia.
- **Splitting the status onto two axes — BEFORE `step 1`.** The core of modify is regeneration, which today's
form forbids; done afterwards, modify would get a detour that then has to be thrown away. With it,
`ThrowIfAlreadyAtNav` keys on the lifecycle: on a `Sent` row *create*-generation is forbidden, modify-generation
is allowed. The invariant (`Sent` `EkaerNumber`) gets a test.
- `modify` at the NAV boundary: `queryTradeCards` on the `tcn`, carrying the delivery-plan and item ids along the
`externalId`s, reasoning the differing fields measured against `NavXmlDoc`, then the `modify`; the grid's button
and the dialog. The query is PART of the operation, not a separately callable step — the ids can therefore not
reach the request from outside.
- Finding the expired declaration in the grid: the `NavStatus` column already carries the `I`, but the
„Pótlásra/Véglegesítésre vár" tab does not distinguish it. This is the second half of the open `Inactive`
question — the first (how we find out) was answered by the query.
- The `finalize` operation at the NAV boundary (`FmEkaerNavGateway`), in the `tcn` + `arrivalDate` form; the
grid's button and the confirmation dialog.
- Transaction coverage of the EKÁER writes: the delete endpoint (`EkaerHistory` plus mappings) and the
`finalize` status write into `TransactionSafeAsync`. `DATA_LAYER.md`'s transaction claim is to be corrected
along with it.
- Stating the `EkaerStatus``TradeCardStatusType` correspondence in the documentation. The assumed
`Sent` = NAV `P` **needs measuring**: on the test system the freshly created card came back as `S` immediately.
The `NavStatus` column will show this in production without guessing — the correspondence becomes readable from
then on, not something to assume.
- The status lifecycle description in `Nop.Plugin.Misc.AIPlugin/docs/EKAER/README.md` stops at `Sent` today —
when finalization lands, it has to become current fact there.
## Alternatives considered
**A trigger — automatikus véglegesítés a mérés lezárásakor** (elvetve): a mérés lezárása nem azonos a
lerakodással, és a `finalize` visszafordíthatatlan. Egy téves automatizmus pótlékos `correction`-t okoz.
**The trigger — automatic finalization when weighing closes** (rejected): closing the weighing is not the same as
unloading, and `finalize` is irreversible. A mistaken automatism causes a `correction` with a surcharge.
**A trigger — ütemezett seprés** (elvetve): ugyanaz a baj, plusz a felhasználó nem látja, mi történt a nevében.
**The trigger — a scheduled sweep** (rejected): the same problem, plus the user does not see what happened in
their name.
**Érkezési idő — a gombnyomás pillanata** (elvetve): a lerakodás és a lezárás között órák-napok telhetnek; a
mező a tényleges lerakodást kéri.
**Arrival time — the moment the button is pressed** (rejected): hours or days can pass between unloading and
closing; the field asks for the actual unloading.
**Érkezési idő — a felhasználó írja be a dialógusban** (elvetve *egyelőre*): pontosabb lehet, de új beviteli
felületet és validálást kíván. Ha a `MeasuredDate` a gyakorlatban rossznak bizonyul, ez a következő lépés.
**Arrival time — the user types it into the dialog** (rejected *for now*): it could be more accurate, but it
wants a new input surface and validation. If `MeasuredDate` proves wrong in practice, this is the next step.
**A hiányzó rendszám — fejrész-only `modify`** (elvetve, mérve 2026-08-19): a tétel-lista elhagyásával a NAV
boríték-szinten `OPERATION_FAILED`-del felel, azaz a kérést fel sem dolgozza. A választott ágnak nincs is rá
szüksége — a tételek az id-jükkel együtt kimennek.
**The missing plate — header-only `modify`** (rejected, measured 2026-08-19): dropping the item list, NAV answers
with an envelope-level `OPERATION_FAILED`, meaning it does not even process the request. The chosen branch does
not need it either — the items go out together with their ids.
**A hiányzó rendszám — `delete` + újra `create`** (elvetve): működik, de **új `tcn`-t ad**. A régi számot a
fuvarlevélre addigra ráírták, és a `delete` külön indoklást is kér.
**The missing plate — `delete` plus a fresh `create`** (rejected): it works, but **it gives a new `tcn`**. The old
number has by then been written onto the waybill, and `delete` also asks for a separate reason.
**A hiányzó rendszám — a rendszám kötelezővé tétele beküldés előtt** (elvetve): megszüntetné a
`SentWithMissingData` állapotot, cserébe visszatartaná a bejelentést olyankor is, amikor a NAV a későbbi pótlást
megengedi (a rendszám a felrakodás megkezdéséig adható meg).
**The missing plate — making the plate mandatory before submission** (rejected): it would eliminate the
`SentWithMissingData` state, in exchange for holding back the declaration even when NAV permits completing it
later (the plate may be given up to the start of loading).
**Fülönkénti gomb-oszlopok, kódból állított `Visible`-lel** (elvetve): ugyanazt a kapcsolót fogná a kód, a
felhasználó Column Chooser-e és a visszatöltött layout — utóbbi a következő betöltésnél felül is írja. Az öt
gombhoz három oszlop-definíció tartozna, a Generate kettőben leírva.
**The status model — extending the existing enum** (rejected): `ModifyReady` / `ModifyValidationError` appended at
the end. Its argument was real: the enum is append-only, so this is the sanctioned path, there is no migration,
and the tab filter's precomputed int array keeps working **unchanged**, because the predicate still depends only
on the enum. Against it: the card's validity has to be stated twice — the modify branch rewrites the create
branch's vocabulary — and every further operation multiplies it again. The two axes give the same thing without
duplicating vocabulary.
**Minden gomb minden fülön, csak `Enabled`-del szabályozva** (elvetve): a „Lezárt" fülön öt letiltott gomb
sorakozna. A szabály helye jó (a státusz), a megjelenítésé nem.
**The status model — a derived phase, from expressions** (rejected): an extension bound to `IFmEkaerHistory`
computes the phase from the existing fields, without storing it. Its pitfall is the query: the tab filter
translates to SQL, an extension does not — `GetByFilter` would have received a second, hand-maintained truth,
exactly what today's code comment guards against ("Felsorolva a kimaradása néma lenne" — enumerated, its omission
would be silent). It could have been solved with an `Expression<Func<…, bool>>` per phase, but two stored columns
give the same thing more simply.
**Per-tab button columns, with `Visible` set from code** (rejected): the code, the user's Column Chooser and the
restored layout would all grab the same switch — and the last of those overwrites it on the next load. Three
column definitions would belong to the five buttons, with Generate written in two of them.
**Every button on every tab, governed only by `Enabled`** (rejected): on the „Lezárt" tab five disabled buttons
would line up. The rule's place is right (the status), its presentation is not.
## Related
- Related ADRs: `ADR 0003` (a bejelentés-egység: allokáció → kamion)
- Related TODOs/Issues: `FBANKAPP-EKAER-T-R9D4` (horgony), `FBANKAPP-EKAER-T-K3P7` (strukturált házszám),
`MGFBANKPLUG-EKAER-I-T7M3` (a kimenő dátum forrása), `MGFBANKPLUG-EKAER-T-N4E7` (delete/újragenerálás)
- External references: `AyCode.Services/docs/NAV/EKAER_OPERATIONS.md` (műveletek és státusz-életciklus)
- Related ADRs: `ADR 0003` (the declaration unit: allocation → truck)
- Related TODOs/Issues: `FBANKAPP-EKAER-T-R9D4` (anchor), `FBANKAPP-EKAER-T-K3P7` (structured house number),
`MGFBANKPLUG-EKAER-I-T7M3` (the source of the outbound date), `MGFBANKPLUG-EKAER-T-N4E7`
(delete / regeneration)
- External references: `AyCode.Services/docs/NAV/EKAER_OPERATIONS.md` (operations and the status lifecycle)
+31 -26
View File
@@ -1,47 +1,52 @@
# Architecture Decision Records (ADR)
Architektúra-szintű döntések durable rekordjai (Nygard-stílus): egy fájl = egy döntés, `NNNN-<RAND>-<slug>.md` néven. Minden ADR: kontextus → döntés → következmények → alternatívák. A `Status` a Nygard 4-értékű szótár: `Proposed` / `Accepted` / `Superseded by ADR-XXXX` / `Rejected` (ez KÜLÖN a `_ISSUES.md`/`_TODO.md` 3-értékű `Open`/`InProgress`/`Closed` szótárától).
Durable records of architecture-level decisions (Nygard style): one file = one decision, named `NNNN-<RAND>-<slug>.md`. Every ADR: context → decision → consequences → alternatives. `Status` is Nygard's four-value vocabulary: `Proposed` / `Accepted` / `Superseded by ADR-XXXX` / `Rejected` (SEPARATE from the three-value `Open` / `InProgress` / `Closed` vocabulary of `_ISSUES.md` / `_TODO.md`).
> Formátum + folyamat: `adr-author` skill (AyCode.Core `.github/skills/adr-author/`), sablon: `references/ADR_TEMPLATE.md`.
> Format and procedure: the `adr-author` skill (AyCode.Core `.github/skills/adr-author/`), template: `references/ADR_TEMPLATE.md`.
## Számozás és hivatkozás
## Numbering and citation
- **`<RAND>`**: 4 karakter `[A-Z0-9]`, generálva és ütközés-ellenőrizve az `AyCode.Core/.github/skills/docs-check/references/TOPIC_CODES.md` eljárása szerint. A séma előtt írt rekordok a csupasz `NNNN-<slug>.md` alakot viselik.
- **`NNNN`**: `max(existing) + 1` írás előtt — a **monotonitásért**, nem az egyediségért; az ütközés ellen a suffix véd (ACCORE ADR 0018-P2X8). **Az azonos `NNNN` érvényes**: azt jelenti, hogy a két rekord párhuzamosan született, egyik sem előzi a másikat — nem kell átszámozni.
- **Hivatkozás**: a szabály a **suffixre** kulcsol, nem számra. Ahol van suffix, mindig a **teljes ID**-t kell írni; ahol nincs, a rekord úgy hivatkozandó, ahogy van. A suffix hozzáfűzött, nem helyettesítő — egy `ADR 0001` keresés továbbra is megtalálja az `ADR 0001-K7M2`-t.
- **`<RAND>`**: 4 characters `[A-Z0-9]`, generated and collision-checked per the procedure in `AyCode.Core/.github/skills/docs-check/references/TOPIC_CODES.md`. Records written before this scheme carry the bare `NNNN-<slug>.md` form.
- **`NNNN`**: `max(existing) + 1` at write time — for **monotonicity**, not uniqueness; the suffix is what guards against collision (ACCORE ADR 0018-P2X8). **A repeated `NNNN` is valid**: it means the two records were written in parallel, neither preceding the other — there is nothing to renumber.
- **Citation**: the rule keys on the **suffix**, not on a number. Where there is a suffix, always write the **full ID**; where there is none, the record is cited as it stands. The suffix is appended, not substituted — an `ADR 0001` search still finds `ADR 0001-K7M2`.
## Index
Az implementáció állapotát ADR-enként **pontosan egy** `_TODO` / `_ISSUES` horgony-bejegyzés hordozza — ezért külön oszlop.
The implementation status is carried by **exactly one** `_TODO` / `_ISSUES` anchor entry per ADR — hence the separate column.
| ADR | Cím | Status | Tracking entry |
| ADR | Title | Status | Tracking entry |
|---|---|---|---|
| [0001](0001-non-measurable-item-derived-weight.md) | Nem mérendő tétel súlya — derivált pallet-mérés a termék átlagsúlyából és tárájából | Proposed (2026-07-14) | `FBANKAPP-MEASUR-T-K4W7` ([MEASURING_TODO](../MEASURING/MEASURING_TODO.md)) — InProgress |
| [0002](0002-environment-infrastructure-to-framework.md) | Csatlakozás a keret környezet-infrastruktúrájához (opt-in, delegátok, endpoint) | Accepted (2026-07-15, rev. 2026-07-25) | `FBANKAPP-ENV-T-R3M9` ([ENV_TODO](../ENV/ENV_TODO.md)) — InProgress |
| [0003](0003-shipping-item-to-shipping-mapping.md) | Tétel↔kamion hozzárendelés külön mapping-táblával (`ShippingItemToShipping`) | Accepted (2026-07-23, rev. 2026-07-24 / 07-27) | `FBANKAPP-SHPLAN-T-W2J6` ([SHPLAN_TODO](../SHPLAN/SHPLAN_TODO.md)) — Implemented |
| [0004-M9F3](0004-M9F3-fractional-pallets-and-remainder-grouping.md) | Tört raklapszám a rekesz/raklap arányból, és a maradékok összevonása közös raklapra | Accepted (2026-08-02) | `FBANKAPP-SHPLAN-T-D8K5` ([SHPLAN_TODO](../SHPLAN/SHPLAN_TODO.md)) — Open |
| [0005-H2Q6](0005-H2Q6-ekaer-consumer-implementation.md) | Az EKÁER fogyasztói oldala — a bejelentés vezérlése a beküldés után | Proposed (2026-08-18, rev. 2026-08-18, 2026-08-19, 2026-08-20) | `FBANKAPP-EKAER-T-R9D4` ([EKAER_TODO](../EKAER/EKAER_TODO.md)) — InProgress |
| [0005-H2Q6](0005-H2Q6-ekaer-consumer-implementation.md) | The consumer side of EKÁER — steering the declaration after submission | Proposed (2026-08-18, rev. 2026-08-18, 2026-08-19, 2026-08-20, 2026-08-21) | `FBANKAPP-EKAER-T-R9D4` ([EKAER_TODO](../EKAER/EKAER_TODO.md)) — InProgress |
## Ezt a repót érintő döntések más rétegben
> The `Title` cells above are **bound**: each is its record's H1 with the `ADR <NNNN>[-<RAND>]: ` prefix stripped,
> character-identical (docs-audit invariant 47). A re-title changes both, in the same write.
Az ADR-struktúra még nem él minden repóban, ezért néhány **termék- / nagyker-témájú** döntés jelenleg az
`AyCode.Core` `docs/adr/` mappájában van rögzítve. A rétegszabály (alacsonyabb layer nem hivatkozhat magasabbra)
miatt onnan nem lehet ide visszamutatni — **ez a tábla a lefelé mutató ellenpárja**, és ez az egyetlen hely,
ahol ezek a rekordok innen felfedezhetők.
## Decisions affecting this repo, recorded in another layer
> **Interim.** Amikor ezek az ADR-ek a saját rétegükbe költöznek (a wholesale / Mango.Nop réteg, amint létezik),
> ez a tábla felülvizsgálandó. A tartós megoldás nem ez, hanem az ADR-ek helyes rétegbe helyezése.
The ADR structure does not exist in every repo yet, so a few **product / wholesale** decisions are currently
recorded in `AyCode.Core`'s `docs/adr/` folder. The layer rule (a lower layer may not reference a higher one)
means they cannot point back here — **this table is the downward counterpart**, and it is the only place from
which those records are discoverable from here.
| ADR | Cím | Status |
> **Interim.** When these ADRs move into their own layer (the wholesale / Mango.Nop layer, once it exists), this
> table is to be revisited. The durable answer is not this table but placing the ADRs in the right layer.
| ADR | Title | Status |
|---|---|---|
| [ACCORE 0009](../../../../../Aycode/Source/AyCode.Core/docs/adr/0009-shared-layer-across-nop-versions.md) | Hogyan támogatja a közös réteg a több nopCommerce-verziót | Proposed (2026-07-21) |
| [ACCORE 0010](../../../../../Aycode/Source/AyCode.Core/docs/adr/0010-product-solution-scaffolding-template.md) | Scaffolding új termékhez — template a solution-váznak, skill a repók közti bekötésnek | Proposed (2026-07-21, rev. 2026-08-06) |
| [ACCORE 0011](../../../../../Aycode/Source/AyCode.Core/docs/adr/0011-copied-and-overridden-nop-code.md) | Másolt és override-olt nopCommerce-kód kezelése | Proposed (2026-07-25) |
| [ACCORE 0002](../../../../../Aycode/Source/AyCode.Core/docs/adr/0002-environment-infrastructure-to-framework.md) | Környezet-infrastruktúra a keretben — a fenti 0002 **keret-oldali párja** (mechanizmus: `AcEnv`, fenntartott tagek, kliens-base) | Accepted (2026-07-15, rev. 2026-07-25) |
| [ACCORE 0009](../../../../../Aycode/Source/AyCode.Core/docs/adr/0009-shared-layer-across-nop-versions.md) | How the shared layer supports several nopCommerce versions | Proposed (2026-07-21) |
| [ACCORE 0010](../../../../../Aycode/Source/AyCode.Core/docs/adr/0010-product-solution-scaffolding-template.md) | Scaffolding a new product a template for the solution skeleton, a skill for the cross-repo wiring | Proposed (2026-07-21, rev. 2026-08-06) |
| [ACCORE 0011](../../../../../Aycode/Source/AyCode.Core/docs/adr/0011-copied-and-overridden-nop-code.md) | Handling copied and overridden nopCommerce code | Proposed (2026-07-25) |
| [ACCORE 0002](../../../../../Aycode/Source/AyCode.Core/docs/adr/0002-environment-infrastructure-to-framework.md) | Environment infrastructure in the framework — the **framework-side pair** of 0002 above (mechanism: `AcEnv`, reserved tags, client base) | Accepted (2026-07-15, rev. 2026-07-25) |
> Ide **csak más rétegben lakó** rekord kerülhet. A repó saját ADR-jei a fenti [Index](#index)-ben vannak — oda
> tartoznak, mert a `Tracking entry` oszlopuk (a horgony-bejegyzés) ebben a táblában nem ábrázolható.
> Only a record living in **another layer** belongs here. This repo's own ADRs are in the [Index](#index) above —
> they belong there, because their `Tracking entry` column (the anchor entry) cannot be represented in this table.
> These `Title` cells are **not** bound the way the Index's are: a row pointing into another repo's tree is a
> pointer, not a mirror, so the cell is local knowledge — a gloss.
**Kapcsolódó terv:** a tervezési összefoglaló a FruitMasterErp repóba költözött (`FRAMEWORK_PLAN.md`, a `FRAMEWORK`
topik alatt) — a fenti ADR-ek arra a draftra hivatkoznak, és részben felül is írják; a supersession-lista a draft
fejlécében.
**Related plan:** the design summary moved into the FruitMasterErp repo (`FRAMEWORK_PLAN.md`, under the `FRAMEWORK`
topic) — the ADRs above reference that draft and partly override it; the supersession list is in the draft's
header.