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 publicznavoid Ewidencjonuj(), property[Context] Params Param. EwidencjonowanieZbiorczeWorker.Params(Context cx)— konstruktor zContext. 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 otwieraSession.Logout(true)i kończyCommitUI(). Nie wywołuj go we własnej transakcji edycyjnej (zagnieżdżenie/podwójny commit). Po nim wykonajsession.Save()(w testachSaveDispose()).Paramustaw przedEwidencjonuj()— jest to property[Context]; bez niej worker rzuciNullReferenceException.DateiFromToto typy biznesowe — używajDate/Date.Today, nieDateTime(safe-code.md§10).FromTo.All= bez ograniczenia,FromTo.Emptyworker zamienia naAll.Definicjato rekord konfiguracyjny — pobierz istniejący (DefDokumentow.WgTypu[...]/WgSymbolu[...]), nie twórz „w locie". GdyDefinicja == 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()jakoRowConflictException— 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(namespaceSoneta.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:
IRelacjeServicewymaga, 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
Stanmusi być w transakcji (session.Logout(true)); w workerze/extenderzet.CommitUI()zamiastt.Commit(). - Nie iteruj całej tabeli
DokHandlowezifw pamięci — filtr serwerowy z zakresem czasowym (§6.1, §6.3). Zaznaczony w UI zbiór masz wcontextjakoDokumentHandlowy[]. 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)), inaczejAccessWriteDenied. (W testach wzorzec toSave()→SaveDispose()→ odczyt na świeżej sesji poGuid.) - Obsłuż
RowConflictExceptionper paczka (refresh + retry lub log i kontynuacja), nie łapExceptionogólnie (§4, §9.1). Połknięty wyjątek zSave()= utrata danych. - Nie współdziel
Session/Rowmiędzy wątkami — równoległe przetwarzanie wymaga osobnej sesji na wątek (§3.1). - Sesja zawsze w
using/try-finallyzDispose()(§1.1); transakcja bezCommit()= 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).