Files
soneta-erp-skills/soneta-programming/references/domeny/handel/HANDEL10-batch.md
T

11 KiB

HANDEL10 — Operacje zbiorcze (batch)

Wspólne fakty o typie, podstawowe typy i szablon wzorca: ../handel.md.

Operacje na zbiorze dokumentów (ewidencjonowanie do księgowości, hurtowe zatwierdzanie, generowanie dokumentów podrzędnych) wykonujemy efektywnie i bezpiecznie: filtr serwerowy zamiast pełnego skanu tabeli, krótkie transakcje (paczki), świadoma obsługa blokady optymistycznej w Save(). Tabela DokHandlowe jest operacyjna (guided) — pełny skan bez zakresu czasowego jest zabroniony (safe-code.md §6.3). Duże pętle dziel na paczki, by nie trzymać długiej transakcji edycyjnej (§13.1).

HANDEL-W53 — Ewidencjonowanie / eksport do księgowości wielu dokumentów

Cel: zbiorczo zaewidencjonować (zaksięgować do ewidencji księgowej) wiele dokumentów handlowych z danego okresu — np. raport fiskalny zbiorczy z paragonów lub korekt paragonów. Realizuje to publiczny worker EwidencjonowanieZbiorczeWorker, który sam grupuje dokumenty (po drukarce / oddziale / rodzaju podmiotu) i tworzy zbiorcze dokumenty ewidencji DokEwidencji.

Warianty:

Wariant Ustawienie Params
Raport fiskalny z paragonów RaportDla = RaportDla.Paragonów
Raport dla korekt paragonów RaportDla = RaportDla.KorektParagonów
Zawężenie do jednej drukarki SymbolKasy = "D1" (puste = wszystkie z niepustym symbolem kasy)
Wskazanie definicji ewidencji Definicja (typ SprzedażZbiorczaEwidencja) — gdy chcemy inną niż domyślna
Filtr po dacie wystawienia ZaOkres: FromTo
Filtr po dacie dostawy / zaliczki OkresDostawyZaliczki: FromTo
Wielooddziałowość Oddzial: OddzialFirmy (gdy włączona w konfiguracji)

Pola i typy:

  • Worker: Soneta.Handel.EwidencjonowanieZbiorczeWorker (public), metoda publiczna void Ewidencjonuj(), property [Context] Params Param.
  • EwidencjonowanieZbiorczeWorker.Params(Context cx) — konstruktor z Context. Pola: ZaOkres: FromTo, OkresDostawyZaliczki: FromTo, RaportDla: RaportDla, SymbolKasy: string, Definicja: Soneta.Core.DefinicjaDokumentu, Oddzial: OddzialFirmy.
  • EwidencjonowanieZbiorczeWorker.RaportDla (enum): Paragonów, KorektParagonów.
  • Worker przetwarza tylko dokumenty w stanie Zatwierdzony / Zablokowany; pomija już zaewidencjonowane (EwidencjaZbiorcza != null).

Snippet:

// Worker SAM otwiera transakcję edycyjną i robi CommitUI() w środku — NIE owijaj go
// w session.Logout(true). Wystarczy go skonfigurować, wywołać i zapisać.
var worker = new EwidencjonowanieZbiorczeWorker
{
    Param = new EwidencjonowanieZbiorczeWorker.Params(context)
    {
        RaportDla = EwidencjonowanieZbiorczeWorker.RaportDla.Paragonów,
        ZaOkres = new FromTo(new Date(2026, 6, 1), new Date(2026, 6, 30)),  // data wystawienia
        OkresDostawyZaliczki = FromTo.All,                                  // bez filtra dostawy
        SymbolKasy = "D1",                                                  // jedna drukarka
        Definicja = CoreModule.GetInstance(session).DefDokumentow.WgSymbolu["SPZE"],
    }
};

worker.Ewidencjonuj();   // tworzy zbiorcze DokEwidencji w transakcji wewnętrznej (CommitUI)
session.Save();          // dopiero teraz zapis do bazy — tu wykrywane konflikty optymistyczne

