Update GrossWeight logic, add wholesale framework plan

Changed GrossWeight in OrderItemDto to sum GrossWeight instead of NetWeight and updated its ToonDescription. Added WHOLESALE_FRAMEWORK_PLAN.md as a draft design summary for a future shared wholesale framework, outlining architecture, layering, and migration strategy.
This commit is contained in:
Loretta 2026-07-14 18:25:51 +02:00
parent 451e029869
commit 5c20b3a04c
2 changed files with 326 additions and 2 deletions

View File

@ -79,12 +79,12 @@ public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
}
[NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
[ToonDescription(BusinessRule = "get => double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1)")]
[ToonDescription(BusinessRule = "get => double.Round(OrderItemPallets.Sum(x => x.GrossWeight), 1)")]
public double GrossWeight
{
get
{
return double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1);
return double.Round(OrderItemPallets.Sum(x => x.GrossWeight), 1);
//CommonHelper.To<double>(GenericAttributes.SingleOrDefault(x => x.Key == nameof(IMeasuringGrossWeight.GrossWeight))?.Value ?? "0");
}
set

View File

@ -0,0 +1,324 @@
# Wholesale Framework — tervezési összefoglaló (handoff-mag)
> **Status:** Proposed (Draft) — 2026-07-14
> **Jelleg:** Ez NEM ADR és NEM végleges doksi. Provizórikus gondolkodási összefoglaló, ideiglenesen a
> FruitBankHybridApp `docs/` mappájában. A tárgyalt Wholesale framework-repo **még nem létezik**, a neve
> **változhat**, és **semmilyen döntés nem végleges**. Amint a repo létrejön, ez a fájl a forrása a rendes
> struktúrának (ADR-ek + repo-doksik) — lásd a záró "Mi lesz ebből" szakaszt.
> **Előzmény:** két korábbi session terv-összegzése (app-oldal + nop-plugin-oldal) + ez a session konszolidációja.
---
## 1. Cél és kiváltó ok
A FruitBank (zöldség-gyümölcs nagyker, nopCommerce-plugin + Blazor Hybrid app) mellé indul a **VeggieVoyage**
(veggievoyage.hu), egy közel azonos nagyker-rendszer apró eltérésekkel. Emellett **több további zöldséges nagyker
érdeklődő** is van a rendszerre. Cél: egy **közös nagyker-framework réteg**, amelyből minden nagyker-termék
származik, hogy egy új terméknél a lehető legkevesebbet kelljen újraírni.
Jelenleg (2026-07-14) csak a FruitBank-oldal létezik; sem a Wholesale-repo/könyvtár, sem a VeggieVoyage-projekt
nincs még létrehozva.
---
## 2. Architektúra-döntés: base-class minta (maximalista pull-up)
**Öröklés-alapú, NEM típus-átköltöztetés.** A közös rétegbe **szinte minden osztály felkerül** — abstract vagy
virtual formában (base class-ok, virtual method-ok/property-k). A konkrét (gyakran szinte üres) záró osztályok a
termék-repóban élnek és származnak a base-ből.
- **Amit lehet, a közös rétegben implementálunk** (virtual default üzleti logika); ami termék-specifikus, az a
származtatottban override-ol.
- **Az eltérés a fejlesztés közben derül ki, nem előre.** Default: minden felmegy közös virtual defaultként. Amikor
a VeggieVoyage (vagy egy későbbi termék) fejlesztése **bizonyítja**, hogy valami mégsem közös, akkor az a member
lemegy ("demote") a záró rétegbe. Amíg csak egy termék használ egy membert, a lehúzása senkit nem tör.
- Ez a **meglévő házi minta** kiterjesztése: `MgStockTakingItem<TStockTaking, TProduct> : MgEntityBase`,
`PartnerBase → CargoPartner`, `MeasuringItemPalletBase → 3 pallet`, `AcConst` abstract-constants minta.
### Miért ez nyert (a mozgatásos alternatívával szemben)
| Aggály | Feloldás |
|---|---|
| Tábla-nevek (`fbShipping`…) | `[Table(Name=…)]` a **származtatott** osztályon, termék-konstansból — nincs migráció, nincs runtime-konfig |
| AcBinary wire-compat (`SignalDataType` = AssemblyQualifiedName) | Konkrét típusok **helyben maradnak** a termék-assembly-ben → AQN változatlan, nincs lockstep-kényszer |
| `AcBinaryForcedInit` (AssemblyLoadContext-quirk) | Generált szerializálók a konkrét típusokra → meglévő ForceRegister érintetlen |
| LinqToDB `NameCompatibility` | Termék-pluginban marad, konkrét típusokra |
**Következmény:** a FruitBank-oldal minden pull-up lépésnél **fordítás-azonos** marad; a VeggieVoyage viszont
"majdnem készen" indul, mert a scaffold záró osztályai szinte üresek.
### Új DB + új plugin a VeggieVoyage-nek
- A VeggieVoyage **teljesen új adatbázist** kap → a `[Table(Name=…)]` (identitás) így is a záró rétegen marad; a
base hordozza az összes `[Column]`-t, a tábla-NÉV termék-konstans. A `vv`/`fb` prefix szabad termék-döntés, nem
framework-ügy.
- A VeggieVoyage **új nopCommerce-plugin** is → nem csak az üzleti logikát, hanem a **plugin-infrastruktúrát** is
base-formában ki kell szervezni: install/uninstall, locale-resource telepítés, admin-menü regisztráció,
settings-defaultok, schedule task-ok, `MgwPluginStartupBase` virtual DI-regisztrációkkal. A Veggie-pluginban
ideálisan csak `plugin.json` (SystemName), a záró osztályok és a konstansok maradnak.
- **Zöldmezős schema-generálás:** a friss install-nak a teljes sémát egy menetben kell felhúznia üres DB-n (a
FruitBank táblái évek alatt, lépésenként jöttek létre). Ellenőrizendő, hogy a meglévő install/tábla-létrehozó út
(MgDbContext-vonal) teljes és determinisztikus-e nulláról — GenericAttribute-kulcsok és seed-adatok is.
---
## 3. Rétegzés / célstruktúra
Rétegtérkép a legáltalánosabbtól (legmélyebb) a legspecifikusabbig:
```
L0 AyCode.Core (Ac*) + AyCode.Blazor — tech-framework, domain-mentes, bármely projekt
SignalR, AcBinary + SourceGenerator, logolás, NAV/EKÁER-protokoll, MgGrid/komponens-base-ek
L2 Mango.Nop Libraries (Mg*) — nop-generikus, nop-VERZIÓFÜGGŐ (4.70 mappa alatt)
L2.5 Mango.Nop.Wholesale.Core / .Services — nagyker-domain, nop-VERZIÓFÜGGŐ, prefix: Mgw
(Libraries alatt, 4.70) entitás-bázisok, DbTable/DbContext-bázisok, endpoint-bázisok, EKÁER-riport
L2.5 Wholesale.Common / .Common.Server — nagyker-domain, nop-verzió-FÜGGETLEN (ÚJ repo)
domain-modell, interfészek, enumok, DTO-bázisok, SignalR-tag core-tartomány, mérés-helperek,
service-bázisok (a Mango.Nop DLL-jei UTÁN build-el — pont mint ma a Common-ok)
L2.5 Wholesale.Shared (Blazor, ÚJ repo) — grid-/komponens-base-ek, SignalR-kliens base
L3 Termékek: FruitBank / VeggieVoyage / … — vékony záró osztályok + identitás + termék-delta
```
### Kétotthonos base-réteg — a besorolás kritériuma
A "hova kerüljön" kritérium **NEM az, hogy használ-e Mango.Nop-ot** (a Common-ok ma is jogosan használják),
hanem hogy **nop-verziófüggő-e**:
- **nop-verziófüggő** (Mg*/linq2db/nopCommerce-entitásokra épül, a nop belső API-ját tükrözi) → `Mango.Nop.Wholesale`
(Libraries, verziómappa alatt).
- **nop-verzió-független** nagyker-domain → `Wholesale.Common` / `.Common.Server` (új repo).
> Fontos: a mai `FruitBank.Common.Server` "nopCommerce integration"-t is tartalmaz. Pull-upnál a nop-ot érintő
> részek a `Mango.Nop.Wholesale.Services`-be mennek, nem a `Wholesale.Common.Server`-be. A kritérium szigorúan
> "nop-verziófüggő?", nem projektnév.
### Build-lánc (interleaved — ma is így épül, csak beékelődik két réteg)
```
AyCode → Mango.Nop Libraries → Mango.Nop.Wholesale.Core (entitás-bázisok)
→ Wholesale.Common / .Common.Server (domain- és service-bázisok, Mgw.Core-ra épülve)
→ Mango.Nop.Wholesale.Services (DbTable/DbContext/endpoint-bázisok, Wholesale.Common-ra épülve)
→ termékek (HybridApp + plugin)
```
Ezért a `Mango.Nop.Wholesale` **Core/Services szétvágása kötelező**, nem ízlés: a Core a Wholesale.Common ALATT,
a Services FELETTE — különben körkörös függőség.
### Per-app bin-profil
A referencia-minta már ma per-app build-profilos: az AyCode és Mango.Nop.Core DLL-jei
`bin\FruitBank\$(Configuration)\net9.0`-ból jönnek (HintPath). Lesz `bin\VeggieVoyage\…` is. TFM-ek: Common=net9.0
(WASM-kliens is fogyasztja, szerver-only függőség nélkül), Shared=net10.0. A `FruitBankHybrid.Shared.Common`
projekt ma üres váz — felhasználható.
---
## 4. Placement-kánon (a legfontosabb szabály)
**A kód a legmélyebb rétegbe kerül, amelynek a domain-hatóköre még jogosan magába foglalja a fogalmat.**
Két teszt együtt dönt:
1. **Kimenő-függőség teszt (szükséges, objektív):** egy típus akkor *mehet* mélyebbre, ha a függőségei engedik.
AyCode-ba csak az kerülhet, ami *nulla* nagyker/nop fogalomra hivatkozik (a `using`-ok és hivatkozott típusok
döntik el, nem érzés). Ez adja meg, milyen mélyre **mehetne** a kód.
2. **Hatókör-kohézió teszt (elégséges, logikai):** hova **való** a fogalom. Ez megállíthatja a kódot, mielőtt a
függőségei alapján túl mélyre süllyedne.
**Kanonikus példa — tálca/tare-aritmetika:** tiszta `decimal`-matematika, nulla domain-függőséggel *lefordulna*
AyCode.Core-ban. Mégis a **Wholesale**-be való, mert a fogalom hatóköre "nagyker-mérés". Core-ba nem azért nem
kerül, mert nem fordulna, hanem mert a Core hatóköre (*minden* projekt) tágabb, mint a fogalom valódi közönsége.
### Két hibamód
- **Túl mélyre** (nagyker-fogalom Core-ban): a univerzális réteg beszennyeződik, a nagyker-fejlesztő nem is ott
keresi. Ez a *csábító* hiba ("ez matek, mehet Core-ba"). **Fordul, csak koszol** → csendes hiba, ezt kell fogni.
- **Túl sekélyen** (a termékben): redundancia — a következő zöldséges projekt újraírja. Ezt a framework szünteti meg.
Iránytű: a redundancia-kerülés **lefelé** (általánosabb felé) tol; a hatókör-kohézió a **fék**. A hiba-irány
aszimmetrikus: túl mélyre tett domain-kód *nem fordul* (a rendszer önvédő); túl magasra tett specifikus kód
*fordul, csak koszol*. Ezért a default "a legmélyebb, ami fordul", és a **feljebb-tétel** az, ami indoklást igényel.
### Redundancia-ellensúly (hogy a DRY ne forduljon visszájára)
"Ne írd le kétszer ugyanazt a **fogalmat**" — NEM "vonj össze mindent, ami hasonlít". Ha két dolog csak *véletlenül*
hasonlít (nem ugyanaz a fogalom) és közös base-be húzod, hamis csatolást hozol létre — a legrosszabb fajta
redundancia-"javítás". A **rule-of-two/N** a védőkorlát: legalább két valódi fogyasztó bizonyítsa, hogy a közösség
valódi és nem esetleges.
### Identitás soha nem megy fel (checklist a záró réteghez)
- `[Table(Name=…)]` tábla-nevek (termék-konstansból)
- `[AcBinarySerializable]` konkrét típusok
- SignalRTag termék-tartománybeli értékei
- plugin SystemName, route-URL-ek, locale-fájlok, branding
- appsettings **értékek** (nem a séma)
### Hol él a kánon (doc-layering)
A placement-kánon kánonja a legmélyebb rétegben (AyCode.Core) él; minden alsóbb réteg copilot-instructions.md-je
erre **mutat** + a saját befogadási kritériumát mondja ki (AyCode = "domain-mentes"; Wholesale = "nagyker-domain,
nop-verzió-független"; Mango.Nop = "nop-verziófüggő"). A doksi vigyen 3-4 **levezetett** határeset-példát (a
tare-képlet a kanonikus) — az absztrakt szabály nem elég horgony, a *gondolkodásmódot* a példák tanítják.
---
## 5. Zöldség-kalibráció + jövőbeli továbbbontás (elhalasztott)
Az érdeklődők **egyelőre mind zöldséges nagykerek**, közel azonos igénnyel. Ezért a Wholesale-réteg **mai valódi
hatóköre "zöldséges nagyker"**, nem "általános nagyker" — a "zöldséges-specifikus vs. általános-nagyker" hasítás
**jövőbeli, szándékosan még meg nem tett rétegvágás**.
Következmények:
- **A zöldséges-specifikusság ma NEM tolja fel a kódot a termékbe.** Egy kimondottan zöldséges logika (érettség,
osztályozás, zöldség-mérés) jogosan a framework-rétegé — bármely másik zöldséges nagyker újra használná. Óvakodni
attól, hogy "ez túl zöldség-specifikus, hagyjuk a termékben" — ez **kiéheztetné a framework-öt**.
- **NE annotáld előre** a "zöldséges vs. általános" varratot — nincs második nem-zöldséges domain, ami bizonyítaná,
tehát bármilyen jelölés spekuláció (ugyanaz a korai-triázs-csapda). A jövőbeli *nem*-zöldséges nagyker fogja
felfedni a varratot, ugyanúgy, ahogy a Veggie-scaffold felfedi a termék-hardcode-ot.
- Ha egyszer jön nem-zöldséges nagyker: ugyanaz a mechanikus pull-up egy szinttel lejjebb (pl. `Trade`/`Distribution`
réteg), a Wholesale maga válik vékony leszármazottá. Mivel minden abstract/virtual, ez is fordítás-azonos lesz.
- A rétegnév lehet jövő-álló ("Wholesale", prefix `Mgw`), de a doksinak ki kell mondania a mai zöldség-kalibrációt,
hogy egy jövőbeli session ne nézze design-hibának és ne kezdjen kéretlenül általánosítani.
---
## 6. nopCommerce-verzió probléma + karantén-stratégia
**Kiváltó félelem:** a nopCommerce design sok hibája miatt gyakran nop-kódot kell másolni/átírni → nagyon nehéz a
FruitBank upgrade-je. A VeggieVoyage viszont már **új nop-verzióval** készülne. Kockázat: ha a catalog-táblák
változtak, az gond lehet.
**A rétegzés maga a válasz — csak explicit szabályként kell kimondani:**
1. **A verziófüggés karanténban van.** A `Mango.Nop Libraries` + `Mango.Nop.Wholesale` definíció szerint
nop-verziófüggő (ezért él verziómappa alatt). Új nop-verziónál **ez a két réteg forkol** (`4.90/Libraries`,
`4.90/…Wholesale`), és a per-app bin-profil ezt kiszolgálja: a FruitBank a 4.80-as fork ellen build-el, a
VeggieVoyage a 4.90-es ellen — **ugyanabból a Wholesale-forrásból**. Feltétel: a Mg*/Mgw típusok **publikus
felülete** forrás-kompatibilis maradjon a forkok között. A Mango.Nop réteg így **anti-korrupciós réteg**: a nop
churn a fork belsejében marad, a Wholesale nem látja.
2. **Másolt/átírt nop-kód** (a tényleges upgrade-fájdalom): jól megjelölt helyre (pl. `NopOverrides/` a verziófüggő
rétegben), mindegyik mellé kötelező jegyzet: *miért kellett, melyik nop-fájlból, mit kell újranézni port-nál*.
Így a 4.90-port checklist lesz, nem régészet.
3. **Catalog-tábla drift:** (a) minden catalog-hozzáférés a verziófüggő rétegben, a Wholesale csak interfészt/DTO-t
lát; (b) a **GenericAttribute-minta** (IsMeasurable, Tare, AverageWeight, IncomingQuantity, NetWeight mind GA-ban,
nem catalog-oszlopban) verzió-rezisztens — a GA a nop legstabilabb mechanizmusa. A kitettség arra szűkül, amit
tényleg a Product/Order/Customer **natív oszlopaiból** olvastok.
4. **A két nagy műveletet ne egyszerre.** Előbb pull-up a **mostani** nop-verzión (FruitBank fordítás-azonos, a
Veggie-scaffold ugyanazon a verzión bizonyítja a framework-öt), **utána** a verziófüggő réteg portja az új
verzióra. Egyszerre futtatva minden hiba kétesélyes lenne ("framework-bug vagy port-bug?").
5. **Reframe:** a VeggieVoyage **kifizeti a FruitBank upgrade-adósságát**. Ha a Veggie kikényszeríti a
Mango.Nop(+Wholesale) új-verziós portját, a FruitBank későbbi upgrade-je már csak "bin-profil átállítás a kész
forkra + termékrétegbeli maradék".
**Konkrét vizsgálat, amivel a catalog-félelem végessé tehető** (még nem futott le):
- (a) **Kitettség-térkép:** végigmenni a plugin + Libraries kódon, mely nop-entitásokat/service-eket/natív
oszlopokat használtok közvetlenül.
- (b) **Séma-diff** a cél nop-verzióval, a ténylegesen használt táblákra (a nop upgrade-migrációiból).
→ Cél nop-verzió megadása után elvégezhető.
---
## 7. Eltérés-taxonómia: settings vs. override (rule-of-N)
Több terméknél a "rule-of-two" **rule-of-N** lesz. Ökölszabály:
- **parametrikus eltérés → settings/config** (EkaerSettings-minta, DI mögé)
- **strukturális eltérés → virtual override**
- **identitás → abstract const a záró rétegen**
Ha ugyanazt a metódust minden termék máshogy override-olja, az annak a jele, hogy **paraméternek** kellett volna
lennie. Minél több megy settings-be, annál kevesebb kódot kell egy új terméknél megírni. Nem kell előre kitalált
settings-taxonómia — **demote-to-settings on evidence**, ugyanaz a workflow, mint a kódnál: minden fent marad közös
defaultként, és az első valódi termék-eltérésnél dől el, override vagy setting lesz.
**Demote-ablak szűkül N-nel:** amíg egy termék használ egy membert, a lehúzása fájdalommentes; a 2. fogyasztónál már
közös és fent marad. Ezért a Veggie-fázisban agresszíven demote-olni.
**Termék-scaffold = template.** A Veggie "új termék felhúzása" maga is termék: minden scaffold-lépést checklistként
dokumentálni a Wholesale-repo doksijába — a 3. terméknél már ez A termék-létrehozási folyamat. (Nem kell most
dotnet-template; a jól dokumentált Veggie-scaffold az első draft.)
**Framework-evolúció N fogyasztóval:** minden wholesale-változás N terméket érint. Per-app bin-profil jó izoláció
(mindenki a saját build-jét pinneli), de wire-formátumot (AcBinary) és base-séma `[Column]`-okat érintő változásnál
`CONSUMERS.md` szerinti végigfuttatás kell (P1).
**SignalRTag-tartomány kiosztás** (hogy két termék soha ne ütközzön közös infrastruktúrán): közös core-tartomány +
termék-tartományok, pl. FruitBank 09999, VeggieVoyage 1000019999, 3. termék 20000+…
---
## 8. Pilotban igazolandó mechanizmusok (a maximalista minta ezekre tesz fel mindent)
Az első pull-up ELŐTT explicit tesztelendő:
1. **`[Association]` keresztreferenciák öröklésnél** — interfész-kulcsú társítás
(`OtherKey = nameof(IShipping.Id)`) VAGY a navigációk a leszármazottban maradnak (base = skalárok + FK int-ek),
entitásonként döntve.
2. **AcBinary öröklött property-kkel** — a `MeasuringItemPalletBase` minta szerint; az első költözőnél explicit teszt.
3. **`[SignalR]` attribútum öröklött kontroller-metóduson** — az AyCode dispatcher végpont-felderítése lássa a
base-metódusokat.
4. **Razor `@typeparam` a DevExpress template-ekkel** — hol megy, hol marad vékony app-razor.
---
## 9. Migrációs fázisterv ("pull-up", lépésenként, FruitBank végig fordítás-azonos)
0. Skeleton repo + per-app bin-profil bekötés + mechanikus bázis-kiemelés (interfészek, enumok, konstans-bázisok) —
compile+wire-identical.
1. **Pilot:** a legkisebb/legtisztább gráf végigvitele (jelölt: Cargo — legkisebb; vagy EKÁER-lánc — legfrissebb).
Itt bizonyítjuk a 8. szakasz mechanizmusait.
2. Data-réteg bázisok (DbContext/DbTable).
3. Service-ek domain-enként (mérés → preorder → cargo → invoicing), FruitBank-osztályok vékony leszármazottá.
4. VeggieVoyage-plugin scaffold záró-osztályokkal — itt bukik ki a maradék hardcode.
5. Blazor Shared (grid-/komponens-base-ek).
6. Verziófüggő réteg portja az új nop-verzióra (a pull-up UTÁN, külön lépés).
---
## 10. Meta-protokoll teendők (amikor a repo létrejön)
- Új repo: `.github/copilot-instructions.md` + `@repo` blokk, **repo-prefix** (self-service a `REPO_PREFIXES.md`
szerint; korábbi javaslatok: nop-oldali libre `MGNOPWHOLE`, a nop-független Wholesale repóra pl. `MGWHOLE`
eldöntendő), layer-szám.
- **Class-prefix:** `Mgw` (Mango Wholesale) — collision-check lefutott, tiszta (a `Ws`, `Wh/Whs`, `Nk` elvetve).
A doksi tisztázza: a prefix jövő-álló, de a mai tartalom zöldség-kalibrált.
- **Doc-layering:** a defining layerrel a doksik is költöznek — a FruitBank-plugin `PREORDER/` doksija + a
`PREO`/`EKAER`/`ORDD` topic-kódok a wholesale-registry-be; a termék-pluginban pointer + delta marad.
- **`CONSUMERS.md`** a Wholesale-repóban (FruitBank, VeggieVoyage, …).
- **`LLMP-DEC` bejegyzés** az új repo létéről (workspace-szignifikáns).
- **ADR-ek** a végleges struktúrában (lásd lent).
---
## 11. Nyitott kérdések
- Wholesale-repo **neve** és pontos szeparációja (egy repo Common + Common.Server + Shared, vagy több?).
- **Cél nop-verzió** a VeggieVoyage-hez (ez indítja a 6/(b) séma-diffet).
- Repo-**prefix**(ek) véglegesítése.
- Generic-paraméter-robbanás mély gráfoknál (`MgwShippingDocumentBase<TShipping, TPartner, TShippingItem, >`) —
a házi minta elfogadta, de ez az ára; entitásonként figyelni.
- `Mango.Nop` vs `Wholesale` határ entitásonként: ami bármely nop-projektnek jó → Mango.Nop; ami nagyker-domain →
Wholesale.
---
## 12. Mi lesz ebből (amint van repo)
Ez a fájl a **forrás**; a rendes struktúrába szétszedve:
- **ADR-ek** (`<repo>/docs/adr/NNNN-*.md`, Nygard-stílus):
- `base-class-pull-up-vs-type-move` (2. szakasz)
- `wholesale-layer-split-and-nop-version-quarantine` (3. + 6. szakasz)
- `placement-canon` (4. szakasz) — vagy CONVENTIONS.md-be, a repo szokása szerint
- **Repo-doksik:** ARCHITECTURE (rétegtérkép + build-lánc), CONVENTIONS (placement-kánon + tare-példa +
zöldség-kalibráció), CONSUMERS.md, termék-scaffold checklist.
- **LLMP-DEC** sor: az új repo léte + a protokoll-relevanciák.
- A FruitBank-oldali doc-layering pointer-ek frissítése.
Ez a `WHOLESALE_FRAMEWORK_PLAN.md` a szétszedés után **archiválható vagy törölhető** (a tartalma a végleges
helyekre kerül).