Files

13 KiB

HANDEL01 — Fundamenty i identyfikacja

Wspólne fakty o typie, podstawowe typy i szablon wzorca: ../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, session-login.md oraz 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 ISessionableRow, 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:

// 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:

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.KategoriaHandlowakluczowy 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:

// 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()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.