using System.Linq; using AwesomeAssertions; using NUnit.Framework; using Soneta.Handel; using Soneta.Types; namespace Soneta.Skills.Test.Handel.DokumentyHandlowe; /// /// Rozdział 1 — „Fundamenty i identyfikacja” (W1–W3) dokumentu handlowego. /// Testy pełnią podwójną rolę: weryfikują publiczny kontrakt platformy ORAZ stanowią dokumentację /// poprawnych wzorców kodu dla programisty dodatku zewnętrznego. Pokrywają: /// /// W1 — dostęp z sesji do modułów handlowo-magazynowych (Handel/Magazyny/Towary/CRM) /// oraz do tabeli dokumentów DokHandlowe; /// W2 — wybór definicji dokumentu (DefDokHandlowego) po symbolu (klucz unikalny); /// W3 — rozpoznanie rodzaju dokumentu (faktura / magazynowy / zamówienie / korekta / zaliczka) /// wg Definicja.Kategoria oraz flag dokumentu. /// /// Wszystko operuje wyłącznie na publicznym kontrakcie — tak jak dodatek bez dostępu do kodu źródłowego. /// [TestFixture] public class Rozdzial01_FundamentyTest : DokumentHandlowyTestBase { // ============================================================================================ // W1 — Dostęp do modułów handlowo-magazynowych i tabeli DokHandlowe // ============================================================================================ [Test] [Description("W1: z sesji dostępne są wszystkie cztery moduły (Handel, Magazyny, Towary, CRM) " + "i każdy wskazuje z powrotem na tę samą sesję (ISessionable.Session).")] public void W1_DostepDoModulow_ModulyDostepneIWskazujaNaSesje() { // Punkt wejścia każdego scenariusza: z Session pobieramy moduły metodami rozszerzającymi. // Helpery bazy (Handel/Magazyny/Towary/Crm) opakowują session.GetHandel()/GetMagazyny() itd. Handel.Should().NotBeNull("session.GetHandel() musi zwrócić moduł Handel"); Magazyny.Should().NotBeNull("session.GetMagazyny() musi zwrócić moduł Magazyny"); Towary.Should().NotBeNull("session.GetTowary() musi zwrócić moduł Towary"); Crm.Should().NotBeNull("session.GetCRM() musi zwrócić moduł CRM"); // Każdy moduł implementuje ISessionable — property Session zamyka pętlę dostępu do danych. Handel.Session.Should().BeSameAs(Session); Magazyny.Session.Should().BeSameAs(Session); Towary.Session.Should().BeSameAs(Session); Crm.Session.Should().BeSameAs(Session); } [Test] [Description("W1: moduł Handel udostępnia tabelę dokumentów DokHandlowe oraz tabelę definicji " + "DefDokHandlowych — to dwa podstawowe punkty dostępu do danych handlowych.")] public void W1_ModulHandel_UdostepniaTabeleDokumentowIDefinicji() { // DokHandlowe — operacyjna tabela dokumentów (faktur, magazynowych, zamówień...). // DefDokHandlowych — konfiguracyjna tabela definicji wyznaczających rodzaj dokumentu. Handel.DokHandlowe.Should().NotBeNull("tabela dokumentów handlowych musi istnieć w module"); Handel.DefDokHandlowych.Should().NotBeNull("tabela definicji dokumentów musi istnieć w module"); // Obie tabele należą do tej samej sesji co moduł (spójność kontekstu danych). Handel.DokHandlowe.Session.Should().BeSameAs(Session); } [Test] [Description("W1: tabelę DokHandlowe iterujemy ZAWSZE z zawężeniem zakresu (filtr serwerowy na " + "indeksie WgDaty), zamiast ładować całą rosnącą tabelę operacyjną do pamięci.")] public void W1_IteracjaDokumentow_FiltrSerwerowyPoDacie_NieRzucaIDziala() { // Wzorzec safe-code: warunek RowCondition aplikujemy na indeksie (wykona się po stronie SQL). // W warunku używamy wyłącznie pól bazodanowych (Data) — pole kalkulowane rzuciłoby wyjątek. var od = Date.Today.AddMonths(-1); // Sama materializacja zapytania (Count) potwierdza, że filtr serwerowy jest poprawny składniowo // i wykonalny; nie zakładamy konkretnej liczby dokumentów w bazie Demo (fakt niestabilny). var liczba = Handel.DokHandlowe .WgDaty[(DokumentHandlowy x) => x.Data >= od] .Count(); liczba.Should().BeGreaterThanOrEqualTo(0, "filtr serwerowy powinien się wykonać bez błędu"); } // ============================================================================================ // W2 — Wybór definicji dokumentu (DefDokHandlowego) wg symbolu // ============================================================================================ [Test] [Description("W2: WgSymbolu to indeks UNIKALNY — dla istniejącego symbolu (FV) zwraca pojedynczy " + "rekord, którego Symbol odpowiada żądanemu (lookup symboli jest spójny).")] public void W2_DefinicjaPoSymbolu_KluczUnikalny_ZwracaRekordOZgodnymSymbolu() { // WgSymbolu["FV"] — klucz unikalny: zwraca pojedynczy DefDokHandlowego albo null. var defFV = Definicja(Definicje.FakturaSprzedazy); defFV.Should().NotBeNull("baza Demo zawiera definicję faktury sprzedaży o symbolu FV"); defFV.Symbol.Should().Be(Definicje.FakturaSprzedazy, "indeks WgSymbolu musi zwrócić rekord o dokładnie tym symbolu"); } [Test] [Description("W2: dla symbolu NIEISTNIEJĄCEGO indeks unikalny WgSymbolu zwraca null — to sygnał " + "do walidacji przed utworzeniem dokumentu (nie zakładaj obecności symbolu na sztywno).")] public void W2_DefinicjaPoNieistniejacymSymbolu_ZwracaNull() { // Symbole zależą od konfiguracji bazy — zawsze sprawdzaj != null przed użyciem. var brak = Definicja("NIE_ISTNIEJE_XYZ"); brak.Should().BeNull("dla nieznanego symbolu klucz unikalny zwraca null, nie wyjątek"); } [Test] [Description("W2: definicja jest PIERWSZYM polem nowego dokumentu — po jej ustawieniu dokument " + "ma przypisaną definicję o oczekiwanym symbolu (UtworzDokument ustawia ją jako pierwszą).")] public void W2_UtworzenieDokumentu_DefinicjaUstawionaJakoPierwszaJestPrzypisana() { // Kolejność z helpera UtworzDokument: AddRow -> Definicja (pierwsza) -> Magazyn -> Kontrahent. // Tu sprawdzamy sam fakt poprawnego przypisania definicji do świeżego dokumentu. var dok = UtworzDokument(Definicje.PrzyjecieWewnetrzne, magazyn: Magazyn(Magazyn_.Firma)); dok.Should().NotBeNull(); dok.Definicja.Should().NotBeNull("definicja musi być ustawiona jako pierwsze pole dokumentu"); dok.Definicja.Symbol.Should().Be(Definicje.PrzyjecieWewnetrzne); } [Test] [Description("W2: ten sam rekord definicji jest osiągalny z dwóch dróg — bezpośrednio z tabeli " + "definicji (WgSymbolu) oraz przez utworzony dokument (dok.Definicja) — to jeden obiekt.")] public void W2_DefinicjaDokumentu_TozsamaZRekordemZTabeliDefinicji() { // Tożsamość referencyjna potwierdza, że dok.Definicja wskazuje rekord z tabeli DefDokHandlowych, // a nie kopię — kluczowe dla rozpoznawania rodzaju dokumentu po Definicja.Kategoria (W3). var defPW = Definicja(Definicje.PrzyjecieWewnetrzne); var dok = UtworzDokument(Definicje.PrzyjecieWewnetrzne, magazyn: Magazyn(Magazyn_.Firma)); dok.Definicja.Should().BeSameAs(defPW, "definicja dokumentu to ten sam rekord co pobrany z tabeli definicji"); } // ============================================================================================ // W3 — Rozpoznanie rodzaju dokumentu (kategoria + flagi) // ============================================================================================ [Test] [Description("W3: definicja faktury sprzedaży (FV) ma kategorię w zakresie HANDLOWYM " + "(HandelPierwszy..HandelOstatni) — rodzaj rozpoznajemy po zakresie kategorii, nie po symbolu.")] public void W3_FakturaSprzedazy_KategoriaWZakresieHandlowym() { // Rozpoznanie rodzaju opieramy na Definicja.Kategoria, NIE na porównaniu Symbol == "FV" // (symbol jest dowolny i zależny od bazy). Markery zakresów enuma są publiczne. var defFV = Definicja(Definicje.FakturaSprzedazy); defFV.Should().NotBeNull(); var kat = defFV.Kategoria; kat.Should().BeOneOf(KategoriaHandlowa.Sprzedaż, KategoriaHandlowa.KorektaSprzedaży); WCzyZakresie(kat, KategoriaHandlowa.HandelPierwszy, KategoriaHandlowa.HandelOstatni) .Should().BeTrue("kategoria faktury mieści się w zakresie handlowym"); } [Test] [Description("W3: definicje dokumentów magazynowych (PW/PZ/WZ/RW) mają kategorie w zakresie " + "MAGAZYNOWYM (MagazynPierwszy..MagazynOstatni) — rozpoznanie grupy zakresem markerów.")] public void W3_DokumentyMagazynowe_KategorieWZakresieMagazynowym() { // Klasyfikacja „grupy” dokumentu po zakresie wartości enuma — bez wyliczania wszystkich symboli. foreach (var symbol in new[] { Definicje.PrzyjecieWewnetrzne, Definicje.PrzyjecieZewnetrzne, Definicje.WydanieZewnetrzne, Definicje.RozchodWewnetrzny }) { var def = Definicja(symbol); def.Should().NotBeNull($"baza Demo zawiera definicję magazynową {symbol}"); WCzyZakresie(def.Kategoria, KategoriaHandlowa.MagazynPierwszy, KategoriaHandlowa.MagazynOstatni) .Should().BeTrue($"kategoria dokumentu {symbol} ma być w zakresie magazynowym"); } } [Test] [Description("W3: definicje zamówień (ZO/ZD) mają kategorie zamówień (ZamówienieOdbiorcy/" + "ZamówienieDostawcy) — leżą poza zakresami handlowym i magazynowym.")] public void W3_Zamowienia_RozpoznawaneJakoKategorieZamowien() { var defZO = Definicja(Definicje.ZamowienieOdbiorcy); var defZD = Definicja(Definicje.ZamowienieDoDostawcy); defZO.Should().NotBeNull(); defZD.Should().NotBeNull(); // Zamówienie to ani dokument handlowy (faktura), ani magazynowy — własna grupa kategorii. defZO.Kategoria.Should().Be(KategoriaHandlowa.ZamówienieOdbiorcy); defZD.Kategoria.Should().Be(KategoriaHandlowa.ZamówienieDostawcy); WCzyZakresie(defZO.Kategoria, KategoriaHandlowa.HandelPierwszy, KategoriaHandlowa.HandelOstatni) .Should().BeFalse("zamówienie nie należy do zakresu handlowego (faktur)"); WCzyZakresie(defZO.Kategoria, KategoriaHandlowa.MagazynPierwszy, KategoriaHandlowa.MagazynOstatni) .Should().BeFalse("zamówienie nie należy do zakresu magazynowego"); } [Test] [Description("W3: pełna klasyfikacja rodzaju przez funkcję rozgałęziającą po zakresie kategorii — " + "FV→handlowy, PW/WZ→magazynowy, ZO→zamówienie (wzorzec z dokumentacji rozdziału).")] public void W3_RozpoznajRodzaj_ZwracaPoprawnaGrupeDlaKazdejDefinicji() { // Wzorzec RozpoznajRodzaj klasyfikuje dokument po Definicja.Kategoria zakresami markerów. RozpoznajRodzaj(UtworzDokument(Definicje.FakturaSprzedazy, kontrahent: Kontrahent(Kontrahent_.Abc))) .Should().Be(RodzajDokumentu.Handlowy); RozpoznajRodzaj(UtworzDokument(Definicje.PrzyjecieWewnetrzne, magazyn: Magazyn(Magazyn_.Firma))) .Should().Be(RodzajDokumentu.Magazynowy); RozpoznajRodzaj(UtworzDokument(Definicje.WydanieZewnetrzne, magazyn: Magazyn(Magazyn_.Firma))) .Should().Be(RodzajDokumentu.Magazynowy); RozpoznajRodzaj(UtworzDokument(Definicje.ZamowienieOdbiorcy, kontrahent: Kontrahent(Kontrahent_.Abc))) .Should().Be(RodzajDokumentu.Zamowienie); } [Test] [Description("W3: świeżo utworzony zwykły dokument (nie z relacji korekty) ma flagę Korekta=false — " + "korektę tworzy się przez relacje dokumentów, a nie przez przestawienie flagi.")] public void W3_ZwyklyDokument_FlagaKorektaFalsz() { // dok.Korekta rozpoznaje korektę. Zwykły dokument utworzony „od zera” nie jest korektą. var dok = UtworzDokument(Definicje.PrzyjecieWewnetrzne, magazyn: Magazyn(Magazyn_.Firma)); dok.Korekta.Should().BeFalse("dokument utworzony od zera (nie z relacji) nie jest korektą"); } [Test] [Description("W3: zwykły dokument (faktura/magazynowy/zamówienie) nie jest dokumentem zaliczkowym — " + "flaga rozpoznająca zaliczkę jest false dla dokumentów utworzonych bez powiązania zaliczki.")] public void W3_ZwyklyDokument_NieJestZaliczkowy() { // Rozpoznanie zaliczki ma pierwszeństwo przed klasyfikacją zakresową (zaliczka bywa fakturą), // ale zwykły dokument utworzony od zera zaliczką nie jest. var faktura = UtworzDokument(Definicje.FakturaSprzedazy, kontrahent: Kontrahent(Kontrahent_.Abc)); var magazynowy = UtworzDokument(Definicje.PrzyjecieWewnetrzne, magazyn: Magazyn(Magazyn_.Firma)); faktura.JestZaliczkowy.Should().BeFalse("zwykła faktura sprzedaży nie jest dokumentem zaliczkowym"); magazynowy.JestZaliczkowy.Should().BeFalse("dokument magazynowy nie jest dokumentem zaliczkowym"); } // ============================================================================================ // Pomocnicze (wzorce klasyfikacji z dokumentacji rozdziału) // ============================================================================================ /// Grupa rodzajowa dokumentu rozpoznana po kategorii jego definicji. private enum RodzajDokumentu { Handlowy, Magazynowy, Zamowienie, Inny } /// /// Klasyfikacja rodzaju dokumentu po Definicja.Kategoria z użyciem publicznych markerów /// zakresów enuma — odzwierciedla wzorzec ze snippetu rozdziału. /// private static RodzajDokumentu RozpoznajRodzaj(DokumentHandlowy dok) { // Definicja może być null na świeżo nieskonfigurowanym dokumencie — zabezpieczamy dostęp. if (dok.Definicja == null) return RodzajDokumentu.Inny; var kat = dok.Definicja.Kategoria; return kat switch { >= KategoriaHandlowa.HandelPierwszy and <= KategoriaHandlowa.HandelOstatni => RodzajDokumentu.Handlowy, >= KategoriaHandlowa.MagazynPierwszy and <= KategoriaHandlowa.MagazynOstatni => RodzajDokumentu.Magazynowy, KategoriaHandlowa.ZamówienieOdbiorcy or KategoriaHandlowa.ZamówienieDostawcy or KategoriaHandlowa.ZamówienieWewnętrzne => RodzajDokumentu.Zamowienie, _ => RodzajDokumentu.Inny }; } /// Sprawdza, czy kategoria mieści się w zakresie [od, do] (markery zakresów enuma). private static bool WCzyZakresie(KategoriaHandlowa kat, KategoriaHandlowa od, KategoriaHandlowa gora) => kat >= od && kat <= gora; }