# KADRY04 — Nieobecności i czas pracy > Wspólne fakty o typie, podstawowe typy i szablon wzorca: [../kadry.md](../kadry.md). ### KADRY-D1 — Wprowadzanie nieobecności (★) **Cel:** zarejestrować nieobecność pracownika (urlop wypoczynkowy, zwolnienie chorobowe, urlop bezpłatny, opieka itp.) za wskazany okres oraz odczytać nieobecności obowiązujące w danym przedziale dat. **Fakty o typie (zweryfikowane skanem DLL):** - **`Soneta.Kalend.Nieobecnosc` jest klasą abstrakcyjną** — tabela `Nieobecnosci` (`GuidedRow` root, child `Pracownik`-a). `new Nieobecnosc(...)` się nie skompiluje. - Konkretny typ do tworzenia: **`Soneta.Kalend.NieobecnośćPracownika`** (dziedziczy z `Nieobecnosc`), z **publicznym konstruktorem `new NieobecnośćPracownika(Pracownik pracownik)`** — ctor od razu wiąże nieobecność z pracownikiem. (Drugi konkretny typ to `KorektaNieobecności` — patrz KADRY-D2.) - Kolekcja na pracowniku: **`pracownik.Nieobecnosci: FromToSubTable`** (uporządkowana po okresie „od–do"). - Tabela z poziomu modułu: `session.GetKalend().Nieobecnosci`. **Pola i typy (`Nieobecnosc` / `NieobecnośćPracownika`):** | Pole | Typ | Rodzaj | Opis | |---|---|---|---| | `Definicja` | `Soneta.Kalend.DefinicjaNieobecnosci` | bazodanowe, **zapisywalne** | rodzaj nieobecności (słownik konfiguracyjny); decyduje o typie (urlop / zasiłek / bezpłatny) | | `Okres` | `Soneta.Types.FromTo` | bazodanowe, **zapisywalne** | zakres dat nieobecności „od–do" | | `OdGodziny`, `DoGodziny` | `Soneta.Types.Time` | — | godziny (nieobecności godzinowe) | | `Norma`, `NormaNie` | `Soneta.Types.Time` | bazodanowe | normy czasowe | | `IlośćDni` / `Dni` | `int` | kalkulowane/zapisywalne | liczba dni nieobecności | | `Pracownik` | `Soneta.Kadry.Pracownik` | **tylko do odczytu** | właściciel (ustawiany ctorem, nie da się zmienić setterem) | | `Zwolnienie` | `Soneta.Kalend.ZwolnienieZUS` (subrow) | bazodanowe | dane ZUS dla zwolnień chorobowych (`KodChoroby`, `Numer` ZLA, `PonownieUstalPodstawe`…) | | `Urlop`, `Macierzynski`, `Wychowawczy`, `Okolicznosciowy` | subrowy | bazodanowe | szczegóły poszczególnych typów urlopów | | `Korygowana` | `bool` | **tylko do odczytu** | czy nieobecność jest korektą (patrz KADRY-D2) | **Dostęp do definicji nieobecności (`DefNieobecnosci`):** - `session.GetKalend().DefNieobecnosci.WgNazwy[string]` — pobranie po nazwie, np. `WgNazwy["Urlop wypoczynkowy"]`, `WgNazwy["Zwolnienie chorobowe"]`, `WgNazwy["Urlop bezpłatny (art 174 kp)"]`. Nazwy muszą **dokładnie** odpowiadać słownikowi danej bazy (w Demo nie ma wpisu „Urlop bezpłatny" — jest „Urlop bezpłatny (art 174 kp)"); `WgNazwy[...]` dla nieistniejącej nazwy zwraca `null`. - `session.GetKalend().DefNieobecnosci[string]` (indeksator domyślny po nazwie) — równoważne. - `DefinicjaNieobecnosci` ma pola `Nazwa: string`, `Kod: string`, `Typ: TypNieobecnosci`. **Wyszukiwanie po dacie/okresie:** `pracownik.Nieobecnosci.GetIntersectedRows(FromTo)` zwraca `IList` nieobecności przecinających podany przedział. **Snippet:** ```csharp var kalend = session.GetKalend(); var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; // Nieobecność BEZ limitu (np. urlop bezpłatny) można wprowadzić wprost. Dla nieobecności // LIMITOWANYCH (urlop wypoczynkowy) najpierw musi istnieć naliczony limit — patrz pułapki i KADRY-D7. var defNieob = kalend.DefNieobecnosci.WgNazwy["Urlop bezpłatny (art 174 kp)"]; using (var t = session.Logout(editMode: true)) { // typ konkretny; ctor wiąże nieobecność z pracownikiem var nieobecnosc = session.AddRow(new NieobecnośćPracownika(pracownik)); nieobecnosc.Definicja = defNieob; // rodzaj nieobecności nieobecnosc.Okres = new FromTo(new Date(2026, 7, 1), new Date(2026, 7, 5)); t.Commit(); } session.Save(); // Odczyt nieobecności przecinających lipiec 2026: var lipiec = new FromTo(new Date(2026, 7, 1), new Date(2026, 7, 31)); foreach (Nieobecnosc n in pracownik.Nieobecnosci.GetIntersectedRows(lipiec)) { // n.Definicja.Nazwa, n.Okres, n.Dni } ``` **Pułapki:** - **Nie** rób `new Nieobecnosc(...)` — typ jest abstrakcyjny. Używaj `new NieobecnośćPracownika(pracownik)`. - **Nieobecności limitowane wymagają istniejącego limitu.** Ustawienie `Okres` dla nieobecności powiązanej z limitem (np. „Urlop wypoczynkowy") synchronicznie przelicza limit i rzuca `Soneta.Kalend.DefinicjaLimitu.LimitNotFoundException`, gdy pracownik nie ma naliczonego limitu na dany rok. Dlatego: albo najpierw nalicz limit (patrz KADRY-D7), albo użyj nieobecności bez limitu (np. „Urlop bezpłatny (art 174 kp)") — jak w snippetcie powyżej. - `Definicja` jest **wymagana** — bez niej nieobecność nie zostanie poprawnie naliczona/zapisana. Pobieraj istniejący wpis słownika przez `DefNieobecnosci.WgNazwy[...]`, nie twórz „w locie". - `Pracownik` jest **tylko do odczytu** — relację ustawia konstruktor, nie da się jej później zmienić. - Tabela `Nieobecnosci` jest **operacyjna guided** — przy przeglądaniu poprzecznym (po wszystkich pracownikach) filtruj zakresem czasowym (safe-code §6.3). W zakresie jednego pracownika korzystaj z `pracownik.Nieobecnosci` i `GetIntersectedRows`. - Nakładające się nieobecności i niepoprawne okresy wychwytują weryfikatory przy `Save()` (`RowException`) — obsłuż wyjątek. - Pełna transakcja w `session.Logout(editMode: true)`; brak `Commit()` = rollback przy `Dispose()`. --- ### KADRY-D2 — Korygowanie nieobecności już wypłaconych (★) **Cel:** poprawić nieobecność, która została już rozliczona w wypłacie — zmienić jej okres lub typ (definicję) i/lub wymusić ponowne ustalenie podstawy naliczania zasiłku. enova rozróżnia dwie ścieżki: (a) **modyfikacja istniejącej nieobecności** + ponowne ustalenie podstawy, (b) **korekta** jako odrębny rekord typu `KorektaNieobecności`. **Fakty o typie (zweryfikowane skanem DLL):** - Pola `Definicja: DefinicjaNieobecnosci` i `Okres: FromTo` na `Nieobecnosc` są **zapisywalne** publicznie — można je zmienić na istniejącym rekordzie. - `Nieobecnosc.Korygowana: bool` i `Nieobecnosc.Pracownik` są **tylko do odczytu**. - Subrow `Zwolnienie: Soneta.Kalend.ZwolnienieZUS` posiada flagę `PonownieUstalPodstawe: bool` oraz **publiczną metodę `SetPonownieUstalPodstawe(bool)`** — to ona steruje przeliczeniem podstawy zasiłku przy kolejnym naliczeniu wypłaty. - Worker (czynność menu, `DataType = Nieobecnosc`): klasa **`Soneta.Kalend.Nieobecnosc.UstalPonowniePodstawęNaliczaniaWorker`** — czynność „Ustal ponownie podstawę naliczania". Worker: - ma publiczny bezparametrowy ctor; - przyjmuje kontekst przez settowalną property `[Context] public Params Nieobecność`; - klasa `…Worker.Params : ContextBase` ma **publiczny ctor `Params(Context context)`**, który czyta nieobecność z `context[typeof(Nieobecnosc)]`, oraz settowalną property `UstalPodstawę: bool`; - metoda `public void PonownieUstalPodstawę()` jest jego akcją; - `static bool IsEnabledPonownieUstalPodstawę(Nieobecnosc)` mówi, kiedy czynność jest aktywna (dotyczy zwolnień ZUS i urlopów macierzyńskich: `Zwolnienie.IsZUS || Macierzynski.IsMacierzyński`, przy braku `BlokadaOkresu`). - Drugi konkretny typ nieobecności: **`Soneta.Kalend.KorektaNieobecności`** (dziedziczy `Nieobecnosc`), z **publicznym ctor `new KorektaNieobecności(NieobecnośćPracownika nieobecność)`** — tworzy rekord korygujący wskazaną nieobecność. Ma zapisywalne `Definicja`, `Okres`, `IlośćDni`, `RozliczenieWDniu`, `RozliczenieData`, a kolekcje `ElementyKorygowane`/`ElementyKorygowaneStorno` są tylko do odczytu (wyliczane). **Wariant A — zmiana okresu/typu + ponowne ustalenie podstawy (modyfikacja istniejącego rekordu):** ```csharp var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; var okresStary = new FromTo(new Date(2026, 3, 2), new Date(2026, 3, 10)); // odszukaj istniejącą (już rozliczoną) nieobecność po przecięciu z okresem var nieobecnosc = (Nieobecnosc)pracownik.Nieobecnosci.GetIntersectedRows(okresStary)[0]; using (var t = session.Logout(editMode: true)) { nieobecnosc.Okres = new FromTo(new Date(2026, 3, 2), new Date(2026, 3, 12)); // wydłużenie okresu // dla zwolnień ZUS — wymuś ponowne ustalenie podstawy przy najbliższym naliczeniu wypłaty: nieobecnosc.Zwolnienie.SetPonownieUstalPodstawe(true); t.Commit(); } session.Save(); ``` **Wariant B — czynność „Ustal ponownie podstawę naliczania" przez worker (kontekst):** ```csharp var worker = new Nieobecnosc.UstalPonowniePodstawęNaliczaniaWorker(); var ctx = Context.Empty.Clone(session); ctx[typeof(Nieobecnosc)] = nieobecnosc; // worker czyta nieobecność z kontekstu worker.Nieobecność = new Nieobecnosc.UstalPonowniePodstawęNaliczaniaWorker.Params(ctx) { UstalPodstawę = true }; worker.PonownieUstalPodstawę(); // wykonuje własną transakcję + Commit session.Save(); ``` **Wariant C — odrębny rekord korekty (`KorektaNieobecności`):** ```csharp using (var t = session.Logout(editMode: true)) { var nPrac = (NieobecnośćPracownika)nieobecnosc; // korekta dotyczy NieobecnośćPracownika var korekta = session.AddRow(new KorektaNieobecności(nPrac)); korekta.Definicja = nPrac.Definicja; // Okres korekty MUSI być podzbiorem okresu korygowanej nieobecności (tu: 2..10): korekta.Okres = new FromTo(new Date(2026, 3, 3), new Date(2026, 3, 8)); t.Commit(); } session.Save(); // Po zapisie korygowana nieobecność ma flagę Korygowana == true. ``` **Pułapki:** - **Faktyczne** przeliczenie wartości zasiłku NIE następuje w momencie ustawienia flagi/wywołania workera — flaga `PonownieUstalPodstawe` jest odczytywana dopiero przy **ponownym naliczeniu wypłaty** (mechanizm `PodstawaZasilku`). Sam test korekty rekordu nieobecności (Demo, rollback) zweryfikuje zmianę `Okres`/`Definicja`/flagi, ale **nie zweryfikuje przeliczonych kwot wypłaty** bez pełnego scenariusza naliczenia listy płac (patrz sekcja „funkcjonalności niewykonalne"). - `IsEnabledPonownieUstalPodstawę` ogranicza czynność do zwolnień ZUS / macierzyńskich — dla zwykłego urlopu wypoczynkowego worker nie ma zastosowania; tam korektę robisz przez zmianę `Okres`/`Definicja` albo rekord `KorektaNieobecności`. - **Okres korekty (`KorektaNieobecności.Okres`) musi być podzbiorem okresu korygowanej nieobecności** — wyjście poza ten zakres rzuca `Nieobecnosc.KorygowanyOkresException`. - Dla nieobecności bez skutków płacowych (np. urlop bezpłatny) `KorektaNieobecności` **nie pojawia się jako osobny wiersz** w `pracownik.Nieobecnosci` — obserwowalnym efektem jest flaga `Korygowana == true` na nieobecności pierwotnej. - Korekta zmienia dane operacyjne powiązane z wypłatą — trzymaj transakcję krótką i obsłuż `RowConflictException` / `RowException` z `Save()` (safe-code §4, §13.1). - Worker wykonuje własną transakcję (`Session.Logout(true)` + `Commit`) — nie zagnieżdżaj go w innej otwartej transakcji edycyjnej. --- ### KADRY-D7 — Analiza limitów urlopowych (★) **Cel:** odczytać limit nieobecności (np. urlop wypoczynkowy) pracownika za dany rok — ile przysługuje, ile wykorzystano, ile pozostało. Limity **nie są tworzone ręcznie** — powstają przez naliczanie. **Fakty o typie (zweryfikowane skanem DLL):** - **`Soneta.Kalend.LimitNieobecnosci`** — tabela `LimNieobecnosci`, `GuidedRow` **child** pracownika (relacja przez pole `Pracownik`). Instancje powstają wyłącznie przez naliczanie — **nie twórz ich konstruktorem**. - Kolekcja na pracowniku: **`pracownik.Limity: SubTable`** (nazwa kolekcji to `Limity`, nie „LimityNieobecnosci"). - Tabela z poziomu modułu: `session.GetKalend().LimNieobecnosci`. **Pola i typy (`LimitNieobecnosci`) — odczyt:** | Pole | Typ | Rodzaj | Opis | |---|---|---|---| | `Definicja` | `Soneta.Kalend.DefinicjaLimitu` | bazodanowe | rodzaj limitu (urlop wypoczynkowy itd.) | | `Okres` | `Soneta.Types.FromTo` | bazodanowe | okres limitu (zwykle rok) | | `OkresWażności` | `Soneta.Types.FromTo` | kalkulowane | okres ważności limitu | | `Limit` | `int` | bazodanowe | limit (dni) wynikający z kodeksu pracy | | `LimitDni` | `int` | kalkulowane | limit w dniach | | `LimitGodz` | `Soneta.Types.Time` | bazodanowe | limit w godzinach | | `Razem` / `RazemGodz` | `int` / `Time` | kalkulowane | łączny przysługujący (limit + przeniesienia + zmiany) | | `Wykorzystane` / `WykorzystaneGodz` | `int` / `Time` | bazodanowe | wykorzystane dni/godziny | | `Pozostalo` | `int` | kalkulowane | pozostało (dni, int) | | `PozostaloDni` | `double` | kalkulowane | pozostało dni (z częścią ułamkową) | | `PozostaloGodz` | `Soneta.Types.Time` | kalkulowane | pozostało godzin | | `ZaleglyDni` / `ZaleglyGodz` | `double` / `Time` | kalkulowane | zaległy z poprzednich okresów | | `Przeniesienie` / `PrzeniesienieDni` | `int` / `double` | kalkulowane | przeniesione z poprzedniego roku | | `Korekta`, `Zmiana` | `int` | bazodanowe | korekty/zmiany limitu | | `Pracownik` | `Soneta.Kadry.Pracownik` | bazodanowe (guided-parent), **read-only** | właściciel | > **Wykorzystany = `Razem - Pozostalo`** (lub bezpośrednio pole `Wykorzystane`). „Przysługujący" to > `Razem` (limit kodeksowy + przeniesienia + zmiany), a nie samo `Limit`. **Dostęp do definicji limitów (`DefinicjeLimitow`):** - `session.GetKalend().DefinicjeLimitow.WgNazwy[string]` — np. `WgNazwy["Urlop wypoczynkowy"]`. - Skróty typowane (property zwracające `DefinicjaLimitu`): `DefinicjeLimitow.UrlopWypoczynkowy`, `.UrlopDodatkowy`, `.OpiekaNadZdrowym`, `.UrlopOpiekunczy`, `.ZwolnienieOdPracySilaWyzsza` itd. - `DefinicjaLimitu` ma pola `Nazwa: string`, `Typ: TypLimitu`. **Naliczanie limitu (by mógł istnieć do odczytu) — `Soneta.Kalend.NaliczanieLimitow`:** - Klasa z **publicznym bezparametrowym ctor**; settowalne property: - `Pars: NaliczanieLimitow.Params` (set), - `Pracownicy: ICollection` (set) **albo** `PracownicyIdx: Pracownik[]` (set). - Klasa `NaliczanieLimitow.Params : ContextBase` ma **publiczny ctor `Params(Context context)`** oraz settowalne: `Definicja: DefinicjaLimitu`, `Okres: FromTo`, `KopiujKorekty: bool`, `ZapisPerPracownik: bool`. - Metoda **`public void DodajLimit()`** — nalicza limit (zapisuje rekordy `LimitNieobecnosci`). (Jest też `DodajLimitUrlopowy()`.) **Snippet — naliczenie + odczyt:** ```csharp var kalend = session.GetKalend(); var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; var defUrlop = kalend.DefinicjeLimitow.WgNazwy["Urlop wypoczynkowy"]; // lub DefinicjeLimitow.UrlopWypoczynkowy var rok = FromTo.Year(new Date(2026, 1, 1)); using (var t = session.Logout(editMode: true)) { var naliczanie = new NaliczanieLimitow { Pars = new NaliczanieLimitow.Params(Context.Empty.Clone(session)) { Definicja = defUrlop, Okres = rok, KopiujKorekty = true }, Pracownicy = new Pracownik[] { pracownik } }; naliczanie.DodajLimit(); // tworzy/aktualizuje LimitNieobecnosci t.Commit(); } session.Save(); // Odczyt limitu urlopu wypoczynkowego za rok 2026. // UWAGA: filtr serwerowy obejmuje TYLKO pola bazodanowe i prostych porównań — Okres (FromTo) // NIE da się porównać serwerowo (==), więc filtrujemy serwerowo po Definicja, a rok w pamięci: var lim = pracownik.Limity[(LimitNieobecnosci l) => l.Definicja == defUrlop] .Cast() .FirstOrDefault(l => l.Okres.From == rok.From); if (lim != null) { int przysluguje = lim.Razem; // przysługujący (limit + przeniesienia + zmiany) int pozostalo = lim.Pozostalo; // pozostało int wykorzystany = przysluguje - pozostalo; // == lim.Wykorzystane // lim.PozostaloDni, lim.PozostaloGodz, lim.ZaleglyDni } ``` **Pułapki:** - **Nie** twórz `new LimitNieobecnosci(...)` — limit powstaje przez naliczanie (`DodajLimit`). W bazie Demo limit dla danego roku może jeszcze nie istnieć — w teście trzeba go **najpierw naliczyć**. - Kolekcja na pracowniku to `pracownik.Limity` (nie `LimityNieobecnosci`). - **Nie porównuj `Okres` (FromTo) w filtrze serwerowym** — `l.Okres == rok` rzuca `ArgumentException` („pole nieznalezione"). Filtruj serwerowo po `Definicja`, a okres/rok porównaj w pamięci (`.FirstOrDefault(l => l.Okres.From == rok.From)`). - `Razem` może wynosić `0` dla pracowników bez danych napędzających wymiar urlopu (staż, data urodzenia) — asercje opieraj na spójności (`Wykorzystane == Razem - Pozostalo`, `Razem >= 0`), a nie na założeniu `Razem > 0`. - `Pracownik` na limicie jest read-only (relacja guided) — naliczanie samo wiąże rekord z pracownikiem. - Filtruj limity serwerowo po `Definicja` i `Okres` (`pracownik.Limity[condition]`), nie iteruj całości z `if` w pamięci (safe-code §6.1). Tabela `LimNieobecnosci` jest operacyjna guided. - `Context.Empty.Clone(session)` daje kontekst związany z bieżącą sesją — wymagany przez ctor `NaliczanieLimitow.Params(Context)`. - Naliczanie modyfikuje dane operacyjne — w transakcji edycyjnej, krótko, z obsługą wyjątków z `Save()`. ### KADRY-D3 — Import e-ZLA z PUE ZUS (zwolnienia lekarskie) **Cel:** zaewidencjonować w systemie zwolnienie lekarskie pobrane z PUE ZUS (e-ZLA). Sam **import to operacja sieciowa** (komunikacja z PUE ZUS) — w kodzie biznesowym/teście dokumentujemy **model danych** nieobecności chorobowej i jej dane ZUS, a nie samo połączenie z bramką PUE. **Fakty o typie (zweryfikowane skanem DLL):** - Zwolnienie chorobowe to `Soneta.Kalend.NieobecnośćPracownika` (typ konkretny z KADRY-D1) z `Definicja` wskazującą na rodzaj zasiłkowy (np. „Zwolnienie chorobowe"). - Dane ZUS zwolnienia leżą w subrowie **`Nieobecnosc.Zwolnienie: Soneta.Kalend.ZwolnienieZUS`** (bazodanowy subrow na rekordzie nieobecności). - Dane samego dokumentu ZLA leżą w subrowie **`Nieobecnosc.ZLA: Soneta.Kalend.ZLA`** (`ZLA.Data: Date`, `ZLA.Wersja: WersjaZLA`, `ZLA.Zrodlo: MemoText`). **Pola i typy (`Nieobecnosc.Zwolnienie: ZwolnienieZUS`) — zapisywalne, bazodanowe:** | Pole | Typ | Opis | |---|---|---| | `Numer` | `string` | numer dokumentu ZLA (pole tekstowe — **maks. 9 znaków**) | | `KodChoroby` | `string` | kod literowy choroby (A, B, C, D, …) | | `Przyczyna` | `Soneta.Kalend.PrzyczynaZwolnienia` | przyczyna niezdolności do pracy | | `Kwarantanna` | `Soneta.Kalend.ZwolnienieKwarantanna` | kwarantanna/izolacja | | `LeczenieSzpitalne` | `bool` | pobyt w szpitalu | | `ZwolnienieWystawione` | `Soneta.Types.Date` | data wystawienia ZLA | | `ZwolnienieDostarczone` | `Soneta.Types.Date` | data dostarczenia | | `PomniejszajZasilek` | `bool` | obniżenie zasiłku | | `PonownieUstalPodstawe` | `bool` | wymuszenie przeliczenia podstawy (patrz KADRY-D2/KADRY-D6) | **Pola i typy (`Nieobecnosc.ZLA: ZLA`):** `Data: Date`, `Wersja: WersjaZLA`, `Zrodlo: MemoText`. **Snippet — ręczne odwzorowanie e-ZLA jako nieobecności chorobowej (bez sieci):** ```csharp var kalend = session.GetKalend(); var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; var defChor = kalend.DefNieobecnosci.WgNazwy["Zwolnienie chorobowe"]; using (var t = session.Logout(editMode: true)) { var nieob = session.AddRow(new NieobecnośćPracownika(pracownik)); nieob.Definicja = defChor; nieob.Okres = new FromTo(new Date(2026, 5, 4), new Date(2026, 5, 10)); // dane ZUS z e-ZLA (subrow Zwolnienie): nieob.Zwolnienie.Numer = "ZLA000001"; // pole Numer ma limit 9 znaków nieob.Zwolnienie.KodChoroby = "A"; t.Commit(); } session.Save(); ``` **Pułapki:** - **Sam import e-ZLA z PUE wymaga sieci** (uwierzytelnienie + bramka ZUS) — nie da się go odtworzyć w teście jednostkowym na bazie Demo; testuj wyłącznie **odwzorowanie modelu danych** (subrow `Zwolnienie`). - `Zwolnienie` i `ZLA` to subrowy — nie tworzysz ich osobno, są częścią rekordu `Nieobecnosc`; ustawiasz ich pola po utworzeniu nieobecności. - Definicja zasiłkowa musi istnieć w słowniku bazy (`DefNieobecnosci.WgNazwy[...]` ≠ `null`). - **Faktyczne kwoty zasiłku** liczą się dopiero przy naliczeniu wypłaty — patrz uwaga przy KADRY-D2. --- ### KADRY-D4 — Generowanie deklaracji Z-3 / Z-3a dla nieobecności chorobowej **Cel:** wygenerować zaświadczenie płatnika składek **Z-3** (pracownik etatowy) lub **Z-3a** (umowy/inni ubezpieczeni) dla konkretnej nieobecności zasiłkowej. **Fakty o typie (zweryfikowane skanem DLL):** - Worker (czynność na `Nieobecnosc`): **`Soneta.Deklaracje.ZUS.ZUSZ3.Z3Worker`** — akcja „Generuj deklarację Z-3", metoda `public object UtworzDeklaracjeZ3()`. - Analogicznie **`Soneta.Deklaracje.ZUS.ZUSZ3.Z3aWorker`** — akcja „Generuj deklarację Z-3a", metoda `public object UtworzDeklaracjeZ3a()`. - Oba workery przyjmują przez `[Context]`: - `KeduContext: DeklaracjaZUS.PUEContext` (property `Kedu: KEDU`), - `Z3ParamContext: Z3ParamContext` / `Z3aParamContext` z polami m.in.: `Nieobecnosc: INieobecnoscLubZbieg`, `NieobecnoscZContextu: bool`, `Pracownik: Pracownik`, `PracownikZContextu: bool`, `Okres: FromTo`, `OkresZasiłkowy: FromTo`, `OkresZasilkowyOd: Date`, `Współczynnik: Fraction`, `RachBank: string`, `KontynuacjaŚwiadczenia: bool`. **Snippet — generowanie Z-3 dla nieobecności (kontekst):** ```csharp var worker = new Soneta.Deklaracje.ZUS.ZUSZ3.Z3Worker(); var ctx = Context.Empty.Clone(session); ctx[typeof(Nieobecnosc)] = nieobChorobowa; // worker czyta nieobecność z kontekstu var deklaracja = worker.UtworzDeklaracjeZ3(); // zwraca obiekt deklaracji Z-3 session.Save(); ``` **Pułapki:** - **Sensowny Z-3 wymaga naliczonej wypłaty/podstawy zasiłku** — bez naliczonej podstawy deklaracja powstanie z pustymi/zerowymi kwotami. W teście na czystej Demo zweryfikujesz fakt powstania obiektu i ustawienie pól nagłówkowych (pracownik, okres), ale **nie kwoty zasiłku**. - Worker przyjmuje dane przez `Context` (`ctx[typeof(Nieobecnosc)]`/`ctx[typeof(Pracownik)]`) — nie ma prostego ctora parametrowego; zegnij pod swój scenariusz `Z3ParamContext`. - Z-3 dotyczy etatu, Z-3a umów/innych ubezpieczonych — dobierz worker do tytułu ubezpieczenia. - Metody zwracają `object` (deklaracja KEDU) — zachowaj/odczytaj wynik, nie zakładaj typu wprost. --- ### KADRY-D5 — Obsługa przestoju (dodanie/usunięcie, przestój ekonomiczny — % wynagrodzenia) **Cel:** zaewidencjonować przestój pracownika (np. ekonomiczny) za okres oraz wskazać procent wynagrodzenia przestojowego; usunąć przestój nakładający się na nieobecność ZUS. **Fakty o typie (zweryfikowane skanem DLL):** - **Dodanie przestoju:** worker **`Soneta.Kadry.DodajPrzestojWorker`** (czynność „Przestój/Dodaj przestój", metoda `public void DodajPrzestoj()`): - settowalne property: `Pracownicy: Pracownik[]`, `Pars: DodajPrzestojWorker.Params`; - `Params` z polami: `DefinicjaStrefy: Soneta.Kalend.DefinicjaStrefy`, `Okres: FromTo`. - **Procent wynagrodzenia przestojowego (przestój ekonomiczny):** worker **`Soneta.Kadry.IndywidualnyProcentWynagrPrzestojowegoWorker`** (czynność „Przestój/Przestój ekonomiczny - procent wynagr.", metoda `public void Aktualizuj()`): - `Pracownicy: Pracownik[]`, `Pars.Data: Date`, `Pars.Procent: Soneta.Types.Percent`. - **Usunięcie przestoju podczas nieobecności ZUS:** worker **`Soneta.Kadry.UsunPrzestojNieobecnoscWorker`** (czynność „Przestój/Usuń przestój podczas nieobecności ZUS", metoda `public void UsunPrzestoj()`): `Pracownicy: Pracownik[]`, `Pars.Okres: FromTo`. - Procent wynagrodzenia przestojowego jest też trzymany na etacie: `PracHistoria.Etat.Postojowe: Soneta.Kadry.WynagrodzeniePostojowe` (`Procent: Percent`, `Standardowe: bool`). - `DefinicjaStrefy` (`session.GetKalend().DefinicjeStref`) — słownik konfiguracyjny stref (m.in. przestoju). **Snippet — dodanie przestoju:** ```csharp var kalend = session.GetKalend(); var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; var defStrefa = kalend.DefinicjeStref.WgNazwy["Przestój"]; // nazwa wg słownika danej bazy var worker = new Soneta.Kadry.DodajPrzestojWorker { Pracownicy = new[] { pracownik }, Pars = new Soneta.Kadry.DodajPrzestojWorker.Params(Context.Empty.Clone(session)) { DefinicjaStrefy = defStrefa, Okres = new FromTo(new Date(2026, 6, 1), new Date(2026, 6, 5)) } }; worker.DodajPrzestoj(); // worker wykonuje własną transakcję session.Save(); ``` **Snippet — przestój ekonomiczny (procent):** ```csharp var worker = new Soneta.Kadry.IndywidualnyProcentWynagrPrzestojowegoWorker { Pracownicy = new[] { pracownik }, Pars = new Soneta.Kadry.IndywidualnyProcentWynagrPrzestojowegoWorker.Params(Context.Empty.Clone(session)) { Data = new Date(2026, 6, 1), Procent = new Percent(0.5m) // 50% wynagrodzenia } }; worker.Aktualizuj(); session.Save(); ``` **Pułapki:** - `DefinicjeStref.WgNazwy[...]` zależy od słownika danej bazy — zweryfikuj nazwę przestoju w Demo (może być inna niż „Przestój"); dla nieistniejącej nazwy zwraca `null`. - Worker wykonuje własną transakcję — nie zagnieżdżaj go w otwartej transakcji edycyjnej. - `Percent` przyjmuj jako ułamek (`0.5m` = 50%), nie liczbę 50. - `UsunPrzestojNieobecnoscWorker` usuwa przestój **kolidujący z nieobecnością ZUS** — to nie generyczne „usuń przestój"; zakres działania ogranicza okres + obecność nieobecności ZUS. - Skutki płacowe (wynagrodzenie przestojowe) liczą się dopiero przy naliczeniu wypłaty. --- ### KADRY-D6 — Ustalanie/zmiana parametrów okresu zasiłkowego **Cel:** zmienić parametry okresu zasiłkowego nieobecności chorobowej — kontynuację/przedłużenie okresu zasiłkowego oraz wymusić ponowne ustalenie podstawy naliczania zasiłku. **Fakty o typie (zweryfikowane skanem DLL):** - Parametry okresu zasiłkowego są w subrowie **`Nieobecnosc.Zwolnienie: ZwolnienieZUS`** (bazodanowe, zapisywalne): - `KontynuacjaOkrZas: Soneta.Kalend.KontynuacjaOkrZas` (enum: `Warunkowo`, `Tak`, `Nie`), - `PrzedluzenieOkrZas: bool`, `PrzedluzeniaData: Soneta.Types.Date`, - `PonownieUstalPodstawe: bool` + metoda `SetPonownieUstalPodstawe(bool)` (patrz KADRY-D2). - Worker korekty okresu zasiłkowego: **`Soneta.Kalend.Nieobecnosc.KorektaOkresuZasiłkowegoWorker`** (czynność „Zmień pozostałe parametry okresu zasiłkowego", metoda `public void PonownieUstalPodstawę()`): - settowalne `Pars: KorektaOkresuZasiłkowegoWorker.Params` z polami: `KontynuacjaOkrZas: KontynuacjaOkrZas`, `PrzedluzenieOkrZas: bool`, `PrzedluzeniaData: Date`. - BO okresu zasiłkowego (przy wdrożeniu) — patrz KADRY-D10: `PracHistoria.ChorobowyBO` (`DniZasilkowe`, `ZasilekOdDnia`, `PrzedluzenieOZ`). **Snippet — zmiana parametrów wprost na rekordzie:** ```csharp using (var t = session.Logout(editMode: true)) { nieobChorobowa.Zwolnienie.KontynuacjaOkrZas = KontynuacjaOkrZas.Tak; nieobChorobowa.Zwolnienie.PrzedluzenieOkrZas = true; nieobChorobowa.Zwolnienie.PrzedluzeniaData = new Date(2026, 5, 31); nieobChorobowa.Zwolnienie.SetPonownieUstalPodstawe(true); t.Commit(); } session.Save(); ``` **Snippet — przez worker korekty okresu zasiłkowego:** ```csharp var worker = new Nieobecnosc.KorektaOkresuZasiłkowegoWorker(); var ctx = Context.Empty.Clone(session); ctx[typeof(Nieobecnosc)] = nieobChorobowa; worker.Pars = new Nieobecnosc.KorektaOkresuZasiłkowegoWorker.Params(ctx) { KontynuacjaOkrZas = KontynuacjaOkrZas.Tak, PrzedluzenieOkrZas = true, PrzedluzeniaData = new Date(2026, 5, 31) }; worker.PonownieUstalPodstawę(); // własna transakcja + Commit session.Save(); ``` **Pułapki:** - **Faktyczne** przeliczenie kwot zasiłku następuje dopiero przy **ponownym naliczeniu wypłaty** — test na Demo zweryfikuje zmianę pól `KontynuacjaOkrZas`/`PrzedluzenieOkrZas`/`PrzedluzeniaData`/flagi, ale nie kwoty. - Parametry okresu zasiłkowego mają sens tylko dla nieobecności **ZUS** (zwolnienia chorobowe/zasiłki) — dla urlopu wypoczynkowego są bez znaczenia. - Worker wykonuje własną transakcję — nie zagnieżdżaj go w innej otwartej transakcji. --- ### KADRY-D8 — Naliczanie i przeliczanie limitów nieobecności **Cel:** naliczyć limit nieobecności (jak KADRY-D7 — `NaliczanieLimitow.DodajLimit()`) oraz przeliczyć liczbę wykorzystanych dni limitu (czynność „Przelicz wykorzystane"). **Fakty o typie (zweryfikowane skanem DLL):** - **Naliczenie limitu:** klasa **`Soneta.Kalend.NaliczanieLimitow`** — publiczny bezparametrowy ctor; settowalne `Pars: NaliczanieLimitow.Params` (`Definicja: DefinicjaLimitu`, `Okres: FromTo`, `KopiujKorekty: bool`, `ZapisPerPracownik: bool`) oraz `Pracownicy: ICollection` / `PracownicyIdx: Pracownik[]`; metoda `public void DodajLimit()` (i `DodajLimitUrlopowy()`). Wariant UI per-pracownik: worker **`Soneta.Kalend.UI.PracownikLimityNaliczanieWorker`** (czynność „Nalicz limit nieobecności", metoda `DodajLimit()`) — `Pracownik: Pracownik`, `Pars` jak wyżej. - **Przeliczenie wykorzystanych:** worker **`Soneta.Kalend.LimitNieobecnosci.Pracownicy.PrzeliczWykorzystaneWorker`** (czynność „Limity nieobecności/Przelicz wykorzystane", metoda `public void PrzeliczWykorzystane()`): - settowalne `Pracownicy: Pracownik[]`, `Pars.Definicja: DefinicjaLimitu`, `Pars.Okres: FromTo`. **Snippet — naliczenie + przeliczenie wykorzystanych:** ```csharp var kalend = session.GetKalend(); var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; var defUrlop = kalend.DefinicjeLimitow.WgNazwy["Urlop wypoczynkowy"]; var rok = FromTo.Year(new Date(2026, 1, 1)); // 1) naliczenie limitu (jak KADRY-D7) var naliczanie = new NaliczanieLimitow { Pars = new NaliczanieLimitow.Params(Context.Empty.Clone(session)) { Definicja = defUrlop, Okres = rok, KopiujKorekty = true }, Pracownicy = new[] { pracownik } }; naliczanie.DodajLimit(); session.Save(); // 2) przeliczenie wykorzystanych var przelicz = new LimitNieobecnosci.Pracownicy.PrzeliczWykorzystaneWorker { Pracownicy = new[] { pracownik }, Pars = new LimitNieobecnosci.Pracownicy.PrzeliczWykorzystaneWorker.Params(Context.Empty.Clone(session)) { Definicja = defUrlop, Okres = rok } }; przelicz.PrzeliczWykorzystane(); session.Save(); ``` **Pułapki:** - **Nie** twórz `new LimitNieobecnosci(...)` — limit powstaje przez naliczanie (jak w KADRY-D7). - `PrzeliczWykorzystane` aktualizuje pole `LimitNieobecnosci.Wykorzystane` na podstawie wprowadzonych nieobecności — ma sens dopiero **po** naliczeniu limitu i wprowadzeniu nieobecności limitowanych. - `Razem` może wynosić `0` dla pracownika bez danych napędzających wymiar — opieraj asercje na spójności (`Wykorzystane == Razem - Pozostalo`), nie na `Razem > 0` (patrz KADRY-D7). - Workery wykonują własne transakcje — wywołuj poza otwartą transakcją edycyjną; obsłuż wyjątki z `Save()`. --- ### KADRY-D9 — Aktualizacja podstaw nieobecności ZUS / podstaw urlopu **Cel:** odczytać/wprowadzić ręcznie podstawy naliczania zasiłków (chorobowe/macierzyńskie/opiekuńcze/ rehabilitacyjne) używane przy nieobecnościach ZUS — np. przy wdrożeniu lub korekcie podstawy. **Fakty o typie (zweryfikowane skanem DLL):** - Kolekcja na pracowniku: **`pracownik.PodstawyNieobecności: SubTable`** (jest też `PodstawyNieobecnościOkresowe: SubTable`). - **`Soneta.Place.PodstawaNieobecnosci`** — tabela `PodstawyNieobec`, `GuidedRow` **child** pracownika (relacja przez pole `Pracownik`). - **Brak publicznego ctora** — `PodstawaNieobecnosci` ma jedynie ctory niepubliczne (`(RowCreator)`, `(Pracownik, TypyPodstawNieobecnosci)`). Rekordy powstają z **naliczenia wypłaty**; w kodzie biznesowym/teście realnie testowalny jest **odczyt** (dodawanie ręczne — patrz pułapki/spec). **Pola i typy (`PodstawaNieobecnosci`) — bazodanowe, zapisywalne:** | Pole | Typ | Opis | |---|---|---| | `Data` | `Soneta.Types.Date` | data podstawy | | `Miesieczne` | `decimal` | podstawa miesięczna | | `Kwartalne` / `Roczne` | `decimal` | składowe | | `Podstawa` | `decimal` | podstawa naliczania chorobowego | | `PodstawaM` / `PodstawaO` / `PodstawaR` | `decimal` | podstawa macierzyńskiego / opiekuńczego / rehabilitacyjnego | | `Typ` | `Soneta.Place.TypyPodstawNieobecnosci` | `Chorobowa` / `Wypoczynkowy` | | `Norma` / `NormaDni` | `Time` / `int` | norma czasu/dni | | `Praca` / `PracaDni` | `Time` / `int` | przepracowane | | `ProcentSkladki` | `Soneta.Types.Percent` | procent składki | > **Podstawy urlopu wypoczynkowego** rozróżnia pole `Typ = TypyPodstawNieobecnosci.Wypoczynkowy`; > podstawy zasiłków ZUS → `Typ = Chorobowa`. **Snippet — odczyt podstaw + dodanie podstawy ręcznej:** ```csharp var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; // Odczyt podstaw chorobowych (filtr serwerowy po Typ): foreach (PodstawaNieobecnosci p in pracownik.PodstawyNieobecności[(PodstawaNieobecnosci x) => x.Typ == TypyPodstawNieobecnosci.Chorobowa]) { // p.Data, p.Podstawa, p.Miesieczne } // UWAGA: PodstawaNieobecnosci NIE ma publicznego ctora — normalnie powstaje z naliczenia wypłaty. // Ręczne dodanie wymagałoby niepublicznego API → w teście testuj wyłącznie ODCZYT (powyżej). ``` **Pułapki:** - Kwoty (`Miesieczne`, `Podstawa`, …) są typu `decimal` — to dane operacyjne podstaw; **normalnie podstawy powstają z naliczenia wypłaty** (brak publicznego ctora — patrz wyżej). - `Pracownik` na podstawie jest read-only (guided-parent). - Filtruj serwerowo po `Typ` (`PodstawyNieobecności[condition]`) — nie iteruj całości z `if` w pamięci. - W teście na czystej Demo kolekcja `PodstawyNieobecności` może być pusta, dopóki nie naliczono wypłaty z zasiłkiem — testuj odczyt asercją na model/spójność, a scenariusz „dodaj ręcznie" oznacz `[Ignore]`. --- ### KADRY-D10 — Bilans otwarcia nieobecności i urlopów **Cel:** wprowadzić bilans otwarcia (BO) przy wdrożeniu / starcie roku — historię chorobową (okres zasiłkowy, dni wykorzystane) oraz urlop wykorzystany u poprzednich pracodawców / w pierwszym miesiącu. **Fakty o typie (zweryfikowane skanem DLL):** - BO leży na rekordzie historycznym **`Soneta.Kadry.PracHistoria`** w dwóch subrowach (bazodanowe, zapisywalne): - **`PracHistoria.ChorobowyBO: Soneta.Kadry.ChorobowyBO`** (BO chorobowy / okres zasiłkowy), - **`PracHistoria.DodatkowyBO: Soneta.Kadry.DodatkowyBO`** (BO urlopowy — urlop u poprzednich pracodawców). - BO nieobecności pojedynczej oznacza też flaga `Nieobecnosc.BilansOtwarcia: bool` (interfejs `IBilansOtwarcia` na `Nieobecnosc`). **Pola i typy (`ChorobowyBO`) — bazodanowe:** | Pole | Typ | Opis | |---|---|---| | `Data` | `Soneta.Types.Date` | data BO | | `MiesiacPodstawy` | `Soneta.Types.YearMonth` | miesiąc podstawy | | `Podstawa` | `decimal` | podstawa BO | | `DniWynagrodzenia` | `int` | dni zwolnienia finansowane przez pracodawcę | | `DniZasilkowe` | `int` | dni wliczane do bieżącego okresu zasiłkowego | | `DniZwolnienia` | `int` | dni nieprzerwanego zwolnienia dobrowolnego | | `ZasilekOdDnia` | `Soneta.Types.Date` | zasiłek od dnia | | `PrzedluzenieOZ` | `bool` | okres zasiłkowy przedłużony o 3 mies. | **Pola i typy (`DodatkowyBO`) — bazodanowe:** | Pole | Typ | Opis | |---|---|---| | `UPoprzednich` | `decimal` | urlop wykorzystany u poprzednich pracodawców (dni) | | `Wykorzystany` | `Soneta.Types.Time` | wykorzystany przypadający na bieżące zatrudnienie (godz.) | | `BezPierwszego` | `bool` | prawo do urlopu w 1. mies. nabyte u poprzedniego pracodawcy | **Snippet — wprowadzenie BO chorobowego i urlopowego na zapisie historycznym:** ```csharp var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; var historia = pracownik.Historia[Date.Today]; // właściwy zapis historyczny „na dzień" using (var t = session.Logout(editMode: true)) { // BO chorobowy / okres zasiłkowy historia.ChorobowyBO.DniZasilkowe = 33; historia.ChorobowyBO.ZasilekOdDnia = new Date(2026, 1, 1); // BO urlopowy historia.DodatkowyBO.UPoprzednich = 10m; t.Commit(); } session.Save(); ``` **Pułapki:** - `ChorobowyBO`/`DodatkowyBO` to **subrowy** zapisu `PracHistoria` — nie tworzysz ich osobno, edytujesz ich pola na istniejącym zapisie historycznym. - **`ChorobowyBO`** (`DniZasilkowe`, `ZasilekOdDnia`, `PrzedluzenieOZ`, …) jest **zapisywalny** na zwykłym zapisie historii (zweryfikowane testem KADRY-D10 na Demo). - **`DodatkowyBO`** (`UPoprzednich`, `BezPierwszego`, `Wykorzystany`) na zwykłym zapisie historii Demo rzuca **`ColReadOnlyException`** („pole w trybie tylko do odczytu") — BO urlopowy jest zapisywalny tylko na zapisie historycznym oznaczonym jako **bilans otwarcia / start zatrudnienia**, nie na dowolnym zapisie „na dzień". W teście na gotowych pracownikach Demo dodawanie `DodatkowyBO` oznacz `[Ignore]`. - Pobierz właściwy zapis historyczny przez `pracownik.Historia[data]` (patrz KADRY-A14/KADRY-A15) — edycja BO na niewłaściwym zapisie da błędne dane „na dzień". - BO ma sens przy wdrożeniu — nie miesza się z normalnym naliczaniem; po wprowadzeniu wpływa na limity (KADRY-D8) i okres zasiłkowy (KADRY-D6) dopiero przy przeliczeniu/naliczeniu. --- ### KADRY-D11 — Wnioski o urlop / delegację **Cel:** zarejestrować wniosek urlopowy (lub o delegację), zmienić jego stan (akceptacja/odrzucenie/ przywrócenie) i — docelowo — przekształcić zaakceptowany wniosek w nieobecność. **Fakty o typie (zweryfikowane skanem DLL):** - Wniosek urlopowy: **`Soneta.Kadry.WniosekUrlopowy`** — tabela `WnioskiUrlopowe`, `GuidedRow` root. Konstruktory publiczne: **`new WniosekUrlopowy(Pracownik pracownik)`** oraz **`new WniosekUrlopowy(Pracownik pracownik, DefinicjaNieobecnosci definicja)`**. - Kolekcja na pracowniku: **`pracownik.WnioskiUrlopowe: SubTable`** (oraz `WnioskiKierownika`, `WnioskiZastępcy` — te same wnioski w roli kierownika/zastępcy). - Pola `WniosekUrlopowy` (bazodanowe, zapisywalne): `Pracownik: Pracownik`, `Definicja: DefinicjaNieobecnosci`, `Okres: FromTo`, `Data: Date`, `DataDecyzji: Date`, `Kierownik: Pracownik`, `Opis: MemoText`, `Stan: Soneta.Kadry.StanWnioskuUrlopowego`. - `StanWnioskuUrlopowego`: `Oczekujący`, `Anulowany`, `Zaakceptowany`, `Odrzucony`, `Korygowana`. - Wniosek o delegację jest subrowem wniosku: `WniosekUrlopowy.Delegacja: Soneta.Kadry.WniosekODelegację` (`DataRozpoczeciaPlanowana`, `DataZakonczeniaPlanowana: DateShortTime`, `KrajDocelowy`, `Cel: MemoText`, `WnioskowanaZaliczka: Currency`); samodzielny `new WniosekODelegację()` ma publiczny ctor bezparametrowy. - **Planowane nieobecności** (osobny model, np. plan urlopów): kolekcja **`pracownik.PlanowaneNieobecności: FromToSubTable`**; typ `PlanowanaNieobecność` (tabela `PlanNieobecnosci`, root) z ctorem **`new PlanowanaNieobecność(Pracownik pracownik)`**, polami `Definicja`, `Okres: FromTo`. - **`Definicja` musi mieć zaznaczone pole `Planowana`** (`DefinicjaNieobecnosci.Planowana == true`) — inaczej setter rzuca `RowException` „Wybrana definicja musi mieć zaznaczone pole 'Planowana'."; dobierz definicję dynamicznie: `DefNieobecnosci.Cast().First(d => d.Planowana)`. - **`Stan: StanPlanowanejNieobecności` jest READ-ONLY** (`Oczekująca`, `Wprowadzona`, `Korygowana`, `Zatwierdzona`, `Anulowana`) — **nie przypisujesz** go wprost (`plan.Stan = …` → błąd kompilacji „cannot be assigned to"); przejścia stanu wykonujesz metodami domenowymi **`StanWprowadzona()` / `StanZatwierdzona()` / `StanAnulowana()` / `StanOczekująca()`**. - Akceptacja/odrzucenie/przywrócenie z poziomu Pulpitu: worker (UI/Net) **`PracownikNetWnioskiUrlopowe`** z akcjami „Zatwierdź wniosek"/`Zatwierdz`, „Odrzuć wniosek"/`Odrzuc`, „Przywróć wniosek"/`Przywroc`. W kodzie biznesowym/teście prościej ustawiać `Stan` wprost. **Snippet — rejestracja wniosku urlopowego + akceptacja:** ```csharp var kalend = session.GetKalend(); var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; // UWAGA: dla definicji limitowanej (np. „Urlop wypoczynkowy") akceptacja wniosku (set Stan) wyzwoli // przeliczenie limitu → LimitNotFoundException, jeśli limit nie został wcześniej naliczony (patrz pułapki). // Tu używamy definicji bezlimitowej (np. „Urlop bezpłatny (art 174 kp)") albo najpierw naliczamy limit (KADRY-D8). var defUrlop = kalend.DefNieobecnosci.WgNazwy["Urlop bezpłatny (art 174 kp)"]; using (var t = session.Logout(editMode: true)) { var wniosek = session.AddRow(new WniosekUrlopowy(pracownik, defUrlop)); wniosek.Okres = new FromTo(new Date(2026, 8, 3), new Date(2026, 8, 7)); wniosek.Data = Date.Today; wniosek.Stan = StanWnioskuUrlopowego.Oczekujący; t.Commit(); } session.Save(); // Akceptacja (zmiana stanu): using (var t = session.Logout(editMode: true)) { var wniosek = pracownik.WnioskiUrlopowe .Cast() .First(w => w.Stan == StanWnioskuUrlopowego.Oczekujący); wniosek.Stan = StanWnioskuUrlopowego.Zaakceptowany; wniosek.DataDecyzji = Date.Today; t.Commit(); } session.Save(); ``` **Pułapki:** - **Akceptacja wniosku na definicji limitowanej rzuca `LimitNotFoundException`** bez wcześniej naliczonego limitu: ustawienie `Stan` (np. `Zaakceptowany`) na wniosku z definicją „Urlop wypoczynkowy" wewnętrznie ustawia `Okres` nieobecności i wyzwala `DefinicjaLimitu.Przelicz(...)`, który dla pracownika bez limitu na ten dzień rzuca wyjątek. Rozwiązanie: albo nalicz limit (KADRY-D8) **przed** zmianą stanu, albo do scenariusza obsługi samego rekordu wniosku użyj definicji **bezlimitowej** (np. „Urlop bezpłatny (art 174 kp)"). - **Przekształcenie wniosku w nieobecność** wymaga, by nieobecność limitowana miała naliczony limit (jak KADRY-D1) — sama akceptacja wniosku nie tworzy automatycznie rozliczonej nieobecności w teście bez naliczonego limitu/wypłaty. - `WniosekODelegację` to subrow wniosku (`WniosekUrlopowy.Delegacja`) — wnioskowanie o delegację ustawiasz na tym subrowie; pełne rozliczenie delegacji to moduł `Soneta.Delegacje` (osobny dokument handlowy PWS), poza zakresem wniosku. - Filtruj kolekcję wniosków przez `WnioskiUrlopowe[condition]` lub iteruj w zakresie jednego pracownika; nie skanuj globalnej tabeli `WnioskiUrlopowe` bez zakresu (tabela operacyjna guided). - Stan zmieniaj świadomie wg enuma `StanWnioskuUrlopowego` — workery Net robią to samo z dodatkową logiką workflow (powiadomienia), której w teście jednostkowym nie odtworzysz. --- ### KADRY-D12 — Praca zdalna (wnioski, lokalizacje, ewidencja) **Cel:** skonfigurować pracę zdalną pracownika (model pracy, limit pracy zdalnej okazjonalnej), zarejestrować wniosek o pracę zdalną i lokalizacje jej świadczenia oraz odczytać ewidencję. **Fakty o typie (zweryfikowane skanem DLL):** - Parametry pracy zdalnej leżą na etacie/historii: **`PracHistoria.PracaZdalna: Soneta.Kadry.PracZdalna`** (subrow, bazodanowe, zapisywalne): - `ModelPracy: Soneta.Kadry.ModelPracy` (`NieDotyczy`, `PracaStacjonarna`, `PracaHybrydowa`, `PracaZdalna`), - `OswiadczenieWarunki: bool` (warunki lokalowe/techniczne), - `LimitPZ: int`, `IndywidualnyLimitPZ: bool`, `TypLimituPZ: TypLimituPracyZdalnej` (`Roczny`, `Miesieczny`, `Tygodniowy`, `Kwartalny`, `Półroczny`). - Lokalizacje pracy zdalnej: **`pracownik.LokalizacjePracyZdalnej: SubTable`** (tabela `LokPracZdalnej`). - Wnioski o pracę zdalną: **`pracownik.WnioskiPracyZdalnej: SubTable`** (oraz `WnioskiPracyZdalnejKierownika`); typ `WniosekPracyZdalnej` ma ctor `(Pracownik, DefinicjaRodzajuPracyZdalnej)` — **ctory są niepubliczne**, więc tworzenie wniosku idzie przez worker (`GrupoweZleceniePracyZdalnejWorker`) lub Pulpit, nie wprost `new`. - Lokalizacja pracy zdalnej: `Soneta.Kadry.LokalizacjaPracyZdalnej` ma **publiczny ctor `new LokalizacjaPracyZdalnej(Pracownik pracownik)`**. - Ewidencja/odczyt limitu pracy zdalnej okazjonalnej: worker **`Soneta.Kadry.Pracownik.PracaZdalnaWorker`** — property odczytowe (bez akcji modyfikującej): `DniPracyZdalnejRazem: int`, `DniPracyZdalnejOkazjonalnej: int`, `DniPracyZdalnejOkazjonalnejLimit: int`, `CzasPracyZdalnejRazem: Time`, `LimitPracaZdalnaOkazjonalna: int`, `PozostaloPracaZdalnaOkazjonalna: int`; kontekst: `Pracownik: Pracownik`, `Okres: FromTo`. - Grupowe zlecenie pracy zdalnej (Pulpit/seryjne): worker **`Soneta.Kadry.UI.KadryNet.Workers.GrupoweZleceniePracyZdalnejWorker`** (akcja „Dodaj wnioski zlecenia pracy zdalnej"/`DodajZleceniaPracyZdalnej`): `Pracownicy: Pracownik[]`, `Pars.Okres: FromTo`, `Pars.Data: Date`, `Pars.Uwagi: string`. - Aktualizacja podzielników kosztów na podstawie pracy hybrydowej: worker **`AktualizujPodzielnikowPracaZdalnaWorker`** (`DefinicjaPodzielnika`, `Okres: YearMonth`, …). **Snippet — ustawienie modelu pracy zdalnej + lokalizacja + wniosek:** ```csharp var kadry = session.GetKadry(); var pracownik = kadry.Pracownicy.WgKodu["006"]; using (var t = session.Logout(editMode: true)) { var historia = pracownik.Historia[Date.Today]; historia.PracaZdalna.ModelPracy = ModelPracy.PracaHybrydowa; historia.PracaZdalna.OswiadczenieWarunki = true; // lokalizacja pracy zdalnej (np. adres domowy) var lok = session.AddRow(new LokalizacjaPracyZdalnej(pracownik)); // … pola adresowe lokalizacji wg LokalizacjaPracyZdalnej t.Commit(); } session.Save(); // Odczyt ewidencji pracy zdalnej okazjonalnej (worker odczytowy): // Pracownik i Okres są zwykłymi, settowalnymi property (nie trzeba przekazywać przez Context): var pz = new Soneta.Kadry.Pracownik.PracaZdalnaWorker { Pracownik = pracownik, Okres = FromTo.Year(new Date(2026, 1, 1)) }; // odczyt: pz.DniPracyZdalnejRazem, pz.LimitPracaZdalnaOkazjonalna, pz.PozostaloPracaZdalnaOkazjonalna ``` **Pułapki:** - `PracaZdalnaWorker` to worker **odczytowy** (ma property, brak akcji modyfikującej) — służy do prezentacji ewidencji/limitu, nie do zapisu. - `ModelPracy`/`OswiadczenieWarunki` są na **historycznym** zapisie etatu (`PracHistoria.PracaZdalna`) — edytuj właściwy zapis „na dzień". - `WniosekPracyZdalnej` ma **niepubliczne ctory** — w teście jednostkowym nie utworzysz go przez `new`; zlecenie pracy zdalnej idzie przez worker `GrupoweZleceniePracyZdalnejWorker` (czynność Net/UI, wymaga `Context`). Testuj raczej `ModelPracy`/`OswiadczenieWarunki` na `PracHistoria.PracaZdalna` i `LokalizacjaPracyZdalnej` (ma publiczny ctor). - `LokalizacjaPracyZdalnej` ma publiczny ctor `(Pracownik)` — testowalna wprost.