Pułapki:

  • Ewidencjonuj() samodzielnie otwiera Session.Logout(true) i kończy CommitUI(). Nie wywołuj go we własnej transakcji edycyjnej (zagnieżdżenie/podwójny commit). Po nim wykonaj session.Save() (w testach SaveDispose()).
  • Param ustaw przed Ewidencjonuj() — jest to property [Context]; bez niej worker rzuci NullReferenceException.
  • Date i FromTo to typy biznesowe — używaj Date/Date.Today, nie DateTime (safe-code.md §10). FromTo.All = bez ograniczenia, FromTo.Empty worker zamienia na All.
  • Definicja to rekord konfiguracyjny — pobierz istniejący (DefDokumentow.WgTypu[...] / WgSymbolu[...]), nie twórz „w locie". Gdy Definicja == null, worker użyje domyślnej.
  • Worker działa na danych z ZaOkres (data wystawienia) — zawsze podaj zakres, nie zostawiaj pełnego skanu całej historii.
  • Konflikt edycji (ktoś zapisał ten sam dokument) wybuchnie w session.Save() jako RowConflictException — obsłuż go (refresh + retry lub eskalacja), nie połykaj (§4).

HANDEL-W54 — Hurtowe zatwierdzanie / generowanie dokumentów dla zaznaczonego zbioru

Cel: wykonać operację cyklu życia (zatwierdzenie, cofnięcie do bufora, anulowanie) na wielu dokumentach naraz, albo wygenerować dla zaznaczonego zbioru dokumenty podrzędne (np. wiele zamówień → faktury, wiele faktur → jeden zbiorczy WZ) za pomocą IRelacjeService, który przyjmuje tablicę dokumentów.

Warianty:

Wariant Mechanizm
Hurtowe zatwierdzanie pętla po zbiorze, dok.Stan = StanDokumentuHandlowego.Zatwierdzony, jedna (krótka) transakcja
Hurtowe cofnięcie do bufora / anulowanie dok.Stan = StanDokumentuHandlowego.Bufor / .Anulowany
Indywidualne generowanie podrzędnych IRelacjeService.NowyPodrzednyIndywidualny(DokumentHandlowy[], symbol) — N nadrzędnych → N podrzędnych
Zbiorcze generowanie podrzędnego IRelacjeService.NowyPodrzednyZbiorczy(DokumentHandlowy[], symbol) — wiele FA → 1 WZ
Zbiorcza korekta IRelacjeService.NowaKorektaZbiorcza(DokumentHandlowy[])
Dołączenie nadrzędnego / podrzędnego DolaczNadrzedny, DolaczPodrzednyIndywidualny

Pola i typy:

  • dok.Stan: Soneta.Handel.StanDokumentuHandlowego (Bufor=0, Zatwierdzony=1, Zablokowany=2, Anulowany=3). Skróty read-only: dok.Bufor, dok.Zatwierdzony, dok.Anulowany.
  • IRelacjeService (namespace Soneta.Handel.RelacjeDokumentow.Api): metody przyjmują DokumentHandlowy[] i zwracają DokumentHandlowy[]. Dokumenty nadrzędne muszą być zatwierdzone. Dostęp: session.GetRequiredService<IRelacjeService>() (using Microsoft.Extensions.DependencyInjection;).

Snippet:

var hm = session.GetHandel();
var fv = hm.DefDokHandlowych.WgSymbolu["FV"];
var od = new Date(2026, 6, 1);

// (1) Hurtowe zatwierdzanie zamówień z czerwca — filtr SERWEROWY + krótka transakcja
using (var t = session.Logout(editMode: true))
{
    foreach (DokumentHandlowy d in hm.DokHandlowe[(DokumentHandlowy d) =>
                 d.Data >= od && d.Definicja == fv && d.Stan == StanDokumentuHandlowego.Bufor])
    {
        d.Stan = StanDokumentuHandlowego.Zatwierdzony;   // pętla po Stan na zaznaczonym zbiorze
    }
    t.Commit();   // CommitUI() w workerze/extenderze
}
session.Save();

// (2) Wygenerowanie faktur dla zaznaczonych (zatwierdzonych) zamówień — IRelacjeService na tablicy
var rel = session.GetRequiredService<IRelacjeService>();
DokumentHandlowy[] zamowienia = /* zaznaczone, zatwierdzone ZO */;
using (var t = session.Logout(editMode: true))
{
    DokumentHandlowy[] faktury = rel.NowyPodrzednyIndywidualny(zamowienia, "FV");
    t.Commit();
}
session.Save();

