223 lines
11 KiB
Markdown
223 lines
11 KiB
Markdown
# HANDEL10 — Operacje zbiorcze (batch)
|
|
|
|
> Wspólne fakty o typie, podstawowe typy i szablon wzorca: [../handel.md](../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:**
|
|
|
|
```csharp
|
|
// 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:**
|
|
|
|
```csharp
|
|
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:**
|
|
|
|
```csharp
|
|
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).
|
|
|
|
---
|
|
|