SKILL: Uporządkowanie skills domenowych - podział na mniejsze pliki i wspólna numeracja
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
# 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`.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user