Pułapki:

  • IRelacjeService wymaga, by dokumenty nadrzędne były zatwierdzone — najpierw zatwierdź (wariant 1), potem generuj podrzędne.
  • Operacje masowe wykonuj w jednej transakcji tylko gdy zbiór jest mały; dla dużych dziel na paczki (HANDEL-W55) — długa transakcja blokuje innych i zwiększa ryzyko konfliktu (§13.1).
  • Zmiana Stan musi być w transakcji (session.Logout(true)); w workerze/extenderze t.CommitUI() zamiast t.Commit().
  • Nie iteruj całej tabeli DokHandlowe z if w pamięci — filtr serwerowy z zakresem czasowym (§6.1, §6.3). Zaznaczony w UI zbiór masz w context jako DokumentHandlowy[].
  • Save() po operacji relacji może rzucić RowConflictException (optimistic lock) — obsłuż (§4).

HANDEL-W55 — Wydajne przetwarzanie wielu dokumentów w jednej sesji (paczki)

Cel: przetworzyć duży zbiór dokumentów (tysiące) w jednej sesji bez blokowania innych użytkowników i bez ryzyka, że pojedynczy konflikt unieważni całą operację — przez podział na paczki (krótkie transakcje, okresowy Save()).

Warianty:

Wariant Technika
Filtr serwerowy z zakresem czasowym hm.DokHandlowe[(DokumentHandlowy d) => d.Data >= od && d.Data <= doD && …]
Paczki o stałym rozmiarze licznik w pętli + Commit() / Save() co N rekordów
Izolacja konfliktu paczki try/catch (RowConflictException) wokół Save() paczki, retry/log paczki
Tylko odczyt (raport) login.CreateSession(readOnly: true, …) — bez transakcji edycyjnej

Pola i typy: Soneta.Types.Date (zakres), StanDokumentuHandlowego, RowConflictException (session.Save()), IDisposable na sesji i transakcji.

Snippet:

const int rozmiarPaczki = 200;       // przetwarzaj po 200 dokumentów na transakcję
var hm = session.GetHandel();
var od  = new Date(2026, 1, 1);
var doD = Date.Today;

// Materializujemy KLUCZE/ID po stronie serwera (filtr), nie całe rekordy w pamięci wszystkie naraz.
// Iterujemy serwerowy zbiór i commitujemy paczkami — krótka transakcja na każdą paczkę.
int licznik = 0;
ITransaction t = session.Logout(editMode: true);
try
{
    foreach (DokumentHandlowy d in hm.DokHandlowe[(DokumentHandlowy d) =>
                 d.Data >= od && d.Data <= doD && d.Stan == StanDokumentuHandlowego.Bufor])
    {
        d.Stan = StanDokumentuHandlowego.Zatwierdzony;

        if (++licznik % rozmiarPaczki == 0)
        {
            t.Commit();
            t.Dispose();
            session.Save();                  // zamknięcie paczki — krótka transakcja
            t = session.Logout(editMode: true);
        }
    }
    t.Commit();
}
finally
{
    t.Dispose();
}
session.Save();                              // ostatnia (niepełna) paczka

Pułapki:

  • Krótka transakcja to bezpieczeństwo, nie tylko wydajność — operacja > ~30 s powinna iść paczkami (§13.1). Jedna gigantyczna transakcja blokuje innych i zwiększa szansę konfliktu.
  • Filtruj serwerowo (SubTable[condition]), z zakresem czasowym dla tabeli operacyjnej guided (DokHandlowe) — nigdy pełny skan (§6.1, §6.3). Nie używaj .ToList().Where(...) (§13.2).
  • Po session.Save() w środku pętli okno edycji jest zamknięte — kolejną edycję otwórz nową transakcją (session.Logout(true)), inaczej AccessWriteDenied. (W testach wzorzec to Save()SaveDispose() → odczyt na świeżej sesji po Guid.)
  • Obsłuż RowConflictException per paczka (refresh + retry lub log i kontynuacja), nie łap Exception ogólnie (§4, §9.1). Połknięty wyjątek z Save() = utrata danych.
  • Nie współdziel Session/Row między wątkami — równoległe przetwarzanie wymaga osobnej sesji na wątek (§3.1).
  • Sesja zawsze w using/try-finally z Dispose() (§1.1); transakcja bez Commit() = automatyczny rollback.

Powiązane: rozdz. 5 (cykl życia / Stan), rozdz. 8 (relacje, IRelacjeService), safe-code.md §4 (optimistic lock), §6 (filtr serwerowy), §13 (paczki), rowcondition.md (serwerowy LINQ).