Merge branch 'FruitBank_v0.0.8.2' of https://git.aycode.com/Adam/Mango.Nop.Plugins into FruitBank_v0.0.8.2

This commit is contained in:
2026-08-19 00:18:50 +02:00
16 changed files with 39 additions and 39 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ This file is injected by the host. **Before answering anything, read `.github/LL
Every response begins with the `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files (+K this turn: <short names>)]` prefix — `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files, no new loads]` when nothing new. The timestamp is the present time, obtained in this turn; `no clock` only where the clock cannot be read. Semantics in `BASE:CONTEXT_LEDGER`.
After any context compaction or session summary, `[LOADED_DOCS]` is `NONE` — the count survives compaction, the file contents do not. Run Session Setup again before answering: a continuation instruction resumes the task, never the context.
`[LOADED_DOCS]` counts only what a tool call in the visible conversation loaded. A summary or a compaction is not a tool call: after one the ledger is `NONE`, whatever the previous prefix claimed. The first response of a context runs Session Setup and names the loaded files in `+K`; a context's first prefix never says `no new loads`. A continuation instruction resumes the task, never the context.
## Sequential execution
+1 -1
View File
@@ -6,7 +6,7 @@ CRITICAL: Your FIRST action in every session is the bootstrap chain below. Compl
Every response begins with the `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files (+K this turn: <short names>)]` prefix — `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files, no new loads]` when nothing new. The timestamp is the present time, obtained in this turn; `no clock` only where the clock cannot be read. Semantics in `BASE:CONTEXT_LEDGER`.
After any context compaction or session summary, `[LOADED_DOCS]` is `NONE` — the count survives compaction, the file contents do not. Run Session Setup again before answering: a continuation instruction resumes the task, never the context.
`[LOADED_DOCS]` counts only what a tool call in the visible conversation loaded. A summary or a compaction is not a tool call: after one the ledger is `NONE`, whatever the previous prefix claimed. The first response of a context runs Session Setup and names the loaded files in `+K`; a context's first prefix never says `no new loads`. A continuation instruction resumes the task, never the context.
## Sequential execution
+1 -1
View File
@@ -6,7 +6,7 @@ CRITICAL: Your FIRST action in every session is the bootstrap chain below. Compl
Every response begins with the `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files (+K this turn: <short names>)]` prefix — `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files, no new loads]` when nothing new. The timestamp is the present time, obtained in this turn; `no clock` only where the clock cannot be read. Semantics in `BASE:CONTEXT_LEDGER`.
After any context compaction or session summary, `[LOADED_DOCS]` is `NONE` — the count survives compaction, the file contents do not. Run Session Setup again before answering: a continuation instruction resumes the task, never the context.
`[LOADED_DOCS]` counts only what a tool call in the visible conversation loaded. A summary or a compaction is not a tool call: after one the ledger is `NONE`, whatever the previous prefix claimed. The first response of a context runs Session Setup and names the loaded files in `+K`; a context's first prefix never says `no new loads`. A continuation instruction resumes the task, never the context.
## Sequential execution
+1 -1
View File
@@ -6,7 +6,7 @@ This file is injected by the host. **Before answering anything, read `.github/LL
Every response begins with the `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files (+K this turn: <short names>)]` prefix — `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files, no new loads]` when nothing new. The timestamp is the present time, obtained in this turn; `no clock` only where the clock cannot be read. Semantics in `BASE:CONTEXT_LEDGER`.
After any context compaction or session summary, `[LOADED_DOCS]` is `NONE` — the count survives compaction, the file contents do not. Run Session Setup again before answering: a continuation instruction resumes the task, never the context.
`[LOADED_DOCS]` counts only what a tool call in the visible conversation loaded. A summary or a compaction is not a tool call: after one the ledger is `NONE`, whatever the previous prefix claimed. The first response of a context runs Session Setup and names the loaded files in `+K`; a context's first prefix never says `no new loads`. A continuation instruction resumes the task, never the context.
## Sequential execution
+1 -1
View File
@@ -6,7 +6,7 @@ CRITICAL: Your FIRST action in every session is the bootstrap chain below. Compl
Every response begins with the `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files (+K this turn: <short names>)]` prefix — `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files, no new loads]` when nothing new. The timestamp is the present time, obtained in this turn; `no clock` only where the clock cannot be read. Semantics in `BASE:CONTEXT_LEDGER`.
After any context compaction or session summary, `[LOADED_DOCS]` is `NONE` — the count survives compaction, the file contents do not. Run Session Setup again before answering: a continuation instruction resumes the task, never the context.
`[LOADED_DOCS]` counts only what a tool call in the visible conversation loaded. A summary or a compaction is not a tool call: after one the ledger is `NONE`, whatever the previous prefix claimed. The first response of a context runs Session Setup and names the loaded files in `+K`; a context's first prefix never says `no new loads`. A continuation instruction resumes the task, never the context.
## Sequential execution
+1 -1
View File
@@ -6,7 +6,7 @@ CRITICAL: Your FIRST action in every session is the bootstrap chain below. Compl
Every response begins with the `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files (+K this turn: <short names>)]` prefix — `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files, no new loads]` when nothing new. The timestamp is the present time, obtained in this turn; `no clock` only where the clock cannot be read. Semantics in `BASE:CONTEXT_LEDGER`.
After any context compaction or session summary, `[LOADED_DOCS]` is `NONE` — the count survives compaction, the file contents do not. Run Session Setup again before answering: a continuation instruction resumes the task, never the context.
`[LOADED_DOCS]` counts only what a tool call in the visible conversation loaded. A summary or a compaction is not a tool call: after one the ledger is `NONE`, whatever the previous prefix claimed. The first response of a context runs Session Setup and names the loaded files in `+K`; a context's first prefix never says `no new loads`. A continuation instruction resumes the task, never the context.
## Sequential execution
@@ -542,12 +542,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
// Partner/dokumentum nélküli allocation CSENDBEN kihagyva — nincs feladó, nem kész; a mentes partner
// (IsEkaer=false) is. A maradék (Shipping=allocation.ShippingId, Partner, PartnerDepot) csoportban.
// A csoportok ShippingDate szerint növekvő sorrendben kerülnek beszúrásra, hogy a keletkező Id-sorrend
// kronologikus legyen (a grid irányonként szűrve így dátum szerint rendezetten mutatja őket).
// A sorrend itt közömbös: mindkét ág csak GYŰJT, a beszúrás egyetlen közös ciklusban rendez (lásd lentebb).
foreach (var group in candidates
.Where(a => a.ShippingItem?.ShippingDocument?.Partner != null && a.ShippingItem.ShippingDocument.Partner!.IsEkaer != false)
.GroupBy(a => (a.ShippingId, a.ShippingItem!.ShippingDocument!.PartnerId, a.ShippingItem.ShippingDocument.PartnerDepotId))
.OrderBy(g => g.Min(a => a.ShippingItem!.ShippingDocument!.ShippingDate)))
.GroupBy(a => (a.ShippingId, a.ShippingItem!.ShippingDocument!.PartnerId, a.ShippingItem.ShippingDocument.PartnerDepotId)))
{
var allocs = group.ToList();
var doc0 = allocs[0].ShippingItem!.ShippingDocument!;
@@ -616,8 +614,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
.ToListAsync())
.ToDictionary(ga => ga.EntityId, ga => CommonHelper.To<bool>(ga.Value));
// DateOfReceipt szerint növekvő — ugyanaz a kronologikus Id-sorrend, mint a bejövő ágon.
foreach (var order in missingOrders.OrderBy(o => o.DateOfReceipt))
foreach (var order in missingOrders)
{
try
{
@@ -639,8 +636,13 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
}
// ── Beszúrás ──
// A KÉT IRÁNY EGYÜTT rendeződik dátum szerint, mert a beszúrási sorrend adja az Id-ket, és a grid
// alapértelmezett nézete Id szerint áll. Ágakon belül rendezve a lista irány-, majd dátum-rendezett
// lenne — ezért a rendezés itt van, a közös találkozási ponton, nem a két gyűjtő ágban.
// A ShippingDate nullable, de egyik ág sem tud null-t adni: bejövőn a forrás-oszlop nem nullable,
// kimenőn a jelölt-szűrő megköveteli az értelmezhető DateOfReceipt attribútumot.
var createdCount = 0;
foreach (var (history, foreignKeys) in toCreate)
foreach (var (history, foreignKeys) in toCreate.OrderBy(x => x.History.ShippingDate))
{
try
{
@@ -1463,14 +1465,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
return await ctx.ProductDtos.GetAll(false).ToListAsync();
}
//[SignalR(SignalRTags.GetAllMeasuringProductDtos)]
//public async Task<List<MeasuringProductDto>> GetAllMeasuringProductDtos()
//{
// _logger.Detail($"GetAllMeasuringProductDtos invoked");
// return await ctx.GetAllMeasuringProductDtos(false).ToListAsync();
//}
[SignalR(SignalRTags.GetMeasuringProductDtoById)]
[SignalR(SignalRTags.GetProductDtoById)]
public async Task<ProductDto?> GetProductDtoById(int productId)
{
_logger.Detail($"GetProductDtoById invoked; productId: {productId}");
@@ -26,12 +26,20 @@ public class EkaerHistoryDbTable : MgDbTableBase<EkaerHistory>
// miatt már nem IOrderedQueryable) → explicit a LinqToDB-é, ugyanúgy mint a CountByFilterAsync-nél.
public Task<EkaerHistory> GetByIdAsync(int id, bool loadRelations) => LinqToDB.AsyncExtensions.FirstOrDefaultAsync(GetAll(loadRelations), p => p.Id == id);
// A NAV-nál lévő partíciók fix StatusId-k; minden más (a jövőbeni új státuszok is) „beküldésre váró".
private static readonly int[] ToSubmitStatusIds = [.. Enum.GetValues<EkaerStatus>().Select(s => (int)s).Except([(int)EkaerStatus.Sent, (int)EkaerStatus.SentWithMissingData])];
// A partíciók a státusz-kategóriákból SZÁRMAZNAK, nem felsorolásból: egy új EkaerStatus így automatikusan oda
// kerül, ahová a helpere sorolja. Felsorolva a kimaradása néma lenne — egy NAV-nál lévő új állapot a
// „beküldésre váró" fülre esne, hiba nélkül. A kiértékelés memóriában, típus-inicializáláskor fut egyszer.
private static readonly int[] ToSubmitStatusIds = StatusIdsWhere(s => !s.IsSent());
private static readonly int[] ClosedStatusIds = StatusIdsWhere(s => s.IsClosed());
private static readonly int[] OpenAtNavStatusIds = StatusIdsWhere(s => s.IsOpenAtNav());
private static int[] StatusIdsWhere(Func<EkaerStatus, bool> predicate)
=> [.. Enum.GetValues<EkaerStatus>().Where(predicate).Select(s => (int)s)];
/// <summary>Flag-alapú szűrés a tabokhoz/count-hoz. <see cref="EkaerHistoryFilter.All"/> (= 0) → minden;
/// egyébként a beállított flag-ek diszjunkt StatusId-partícióinak UNIÓJA (IN). A státusz-helperek (IsSent stb.)
/// nem fordulnak SQL-re, ezért közvetlen StatusId-halmazzal szűrünk.</summary>
/// egyébként a beállított flag-ek diszjunkt StatusId-partícióinak UNIÓJA (IN). A státusz-helperek nem
/// fordulnak SQL-re, ezért a szűrés kész StatusId-halmazzal megy — a halmazokat viszont ugyanazok a
/// helperek állítják elő (fent), így a partíciók egyetlen forrásból következnek.</summary>
public IQueryable<EkaerHistory> GetByFilter(EkaerHistoryFilter filter, bool loadRelations)
{
// HasFlag(All) mindig true (0 bit), ezért előbb az All-ágat zárjuk rövidre.
@@ -39,8 +47,8 @@ public class EkaerHistoryDbTable : MgDbTableBase<EkaerHistory>
var statusIds = new List<int>();
if (filter.HasFlag(EkaerHistoryFilter.ToSubmit)) statusIds.AddRange(ToSubmitStatusIds);
if (filter.HasFlag(EkaerHistoryFilter.Sent)) statusIds.Add((int)EkaerStatus.Sent);
if (filter.HasFlag(EkaerHistoryFilter.NeedsCompletion)) statusIds.Add((int)EkaerStatus.SentWithMissingData);
if (filter.HasFlag(EkaerHistoryFilter.Closed)) statusIds.AddRange(ClosedStatusIds);
if (filter.HasFlag(EkaerHistoryFilter.OpenAtNav)) statusIds.AddRange(OpenAtNavStatusIds);
return GetAll(loadRelations).Where(eh => statusIds.Contains(eh.StatusId));
}
@@ -208,12 +208,6 @@ public class FruitBankDbContext : MgDbContextBase,
//public IQueryable<ProductDto> GetAllProductDtos(bool includeDeleted)
// => GetAllProducts(includeDeleted).Select(product => new ProductDto(product));
//public IAsyncEnumerable<MeasuringProductDto> GetAllMeasuringProductDtos(bool includeDeleted)
// => GetAllProducts(includeDeleted).AsEnumerable().SelectAwait(async product => new MeasuringProductDto(product, await GetMeasuringAttributeValuesByProductIdAsync(product.Id)));
//public async Task<MeasuringProductDto?> GetMeasuringProductDtoByIdAsync(int productId)
// => await Products.Table.Where(product => product.Id == productId).AsEnumerable().SelectAwait(async product => new MeasuringProductDto(product, await GetMeasuringAttributeValuesByProductIdAsync(product.Id))).FirstOrDefaultAsync();
//public async Task<MeasuringAttributeValues?> GetMeasuringAttributeValuesByProductIdAsync(int productId)
// => await _fruitBankAttributeService.GetMeasuringAttributeValuesAsync<Product>(productId);
@@ -214,7 +214,8 @@ public class PluginNopStartup : INopStartup
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { SslProtocols = SslProtocols.Tls12 }); // a NAV TLS 1.2-t vár
// Ellenőrző végpont: a generáláskor fut, bejelentést NEM hoz létre (validateTradeCardRequest).
services.AddHttpClient<EkaerValidateService>()
// Interfészen át, mint a beküldő párja — így a NAV-határ mindkét fele kicserélhető (teszt-dublúra).
services.AddHttpClient<IEkaerValidateService, EkaerValidateService>()
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { SslProtocols = SslProtocols.Tls12 });
// A NAV-fiók hitelesítő adatai + a saját telephely/raktár — konfigurációból (appsettings "Ekaer" szekció).
@@ -264,6 +265,7 @@ public class PluginNopStartup : INopStartup
// A NAV a street+streetNumber PÁRT követeli (vagy lotNumber-t) — csak utcanévvel
// TC_LOCATION_NOT_COMPLETE-et ad.
Street = site["Street"],
StreetType = site["StreetType"],
StreetNumber = site["StreetNumber"],
Phone = co["Phone"],
Email = co["Email"],
@@ -51,6 +51,7 @@ Validációs hiba → **hibalista** (`EkaerSubmitResult.Invalid`), nem megy ki k
| `vehicle` / `vehicle2` | `Shipping.CargoTruck` / `CargoTrailer` (`LicencePlate` + `CountrySign` — a felségjel, NEM ISO-2 országkód; normalizálva) |
| `loadLocation` (felrakodás) | **bejövő:** a `ShippingDocument.PartnerDepot` **címe** (kötelező — **T-D8R4** kész; a `VatNumber` a Partneré, a depot `TaxId`-ja passthrough); **kimenő:** `company.Site` (configból) |
| `unloadLocation` (lerakodás) | **bejövő:** `company.Site`; **kimenő:** az `order.Site` snapshot (fallback: a vevő fő címe) |
| a cím **mezői** (`street` / `streetType` / `streetNumber`, vagy helyette `lotNumber`) | `company.Site`: külön config-kulcsok (`Ekaer:Company:Site`), tehát a `streetType` is töltve. Partner/depot és vevő: **egyetlen** szabad szöveges utca-mező, amit a `NavAddress.SplitStreet` bont utcára és házszámra — a `streetType` ott üresen marad. |
| tétel `productVtsz` | `ShippingItem.ProductDto.Gtin` (átmenetileg — lásd `EKAER_ISSUES.md`) |
| tétel `productName` | `ShippingItem.ProductName` (a VTSZ-szerinti csoportosítás + a csoport-név `Category`-ből: TODO **T-V9G3**) |
| tétel `weight` | `ShippingItem.GrossWeightOnDocument` — a **szállítólevélen szereplő** bruttó (a bejelentés a kísérő dokumentum adatairól megy, mint a value; NEM a későbbi mérlegelésről) |
@@ -101,7 +101,7 @@ a felület azelőtt, hogy a tárolás átállna.
## Decision
> **An ADR records reasoning, not the state. What is true now is in the docs.**
> **An ADR records reasoning, not the state, and binds nothing — `BASE:ARTIFACT_ROLES`.**
**Az öt érték elsőrendű, tipizált oszloppá válik egy új, plugin-tulajdonú `ProductMasterData` táblában (1:1 a
`Product`-tal), nem pedig a nopCommerce `Product` táblájának bővítésével; a `GenericAttribute`-os tárolás
@@ -56,7 +56,7 @@ ratifikálásig `Proposed`. Az implementáció állapotát a horgony-bejegyzés
## Decision
> **An ADR records reasoning, not the state. What is true now is in the docs.**
> **An ADR records reasoning, not the state, and binds nothing — `BASE:ARTIFACT_ROLES`.**
**A nyereség-kimutatás alapegysége a `ShippingDocument`, a költség-réteg kulcsa a `ShippingItem`; a rendelés-tételek
FIFO-val fogyasztják a rétegeket; a fogyasztott mennyiségen kívül semmilyen pénzügyi érték nem tárolódik —
+1 -1
View File
@@ -6,7 +6,7 @@ This file is injected by the host. **Before answering anything, read `.github/LL
Every response begins with the `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files (+K this turn: <short names>)]` prefix — `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files, no new loads]` when nothing new. The timestamp is the present time, obtained in this turn; `no clock` only where the clock cannot be read. Semantics in `BASE:CONTEXT_LEDGER`.
After any context compaction or session summary, `[LOADED_DOCS]` is `NONE` — the count survives compaction, the file contents do not. Run Session Setup again before answering: a continuation instruction resumes the task, never the context.
`[LOADED_DOCS]` counts only what a tool call in the visible conversation loaded. A summary or a compaction is not a tool call: after one the ledger is `NONE`, whatever the previous prefix claimed. The first response of a context runs Session Setup and names the loaded files in `+K`; a context's first prefix never says `no new loads`. A continuation instruction resumes the task, never the context.
## Sequential execution
+1 -1
View File
@@ -6,7 +6,7 @@ CRITICAL: Your FIRST action in every session is the bootstrap chain below. Compl
Every response begins with the `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files (+K this turn: <short names>)]` prefix — `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files, no new loads]` when nothing new. The timestamp is the present time, obtained in this turn; `no clock` only where the clock cannot be read. Semantics in `BASE:CONTEXT_LEDGER`.
After any context compaction or session summary, `[LOADED_DOCS]` is `NONE` — the count survives compaction, the file contents do not. Run Session Setup again before answering: a continuation instruction resumes the task, never the context.
`[LOADED_DOCS]` counts only what a tool call in the visible conversation loaded. A summary or a compaction is not a tool call: after one the ledger is `NONE`, whatever the previous prefix claimed. The first response of a context runs Session Setup and names the loaded files in `+K`; a context's first prefix never says `no new loads`. A continuation instruction resumes the task, never the context.
## Sequential execution
+1 -1
View File
@@ -6,7 +6,7 @@ CRITICAL: Your FIRST action in every session is the bootstrap chain below. Compl
Every response begins with the `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files (+K this turn: <short names>)]` prefix — `[LOADED_DOCS: YYYY-MM-DD HH:mm · N files, no new loads]` when nothing new. The timestamp is the present time, obtained in this turn; `no clock` only where the clock cannot be read. Semantics in `BASE:CONTEXT_LEDGER`.
After any context compaction or session summary, `[LOADED_DOCS]` is `NONE` — the count survives compaction, the file contents do not. Run Session Setup again before answering: a continuation instruction resumes the task, never the context.
`[LOADED_DOCS]` counts only what a tool call in the visible conversation loaded. A summary or a compaction is not a tool call: after one the ledger is `NONE`, whatever the previous prefix claimed. The first response of a context runs Session Setup and names the loaded files in `+K`; a context's first prefix never says `no new loads`. A continuation instruction resumes the task, never the context.
## Sequential execution