16 KiB
HANDEL03 — Stany dokumentu i cykl życia
Wspólne fakty o typie, podstawowe typy i szablon wzorca: ../handel.md.
Stan dokumentu handlowego steruje całym jego cyklem życia: od bufora (rekord roboczy, swobodnie
edytowalny i usuwalny), przez zatwierdzenie (księgowanie obrotów magazynowych, generowanie
płatności, blokada większości pól), aż po anulowanie. Stanem steruje jedno zapisywalne pole
dok.Stan, a dodatkowe operacje serwisowe (naprawa, przeliczenie) wykonują publiczne workery.
Fundamenty (sesja, transakcja edycyjna
session.Logout(editMode: true),Commit/CommitUI, blokada optymistyczna wSave()) opisujesafe-code.md— tu się do nich odwołujemy, nie powtarzamy. Cały kod jest zgodny z C# 10 i operuje wyłącznie na publicznym kontrakcie platformy.
Fakty o stanie (zweryfikowane):
- Pole sterujące:
dok.Stan: Soneta.Handel.StanDokumentuHandlowego(zapisywalne w transakcji). - Enum
StanDokumentuHandlowego:Bufor=0,Zatwierdzony=1,Zablokowany=2,Anulowany=3. WartośćZablokowanyustawia platforma (np. po zaksięgowaniu w ewidencji) — nie ustawiaj jej z dodatku „z palca". - Skróty kalkulowane (tylko do odczytu,
bool):dok.Bufor,dok.Zatwierdzony,dok.Anulowany. - Usunięcie z bufora:
dok.Delete()w transakcji (tylko gdy brak zależności). - Workery publiczne (cykl życia / naprawa):
Soneta.Handel.PoprawaStanuDokumentuWorker,Soneta.Magazyny.PrzeliczenieStanuWorker.
HANDEL-W12 — Zatwierdzenie dokumentu (bufor → zatwierdzony)
Cel: przeprowadzić dokument z bufora do stanu zatwierdzonego. Dopiero zatwierdzenie + Save()
księguje obroty magazynowe, tworzy zasoby/partie, generuje płatności i czyni dokument nadrzędnym dla
relacji (np. ZO→FV, FA→WZ — patrz rozdział o relacjach).
Warianty:
| Wariant | Operacja | Uwaga |
|---|---|---|
| Zatwierdzenie pojedyncze | dok.Stan = StanDokumentuHandlowego.Zatwierdzony |
w transakcji + Save() |
| Zatwierdzenie zbiorcze | worker EwidencjonowanieZbiorczeWorker ([Context] DokumentHandlowy[]) |
wiele dokumentów naraz |
| Sprawdzenie stanu | dok.Zatwierdzony / dok.Bufor (kalkulowane bool) |
bez porównywania enuma |
Stan Zablokowany |
ustawiany przez platformę (księgowanie ewidencji) | nie ustawiaj ręcznie |
Pola i typy: dok.Stan: StanDokumentuHandlowego (zapisywalne), dok.Bufor/Zatwierdzony/Anulowany: bool (kalkulowane). Wartości magazynowe widoczne po Save(): dok.Zasoby, dok.Obroty,
dok.SumyVAT.
Snippet:
var hm = session.GetHandel();
var dok = hm.DokHandlowe.WgDaty[/* ... */]; // odczytany dokument w buforze
using (var t = session.Logout(editMode: true))
{
dok.Stan = StanDokumentuHandlowego.Zatwierdzony; // bufor -> zatwierdzony
t.Commit(); // CommitUI() w workerze/extenderze
}
session.Save(); // DOPIERO TERAZ księgowane są obroty/zasoby/płatności
// Sprawdzenie po zapisie — czytaj pola kalkulowane, nie porównuj enuma:
if (dok.Zatwierdzony)
{
foreach (var z in dok.Zasoby) { /* zasoby utworzone przez dokument przychodowy */ }
}
Pułapki:
- Magazyn księguje się dopiero po
Save()— samoCommit()/CommitUI()nie tworzy obrotów ani zasobów. Jeśli baza blokuje stan ujemny (weryfikatorStanUjemnyVerifier, jak w bazie Demo), rozchód (FV/WZ/RW) wymaga wcześniej zapisanego przyjęcia (PW/PZ) tego towaru — inaczejSave()rzuci wyjątek. - Zatwierdzenie uruchamia walidatory dokumentu (kompletność pozycji, magazyn, kontrahent, tabela
VAT). Błędy wychodzą w
Commit()/Save()jakoRowException— nie połykaj ich (safe-code §4). - W workerze/extenderze użyj
t.CommitUI()zamiastt.Commit()(worker-extender.md). - Po
Save()w środku jednej sesji zamyka się okno edycji; kolejna edycja na tym samym obiekcie bez ponownegoLogoutrzuciAccessWriteDenied. Wzorzec: zapis → odczyt na świeżej sesji. - Nie ustawiaj
Stan = Zablokowanyz dodatku — to stan wewnętrzny platformy (np. po zaksięgowaniu w ewidencji).
HANDEL-W13 — Cofnięcie do bufora / odtwierdzenie
Cel: wycofać zatwierdzony dokument z powrotem do bufora, aby go poprawić. Operacja odksięgowuje to, co zatwierdzenie zaksięgowało (obroty, płatności), więc jest dozwolona tylko gdy nie ma zależności blokujących (zamknięty okres magazynowy/VAT, zaksięgowanie w ewidencji, dokumenty podrzędne).
Warianty:
| Wariant | Operacja | Warunek dozwolenia |
|---|---|---|
| Cofnięcie do bufora | dok.Stan = StanDokumentuHandlowego.Bufor |
okres otwarty, brak podrzędnych, nie zaksięgowany |
| Dokument zablokowany | najpierw zdjąć blokadę po stronie ewidencji/księgowości | dok.Stan == Zablokowany blokuje cofnięcie |
| Z dokumentami podrzędnymi | najpierw usuń/rozłącz podrzędne (relacje) | patrz rozdział o relacjach i HANDEL-W16 |
Pola i typy: dok.Stan: StanDokumentuHandlowego, dok.Zatwierdzony/Bufor: bool (kalkulowane),
dok.DokumentyMagazynowe, dok.DokumentyHandlowe, dok.DokumentyKorygujące (kalkulowane — do
sprawdzenia zależności przed cofnięciem).
Snippet:
var dok = session.GetHandel().DokHandlowe.WgDaty[/* ... */];
if (!dok.Zatwierdzony) return; // już w buforze / anulowany — nic do zrobienia
// Cofnięcie jest zablokowane, gdy istnieją dokumenty podrzędne (korekty, magazynowe):
bool maZaleznosci = dok.DokumentyKorygujące.Any() || dok.DokumentyMagazynowe.Length > 0;
if (maZaleznosci)
throw new BusException(
"Nie można cofnąć dokumentu do bufora — istnieją powiązane dokumenty.".Translate());
using (var t = session.Logout(editMode: true))
{
dok.Stan = StanDokumentuHandlowego.Bufor; // odtwierdzenie: zatwierdzony -> bufor
t.Commit(); // CommitUI() w workerze/extenderze
}
session.Save(); // tu odksięgowanie obrotów/płatności i wykrycie konfliktów
Pułapki:
- Cofnięcie dokumentu w zamkniętym okresie magazynowym/VAT albo zaksięgowanego w ewidencji
zakończy się wyjątkiem w
Commit()/Save(). Sprawdź stan otwarcia okresu zanim spróbujesz. - Dokument w stanie
Zablokowanynie cofniesz przezdok.Stan = Bufor— blokada wynika z innego modułu (np. ewidencja zaksięgowana). Do diagnozy/naprawy rozbieżności stanu dokument↔ewidencja służyPoprawaStanuDokumentuWorker(HANDEL-W15). - Jeśli istnieją dokumenty podrzędne (korekty, powiązane magazynowe), cofnięcie się nie powiedzie — najpierw rozwiąż powiązania (rozdział o relacjach), patrz też HANDEL-W16.
- To nie to samo co anulowanie (HANDEL-W14): cofnięcie wraca do edytowalnego bufora, anulowanie zamyka dokument w stanie nieodwracalnym.
HANDEL-W14 — Anulowanie dokumentów
Cel: unieważnić dokument, który nie powinien już brać udziału w obrocie (np. wystawiony omyłkowo),
zachowując go w bazie dla ciągłości numeracji i audytu. Anulowanie odksięgowuje skutki magazynowe i
finansowe, ale rekord pozostaje (w przeciwieństwie do Delete()).
Warianty:
| Wariant | Operacja | Uwaga |
|---|---|---|
| Anulowanie z bufora | dok.Stan = StanDokumentuHandlowego.Anulowany |
bufor → anulowany |
| Anulowanie zatwierdzonego | dok.Stan = StanDokumentuHandlowego.Anulowany |
odksięgowuje obroty/płatności; tylko gdy okres otwarty |
| Sprawdzenie | dok.Anulowany (kalkulowane bool) |
bez porównywania enuma |
Pola i typy: dok.Stan: StanDokumentuHandlowego, dok.Anulowany: bool (kalkulowane).
Snippet:
var dok = session.GetHandel().DokHandlowe.WgDaty[/* ... */];
if (dok.Anulowany) return; // już anulowany
using (var t = session.Logout(editMode: true))
{
dok.Stan = StanDokumentuHandlowego.Anulowany; // bufor lub zatwierdzony -> anulowany
t.Commit(); // CommitUI() w workerze/extenderze
}
session.Save();
// Po anulowaniu dokument pozostaje w bazie (numeracja zachowana), ale nie wpływa na stany:
bool wycofany = dok.Anulowany;
Pułapki:
- Anulowanie zatwierdzonego dokumentu odksięgowuje jego skutki — w zamkniętym okresie albo gdy istnieją dokumenty podrzędne kończy się wyjątkiem. Najpierw rozwiąż zależności (jak w HANDEL-W13).
- Anulowanie jest nieodwracalne — nie ma przejścia
Anulowany → Buforna poziomie polaStan. Gdy chcesz tylko poprawić dokument, użyj cofnięcia do bufora (HANDEL-W13). - Anulowany dokument zwykle nie powinien być źródłem relacji ani korekt — generowanie podrzędnych z anulowanego nadrzędnego zostanie odrzucone.
- Do trwałego usunięcia rekordu (gdy dozwolone) służy
Delete()(HANDEL-W16), a nie anulowanie — anulowanie zachowuje rekord i numer.
HANDEL-W15 — Naprawa i przeliczenie stanu dokumentu
Cel: naprawić rozbieżności między dokumentem a jego skutkami: stan dokumentu vs stan dokumentu
ewidencji (PoprawaStanuDokumentuWorker) oraz zgodność obrotów/zasobów magazynowych z pozycjami
(PrzeliczenieStanuWorker). To operacje serwisowe — uruchamiaj świadomie, nie w pętli zwykłej
logiki.
Warianty:
| Wariant | Worker (publiczny) | Akcja menu / wejście |
|---|---|---|
| Naprawa stanu dokumentu (synchron. z ewidencją) | Soneta.Handel.PoprawaStanuDokumentuWorker |
„Narzędziowe/Naprawa stanu dokumentu"; [Context] Dokument |
| Sprawdzenie poprawności obrotów (bez zapisu) | Soneta.Magazyny.PrzeliczenieStanuWorker, Opcje.SprawdzićPoprawność |
„Narzędziowe/Naliczenie obrotów towaru" |
| Ponowne pełne przeliczenie | PrzeliczenieStanuWorker, Opcje.PonowniePrzeliczyć |
jw. (zapis w transakcji) |
| Poprawa tylko błędnych | PrzeliczenieStanuWorker, Opcje.PoprawićTylkoBłędne |
jw. |
| Poprawa / sprawdzenie samych obrotów | Opcje.PoprawićObroty / Opcje.SprawdzićObroty |
jw. |
Pola i typy (publiczny kontrakt workerów):
PoprawaStanuDokumentuWorker: property[Context] public DokumentHandlowy Dokument; akcjapublic void NaprawStan(); predykat widocznościpublic static bool IsVisibleNaprawStan(DokumentHandlowy dokument). Worker sam zarządza transakcją wewnątrzNaprawStan()(synchronizujedok.Stanz dokumentem ewidencji, w razie potrzeby tworzy/kasuje ewidencję, może przestawićStannaZablokowany/Zatwierdzony).PrzeliczenieStanuWorker: enumpublic enum Opcje { SprawdzićPoprawność, PoprawićTylkoBłędne, PrzeliczyćTylkoNiepoprawione, PonowniePrzeliczyć, PoprawićObroty, SprawdzićObroty }; konstruktor publicznyPrzeliczenieStanuWorker(Opcje wykonaj, bool wszystkieMagazyny, bool rozchód0, bool przywracajWartość); property[Context]Dokument,Towar,Magazyny(Magazyn[]); akcjapublic void PrzeliczStan(). Worker sam otwiera transakcje wewnątrzPrzeliczStan().
Snippet:
var dok = session.GetHandel().DokHandlowe.WgDaty[/* ... */];
// 1. Naprawa rozbieżności stanu dokumentu względem dokumentu ewidencji.
// Worker sam prowadzi transakcje — ustaw tylko kontekst i wywołaj akcję.
var naprawa = new PoprawaStanuDokumentuWorker { Dokument = dok };
naprawa.NaprawStan();
session.Save(); // utrwalenie zmian dokonanych przez workera
// 2. Sprawdzenie poprawności obrotów dokumentu BEZ wprowadzania zmian (tryb diagnostyczny):
var sprawdz = new PrzeliczenieStanuWorker(
PrzeliczenieStanuWorker.Opcje.SprawdzićPoprawność,
wszystkieMagazyny: false, rozchód0: false, przywracajWartość: true) { Dokument = dok };
sprawdz.PrzeliczStan(); // tryb SprawdzićPoprawność nie commituje — tylko raportuje (Trace)
// 3. Pełne ponowne przeliczenie obrotów dokumentu (modyfikuje dane):
var przelicz = new PrzeliczenieStanuWorker(
PrzeliczenieStanuWorker.Opcje.PoprawićTylkoBłędne,
wszystkieMagazyny: false, rozchód0: false, przywracajWartość: true) { Dokument = dok };
przelicz.PrzeliczStan();
session.Save();
Pułapki:
- Oba workery same zarządzają transakcjami wewnątrz swoich akcji (
NaprawStan/PrzeliczStan). Nie owijaj wywołania własnymsession.Logout(true)— wystarczysession.Save()po akcji, by utrwalić zmiany. - W realnej aplikacji akcje są rejestrowane z
Mode = ActionMode.IsolatedSession | Progress, czyli uruchamiają się w izolowanej sesji. Przy programowym wywołaniu działasz na bieżącej sesji — upewnij się, że nie koliduje to z innymi otwartymi transakcjami. Opcje.SprawdzićPoprawnośćto tryb tylko diagnostyczny — nie zmienia danych, raportuje przezTrace. Do faktycznej naprawy użyjPoprawićTylkoBłędne/PonowniePrzeliczyć.PrzeliczenieStanuWorkerrzucaRowException, gdy napotka obrót w zamkniętym okresie magazynowym albo dokument korygowany w buforze („Dokument korygowany … w buforze. Należy go zatwierdzić.") — obsłuż te przypadki, nie wywołuj przeliczenia na ślepo.PoprawaStanuDokumentuWorker.IsVisibleNaprawStanzwracafalsedla dokumentów z obsługą technologii produkcji i magazynu pozabilansowego — to sygnał, że dla takich dokumentów naprawa nie ma zastosowania.- To są narzędzia serwisowe — nie używaj ich jako rutynowego elementu logiki tworzenia dokumentów.
HANDEL-W16 — Bezpieczne usunięcie dokumentu z bufora i obsługa zależności
Cel: trwale usunąć dokument z bazy (Delete()), gdy jest błędny i jeszcze niepowiązany. Usuwanie
jest dozwolone wyłącznie w buforze i tylko gdy nie istnieją zależności (rezerwacje, dokumenty
magazynowe/handlowe powiązane, korekty). W przeciwnym razie świadomie odmów (lub anuluj — HANDEL-W14).
Warianty:
| Wariant | Sytuacja | Zalecenie |
|---|---|---|
| Usunięcie czyste | bufor, brak powiązań i rezerwacji | dozwolone (dok.Delete()) |
| Dokument zatwierdzony | poza buforem | najpierw cofnij do bufora (HANDEL-W13) lub anuluj (HANDEL-W14) |
| Z rezerwacją | dok.Rezerwacja != null |
usuń/zwolnij rezerwację najpierw (relacje) |
| Z dokumentami powiązanymi | DokumentyMagazynowe/DokumentyHandlowe/korekty niepuste |
rozłącz/usuń podrzędne lub anuluj |
Pola i typy (do oceny zależności — kalkulowane, tylko odczyt): dok.Bufor: bool,
dok.Rezerwacja, dok.DokumentyMagazynowe: DokumentHandlowy[], dok.DokumentyHandlowe: DokumentHandlowy[], dok.DokumentyKorygujące: IEnumerable, dok.DokumentKorygowany,
dok.DokumentyZaliczkowe.
Snippet:
var dok = session.GetHandel().DokHandlowe.WgDaty[/* ... */];
// 1. Usuwać można tylko z bufora:
if (!dok.Bufor)
throw new BusException(
"Usunąć można tylko dokument w buforze. Cofnij do bufora lub anuluj.".Translate());
// 2. Zależności blokujące usunięcie (rezerwacja, powiązane, korekty):
bool maZaleznosci =
dok.Rezerwacja != null ||
dok.DokumentyMagazynowe.Length > 0 ||
dok.DokumentyHandlowe.Length > 0 ||
dok.DokumentyKorygujące.Any();
if (maZaleznosci)
throw new BusException(
"Nie można usunąć dokumentu — istnieją powiązania (rezerwacja/dokumenty/korekty).".Translate());
using (var t = session.Logout(editMode: true))
{
dok.Delete(); // twarde usunięcie — tylko gdy bufor i brak zależności
t.Commit(); // CommitUI() w workerze/extenderze
}
session.Save(); // integralność weryfikowana także tutaj
Pułapki:
- Sprawdzaj zależności przed
Delete(). Próba usunięcia powiązanego dokumentu i tak zostanie odrzucona przez integralność (wyjątek wSave()), ale lepiej zdecydować świadomie i zwrócić czytelny komunikat. - Usunięcie usuwa też pozycje dokumentu — wykonuj je jedną transakcją; nie kasuj pozycji „ręcznie"
przed
dok.Delete(), jeśli i tak usuwasz cały dokument. - Gdy dokument jest zatwierdzony, najpierw cofnij go do bufora (HANDEL-W13). Jeśli cofnięcie jest zablokowane (okres zamknięty, podrzędne), rozważ anulowanie (HANDEL-W14) zamiast usuwania — anulowanie zachowuje numer i ścieżkę audytu.
- Rezerwacje rozwiązuje logika relacji/magazynu (workery rezerwacji są internal — z dodatku
operuj przez publiczne API relacji oraz pola
dok.Rezerwacja), nie kasuj rekordów rezerwacji bezpośrednio z dodatku. Delete()na dokumencie poza buforem (zatwierdzony/zablokowany/anulowany) jest zabronione — nie obchodź tego przez bezpośrednie operacje na tabeli.