226 lines
13 KiB
Markdown
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`)
|
|
są **`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()` są **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`.
|
|
|
|
---
|
|
|