16 KiB
HANDEL09 — Korekty i dokumenty specjalne
Wspólne fakty o typie, podstawowe typy i szablon wzorca: ../handel.md.
Rozdział obejmuje korekty (ilościowe, ceny, wartości przyjęcia) oraz dokumenty „specjalne": inwentaryzację (INW), fakturę zaliczkową wraz z jej rozliczeniem oraz przesunięcie międzymagazynowe (MM). Wszystkie wzorce operują wyłącznie na publicznym kontrakcie platformy. Kluczowym narzędziem jest serwis relacji IRelacjeService (namespace Soneta.Handel.RelacjeDokumentow.Api), opisany w rozdziale o relacjach — tutaj koncentrujemy się na metodzie NowaKorekta oraz na specyfice każdego typu dokumentu.
Wspólne reguły (powtórzone z fundamentów,
safe-code.md):
- Dostęp do serwisu:
var rel = session.GetRequiredService<IRelacjeService>();(wymagausing Microsoft.Extensions.DependencyInjection;).- Dokument nadrzędny / korygowany musi być zatwierdzony (
StanDokumentuHandlowego.Zatwierdzony) przed wywołaniem relacji.- Każda modyfikacja w transakcji (
session.Logout(editMode: true)+Commit()/CommitUI()w workerze), potemsession.Save(). Magazyn księguje się dopiero poSave().- Pola
DokumentKorygowany,DokumentyKorygujące,DokumentyZaliczkowesą kalkulowane (read-only) — nie ustawiaj ich ręcznie; powstają jako efekt utworzenia relacji.
HANDEL-W48 — Korekta ilościowa i korekta ceny
Cel: utworzyć dokument korygujący do zatwierdzonej faktury / dokumentu magazynowego (zmiana ilości, ceny, rabatu lub VAT) i zapisać poprawione wartości na pozycjach korekty.
Warianty:
| Wariant | Wywołanie | Uwaga |
|---|---|---|
| Korekta pojedynczego dokumentu | NowaKorekta(new[]{ dok }, symbolKorekty) |
zwraca tablicę korekt (zwykle 1 element) |
| Korekta zbiorcza (wiele dok. → jedna) | NowaKorektaZbiorcza(korygowane, symbolKorekty) |
grupuje korygowane dokumenty |
| Domyślny symbol korekty | NowaKorekta(new[]{ dok }) (bez symbolu) |
platforma dobiera definicję korekty wg definicji korygowanego |
| Korekta ilościowa | po utworzeniu: zmiana poz.Ilosc na pozycji korekty |
różnica ilości |
| Korekta ceny / rabatu | zmiana poz.Cena / poz.Rabat |
różnica wartości |
| Korekta „do zera" (zwrot całości) | ustaw poz.Ilosc = Quantity.Zero (w jednostce pozycji) |
pełny storno |
Pola i typy:
IRelacjeService.NowaKorekta(DokumentHandlowy[] korygowane, string symbolKorekty = null, Context context = null, HandlerSet handlers = null): DokumentHandlowy[].IRelacjeService.NowaKorektaZbiorcza(DokumentHandlowy[] korygowane, string symbolKorekty = null, …): DokumentHandlowy[].- Na pozycji korekty:
PozycjaDokHandlowego.Ilosc: Quantity,Cena: DoubleCy,Rabat: Percent,PozycjaKorygowana(powiązanie z pozycją oryginału, read-only). - Odczyt powiązań:
dok.DokumentyKorygujące(kolekcja korekt),korekta.DokumentKorygowany(oryginał).
Snippet:
using Microsoft.Extensions.DependencyInjection;
using Soneta.Handel;
using Soneta.Handel.RelacjeDokumentow.Api;
using Soneta.Types;
// 1. Oryginał musi być zatwierdzony:
using (var t = session.Logout(editMode: true)) {
faktura.Stan = StanDokumentuHandlowego.Zatwierdzony;
t.Commit();
}
session.Save();
// 2. Utworzenie korekty przez serwis relacji:
var rel = session.GetRequiredService<IRelacjeService>();
DokumentHandlowy korekta;
using (var t = session.Logout(editMode: true)) {
korekta = rel.NowaKorekta(new[] { faktura }, "KWN")[0]; // symbol definicji korekty
// 3. Korekta ilościowa: zmiana ilości na pozycji korekty
// (pozycje korekty są wstępnie zainicjowane wartościami oryginału)
var poz = korekta.Pozycje.First();
poz.Ilosc = new Quantity(8, poz.Ilosc.Symbol); // było 10 -> korygujemy do 8
// 4. Korekta ceny / rabatu — alternatywnie:
// poz.Cena = new DoubleCy(4.5m, poz.Cena.Symbol);
// poz.Rabat = new Percent(0.15);
t.Commit();
}
session.Save();
// Odczyt powiązania:
DokumentHandlowy oryginal = korekta.DokumentKorygowany;
Pułapki:
NowaKorektazwraca tablicęDokumentHandlowy[]— dla jednego dokumentu bierz[0]/.Single().- Korygowany dokument musi być zatwierdzony; korekta do dokumentu w buforze nie powstanie.
- Pozycje korekty są inicjowane wartościami oryginału — modyfikujesz je „do wartości docelowej", a system sam policzy różnicę. Nie wpisuj różnicy „z palca".
symbolKorektyto symbol definicji korekty (np. „KWN", „KS"), a nie symbol korygowanej faktury. Definicja korekty musi istnieć i być odblokowana.- Całą sekwencję (utworzenie + edycja pozycji) wykonuj w jednej transakcji, dopiero potem
Save(). - Symbol jednostki na
Iloscmusi pochodzić z istniejącej pozycji (poz.Ilosc.Symbol) — nie twórzQuantityz gołą liczbą.
HANDEL-W49 — Korekta wartości przyjęcia magazynowego
Cel: skorygować ilość/wartość przyjęcia magazynowego (PZ/PW) tak, aby poprawić zaksięgowane obroty i partie dostaw.
Warianty:
| Wariant | Mechanizm publiczny |
|---|---|
| Korekta przyjęcia ilościowa | IRelacjeService.NowaKorekta(new[]{ przyjecie }, …) + korekta Ilosc na pozycji |
| Korekta wartości (ceny) przyjęcia | jw., zmiana Cena na pozycji korekty |
| Korekta wskazanej dostawy / partii | korekta z odwołaniem do partii — Soneta.Magazyny.GrupaDostaw |
Pola i typy: te same co HANDEL-W48 — IRelacjeService.NowaKorekta(...), PozycjaDokHandlowego.Ilosc/Cena, PozycjaKorygowana.
Snippet:
var rel = session.GetRequiredService<IRelacjeService>();
DokumentHandlowy korektaPrzyjecia;
using (var t = session.Logout(editMode: true)) {
// przyjecie = zatwierdzony dokument PZ/PW
korektaPrzyjecia = rel.NowaKorekta(new[] { przyjecie })[0];
var poz = korektaPrzyjecia.Pozycje.First();
poz.Ilosc = new Quantity(9, poz.Ilosc.Symbol); // przyjęto 10, korygujemy stan do 9
t.Commit();
}
session.Save(); // tu księgują się skorygowane obroty/partie
Pułapki:
- Dedykowany worker
UtworzKorektePrzyjeciaWorkerjestinternal— nie da się go zainstancjonować z dodatku zewnętrznego. Publiczny tor toIRelacjeService.NowaKorekta(wewnętrznie worker robi dokładnie to samo:NowaKorekta+ dostosowaniePozycje[].Iloscz uwzględnieniem obrotów/storn). - Korekta przyjęcia działa na zaksięgowanych obrotach i partiach — różnicowe wyliczenia ilości względem obrotów (
MagazynyModule.Obroty) i storn wykonuje platforma. Z poziomu publicznego kontraktu ustaw docelowąIlosc/Cenana pozycji korekty. - Magazyn (zasoby/obroty) aktualizuje się dopiero po
session.Save(), nie poCommit(). - Jeśli przyjęcie wskazywało partię/dostawę, korekta musi odnosić się do tej samej dostawy — przy złożonych scenariuszach (rozchody z tej partii, przesunięcia) korektę realizuj na pełnej, zalogowanej sesji aplikacyjnej.
HANDEL-W50 — Dokument inwentaryzacji (INW)
Cel: utworzyć dokument spisu z natury (INW), na którym wprowadza się stany rzeczywiste; system wylicza różnice (nadwyżka / strata) względem stanu ewidencyjnego i generuje dokumenty korygujące stan.
Warianty:
| Wariant | Charakterystyka |
|---|---|
| Spis z natury | pozycje = stan rzeczywisty zliczony fizycznie |
| Stan początkowy / bilans otwarcia | INW jako dokument ustalający stany na start |
| Nadwyżka | stan rzeczywisty > ewidencyjny → relacja InwentaryzacjaNadwyżka |
| Strata / niedobór | stan rzeczywisty < ewidencyjny → relacja InwentaryzacjaStrata |
| Inwentaryzacja wg partii / wskazania dostawy | spis z dokładnością do partii (GrupaDostaw) |
Pola i typy:
- Definicja:
session.GetHandel().DefDokHandlowych.WgSymbolu["INW"]. DokumentHandlowy.Magazyn(Soneta.Magazyny.Magazyn) — inwentaryzowany magazyn (wymagany).PozycjaDokHandlowego.Ilosc: Quantity— stan rzeczywisty.- Dokumenty różnic (odczyt):
dok.Podrzędne[...]/ relacje inwentaryzacyjne; różnica wartości dostępna na dokumencie różnicy (np.Ewidencja.Wartosc).
Snippet:
var hm = session.GetHandel();
var magazyny = session.GetMagazyny();
var towary = session.GetTowary();
DokumentHandlowy inw;
using (var t = session.Logout(editMode: true)) {
inw = new DokumentHandlowy();
session.AddRow(inw);
inw.Definicja = hm.DefDokHandlowych.WgSymbolu["INW"]; // definicja PIERWSZA
inw.Magazyn = magazyny.Magazyny.WgSymbol["F"]; // inwentaryzowany magazyn
// Pozycja = stan rzeczywisty zliczony fizycznie:
var poz = new PozycjaDokHandlowego(inw);
session.AddRow(poz);
poz.Towar = towary.Towary.WgKodu["BIKINI"]; // Towar PIERWSZY (inicjuje jednostkę)
poz.Ilosc = new Quantity(9, poz.Ilosc.Symbol); // ewidencyjnie 10 -> spis 9
inw.Stan = StanDokumentuHandlowego.Zatwierdzony; // zatwierdzenie wylicza różnice
t.Commit();
}
session.Save(); // tu powstają dokumenty różnic i korekta stanu
Pułapki:
- INW wymaga wskazanego magazynu; bez niego nie da się policzyć różnic.
- Różnice (nadwyżka/strata) i ich zaksięgowanie powstają przy zatwierdzeniu + Save, nie wcześniej. Dokumenty różnic to obiekty podrzędne — czytaj je przez kolekcje relacji, nie twórz ręcznie.
- Inwentaryzacja wg partii wymaga wskazania dostawy/partii (
Soneta.Magazyny.GrupaDostaw) — bez tego spis odnosi się do stanu zbiorczego. - W bazie Demo obowiązuje blokada stanu ujemnego (
StanUjemnyVerifier) — żeby spis miał sens, towar musi mieć wcześniejsze, zapisane przyjęcie (PW/PZ). - Nie modyfikuj wartości na dokumentach różnic ręcznie — to wynik wyliczeń platformy.
HANDEL-W51 — Faktura zaliczkowa i jej rozliczenie dokumentem końcowym
Cel: wystawić fakturę zaliczkową (FZAL) na poczet przyszłej dostawy, a następnie rozliczyć ją dokumentem końcowym (FV), tak by wartość końcowej została pomniejszona o wpłaconą zaliczkę.
Warianty:
| Wariant | Mechanizm |
|---|---|
| Utworzenie zaliczkowej z zamówienia | NowyPodrzednyIndywidualny(new[]{ zamowienie }, "FZAL") |
| Rozliczenie zaliczki na dokumencie końcowym | NowyPodrzednyIndywidualny(new[]{ zaliczkowa }, "FV", handlers: …) |
| Przenoszenie zaliczki na pozycje | callback WybierzDokumentyZaliczkoweCallback + DokumentHandlowyRealizacjaZaliczkiWorker |
| Przenoszenie zaliczki wg stawki VAT | callback WybierzZaliczkiWgStawkiVatCallback |
| Wiele zaliczek do jednej końcowej | dodaj wszystkie w callbacku (Wybrany = true dla każdej) |
Pola i typy:
IRelacjeService.NowyPodrzednyIndywidualny(DokumentHandlowy[] nadrzedne, string symbolPodrzednego, Context context = null, HandlerSet handlers = null): DokumentHandlowy[].HandlerSet.WybierzDokumentyZaliczkoweCallback: Action<DokumentDocelowy>— wskazanie zaliczek (tor „na pozycje").HandlerSet.WybierzZaliczkiWgStawkiVatCallback: Action<DokumentDocelowy>— tor „wg stawki VAT".- Worker publiczny do wskazania zaliczki:
DokumentHandlowyRealizacjaZaliczkiWorkerz property[Context] Dokument: DokumentHandlowy,[Context] Docelowy: DokumentDocelowy,Wybrany: bool. - Odczyt:
dok.DokumentyZaliczkowe(kalkulowane) — zaliczki powiązane z końcowym;dok.SumyVAT: SubTable<SumaVAT>;dok.BruttoCy.
Snippet:
using Microsoft.Extensions.DependencyInjection;
using Soneta.Handel;
using Soneta.Handel.RelacjeDokumentow.Api;
var rel = session.GetRequiredService<IRelacjeService>();
// zaliczkowa = zatwierdzona faktura zaliczkowa (FZAL).
// Rozliczamy ją dokumentem końcowym FV — callback wskazuje, które zaliczki przenieść:
DokumentHandlowy[] koncowy;
using (var t = session.Logout(editMode: true)) {
koncowy = rel.NowyPodrzednyIndywidualny(
new[] { zaliczkowa },
"FV",
handlers: new HandlerSet {
WybierzDokumentyZaliczkoweCallback = WybierzZaliczki
});
t.Commit();
}
session.Save();
// koncowy[0].BruttoCy == 0, jeśli zaliczka pokryła całość
// Callback: zaznacza wszystkie dokumenty zaliczkowe powiązane z dokumentem docelowym.
static void WybierzZaliczki(DokumentDocelowy target) {
var w = new DokumentHandlowyRealizacjaZaliczkiWorker { Docelowy = target };
foreach (var d in target.DokumentyZaliczkowe.Cast<DokumentHandlowy>()) {
w.Dokument = d;
w.Wybrany = true; // przenosi zaliczkę na dokument końcowy
}
}
Pułapki:
- Bez dostarczenia odpowiedniego callbacka (
WybierzDokumentyZaliczkoweCallback/WybierzZaliczkiWgStawkiVatCallback) domyślne handlery rzucająNotImplementedException— musisz wskazać tryb przenoszenia zaliczki zgodny z konfiguracją definicji końcowej (SposobPrzenoszeniaZaliczki:NaPozycjevsNaDokument). - Tryb przenoszenia (na pozycje / wg stawki VAT) jest cechą definicji dokumentu końcowego — użyj callbacka pasującego do konfiguracji, inaczej rozliczenie nie zadziała.
- Worker rozliczenia (
RealizacjaZaliczkiWorker, edytor kwot wg stawki) jestinternal— z dodatku używaj publicznegoDokumentHandlowyRealizacjaZaliczkiWorker(wskazanie dokumentów) wewnątrz callbacka. - Faktura zaliczkowa musi być zatwierdzona przed rozliczeniem;
DokumentyZaliczkoweto pole kalkulowane — nie ustawiasz go, czytasz. - Tabela VAT dokumentu zaliczkowego jest przeliczana proporcjonalnie do wpłaconej zaliczki (logika
DokumentZaliczkowyWorker) — nie modyfikujSumyVATręcznie.
HANDEL-W52 — Przesunięcie międzymagazynowe (MM)
Cel: przesunąć zasób z jednego magazynu do drugiego dokumentem MM — rozchód z magazynu źródłowego i przychód do magazynu docelowego w jednej operacji.
Warianty:
| Wariant | Mechanizm |
|---|---|
| Przesunięcie w obrębie firmy | MM z MagazynZ (źródło) i MagazynDo (cel) |
| Wskazanie partii / dostawy przy rozchodzie | pozycja z odwołaniem do GrupaDostaw |
| Korekta przesunięcia | IRelacjeService.NowaKorekta(new[]{ mm }, …) |
Pola i typy:
- Definicja:
session.GetHandel().DefDokHandlowych.WgSymbolu["MM"]. DokumentHandlowy.MagazynZ: Soneta.Magazyny.Magazyn— magazyn źródłowy (rozchód).DokumentHandlowy.MagazynDo: Soneta.Magazyny.Magazyn— magazyn docelowy (kalkulowane: ustawia magazyn na podrzędnym dokumencie przesunięciaPodrzędne[TypRelacjiHandlowej.PrzesunięcieDo]; wymaga, by dokument przesunięcia już istniał — ustawiaj poDefinicja).PozycjaDokHandlowego.Towar,Ilosc: Quantity.
Snippet:
var hm = session.GetHandel();
var magazyny = session.GetMagazyny();
var towary = session.GetTowary();
DokumentHandlowy mm;
using (var t = session.Logout(editMode: true)) {
mm = new DokumentHandlowy();
session.AddRow(mm);
mm.Definicja = hm.DefDokHandlowych.WgSymbolu["MM"]; // definicja PIERWSZA
mm.MagazynZ = magazyny.Magazyny.WgSymbol["F"]; // magazyn źródłowy
mm.MagazynDo = magazyny.Magazyny.WgNazwa["Magazyn 2"]; // magazyn docelowy (po ustawieniu definicji)
var poz = new PozycjaDokHandlowego(mm);
session.AddRow(poz);
poz.Towar = towary.Towary.WgKodu["BIKINI"]; // Towar PIERWSZY
poz.Ilosc = new Quantity(5, poz.Ilosc.Symbol);
mm.Stan = StanDokumentuHandlowego.Zatwierdzony;
t.Commit();
}
session.Save(); // tu księguje się rozchód ze źródła i przychód do celu
Pułapki:
MagazynDojest polem kalkulowanym delegującym do podrzędnego dokumentu przesunięcia — ustaw je poDefinicja(a najlepiej przed dodaniem pozycji), boIsReadOnlyMagazynDo()blokuje zmianę magazynu, gdy istnieją już pozycje.MagazynZiMagazynDomuszą być różne i oba dostępne (prawa do magazynów / przypisanie definicji do magazynu wg konfiguracjiOgólne.PrzypisanieDefinicjiDoMagazynu).- Rozchód MM podlega blokadzie stanu ujemnego (Demo:
StanUjemnyVerifier) — magazyn źródłowy musi mieć zapisany zasób przesuwanego towaru. - Obroty (rozchód + przychód) księgują się po
session.Save(), nie poCommit(). - Korektę przesunięcia wykonuj przez
IRelacjeService.NowaKorekta(jak w HANDEL-W48/HANDEL-W49); ręczna korekta partii przy MM jest złożona i wymaga pełnej sesji aplikacyjnej.