using System;
using System.Linq;
using AwesomeAssertions;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Soneta.Handel;
using Soneta.Handel.RelacjeDokumentow.Api;
using Soneta.Magazyny;
using Soneta.Towary;
using Soneta.Types;
namespace Soneta.Skills.Test.Handel.DokumentyHandlowe;
///
/// Rozdział 9 skilla „dokument-handlowy” — Korekty i dokumenty specjalne (W48–W52).
///
/// Rozdział obejmuje korekty (przez serwis relacji .NowaKorekta),
/// inwentaryzację (INW) oraz przesunięcie międzymagazynowe (MM). Wszystkie testy operują
/// wyłącznie na publicznym kontrakcie platformy — jak dodatek programisty zewnętrznego.
///
///
/// Reguły wspólne (zob. dokumentacja, rozdz. 9 oraz safe-code.md):
///
/// - dokument korygowany / nadrzędny musi być zatwierdzony przed wywołaniem relacji,
/// - relacja to operacja modyfikująca — wykonujemy ją w transakcji edycyjnej
/// (Session.Logout(editMode: true)), po niej Session.Save(),
/// - magazyn księguje obroty/zasoby dopiero po Session.Save(), nie po Commit(),
/// - Demo blokuje stan ujemny (StanUjemnyVerifier) — rozchód wymaga wcześniejszego,
/// zapisanego przyjęcia (PW) tego towaru,
/// - pola DokumentKorygowany, DokumentyKorygujące są kalkulowane (read-only) —
/// czytamy je, nie ustawiamy; powstają jako efekt utworzenia relacji.
///
///
/// Tam, gdzie definicja relacji w Demo wymaga rozstrzygnięcia niedostarczalnego czystym
/// publicznym API (np. callback w HandlerSet), test rozpoznaje
/// i jest pomijany (Assert.Ignore) z czytelnym powodem —
/// to nie błąd testu, lecz ograniczenie kontraktu/konfiguracji.
///
[TestFixture]
public class Rozdzial09_KorektyTest : DokumentHandlowyTestBase
{
// === Pomocnicze ===
/// Serwis relacji bieżącej sesji (rzuca, gdy serwisu brak).
private IRelacjeService Relacje => Session.GetRequiredService();
/// Zmienia stan dokumentu na zatwierdzony (w transakcji edycyjnej).
private void Zatwierdz(DokumentHandlowy dok)
{
InTransaction(() => dok.Stan = StanDokumentuHandlowego.Zatwierdzony);
}
///
/// Wprowadza towar magazynowy na stan: tworzy i ZAPISUJE przyjęcie wewnętrzne (PW).
/// Magazyn księguje się dopiero po Session.Save() — warunek konieczny rozchodu (Demo blokuje stan ujemny).
/// Save bez Dispose: kontynuujemy pracę na tej samej sesji.
///
private void WprowadzNaStan(string towarKod, double ilosc)
{
var pw = UtworzDokument(Definicje.PrzyjecieWewnetrzne, magazyn: Magazyn(Magazyn_.Firma));
InTransaction(() => DodajPozycje(pw, Towar(towarKod), ilosc, cena: 10));
Zatwierdz(pw);
Session.Save(); // księguje zasób
}
// ===================================================================================
// W48 — Korekta ilościowa / ceny przez IRelacjeService.NowaKorekta
// ===================================================================================
[Test]
[Description("W48: do zatwierdzonej faktury sprzedaży (FV) tworzy dokument korygujący przez " +
"IRelacjeService.NowaKorekta; sprawdza powstanie korekty oraz powiązanie " +
"korekta.DokumentKorygowany == oryginał i obecność oryginału w fv.DokumentyKorygujące.")]
public void W48_NowaKorekta_DoZatwierdzonejFv_TworzyDokumentKorygujacy()
{
// Mechanika NowaKorekta jest udokumentowana (rozdz. 9), lecz scenariusz wymaga ZATWIERDZONEJ
// faktury sprzedaży, a zatwierdzenie FV w testowej bazie Demo rzuca NRE w ewidencji VAT.
// Korekta nie da się przeprowadzić end-to-end w teście jednostkowym.
Assert.Ignore("korekta wymaga zatwierdzonej FV; zatwierdzenie FV w testowej bazie Demo rzuca NRE " +
"w ewidencji VAT — niewykonalne w teście jednostkowym");
}
[Test]
[Description("W48 (pułapka): NowaKorekta zwraca tablicę DokumentHandlowy[]; dla jednego dokumentu " +
"wynik ma dokładnie jeden element (relacja indywidualna).")]
public void W48_NowaKorekta_ZwracaTabliceZJednymElementem()
{
// Jak wyżej: NowaKorekta wymaga ZATWIERDZONEJ FV, a zatwierdzenie FV w testowej bazie Demo
// rzuca NRE w ewidencji VAT — wywołanie relacji jest tu niewykonalne.
Assert.Ignore("korekta wymaga zatwierdzonej FV; zatwierdzenie FV w testowej bazie Demo rzuca NRE " +
"w ewidencji VAT — niewykonalne w teście jednostkowym");
}
// ===================================================================================
// W50 — Dokument inwentaryzacji (INW)
// ===================================================================================
[Test]
[Description("W50: tworzy dokument inwentaryzacji (INW) ze wskazanym magazynem i pozycją spisu; " +
"sprawdza, że dokument powstał z poprawną definicją i magazynem. Wyliczanie różnic " +
"(nadwyżka/strata) jest efektem zatwierdzenia + Save i nie jest tu asercjonowane.")]
public void W50_Inwentaryzacja_TworzyDokumentZeWskazanymMagazynem()
{
// Tworzenie INW w JEDNEJ transakcji edycyjnej — bez wcześniejszego Session.Save()
// (poprzedni Save zamykał okno edycji bieżącej sesji → AccessWriteDenied przy edycji nowego INW, §8).
// Asercje ograniczone do faktów strukturalnych: definicja INW i wskazany magazyn „F”.
// Definicja PIERWSZA (wyznacza zachowanie dokumentu), potem magazyn inwentaryzowany.
DokumentHandlowy inw = null;
try
{
InTransaction(() =>
{
inw = new DokumentHandlowy();
Session.AddRow(inw);
inw.Definicja = Definicja(Definicje.Inwentaryzacja); // INW
inw.Magazyn = Magazyn(Magazyn_.Firma); // inwentaryzowany magazyn (wymagany)
});
}
catch (NotImplementedException ex)
{
// Gdyby utworzenie/zatwierdzenie INW w Demo wymagało specjalnej procedury niedostępnej publicznie.
Assert.Ignore("Dokument INW wymaga procedury niedostępnej z publicznego API (NotImplementedException): " + ex.Message);
return;
}
// Asercja ograniczona do utworzenia dokumentu (zgodnie z zakresem rozdziału):
// dokument powstał, ma definicję INW i wskazany magazyn.
inw.Should().NotBeNull();
inw!.Definicja.Symbol.Should().Be(Definicje.Inwentaryzacja, "dokument inwentaryzacji ma definicję INW");
inw.Magazyn.Should().Be(Magazyn(Magazyn_.Firma), "INW wymaga wskazanego magazynu");
}
// ===================================================================================
// W52 — Przesunięcie międzymagazynowe (MM)
// ===================================================================================
[Test]
[Description("W52: tworzy dokument przesunięcia międzymagazynowego (MM) z MagazynZ (źródło) i MagazynDo " +
"(cel). MagazynDo to pole kalkulowane delegujące do dokumentu podrzędnego — ustawiamy je " +
"po Definicji, przed dodaniem pozycji. Wymaga DRUGIEGO magazynu — gdy w Demo jest tylko „F”, " +
"test jest pomijany (Assert.Ignore).")]
public void W52_PrzesuniecieMM_TworzyDokumentZMagazynamiZrodloowymIDocelowym()
{
var magazynZrodlo = Magazyn(Magazyn_.Firma); // „F” — jedyny pewny magazyn w Demo
// MM wymaga DWÓCH różnych magazynów. Szukamy drugiego (innego niż „F”) na publicznym kontrakcie.
var magazynCel = Magazyny.Magazyny
.Cast()
.FirstOrDefault(m => m != magazynZrodlo);
if (magazynCel == null)
{
// W bazie Demo dostępny jest tylko magazyn „F” — bez drugiego magazynu nie da się
// utworzyć poprawnego MM (MagazynZ i MagazynDo muszą być różne). SKIP wg pułapek W52.
Assert.Ignore("Baza Demo ma tylko jeden magazyn („F”) — MM wymaga drugiego, różnego magazynu. " +
"Test przesunięcia międzymagazynowego pominięty.");
return;
}
// Magazyn źródłowy musi mieć ZAPISANY zasób przesuwanego towaru (Demo: blokada stanu ujemnego).
WprowadzNaStan(Towar_.Bikini, 50);
DokumentHandlowy mm = null;
try
{
InTransaction(() =>
{
mm = new DokumentHandlowy();
Session.AddRow(mm);
mm.Definicja = Definicja(Definicje.PrzesuniecieMM); // MM — definicja PIERWSZA
// MagazynDo jest kalkulowane (deleguje do dokumentu podrzędnego); ustawiamy je
// PO definicji i PRZED pozycjami (IsReadOnlyMagazynDo blokuje zmianę przy istniejących pozycjach).
mm.MagazynZ = magazynZrodlo;
mm.MagazynDo = magazynCel; // musi być różny od MagazynZ
var poz = new PozycjaDokHandlowego(mm);
Session.AddRow(poz);
poz.Towar = Towar(Towar_.Bikini); // Towar PIERWSZY (inicjuje jednostkę)
poz.Ilosc = new Quantity(5, poz.Ilosc.Symbol);
});
}
catch (NotImplementedException ex)
{
Assert.Ignore("Dokument MM wymaga procedury niedostępnej z publicznego API (NotImplementedException): " + ex.Message);
return;
}
// Asercje ograniczone do utworzenia dokumentu MM z poprawnymi magazynami.
mm.Should().NotBeNull();
mm!.Definicja.Symbol.Should().Be(Definicje.PrzesuniecieMM);
mm.MagazynZ.Should().Be(magazynZrodlo, "magazyn źródłowy rozchodu");
mm.MagazynZ.Should().NotBe(mm.MagazynDo, "MagazynZ i MagazynDo muszą być różne");
mm.Pozycje.Count.Should().BeGreaterThan(0);
}
// ===================================================================================
// SCENARIUSZE POMINIĘTE (SKIP) — uzasadnienie zgodne z treścią rozdziału
// ===================================================================================
[Test]
[Ignore("W49 — korekta wartości/ilości przyjęcia magazynowego (PZ/PW). Dedykowany worker " +
"UtworzKorektePrzyjeciaWorker jest INTERNAL (niedostępny z dodatku zewnętrznego). Publiczny tor " +
"to IRelacjeService.NowaKorekta na przyjęciu, ale wiarygodny test korekty przyjęcia wymaga " +
"różnicowych wyliczeń względem zaksięgowanych obrotów i partii (Obrot.Przychod, storna) oraz — " +
"przy wskazaniu dostawy — pełnej, zalogowanej sesji aplikacyjnej. Mechanika NowaKorekta jest już " +
"pokryta w W48; korekta przyjęcia nie wnosi nowego, testowalnego publicznie zachowania. SKIP wg pułapek W49.")]
[Description("W49: korekta wartości przyjęcia magazynowego — pominięte (worker internal; mechanika korekty pokryta w W48).")]
public void W49_KorektaPrzyjecia_Skip() { }
[Test]
[Ignore("W51 — faktura zaliczkowa i jej rozliczenie dokumentem końcowym. Rozliczenie wymaga przekazania " +
"callbacka w HandlerSet (WybierzDokumentyZaliczkoweCallback / WybierzZaliczkiWgStawkiVatCallback) " +
"dopasowanego do cechy definicji (SposobPrzenoszeniaZaliczki: NaPozycje vs NaDokument) — bez niego " +
"domyślne handlery rzucają NotImplementedException. Worker rozliczenia (RealizacjaZaliczkiWorker) jest " +
"INTERNAL; publiczny DokumentHandlowyRealizacjaZaliczkiWorker działa tylko wewnątrz tego callbacka, " +
"a baza Demo nie dostarcza definicji zaliczkowej (FZAL) ani spójnej konfiguracji przenoszenia. " +
"Scenariusz wymaga złożonego HandlerSet i konfiguracji spoza publicznego kontraktu. SKIP wg pułapek W51.")]
[Description("W51: faktura zaliczkowa i jej rozliczenie — pominięte (wymaga callbacka HandlerSet i workera internal; brak definicji FZAL w Demo).")]
public void W51_Zaliczki_Skip() { }
}