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;
///
/// Rozdział 6 skilla „dokument-handlowy” — Magazyn, zasoby, partie, obroty (W31–W39).
///
/// Testy weryfikują odczyt efektów magazynowych dokumentu: zasobów (dok.Zasoby),
/// obrotów (dok.Obroty/dok.ObrotyWszystkie), stanu magazynowego z modułu
/// (Magazyny.Zasoby) oraz partii (Magazyny.GrupyDostaw).
///
///
/// Klucz całego rozdziału: magazyn księguje obroty i zasoby dopiero po
/// Session.Save() dokumentu — samo Commit()/CommitUI() ich nie nalicza.
/// W bazie Demo działa StanUjemnyVerifier: rozchód wymaga wcześniejszego zapisanego
/// przyjęcia tego towaru. Wzorzec testów: utwórz → SaveDispose() → odczyt na świeżej
/// sesji po Guid (po Save() w środku testu okno edycji się zamyka).
///
/// Cały kod operuje wyłącznie na publicznym kontrakcie platformy Soneta.
///
[TestFixture]
public class Rozdzial06_MagazynTest : DokumentHandlowyTestBase
{
// ── Stała ilość przyjęcia używana w testach (towar magazynowy w sztukach) ──
private const double IloscPrzyjecia = 10;
///
/// 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 .
///
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(guid);
dok.Should().NotBeNull();
// dok.Zasoby to SubTable elementów Soneta.Magazyny.Zasob.
var zasoby = dok.Zasoby.Cast().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().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(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().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().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().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().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().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() { }
}