Files

334 lines
15 KiB
Markdown

# HANDEL08 — VAT, wartości i waluty
> Wspólne fakty o typie, podstawowe typy i szablon wzorca: [../handel.md](../handel.md).
Rozdział opisuje publiczny kontrakt dokumentu handlowego w zakresie tabeli VAT, podsumowań
wartości, ręcznej korekty VAT, sposobu liczenia VAT oraz zmiany waluty dokumentu i cen. Cały kod
jest zgodny z **C# 10** i operuje wyłącznie na **publicznych** typach i workerach platformy.
> **Wartości pieniężne** na pozycjach tabeli VAT i podsumowaniach mają dwie reprezentacje:
> `BruttoNetto` — kwoty w walucie systemowej jako `decimal` (`Netto`, `VAT`, `Brutto`); `BruttoNettoCy`
> — kwoty w walucie dokumentu jako `Currency` (`NettoCy`, `VATCy`, `BruttoCy`). Nie operuj na
> niezaokrąglonych `decimal` — platforma weryfikuje zaokrąglenie (safe-code §10).
---
### HANDEL-W43 — Odczytanie tabeli VAT (`SumyVAT`)
**Cel:** odczytać rozbicie wartości dokumentu na stawki VAT (netto / VAT / brutto wg stawki) — np.
do wydruku, eksportu lub kontroli sumy podatku.
**Warianty:**
| Wariant | Źródło | Uwaga |
|---|---|---|
| Tabela VAT dokumentu | `dok.SumyVAT` (`SubTable<SumaVAT>`) | po jednej pozycji na stawkę |
| Kwoty w walucie systemowej | `suma.Suma` (`BruttoNetto`) | `Netto`/`VAT`/`Brutto` jako `decimal` |
| Kwoty w walucie dokumentu | `suma.SumaCy` (`BruttoNettoCy`) | `NettoCy`/`VATCy`/`BruttoCy` jako `Currency` |
| Procent / opis stawki | `suma.Stawka`, `suma.DefinicjaStawki` | `StawkaVat.Procent: Percent` |
| Sumy z dokumentów nadrzędnych | `dok.NadrzędneSumyVAT` (`IList`) | scalone stawki nadrzędnych |
**Pola i typy:** `dok.SumyVAT: SubTable<SumaVAT>`. `SumaVAT` udostępnia: `DefinicjaStawki:
DefinicjaStawkiVat`, `Stawka: StawkaVat` (`Stawka.Procent: Percent`), `Suma: BruttoNetto`
(`Netto`, `VAT`, `Brutto``decimal`), `SumaCy: BruttoNettoCy` (`NettoCy`, `VATCy`, `BruttoCy`
`Currency`), `Dokument: DokumentHandlowy`.
**Snippet:**
```csharp
var dok = session.GetHandel().DokHandlowe.WgDaty[...]; // lub po Guid
// Iteracja po tabeli VAT — jedna pozycja (SumaVAT) na każdą stawkę dokumentu:
foreach (SumaVAT s in dok.SumyVAT)
{
Percent stawka = s.Stawka.Procent; // np. 23%
decimal netto = s.Suma.Netto; // kwota netto w walucie systemowej
decimal vat = s.Suma.VAT; // kwota podatku VAT
decimal brutto = s.Suma.Brutto; // kwota brutto
// Kwoty w walucie dokumentu (Currency = wartość + symbol waluty):
Currency vatCy = s.SumaCy.VATCy;
Console.WriteLine($"{stawka}: netto={netto} VAT={vat} brutto={brutto}");
}
// Łączna kwota VAT dokumentu z tabeli VAT:
decimal vatRazem = dok.SumyVAT.Sum(s => s.Suma.VAT);
```
**Pułapki:**
- `dok.SumyVAT` to `SubTable<SumaVAT>` — kolekcja serwerowa; iteruj po niej, nie materializuj do listy,
jeśli wystarczy przebieg jednorazowy. Tabela VAT jest mała (kilka stawek), więc `.Sum(...)` jest
akceptowalne.
- Rozróżniaj `Suma` (`BruttoNetto`, `decimal` w walucie systemowej) od `SumaCy` (`BruttoNettoCy`,
`Currency` w walucie dokumentu). Dla dokumentu walutowego do prezentacji używaj `SumaCy`.
- `Stawka` to `StawkaVat` (typ stawki), `Procent` zwraca `Percent` — nie myl z `decimal`.
- Tabela VAT jest **wyliczana z pozycji** dokumentu (chyba że włączono `KorektaVAT` — patrz HANDEL-W45). Nie
modyfikuj jej, gdy chcesz tylko odczytać wartości.
---
### HANDEL-W44 — Odczyt podsumowań wartości dokumentu
**Cel:** odczytać zsumowane wartości netto / VAT / brutto całego dokumentu oraz proponowany rabat —
bez ręcznego sumowania pozycji.
**Warianty:**
| Wariant | Pole | Typ | Uwaga |
|---|---|---|---|
| Podsumowanie dokumentu | `dok.Suma` | `BruttoNetto` | `Netto`/`VAT`/`Brutto` (`decimal`, waluta systemowa) |
| Wartość brutto w walucie | `dok.BruttoCy` | `Currency` | brutto w walucie dokumentu |
| Suma wyliczona z pozycji | `dok.SumaPozycji` | `BruttoNettoPozycji` | `Netto`/`VAT`/`Brutto` (read-only) |
| Suma pozycji tow./prod. | `dok.SumaPozycjiTowProd` | `BruttoNettoPozycji` | tylko towary i produkty |
| Proponowany rabat | `dok.Rabat` | `Percent` | przepisywany do pozycji |
**Pola i typy:** `dok.Suma: BruttoNetto` (podsumowana wartość dokumentu), `dok.BruttoCy: Currency`,
`dok.SumaPozycji: BruttoNettoPozycji` (`Netto`/`VAT`/`Brutto``decimal`, **tylko do odczytu**,
liczone na bieżąco z pozycji), `dok.Rabat: Percent`.
**Snippet:**
```csharp
var dok = session.GetHandel().DokHandlowe.WgDaty[...];
// Podsumowanie całego dokumentu (waluta systemowa):
decimal netto = dok.Suma.Netto;
decimal vat = dok.Suma.VAT;
decimal brutto = dok.Suma.Brutto;
// Brutto w walucie dokumentu (dla dokumentów walutowych):
Currency bruttoCy = dok.BruttoCy;
// Suma wyliczana z pozycji (przydatne do kontroli spójności z dok.Suma):
var sp = dok.SumaPozycji;
Console.WriteLine($"Pozycje: netto={sp.Netto} VAT={sp.VAT} brutto={sp.Brutto}");
// Proponowany rabat dokumentu (przepisywany do nowych pozycji):
Percent rabat = dok.Rabat;
```
**Pułapki:**
- `dok.Suma` to **stan zapisany** podsumowania, a `dok.SumaPozycji` jest **wyliczane na bieżąco**
z pozycji za każdym odczytem. Dla dokumentu w buforze, przed ponownym przeliczeniem, mogą się
chwilowo różnić.
- `SumaPozycji`/`SumaPozycjiTowProd` zwracają `BruttoNettoPozycji` — typ **tylko do odczytu** (brak
setterów); nie próbuj przez nie modyfikować wartości.
- `dok.Rabat` to `Percent` — proponowany rabat dokumentu, przepisywany do nowo dodawanych pozycji;
ustawienie nie przelicza wstecznie pozycji już istniejących.
- Wartości brutto/netto na poziomie dokumentu zależą od `LiczonaOd` (HANDEL-W46) i ewentualnej korekty
tabeli VAT (`KorektaVAT`, HANDEL-W45).
---
### HANDEL-W45 — Ręczna korekta tabeli VAT (`KorektaVAT`)
**Cel:** ręcznie skorygować kwoty w tabeli VAT (gdy wyliczenie z pozycji nie odpowiada wartości
docelowej — np. zaokrąglenia faktury źródłowej), włączając flagę `KorektaVAT` i edytując wiersze
`SumyVAT`.
**Warianty:**
| Wariant | Operacja |
|---|---|
| Włączenie trybu korekty | `dok.KorektaVAT = true` |
| Ręczna zmiana kwoty stawki | edycja `suma.Suma.Netto` / `.VAT` / `.Brutto` na wierszu `SumaVAT` |
| Dostępność korekty | `dok.IsReadOnlyKorektaVAT()`, `dok.IsReadOnlySumyVAT()` (sterowanie UI) |
| Powrót do automatu | `dok.KorektaVAT = false` (tabela liczona ponownie z pozycji) |
**Pola i typy:** `dok.KorektaVAT: bool` (czy sumy VAT zmieniono ręcznie i nie zależą od pozycji),
`SumaVAT.Suma: BruttoNetto` (`Netto`/`VAT`/`Brutto``decimal`). Wiersze tabeli VAT są edytowalne
**tylko gdy** `KorektaVAT == true` (`SumaVAT.IsReadOnly()` zwraca `true` przy wyłączonej fladze).
> **Worker `KorektaTabeliVATWorker` jest `internal`** — nie da się go zainstancjonować z dodatku
> zewnętrznego. Publiczny tor korekty prowadzi przez flagę `dok.KorektaVAT` i bezpośrednią edycję
> pól wierszy `dok.SumyVAT`.
**Snippet:**
```csharp
var dok = session.GetHandel().DokHandlowe.WgDaty[...];
using (var t = session.Logout(editMode: true)) // CommitUI() w workerze/extenderze
{
// 1. Włącz ręczną korektę — odblokowuje edycję wierszy tabeli VAT:
dok.KorektaVAT = true;
// 2. Skoryguj kwoty na wybranej stawce (np. wyrównanie groszowe na 23%):
foreach (SumaVAT s in dok.SumyVAT)
{
if (s.Stawka.Procent == new Percent(0.23))
{
s.Suma.VAT = 230.01m; // wartości MUSZĄ być zaokrąglone do grosza
s.Suma.Brutto = 1230.01m;
}
}
t.Commit();
}
session.Save();
```
**Pułapki:**
- Edycja wierszy `SumyVAT` bez `dok.KorektaVAT = true` zostanie zablokowana — `SumaVAT` jest wtedy
read-only (sumy zależą od pozycji).
- Przypisywane kwoty muszą być **zaokrąglone do grosza** — w trybie DEBUG ustawienie
niezaokrąglonej wartości `Netto`/`VAT`/`Brutto` rzuca `ArgumentException`. Zaokrąglaj wejście
(`Soneta.Tools.Math.RoundCy(...)`).
- `KorektaVAT` jest dostępna tylko, gdy definicja dokumentu na to pozwala
(`Definicja.SumyVAT` w trybie korekty) — sprawdzaj `dok.IsReadOnlyKorektaVAT()` zanim ustawisz
flagę z poziomu UI.
- Po włączeniu korekty tabela VAT **przestaje** śledzić zmiany pozycji. Wyłączenie
(`KorektaVAT = false`) przywraca wyliczanie z pozycji i nadpisuje ręczne kwoty.
- `DefinicjaStawki` na wierszu `SumaVAT` można zmieniać tylko przy włączonej korekcie
(`IsReadOnlyDefinicjaStawki()` zależy od `KorektaVAT`).
---
### HANDEL-W46 — Sposób liczenia VAT (`LiczonaOd`) i przeliczenie procedur VAT
**Cel:** ustawić, czy dokument jest liczony od netto czy od brutto (`LiczonaOd`), oraz przeliczyć
procedury VAT (JPK) na dokumencie zatwierdzonym/zaksięgowanym przy użyciu publicznego workera.
**Warianty:**
| Wariant | Mechanizm |
|---|---|
| Liczenie od netto | `dok.LiczonaOd = SposobLiczeniaVAT.OdNetto` |
| Liczenie od brutto | `dok.LiczonaOd = SposobLiczeniaVAT.OdBrutto` |
| Od brutto minus netto | `dok.LiczonaOd = SposobLiczeniaVAT.OdBruttoMinusNetto` |
| Wg ustawień kontrahenta | `dok.LiczonaOd = SposobLiczeniaVAT.ZależyOdKontrahenta` |
| Przeliczenie procedur VAT | worker `PrzeliczProceduryVATWorker` (publiczny) |
**Pola i typy:** `dok.LiczonaOd: SposobLiczeniaVAT` — enum `Soneta.Handel.SposobLiczeniaVAT`:
`OdNetto=1`, `OdBrutto=2`, `OdBruttoMinusNetto=3`, `ZależyOdKontrahenta=4` (wartość `0` jest
niedozwolona — rzuca `RequiredException`). Worker `PrzeliczProceduryVATWorker` ma publiczną klasę
parametrów `PrzeliczProceduryVATParams : ContextBase` (`Zatwierdzone: bool = true`,
`Zaksiegowane: bool = false`) oraz właściwości `[Context]`: `Dokument: DokumentHandlowy`,
`Params: PrzeliczProceduryVATParams`.
**Snippet:**
```csharp
var dok = session.GetHandel().DokHandlowe.WgDaty[...];
// 1. Zmiana sposobu liczenia VAT (dokument w buforze):
using (var t = session.Logout(editMode: true))
{
dok.LiczonaOd = SposobLiczeniaVAT.OdBrutto; // 0 jest niedozwolone
t.Commit();
}
session.Save();
// 2. Przeliczenie procedur VAT (JPK) workerem publicznym.
// Worker działa tylko dla dokumentu zatwierdzonego (Params.Zatwierdzone)
// lub zablokowanego/zaksięgowanego (Params.Zaksiegowane):
var p = new PrzeliczProceduryVATWorker.PrzeliczProceduryVATParams(context)
{
Zatwierdzone = true,
Zaksiegowane = false,
};
var worker = new PrzeliczProceduryVATWorker
{
Dokument = dok,
Params = p,
};
worker.PrzeliczProceduryVAT(); // sam otwiera transakcję i Commit
session.Save();
```
**Pułapki:**
- `LiczonaOd` nie przyjmuje wartości `0` (`RequiredException`). Zawsze ustaw konkretny wariant enuma.
- Zmiana `LiczonaOd` na dokumencie z pozycjami wpływa na sposób przeliczenia netto↔brutto pozycji
i tabeli VAT — rób to przed wprowadzeniem cen lub świadomie po przeliczeniu.
- `PrzeliczProceduryVATWorker.PrzeliczProceduryVAT()` **nic nie zrobi**, jeśli dokument jest w
buforze albo stan nie pasuje do flag `Params` (`Zatwierdzone`/`Zaksiegowane`). Worker sam otwiera
transakcję (`Logout(true)` + `Commit`) — nie owijaj go w dodatkową transakcję edycyjną.
- Worker jest widoczny tylko, gdy definicja liczy sumy VAT i ma definicję ewidencji
(`IsVisiblePrzeliczProceduryVAT`); z poziomu kodu i tak sprawdź stan dokumentu przed wywołaniem.
- `PrzeliczProceduryVATParams` dziedziczy po `ContextBase` — przy ręcznym tworzeniu przekaż `Context`
do konstruktora.
---
### HANDEL-W47 — Zmiana waluty dokumentu i cen
**Cel:** zmienić walutę dokumentu handlowego (i opcjonalnie przeliczyć ceny pozycji) — np. wystawić
fakturę w EUR zamiast PLN, z kursem z wybranej tabeli kursowej.
**Warianty:**
| Wariant | Mechanizm |
|---|---|
| Zmiana waluty z przeliczeniem cen | parametry `DokumentHandlowyZmianaWalutyWorkerParams` + akcja „Zmień walutę dokumentu i cen..." |
| Zmiana waluty bez cen | te same parametry z `ZmienCeny = false` |
| Ręczne ustawienie waluty/kursu | `dok.TabelaKursowa`, `dok.KursWaluty`, `dok.DataOgłoszeniaKursu`, `dok.BruttoCy` |
**Pola i typy:** klasa parametrów (publiczna) `DokumentHandlowyZmianaWalutyWorkerParams :
PozycjaDokHandlowegoZmianaWalutyCenyWorkerParams` (ctor `(Context, [Context] DokumentHandlowy)`)
udostępnia: `Waluta: Waluta` („na walutę"), `WalutaBazowa: Waluta` (read-only, „z waluty"),
`TabelaKursowa: TabelaKursowa`, `Data: Date`, `KursWaluty: double`, `ZmienCeny: bool`. Pola
dokumentu: `dok.TabelaKursowa: TabelaKursowa`, `dok.KursWaluty: double`, `dok.BruttoCy: Currency`.
Moduł walut (jest `internal` jako extension): `Soneta.Waluty.WalutyModule.GetInstance(session)`
`.Waluty.WgSymbolu["EUR"]`, `.TabeleKursowe`.
> **Worker `DokumentHandlowyZmianaWalutyWorker` jest `internal`** — nie da się go zainstancjonować
> bezpośrednio z dodatku zewnętrznego. Jest jednak zarejestrowany jako akcja menu Czynności („Zmień
> walutę dokumentu i cen...", `Shift+F11`) i przyjmuje publiczne parametry
> `DokumentHandlowyZmianaWalutyWorkerParams`. Z poziomu kodu dodatku zewnętrznego dostępne tory to:
> (1) uruchomienie akcji przez mechanizm Czynności z przygotowanym `Context`, albo (2) bezpośrednie
> ustawienie pól waluty/kursu na dokumencie i pozycjach.
**Snippet:**
```csharp
using Microsoft.Extensions.DependencyInjection; // jeśli korzystasz z serwisów
using Soneta.Waluty;
var dok = session.GetHandel().DokHandlowe.WgDaty[...];
// --- Tor 1: przygotowanie parametrów workera (do uruchomienia przez akcję Czynności) ---
// Worker jest internal — z dodatku przygotowujemy publiczne Params i uruchamiamy akcję
// przez mechanizm menu Czynności (Context z zaznaczonym dokumentem).
var wm = WalutyModule.GetInstance(session);
var p = new DokumentHandlowyZmianaWalutyWorkerParams(context, dok)
{
Waluta = wm.Waluty.WgSymbolu["EUR"], // waluta docelowa
TabelaKursowa = wm.TabeleKursowe.NBP,
Data = Date.Today,
ZmienCeny = true, // przelicz też ceny pozycji
};
// KursWaluty wylicza się automatycznie po ustawieniu Waluta/TabelaKursowa/Data;
// w razie potrzeby można nadpisać: p.KursWaluty = 4.30;
// --- Tor 2: ręczne ustawienie waluty i kursu na dokumencie (bez workera) ---
using (var t = session.Logout(editMode: true))
{
dok.TabelaKursowa = wm.TabeleKursowe.NBP;
dok.KursWaluty = 4.30;
// dok.BruttoCy = new Currency(..., "EUR"); // kwoty w walucie dokumentu
t.Commit();
}
session.Save();
```
**Pułapki:**
- Worker `DokumentHandlowyZmianaWalutyWorker` jest `internal`**nie** wywołasz `new ...Worker(...)`
ani `.ZmienWalute()` z dodatku zewnętrznego. Używaj publicznych `Params` + akcji Czynności lub
bezpośredniej edycji pól dokumentu.
- `session.GetWaluty()` jest **internal** — moduł walut pobieraj przez
`WalutyModule.GetInstance(session)` (namespace `Soneta.Waluty`).
- Jeśli w bazie **brak kursu** na żądaną datę (np. Demo nie ma kursu EUR „na dziś"), platforma rzuci
`KursWalutyNotFoundException`. `KursWaluty` w parametrach wylicza się automatycznie tylko, gdy kurs
istnieje; w przeciwnym razie ustaw `KursWaluty` ręcznie.
- Zmiana waluty ma sens tylko dla dokumentu w **buforze** (`IsVisibleZmienWalute` wymaga
`dok.Bufor`); dla dokumentu zatwierdzonego operacja jest niedostępna.
- `WalutaBazowa` jest read-only — wyznaczana z bieżącej waluty dokumentu (`dok.BruttoCy.Symbol`).
Ustawiasz tylko `Waluta` (docelową).
- Kwoty pieniężne to `Currency` (wartość + symbol), nie `decimal`/`double`. Sam `KursWaluty` jest
`double`.
---
---