Az ÁFA-kimutatás kimenő áfája a bruttóból számol
A kimenő áfa eddig a PriceInclTax - PriceExclTax különbségből állt elő, ez a kettő viszont a tételek 99,9%-án azonos: a nop adószolgáltatása ezekre a termékekre változatlanul adja vissza az árat. Augusztusra 162 072 Ft-ot mutatott 26,8 millió helyett, és az egyenleg előjelét is megfordította. - OutgoingVatRate const, egy helyen; a kimenő áfa a bruttóból, ezzel a kulccsal - a nézet kiírja a kulcsot, mert a számból nem derül ki - ADR 0004-K9T3: a kimenő oldal a lezárt rendelésekből, az átvétel hónapja szerint fog számolni (a mai forrás, a fogyasztás-sor, csak aug. 20-tól létezik, ezért a riport 12 napot mutatott 31 helyett) - horgony-bejegyzés: MGFBANKPLUG-INNV-T-R6D2 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,10 @@
|
||||
<div class="card-body py-2">
|
||||
<p class="mb-1 text-muted">
|
||||
<strong>Bejövő:</strong> az InnVoice-ból tükrözött szállítói számlák, a <strong>teljesítés kelte</strong>
|
||||
szerinti hónapban. <strong>Kimenő:</strong> a saját lezárt rendeléseink áfája, a lezárás hónapjában.
|
||||
szerinti hónapban. <strong>Kimenő:</strong> a saját lezárt rendeléseink áfája, a lezárás hónapjában —
|
||||
a tétel <strong>bruttó</strong> végösszegéből, egységes
|
||||
<strong>@((Model.OutgoingVatRate * 100).ToString("0.##"))%</strong>-os kulccsal,
|
||||
ugyanazzal, amivel a számla kimegy.
|
||||
</p>
|
||||
<p class="mb-1 text-muted">
|
||||
⚠️ <strong>Belső tájékozódásra</strong>, nem bevallás: nincs benne fordított adózás, arányosítás,
|
||||
|
||||
@@ -26,6 +26,19 @@ namespace Nop.Plugin.Misc.FruitBankPlugin
|
||||
/// </summary>
|
||||
public const int PreOrderConversionWindowDays = 4;
|
||||
|
||||
// ── Sales VAT ────────────────────────────────────────────────────────────
|
||||
/// <summary>The VAT rate our outgoing invoices are issued with.</summary>
|
||||
/// <remarks>
|
||||
/// The rate is stated here rather than read from the product's nop tax category, because that route
|
||||
/// yields nothing today: the tax service returns the price unchanged for these products, which is why
|
||||
/// <c>OrderItem.PriceExclTax</c> equals <c>PriceInclTax</c> on almost every row. A report deriving VAT
|
||||
/// from those two columns reads zero.
|
||||
/// <para>Whoever gives a product a different rate must replace this constant with a per-product lookup:
|
||||
/// the outgoing invoice (<c>InnVoiceOrderController</c>, <c>AfaSzoveg</c>) and the VAT report both state
|
||||
/// one rate for every line, so a second rate silently produces a wrong figure on both.</para>
|
||||
/// </remarks>
|
||||
public const decimal OutgoingVatRate = 0.27m;
|
||||
|
||||
// ── Customer generic-attribute keys (store-agnostic, StoreId 0) ──────────
|
||||
/// <summary>Bool flag: the customer is subject to EKÁER reporting.</summary>
|
||||
public const string IsEkaerAttribute = "isEkaer";
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
/// <para><b>A bejövő oldal a tükör.</b> A szállítmány-költségek (<c>fbShippingCost</c>) és a beszállítói
|
||||
/// szállítólevelek NEM számítanak bele: azok is az InnVoice-on át jönnek, és kétszer venni őket
|
||||
/// duplázás lenne.</para>
|
||||
/// <para><b>A kimenő oldal a bruttóból számol</b>, egyetlen kulccsal
|
||||
/// (<see cref="FruitBankPluginConst.OutgoingVatRate"/>) — ugyanazzal, amivel a számla kimegy. A rendelés-tétel
|
||||
/// nettó oszlopa erre nem alkalmas, az ott áll, hogy miért.</para>
|
||||
/// <para>A kimutatás teljes szabálykészlete: <c>docs/INNVOICE/INNVOICE_INCOMING.md</c> → Monthly VAT report.</para>
|
||||
/// </remarks>
|
||||
public class VatReportService(
|
||||
IncomingInvoiceDbTable incomingInvoices,
|
||||
@@ -226,7 +230,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
|
||||
var orderItems = (await dbContext.OrderItemDtos.GetAll()
|
||||
.Where(item => allItemIds.Contains(item.Id))
|
||||
.Select(item => new { item.Id, item.PriceExclTax, item.PriceInclTax })
|
||||
.Select(item => new { item.Id, item.PriceInclTax })
|
||||
.ToListAsync())
|
||||
.ToDictionary(item => item.Id);
|
||||
|
||||
@@ -244,9 +248,18 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
continue;
|
||||
}
|
||||
|
||||
// ⚠️ Az áfa a BRUTTÓBÓL áll elő, soha nem a PriceInclTax − PriceExclTax különbségből. A nop
|
||||
// adószolgáltatása ezekre a termékekre változatlanul adja vissza az árat
|
||||
// (FruitBankOrderItemService), tehát a két oszlop a tételek túlnyomó részén AZONOS, és a
|
||||
// különbség nullát ad — a kimutatás így a valós áfa fél százalékát mutatta. A PriceInclTax
|
||||
// viszont megbízható: ezzel az egységárral megy ki a számla is (InnVoiceOrderController).
|
||||
var grossAmount = item.PriceInclTax;
|
||||
var netAmount = decimal.Round(
|
||||
grossAmount / (1m + FruitBankPluginConst.OutgoingVatRate), 2);
|
||||
|
||||
row.OutgoingOrderItemCount++;
|
||||
row.OutgoingNetAmount += item.PriceExclTax;
|
||||
row.OutgoingVatAmount += item.PriceInclTax - item.PriceExclTax;
|
||||
row.OutgoingNetAmount += netAmount;
|
||||
row.OutgoingVatAmount += grossAmount - netAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,6 +304,9 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
/// <summary>A lekérdezhető legkorábbi nap — a felület ezt teszi a dátummezők alsó határának.</summary>
|
||||
public DateTime DataAvailableFrom => VatReportService.DataAvailableFrom;
|
||||
|
||||
/// <summary>A kimenő oldal áfakulcsa — a felület kiírja, mert a számból nem derül ki.</summary>
|
||||
public decimal OutgoingVatRate => FruitBankPluginConst.OutgoingVatRate;
|
||||
|
||||
/// <summary>A kért kezdő dátum korábbi volt az adathatárnál, ezért felhúztuk.</summary>
|
||||
public bool FromClamped { get; set; }
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -74,3 +74,34 @@ következő friss adatbázison ugyanide jutnánk.
|
||||
kérdésre továbbra is csak tippelni lehet.
|
||||
- [ ] **A „Nyers válasz" gomb is a teljes lekérdezést indítja üres tükörnél** (a vízjel `null`), tehát a
|
||||
diagnosztikának szánt gomb a legszűkösebb kvótából költ. Jelezni kell a felületen, vagy dátumot kérni hozzá.
|
||||
|
||||
---
|
||||
|
||||
## MGFBANKPLUG-INNV-T-R6D2: The VAT report's outgoing side moves from cost-consumption rows to completed orders
|
||||
|
||||
**Status:** Open · **Priority:** P1 · **Type:** T · **2026-08-31**
|
||||
|
||||
The anchor entry of [ADR 0004-K9T3](../adr/0004-K9T3-vat-report-outgoing-from-orders.md). This entry carries the
|
||||
implementation status; the decision, its alternatives and its consequences stay in the record.
|
||||
|
||||
Measured on PROD (2026-08-31): the report showed 126 317 060 Ft gross for 2026-08 against 309 693 697 Ft of
|
||||
completed orders, because the outgoing side only sees orders that have a cost-consumption row, and those begin
|
||||
around 2026-08-20.
|
||||
|
||||
### Steps
|
||||
|
||||
- [ ] **Read orders, not consumption rows.** `VatReportService.AddOutgoingAsync` takes every `Complete`,
|
||||
non-deleted order instead of `CostConsumptionDbTable.GetAllByPeriod`. The order item totals stay the
|
||||
revenue source; the VAT stays derived from the gross.
|
||||
- [ ] **Group by `DateOfReceipt`**, parsed with invariant culture from the `GenericAttribute` string. Not
|
||||
`DateOfReceiptOrCreated` — the fallback would hide a missing date.
|
||||
- [ ] **Anomaly counters, with the numbers on the page**: completed order without a pickup date; completed order
|
||||
with no `InnVoiceOrderTableId`; pickup date that does not parse.
|
||||
- [ ] **Drop `DataAvailableFrom`** and the header sentence explaining the floor; the date pickers lose their
|
||||
lower bound.
|
||||
- [ ] **The InnVoice attribute keys** (`InnVoiceOrderTechId`, `InnVoiceOrderTableId`, `InnVoiceOrderPrintLink`)
|
||||
move to `FruitBankPluginConst` before a second reader copies the literals.
|
||||
- [ ] **Verify against the month already measured**: 2026-08 must come out at 309 693 697 Ft gross, and the
|
||||
unsubmitted count must explain the distance from the invoiced figure.
|
||||
- [ ] **Docs** — [`INNVOICE_INCOMING.md`](INNVOICE_INCOMING.md) → *Monthly VAT report* states the new outgoing
|
||||
rules as current fact, and the calendar difference against the margin report.
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# ADR 0004-K9T3: The VAT report's outgoing side reads completed orders, keyed on the pickup date
|
||||
|
||||
## Status
|
||||
|
||||
Accepted (2026-08-31)
|
||||
|
||||
## Context
|
||||
|
||||
Measured on PROD, 2026-08-31, for the month 2026-08:
|
||||
|
||||
| Source | Population | Gross |
|
||||
|---|---|---|
|
||||
| Monthly VAT report, outgoing side | orders that have a cost-consumption row | 126 317 060 Ft |
|
||||
| nop sales summary, daily | every `Complete` order, by order date | 309 693 697 Ft |
|
||||
| The company's own books | what was actually invoiced | ~283 500 000 Ft (222 M net, 61.5 M VAT) |
|
||||
|
||||
The first figure is not a partial value but a partial period. The outgoing side reads `fbCostConsumption` and takes the month from the consumption row's `Created`. Such rows exist only since the margin feature started producing them on PROD, around 2026-08-20. The nop daily report's running total from 2026-08-20 to 2026-08-31 is 126 317 064 Ft — the reported figure. The report presented twelve days under a thirty-one-day heading.
|
||||
|
||||
`DataAvailableFrom = 2026-08-01` was meant to encode that floor. It is a hand-written constant, and it points three weeks earlier than the data starts.
|
||||
|
||||
The two halves of the report also use different calendars: the incoming side groups by `PerformanceDate`, the outgoing side by a cost-attribution timestamp. Neither that timestamp nor the nop order date is the date the VAT is owed on.
|
||||
|
||||
Four facts shape the options:
|
||||
|
||||
- The performance date of a sale is the pickup time, held in `OrderDto.DateOfReceipt`: a `GenericAttribute` on `Order`, written as a string in `MM/dd/yyyy HH:mm:ss` invariant format, store-scoped, `readonly, not-mapped` on the DTO. `DateOfReceiptOrCreated` already encodes a fallback to `CreatedOnUtc`.
|
||||
- Whether an order reached InnVoice is recorded on the order as `InnVoiceOrderTechId`, `InnVoiceOrderTableId` and `InnVoiceOrderPrintLink` — also GenericAttributes, also strings, also store-scoped, keyed by literals rather than constants. They record that an **order** was submitted. The outbound direction submits an order rather than an invoice (`MGFBANKPLUG-INNV-T-B2N7`), so they do not prove an invoice exists.
|
||||
- 2026-08 holds ~1223 orders. A month is small enough to filter in memory.
|
||||
- The gap between the nop figure and the company's books is orders completed but never invoiced. That is a business fact, not a defect of the report.
|
||||
|
||||
Scope IN: the outgoing side's population, its month key, and the anomalies it must surface. Scope OUT: the VAT rate per product (its own decision), the incoming side, the margin report's month key (ADR 0002-R7Q2, point 10), and the switch to direct draft-invoice submission.
|
||||
|
||||
## Decision
|
||||
|
||||
> **An ADR records reasoning, not the state, and binds nothing — `BASE:ARTIFACT_ROLES`.**
|
||||
|
||||
**The outgoing side reads every completed order and groups it by the pickup date; what is missing is shown, never substituted.**
|
||||
|
||||
1. **Population: every `Complete`, non-deleted order.** An order that never reached InnVoice stays in the figure and is counted as an anomaly. A missing submission is a business exception, not a report filter.
|
||||
|
||||
2. **The month is `DateOfReceipt`.** An order without one is placed in no month and appears on an anomaly line. `DateOfReceiptOrCreated` is deliberately not used here: its fallback would hide a missing date behind a plausible month, and the incoming side already treats a missing performance date the same way.
|
||||
|
||||
3. **The pickup date stays a `GenericAttribute`.** No column is added to a nop-owned table, so a nopCommerce upgrade migration cannot collide with it.
|
||||
|
||||
4. **The period is therefore evaluated in memory.** SQL narrows to the order status; the stored string is parsed with invariant culture. The parse must be invariant: under a Hungarian locale `08/05/2026` reads as 8 May, and that error is silent for every day from 1 to 12.
|
||||
|
||||
5. **`DataAvailableFrom` is dropped.** Its reason was the consumption rows' start date; the order list has no such floor.
|
||||
|
||||
6. **The two reports keep different calendars.** The VAT report groups by pickup, the margin report by completion. Each is correct for its own purpose, and the difference is stated wherever either is read.
|
||||
|
||||
7. **Outgoing VAT stays derived from the gross** with `FruitBankPluginConst.OutgoingVatRate` (pre-flight fix, 2026-08-31), until the per-product rate is decided.
|
||||
|
||||
Anomaly lines, each carrying a count: a completed order with no pickup date; a completed order never submitted to InnVoice; a pickup date that does not parse.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
|
||||
- The month is the whole month, so the report can be held against the accounting figure at all.
|
||||
- The grouping date is the one VAT is owed on, and both halves of the report answer for the same period.
|
||||
- Earlier months become answerable, because the order list reaches back as far as the orders do.
|
||||
- No schema change, so nothing here interacts with a nopCommerce upgrade.
|
||||
- What is missing is visible: unsubmitted and undated orders are counted rather than dropped.
|
||||
|
||||
**Negative:**
|
||||
|
||||
- The period filter is not enforced by the database. ~1200 orders a month is small, but nothing in the schema keeps it small.
|
||||
- The string date stays a trap: a writer using another format, or a reader using another culture, corrupts the month silently for days 1 to 12.
|
||||
- The report exceeds the invoiced figure by whatever was completed and never invoiced. This is intended, and the anomaly count is what explains the difference.
|
||||
- A completed order with no pickup date falls out of every month, exactly as an undated supplier invoice does. Its counter is what stops the loss from being silent.
|
||||
- Two reports over the same sales now group by different dates, which needs explaining to anyone reading both.
|
||||
|
||||
**Follow-ups required:**
|
||||
|
||||
- `MGFBANKPLUG-INNV-T-R6D2` ([INNVOICE_TODO.md](../INNVOICE/INNVOICE_TODO.md)) carries the implementation status.
|
||||
- [`INNVOICE_INCOMING.md`](../INNVOICE/INNVOICE_INCOMING.md) → *Monthly VAT report* states the outgoing rules as current fact as they land.
|
||||
- The InnVoice attribute keys are literals at their call sites; a report reading them would make a second copy. They belong in `FruitBankPluginConst`, beside the other order attribute keys.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Keep `fbCostConsumption` as the source** (rejected): its rows begin when the margin feature began, and its date is a cost-attribution timestamp rather than a performance date. The Context measurement is what that costs — twelve days reported as a month.
|
||||
- **Group by nop's `CreatedOnUtc`** (rejected): it is mapped, indexable and the cheapest of the options, but it is the order date rather than the performance date, and it would leave the report's two halves on different calendars for no gain.
|
||||
- *Cost:* materially lower than the chosen option — no attribute join, no in-memory filter.
|
||||
- **Promote `DateOfReceipt` to a first-class column** (rejected, Adam 2026-08-31): it would be SQL-filterable and indexable and would end the string-format trap, but it adds a column to a nop-owned table, where a future nopCommerce upgrade migration could collide with it. The same reasoning does not extend to the plugin's own tables.
|
||||
- *Reversibility:* low — a migration plus a backfill, and the shared contract would need a co-deploy.
|
||||
- **Restrict the population to InnVoice-submitted orders** (rejected): submission is not invoicing, and a missing submission is precisely what the report should reveal.
|
||||
- **Ask InnVoice per order whether an invoice was issued** (rejected): the id-less budget is 20 requests an hour and overrun degrades silently, so a month of orders would exhaust it and then answer with less data without saying so.
|
||||
|
||||
## Related
|
||||
|
||||
- Related ADRs: [0002-R7Q2](0002-R7Q2-document-cost-layers-and-margin.md) — placed the VAT analytics out of scope, and fixed the margin report's month key that point 6 diverges from.
|
||||
- Related TODOs/Issues: `MGFBANKPLUG-INNV-T-R6D2` (tracking) · `MGFBANKPLUG-INNV-T-B2N7` (draft-invoice submission, which would make the invoiced / not-invoiced distinction exact).
|
||||
@@ -22,6 +22,7 @@ Az implementáció állapotát ADR-enként **pontosan egy** `_TODO` / `_ISSUES`
|
||||
| [0001-V4T7](0001-V4T7-product-master-data-storage.md) | A termék mérési/logisztikai törzsadata elsőrendű, plugin-tulajdonú táblába kerül a `GenericAttribute`-ok helyett | Proposed (2026-08-11) | `MGFBANKPLUG-PMDATA-T-H3N8` ([PMDATA_TODO](../PMDATA/PMDATA_TODO.md)) — Open |
|
||||
| [0002-R7Q2](0002-R7Q2-document-cost-layers-and-margin.md) | A nyereség-kimutatás a szállítólevél szintjén számol, FIFO-val fogyasztott költség-rétegekből, és minden pénzügyi értéket lekérdezéssel állít elő | Proposed (2026-08-13) | `MGFBANKPLUG-MARGIN-T-K5V9` ([MARGIN_TODO](../MARGIN/MARGIN_TODO.md)) — InProgress |
|
||||
| [0003-H7C4](0003-H7C4-dto-event-bridge-wiring.md) | A DTO-repók esemény-hídja bekötésre kerül a keretben, a `Nop.Services` referencia árán | Accepted (2026-08-18) | `MGFBANKPLUG-NOTIF-I-Q3M8` ([NOTIFICATION_ISSUES](../NOTIFICATION/NOTIFICATION_ISSUES.md)) — Closed |
|
||||
| [0004-K9T3](0004-K9T3-vat-report-outgoing-from-orders.md) | The VAT report's outgoing side reads completed orders, keyed on the pickup date | Accepted (2026-08-31) | `MGFBANKPLUG-INNV-T-R6D2` ([INNVOICE_TODO](../INNVOICE/INNVOICE_TODO.md)) — Open |
|
||||
|
||||
## Ezt a repót érintő döntések más rétegben
|
||||
|
||||
|
||||
Reference in New Issue
Block a user