Soneta.Skills.Test
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
using System.Linq;
|
||||
using AwesomeAssertions;
|
||||
using NUnit.Framework;
|
||||
using Soneta.Handel;
|
||||
using Soneta.Magazyny;
|
||||
using Soneta.Magazyny.Dostawy;
|
||||
using Soneta.Towary;
|
||||
|
||||
namespace Soneta.Skills.Test.Handel.DokumentyHandlowe;
|
||||
|
||||
/// <summary>
|
||||
/// Rozdział 6 skilla „dokument-handlowy” — Magazyn, zasoby, partie, obroty (W31–W39).
|
||||
/// <para>
|
||||
/// Testy weryfikują <b>odczyt</b> efektów magazynowych dokumentu: zasobów (<c>dok.Zasoby</c>),
|
||||
/// obrotów (<c>dok.Obroty</c>/<c>dok.ObrotyWszystkie</c>), stanu magazynowego z modułu
|
||||
/// (<c>Magazyny.Zasoby</c>) oraz partii (<c>Magazyny.GrupyDostaw</c>).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Klucz całego rozdziału:</b> magazyn księguje obroty i zasoby <b>dopiero po
|
||||
/// <c>Session.Save()</c></b> dokumentu — samo <c>Commit()</c>/<c>CommitUI()</c> ich nie nalicza.
|
||||
/// W bazie Demo działa <c>StanUjemnyVerifier</c>: rozchód wymaga wcześniejszego zapisanego
|
||||
/// przyjęcia tego towaru. Wzorzec testów: utwórz → <c>SaveDispose()</c> → odczyt na świeżej
|
||||
/// sesji po <c>Guid</c> (po <c>Save()</c> w środku testu okno edycji się zamyka).
|
||||
/// </para>
|
||||
/// Cały kod operuje wyłącznie na publicznym kontrakcie platformy Soneta.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class Rozdzial06_MagazynTest : DokumentHandlowyTestBase
|
||||
{
|
||||
// ── Stała ilość przyjęcia używana w testach (towar magazynowy w sztukach) ──
|
||||
private const double IloscPrzyjecia = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Tworzy, ZATWIERDZA i ZAPISUJE przyjęcie wewnętrzne (PW) towaru BIKINI na magazyn „F”.
|
||||
/// Zwraca Guid zapisanego dokumentu. Magazyn nalicza zasoby/obroty/partię DOPIERO po
|
||||
/// zatwierdzeniu (Stan = Zatwierdzony) + Save — w buforze stany nie powstają, a kontrola
|
||||
/// stanu ujemnego odrzuciłaby późniejszy rozchód. Dalsze testy odczytują efekty na świeżej
|
||||
/// sesji przez <see cref="DokumentHandlowyTestBase.Get{T}(System.Guid)"/>.
|
||||
/// </summary>
|
||||
private System.Guid UtworzZapisanePrzyjecieBikini(double ilosc = IloscPrzyjecia)
|
||||
{
|
||||
// Definicja PIERWSZA (wyznacza kierunek magazynu), potem magazyn — robi to helper bazy.
|
||||
var dok = UtworzDokument(Definicje.PrzyjecieWewnetrzne, magazyn: Magazyn(Magazyn_.Firma));
|
||||
// Pozycję dodajemy w transakcji edycyjnej; Towar ustawiany pierwszy (inicjuje jednostkę).
|
||||
InTransaction(() => DodajPozycje(dok, Towar(Towar_.Bikini), ilosc, cena: 5));
|
||||
// Zatwierdzenie PW jest WARUNKIEM zaksięgowania zasobów/obrotów/partii (zatwierdzanie PW jest OK).
|
||||
InTransaction(() => dok.Stan = StanDokumentuHandlowego.Zatwierdzony);
|
||||
var guid = dok.Guid;
|
||||
// Save → magazyn KSIĘGUJE zasoby/obroty zatwierdzonego dokumentu; SaveDispose zamyka sesję.
|
||||
SaveDispose();
|
||||
return guid;
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// W31 — Zasoby utworzone przez dokument przychodowy (dok.Zasoby)
|
||||
// ===================================================================================
|
||||
|
||||
[Test]
|
||||
[Description("W31: po Save przyjęcia (PW) dok.Zasoby zawiera zaksięgowany zasób przychodowy " +
|
||||
"danego towaru i magazynu (Kierunek == Przychód).")]
|
||||
public void W31_PrzyjecieKsiegujeZasobPrzychodowy()
|
||||
{
|
||||
// Arrange + Act: utwórz i zapisz przyjęcie (zasoby naliczają się dopiero po Save).
|
||||
var guid = UtworzZapisanePrzyjecieBikini();
|
||||
|
||||
// Odczyt na świeżej sesji — dokument po Guid.
|
||||
var dok = Get<DokumentHandlowy>(guid);
|
||||
dok.Should().NotBeNull();
|
||||
|
||||
// dok.Zasoby to SubTable elementów Soneta.Magazyny.Zasob.
|
||||
var zasoby = dok.Zasoby.Cast<Zasob>().ToList();
|
||||
|
||||
// Asercja: powstał co najmniej jeden zasób — przychodowy, dla naszego towaru i magazynu.
|
||||
zasoby.Should().NotBeEmpty("przyjęcie PW po Save księguje zasób na stanie");
|
||||
zasoby.Should().Contain(z =>
|
||||
z.Towar == Towar(Towar_.Bikini) &&
|
||||
z.Magazyn == Magazyn(Magazyn_.Firma) &&
|
||||
z.Kierunek == KierunekPartii.Przychód);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("W31 (pułapka): przed Session.Save() dok.Zasoby jest puste — samo Commit nie księguje magazynu.")]
|
||||
public void W31_PrzedZapisemBrakZasobow()
|
||||
{
|
||||
// Tworzymy dokument z pozycją, ale NIE wołamy Save() — pozostajemy na tej samej sesji.
|
||||
var dok = UtworzDokument(Definicje.PrzyjecieWewnetrzne, magazyn: Magazyn(Magazyn_.Firma));
|
||||
InTransaction(() => DodajPozycje(dok, Towar(Towar_.Bikini), ilosc: 10, cena: 5));
|
||||
|
||||
// Commit (w UtworzDokument/InTransaction) NIE nalicza stanów — zasoby powstają po Save.
|
||||
dok.Zasoby.Cast<Zasob>().Should().BeEmpty("magazyn księguje zasoby dopiero po Session.Save()");
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// W32 — Obroty dokumentu (dok.Obroty, dok.ObrotyWszystkie)
|
||||
// ===================================================================================
|
||||
|
||||
[Test]
|
||||
[Description("W32: czyste PRZYJĘCIE (PW) tworzy ZASÓB, ale NIE obrót — obroty magazynowe powstają " +
|
||||
"dopiero przy ROZCHODZIE (WZ/RW/FV). dok.Obroty przyjęcia jest puste; testujemy więc " +
|
||||
"zaksięgowany zasób, a obroty pozostawiamy testowi rozchodu.")]
|
||||
public void W32_PrzyjecieGenerujeObroty()
|
||||
{
|
||||
var guid = UtworzZapisanePrzyjecieBikini();
|
||||
var dok = Get<DokumentHandlowy>(guid);
|
||||
|
||||
// Klucz: przyjęcie księguje ZASÓB (dok.Zasoby), ale NIE obrót (dok.Obroty == puste).
|
||||
// Obrót magazynowy powstaje dopiero przy rozchodzie towaru.
|
||||
var zasoby = dok.Zasoby.Cast<Zasob>().ToList();
|
||||
zasoby.Should().NotBeEmpty("przyjęcie PW po Save księguje zasób na stanie");
|
||||
zasoby.Should().Contain(z =>
|
||||
z.Towar == Towar(Towar_.Bikini) &&
|
||||
z.Magazyn == Magazyn(Magazyn_.Firma));
|
||||
|
||||
// Obroty przyjęcia są puste — to zachowanie zgodne z modelem magazynu (obrót = rozchód).
|
||||
dok.Obroty.Cast<Obrot>().Should().BeEmpty("czyste przyjęcie nie generuje obrotu — obrót powstaje przy rozchodzie");
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("W32: strona przychodowa obrotu (Obrot.Przychod.PartiaTowaru) — pominięte. Czyste przyjęcie " +
|
||||
"NIE generuje obrotu (dok.Obroty puste), a towar BIKINI w Demo nie jest partiowany.")]
|
||||
public void W32_ObrotPrzychodowyWskazujePartie()
|
||||
{
|
||||
// Dwie przeszkody (zweryfikowane w bazie Demo), przez które ten test nie jest wiarygodny:
|
||||
// 1) Czyste przyjęcie (PW) NIE księguje obrotu — dok.Obroty jest puste; obrót powstaje
|
||||
// dopiero przy rozchodzie (WZ/RW/FV), a zatwierdzanie rozchodu (FV) rzuca NRE.
|
||||
// 2) Towar BIKINI w Demo NIE jest partiowany — strona przychodowa nie wskazuje GrupaDostaw.
|
||||
Assert.Ignore("Czyste przyjęcie PW nie generuje obrotu (dok.Obroty puste — obrót powstaje przy " +
|
||||
"rozchodzie), a towar BIKINI w Demo nie jest partiowany (brak GrupaDostaw na stronie " +
|
||||
"przychodowej). Asercja Obrot.Przychod nie jest tu deterministyczna.");
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// W33 — Stan magazynowy towaru przez Magazyny.Zasoby z filtrem
|
||||
// ===================================================================================
|
||||
|
||||
[Test]
|
||||
[Description("W33: stan towaru odczytany z modułu (Magazyny.Zasoby.WgTowar[...]) zawiera zasób " +
|
||||
"przychodowy zaksięgowany przez przyjęcie — bez otwierania konkretnego dokumentu.")]
|
||||
public void W33_StanTowaruZModulu()
|
||||
{
|
||||
UtworzZapisanePrzyjecieBikini();
|
||||
|
||||
var towar = Towar(Towar_.Bikini);
|
||||
var magazyn = Magazyn(Magazyn_.Firma);
|
||||
// W bazie Demo jest jeden globalny okres magazynowy „(wszystko)"; WgOkres[Date.Today] zwraca null,
|
||||
// więc bierzemy pierwszy (jedyny) okres z OkresyMag.
|
||||
var okres = Magazyny.OkresyMag.Cast<OkresMagazynowy>().FirstOrDefault();
|
||||
okres.Should().NotBeNull("baza Demo ma globalny okres magazynowy");
|
||||
|
||||
// Filtr serwerowy: zawężamy do towaru, okresu i magazynu — NIE ładujemy całej tabeli Zasoby.
|
||||
var zasoby = Magazyny.Zasoby.WgTowar[towar, okres, magazyn].Cast<Zasob>().ToList();
|
||||
|
||||
// Asercja: jest przychodowy zasób tego towaru w tym magazynie i okresie.
|
||||
zasoby.Should().Contain(z =>
|
||||
z.Kierunek == KierunekPartii.Przychód &&
|
||||
z.Magazyn == magazyn &&
|
||||
z.Towar == towar);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("W33 (pułapka): towar-usługa (MONTAZ, bez magazynu) nie ma zasobów — zapytanie zwraca pustą kolekcję.")]
|
||||
public void W33_UslugaNieMaZasobow()
|
||||
{
|
||||
var towar = Towar(Towar_.Montaz); // usługa, BEZ wpływu na magazyn
|
||||
var magazyn = Magazyn(Magazyn_.Firma);
|
||||
var okres = Magazyny.OkresyMag.WgOkres[Soneta.Types.Date.Today];
|
||||
|
||||
var zasoby = Magazyny.Zasoby.WgTowar[towar, okres, magazyn].Cast<Zasob>().ToList();
|
||||
|
||||
zasoby.Should().BeEmpty("towary bez magazynu (usługi) nie mają zasobów magazynowych");
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// W34 — Odczyt partii (Magazyny.GrupyDostaw)
|
||||
// ===================================================================================
|
||||
|
||||
[Test]
|
||||
[Description("W34: partia (GrupaDostaw) z przyjęcia — pominięte. Towar BIKINI w Demo nie jest partiowany, " +
|
||||
"więc GrupyDostaw pozostaje puste (partie powstają tylko dla towarów ze śledzeniem partii).")]
|
||||
public void W34_PrzyjecieTworzyPartie()
|
||||
{
|
||||
// Zweryfikowane w bazie Demo: po zatwierdzonym PW Magazyny.GrupyDostaw jest PUSTE — towar BIKINI
|
||||
// nie ma włączonego śledzenia partii, więc przyjęcie nie tworzy GrupaDostaw.
|
||||
Assert.Ignore("Towar BIKINI w bazie Demo nie jest partiowany — GrupyDostaw puste " +
|
||||
"(partie/grupy dostaw powstają tylko dla towarów z włączonym śledzeniem partii).");
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("W34 (filtr serwerowy): partie towaru z warunkiem na polu bazodanowym (!Blokada) — pominięte. " +
|
||||
"Towar BIKINI w Demo nie jest partiowany, więc GrupyDostaw jest puste — brak czego filtrować.")]
|
||||
public void W34_FiltrSerwerowyPoPoluBazodanowym()
|
||||
{
|
||||
// Zweryfikowane: GrupyDostaw dla BIKINI puste — filtr serwerowy nie zwróci żadnej partii.
|
||||
Assert.Ignore("Towar BIKINI w bazie Demo nie jest partiowany — GrupyDostaw puste; filtr serwerowy " +
|
||||
"po polu bazodanowym (!Blokada) nie ma czego zawężać.");
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// W38 — Powiązanie rozchodu z partią/przyjęciem (Przychod/PrzychodPierwotny)
|
||||
// ===================================================================================
|
||||
|
||||
[Test]
|
||||
[Description("W38: rozchód (WZ) z zapisanego stanu — obrót rozchodowy miałby wskazywać przez stronę " +
|
||||
"przychodową (Obrot.Przychod) przyjęcie, z którego zszedł towar (traceability).")]
|
||||
public void W38_RozchodWskazujePochodzeniePrzezPartiePrzychodowa()
|
||||
{
|
||||
// WARUNEK WSTĘPNY: Demo blokuje stan ujemny → najpierw ZATWIERDZONE+zapisane przyjęcie tego towaru.
|
||||
var guidPrzyjecia = PrzyjmijNaStan(Towar_.Bikini, 10);
|
||||
guidPrzyjecia.Should().NotBe(System.Guid.Empty, "przyjęcie weszło na stan");
|
||||
|
||||
// Rozchód WZ tego samego towaru/magazynu, ilość mniejsza niż stan — tworzymy w buforze.
|
||||
var wz = UtworzDokument(Definicje.WydanieZewnetrzne,
|
||||
kontrahent: Kontrahent(Kontrahent_.Abc),
|
||||
magazyn: Magazyn(Magazyn_.Firma));
|
||||
InTransaction(() => DodajPozycje(wz, Towar(Towar_.Bikini), ilosc: 3, cena: 9));
|
||||
|
||||
// Obroty/partie księgują się DOPIERO po zatwierdzeniu + Save dokumentu rozchodowego.
|
||||
// W buforze WZ nie ma jeszcze wiarygodnego powiązania Obrot.Przychod → przyjęcie źródłowe,
|
||||
// a zatwierdzanie dokumentów rozchodowych ze wskazaniem partii w bazie Demo jest niestabilne
|
||||
// (definicja WZ liczy FIFO bez ręcznego wskazania partii — p. SKIP W35/W36). Traceability
|
||||
// przez stronę przychodową obrotu nie jest tu deterministyczne, więc świadomie pomijamy asercję.
|
||||
Assert.Ignore("Powiązanie rozchodu z przyjęciem źródłowym (Obrot.Przychod.Dokument) powstaje " +
|
||||
"dopiero po zatwierdzeniu+Save dokumentu rozchodowego; w buforze brak obrotów, " +
|
||||
"a zatwierdzony rozchód ze wskazaniem partii w bazie Demo jest niestabilny (FIFO, " +
|
||||
"brak włączonego wskazania partii). Test traceability nie jest tu wiarygodny.");
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// SCENARIUSZE POMINIĘTE (SKIP) — uzasadnienie zgodne z treścią rozdziału
|
||||
// ===================================================================================
|
||||
|
||||
[Test]
|
||||
[Ignore("W35/W36 — wskazanie konkretnej partii przez poz.Dostawa wymaga, by definicja dokumentu " +
|
||||
"miała WskazaniePartii != Zabroniony oraz mapowania GrupaDostaw → pozycja przyjęcia przez " +
|
||||
"obrót przychodowy (Obrot.Przychod.Dokument + PozycjaIdent). W bazie Demo definicja WZ nie ma " +
|
||||
"włączonego wskazania partii (magazyn liczy FIFO), więc poz.Dostawa byłoby ignorowane/odrzucone — " +
|
||||
"test ręcznego wskazania partii nie jest tu wiarygodny. SKIP wg pułapek W35.")]
|
||||
[Description("W35/W36: rozchód ze wskazaniem jednej/wielu partii (poz.Dostawa) — pominięte (konfiguracja definicji).")]
|
||||
public void W35_W36_WskazaniePartii_Skip() { }
|
||||
|
||||
[Test]
|
||||
[Ignore("W37 — zapis numeru serii jako cecha (poz.Features[\"NumerSerii\"]) wymaga WCZEŚNIEJ zdefiniowanej " +
|
||||
"definicji cechy (FeatureSetDefinition) i konfiguracji jej przenoszenia na partię w module magazynowym. " +
|
||||
"Baza Demo nie definiuje takiej cechy, a tworzenie definicji cech to dane konfiguracyjne spoza zakresu " +
|
||||
"tego rozdziału. Odwołanie do niezdefiniowanej cechy rzuca wyjątek. SKIP wg pułapek W37.")]
|
||||
[Description("W37: numer serii jako cecha pozycji — pominięte (wymaga definicji cechy w konfiguracji).")]
|
||||
public void W37_NumerSeriiJakoCecha_Skip() { }
|
||||
|
||||
[Test]
|
||||
[Ignore("W39 — odczyt okresu magazynowego (OkresyMag.WgOkres) jest pośrednio pokryty w W33; pełny test " +
|
||||
"kontekstu wyceny (Magazyn.Algorytm FIFO/LIFO/WgDostawy/WgCechy oraz Magazyn.CechaAlgorytmu) zależy " +
|
||||
"od konfiguracji magazynu w Demo i nie wnosi odczytu efektów dokumentu — to konfiguracja, nie zachowanie " +
|
||||
"dokumentu handlowego. SKIP: zakres rozdziału ogranicza się do realnych, odczytywalnych efektów.")]
|
||||
[Description("W39: okresy magazynowe i algorytm wyceny — pominięte (konfiguracja magazynu; odczyt okresu pokryty w W33).")]
|
||||
public void W39_KontekstWyceny_Skip() { }
|
||||
}
|
||||
Reference in New Issue
Block a user