SKILL: Uporządkowanie skills domenowych - podział na mniejsze pliki i wspólna numeracja
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
# HANDEL03 — Stany dokumentu i cykl życia
|
||||
|
||||
> Wspólne fakty o typie, podstawowe typy i szablon wzorca: [../handel.md](../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 w `Save()`) opisuje [`safe-code.md`](../safe-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ść `Zablokowany` ustawia **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:**
|
||||
|
||||
```csharp
|
||||
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()`** — samo `Commit()`/`CommitUI()` nie tworzy obrotów ani
|
||||
zasobów. Jeśli baza blokuje stan ujemny (weryfikator `StanUjemnyVerifier`, jak w bazie Demo),
|
||||
rozchód (FV/WZ/RW) wymaga **wcześniej zapisanego** przyjęcia (PW/PZ) tego towaru — inaczej `Save()`
|
||||
rzuci wyjątek.
|
||||
- Zatwierdzenie uruchamia walidatory dokumentu (kompletność pozycji, magazyn, kontrahent, tabela
|
||||
VAT). Błędy wychodzą w `Commit()`/`Save()` jako `RowException` — nie połykaj ich (safe-code §4).
|
||||
- W workerze/extenderze użyj `t.CommitUI()` zamiast `t.Commit()`
|
||||
([`worker-extender.md`](../worker-extender.md)).
|
||||
- Po `Save()` w środku jednej sesji zamyka się okno edycji; kolejna edycja na **tym samym** obiekcie
|
||||
bez ponownego `Logout` rzuci `AccessWriteDenied`. Wzorzec: zapis → odczyt na świeżej sesji.
|
||||
- Nie ustawiaj `Stan = Zablokowany` z 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:**
|
||||
|
||||
```csharp
|
||||
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 `Zablokowany` nie cofniesz przez `dok.Stan = Bufor` — blokada wynika z innego
|
||||
modułu (np. ewidencja zaksięgowana). Do diagnozy/naprawy rozbieżności stanu dokument↔ewidencja służy
|
||||
`PoprawaStanuDokumentuWorker` (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:**
|
||||
|
||||
```csharp
|
||||
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 → Bufor` na poziomie pola `Stan`.
|
||||
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`; akcja
|
||||
`public void NaprawStan()`; predykat widoczności
|
||||
`public static bool IsVisibleNaprawStan(DokumentHandlowy dokument)`. Worker sam zarządza
|
||||
transakcją wewnątrz `NaprawStan()` (synchronizuje `dok.Stan` z dokumentem ewidencji, w razie potrzeby
|
||||
tworzy/kasuje ewidencję, może przestawić `Stan` na `Zablokowany`/`Zatwierdzony`).
|
||||
- `PrzeliczenieStanuWorker`: enum `public enum Opcje { SprawdzićPoprawność, PoprawićTylkoBłędne,
|
||||
PrzeliczyćTylkoNiepoprawione, PonowniePrzeliczyć, PoprawićObroty, SprawdzićObroty }`; konstruktor
|
||||
publiczny `PrzeliczenieStanuWorker(Opcje wykonaj, bool wszystkieMagazyny, bool rozchód0, bool
|
||||
przywracajWartość)`; property `[Context]` `Dokument`, `Towar`, `Magazyny` (`Magazyn[]`); akcja
|
||||
`public void PrzeliczStan()`. Worker sam otwiera transakcje wewnątrz `PrzeliczStan()`.
|
||||
|
||||
**Snippet:**
|
||||
|
||||
```csharp
|
||||
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łasnym `session.Logout(true)` — wystarczy `session.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 przez
|
||||
`Trace`. Do faktycznej naprawy użyj `PoprawićTylkoBłędne`/`PonowniePrzeliczyć`.
|
||||
- `PrzeliczenieStanuWorker` rzuca `RowException`, 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.IsVisibleNaprawStan` zwraca `false` dla 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:**
|
||||
|
||||
```csharp
|
||||
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 w `Save()`), 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.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user