Compare commits

...

2 Commits

Author SHA1 Message Date
Loretta e9438e4374 Ekaer validation tightened, SignalR tags, doc updates
- Enforce `weight > 0` and `value > 0` for each trade item in `EkaerTradeCardValidator` (NAV 2021+ requirement); add tests for zero/missing values.
- Add grid layout roaming tags (90010–90012) to `AcSignalRTags` for key-value layout storage.
- Suppress info-level logging for SignalR ping messages in `AcBinaryHubProtocol`.
- Update EKÁER and SignalR documentation with new rules, tags, and latest legal references (2026-07).
- Add new build/test scripts to `settings.local.json`.
2026-07-10 15:36:58 +02:00
Loretta dc8e01250d Rotate DEC log, archive closed TODOs, add GiaApp consumer
- Rotated `LLM_PROTOCOL_DECISIONS.md` per last-15-active policy: archived entries 53–54 to `LLM_PROTOCOL_DECISIONS_2026_04.md`, updated archive pointers and summary notes.
- Archived all `Status: Closed` entries from `SIGNALR_BINARY_PROTOCOL_TODO.md` to new `SIGNALR_BINARY_PROTOCOL_TODO_2026_05.md`; added archive pointer and explanatory header.
- Updated `CONSUMERS.md` to add GiaApp (GIAAPP), raising consumer count to 7.
- Keeps active logs concise and historical entries accessible per year-month bucket archival policy.
2026-07-09 06:57:22 +02:00
15 changed files with 213 additions and 142 deletions

View File

@ -147,7 +147,10 @@
"Bash(git -C \"H:/Applications/Aycode/Source/AyCode.Core\" mv \"AyCode.Services.Server/docs/SIGNALR_DATASOURCE\" \"AyCode.Services/docs/SIGNALR_DATASOURCE\")", "Bash(git -C \"H:/Applications/Aycode/Source/AyCode.Core\" mv \"AyCode.Services.Server/docs/SIGNALR_DATASOURCE\" \"AyCode.Services/docs/SIGNALR_DATASOURCE\")",
"Bash(awk 'NR==135' \"H:/Applications/Aycode/Source/AyCode.Core/.github/LLM_PROTOCOL_DECISIONS.md\")", "Bash(awk 'NR==135' \"H:/Applications/Aycode/Source/AyCode.Core/.github/LLM_PROTOCOL_DECISIONS.md\")",
"Bash(git -C \"H:/Applications/Aycode/Source/AyCode.Core\" status --short)", "Bash(git -C \"H:/Applications/Aycode/Source/AyCode.Core\" status --short)",
"Bash(git -C \"H:/Applications/Aycode/Source/AyCode.Core\" reset)" "Bash(git -C \"H:/Applications/Aycode/Source/AyCode.Core\" reset)",
"Bash(dotnet build *)",
"PowerShell(dotnet build AyCode.Services\\\\AyCode.Services.csproj --nologo -v q 2>$null)",
"PowerShell(dotnet test AyCode.Services.Server.Tests\\\\AyCode.Services.Server.Tests.csproj --nologo --no-restore --filter \"FullyQualifiedName~AcBinaryHubProtocolConcurrencyTests\" 2>$null)"
] ]
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -46,6 +46,7 @@ public sealed class EkaerTradeCardValidatorTests
ProductVtsz = "08081010", ProductVtsz = "08081010",
ProductName = "Alma", ProductName = "Alma",
Weight = 123.5m, Weight = 123.5m,
Value = 250_000,
}); });
return operation; return operation;
} }
@ -96,6 +97,30 @@ public sealed class EkaerTradeCardValidatorTests
Assert.IsTrue(HasError(Sut.Validate(op), "címzett"), "üres/whitespace címzettnév → hiba"); Assert.IsTrue(HasError(Sut.Validate(op), "címzett"), "üres/whitespace címzettnév → hiba");
} }
[TestMethod]
public void Validate_ZeroWeight_ReportsError()
{
var op = ValidOperation();
op.TradeCard!.Items[0].Weight = 0;
Assert.IsTrue(HasError(Sut.Validate(op), "tömege"), "0 tömeg (hiányzó súly a doksin) → üzleti szabály hiba");
}
[TestMethod]
public void Validate_MissingValue_ReportsError()
{
var op = ValidOperation();
op.TradeCard!.Items[0].Value = null;
Assert.IsTrue(HasError(Sut.Validate(op), "értéke"), "hiányzó tétel-érték → üzleti szabály hiba (NAV 2021-től)");
}
[TestMethod]
public void Validate_ZeroValue_ReportsError()
{
var op = ValidOperation();
op.TradeCard!.Items[0].Value = 0;
Assert.IsTrue(HasError(Sut.Validate(op), "értéke"), "0 tétel-érték → üzleti szabály hiba (NAV 2021-től)");
}
// ---- Séma-szint (generált DataAnnotations) ------------------------------ // ---- Séma-szint (generált DataAnnotations) ------------------------------
[TestMethod] [TestMethod]

