Az árrés-topik doksijainak átvezetése a landolt állapotra

- MARGIN/README.md: jelen-állapot — az elszámolási egység, a FIFO-hozzárendelés, a számítási képletek, a
  beszerzési érték útja, a költségek és a riport
- MARGIN_TODO: a megépült lépések kipipálva, a máshogy megoldott döntések indoklással; új nyitott pontok
  (leltári leírás mint fogyasztó, előrendelés pontos forrása, SCHEMA.md újragenerálás)
- MARGIN_ISSUES (új): MGFBANKPLUG-MARGIN-B-T4M6 — a fbShippingDocument.FxRate DEFAULT (1) miatt a régi
  devizás beszerzés forintként számol; éles méréssel, az enyhítéssel és a megoldás irányával
- STOCK/README: a rendelés-lezárás készlet-mozgása költség-réteget is fogyaszt
- DOMAIN_MODEL: a két új tábla az entitás-hierarchiában
This commit is contained in:
2026-08-14 16:15:07 +02:00
parent afb6b61b6a
commit 6f23c61830
6 changed files with 161 additions and 35 deletions
@@ -103,6 +103,20 @@ StockTaking (fbStockTaking) ← inventory session
└─ ProductDto (Product) [N:1]
```
### Beszerzési önköltség és árrés
```
ShippingItem (fbShippingItem) ← a költség-réteg: egy szállítólevél-tétel
└─ CostConsumption (fbCostConsumption) ← mennyi fogyott belőle, és mi fogyasztotta [1:N]
Shipping / ShippingDocument
└─ ShippingCost (fbShippingCost) ← fuvardíj / vám / rakodás / egyéb [1:N]
```
A `CostConsumption` fogyasztója polimorf (`ConsumerTypeId` + `ConsumerId`): rendelés-tétel vagy leltár-tétel.
A `ShippingCost` tulajdonosa pontosan egy a kamion és a szállítólevél közül. Mindkét tábla **plugin-tulajdonú**,
a klienssel nem megosztott. Részletek: [`MARGIN/README.md`](MARGIN/README.md).
## FullProcessModel
Container for bulk data sync between server and FruitBankHybridApp via SignalR:
@@ -0,0 +1,42 @@
# ÁRRÉS — ISSUES
> Companion to [`README.md`](README.md). Topic `MARGIN`, prefix `MGFBANKPLUG` → entry IDs `MGFBANKPLUG-MARGIN-<I|B>-<RAND>`.
> ID format, Status vocabulary, type codes and archival are **not restated here** — see [`TOPIC_CODES.md`](../../.github/TOPIC_CODES.md) (→ framework registry).
## Active entries
## MGFBANKPLUG-MARGIN-B-T4M6: A `fbShippingDocument.FxRate` alapértéke 1, ezért a régi devizás beszerzés forintként számol
**Status:** Open · **Priority:** P2 · **Type:** B (séma-defekt, éles adaton mérve) · **2026-08-14**
A séma-bevezetéskor (`MARGIN_schema.sql`) az `FxRate` oszlop `NOT NULL CONSTRAINT ... DEFAULT (1)` értéket kapott,
a `CurrencyCode` pedig `NULL` maradt. Így **minden, a funkció előtt keletkezett szállítólevél 1-es árfolyammal
szerepel**, függetlenül attól, milyen devizában érkezett.
**Éles mérés (2026-08-14):** az `A6513` dokumentum (2026-06-13, EUR-s beszállító) 1250 EUR tétel-értéke
1120 rekeszre osztva 19 rekesz eladásánál **21 Ft** önköltséget adott — a helyes érték a 356-os árfolyammal
ennek 356-szorosa. Az árrés így 98,69% volt a valós helyett.
**A hiba iránya mindig ugyanaz:** az önköltség a valós töredéke, tehát a nyereség irreálisan magasnak látszik —
ez a rosszabb irány, mert nem tűnik hibának.
**Ellentmondás a kódon belül:** a `ShippingCostService.ResolveFxRate` szándékosan **0**-t ad vissza hiányzó
árfolyamnál, épp azért, hogy ne vegyünk forintnak egy eurót. Az oszlop alapértéke ezzel szembement.
### Enyhítés (kész, 2026-08-14)
A riport jelzi az érintett sorokat: `MarginRow.HasUntrustworthyFxRate` — nincs pénznem, vagy a pénznem nem HUF
és az árfolyam ≤ 1. Sor szinten euró-ikon a tényleges értékekkel, felül összesített figyelmeztetés. A hibás szám
így nem tűnik el, de **látszik**, hogy nem megbízható.
### Megoldás iránya
1. A meglévő sorok visszatöltése a partner devizájából és a config árfolyamából. ⚠️ A config a **mai** kulcsot
tartja, tehát a régi hónapokra ez közelítés — a pontos érték csak akkor lenne meg, ha akkor rögzítettük volna.
2. Az alapérték felülvizsgálata: a `DEFAULT (1)` csak akkor helyes, ha a pénznem is ismerten HUF. Egy `NULL`
alapértelmezés (vagy 0) a hiányt hiányként ábrázolná, összhangban a `ResolveFxRate` viselkedésével.
**Affected:**
- `docs/MARGIN/MARGIN_schema.sql` — az oszlop alapértéke
- `Services/MarginCalculationService.cs``HasUntrustworthyFxRate` (enyhítés)
- `Areas/Admin/Views/MarginReport/Index.cshtml` — a jelzés
@@ -9,7 +9,11 @@ Scope: az önköltség- és árrés-számítás megépítése.
## MGFBANKPLUG-MARGIN-T-K5V9: Dokumentum-szintű nyereség-kimutatás — költség-rétegek, FIFO-fogyasztás, számított árrés
**Status:** Open · **Priority:** P2 · **Type:** T (adatmodell + szolgáltatás + admin riport) · **2026-08-13**
**Status:** InProgress · **Priority:** P2 · **Type:** T (adatmodell + szolgáltatás + admin riport) · **2026-08-13**
> **2026-08-14:** a lánc végigépült és éles adaton futott — séma, entitások, FIFO-hozzárendelés, költség-bevitel,
> számítás és riport. Ami hátravan, az alább pipa nélkül áll. A menet közben talált séma-defekt külön bejegyzés:
> [`MGFBANKPLUG-MARGIN-B-T4M6`](MARGIN_ISSUES.md).
Az [ADR 0002-R7Q2](../adr/0002-R7Q2-document-cost-layers-and-margin.md) **horgony-bejegyzése** — ez az entry viszi
a döntés implementációs állapotát. A döntés indoklása, alternatívái és következményei az ADR-ben; itt csak a
@@ -17,34 +21,53 @@ lépések és az állapotuk.
### Lépések
- [ ] **Séma — beszerzési oldal.** `ShippingItem.NetTotalOnDocument` (nettó tétel-végösszeg a dokumentum
- [x] **Séma — beszerzési oldal.** `ShippingItem.NetTotalOnDocument` (nettó tétel-végösszeg a dokumentum
devizájában) és `ShippingItem.VatRate`; `ShippingDocument.CurrencyCode` + `FxRate`. Az entitások a
`FruitBank.Common/Entities/` alatt vannak → **plugin + Hybrid együtt-deploy**.
- [ ] **Séma — költségek.** `fbShippingCost`: tulajdonos `ShippingId` **vagy** `ShippingDocumentId` (pontosan az
*A négy mező a `FruitMasterErp` keret-rétegébe került (`FmShippingItemBase` / `FmShippingDocumentBase`),
mert a párjuk (`UnitPriceOnDocument`) is ott van, és a fogalom nem FruitBank-specifikus.*
- [x] **Séma — költségek.** `fbShippingCost`: tulajdonos `ShippingId` **vagy** `ShippingDocumentId` (pontosan az
egyik), `CostTypeId` (fuvar / vám / rakodás / egyéb), `NetAmount`, `Currency`, `FxRate`, megnevezés.
- [ ] **Séma — fogyasztás.** `fbCostConsumption` (plugin-tulajdonú, a Common-ba NEM kerül): `ShippingItemId`,
- [x] **Séma — fogyasztás.** `fbCostConsumption` (plugin-tulajdonú, a Common-ba NEM kerül): `ShippingItemId`,
fogyasztó típusa + azonosítója, `Quantity`, `NetWeight`, `Created`, `IsManual`.
- [ ] **A `VatRate` alapértelmezésének forrása** tisztázandó: a `ProductDto` ma nem hordozza a nop adókategóriát,
- [x] **A `VatRate` alapértelmezésének forrása** tisztázandó: a `ProductDto` ma nem hordozza a nop adókategóriát,
tehát vagy a nop `Product` entitásból olvassuk, vagy a DTO bővül.
- [ ] **Az árfolyam forrása** tisztázandó (kézi bevitel vagy külső lekérdezés). Ellenőrizendő, hogy az
*Megoldva másképp: a kulcs a beszállító országkódjából jön (nem-HU → 0), belföldinél az admin adja meg.
A termék adókategóriája mint forrás az áfa-analitika feature kérdése marad — az árrés végig nettóval számol.*
- [x] **Az árfolyam forrása** tisztázandó (kézi bevitel vagy külső lekérdezés). Ellenőrizendő, hogy az
`EkaerHistory.ConversionRate` körül van-e már működő megoldás.
- [ ] **Fogyasztás-képzés** a `SetOrderStatusToCompleteAsync` tranzakciójában, a készlet-levonással egy eseményből;
*A meglévő EKÁER-forrás lett (`Ekaer:ExchangeRate:EurHuf`), a `ShippingCostService.ResolveFxRate`-en át;
az EKÁER-től eltérően az érték a dokumentumon TÁROLÓDIK, a bevételezéskor befagyasztva.*
- [x] **Fogyasztás-képzés** a `SetOrderStatusToCompleteAsync` tranzakciójában, a készlet-levonással egy eseményből;
FIFO a beérkezés dátuma szerint, előrendelésnél a konverziót kiváltó szállítólevélből, réteg híján
„ismeretlen forrású" jelöléssel. A leltár-zárás ugyanez, más fogyasztó-típussal.
- [ ] **Számító szolgáltatás:** fajlagos önköltség, fuvar-hányad (raklap-arányos, az adott kamionon **elfoglalt**
*⚠️ Két része NEM készült el: az előrendelés pontos forrása (lásd lent) és a leltár-zárás (lásd lent).*
- [x] **Számító szolgáltatás:** fajlagos önköltség, fuvar-hányad (raklap-arányos, az adott kamionon **elfoglalt**
raklapokra osztva), extra-hányad, árrés — mind lekérdezésből, tárolt pénzügyi érték nélkül.
- [ ] **Admin riport** dokumentum- és kamion-bontásban, valamint havonta (a fogyasztási sor `Created` dátuma
- [x] **Admin riport** dokumentum- és kamion-bontásban, valamint havonta (a fogyasztási sor `Created` dátuma
szerint), külön soron az „ismeretlen forrású" és a még készleten lévő rész.
- [ ] **Leltári leírás mint fogyasztó** — a `StockTakingDbContext.CloseStockTaking` még nem hív a
`CostAttributionService`-be, ezért a leírt áru bent marad a rétegben, és a riport `Maradt` oszlopa
készletnek mutatja. A `CostConsumerType.StockTakingItem` már létezik.
- [ ] **Előrendelés pontos forrása** — a `fbPreOrderItem` nem tárolja, melyik szállítólevél elégítette ki, ezért
ott is FIFO dönt. Egy oszlop + egy értékadás a `PreOrderConversionService`-ben.
- [ ] **Futásidejű ellenőrzés a devizás javítás után** — a régi dokumentumok árfolyamának visszatöltése után
igazolni, hogy az önköltség a várt nagyságrendbe kerül (`MGFBANKPLUG-MARGIN-B-T4M6`).
- [ ] **Az allocation-backfill állapotának ellenőrzése** — a fuvar-szétosztás az allocationökre épül; ha egy régi
szállítmánynál nincs allocation, a fuvarköltség némán nulla. A
[`SHPLAN/README.md`](../../../../../../FruitBankHybridApp/docs/SHPLAN/README.md) P0/P1 sorai a backfillt még
nyitottként jelölik, a P3P7 viszont kész — a jelzés állapota ellenőrizetlen.
- [ ] **A `FruitBankDataController.cs:1360` néma no-op sorsa** (beállítja a `ProductCost`-ot, de nem menti):
törlés vagy javítás, a nop saját funkcióinak igénye szerint.
- [ ] **Docs, ahogy a részek landolnak:** [`README.md`](README.md) átírása jelen-állapotra, a
- [x] **Docs, ahogy a részek landolnak:** [`README.md`](README.md) átírása jelen-állapotra, a
[`SCHEMA.md`](../SCHEMA.md) újragenerálása, a [`STOCK/README.md`](../STOCK/README.md) kiegészítése azzal,
hogy a készlet-mozgás költség-réteget is fogyaszt, és a [`DOMAIN_MODEL.md`](../DOMAIN_MODEL.md)
entitás-hierarchiája az új táblákkal.
*A `README.md`, a `STOCK/README.md` és a `DOMAIN_MODEL.md` átvezetve (2026-08-14). A `SCHEMA.md` NEM —
lásd a következő pontot.*
- [ ] **`SCHEMA.md` újragenerálása.** A fájl generált (kézzel nem szerkeszthető), és a generátor a
`FullProcessModel` gyökereit járja be — a két új, plugin-tulajdonú entitás nincs benne. Előbb a seedbe
kell felvenni őket, utána futtatható a generálás.
### Affected
+66 -24
View File
@@ -1,36 +1,78 @@
# ÁRRÉS — beszerzési önköltség és nyereség-kimutatás
> Topic `MARGIN` (prefix `MGFBANKPLUG`) → entry ID-k `MGFBANKPLUG-MARGIN-<I|T|B>-<RAND>`. ID-formátum/Status-szótár → [`TOPIC_CODES.md`](../../.github/TOPIC_CODES.md) (→ keret-registry).
> Companion: [`MARGIN_TODO.md`](MARGIN_TODO.md) — a nyitott munka.
> Companions: [`MARGIN_TODO.md`](MARGIN_TODO.md) — nyitott munka · [`MARGIN_ISSUES.md`](MARGIN_ISSUES.md) — ismert defektek.
**A pluginban ma nincs árrés- vagy önköltség-számítás.** Ez a doksi azt írja le, milyen adat áll rendelkezésre
hozzá, és mi hiányzik belőle.
A kiadott áru hozzá van rendelve ahhoz a beszerzéshez, amiből származik, és ebből áll elő az árrés
szállítólevél, kamion és hónap bontásban. Séma: [`MARGIN_schema.sql`](MARGIN_schema.sql).
## Ami ma megvan
## Az elszámolás egysége
| Adat | Hol | Mértékegység |
|---|---|---|
| Beszerzési egységár | `ShippingItem.UnitPriceOnDocument` (a szállítólevélről) | **rekesz** — a sorérték `UnitPriceOnDocument × QuantityOnDocument` (`FmEkaerValueCalculator.ItemLineValue`) |
| Eladási egységár | `OrderItemDto.UnitPriceExclTax` / `UnitPriceInclTax` | mérendő terméknél **kg**, nem mérendőnél **rekesz** — az árazási ágat a `CustomPriceCalculationService` választja (lásd [`MEASUREMENT.md`](../MEASUREMENT.md)) |
| Beszállító pénzneme | `Partner.Currency` | ISO 4217; EUR-s beszállító is van |
| Fuvarozó pénzneme | `CargoPartner.Currency` | ugyanaz |
| Tétel↔kamion megoszlás | `ShippingItemToShipping` (`Quantity`, `Pallets`) | egy szállítólevél-tétel több kamionra bomolhat |
A **szállítólevél-tétel** (`ShippingItem`) egy költség-réteg. A kamion nem tárolt kapcsolat: a tétel árrése a
`ShippingItemToShipping` allocationökön át, raklap-arányosan oszlik a kamionjai között.
## Ami ma hiányzik
## A hozzárendelés
- **Nincs önköltség-előzmény.** A `Product.ProductCost` egyetlen írási kísérlete
([`FruitBankDataController.cs:1360`](../../Controllers/FruitBankDataController.cs)) beállítja a lekért entitáson
az értéket, de nem menti — az írás elvész. A nopCommerce admin `OriginalProductCost`-ja élő lekérdezés az
aktuális `Product.ProductCost`-ra, nem pillanatkép; visszamenőleges önköltség tehát egyik forrásból sem áll elő.
- **Nincs adat arról, hogy egy rendelés-tétel melyik beszerzésből származik.** A készlet-levonás
(`FruitBankDbContext.UpdateStockQuantityAndWeightAsync`) mennyiséget és súlyt mozgat, forrást nem jelöl.
- **Nincs hely a fuvardíjnak és az egyéb szállítmány-költségeknek.** Sem a `Shipping`, sem a `ShippingDocument`
nem hordoz költség-mezőt.
- **Nincs a beszerzési oldalon áfakulcs**, tehát a dokumentum-érték nettósítása adatból nem levezethető.
A rendelés lezárásakor (`FruitBankDbContext.SetOrderStatusToCompleteAsync`) a `CostAttributionService`
**FIFO** szerint osztja szét a kiadott árut a termék rétegei között, a szállítólevél dátuma szerint a
legrégebbitől. Ugyanabban a tranzakcióban fut, mint a készlet-levonás.
- A fogyasztás **vezérlő** mértékegysége terméktípus-függő, és követi az árazást: mérendőnél **kg**,
nem mérendőnél **rekesz**. A sor a másik dimenziót is kitölti a tétel arányában.
- Réteg híján a sor **ismeretlen forrású** (`ShippingItemId = null`), és naplóba is kerül. Nulla önköltséggel
beolvasztani tilos — a riport külön során jelenik meg.
- Idempotens: ha egy rendelés-tételhez már van fogyasztás, nem keletkezik újabb.
Tárolt adat egyedül az elfogyasztott mennyiség (`fbCostConsumption`). Minden pénzügyi érték lekérdezés, ezért
egy később érkező helyesbített beszállítói vagy fuvarszámla **visszamenőleg átüt** a riporton.
## A számítás
| Elem | Képlet |
|---|---|
| fajlagos önköltség | `ShippingItem.NetTotalOnDocument × ShippingDocument.FxRate / MÉRT mennyiség` |
| áru-önköltség | fajlagos × eladott |
| fuvar-hányad | `Σ_kamion (kamion költségei × allocation.Pallets / kamion összes raklapja)`, az eladott arányra szűkítve |
| extra-hányad | `dokumentum költségei × tétel raklapja / dokumentum összes raklapja`, az eladott arányra szűkítve |
| árbevétel | `Σ eladott mennyiség × OrderItemDto.UnitPriceExclTax` |
| árrés | árbevétel áru-önköltség fuvar extra |
Minden érték **nettó** és **HUF**. Az osztó a kamionon ténylegesen elfoglalt raklapok száma, nem a kapacitás:
egy félig pakolt kamion drágábbá teszi az árut. A gyűjtőjárat (`Shipping.IsPartial`) nem kap külön ágat — ott a
díj eleve csak a saját részünkre szól.
A fuvar és az extra a **teljes tételre** szól, de csak az eladott hányada költség; a maradék a készlet értékében
ül. A `MarginRow.Remaining` mutatja, mennyi van még a rétegben.
## A beszerzési érték útja
| Lépés | Hol |
|---|---|
| Az AI kiolvassa a nettó tétel-végösszeget (`netTotal`) | `OpenAIApiService` prompt → `FileManagerController` |
| Hiányzó sor-végösszegnél tartalék: `egységár × mennyiség` | `FileManagerController.ResolveNetTotalOnDocument` |
| Az admin ellenőrzi/javítja | `Areas/Admin/Views/Extras/ImageTextExtraction.cshtml` |
| Áfakulcs a beszállító országkódjából (nem-HU → 0) | `FileManagerController.IsDomesticPartner` |
| Deviza a partnerről, árfolyam a bevételezéskor befagyasztva | `ShippingCostService.ResolveFxRate` |
Az áfakulcsot **szándékosan nem az AI adja**: levezethető, és két egymásnak ellentmondó forrás rosszabb, mint egy.
Az árfolyam forrása a meglévő EKÁER-mechanizmus (appsettings `Ekaer:ExchangeRate:EurHuf`) — nincs második
árfolyam-forrás a rendszerben.
## Költségek
`fbShippingCost`: a tulajdonos **vagy** a kamion (fuvardíj), **vagy** a szállítólevél (vám, rakodás) — pontosan az
egyik, DB-oldali CHECK őrzi. Az összeg mindig nettó, a saját devizájában, rögzítéskor befagyasztott árfolyammal.
Felület: admin → **Szállítmány-költségek**.
## Riport
Admin → **Árrés-kimutatás**. Az időszak az **eladás** dátumára szűr (a rendelés lezárása), nem a beérkezésére.
Bontás szállítólevél, kamion vagy hónap szerint. Külön jelöli az ismeretlen forrású sorokat, a beszerzési érték
nélküli tételeket, a kamionhoz nem rendelt tételeket és a nem megbízható árfolyamot.
## Kapcsolódó
- A tervezett modell döntése és alternatívái: [ADR 0002-R7Q2](../adr/0002-R7Q2-document-cost-layers-and-margin.md).
- A tétel↔kamion allocation: [FBANKAPP ADR 0003](../../../../../../FruitBankHybridApp/docs/adr/0003-shipping-item-to-shipping-mapping.md) ·
a raklapszám mint tört érték: [FBANKAPP ADR 0004-M9F3](../../../../../../FruitBankHybridApp/docs/adr/0004-M9F3-fractional-pallets-and-remainder-grouping.md).
- A döntés és alternatívái: [ADR 0002-R7Q2](../adr/0002-R7Q2-document-cost-layers-and-margin.md).
- Tétel↔kamion allocation: [FBANKAPP ADR 0003](../../../../../../FruitBankHybridApp/docs/adr/0003-shipping-item-to-shipping-mapping.md) ·
tört raklapszám: [FBANKAPP ADR 0004-M9F3](../../../../../../FruitBankHybridApp/docs/adr/0004-M9F3-fractional-pallets-and-remainder-grouping.md).
- Készlet-mozgatás: [`STOCK/README.md`](../STOCK/README.md) · mérési folyamatok: [`MEASUREMENT.md`](../MEASUREMENT.md).
+1 -1
View File
@@ -22,7 +22,7 @@ Topic documentation for the FruitBank-specific NopCommerce plugin (Layer 2 — c
- [`ORDERDRAFT/`](ORDERDRAFT/README.md) — AI-assisted intake: free-text messages parsed into order drafts, admin-approved into pre-orders (+ `ORDERDRAFT_ISSUES.md`)
- [`EKAER/`](EKAER/README.md) — NAV EKÁER reporting, server side: obligation gate, `Shipping`/`Order``tradeCard` mapping, submission (+ `EKAER_ISSUES.md` / `EKAER_TODO.md`)
- [`STOCK/`](STOCK/README.md) — stock movement: quantity + weight running balance, history and its consistency check (+ `STOCK_ISSUES.md`)
- [`MARGIN/`](MARGIN/README.md) — purchase cost and margin: what data exists today for a profit report, and what is missing (+ `MARGIN_TODO.md`)
- [`MARGIN/`](MARGIN/README.md) — purchase cost and margin: cost layers, FIFO attribution at order completion, freight/extra distribution, and the report (+ `MARGIN_TODO.md` / `MARGIN_ISSUES.md`, and `MARGIN_schema.sql`)
- [`PMDATA/`](PMDATA/README.md) — product measurement/logistics master data (`IsMeasurable`, `Tare`, `AverageWeight`, `AverageWeightTreshold`, `CratesPerPallet`): storage, write paths, validation (+ `PMDATA_TODO.md`; the SQL runbook is the flat [`PRODUCT_WEIGHT_DATA_DB_VALIDATION.md`](PRODUCT_WEIGHT_DATA_DB_VALIDATION.md))
## Navigation
@@ -19,6 +19,11 @@ Ez tudatos, jelenleg nyitott üzleti döntés — lásd
A mechanika (metódusok, hívási sorrend) a [`DATA_LAYER.md`](../DATA_LAYER.md)-ben; a mérési oldal a
[`MEASUREMENT.md`](../MEASUREMENT.md)-ben.
**A rendelés-lezárás készlet-mozgása költség-réteget is fogyaszt:** ugyanabban a tranzakcióban a
`CostAttributionService` FIFO szerint hozzárendeli a kiadott árut ahhoz a beszerzéshez, amiből származik
(`fbCostConsumption`) — ebből áll elő az árrés. Lásd [`MARGIN/README.md`](../MARGIN/README.md).
A **leltár-zárás** ezt még nem teszi meg (`MGFBANKPLUG-MARGIN-T-K5V9`).
## Nyitott munka
- [`STOCK_ISSUES.md`](STOCK_ISSUES.md)