# 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 : 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 0–9999, VeggieVoyage 10000–19999, 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`) — 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** (`/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).