View File

@ -10,7 +10,7 @@ namespace AyCode.Services.Nav.Ekaer;
/// <item>a generált modellek <see cref="ValidationAttribute"/>-jai (Required / RegularExpression / Min-MaxLength), /// <item>a generált modellek <see cref="ValidationAttribute"/>-jai (Required / RegularExpression / Min-MaxLength),
/// rekurzívan a tradeCard + tételek + jármű + helyszínek fölött — ezt a <see cref="Validator"/> végzi;</item> /// rekurzívan a tradeCard + tételek + jármű + helyszínek fölött — ezt a <see cref="Validator"/> végzi;</item>
/// <item>az XSD-ben NEM jelölt, de a NAV által megkövetelt üzleti szabályok (vonó jármű, legalább egy tétel, /// <item>az XSD-ben NEM jelölt, de a NAV által megkövetelt üzleti szabályok (vonó jármű, legalább egy tétel,
/// feladó/címzett alapadatok) — ezeket kézzel ellenőrizzük.</item> /// tétel-tömeg/-érték &gt; 0, feladó/címzett alapadatok) — ezeket kézzel ellenőrizzük.</item>
/// </list> /// </list>
/// </remarks> /// </remarks>
public sealed class EkaerTradeCardValidator : IEkaerTradeCardValidator public sealed class EkaerTradeCardValidator : IEkaerTradeCardValidator
@ -81,6 +81,12 @@ public sealed class EkaerTradeCardValidator : IEkaerTradeCardValidator
// a sémán (a null bukik a Required-en, az "" nem, mert a RegularExpression az üreset validnak veszi), // a sémán (a null bukik a Required-en, az "" nem, mert a RegularExpression az üreset validnak veszi),
// ezért üzleti szabállyal lezárjuk. A { Length: 0 } property-pattern a null-ra NEM illeszkedik → nincs dupla hiba. // ezért üzleti szabállyal lezárjuk. A { Length: 0 } property-pattern a null-ra NEM illeszkedik → nincs dupla hiba.
if (item.ProductVtsz is { Length: 0 }) errors.Add(Error($"{itemPath}.productVtsz", "a tétel VTSZ-száma (productVtsz) nem lehet üres")); if (item.ProductVtsz is { Length: 0 }) errors.Add(Error($"{itemPath}.productVtsz", "a tétel VTSZ-száma (productVtsz) nem lehet üres"));
// A tétel bruttó tömege kötelező és pozitív — a 0 azt jelzi, hogy a forrás-dokumentumról hiányzik a súly (adathiba).
if (item.Weight <= 0) errors.Add(Error($"{itemPath}.weight", "a tétel bruttó tömege (weight) kötelező és nem lehet 0"));
// A NAV 2021.01.01-től minden tételre value > 0-t követel — a hiányzó/0 érték (pl. ismeretlen ár) blokkoló adathiba.
if (item.Value is not > 0) errors.Add(Error($"{itemPath}.value", "a tétel értéke (value) kötelező és pozitív (NAV-követelmény 2021-től)"));
} }
// 3) Üzleti szabályok (az XSD nem jelöli [Required]-ként, de a NAV megköveteli). // 3) Üzleti szabályok (az XSD nem jelöli [Required]-ként, de a NAV megköveteli).

View File

