using System;
using System.Collections.Generic;
using System.Linq;
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.Handel;
namespace Soneta.Skills.Test.Handel.DokumentyHandlowe;
///
/// Rozdział 10 — Operacje zbiorcze (batch), wzorce W53–W55.
///
/// Operacje na zbiorze dokumentów wykonujemy bezpiecznie i wydajnie: filtr serwerowy
/// (a nie pełny skan tabeli operacyjnej DokHandlowe), krótkie transakcje
/// (paczki) oraz świadoma obsługa zapisu (Save(), gdzie wykrywane są konflikty
/// optymistyczne). W testach krótka transakcja = InTransaction(...), a zamknięcie
/// paczki = SaveDispose() (Save + zamknięcie okna edycji sesji).
///
///
/// W bazie Demo działa StanUjemnyVerifier (blokada stanu ujemnego), więc do operacji
/// zbiorczych używamy przychodów (PW) — nie podlegają tej blokadzie i nie wymagają
/// wcześniejszego zapasu towaru. Magazyn księguje się dopiero po Session.Save().
///
/// Cała klasa operuje wyłącznie na publicznym kontrakcie platformy (jak dodatek zewnętrzny).
///
[TestFixture]
public class Rozdzial10_BatchTest : DokumentHandlowyTestBase
{
// === Pomocnik lokalny: kilka przyjęć (PW) w buforze, zapisanych trwale ===
///
/// Tworzy dokumentów przyjęcia wewnętrznego (PW) z jedną pozycją
/// BIKINI, pozostawia je w buforze i zapisuje trwale. Zwraca listę Guidów (sesja zostaje
/// zamknięta przez , więc dalej pracujemy przez odczyt po Guid).
/// PW to przychód — bez ryzyka blokady stanu ujemnego, idealny do testów wsadowych.
///
private List UtworzPwWBuforzeIZapisz(int ile, double ilosc = 10, double cena = 5)
{
var guidy = new List(ile);
for (int i = 0; i < ile; i++)
{
// Każdy dokument tworzymy przez bazowy helper (AddRow -> Definicja -> Magazyn).
var dok = UtworzDokument(Definicje.PrzyjecieWewnetrzne, magazyn: Magazyn(Magazyn_.Firma));
InTransaction(() => DodajPozycje(dok, Towar(Towar_.Bikini), ilosc, cena));
guidy.Add(dok.Guid);
}
// Jeden wspólny Save dla wszystkich utworzonych dokumentów.
SaveDispose();
return guidy;
}
// === W54 — Hurtowe zatwierdzanie wielu dokumentów w jednej transakcji ===
[Test]
[Description("W54: hurtowe zatwierdzanie — kilka PW w buforze zatwierdzonych pętlą po Stan w jednej transakcji; po Save wszystkie są Zatwierdzone.")]
public void W54_HurtoweZatwierdzanie_WszystkieDokumentyZatwierdzone()
{
// 1. Przygotowanie: 3 dokumenty PW w buforze, zapisane trwale.
var guidy = UtworzPwWBuforzeIZapisz(ile: 3);
// Wczytujemy je na świeżej sesji i potwierdzamy stan wyjściowy = Bufor.
var dokumenty = guidy.Select(g => Get(g)).ToArray();
dokumenty.Should().OnlyContain(d => d.Bufor);
// 2. Hurtowe zatwierdzenie: jedna (krótka) transakcja, pętla po zbiorze i zmiana Stan.
// W teście InTransaction odpowiada wzorcowi session.Logout(true) + Commit z dokumentu.
InTransaction(() =>
{
foreach (var d in dokumenty)
d.Stan = StanDokumentuHandlowego.Zatwierdzony;
});
SaveDispose();
// 3. Asercja: po Save wszystkie dokumenty są zatwierdzone (czytamy pola kalkulowane).
foreach (var g in guidy)
{
var zapisany = Get(g);
zapisany.Zatwierdzony.Should().BeTrue();
zapisany.Bufor.Should().BeFalse();
}
}
[Test]
[Description("W54: hurtowe cofnięcie do bufora — kilka zatwierdzonych PW cofniętych jedną pętlą po Stan; po Save wszystkie wracają do bufora.")]
public void W54_HurtoweCofniecieDoBufora_WszystkieWBuforze()
{
// 1. Najpierw zatwierdzamy kilka PW (stan wyjściowy do cofnięcia).
var guidy = UtworzPwWBuforzeIZapisz(ile: 2);
var zatwierdzone = guidy.Select(g => Get(g)).ToArray();
InTransaction(() =>
{
foreach (var d in zatwierdzone)
d.Stan = StanDokumentuHandlowego.Zatwierdzony;
});
SaveDispose();
guidy.Select(g => Get(g)).Should().OnlyContain(d => d.Zatwierdzony);
// 2. Hurtowe cofnięcie: zatwierdzony -> bufor (odksięgowanie przy Save) w jednej transakcji.
var doCofniecia = guidy.Select(g => Get(g)).ToArray();
InTransaction(() =>
{
foreach (var d in doCofniecia)
d.Stan = StanDokumentuHandlowego.Bufor;
});
SaveDispose();
// 3. Asercja: wszystkie z powrotem w buforze.
guidy.Select(g => Get(g))
.Should().OnlyContain(d => d.Bufor && !d.Zatwierdzony);
}
// === W55 — Wydajne przetwarzanie w paczkach (krótkie transakcje, okresowy Save) ===
[Test]
[Description("W55: przetwarzanie w paczkach — kilka dokumentów dzielonych na małe transakcje z okresowym Save; po przetworzeniu wszystkie poprawnie zatwierdzone.")]
public void W55_PrzetwarzanieWPaczkach_WszystkieDokumentyPrzetworzone()
{
// 1. Większy (na potrzeby testu kilkuelementowy) zbiór PW w buforze.
const int ileDokumentow = 5;
var guidy = UtworzPwWBuforzeIZapisz(ile: ileDokumentow);
// 2. Wzorzec paczkowy: małe paczki + Save po każdej paczce (krótka transakcja).
// W produkcyjnym kodzie rozmiar paczki to ~200; w teście używamy 2, by faktycznie
// domknąć więcej niż jedną paczkę i pokazać wzorzec "Save -> nowa sesja po Guid".
// Po SaveDispose okno edycji jest zamknięte, więc kolejną paczkę edytujemy na
// świeżej sesji (odczyt po Guid) — odpowiednik nowej session.Logout(true).
const int rozmiarPaczki = 2;
int przetworzone = 0;
// Iterujemy serwerowo wyłonione dokumenty (tu: po znanych Guidach) paczkami.
foreach (var paczka in guidy.Chunk(rozmiarPaczki))
{
// Każda paczka = osobna krótka transakcja na świeżej sesji.
var dokumentyPaczki = paczka.Select(g => Get(g)).ToArray();
InTransaction(() =>
{
foreach (var d in dokumentyPaczki)
{
d.Stan = StanDokumentuHandlowego.Zatwierdzony;
przetworzone++;
}
});
// Okresowy Save zamyka paczkę (krótka transakcja); kolejna paczka -> nowa sesja.
SaveDispose();
}
// 3. Asercja poprawności: liczba przetworzonych = liczba dokumentów,
// a każdy dokument jest trwale zatwierdzony.
przetworzone.Should().Be(ileDokumentow);
foreach (var g in guidy)
Get(g).Zatwierdzony.Should().BeTrue();
}
[Test]
[Description("W55: filtr serwerowy z zakresem czasowym — wsadowo zatwierdzamy tylko PW z dzisiejszą datą i w buforze; wzorzec SubTable[condition] zamiast pełnego skanu.")]
public void W55_FiltrSerwerowyZakresCzasowy_PrzetwarzaTylkoWybranePaczki()
{
// 1. Tworzymy kilka PW w buforze (data = dziś, nadana domyślnie przez definicję).
const int ileDokumentow = 4;
var guidy = UtworzPwWBuforzeIZapisz(ile: ileDokumentow);
var oczekiwane = new HashSet(guidy);
// 2. Filtr SERWEROWY z zakresem czasowym na tabeli operacyjnej DokHandlowe —
// NIE iterujemy całej tabeli z if-em w pamięci. Zawężamy do PW w buforze z dzisiaj.
var fv = Definicja(Definicje.PrzyjecieWewnetrzne);
var od = Soneta.Types.Date.Today;
// Materializujemy zbiór do paczkowego przetwarzania (w produkcji iterujemy strumieniowo).
var doPrzetworzenia = Handel.DokHandlowe.WgDaty[(DokumentHandlowy d) =>
d.Data >= od && d.Definicja == fv && d.Stan == StanDokumentuHandlowego.Bufor]
.Cast()
.Where(d => oczekiwane.Contains(d.Guid)) // zawężenie tylko do dokumentów tego testu
.Select(d => d.Guid)
.ToList();
// Filtr serwerowy odnalazł wszystkie utworzone dokumenty tego testu.
doPrzetworzenia.Should().HaveCount(ileDokumentow);
// 3. Przetwarzanie paczkami (krótkie transakcje) na wyłonionym zbiorze.
const int rozmiarPaczki = 2;
foreach (var paczka in doPrzetworzenia.Chunk(rozmiarPaczki))
{
var dokumentyPaczki = paczka.Select(g => Get(g)).ToArray();
InTransaction(() =>
{
foreach (var d in dokumentyPaczki)
d.Stan = StanDokumentuHandlowego.Zatwierdzony;
});
SaveDispose();
}
// 4. Asercja: wszystkie wyłonione filtrem dokumenty zostały zatwierdzone.
foreach (var g in doPrzetworzenia)
Get(g).Zatwierdzony.Should().BeTrue();
}
// === W53 — Ewidencjonowanie zbiorcze (EwidencjonowanieZbiorczeWorker) ===
[Test]
[Description("W53: ewidencjonowanie zbiorcze (EwidencjonowanieZbiorczeWorker) — pomijane: wymaga konfiguracji księgowej/ewidencji niedostępnej wprost w bazie Demo.")]
public void W53_EwidencjonowanieZbiorcze_PominietePoniewazWymagaKonfiguracjiKsiegowej()
{
// SKIP: pełny tor ewidencjonowania zbiorczego wymaga skonfigurowanej ewidencji
// księgowej (definicja dokumentu ewidencji typu SprzedażZbiorczaEwidencja) oraz
// dokumentów źródłowych z niepustym symbolem kasy/drukarki fiskalnej. W bazie Demo
// nie jest to dostępne wprost, więc tworzenie zbiorczych DokEwidencji nie zadziała
// w sposób powtarzalny. Opisujemy tu jedynie PUBLICZNY tor wywołania:
//
// var worker = new EwidencjonowanieZbiorczeWorker
// {
// Param = new EwidencjonowanieZbiorczeWorker.Params(context)
// {
// RaportDla = EwidencjonowanieZbiorczeWorker.RaportDla.Paragonów, // lub KorektParagonów
// ZaOkres = new FromTo(new Date(2026, 6, 1), new Date(2026, 6, 30)), // data wystawienia
// OkresDostawyZaliczki = FromTo.All, // bez filtra dostawy/zaliczki
// SymbolKasy = "D1", // jedna drukarka; puste = wszystkie z symbolem kasy
// Definicja = CoreModule.GetInstance(session).DefDokumentow.WgSymbolu["SPZE"], // opcjonalnie
// }
// };
// worker.Ewidencjonuj(); // worker SAM otwiera transakcję i robi CommitUI() w środku
// session.Save(); // dopiero teraz zapis do bazy (tu wykrywane konflikty optymistyczne)
//
// Uwagi (pułapki):
// - NIE owijaj Ewidencjonuj() we własną transakcję edycyjną (worker robi Session.Logout(true)
// + CommitUI() wewnętrznie); zagnieżdżenie = podwójny commit.
// - Param to property [Context] — ustaw PRZED Ewidencjonuj(), inaczej NullReferenceException.
// - Worker przetwarza tylko dokumenty Zatwierdzone/Zablokowane i pomija już
// zaewidencjonowane (EwidencjaZbiorcza != null).
// - Definicja to rekord konfiguracyjny — pobierz istniejący (WgSymbolu/WgTypu), nie twórz "w locie".
Assert.Ignore("W53: ewidencjonowanie zbiorcze wymaga konfiguracji ewidencji księgowej/kasy " +
"niedostępnej wprost w bazie Demo. Publiczny tor (Ewidencjonuj() + Params) opisany w komentarzu.");
}
}