Files

226 lines
13 KiB
Markdown

# HANDEL01 — Fundamenty i identyfikacja
> Wspólne fakty o typie, podstawowe typy i szablon wzorca: [../handel.md](../handel.md).
> Rozdział opisuje, jak z poziomu sesji dotrzeć do modułów handlowo-magazynowych, jak poprawnie
> wskazać **definicję dokumentu** (`DefDokHandlowego`) zanim utworzysz dokument, oraz jak na podstawie
> definicji i flag dokumentu **rozpoznać jego rodzaj** (faktura / magazynowy / zamówienie / korekta /
> zaliczka). Cały kod jest zgodny z **C# 10** i operuje wyłącznie na **publicznym kontrakcie**
> platformy. Fundamenty wspólne (sesja, transakcja `session.Logout(true)` + `Commit`/`CommitUI`,
> blokada optymistyczna, praca z `SubTable`) opisują [`safe-code.md`](../safe-code.md),
> [`session-login.md`](../session-login.md) oraz [`worker-extender.md`](../worker-extender.md) — tutaj
> się do nich odwołujemy, nie powtarzamy ich.
### HANDEL-W1 — Dostęp do modułów handlowo-magazynowych i tabeli `DokHandlowe`
**Cel:** z obiektu `Session` (lub dowolnego `ISessionable``Row`, `Table`, `Context`) dotrzeć do
modułów, na których opiera się logika handlu i magazynu, oraz do tabeli dokumentów `DokHandlowe`.
To punkt wejścia każdego scenariusza w tym dokumencie.
**Warianty:**
| Wariant | Wywołanie (extension method na `Session`) | Co udostępnia |
|---|---|---|
| Moduł handlowy | `session.GetHandel()``HandelModule` | `.DokHandlowe` (tabela dokumentów), `.DefDokHandlowych` (definicje) |
| Moduł magazynowy | `session.GetMagazyny()``MagazynyModule` | `.Magazyny`, `.Zasoby`, `.Obroty`, `.GrupyDostaw` (partie), `.OkresyMag` |
| Moduł towarów | `session.GetTowary()``TowaryModule` | `.Towary`, `.Jednostki` |
| Moduł CRM | `session.GetCRM()``CRMModule` | `.Kontrahenci` |
| Moduł kasowy | `session.GetKasa()``KasaModule` | formy płatności, rozrachunki (dot. płatności dokumentu) |
| Waluty | `Soneta.Waluty.WalutyModule.GetInstance(session)` | `.Waluty`, `.TabeleKursowe` |
**Pola i typy:** `HandelModule.DokHandlowe: DokHandlowe` (tabela `DokumentHandlowy`),
`HandelModule.DefDokHandlowych` (tabela `DefDokHandlowego`),
`MagazynyModule.Magazyny`, `TowaryModule.Towary`, `CRMModule.Kontrahenci`. Wszystkie moduły
implementują `ISessionable` i mają property `.Session`.
**Snippet:**
```csharp
// Punkt wejścia — z sesji pobieramy moduły handlowo-magazynowe:
var handel = session.GetHandel(); // HandelModule
var magazyny = session.GetMagazyny(); // MagazynyModule
var towary = session.GetTowary(); // TowaryModule
var crm = session.GetCRM(); // CRMModule
// Tabela dokumentów handlowych (operacyjna, guided):
var dokumenty = handel.DokHandlowe;
// Iteracja po dokumentach — ZAWSZE zawężaj zakres (data/definicja/kontrahent),
// to tabela operacyjna rosnąca z biznesem. Filtr aplikujemy na indeksie (warunek serwerowy):
var od = Date.Today.AddMonths(-1);
foreach (DokumentHandlowy d in handel.DokHandlowe.WgDaty[(DokumentHandlowy x) => x.Data >= od])
{
// d.* — Numer, Data, Definicja, Kontrahent, Suma, Stan ...
}
// Z dowolnego ISessionable można zejść do modułu również metodą GetInstance:
var hm = Soneta.Handel.HandelModule.GetInstance(jakisRow); // gdy nie mamy zmiennej Session
```
**Pułapki:**
- Moduł i tabela są **single-threaded** — nie współdziel ich między wątkami; pobieraj je z sesji
bieżącego wątku (thread-safety w SKILL.md).
- `session.GetWaluty()` jest **internal** — z dodatku zewnętrznego użyj
`Soneta.Waluty.WalutyModule.GetInstance(session)`.
- **Nie ładuj całej tabeli `DokHandlowe`** do pamięci z `if`-em w pętli. Filtruj serwerowo —
warunek aplikuj na indeksie tabeli (np. `WgDaty[(DokumentHandlowy x) => …]`), żeby wykonał się
po stronie SQL (safe-code §6). W warunku `RowCondition` używaj **tylko pól bazodanowych** — pola
kalkulowane rzucą `LinqConditionException`.
- Pobranie modułu nie tworzy ani nie modyfikuje danych — modyfikacje zawsze w transakcji
(`session.Logout(true)` + `Commit`/`CommitUI`, potem `Save`).
### HANDEL-W2 — Wybór definicji dokumentu (`DefDokHandlowego`) wg symbolu
**Cel:** zanim utworzysz dokument, musisz wskazać jego **definicję** — to ona określa typ dokumentu
(sprzedaż, zakup, magazynowy, zamówienie…), numerację, zachowanie magazynu i płatności. Definicja
jest **pierwszym** ustawianym polem nowego dokumentu (`dok.Definicja = …`), zanim ustawisz magazyn,
kontrahenta czy pozycje.
**Warianty:**
| Wariant | Klucz / mechanizm | Uwaga |
|---|---|---|
| Po symbolu | `DefDokHandlowych.WgSymbolu["FV"]` | indeks **unikalny** — zwraca pojedynczy rekord lub `null` |
| Filtr po kategorii (typie) | `DefDokHandlowych.WgKategorii[KategoriaHandlowa.Sprzedaż]` | zbiór wszystkich definicji danej kategorii |
| Po symbolu w obrębie kategorii | warunek serwerowy na `WgSymbolu` + sprawdzenie `Kategoria` | gdy w bazie istnieje kilka wariantów sprzedaży |
| Walidacja istnienia | `WgSymbolu[symbol] != null` | brak definicji = nie da się utworzyć dokumentu |
Typowe symbole w bazie Demo: **FV** (faktura sprzedaży), **FZ** (faktura zakupu), **PAR** (paragon),
**PZ**/**PW** (przyjęcia magazynowe), **WZ**/**RW** (rozchody magazynowe), **ZO** (zamówienie
odbiorcy), **ZD** (zamówienie do dostawcy), **MM** (przesunięcie międzymagazynowe),
**INW** (inwentaryzacja), **KS** (korekta sprzedaży). Symbole zależą od konfiguracji konkretnej bazy —
nie zakładaj ich „na sztywno", weryfikuj `!= null`.
**Pola i typy:** `DefDokHandlowego.Symbol: string` (maks. 12 znaków, unikalny),
`DefDokHandlowego.Kategoria: Soneta.Handel.KategoriaHandlowa`. Indeks `WgSymbolu` jest unikalny
(zwraca pojedynczy rekord), `WgKategorii` grupuje definicje po kategorii.
**Snippet:**
```csharp
var handel = session.GetHandel();
// 1. Po symbolu — klucz unikalny: pojedynczy rekord albo null
DefDokHandlowego defFV = handel.DefDokHandlowych.WgSymbolu["FV"];
if (defFV == null)
throw new BusException("Brak definicji dokumentu o symbolu FV w tej bazie.".Translate());
// 2. Wszystkie definicje danej kategorii (np. wszystkie definicje sprzedaży):
foreach (DefDokHandlowego d in handel.DefDokHandlowych.WgKategorii[KategoriaHandlowa.Sprzedaż])
{
// d.Symbol, d.Kategoria ...
}
// 3. Użycie definicji przy tworzeniu dokumentu — Definicja USTAWIANA PIERWSZA:
using (var t = session.Logout(editMode: true))
{
var dok = new DokumentHandlowy();
session.AddRow(dok); // AddRow przed ustawianiem pól
dok.Definicja = handel.DefDokHandlowych.WgSymbolu["PW"]; // definicja jako pierwsze pole
// dok.Magazyn / dok.Kontrahent ustawiamy dopiero PO definicji (gdy definicja ich wymaga)
t.Commit(); // CommitUI() w workerze/extenderze
}
session.Save();
```
**Pułapki:**
- `WgSymbolu[...]` zwraca **pojedynczy** rekord (klucz unikalny) i może być `null` — zawsze sprawdź
przed użyciem. `WgKategorii[...]` zwraca **zbiór** — iteruj lub `.FirstOrDefault()`.
- **Definicja musi być ustawiona jako pierwsze pole** dokumentu — od niej zależy widoczność i
wymagalność pozostałych pól (magazyn, kontrahent, numeracja). Ustawienie magazynu/kontrahenta
przed definicją jest błędem.
- Symbole **nie są gwarantowane** — zależą od konfiguracji bazy klienta. Nie polegaj na obecności
„FV"/„WZ"; pobierz definicję i sprawdź `!= null`, a w razie potrzeby filtruj po `Kategoria`.
- `DefDokHandlowego` to dane **konfiguracyjne** (`GuidedRow`) — odczytuj je, nie twórz „w locie" w
kodzie operacyjnym.
### HANDEL-W3 — Rozpoznanie rodzaju dokumentu (faktura / magazynowy / zamówienie / korekta / zaliczka)
**Cel:** ustalić, „czym jest" dany dokument — fakturą, dokumentem magazynowym, zamówieniem, korektą
czy dokumentem zaliczkowym — by rozgałęzić logikę (np. inaczej traktować rozchód magazynowy niż
zamówienie). Rozpoznanie opiera się na **kategorii definicji** (`Definicja.Kategoria`) oraz na
gotowych flagach dokumentu (`Korekta`, `JestDokZaliczkowy()`).
**Warianty:**
| Co rozpoznajemy | Mechanizm (publiczny kontrakt) | Wartości / zakres `KategoriaHandlowa` |
|---|---|---|
| Faktura/handlowy (sprzedaż, zakup, korekty, f. wewnętrzna) | `Definicja.Kategoria` w zakresie handlowym | `Sprzedaż=2`, `KorektaSprzedaży=3`, `Zakup=4`, `KorektaZakupu=5`, `FakturaWewnętrzna=6` (zakres `HandelPierwszy=1 … HandelOstatni=100`) |
| Magazynowy (PW/PZ/WZ/RW/MM/INW…) | `Definicja.Kategoria` w zakresie magazynowym | `PrzyjęcieMagazynowe=102`, `WydanieMagazynowe=104`, `PrzesunięcieMagazynowe=106`, `Inwentaryzacja=107` … (zakres `MagazynPierwszy=101 … MagazynOstatni=200`) |
| Zamówienie (ZO/ZD/wewn.) | `Definicja.Kategoria` | `ZamówienieOdbiorcy=302`, `ZamówienieDostawcy=303`, `ZamówienieWewnętrzne=312` |
| Korekta | flaga `dok.Korekta` **lub** kategoria typu `Korekta*` | `dok.Korekta == true`; kategorie: `KorektaSprzedaży`, `KorektaZakupu`, `KorektaPrzyjęciaMagazynowego`, `KorektaWydaniaMagazynowego` … |
| Dokument zaliczkowy | metoda `dok.JestDokZaliczkowy()` / `dok.JestDokZaliczkowy(out bool korekta)` | `true` = zaliczkowy; `out korekta` = korekta zaliczki |
**Pola i typy:**
- `DokumentHandlowy.Definicja: Soneta.Handel.DefDokHandlowego` — definicja dokumentu.
- `DefDokHandlowego.Kategoria: Soneta.Handel.KategoriaHandlowa`**kluczowy** wyznacznik rodzaju.
- `DokumentHandlowy.Korekta: bool` (kalkulowane, read-only) — czy dokument jest korektą.
- `DokumentHandlowy.JestDokZaliczkowy(): bool` oraz `JestDokZaliczkowy(out bool korekta): bool`
rozpoznanie zaliczki (drugi przeciążony wariant zwraca też, czy to korekta zaliczki).
- `DefDokHandlowego.Symbol: string` — symbol (do logów / komunikatów).
Enum `Soneta.Handel.KategoriaHandlowa` (wartości publiczne) ma czytelne **markery zakresów**:
`HandelPierwszy=1`/`HandelOstatni=100`, `MagazynPierwszy=101`/`MagazynOstatni=200`,
`PozostałePierwszy=301`/`PozostałeOstatni=400`. Pozwalają one rozpoznać „grupę" dokumentu zakresem,
bez wyliczania wszystkich symboli.
**Snippet:**
```csharp
// Rozpoznanie rodzaju dokumentu na podstawie kategorii jego definicji + flag dokumentu.
// KategoriaHandlowa to enum — markery zakresów (HandelPierwszy/Ostatni, MagazynPierwszy/Ostatni)
// pozwalają klasyfikować grupę dokumentu bez wymieniania wszystkich symboli.
static string RozpoznajRodzaj(DokumentHandlowy dok)
{
KategoriaHandlowa kat = dok.Definicja.Kategoria;
// Zaliczka i korekta mają dedykowane, jednoznaczne testy — sprawdzamy je najpierw:
if (dok.JestDokZaliczkowy(out bool korektaZaliczki))
return korektaZaliczki ? "Korekta zaliczki" : "Dokument zaliczkowy";
if (dok.Korekta)
return "Korekta";
// Klasyfikacja grupy po zakresie wartości enuma (markery są publiczne):
return kat switch
{
>= KategoriaHandlowa.HandelPierwszy and <= KategoriaHandlowa.HandelOstatni => "Faktura / dokument handlowy",
>= KategoriaHandlowa.MagazynPierwszy and <= KategoriaHandlowa.MagazynOstatni => "Dokument magazynowy",
KategoriaHandlowa.ZamówienieOdbiorcy
or KategoriaHandlowa.ZamówienieDostawcy
or KategoriaHandlowa.ZamówienieWewnętrzne => "Zamówienie",
_ => "Inny"
};
}
// Przykład użycia — rozgałęzienie logiki po rodzaju:
DokumentHandlowy dok = session.GetHandel().DokHandlowe.WgDaty[
(DokumentHandlowy d) => d.Data == Date.Today].FirstOrDefault();
if (dok != null && dok.Definicja.Kategoria == KategoriaHandlowa.WydanieMagazynowe)
{
// ... logika dotycząca rozchodu magazynowego
}
```
**Pułapki:**
- **Rodzaj wynika z definicji, nie z symbolu.** Symbol (np. „FV") jest dowolny i zależny od bazy —
rozpoznawaj po `Definicja.Kategoria`, a nie po porównaniu `Symbol == "FV"`.
- Pomocnicze metody rozszerzające na enumie (`JestHandlowa`, `JestMagazynowa`, `JestZamowienie`)
**`internal`** — z dodatku zewnętrznego ich nie wywołasz. Klasyfikuj **zakresami markerów**
(`>= HandelPierwszy and <= HandelOstatni` itd.) lub porównaniem do konkretnych wartości — tak jak
w snippetcie.
- Wartości `*Pierwszy`/`*Ostatni` są oznaczone `[Hidden]` (nie pokazują się w UI), ale to **publiczne**
stałe enuma — wolno ich użyć w kodzie jako granic zakresu.
- `Korekta` i wyniki `JestDokZaliczkowy()`**kalkulowane (read-only)** — służą tylko do odczytu;
nie próbuj ich ustawiać. Korektę tworzy się przez relacje dokumentów (`IRelacjeService.NowaKorekta`),
a nie przez przestawienie flagi.
- Sprawdzaj zaliczkę/korektę **przed** klasyfikacją zakresową: korekta sprzedaży nadal mieści się w
zakresie handlowym, a zaliczka bywa fakturą — dedykowane testy (`JestDokZaliczkowy`, `Korekta`)
są bardziej szczegółowe i powinny mieć pierwszeństwo.
- `dok.Definicja` może w teorii być `null` na świeżo utworzonym, jeszcze nieskonfigurowanym
dokumencie — przy klasyfikacji dokumentów „w trakcie tworzenia" zabezpiecz dostęp do `Kategoria`.
---