@ -3,6 +3,8 @@
> **Forrás:** [`eKAERManagementService_2.2.pdf`](eKAERManagementService_2.2.pdf) + [`ekaermanagement.xsd`](ekaermanagement.xsd) / [`common.xsd`](common.xsd). > **Forrás:** [`eKAERManagementService_2.2.pdf`](eKAERManagementService_2.2.pdf) + [`ekaermanagement.xsd`](ekaermanagement.xsd) / [`common.xsd`](common.xsd).
> Ez **LLM-barát kivonat** a gyakori dolgokról — ütközés esetén **a PDF/XSD a hiteles forrás**. > Ez **LLM-barát kivonat** a gyakori dolgokról — ütközés esetén **a PDF/XSD a hiteles forrás**.
> Online: <https://ekaer.nav.gov.hu/faq/?page_id=9> > Online: <https://ekaer.nav.gov.hu/faq/?page_id=9>
>
> **Verzió-állapot (2026-07-10, a NAV-oldalról ellenőrizve):** a **2.2 az aktuális** interfész-verzió, újabb nincs bejelentve. A NAV a 2.2 dokumentációt **2025-05-14-én frissítette** („Lejárt jelszó kezelése" — `EXPIRED_PASSWORD` eset viselkedése). **A helyi PDF EZ a friss kiadás** (byte-azonos méret az online-nal, letöltve 2026-06-01) — nem kell újra letölteni.
## Transport ## Transport

View File

@ -86,11 +86,15 @@ Mezők: `name`, `VATNumber`, `phone`, `e-mail`, `country`, `zipCode`, `city`, `s
| Kód | Jelentés | Biztosíték? | | Kód | Jelentés | Biztosíték? |
|---|---|---| |---|---|---|
| `S` | Termékértékesítés / -beszerzés | **van** | | `S` | Termékértékesítés **/ -beszerzés** — EGY kód MINDKÉT irányra (kimenő eladás ÉS bejövő beszerzés) | **van** |
| `A` | Saját tulajdonú termék (kivezetve 2015.03.01 után — új bejelentésnél nem adható) | van | | `A` | Saját tulajdonú termék (kivezetve 2015.03.01 után — új bejelentésnél nem adható) | van |
| `W` | Bérmunka | nincs | | `W` | Bérmunka | nincs |
| `O` | Egyéb | nincs | | `O` | Egyéb | nincs |
> ⚠️ **Gyakori félreértés:** az `A` NEM „beszerzés" (angol *acquisition* alapján kézenfekvő, de TÉVES) — a `common.xsd` szerint `A` = „Saját tulajdonú termék - Törvény változás miatt kivezetve." A **beszerzésre is az `S`** való; belföldön (`D`) ráadásul CSAK az `S` megengedett (lásd lent).
>
> ⛔ **Az `ekaermanagement.xsd` tétel-kommentje ELAVULT és félrevezet:** a `TradeCardItemGroup`/`tradeReason` elem inline dokumentációja („S: Értékesítés **A: Beszerzés** W: Bérmunka O: Egyéb") a kivezetés ELŐTTI állapot — NE ebből dolgozz. A mérvadó a `common.xsd` `TradeReasonType` enum-annotációja + a PDF (webről is megerősítve, 2026-07-10: az `A` 2015.03.01 után nem adható).
**A `tradeReason` a `tradeType`-tól függően korlátozott:** **A `tradeReason` a `tradeType`-tól függően korlátozott:**
- `E` (export): `S`, `W`, `O` - `E` (export): `S`, `W`, `O`
- `I` (import): `S`, `W`, `O` - `I` (import): `S`, `W`, `O`

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -261,7 +261,10 @@ public class AcBinaryHubProtocol : IHubProtocol
public void WriteMessage(HubMessage message, IBufferWriter<byte> output) public void WriteMessage(HubMessage message, IBufferWriter<byte> output)
{ {
_logger?.LogInformation("Serialize start"); // Keepalive pings (~15 s, both directions) would flood the log channel at Information
// level — real messages keep the start/end brackets (hang diagnostics, ACCORE-SBP-I-T3W9).
var isPing = message is PingMessage;
if (!isPing) _logger?.LogInformation("Serialize start");
// AsyncSegment: chunked protocol framing for messages with streamable arguments // AsyncSegment: chunked protocol framing for messages with streamable arguments
if (_protocolMode == BinaryProtocolMode.AsyncSegment if (_protocolMode == BinaryProtocolMode.AsyncSegment
@ -327,7 +330,7 @@ public class AcBinaryHubProtocol : IHubProtocol
bw.Flush(); bw.Flush();
Unsafe.WriteUnaligned(ref lengthSpan[0], totalPayload); Unsafe.WriteUnaligned(ref lengthSpan[0], totalPayload);
_logger?.LogInformation("Serialize end totalSentSize={TotalSentSize}", LengthPrefixSize + totalPayload); if (!isPing) _logger?.LogInformation("Serialize end totalSentSize={TotalSentSize}", LengthPrefixSize + totalPayload);
if (_logger?.IsEnabled(LogLevel.Debug) == true) if (_logger?.IsEnabled(LogLevel.Debug) == true)
_logger.LogDebug("WriteMessage {MessageType} payloadSize={PayloadSize}", message.GetType().Name, totalPayload); _logger.LogDebug("WriteMessage {MessageType} payloadSize={PayloadSize}", message.GetType().Name, totalPayload);
@ -654,7 +657,12 @@ public class AcBinaryHubProtocol : IHubProtocol
return false; return false;
_logger?.LogTrace("TryParseMessage parsing payloadLength={PayloadLength} inputLength={InputLength}", payloadLength, input.Length); _logger?.LogTrace("TryParseMessage parsing payloadLength={PayloadLength} inputLength={InputLength}", payloadLength, input.Length);
_logger?.LogInformation("Deserialize start");
// Keepalive ping frame (payload = single MsgPing byte) — skip the Information-level
// brackets (~15 s spam, both directions). Wire-level check: the routing-fallback that
// SURFACES PingMessage.Instance for real payloads (see ParseMessage) must keep its brackets.
var isPing = payloadLength == 1 && reader.TryPeek(out var firstByte) && firstByte == MsgPing;
if (!isPing) _logger?.LogInformation("Deserialize start");
message = ParseMessage(ref reader, payloadLength, binder); message = ParseMessage(ref reader, payloadLength, binder);
@ -662,7 +670,7 @@ public class AcBinaryHubProtocol : IHubProtocol
{ {
input = input.Slice(LengthPrefixSize + payloadLength); input = input.Slice(LengthPrefixSize + payloadLength);
_logger?.LogInformation("Deserialize end"); if (!isPing) _logger?.LogInformation("Deserialize end");
if (_logger?.IsEnabled(LogLevel.Debug) == true) _logger.LogDebug("TryParseMessage parsed {MessageType}", message.GetType().Name); if (_logger?.IsEnabled(LogLevel.Debug) == true) _logger.LogDebug("TryParseMessage parsed {MessageType}", message.GetType().Name);
return true; return true;

View File

@ -6,4 +6,11 @@ public class AcSignalRTags
public const int PingTag = 90001; public const int PingTag = 90001;
public const int EchoTag = 90002; public const int EchoTag = 90002;
/// <summary>
/// Grid layout roaming (key-value store): key = composite grid-layout key, value = layout JSON, userId = owning user.
/// </summary>
public const int GetGridLayoutTag = 90010;
public const int SaveGridLayoutTag = 90011;
public const int DeleteGridLayoutTag = 90012;
} }

View File

@ -20,7 +20,7 @@ Custom binary SignalR protocol, client infrastructure, message tagging, and seri
### Message Tagging ### Message Tagging
- **`SignalMessageTagAttribute.cs`** — Three attributes: `TagAttribute` (base, int messageTag), `SignalRAttribute` (server method routing + client notification), `SignalRSendToClientAttribute` (client-side receive). - **`SignalMessageTagAttribute.cs`** — Three attributes: `TagAttribute` (base, int messageTag), `SignalRAttribute` (server method routing + client notification), `SignalRSendToClientAttribute` (client-side receive).
- **`AcSignalRTags.cs`** — Static constants: `None`, `PingTag`, `EchoTag`. - **`AcSignalRTags.cs`** — Static constants: `None`, `PingTag`, `EchoTag`; grid-layout roaming tags `GetGridLayoutTag` / `SaveGridLayoutTag` / `DeleteGridLayoutTag` (90010-12) — key-value layout storage contract (`key`, [`layoutJson`,] `userId`), implemented by consumer hubs.
- **`SignalRCrudTags.cs`** — Sealed class bundling 5 independent CRUD tag integers. `GetMessageTagByTrackingState()` maps `TrackingState` -> tag. See `AyCode.Services/docs/SIGNALR_DATASOURCE/README.md`. - **`SignalRCrudTags.cs`** — Sealed class bundling 5 independent CRUD tag integers. `GetMessageTagByTrackingState()` maps `TrackingState` -> tag. See `AyCode.Services/docs/SIGNALR_DATASOURCE/README.md`.
- **`SendToClientType.cs`** — Enum: None, Others, Caller, All. - **`SendToClientType.cs`** — Enum: None, Others, Caller, All.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
> >
> **Strict rule**: AyCode.Core code, types, namespaces, topic IDs, and any other `.md` files (`docs/`, `.github/` excluding the narrow workspace-meta-history exception) MUST NOT name any specific consumer listed here. **This is the SINGLE place** in ACCORE that may name consumer repos. > **Strict rule**: AyCode.Core code, types, namespaces, topic IDs, and any other `.md` files (`docs/`, `.github/` excluding the narrow workspace-meta-history exception) MUST NOT name any specific consumer listed here. **This is the SINGLE place** in ACCORE that may name consumer repos.
## Workspace consumer map (as of 2026-04-26) ## Workspace consumer map (as of 2026-07-08)
| Prefix | Repo | Layer | Type | Path (workspace-relative) | Brief usage | | Prefix | Repo | Layer | Type | Path (workspace-relative) | Brief usage |
|----------------|-------------------------------|------:|---------------------|--------------------------------------------------------------------|------------------------------------------------------------------------------| |----------------|-------------------------------|------:|---------------------|--------------------------------------------------------------------|------------------------------------------------------------------------------|
@ -14,13 +14,14 @@
| `MGFBANKPLUG` | Nop.Plugin.Misc.AIPlugin | 3 | consumer (plugin) | `.../Plugins/Nop.Plugin.Misc.AIPlugin` | FruitBank nopCommerce plugin source | | `MGFBANKPLUG` | Nop.Plugin.Misc.AIPlugin | 3 | consumer (plugin) | `.../Plugins/Nop.Plugin.Misc.AIPlugin` | FruitBank nopCommerce plugin source |
| `FBANKNOP` | FruitBank | 2 | product (server) | `Mango/Source/FruitBank` | nopCommerce server source for the FruitBank deployment | | `FBANKNOP` | FruitBank | 2 | product (server) | `Mango/Source/FruitBank` | nopCommerce server source for the FruitBank deployment |
| `FBANKAPP` | FruitBankHybridApp | 2 | product (hybrid) | `Mango/Source/FruitBankHybridApp` | MAUI/Blazor hybrid client app | | `FBANKAPP` | FruitBankHybridApp | 2 | product (hybrid) | `Mango/Source/FruitBankHybridApp` | MAUI/Blazor hybrid client app |
| `GIAAPP` | GiaApp | 2 | product (web) | `Mango/Source/GiaApp` | Blazor Server GPS ↔ worksheet reconciliation app (entities/user-stack/data-layer; app-level SignalR hubs on base `AcBinaryHubProtocol`, tag-based layer unused) |
(See `.github/REPO_PREFIXES.md` for the prefix scheme; each consumer's own `.github/copilot-instructions.md` `@repo` block declares its `prefix` value per LLMP-DEC-58.) (See `.github/REPO_PREFIXES.md` for the prefix scheme; each consumer's own `.github/copilot-instructions.md` `@repo` block declares its `prefix` value per LLMP-DEC-58.)
## When modifying AyCode.Core — change-impact lens ## When modifying AyCode.Core — change-impact lens
Use this list to assess change impact: Use this list to assess change impact:
- A breaking change to a base class affects all 6 consumers above (cascading rebuilds + verification). - A breaking change to a base class affects all 7 consumers above (cascading rebuilds + verification).
- A new feature gated behind opt-in DI extension → no cascade, just new capability. - A new feature gated behind opt-in DI extension → no cascade, just new capability.
- A signature change in a widely-used interface (e.g., `IAcSignalRClientBase`) → at minimum: ACBLAZOR + MGNOPLIB + MGFBANKPLUG + FBANKNOP + FBANKAPP all need verification. - A signature change in a widely-used interface (e.g., `IAcSignalRClientBase`) → at minimum: ACBLAZOR + MGNOPLIB + MGFBANKPLUG + FBANKNOP + FBANKAPP all need verification.
- A change to ID format / topic-code conventions → MGNOPCORE and MGFBANKPLUG (inherit-protocol) plus all primary repos need updating. - A change to ID format / topic-code conventions → MGNOPCORE and MGFBANKPLUG (inherit-protocol) plus all primary repos need updating.