19 KiB
HANDEL05 — Odczyt i wyszukiwanie
Wspólne fakty o typie, podstawowe typy i szablon wzorca: ../handel.md.
Odczyt dokumentów handlowych prawie zawsze sprowadza się do filtrowania serwerowego: warunek
budujesz wyrażeniem LINQ i aplikujesz na kluczu tabeli (DokHandlowe.WgXxx[dok => …]) albo na
kolekcji podrzędnej (towar.Pozycje[…], dok.Pozycje[…]). Z bazy do pamięci trafiają wtedy
wyłącznie pasujące wiersze. DokHandlowe to duża tabela operacyjna (guided="Exported") —
nigdy nie iteruj jej w całości z if w pamięci; zawsze zawężaj zakres (okres, kontrahent, definicja)
przez SQL i — przy analizach poprzecznych — ogranicz przedział czasowy.
Fundamenty (sesja, transakcja, blokada optymistyczna) opisuje
safe-code.md, a mechanikę warunków serwerowychrowcondition.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. W wyrażeniu LINQ wolno użyć tylko pól bazodanowych; pole kalkulowane rzuciLinqConditionException.
Fakty o odczycie (zweryfikowane na tabeli DokHandlowe i PozycjeDokHan):
- Klucze tabeli
DokHandlowe(do filtrowania serwerowego i sortowania):WgDaty(Data,Czas),WgMagazynuNumer(Magazyn,Numer.Pelny),WgMagazynuObcy(Magazyn,Obcy.Numer),WgKontrahentaObcy(Kontrahent,Obcy.Numer,Kategoria),WgOkresIntrastat, orazPrimaryKey. Nie ma „gołego" kluczaWgKontrahentaaniWgNumeru— filtruj wyrażeniem na dowolnym z powyższych kluczy (sortowanie bierze się z wybranego klucza). - Indeksator po Guid:
hm.DokHandlowe[guid](zwracaDokumentHandlowy; rzucaRowNotFoundExceptiondla nieznanego Guid). - Pozycje dokumentu:
dok.Pozycje—LpSubTable<PozycjaDokHandlowego>(sortowane poLp). - Pozycje danego towaru (historia obrotu):
towar.Pozycje—SubTable<PozycjaDokHandlowego>(kluczWgTowar). Klucze naPozycjeDokHan:WgDaty(Data),WgKierunek(Towar,KierunekMagazynu,Data,Czas),WgTowarDokumentu(Towar,Dokument). - Numer dokumentu: pole
dok.Numer: NumerDokumentu. Pełny numer do odczytu todok.Numer.NumerPelny(kalkulowane). W warunku serwerowym używaj pola bazodanowegoNumer.Pelny(np.dok => dok.Numer.Pelny == "FV 1/2026"). - Korekty:
dok.DokumentKorygowany(dokument korygowany przez tę korektę),dok.DokumentyKorygujące(IEnumerable<DokumentHandlowy>— łańcuch korekt tego dokumentu),dok.Korekta: bool(pole bazodanowe — czy dokument jest korektą). Wszystkie powiązania korekt to pola kalkulowane (opróczKorekta). - Kolekcje na
Kontrahent(z modułu CRM):k.DokumentyHandloweik.DokumentyHandloweOdbiorcyto nietypowaneSubTable(CRM nie referuje Handlu). Iteracja działa, ale typowane filtrowanie serwerowe rób od strony Handlu:hm.DokHandlowe.WgKontrahentaObcy[dok => dok.Kontrahent == k].
HANDEL-W25 — Odczytanie pozycji dokumentu
Cel: przejść po pozycjach (towar, ilość, cena, rabat, wartość) wczytanego dokumentu — np. do wydruku, eksportu czy przeliczeń własnych.
Warianty:
| Wariant | Źródło / operacja |
|---|---|
| Wszystkie pozycje wg Lp | dok.Pozycje (LpSubTable, sortowane po Lp) |
| Tylko pozycje danego towaru | dok.Pozycje[(PozycjaDokHandlowego p) => p.Towar == towar] |
| Pozycje o niezerowej ilości | warunek serwerowy na p.Ilosc.Value |
| Wartości pozycji | p.WartoscCy, p.Suma (BruttoNetto: NettoCy/VATCy/BruttoCy) |
Pola i typy (PozycjaDokHandlowego): Towar: Towar, Ilosc: Quantity
(.Value, .Symbol), Cena: DoubleCy, Rabat: Percent, WartoscCy: Currency,
Suma: BruttoNetto (NettoCy, VATCy, BruttoCy — typ Currency; Netto/VAT/Brutto — decimal),
Lp: int, Stawka: StawkaVat, Opis: string.
Snippet:
var hm = session.GetHandel();
var dok = hm.DokHandlowe[guid]; // dokument wczytany po Guid (HANDEL-W29)
if (dok == null) return;
// Iteracja po pozycjach (LpSubTable jest już posortowana po Lp):
foreach (PozycjaDokHandlowego p in dok.Pozycje)
{
string towar = p.Towar?.Kod;
Quantity ilosc = p.Ilosc; // p.Ilosc.Value + p.Ilosc.Symbol (jednostka)
DoubleCy cena = p.Cena;
Percent rabat = p.Rabat;
Currency netto = p.Suma.NettoCy; // wartość netto pozycji w PLN
Currency brutto = p.Suma.BruttoCy;
Currency wartosc = p.WartoscCy; // wartość pozycji w walucie ceny
}
// Tylko pozycje wybranego towaru — filtr serwerowy na kolekcji:
var towar = session.GetTowary().Towary.WgKodu["BIKINI"];
foreach (PozycjaDokHandlowego p in dok.Pozycje[(PozycjaDokHandlowego p) => p.Towar == towar])
{
// ...
}
Pułapki:
IlosctoQuantity, aCena/WartoscCytoDoubleCy/Currency(kwota + waluta), niedecimal/double(safe-code §10). Składowe:p.Ilosc.Value,p.Ilosc.Symbol.- Do filtrowania pozycji na jednym dokumencie możesz iterować
dok.Pozycje(to mała kolekcja), ale i tak preferuj warunekdok.Pozycje[p => …]— wykona się serwerowo. p.Suma/p.WartoscCysą przeliczane przez platformę — czytaj je, nie wyliczaj „ręcznie".p.Towarbywanulldla pozycji nietowarowych (opis/koszt) — zabezpiecz dostęp (?.).
HANDEL-W26 — Odczytanie dokumentów dla kontrahenta
Cel: pobrać dokumenty wystawione na danego kontrahenta — jako nabywcę (Kontrahent) lub jako
odbiorcę (Odbiorca).
Warianty:
| Wariant | Źródło | Typ |
|---|---|---|
| Kontrahent jako nabywca (kolekcja CRM) | k.DokumentyHandlowe |
nietypowany SubTable |
| Odbiorca (kolekcja CRM) | k.DokumentyHandloweOdbiorcy |
nietypowany SubTable |
| Filtr typowany od strony Handlu | hm.DokHandlowe.WgKontrahentaObcy[dok => dok.Kontrahent == k] |
SubTable<DokumentHandlowy> |
| Zawężenie okresem | dołóż && dok.Data >= od w warunku |
— |
Pola i typy: dok.Kontrahent: Kontrahent, dok.Odbiorca: Kontrahent (oba bazodanowe).
Kontrahent.DokumentyHandlowe / DokumentyHandloweOdbiorcy to kolekcje SubTable na kontrahencie
(zawężone już do jednego kontrahenta).
Snippet:
var hm = session.GetHandel();
var k = session.GetCRM().Kontrahenci.WgKodu["Abc"];
if (k == null) return;
// Wariant A — kolekcja na kontrahencie (nietypowana, ale wygodna do prostego przejścia):
foreach (DokumentHandlowy dok in k.DokumentyHandlowe)
{
// dok.Numer.NumerPelny, dok.Data, dok.Suma ...
}
// Wariant B — typowany filtr serwerowy od strony Handlu + zawężenie okresem
// (klucz WgKontrahentaObcy nadaje sortowanie wg kontrahenta):
var od = Date.Today.AddMonths(-3);
foreach (DokumentHandlowy dok in hm.DokHandlowe.WgKontrahentaObcy[
(DokumentHandlowy dok) => dok.Kontrahent == k && dok.Data >= od])
{
// tylko dokumenty kontrahenta z ostatnich 3 miesięcy
}
// Dokumenty, w których kontrahent jest ODBIORCĄ:
foreach (DokumentHandlowy dok in hm.DokHandlowe[
(DokumentHandlowy dok) => dok.Odbiorca == k])
{
// ...
}
Pułapki:
k.DokumentyHandlowejest nietypowane (SubTable, nieSubTable<DokumentHandlowy>) — pętlaforeach (DokumentHandlowy …)działa, ale do filtrowania wyrażeniem LINQ użyj kolekcji od strony Handlu (hm.DokHandlowe.WgXxx[…]), gdzie typ wiersza jest znany kompilatorowi.KontrahentiOdbiorcato dwa różne pola — wybierz świadomie (nabywca ≠ odbiorca towaru).- To dane operacyjne — przy szerokich analizach zawężaj okres (
dok.Data >= od), nie ładuj całej historii (safe-code §6.3). - Porównuj po referencji rekordu (
dok.Kontrahent == k), a nie poKod— referencja generuje szybkieJOINpoID.
HANDEL-W27 — Ostatnie pozycje dokumentów dla wskazanego towaru
Cel: prześledzić historię obrotu danym towarem — pozycje dokumentów, w których towar wystąpił (np. ostatnie zakupy/sprzedaże, kierunek magazynowy, ceny historyczne).
Warianty:
| Wariant | Źródło / warunek |
|---|---|
| Wszystkie pozycje towaru | towar.Pozycje (klucz WgTowar) |
| Tylko rozchody / przychody | filtr na p.KierunekMagazynu (KierunekPartii) |
| Z zakresu dat | towar.Pozycje[p => p.Data >= od] |
| Tylko z dokumentów zatwierdzonych | warunek przez referencję: p.Dokument.Stan == StanDokumentuHandlowego.Zatwierdzony |
| Ostatnie N po dacie | sortuj kluczem WgKierunek/WgDaty i ogranicz w pamięci po zawężeniu |
Pola i typy (PozycjaDokHandlowego): Towar: Towar, Dokument: DokumentHandlowy,
Data: Date, Czas: Time, KierunekMagazynu: Soneta.Magazyny.KierunekPartii
(Rozchód=-1, Brak=0, Przychód=1), Cena: DoubleCy, Ilosc: Quantity. Kolekcja
towar.Pozycje: SubTable<PozycjaDokHandlowego>.
Snippet:
var towar = session.GetTowary().Towary.WgKodu["BIKINI"];
if (towar == null) return;
// Pozycje towaru z ostatnich 6 miesięcy — filtr serwerowy na kolekcji towaru:
var od = Date.Today.AddMonths(-6);
foreach (PozycjaDokHandlowego p in towar.Pozycje[(PozycjaDokHandlowego p) => p.Data >= od])
{
DokumentHandlowy dok = p.Dokument; // dokument macierzysty pozycji
string numer = dok.Numer.NumerPelny;
// p.KierunekMagazynu, p.Ilosc, p.Cena, p.Data ...
}
// Tylko rozchody (sprzedaż/wydania) danego towaru z dokumentów zatwierdzonych:
foreach (PozycjaDokHandlowego p in towar.Pozycje[(PozycjaDokHandlowego p) =>
p.KierunekMagazynu == KierunekPartii.Rozchód
&& p.Dokument.Stan == StanDokumentuHandlowego.Zatwierdzony
&& p.Data >= od])
{
// historia rozchodów towaru
}
Pułapki:
- Filtruj na
towar.Pozycje[…](kolekcja zawężona do jednego towaru), nie iteruj globalniePozycjeDokHan— to jedna z największych tabel operacyjnych (safe-code §6.3). - Warunek przez referencję (
p.Dokument.Stan == …) jest dozwolony —Stanjest polem bazodanowym i wygenerujeJOIN. Nie używaj w warunku pól kalkulowanych dokumentu (np.p.Dokument.ZatwierdzonyrzuciLinqConditionException). - „Ostatnie N" realizuj przez sortowanie kluczem (
WgKierunek/WgDaty) po zawężeniu okresem; nie pobieraj całości po to, by wziąć kilka rekordów. KierunekPartiiżyje wSoneta.Magazyny— wymagana referencja do modułu Magazyny.
HANDEL-W28 — Wyszukiwanie dokumentów wg okresu, definicji, stanu, serii
Cel: odfiltrować dokumenty po kryteriach nagłówkowych (data, definicja, stan, magazyn, seria)
serwerowo, bez obiektów warstwy UI (View).
Warianty:
| Wariant | Warunek (pole bazodanowe) |
|---|---|
| Okres dat | dok.Data >= od && dok.Data <= do |
| Konkretna definicja (symbol) | dok.Definicja == def (rekord z DefDokHandlowych.WgSymbolu[...]) |
| Stan dokumentu | dok.Stan == StanDokumentuHandlowego.Zatwierdzony |
| Magazyn | dok.Magazyn == mag |
| Seria | dok.Seria == "A" |
| Wiele kryteriów | koniunkcja && / alternatywa ` |
Pola i typy: dok.Data: Date, dok.Definicja: DefDokHandlowego,
dok.Stan: StanDokumentuHandlowego, dok.Magazyn: Magazyn, dok.Seria: string,
dok.Kategoria: KategoriaHandlowa. Klucz WgDaty daje sortowanie po dacie.
Snippet:
var hm = session.GetHandel();
var def = hm.DefDokHandlowych.WgSymbolu["FV"]; // definicja faktury sprzedaży
var mag = session.GetMagazyny().Magazyny.WgSymbol["F"];
var od = new Date(2026, 1, 1);
var doDt = new Date(2026, 3, 31);
// Zatwierdzone faktury FV z I kwartału na magazynie F — jeden warunek serwerowy.
// Klucz WgDaty nadaje sortowanie po Data, Czas:
foreach (DokumentHandlowy dok in hm.DokHandlowe.WgDaty[(DokumentHandlowy dok) =>
dok.Definicja == def
&& dok.Magazyn == mag
&& dok.Stan == StanDokumentuHandlowego.Zatwierdzony
&& dok.Data >= od && dok.Data <= doDt])
{
// dok.Numer.NumerPelny, dok.Suma, dok.Kontrahent ...
}
// Wariant: warunek jako wartość przekazywana dalej (np. do metody):
var cond = RowCondition.FromExpression<DokumentHandlowy>(
dok => dok.Definicja == def && dok.Seria == "A");
foreach (DokumentHandlowy dok in hm.DokHandlowe.WgDaty[cond]) { /* ... */ }
Pułapki:
- Nie używaj
Vieww kodzie biznesowym (to obiekt UI) — filtrujSubTable[expression]lubRowCondition.FromExpression(rowcondition.md). - Porównuj definicję/magazyn po rekordzie (
dok.Definicja == def), nie po stringu symbolu — rekord pobierz raz przezWgSymbolu[...]/WgSymbol[...]poza pętlą. - Stan porównuj enumem (
dok.Stan == StanDokumentuHandlowego.Zatwierdzony); skrótydok.Zatwierdzonysą kalkulowane i nie wolno ich użyć w warunku LINQ. - Wybór klucza (
WgDaty,WgMagazynuNumer,WgKontrahentaObcy) decyduje tylko o sortowaniu — warunek i tak trafia doWHERE. Dla dużych zbiorów dobierz klucz pasujący do oczekiwanej kolejności.
HANDEL-W29 — Odczyt dokumentu wg numeru lub Guid
Cel: odnaleźć pojedynczy dokument po jego pełnym numerze (Numer.Pelny) albo po globalnym
identyfikatorze Guid (np. zapisanym wcześniej w innym systemie / w teście).
Warianty:
| Wariant | Mechanizm | Zwraca |
|---|---|---|
| Po Guid | hm.DokHandlowe[guid] (indeksator GuidedTable) |
DokumentHandlowy; rzuca RowNotFoundException, gdy brak |
| Po pełnym numerze | filtr serwerowy dok => dok.Numer.Pelny == numer |
zbiór (bierz .FirstOrDefault()) |
| Po numerze w obrębie magazynu | klucz WgMagazynuNumer (Magazyn + Numer.Pelny) |
precyzyjniej (numer bywa unikalny per magazyn) |
| Po numerze obcym | klucz WgMagazynuObcy / pole dok.Obcy.Numer |
dokument z numerem dostawcy |
Pola i typy: dok.Numer: NumerDokumentu (odczyt pełnego numeru: dok.Numer.NumerPelny;
pole bazodanowe w warunku: Numer.Pelny), dok.Guid: Guid (z GuidedRow),
dok.Obcy.Numer: string (numer dokumentu obcego).
Snippet:
var hm = session.GetHandel();
// 1. Po Guid — najpewniejszy, jednoznaczny dostęp. UWAGA: indeksator GuidedTable RZUCA
// RowNotFoundException dla nieznanego Guid (nie zwraca null) — obuduj try/catch, gdy brak pewności:
DokumentHandlowy poGuid;
try { poGuid = hm.DokHandlowe[guid]; }
catch (Soneta.Business.RowNotFoundException) { poGuid = null; }
// 2. Po pełnym numerze — warunek serwerowy na polu bazodanowym Numer.Pelny.
// Numer może się powtarzać między magazynami, więc bierzemy pierwszy / iterujemy:
DokumentHandlowy poNumerze = hm.DokHandlowe.WgMagazynuNumer[
(DokumentHandlowy dok) => dok.Numer.Pelny == "FV 1/2026"].FirstOrDefault();
// 3. Po numerze w obrębie magazynu (precyzyjniej — numeracja zwykle per magazyn):
var mag = session.GetMagazyny().Magazyny.WgSymbol["F"];
DokumentHandlowy wMagazynie = hm.DokHandlowe.WgMagazynuNumer[(DokumentHandlowy dok) =>
dok.Magazyn == mag && dok.Numer.Pelny == "FV 1/2026"].FirstOrDefault();
if (poGuid != null)
{
string pelny = poGuid.Numer.NumerPelny; // odczyt pełnego numeru (kalkulowane)
}
Pułapki:
- W warunku LINQ używaj pola bazodanowego
Numer.Pelny; do odczytu sformatowanego numeru służy kalkulowanedok.Numer.NumerPelny— w wyrażeniu serwerowym rzuciłobyLinqConditionException. - Pełny numer nie jest globalnie unikalny (numeracja bywa per magazyn/seria/rok) — dlatego filtr
zwraca zbiór; bierz
.FirstOrDefault()albo dołóżdok.Magazyn == mag. - Indeksator
hm.DokHandlowe[guid]to dostęp poGuid(zGuidedTable) — dla nieznanegoGuidrzucaSoneta.Business.RowNotFoundException(NIE zwracanull). Gdy brak pewności istnienia, obuduj gotry/catch. Nie myl z dostępem poID(klucz wewnętrzny tabeli). - Numer obcy (dostawcy) jest w
dok.Obcy.Numer— to inne pole niż własnyNumer.
HANDEL-W30 — Korekty dokumentu i dokument korygowany
Cel: dla danego dokumentu ustalić jego korekty (dokumenty korygujące) oraz — dla korekty — dokument, który koryguje.
Warianty:
| Wariant | Pole / kierunek | Typ |
|---|---|---|
| Dokument korygowany przez tę korektę | korekta.DokumentKorygowany |
DokumentHandlowy (lub null) |
| Wszystkie korekty danego dokumentu | dok.DokumentyKorygujące |
IEnumerable<DokumentHandlowy> (łańcuch) |
| Najbliższa korekta | dok.DokumentKorygujący |
DokumentHandlowy (lub null) |
| Ostatnia korekta w łańcuchu | dok.DokumentKorygującyOstatni |
DokumentHandlowy |
| Czy dokument jest korektą | dok.Korekta |
bool (pole bazodanowe) |
| Serwerowy filtr korekt | hm.DokHandlowe[d => d.Korekta] |
SubTable<DokumentHandlowy> |
Pola i typy: dok.Korekta: bool (bazodanowe — czy dokument jest korektą),
dok.DokumentKorygowany: DokumentHandlowy, dok.DokumentyKorygujące: IEnumerable<DokumentHandlowy>,
dok.DokumentKorygujący/DokumentKorygującyOstatni: DokumentHandlowy,
dok.DokumentyKorygowane: IEnumerable<DokumentHandlowy> (cały łańcuch korygowanych) —
wszystkie powiązania kalkulowane (tylko do odczytu; korekty zakładaj przez IRelacjeService).
Snippet:
var hm = session.GetHandel();
var dok = hm.DokHandlowe[guid];
if (dok == null) return;
// Korekty tego dokumentu (łańcuch korekt — kolejne korekty korekt):
foreach (DokumentHandlowy korekta in dok.DokumentyKorygujące)
{
string nr = korekta.Numer.NumerPelny;
DokumentHandlowy korygowany = korekta.DokumentKorygowany; // wskazuje z powrotem na dok
}
// Gdy mamy w ręku korektę — odczyt dokumentu korygowanego:
if (dok.Korekta)
{
DokumentHandlowy zrodlo = dok.DokumentKorygowany; // dokument pierwotny
}
// Serwerowe wyszukanie samych korekt w okresie (pole Korekta jest bazodanowe):
var od = Date.Today.AddMonths(-1);
foreach (DokumentHandlowy k in hm.DokHandlowe.WgDaty[(DokumentHandlowy d) =>
d.Korekta && d.Data >= od])
{
// d.DokumentKorygowany — dokument, którego dotyczy korekta
}
Pułapki:
DokumentKorygowany/DokumentyKorygujące/DokumentKorygującysą kalkulowane (liczone z relacji handlowych) — tylko do odczytu. Tworzenie korekt realizujeIRelacjeService.NowaKorekta(...)(rozdział o relacjach), nie przypisywanie tych pól.- W warunku serwerowym wolno użyć tylko pola
Korekta(bazodanowe). Pola powiązań korekt są kalkulowane → w LINQ rzucąLinqConditionException. DokumentKorygowanyzwracanull, gdy dokument nie jest korektą (Korekta == false) — zawsze sprawdźdok.Korektaalbo!= nullprzed użyciem.DokumentyKorygująceto łańcuch (korekta korekty korekty…), a nie pojedynczy element — gdy potrzebujesz tylko najbliższej, użyjDokumentKorygujący; gdy ostatniej —DokumentKorygującyOstatni.