diff --git a/soneta-programming/SKILL.md b/soneta-programming/SKILL.md index af15b3a..47be731 100644 --- a/soneta-programming/SKILL.md +++ b/soneta-programming/SKILL.md @@ -42,6 +42,7 @@ SKILL.md zawiera "duży obraz" - hierarchię klas, thread-safety, kanoniczne wzo | Gotowe wzorce kodu end-to-end (import, CRUD, obsługa błędów) | [references/examples.md](references/examples.md) | | Receptury kodu per obiekt biznesowy (domeny) — `Kontrahent` (pola, kolekcje, workery, finanse, RODO, KSeF) | [references/domeny/kontrahent.md](references/domeny/kontrahent.md) | | Receptury kodu per obiekt biznesowy (domeny) — `DokumentHandlowy` (faktury/magazynowe/zamówienia/korekty, relacje `IRelacjeService`, cykl życia, magazyn/partie/obroty, VAT/waluty, płatności, KSeF/fiskal/Intrastat, wydruki) | [references/domeny/dokument-handlowy.md](references/domeny/dokument-handlowy.md) | +| Receptury kodu per obiekt biznesowy (domeny) — `Pracownik`/Kadry-Płace (zatrudnienie i dane kadrowe, historia `PracHistoria`+`Etat`, dodatki, nieobecności/limity, plan pracy/RCP, umowy cywilnoprawne, naliczanie wypłat, listy płac, wydruki PDF) | [references/domeny/pracownik.md](references/domeny/pracownik.md) | | **Zasady bezpiecznego kodu biznesowego — checklist do review i refaktoringu** | [references/safe-code.md](references/safe-code.md) | | Skanowanie pól obiektu biznesowego z DLL (Roslyn MetadataReference) | [references/scan-props.md](references/scan-props.md) | | Inwentaryzacja modułów i tabel (`*Module` / `*Row` / `*Table`) z DLL | [references/scan-modules.md](references/scan-modules.md) | diff --git a/soneta-programming/references/domeny/pracownik.md b/soneta-programming/references/domeny/pracownik.md new file mode 100644 index 0000000..f8406e6 --- /dev/null +++ b/soneta-programming/references/domeny/pracownik.md @@ -0,0 +1,6985 @@ +# Pracownik / Kadry-Płace — receptury kodu biznesowego (Soneta / enova365) + +Zbiór gotowych wzorców kodu dla domeny **Kadry i Płace**: obiekt biznesowy +**`Soneta.Kadry.Pracownik`** (tabela `Pracownicy`) wraz z jego historią kadrową, etatem, +nieobecnościami, planem pracy, umowami cywilnoprawnymi i wypłatami. Dokument jest częścią skilla +`soneta-programming`. Celem jest, aby agent pisał **bezbłędny kod biznesowy** operujący na +pracowniku — trafiający w realne pola, kolekcje i workery platformy. + +> Format **zwarty**: każdy wzorzec opisuje ogólny przypadek + tabelę wariantów. Fundamenty (sesja, +> transakcja, blokada optymistyczna, praca z `SubTable`, obsługa błędów, wywoływanie workerów) +> są opisane w [`safe-code.md`](../safe-code.md), [`session-login.md`](../session-login.md) oraz +> [`worker-extender.md`](../worker-extender.md) — tutaj się do nich odwołujemy, nie powtarzamy ich. +> +> **Cały kod w tym dokumencie jest zgodny z C# 10** (target-typed `new`, `var`, wyrażenia `switch`, +> nazwane parametry `bool`). Snippety operują wyłącznie na **publicznym kontrakcie** platformy — nie +> ma odwołań do prywatnych klas ani kodu źródłowego aplikacji. + +## Fakty o typie (zweryfikowane skanem DLL — `scan-props.csx`) + +- **Klasa biznesowa:** `Soneta.Kadry.Pracownik` — `GuidedRow` (root), tabela `Soneta.Kadry.Pracownicy`. +- **Moduły i dostęp z sesji:** + - `Soneta.Kadry.KadryModule` — `session.GetKadry()`; tabela `kadry.Pracownicy`. + - `Soneta.Place.PlaceModule` — `session.GetPlace()`; wypłaty, listy płac, definicje elementów. + - `Soneta.Kalend.KalendModule` — `session.GetKalend()`; nieobecności, kalendarze, plan pracy, RCP, limity. + - `Soneta.HR.HRModule` (`session.GetHR()`), `Soneta.HR2.HR2Module` (`session.GetHR2()`) — definicje + stanowisk, struktura, ZZL/oceny/rekrutacja. +- **Obiekt historyczny:** dane kadrowe i warunki etatu obowiązują „od–do" i są przechowywane w + zapisach historycznych. Kolekcja `Pracownik.Historia: HistorySubTable`. + Rekord `PracHistoria` (tabela `PracHistorie`, child pracownika) zawiera m.in. złożone pole + `Etat: Soneta.Kadry.Etat` (warunki zatrudnienia), adresy, dane podatkowe/ubezpieczeniowe. +- **Najważniejsze pola bazodanowe `Pracownik` (poziom root):** `Kod: string`, `Nazwisko: string`, + `Imie: string`, `PESEL: string`, `ArchiwumInfo`, `NumerRachunkuUS`, `NumerRachunkuZUS`. + (Większość danych kadrowych jest w `PracHistoria`, nie na root.) +- **Kluczowe kolekcje (`SubTable`) na `Pracownik`:** + - `Historia: HistorySubTable` — zapisy historyczne (dane kadrowe + `Etat`). + - `Nieobecnosci: FromToSubTable` — nieobecności. + - `Limity: SubTable` — limity nieobecności (np. urlop). + - `Dodatki: SubTable` — stałe elementy wynagrodzenia (dodatki). + - `Akordy: SubTable` — akordy. + - `Umowy: SubTable` — umowy cywilnoprawne; `UmowyZewnetrzne: SubTable`. + - `Rachunki: SubTable` — rachunki bankowe pracownika. + - `DniPracy: DateSubTable` — plan/realizacja czasu pracy (dzień po dniu). + - `DniRCP: DateSubTable` — zarejestrowany czas pracy (RCP). + - `DniPlanu: DateSubTable` — plan pracy (harmonogram). + - `Kalendarze: SubTable` — kalendarze pracownika. + - `PlanowaneWypłaty`, `PlanowaneElementy`, `PlanowaneNieobecności` — dane planistyczne. +- **Cechy:** `Features: Soneta.Business.FeatureCollection` (indeksator po nazwie definicji cechy). +- **Dane w bazie Demo (GoldStandard):** ~80 zatrudnionych pracowników etatowych, kody `"006"`, `"007"`, + `"008"`, … (po jednym zapisie historii każdy). To stabilne punkty wejścia do scenariuszy odczytu. + +## Podstawowe typy domenowe + +| Typ | Namespace | Zastosowanie | +|---|---|---| +| `Date` | `Soneta.Types` | data bez czasu (daty zatrudnienia, obowiązywania) | +| `FromTo` | `Soneta.Types` | zakres dat „od–do" (okres etatu, nieobecności); `FromTo.Parse`, `FromTo.Year` | +| `Time` | `Soneta.Types` | czas/norma (np. norma dobowa `8:00`) | +| `Fraction` | `Soneta.Types` | wymiar etatu jako ułamek (np. `Fraction.One` = pełny etat, `1/2`) | +| `Currency` / `decimal` | `Soneta.Types` / — | kwoty (stawka, wartość wypłaty) | +| `YearMonth` | `Soneta.Types` | miesiąc rozliczeniowy (okres wypłaty) | + +## Szablon wzorca + +Każdy wzorzec (`Xn`, gdzie `X` = litera sekcji z listy zadań) ma stałą strukturę: + +- **Cel** — co robi i kiedy go użyć. +- **Warianty** — tabela odmian przypadku (gdy dotyczy). +- **Pola i typy** — realne właściwości/kolekcje i ich typy. +- **Snippet** — kod C# 10 na publicznym kontrakcie. +- **Pułapki** — typowe błędy i zasady safe-code. + +> **Konwencja testów:** każdy wzorzec ma odpowiadający test w +> `Soneta.Skills.Test/KadryPlace/Pracownik/` (klasa dziedzicząca z `PracownikTestBase : TestBase`). +> Testy są wykonywane na bazie Demo z automatycznym rollbackiem — można w nich tworzyć i modyfikować +> dowolne dane. Stanowią wykonywalną dokumentację publicznego API. + +--- + + + +## Spis treści + +- [Fakty o typie](#fakty-o-typie-zweryfikowane-skanem-dll--scan-propscsx) +- [Podstawowe typy domenowe](#podstawowe-typy-domenowe) +- [Szablon wzorca](#szablon-wzorca) +Pozycje oznaczone (★) to funkcjonalności priorytetowe (wprost wskazane). Każda receptura ma własny +podrozdział `### Xn` oraz odpowiadający test. + +- **A. Pracownik — zatrudnienie i dane kartotekowe:** A1 (★) zatrudnienie · A2 (★) dane kadrowe · + A3 adresy · A4 kontakt/WWW · A5 rachunki · A6 PIT · A7 (★) ubezpieczenia · A8 ZUS/NFZ · + A9 (★) rodzina · A10 (★) poprzednie miejsca pracy · A11 wykształcenie/języki/wojsko · A12 GUS · + A13 PFRON · A14 (★) aktualizacja historyczna · A15 odczyt na dzień · A16 powiązanie z kontrahentem · + A17 archiwum · A18 zwolnienie/ZWUA · A19 przerejestrowanie +- **B. Etat:** B1 (★) definiowanie etatu · B2 aneks · B3 przeszeregowanie · B4 rozwiązanie umowy · + B5 obniżenie wymiaru · B6 podzielniki kosztów · B7 definicja stanowiska +- **C. Dodatki, potrącenia, akordy:** C1 (★) dodatki · C2 potrącenia · C3 akordy · C4 zajęcia + komornicze · C5 operacje seryjne · C6 ZFŚS · C7 pożyczki +- **D. Nieobecności i czas pracy:** D1 (★) wprowadzanie · D2 (★) korygowanie · D3 e-ZLA · D4 Z-3/Z-3a · + D5 przestój · D6 okres zasiłkowy · D7 (★) limity (odczyt) · D8 naliczanie limitów · D9 podstawy · + D10 bilans otwarcia · D11 wnioski urlopowe · D12 praca zdalna +- **E. Plan pracy i kalendarz:** E1 (★) plan czasu pracy · E2 (★) kopiowanie planu · + E3 aktualizacja kalendarza · E4 doba pracownicza · E5 odczyt normy/czasu przepracowanego +- **F. RCP:** F1 (★) rejestracja czasu · F2 (★) wejścia/wyjścia · F3 import RCP · F4 weryfikacja/korekta · + F5 praca hybrydowa/podzielniki +- **G. Umowy cywilnoprawne:** G1 (★) dodawanie · G2 (★) zmiana/aneks · G3 operacja seryjna · + G4 rozliczenie/rachunek umowy · G5 zgłoszenia ZUS zleceniobiorców +- **H. Płace — naliczanie wypłat:** H1 (★) etatowe · H2 (★) z umów · H3 (★) pozostałe · H4 (★) odczyt + za rok · H5 elementy wypłaty · H6 zaliczka · H7 przelicz podatki · H8 dochód · H9 kalkulator · + H10 stornowanie · H11 anulowanie/bufor +- **I. Listy płac, przelewy, wydruki:** I1 (★) listy płac · I2 (★) PDF paska · I3 (★) PDF listy · + I4 przelewy · I5 eksport do banku · I6 rozliczenia/faktura +- **J. Deklaracje (ZUS, PIT, PFRON, PPK):** J1 zgłoszenia ZUS · J2 rozliczeniowe ZUS · J3 PIT · + J4 PFRON · J5 PPK · J6 bilanse otwarcia +- **K. Ewidencje pracownicze:** K1 badania lekarskie · K2 BHP · K3 szkolenia/uprawnienia · + K4 nagrody/kary/oświadczenia · K5 wypadki · K6 RODO · K7 struktura organizacyjna · K8 oceny · + K9 rekrutacja + +> **Testy weryfikujące** (wykonywalna dokumentacja, baza Demo z rollbackiem) w +> `Soneta.Skills.Test/KadryPlace/Pracownik/`: `SmokeTest`, `RozdzialA_PracownikTest`, +> `RozdzialArest_KartotekaTest`, `RozdzialBC_EtatDodatkiTest`, `RozdzialBrest_EtatTest`, +> `RozdzialCrest_PotraceniaTest`, `RozdzialD_NieobecnosciTest`, `RozdzialDrest_NieobecnosciTest`, +> `RozdzialEF_PlanRcpTest`, `RozdzialEFrest_PlanRcpTest`, `RozdzialG_UmowyTest`, `RozdzialGrest_UmowyTest`, +> `RozdzialH_WyplatyTest`, `RozdzialHrest_WyplatyTest`, `RozdzialI_ListyWydrukiTest`, +> `RozdzialIrest_PrzelewyTest`, `RozdzialJ_DeklaracjeTest`, `RozdzialK1_EwidencjeTest`, +> `RozdzialK2_RodoZzlTest`. Łącznie **172 testy** (134 zielone, 38 świadomie pominiętych `[Ignore]` — +> operacje wymagające sieci, `Context`/`KEDU`, konfiguracji niedostępnej w Demo, praw dostępu lub +> składowych `internal`; powód podany przy każdym pominiętym teście i w pułapkach receptury). + +--- + +## A. Pracownik — zatrudnienie i dane kartotekowe + +> **Model „root + historia".** `Pracownik` (root, tabela `Pracownicy`) trzyma tylko nieliczne pola +> niezmienne w czasie (`Kod`, `Net`, `NumerRachunkuUS`, `NumerRachunkuZUS`, `Rodzaj`, `Typ`, +> `Wieloetatowosc`). **Praktycznie wszystkie dane kadrowe są historyczne** i leżą w zapisach +> `PracHistoria` (tabela `PracHistorie`, child `Pracownik`-a, też `GuidedRow` root z własnym Guid). +> Kolekcją zapisów jest `Pracownik.Historia: HistorySubTable`. Warunki +> zatrudnienia (umowa, wymiar, ubezpieczenia, stanowisko) siedzą w złożonym polu +> `PracHistoria.Etat: Soneta.Kadry.Etat`. +> +> **Skróty na rootcie** (delegują do zapisu „na dziś"/ostatniego): +> - `pracownik.Last : PracHistoria` — **ostatni** (najświehigy) zapis historii; do edycji świeżo +> utworzonego pracownika i odczytu „bieżących" danych kartotekowych. +> - `pracownik[date] : PracHistoria` — indeksator zwracający zapis **obowiązujący na zadaną datę**. +> - Wiele pól osobowych jest też dostępnych z poziomu rootu jako property delegujące (np. `Imie`, +> `Nazwisko`, `PESEL`) — ale **kanonicznie ustawiamy je na zapisie** (`Last.Imie`, `pracownik[d].PESEL`). +> +> **`Pracownik` jest klasą abstrakcyjną** — nie da się zrobić `new Pracownik()`. Konkretny typ +> pracownika firmy to **`Soneta.Kadry.PracownikFirmy`**. + +### A1 — Zatrudnienie nowego pracownika (★) + +**Cel:** utworzyć nowego pracownika z minimalnym kompletem danych pozwalającym na `Session.Save()`. + +**Mechanizm (kluczowy):** dodanie nowego `PracownikFirmy` do tabeli (`AddRow`) automatycznie tworzy +**pierwszy zapis** `PracHistoria` oraz kalendarz pracownika (dzieje się w `OnAdded`). Dlatego **nie +tworzymy** `PracHistoria` ręcznie przy zatrudnieniu — bezpośrednio po `AddRow` istnieje już +`pracownik.Last` (pierwszy zapis), na którym ustawiamy dane osobowe. + +**Pola minimalne do zapisu:** + +| Pole | Gdzie | Typ | Uwaga | +|---|---|---|---| +| `Kod` | `Pracownik` (root) | `string` | identyfikator; przy pustym platforma podstawia prefiks + `?` | +| `Imie` | `PracHistoria` (`Last.Imie`) | `string` | wymagane (domyślnie `"?"` z `OnAdded`) | +| `Nazwisko` | `PracHistoria` (`Last.Nazwisko`) | `string` | wymagane (domyślnie `"?"`) | + +Pierwszy zapis historii ma okres otwarty (do `Date.MaxValue`); warunki etatu (A1 → B) ustawia się +na `Last.Etat` (np. `Etat.Okres`, `Etat.TypUmowy`, `Etat.Wymiar`) — szczegóły w sekcji B. + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); + +using (var t = session.Logout(editMode: true)) +{ + // AddRow zwraca dodany, typowany wiersz; w OnAdded powstaje pierwszy PracHistoria + kalendarz + var pracownik = session.AddRow(new PracownikFirmy()); + + pracownik.Kod = "555"; // pole rootu + + // dane osobowe — na ZAPISIE historii (pierwszy zapis = Last) + pracownik.Last.Nazwisko = "Kowalska"; + pracownik.Last.Imie = "Gabriela"; + pracownik.Last.PESEL = "94010812345"; + + t.Commit(); // Commit() w kodzie biznesowym; CommitUI() w workerze/UI +} +session.Save(); // zapis do bazy; tu wykrywane konflikty/duplikaty +``` + +**Pułapki:** +- **Nie** rób `new Pracownik()` — typ jest abstrakcyjny. Używaj `new PracownikFirmy()`. +- **Nie** dodawaj ręcznie pierwszego `PracHistoria` — robi to `OnAdded`. Ręczne dodanie zapisu + dotyczy dopiero *aktualizacji* danych „od daty" (A14). +- Dane osobowe ustawiaj na `Last`/`pracownik[date]`, nie próbuj ich „obejść" przez root — root + deleguje do zapisu, ale zapis jest właściwym miejscem. +- `Kod` bywa polem wymagającym unikalności (zależnie od konfiguracji) — kolizja wybuchnie w + `Save()` jako `RowException` (z `DuplicateKeyException` w `InnerException`). +- Całość w transakcji (`session.Logout(editMode: true)`); brak `Commit()` = rollback przy `Dispose()`. + +### A2 — Podstawowe dane kadrowe (★) + +**Cel:** uzupełnić dane ewidencyjno-identyfikacyjne pracownika (PESEL, NIP, urodzenie, płeć, +obywatelstwo, rodzice, dokument tożsamości, adresy). + +**Gdzie leżą pola — root vs PracHistoria:** + +| Dana | Lokalizacja | Pole / typ | +|---|---|---| +| Imię, drugie imię, nazwisko | `PracHistoria` | `Imie`, `ImieDrugie`, `Nazwisko: string` | +| Nazwisko rodowe, imię ojca/matki, nazwisko rodowe matki | `PracHistoria` | `NazwiskoRodowe`, `ImieOjca`, `ImieMatki`, `NazwiskoRodoweMatki: string` | +| PESEL | `PracHistoria` (oraz delegat na root) | `PESEL: string` | +| NIP | `PracHistoria` | `NIP: string` | +| Płeć | `PracHistoria` | `Plec: Soneta.Kadry.PłećOsoby` (`Kobieta`/`Mężczyzna`) | +| Data i miejsce urodzenia | `PracHistoria` (subrow `Urodzony`) | `Urodzony.Data: Date`, `Urodzony.Miejsce: string` | +| Obywatelstwo | `PracHistoria` (subrow `Obywatelstwo`) | `Obywatelstwo.Nazwa: string` (słownik), `Obywatelstwo.KodKraju: string` | +| Dokument tożsamości | `PracHistoria` (subrow `Dokument`) | `Dokument.Rodzaj: KodRodzajuDokumentu`, `Dokument.SeriaNumer: string`, `Dokument.WydanyPrzez`, `Dokument.DataWydania/DataWaznosci: Date` | +| Adres zamieszkania / zameldowania / korespondencji | `PracHistoria` | `Adres`, `AdresZameldowania`, `AdresZamieszkania`, `AdresDoKorespondencji: Soneta.Core.Adres` | +| Urząd skarbowy, koszty/ulga | `PracHistoria` (subrow `Podatki`) | `Podatki.UrzadSkarbowy`, `Podatki.KosztyRodzaj`, `Podatki.UlgaMnoznik`, … | +| Kod, numery rachunków US/ZUS | `Pracownik` (root) | `Kod: string`, `NumerRachunkuUS`, `NumerRachunkuZUS` | + +**Pułapki:** +- `Plec` jest **wyliczana z PESEL** przez weryfikator — przy poprawnym PESEL nie musisz jej ustawiać; + ustawienie ręczne ma sens dla osób bez PESEL. Typ to enum `PłećOsoby`, nie string. +- `Urodzony`, `Obywatelstwo`, `Dokument`, `Podatki` to **subrowy** (pola złożone) — edytujesz ich + pola (`Last.Urodzony.Data = …`), nie przypisujesz całego obiektu. +- `Adres` (i pozostałe) to property zwracające `Soneta.Core.Adres` — modyfikuj ich pola + (`Last.Adres.Miejscowosc = …`), nie przypisuj `Last.Adres = …`. `KodPocztowy` jest `int`; do + wartości z myślnikiem używaj `Adres.KodPocztowyS` (string). +- PESEL/NIP są danymi wrażliwymi — nie loguj ich (safe-code §12). + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["555"]; + +using (var t = session.Logout(editMode: true)) +{ + var ph = pracownik.Last; // bieżący (ostatni) zapis kadrowy + + ph.NazwiskoRodowe = "Nowak"; + ph.ImieOjca = "Jan"; + ph.NIP = "1234563218"; + + ph.Urodzony.Data = new Date(1994, 1, 8); // subrow Urodzony + ph.Urodzony.Miejsce = "Kraków"; + ph.Obywatelstwo.Nazwa = "polskie"; // subrow Obywatelstwo (słownik) + + ph.Adres.Ulica = "Wadowicka"; // subrow Adres + ph.Adres.NrDomu = "8A"; + ph.Adres.KodPocztowyS = "30-415"; + ph.Adres.Miejscowosc = "Kraków"; + + t.Commit(); +} +session.Save(); +``` + +### A7 — Dane ubezpieczenia społecznego i zdrowotnego (★) + +**Cel:** ustawić/odczytać tytuł ubezpieczenia, oddział NFZ oraz parametry ubezpieczeń społecznych +(emerytalne, rentowe, chorobowe, wypadkowe) i zdrowotnego — daty zgłoszeń i wyrejestrowań. + +**Gdzie leżą pola:** + +| Dana | Lokalizacja | Pole / typ | +|---|---|---| +| Tytuł ubezpieczenia (kod) | `PracHistoria.Etat.Ubezpieczenia` | `Tyub4: Soneta.Kadry.TytulUbezpieczenia4` (rekord słownika, tytuł 6-znakowy) | +| Zbiorczy stan ubezpieczeń | `PracHistoria.Etat` | `Etat.Ubezpieczenia: Soneta.Kadry.Ubezpieczenia` (subrow) | +| Społeczne (poszczególne) | `…Etat.Ubezpieczenia.*` | `Emerytalne`, `Rentowe`, `Chorobowe`, `Wypadkowe : Soneta.Kadry.Spoleczne` | +| Zdrowotne | `…Etat.Ubezpieczenia.Zdrowotne` | `Soneta.Kadry.Zdrowotne` (subrow) | +| Data objęcia ubezpieczeniami społ. (od) | `…Etat.Ubezpieczenia` (zbiorczo) | `ObowiazkoweOd: Date` — **zapisywalne** na zbiorczym subrowie `Ubezpieczenia` | +| Objęcie poszczególnym społ. | `…Ubezpieczenia.Emerytalne` itd. | `Obowiazkowe: bool`, `Dobrowolne: bool`, `DobrowolneOd: Date`, `Do: Date` (wyrej.) — **zapisywalne**; `Od: Date` jest tylko do odczytu (wyliczane) | +| Data objęcia zdrowotnym | `…Ubezpieczenia.Zdrowotne` | `ObowiazkoweOd: Date` — **zapisywalne** (asymetria względem `Spoleczne`) | +| Przyczyna wyrejestrowania | `…Ubezpieczenia.Emerytalne.Przyczyna` | `Przyczyna: Soneta.Kadry.Wyrejestrowanie` (subrow z kodem) | +| Oddział NFZ | `PracHistoria.OddzialNFZ` | `OddzialNFZ: Soneta.Kadry.OddzialNFZ` (subrow; `Oddział`, `KodGminy`, `OdDnia`) | +| Tytuł na dzień (odczyt) | `PracHistoria.Etat.Ubezpieczenia` | `WyliczTyubNaDzień(Date)` | + +**Pułapki:** +- Cała struktura ubezpieczeń jest **historyczna** (siedzi w `Etat` danego `PracHistoria`) — zmiana + „od daty" wymaga nowego zapisu historii (A14), nie nadpisywania bieżącego. +- `Tyub4` to rekord **konfiguracyjnego** słownika `TytulUbezpieczenia4` — pobierz istniejący wpis + przez `session.GetKadry().TytulyUbezpiecz4.WgKodu[kod]`, gdzie **`kod` jest typu `int`** (np. `110`, + `2241`), nie twórz „w locie". (Pole `Tyub`/`TypUbezpieczenia` to starsze typy — używaj `Tyub4`.) +- `OddzialNFZ` to subrow z polem `Oddział` (enum oddziałów) — ustawiasz `OddzialNFZ.Oddział`, nie + całą strukturę. +- `Emerytalne`/`Rentowe`/`Chorobowe`/`Wypadkowe` to subrowy `Spoleczne`. Ustawiasz na nich flagi + `Obowiazkowe`/`Dobrowolne` oraz `DobrowolneOd`/`Do`. **`Od` jest tylko do odczytu** (wyliczane) — + datę objęcia ubezpieczeniami obowiązkowymi ustawiasz **zbiorczo** przez `Ubezpieczenia.ObowiazkoweOd`. + Na subrowie `Zdrowotne` z kolei `ObowiazkoweOd` jest zapisywalne bezpośrednio. + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["555"]; + +// Odczyt tytułu ubezpieczenia obowiązującego na dziś: +var data = Date.Today; +TytulUbezpieczenia tyubNaDzis = pracownik[data].Etat.Ubezpieczenia.WyliczTyubNaDzień(data); + +using (var t = session.Logout(editMode: true)) +{ + var ub = pracownik.Last.Etat.Ubezpieczenia; // subrow ubezpieczeń bieżącego zapisu + + // Tytuł ubezpieczenia (rekord słownika konfiguracyjnego); klucz WgKodu jest po Kod: int: + ub.Tyub4 = session.GetKadry().TytulyUbezpiecz4.WgKodu[110]; // np. 0110 = pracownik + + // Data objęcia ubezpieczeniami społecznymi obowiązkowymi — ZBIORCZO (Od na Spoleczne jest read-only): + ub.ObowiazkoweOd = new Date(2026, 1, 1); + ub.Emerytalne.Obowiazkowe = true; + ub.Rentowe.Obowiazkowe = true; + ub.Chorobowe.Obowiazkowe = true; + + // Ubezpieczenie zdrowotne — datę objęcia ustawiasz wprost na subrowie Zdrowotne: + ub.Zdrowotne.ObowiazkoweOd = new Date(2026, 1, 1); + + // Oddział NFZ (subrow): + pracownik.Last.OddzialNFZ.OdDnia = new Date(2026, 1, 1); + + t.Commit(); +} +session.Save(); +``` + +### A9 — Dane o rodzinie pracownika (★) + +**Cel:** ewidencjonować członków rodziny i zgłaszać ich do ubezpieczenia zdrowotnego (ZCNA). + +**Kolekcja i typ:** `Pracownik.Rodzina: SubTable` (tabela `Rodzina`, +`GuidedRow` root, child `Pracownik`-a). Nowy członek rodziny tworzony jest konstruktorem +`new CzlonekRodziny(pracownik)`. + +**Pola i typy (`CzlonekRodziny`):** + +| Pole | Typ | Opis | +|---|---|---| +| `Nazwisko`, `Imie`, `ImieDrugie` | `string` | dane osobowe (wymagane: `Nazwisko`, `Imie`) | +| `PESEL`, `NIP` | `string` | identyfikatory | +| `Urodzony` | `Soneta.Kadry.Urodzony` (subrow) | `Urodzony.Data: Date`, `Urodzony.Miejsce: string` | +| `Dokument` | `Soneta.Kadry.DokumentOsoby` (subrow) | dokument tożsamości | +| `StPokrewienstwa` | `Soneta.Kadry.KodStPokrewienstwa` (enum) | stopień pokrewieństwa | +| `Ubezpieczony` | `bool` | zgłoszony do ubezpieczenia zdrowotnego (ZCNA) | +| `UbezpieczenieOkres` | `Soneta.Types.FromTo` | okres zgłoszenia do ubezpieczenia | +| `StNiepelnosprawnosci` | `Soneta.Kadry.KodStNiepelnosprawnosci` (enum) | stopień niepełnosprawności | +| `WspolneGospDomowe`, `NaUtrzymaniu`, `OdbKsztalcenie` | `bool` | przesłanki zgłoszenia | +| `Adres` | `Soneta.Core.Adres` | adres (gdy inny niż pracownika) | +| `Pracownik` | `Soneta.Kadry.Pracownik` | właściciel (ustawiany ctorem) | + +**Pułapki:** +- Zgłoszenie do ubezpieczenia zdrowotnego (ZCNA) realizuje się przez `Ubezpieczony = true` + + `UbezpieczenieOkres` + `StPokrewienstwa` — to z tych pól generowana jest deklaracja ZCNA. Brak + dedykowanego „pola daty wysyłki ZCNA" na członku rodziny. +- `CzlonekRodziny` nie jest historyczny — to płaski child pracownika; okres ubezpieczenia trzyma + pole `UbezpieczenieOkres: FromTo`. +- Konstruktor `new CzlonekRodziny(pracownik)` od razu wiąże rekord z pracownikiem; pojawia się on + w `pracownik.Rodzina`. + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["555"]; + +using (var t = session.Logout(editMode: true)) +{ + var dziecko = new CzlonekRodziny(pracownik); // ctor wiąże z pracownikiem + session.AddRow(dziecko); + dziecko.Nazwisko = "Kowalska"; + dziecko.Imie = "Zofia"; + dziecko.PESEL = "20290512345"; + dziecko.Urodzony.Data = new Date(2020, 9, 5); + dziecko.StPokrewienstwa = KodStPokrewienstwa.Dziecko; // wartość enum wg słownika + + // Zgłoszenie do ubezpieczenia zdrowotnego (ZCNA): + dziecko.Ubezpieczony = true; + dziecko.UbezpieczenieOkres = new FromTo(new Date(2026, 1, 1), Date.MaxValue); + dziecko.NaUtrzymaniu = true; + + t.Commit(); +} +session.Save(); + +// Odczyt aktualnie ubezpieczonych członków rodziny — filtr serwerowy po kolekcji: +foreach (CzlonekRodziny cr in pracownik.Rodzina[(CzlonekRodziny c) => c.Ubezpieczony]) +{ + // cr.Nazwisko, cr.Imie, cr.StPokrewienstwa +} +``` + +### A10 — Poprzednie miejsca pracy (★) + +**Cel:** rejestrować historię zatrudnienia u poprzednich pracodawców i okresy nauki (do wyliczenia +stażu pracy i uprawnień urlopowych). + +**Kolekcja i typ:** `Pracownik.HistoriaZatrudnienia: SubTable` +(tabela `HistZatrudnien`, `GuidedRow` root, child `Pracownik`-a). To **inna kolekcja niż +`Pracownik.Historia`** — `Historia` to historia *bieżącego* zatrudnienia (zapisy `PracHistoria`), +a `HistoriaZatrudnienia` to staż u *poprzednich* pracodawców. + +`HistoriaZatrudnieniaBase` jest typem bazowym z **konstruktorem `protected`** — nie da się go +utworzyć bezpośrednio. Konkretne typy do tworzenia wpisów to: +- `Soneta.Kadry.HistoriaZatrudnienia` — poprzedni pracodawca (ustawia `Typ = Zatrudnienie`), +- `Soneta.Kadry.UkonczonaSzkola` — okres nauki. +Oba mają publiczny ctor `(Pracownik)`. + +**Pola i typy (`HistoriaZatrudnieniaBase`):** + +| Pole | Typ | Opis | +|---|---|---| +| `Typ` | `Soneta.Kadry.TypHistoriiZatrudnienia` | rodzaj wpisu (praca / nauka) — ustawiany przez ctor konkretnej klasy, readonly | +| `Nazwa` | `string` | nazwa zakładu pracy / szkoły (wymagane) | +| `Okres` | `Soneta.Types.FromTo` | okres zatrudnienia/nauki | +| `EfektywnyOkres` | `Soneta.Types.FromTo` | okres efektywnie wliczany do stażu | +| `Adres1`, `Adres2` | `string` | adres pracodawcy | +| `Korekta` | `Soneta.Kadry.StazPracy` | ręczna korekta naliczonego stażu | +| `Staz` | `Soneta.Kadry.StazPracyPracownika` | wyliczony staż (kalkulowane) | +| `RodzajDokumentu` | `Soneta.Kadry.RodzajDokumentu` | dokument potwierdzający | +| `Pracownik` | `Soneta.Kadry.Pracownik` | właściciel (relacja, readonly) | + +**Pułapki:** +- **Nie** rób `new HistoriaZatrudnieniaBase(...)` — ctor jest `protected`. Twórz konkretny typ: + `new HistoriaZatrudnienia(pracownik)` (praca u poprzedniego pracodawcy, `Typ = Zatrudnienie`) albo + `new UkonczonaSzkola(pracownik)` (nauka). +- `EfektywnyOkres` ⊆ `Okres` — to on (a nie sam `Okres`) decyduje o wliczeniu do stażu; jeśli go nie + ustawisz, obowiązują weryfikatory ciągłości. +- Wpisy są niezależne od `PracHistoria` — nie myl `HistoriaZatrudnienia` (poprzedni pracodawcy) + z `Historia` (zapisy bieżącego zatrudnienia). + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["555"]; + +using (var t = session.Logout(editMode: true)) +{ + // konkretny typ: poprzedni pracodawca (Typ = Zatrudnienie ustawia ctor); AddRow zwraca typowany wiersz + var hz = session.AddRow(new HistoriaZatrudnienia(pracownik)); + hz.Nazwa = "Poprzednia Firma Sp. z o.o."; + hz.Okres = new FromTo(new Date(2018, 3, 1), new Date(2025, 12, 31)); + hz.EfektywnyOkres = hz.Okres; + hz.Adres1 = "ul. Główna 1, Kraków"; + t.Commit(); +} +session.Save(); + +// Odczyt historii zatrudnienia (wszystkie typy wpisów, bazowy typ kolekcji): +foreach (HistoriaZatrudnieniaBase hz in pracownik.HistoriaZatrudnienia) +{ + // hz.Nazwa, hz.Okres, hz.EfektywnyOkres, hz.Typ +} +``` + +### A14 — Aktualizacja danych historycznych: zmiana „od daty" vs korekta (★) + +**Cel:** poprawnie zmienić dane kadrowe — **nowy zapis obowiązujący od wskazanego dnia** (zmiana +warunków: podwyżka, zmiana wymiaru etatu, zmiana danych podatkowych) **kontra korekta istniejącego +zapisu** (poprawa błędu w obecnym okresie). Plus: odczyt zapisu obowiązującego „na dzień". + +**Mechanizm `HistorySubTable`:** + +| Operacja | API | Efekt | +|---|---|---| +| Odczyt zapisu na dzień | `pracownik[date]` (== `(PracHistoria)Historia[date]`) | zwraca zapis, którego `Aktualnosc` zawiera `date` | +| Pierwszy zapis | `pracownik.Historia.GetFirst()` | najstarszy zapis | +| Ostatni zapis | `pracownik.Last` (== `Historia.GetPrev()`) | najświeższy zapis | +| **Nowy zapis „od daty"** | `(PracHistoria)pracownik.Historia.Update(date)` | **klonuje** zapis aktualny na `date`, skraca jego okres do `date-1`, zwraca **nowy** klon z okresem od `date`; nowy klon trzeba dodać do tabeli | +| Okres obowiązywania | `PracHistoria.Aktualnosc: Soneta.Types.FromTo` | „od–do" zapisu (zarządzane przez historię) | + +**Wzorzec aktualizacji „od daty" (zmiana warunków od dnia):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["555"]; +var odDnia = new Date(2026, 7, 1); + +using (var t = session.Logout(editMode: true)) +{ + // 1) Update klonuje zapis aktualny na `odDnia` i zwraca nowy klon (okres od `odDnia`). + // Stary zapis zostaje skrócony do dnia poprzedniego. + var nowy = (PracHistoria)pracownik.Historia.Update(odDnia); + + // 2) Klon trzeba dodać do tabeli zapisów historii. + pracownik.Module.PracHistorie.AddRow(nowy); + + // 3) Na nowym zapisie wprowadzamy zmienione warunki (od `odDnia`): + nowy.Etat.MiejscePracy = "Oddział Kraków"; // np. zmiana miejsca pracy + nowy.Podatki.UlgaMnoznik = 1m; // np. zmiana danych podatkowych + + // Uwaga: część pól Etat (np. Etat.Zaszeregowanie.Wymiar) na świeżym klonie potrafi być + // w trybie tylko-do-odczytu (ColReadOnlyException) — odblokowanie zależy od konfiguracji etatu + // (patrz pułapki w sekcji B1: bramką jest Etat.Okres). Dla pewności w przykładzie zmieniamy + // pola bezpiecznie zapisywalne (MiejscePracy, dane podatkowe). + + t.Commit(); +} +session.Save(); +``` + +**Wzorzec korekty istniejącego zapisu (bez nowego okresu):** + +```csharp +using (var t = session.Logout(editMode: true)) +{ + // Modyfikujemy zapis obowiązujący na zadaną datę — bez Update, bez AddRow. + var ph = pracownik[new Date(2026, 3, 15)]; + ph.NazwiskoRodowe = "PoprawioneNazwisko"; // korekta w istniejącym okresie + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- **`Update(date)` + `AddRow(nowy)` to nierozłączna para.** Sam `Update` tworzy „odpięty" klon — + bez `PracHistorie.AddRow(nowy)` zmiana nie zostanie zapisana. +- `Update(date)` rzuca `HistorySubTable.DateDuplicateException`, gdy na `date` już zaczyna się zapis + (`Aktualnosc.From == date`) — nie da się „aktualizować" dwa razy tego samego dnia; wtedy + modyfikuj istniejący zapis (`pracownik[date]`). +- **Korekta** (modyfikacja `pracownik[date]`) zmienia dane w **całym** okresie tego zapisu — używaj + jej tylko do poprawy błędu, nie do „zmiany od dnia". +- `Aktualnosc` (okres zapisu) jest zarządzany przez mechanizm historii — **nie ustawiaj go ręcznie**; + do skrócenia/wstawienia okresu służy `Update`. +- Odczyt „na dzień": `pracownik[date]` zwraca `null`, jeśli dla daty brak zapisu — dla daty sprzed + zatrudnienia. `pracownik.Last` zawsze zwraca najświeższy zapis. +- Aktualizacja danych to operacja na danych operacyjnych pracownika — trzymaj transakcje krótkie + (safe-code §13.1) i obsłuż `RowConflictException` z `Save()` (safe-code §4). + +### A3 — Adresy (zameldowania / zamieszkania / korespondencyjny) + +**Cel:** uzupełnić/odczytać adresy pracownika. Adresy są **historyczne** — leżą na zapisie +`PracHistoria`, dostęp przez `pracownik.Last` (bieżący) lub `pracownik[date]` (na dzień). Każdy adres +to subrow typu `Soneta.Core.Adres` — modyfikujesz jego pola, nie przypisujesz całego obiektu. + +**Gdzie leżą pola — `PracHistoria` (subrowy `Soneta.Core.Adres`):** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Adres podstawowy (kanoniczny) | `Adres: Soneta.Core.Adres` | adres główny pracownika | +| Adres zameldowania | `AdresZameldowania: Soneta.Core.Adres` | | +| Adres zamieszkania | `AdresZamieszkania: Soneta.Core.Adres` | | +| Adres do korespondencji | `AdresDoKorespondencji: Soneta.Core.Adres` | | +| Adres na przelewach | `AdresNaPrzelewach: Soneta.Kadry.AdresPracownikaNaPrzelewach` | osobny typ — adres umieszczany na przelewach | + +**Pola subrowa `Soneta.Core.Adres` (zapisywalne, `bazodanowe`):** + +| Pole | Typ | Opis | +|---|---|---| +| `Miejscowosc` | `string` | miejscowość | +| `Ulica` | `string` | nazwa ulicy/alei/osiedla | +| `NrDomu` | `string` | numer domu/bloku | +| `NrLokalu` | `string` | numer lokalu | +| `KodPocztowy` | `int` | kod pocztowy (liczbowo) | +| `KodPocztowyS` | `string` | kod pocztowy z myślnikiem (np. `"30-415"`) | +| `Poczta` | `string` | poczta | +| `Gmina`, `Powiat` | `string` | gmina, powiat | +| `Wojewodztwo` | `Soneta.Core.Wojewodztwa` (enum) | województwo | +| `Kraj`, `KodKraju` | `string` | kraj / kod kraju | +| `ZagranicznyKodPocztowy` | `string` | zagraniczny kod pocztowy | +| `Telefon`, `Faks` | `string` | telefon/faks związany z adresem | +| `Pełny` | `string` | sformatowany adres (tylko odczyt) | + +**Pułapki:** +- `Adres`, `AdresZameldowania`, … to **subrowy** — modyfikuj pola (`Last.AdresZamieszkania.Ulica = …`), + **nie** przypisuj całego obiektu (`Last.AdresZamieszkania = …` — błąd). +- `KodPocztowy` to `int`; do wartości z myślnikiem używaj `KodPocztowyS` (string). +- Cała struktura jest historyczna — zmiana adresu „od daty" to nowy zapis historii (A14), korekta + bieżącego okresu to modyfikacja `pracownik[date]`. +- `Wojewodztwo` to enum `Soneta.Core.Wojewodztwa`, nie string. + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +using (var t = session.Logout(editMode: true)) +{ + var ph = pracownik.Last; + + ph.AdresZamieszkania.Miejscowosc = "Kraków"; + ph.AdresZamieszkania.Ulica = "Wadowicka"; + ph.AdresZamieszkania.NrDomu = "8A"; + ph.AdresZamieszkania.NrLokalu = "12"; + ph.AdresZamieszkania.KodPocztowyS = "30-415"; + + ph.AdresZameldowania.Miejscowosc = "Wieliczka"; + ph.AdresDoKorespondencji.Miejscowosc = "Kraków"; + + t.Commit(); +} +session.Save(); + +// Odczyt adresu na dzień: +Adres adr = pracownik[Date.Today].AdresZamieszkania; +string opis = $"{adr.Ulica} {adr.NrDomu}, {adr.KodPocztowyS} {adr.Miejscowosc}"; +``` + +--- + +### A4 — Dane kontaktowe (e-mail, telefon) i dostęp WWW/Pulpity + +**Cel:** ustawić/odczytać dane kontaktowe pracownika (e-mail, telefon komórkowy, WWW). Dane kontaktowe +leżą w subrowie `Kontakt: Soneta.Core.Kontakt` — dostępnym zarówno na rootcie `Pracownik`, jak i na +zapisie historii `PracHistoria` (historyczne). Pracownik dodatkowo udostępnia `EMAIL: string` na rootcie. + +**Gdzie leżą pola — subrow `Soneta.Core.Kontakt` (`PracHistoria.Kontakt` / `Pracownik.Kontakt`):** + +| Pole | Typ | Opis | +|---|---|---| +| `EMAIL` | `string` | adres poczty elektronicznej | +| `TelefonKomorkowy` | `string` | telefon komórkowy | +| `WWW` | `string` | adres strony internetowej | +| `Skype` | `string` | identyfikator Skype | +| `SkrytkaPocztowa` | `string` | skrytka pocztowa | + +**Telefon stacjonarny/faks** — w kontekście adresu: `PracHistoria.Adres.Telefon`, `…Adres.Faks: string` +(patrz A3). **Rozbudowane kanały kontaktu** (wiele kontaktów z rodzajem/celem): kolekcja +`PracHistoria.Kontakty: SubTable` (pola `Kontakt: string`, +`Rodzaj: Soneta.Core.RodzajKontaktu`, `Domyslny: bool`, `Opis: string`). + +**Dostęp WWW / Pulpity (IWebOperator):** `Pracownik` implementuje interfejs +`Soneta.…IWebOperator`. Konto dostępu do Pulpitów (operator web, login, uprawnienia) **nie jest +zwykłym zapisywalnym polem** pracownika — jest zarządzane osobnym mechanizmem operatorów/uprawnień +modułu web (poza prostym kontraktem ustawiania pól na pracowniku). W publicznym kontrakcie danych +kartotekowych operujesz danymi kontaktowymi (e-mail/telefon/WWW), a powiązanie operatora web jest +realizowane przez konfigurację operatorów, nie przez `pracownik.Last`. + +**Pułapki:** +- `Kontakt` to **subrow** — modyfikuj pola (`Last.Kontakt.EMAIL = …`), nie przypisuj całego obiektu. +- Pole nazywa się `EMAIL` (wielkimi literami) — uwaga na wielkość liter. +- E-mail/telefon w kontekście „na przelewach"/PPK to inne pola (`OdpisPPK.Email`, + `OdpisPPK.TelefonKomorkowy`) — nie myl z kontaktem osobowym. +- Dostęp do Pulpitu (IWebOperator) nie jest częścią `PracHistoria` — nie szukaj „pola WWW dostępu" + na zapisie kadrowym. + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +using (var t = session.Logout(editMode: true)) +{ + var k = pracownik.Last.Kontakt; // subrow Kontakt bieżącego zapisu + k.EMAIL = "g.kowalska@firma.pl"; + k.TelefonKomorkowy = "600100200"; + k.WWW = "https://firma.pl/g.kowalska"; + t.Commit(); +} +session.Save(); + +// Odczyt: +string mail = pracownik.Last.Kontakt.EMAIL; +``` + +--- + +### A5 — Rachunki bankowe (rachunek do przelewu wynagrodzenia) + +**Cel:** zarejestrować/odczytać rachunki bankowe pracownika oraz wskazać rachunek główny do przelewu +wynagrodzenia. + +**Kolekcja i typ:** `Pracownik.Rachunki: SubTable` (rachunki są +na rootcie pracownika, nie w historii). Rachunek główny (domyślny) zwraca +`Pracownik.DomyslnyRachunek: Soneta.Kasa.RachunekBankowyPodmiotu`. Numery rachunków US/ZUS pracownika +to osobne pola rootu: `Pracownik.NumerRachunkuUS: Soneta.Core.NumerRachunkuUS`, +`Pracownik.NumerRachunkuZUS: Soneta.Core.NumerRachunkuZUS`. + +**Pola i typy (`Soneta.Kasa.RachunekBankowyPodmiotu`):** + +| Pole | Typ | Opis | +|---|---|---| +| `Rachunek` | `Soneta.Kasa.RachunekBankowy` (subrow) | właściwy rachunek; `Rachunek.Numer: Soneta.Kasa.NumerRachunku`, `Rachunek.Bank: Soneta.Kasa.IBank` | +| `Rachunek.Numer.Pełny` / `.PełnyNRB` | `string` | pełny numer rachunku (do odczytu/ustawienia) | +| `Domyslne` | `bool` | rachunek domyślny (do odczytu — odpowiada `DomyslnyRachunek`) | +| `Priorytet` | `int` | priorytet rachunku | +| `Procent` | `Soneta.Types.Percent` | udział % (przy podziale wynagrodzenia na rachunki) | +| `Kwota` | `Soneta.Types.Currency` | kwota stała (przy podziale wynagrodzenia) | +| `Nazwa1`, `Nazwa2` | `string` | linie informacji na przelewie | +| `Oddzial` | `Soneta.Core.OddzialFirmy` | oddział | +| `Blokada` | `bool` | blokada rachunku | +| `Podmiot` | `Soneta.Kasa.IPodmiotKasowy` | właściciel (pracownik) | + +**Pułapki:** +- `RachunekBankowyPodmiotu` to typ z modułu `Soneta.Kasa` — element kolekcji `pracownik.Rachunki`. +- Rachunek główny do wynagrodzenia odczytujesz przez `pracownik.DomyslnyRachunek` (nie iteruj + kolekcji szukając `Domyslne == true`, gdy wystarczy property). +- `Rachunek` to **subrow** — numer ustawiasz na `r.Rachunek.Numer` (typ biznesowy `NumerRachunku`), + nie jako prosty string na poziomie `RachunekBankowyPodmiotu`. +- Numer rachunku to typ biznesowy (`NumerRachunku`/`NumerRachunkuPodmiotu`), z walidacją IBAN/NRB — + nie traktuj go jak zwykły `string`. + +**Snippet (odczyt — bezpieczny, bez zależności od konstruktora rachunku):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +// Rachunek główny do przelewu wynagrodzenia: +RachunekBankowyPodmiotu glowny = pracownik.DomyslnyRachunek; +if (glowny != null) +{ + string numer = glowny.Rachunek.Numer.Pełny; +} + +// Wszystkie rachunki pracownika: +foreach (RachunekBankowyPodmiotu r in pracownik.Rachunki) +{ + bool czyDomyslny = r.Domyslne; + int priorytet = r.Priorytet; +} +``` + +--- + +### A6 — Dane podatkowe (PIT) + +**Cel:** ustawić/odczytać dane podatkowe pracownika: koszty uzyskania przychodu, ulgę podatkową +(PIT-2), próg/typ progów podatkowych, urząd skarbowy oraz numer rachunku US. Dane są **historyczne** — +subrow `PracHistoria.Podatki`; numer rachunku US to pole rootu pracownika. + +**Gdzie leżą pola — subrow `PracHistoria.Podatki` (`pracownik.Last.Podatki`):** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Rodzaj kosztów uzyskania | `Podatki.KosztyRodzaj: Soneta.Kadry.RodzajKosztowUzyskania` (enum) | podstawowe / podwyższone / brak | +| Mnożnik kosztów | `Podatki.KosztyMnoznik: decimal` | np. 1 / 0 | +| Koszty autorskie 50% | `Podatki.Koszty50Procent: Percent`, `Koszty50Limit: decimal`, `Koszty50NieNaliczajOd: YearMonth`, `Koszty50NieNaliczajOdDnia: Date` | koszty autorskie | +| Ulga podatkowa (mnożnik) | `Podatki.UlgaMnoznik: decimal` | PIT-2: pełna kwota zmniejszająca = mnożnik (np. `1m`), `0` = brak | +| Część ulgi (podział PIT-2) | `Podatki.UlgaCzesc: Soneta.Kadry.UlgaPodatkowaCzesc` (enum) | 1/1, 1/2, 1/3 (podział między płatników) | +| Limit ulgi | `Podatki.UlgaLimit: bool` | | +| Ulga „klasa średnia" / emeryt / duża rodzina / zagranica | `Podatki.UlgaKlasaSrednia`, `UlgaEmeryt`, `UlgaDuzaRodzina`, `UlgaZagranica: bool`; `UlgaZagranicaOd/Do: int` | dodatkowe ulgi | +| Typ progów podatkowych | `Podatki.TypProgow: Soneta.Kadry.TypProgowPodatkowych` (enum) | standardowe / indywidualne | +| Progi podatkowe (indywidualne) | `Podatki.ProgiPodatkowe: SubTable` | gdy `TypProgow` = indywidualne | +| Podwyższona zaliczka (próg) | `Podatki.PodwProg2019: bool` | | +| Naliczanie PIT po 26 r.ż. (ulga dla młodych) | `Podatki.Pit26: Soneta.Kadry.NaliczajPit26` (enum) | „zerowy PIT" dla młodych | +| Rezygnacja z rozp. 07.01.22 | `Podatki.RezygnacjaRozp070122`, `…Umowa: bool` | | +| Kwota wolna przy umowie | `Podatki.UmowaKwotaWolna: bool` | | +| Adres na PIT = zameldowania | `Podatki.NaPITAdresZameldowania: bool` | | +| Urząd skarbowy | `Podatki.UrzadSkarbowy: Soneta.Core.IPodmiotUI` (ref); `UrzadSkarbowyEx: Soneta.CRM.UrzadSkarbowy` | pobierz istniejący US | +| Numer rachunku US (pracownika) | `Pracownik.NumerRachunkuUS: Soneta.Core.NumerRachunkuUS` (root) | `NumerRachunkuUS.Numer/.Pełny` | + +**Pułapki:** +- `Podatki` to **subrow** zapisu historii — modyfikuj pola (`Last.Podatki.UlgaMnoznik = …`), nie + przypisuj całego obiektu; zmiana „od daty" to nowy zapis historii (A14). +- PIT-2 (ulga) reprezentowana jest przez `UlgaMnoznik` (pełna/część kwoty zmniejszającej) oraz + `UlgaCzesc` (podział między płatników) — nie ma jednego pola „PIT2 bool". +- `KosztyRodzaj`, `TypProgow`, `UlgaCzesc`, `Pit26` to **enumy**, nie string. +- `UrzadSkarbowy` to **referencja** do istniejącego podmiotu — nie twórz „w locie". +- `NumerRachunkuUS` jest na **rootcie** pracownika, nie w `Podatki`. + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +using (var t = session.Logout(editMode: true)) +{ + var p = pracownik.Last.Podatki; + p.KosztyRodzaj = RodzajKosztowUzyskania.JedenStosPracy; // JedenStosPracy/JedenStos25/WiecejStosPracy/WiecejStos25 + p.UlgaMnoznik = 1m; // pełna kwota zmniejszająca (PIT-2) + p.UlgaCzesc = UlgaPodatkowaCzesc.Ulga112; // podział PIT-2: Ulga112/Ulga124/Ulga136 + p.TypProgow = TypProgowPodatkowych.Standardowe; // enum + t.Commit(); +} +session.Save(); + +// Odczyt: +decimal mnoznikUlgi = pracownik.Last.Podatki.UlgaMnoznik; +RodzajKosztowUzyskania koszty = pracownik.Last.Podatki.KosztyRodzaj; +``` + +--- + +### A8 — Pozostałe dane ubezpieczeniowe / informacje ZUS (oddział, kod) + +**Cel:** ustawić/odczytać dodatkowe dane ZUS pracownika — oddział ZUS oraz dodatkowe świadczenia ZUS +(emerytury/renty z dodatkowymi świadczeniami). Dane są **historyczne** — subrow +`PracHistoria.DodSwiadczeniaZUS`. (Tytuł ubezpieczenia i parametry ubezpieczeń społ./zdrow. opisuje A7.) + +**Gdzie leżą pola — subrow `PracHistoria.DodSwiadczeniaZUS: Soneta.Kadry.DodatkoweŚwiadczeniaZUS`:** + +| Pole | Typ | Opis | +|---|---|---| +| `OddzialZUS` | `Soneta.CRM.OddziałZUS` (ref) | oddział ZUS (referencja do podmiotu/słownika ZUS) | +| `Rodzaj` | `Soneta.Kadry.RodzajeDodatkowychŚwiadczeńZUS` (enum) | rodzaj dodatkowego świadczenia ZUS | +| `Okres` | `Soneta.Types.FromTo` | okres obowiązywania świadczenia | +| `Numer` | `string` | numer (decyzji/świadczenia) | + +**Oddział NFZ (komplementarny do ZUS):** `PracHistoria.OddzialNFZ: Soneta.Kadry.OddzialNFZ` — +pola `OddzialNFZ.Oddział: OddziałNFZ` (enum oddziałów), `OddzialNFZ.KodGminy: string`, +`OddzialNFZ.OdDnia: Date` (patrz też A7). + +**Pułapki:** +- `DodSwiadczeniaZUS` to **subrow** — modyfikuj pola (`Last.DodSwiadczeniaZUS.OddzialZUS = …`). +- `OddzialZUS` to **referencja** (`Soneta.CRM.OddziałZUS`) do istniejącego rekordu — pobierz + istniejący, nie twórz „w locie". +- `Rodzaj` to **enum** `RodzajeDodatkowychŚwiadczeńZUS`, nie string. +- **Cały subrow `DodSwiadczeniaZUS` bywa tylko-do-odczytu** na świeżym zapisie (rzuca + `ColReadOnlyException` nawet dla `Numer`) — pola te aktywuje dopiero zainicjowanie świadczenia + w kreatorze/UI. Zapisywalne wprost jest `OddzialNFZ.OdDnia`. +- Zmiana „od daty" to nowy zapis historii (A14). + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +// Odczyt dodatkowych świadczeń ZUS i oddziału NFZ na dziś: +var ph = pracownik.Last; +RodzajeDodatkowychŚwiadczeńZUS rodzaj = ph.DodSwiadczeniaZUS.Rodzaj; +FromTo okres = ph.DodSwiadczeniaZUS.Okres; +var oddzialNfz = ph.OddzialNFZ.Oddział; // enum oddziałów NFZ + +using (var t = session.Logout(editMode: true)) +{ + // Zapisywalne wprost na świeżym zapisie: data objęcia oddziałem NFZ. + ph.OddzialNFZ.OdDnia = new Date(2026, 1, 1); + t.Commit(); +} +session.Save(); +``` + +--- + +### A11 — Wykształcenie, znajomość języków obcych, służba wojskowa + +**Cel:** ewidencjonować wykształcenie, znane języki obce oraz dane służby wojskowej pracownika. +Wykształcenie i wojsko to subrowy zapisu historii (`PracHistoria`); języki obce to kolekcja na rootcie +pracownika. + +**Wykształcenie — subrow `PracHistoria.Wyksztalcenie: Soneta.Kadry.Wyksztalcenie`:** + +| Pole | Typ | Opis | +|---|---|---| +| `Kod` | `Soneta.Kadry.KodWyksztalcenia` (enum) | poziom/rodzaj wykształcenia | +| `StopienNaukowy` | `string` | stopień naukowy | +| `TytulNaukowy` | `string` | tytuł naukowy | + +(Kod wykształcenia GUS jest osobno — patrz A12: `PracHistoria.GUS.KodWyksztalcenia`.) + +**Języki obce — kolekcja `Pracownik.JęzykiObce: SubTable`:** + +| Pole | Typ | Opis | +|---|---|---| +| `Jezyk` | `Soneta.Kadry.DefinicjaJęzykaObcego` (ref słownik) | język | +| `Mowa` | `Soneta.Kadry.DefinicjaStopiaZnajomościJęzykaObcego` (ref) | stopień znajomości w mowie | +| `Pismo` | `Soneta.Kadry.DefinicjaStopiaZnajomościJęzykaObcego` (ref) | stopień znajomości w piśmie | +| `Zaswiadczenie` | `string` | nr/opis zaświadczenia | +| `DataWydaniaZaswiadczenia` | `Soneta.Types.Date` | data wydania zaświadczenia | +| `Uwagi` | `Soneta.Business.MemoText` | uwagi | + +**Służba wojskowa — subrow `PracHistoria.Wojsko: Soneta.Kadry.Wojsko`:** + +| Pole | Typ | Opis | +|---|---|---| +| `Stosunek` | `Soneta.Kadry.KodStosDoSluzbyWojskowej` (enum) | stosunek do służby wojskowej | +| `KategoriaZdrowia` | `Soneta.Kadry.KategoriaZdrowia` (enum) | kategoria zdrowia (A, B, …) | +| `Stopien` | `string` | stopień wojskowy | +| `NrKsiazeczki` | `string` | nr książeczki wojskowej | +| `NrSpecjalnosci` | `string` | nr specjalności wojskowej | +| `WKU` | `string` | właściwa WKU | +| `PrzydzialMobilizacyjny` | `string` | przydział mobilizacyjny | +| `Podlega` | `bool` | czy podlega obowiązkowi (odczyt) | + +**Pułapki:** +- `Wyksztalcenie` i `Wojsko` to **subrowy** `PracHistoria` (historyczne) — modyfikuj pola, zmiana + „od daty" przez A14. `JęzykiObce` to **kolekcja na rootcie** pracownika (nie historyczna). +- `Jezyk`, `Mowa`, `Pismo` to **referencje** do rekordów słownika (`DefinicjaJęzykaObcego`, + `DefinicjaStopiaZnajomościJęzykaObcego`) — pobierz istniejące, nie twórz „w locie". +- `Kod` (wykształcenie), `Stosunek`, `KategoriaZdrowia` to **enumy**, nie string. + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +using (var t = session.Logout(editMode: true)) +{ + // Wykształcenie (subrow historii): + pracownik.Last.Wyksztalcenie.Kod = KodWyksztalcenia.Wyzsze; // enum + pracownik.Last.Wyksztalcenie.TytulNaukowy = "mgr inż."; + + // Służba wojskowa (subrow historii): + pracownik.Last.Wojsko.Stosunek = KodStosDoSluzbyWojskowej.Rezerwa; // NieDotyczy/NiePodlega/Przedpoborowy/Poborowy/Rezerwa/Inne + pracownik.Last.Wojsko.KategoriaZdrowia = KategoriaZdrowia.A; + pracownik.Last.Wojsko.NrKsiazeczki = "AB123456"; + + t.Commit(); +} +session.Save(); + +// Odczyt znajomości języków obcych (kolekcja na rootcie): +foreach (ZnajomośćJęzykaObcego j in pracownik.JęzykiObce) +{ + var jezyk = j.Jezyk; // DefinicjaJęzykaObcego + var mowa = j.Mowa; // DefinicjaStopiaZnajomościJęzykaObcego +} +``` + +--- + +### A12 — Dane statystyczne GUS, kod zawodu GUS + +**Cel:** ustawić/odczytać dane statystyczne GUS pracownika (kod wykształcenia GUS, rodzaj zatrudnienia +GUS, przesłanki statystyczne) oraz kod wykonywanego zawodu. Dane są **historyczne** — subrow +`PracHistoria.GUS`; kod zawodu siedzi w `Etat`. + +**Dane statystyczne — subrow `PracHistoria.GUS: Soneta.Kadry.StatystykaGUS`:** + +| Pole | Typ | Opis | +|---|---|---| +| `KodWyksztalcenia` | `Soneta.Kadry.KodWykształceniaGUS` (enum) | kod wykształcenia wg GUS | +| `RodzajZatrudnienia` | `Soneta.Kadry.RodzajZatrudnieniaGUS` (enum) | rodzaj zatrudnienia wg GUS | +| `PopMiejsceZatrudnienia` | `Soneta.Kadry.PopMiejsceZatrudnienia` (enum) | poprzednie miejsce zatrudnienia | +| `GlowneMiejscePracy` | `bool` | główne miejsce pracy | +| `PierwszaPraca` | `bool` | pierwsza praca | +| `PracaWNocy` | `bool` | praca w nocy | +| `StRobotnicze` | `bool` | stanowisko robotnicze | +| `SezonowyDorywczy` | `bool` | zatrudnienie sezonowe/dorywcze | +| `PraceInterwencyjne` | `bool` | prace interwencyjne | + +**Kod wykonywanego zawodu — `PracHistoria.Etat`:** + +| Pole | Typ | Opis | +|---|---|---| +| `Etat.KodWykonywanegoZawodu` | `int` | kod zawodu GUS (liczbowo) | +| `Etat.KodWykonywanegoZawoduLnk` | `Soneta.Kadry.KodWykonywanegoZawodu` (ref/odczyt) | dowiązany rekord słownika kodu zawodu | + +**Pułapki:** +- `GUS` to **subrow** `PracHistoria` (historyczny) — modyfikuj pola; zmiana „od daty" przez A14. +- `KodWyksztalcenia` (GUS, enum `KodWykształceniaGUS`) to **inne pole** niż A11 + `Wyksztalcenie.Kod` (enum `KodWyksztalcenia`) — nie myl ich. +- `Etat.KodWykonywanegoZawodu` to `int`; `…Lnk` to dowiązanie do słownika (kanonicznie ustawiasz + kod liczbowo, dowiązanie jest pochodne). +- Pola GUS to enumy/bool, nie string. + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +using (var t = session.Logout(editMode: true)) +{ + var ph = pracownik.Last; + ph.GUS.KodWyksztalcenia = KodWykształceniaGUS.Wyższe; // enum GUS (uwaga na diakrytyk) + ph.GUS.GlowneMiejscePracy = true; + ph.GUS.PierwszaPraca = false; + + ph.Etat.KodWykonywanegoZawodu = 251401; // kod zawodu GUS (int) + t.Commit(); +} +session.Save(); + +// Odczyt: +KodWykształceniaGUS kw = pracownik.Last.GUS.KodWyksztalcenia; +int kodZawodu = pracownik.Last.Etat.KodWykonywanegoZawodu; +``` + +--- + +### A13 — PFRON i niepełnosprawność / schorzenia + +**Cel:** ewidencjonować dane o niepełnosprawności (stopień, orzeczenie, okresy) oraz dane PFRON +(dofinansowania, schorzenia szczególne). Dane są **historyczne** — subrow `PracHistoria.PFRON`. + +**Gdzie leżą pola — subrow `PracHistoria.PFRON: Soneta.Kadry.DanePFRON`:** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Stopień niepełnosprawności | `PFRON.Stopien: Soneta.Kadry.StNiepełnosprawności` (enum) | stopień orzeczony | +| Stopień wg PFRON | `PFRON.StopienPFRON: Soneta.Kadry.KodStNiepelnosprawnosciPFRON` (enum) | klasyfikacja PFRON | +| Okres orzeczenia/uprawnień | `PFRON.Okres: Soneta.Types.FromTo` | okres niepełnosprawności | +| Data orzeczenia | `PFRON.DataOrzeczenia: Soneta.Types.Date` | | +| Data dostarczenia orzeczenia | `PFRON.DataDostarczenia: Soneta.Types.Date` | | +| Data wniosku / zaświadczenia | `PFRON.DataWniosku`, `PFRON.DataZaswiadczenia: Date` | | +| Data zgłoszenia do ewidencji PFRON | `PFRON.DataZgloszeniaDoEwidencji: Date` | | +| Organ wydający orzeczenie | `PFRON.OrganWydajacyOrzeczenie: Soneta.Kadry.OrganWydajacyOrzeczenie` (enum) | | +| Schorzenie szczególne (flaga) | `PFRON.SzczegolneSchorzenie: bool`, `PFRON.SzczegolneSchorzeniePFRON: bool` | | +| Typ schorzenia | `PFRON.TypSchorzenia: Soneta.Kadry.SzczegolneSchorzenia` (enum) | rodzaj schorzenia | +| Schorzenia SOD (1–4) | `PFRON.TypSchorzeniaSOD`, `…2SOD`, `…3SOD`, `…4SOD: Soneta.Kadry.SzczególneSchorzeniaSOD` (enum) | schorzenia dla dofinansowania SOD | +| Lista schorzeń SOD (odczyt) | `PFRON.SchorzeniaSOD: IEnumerable` | wyliczane | +| Efekt zachęty | `PFRON.EfektZachety: bool` | warunek dofinansowania | +| Pomoc publiczna | `PFRON.PomocPubliczna: Soneta.Kadry.StanowiPomocPubliczną` (enum) | | +| Dofinansowanie dodatkowe SOD | `PFRON.DodatkoweDofinansowanieSOD: bool` | | +| Urlop dodatkowy (niepełnospr.) | `PFRON.NaliczajUrlopDodatkowy: bool`, `…Od: Date` | | +| Wymiar urlopu podstawowego | `PFRON.WymiarUPodstawowego: Soneta.Types.Fraction` | | +| Wiek emerytalny od | `PFRON.WiekEmerytalnyOd: Date` | | +| Zgoda na przekazanie danych | `PFRON.ZgodaNaPrzekazanieDanych: bool` | | + +(Zgody na pracę powyżej norm dla osób niepełnosprawnych są na `Etat`: +`Etat.PracownikNiepelnosprawnyZgodaNaPrace8h`, `…ZgodaNaPraceNadgodziny`, +`…ZgodaNaPraceWPorzeNocnej: bool`.) + +**Pułapki:** +- `PFRON` to **subrow** `PracHistoria` (historyczny) — modyfikuj pola; zmiana „od daty" przez A14. +- `Stopien`, `StopienPFRON`, `TypSchorzenia`, `…SOD`, `OrganWydajacyOrzeczenie`, `PomocPubliczna` + to **enumy**, nie string. +- `SchorzeniaSOD` (lista) jest **wyliczana** — schorzenia ustawiasz przez pola `TypSchorzeniaSOD…4SOD`. +- Daty to `Soneta.Types.Date`, okres to `FromTo` — nie `DateTime`. + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +using (var t = session.Logout(editMode: true)) +{ + var pfron = pracownik.Last.PFRON; + pfron.Stopien = StNiepełnosprawności.Umiarkowany; // enum + pfron.Okres = new FromTo(new Date(2026, 1, 1), new Date(2028, 12, 31)); + pfron.DataOrzeczenia = new Date(2025, 12, 1); + pfron.DataDostarczenia = new Date(2025, 12, 15); + pfron.SzczegolneSchorzenie = true; + pfron.TypSchorzeniaSOD = SzczególneSchorzeniaSOD.ChoróbPsychiczna; // wg słownika SOD + t.Commit(); +} +session.Save(); + +// Odczyt stopnia i okresu niepełnosprawności na dzień: +var ph = pracownik[Date.Today]; +StNiepełnosprawności stopien = ph.PFRON.Stopien; +FromTo okresNiepeln = ph.PFRON.Okres; +``` + +### A15 — Odczyt danych pracownika „na dzień" (★) + +**Cel:** pobrać właściwy rekord historyczny `PracHistoria` obowiązujący dla zadanej daty — i odróżnić +**odczyt** (nie zmienia historii) od **zmiany „od daty"** z A14 (`Update` + `AddRow`). Receptura +czysto odczytowa: idealna do uruchomienia na bazie Demo (kody `"006"`–`"009"`). + +**API odczytu — `Pracownik` + `HistorySubTable`:** + +| Operacja | API | Zwraca | +|---|---|---| +| Zapis obowiązujący na dzień | `pracownik[date]` (indeksator, `Item[Date]`) | `PracHistoria` którego `Aktualnosc` zawiera `date`, albo `null` dla daty sprzed zatrudnienia | +| Równoważnie przez kolekcję | `pracownik.Historia[date]` | jw. (indeksator `HistorySubTable.Item[Date]`) | +| Najstarszy (pierwszy) zapis | `pracownik.Historia.GetFirst()` | `PracHistoria` (pierwszy okres zatrudnienia) | +| Najświeższy (ostatni) zapis | `pracownik.Last` (== `Historia.GetLast()`) | `PracHistoria` (zawsze niepusty dla istniejącego pracownika) | +| Sąsiedni zapis | `Historia.GetPrev(ph)` / `Historia.GetNext(ph)` | poprzedni / następny zapis względem podanego | +| Okres obowiązywania zapisu | `PracHistoria.Aktualnosc: FromTo` | „od–do" zapisu (read-only z punktu widzenia kodu — zarządza historia) | + +**Różnica odczyt (A15) vs zmiana (A14):** + +| Aspekt | A15 — odczyt | A14 — zmiana „od daty" | +|---|---|---| +| Wywołanie | `pracownik[date]` | `pracownik.Historia.Update(date)` + `PracHistorie.AddRow(nowy)` | +| Efekt na historii | **żaden** (nie tworzy/skraca zapisów) | klonuje zapis aktualny na `date`, skraca poprzedni do `date-1`, dodaje nowy | +| Transakcja | niepotrzebna (sam odczyt) | wymagana (`session.Logout(editMode: true)` + `Save()`) | +| Zwraca | istniejący zapis (lub `null`) | **nowy** klon do uzupełnienia | + +**Snippet (odczyt — bez transakcji):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +// 1) Zapis obowiązujący na konkretny dzień: +var data = new Date(2026, 3, 15); +PracHistoria phNaDzien = pracownik[data]; // == pracownik.Historia[data] +if (phNaDzien != null) +{ + string nazwisko = phNaDzien.Nazwisko; + FromTo okresZapisu = phNaDzien.Aktualnosc; // okres obowiązywania tego zapisu + Fraction wymiar = phNaDzien.Etat.Wymiar; // warunki etatu „na dzień" +} + +// 2) Pierwszy i ostatni zapis historii: +PracHistoria pierwszy = pracownik.Historia.GetFirst(); // najstarszy +PracHistoria ostatni = pracownik.Last; // najświeższy (== GetLast()) + +// 3) Odczyt aktualny „na dziś" (uwzględnia datę biznesową aplikacji): +PracHistoria phDzis = pracownik[Date.Today]; +``` + +**Pułapki:** +- `pracownik[date]` zwraca **`null`** dla daty sprzed pierwszego zapisu (przed zatrudnieniem) — zawsze + sprawdzaj `null` przy datach historycznych. `pracownik.Last` jest niepusty dla istniejącego pracownika. +- Indeksator to **tylko odczyt** — nie próbuj „ustawiać" `pracownik[date] = …`. Zmiana danych w okresie + to korekta (`pracownik[date].Pole = …` w transakcji, A14) lub nowy zapis (`Update`, A14). +- `Aktualnosc` jest zarządzana przez mechanizm historii — odczytujesz, nie ustawiasz. +- `data` to `Soneta.Types.Date`, nie `DateTime`; do „dziś" używaj `Date.Today` (safe-code §10.2). +- Czysty odczyt nie wymaga transakcji edycyjnej — nie otwieraj `Logout(editMode: true)` bez potrzeby. + +### A16 — Powiązanie pracownika z kontrahentem (★) + +**Cel:** powiązać pracownika z istniejącym kontrahentem (np. gdy pracownik jest jednocześnie +kontrahentem firmy). Dwie drogi: bezpośrednie ustawienie relacji na rootcie **albo** worker +„Powiąż z kontrahentem". + +**Publiczny kontrakt — pole relacji na `Pracownik` (root):** + +| Pole | Typ | Rodzaj | Uwaga | +|---|---|---|---| +| `PowiazanyKontrahent` | `Soneta.CRM.Kontrahent` | bazodanowe, **zapisywalne** | referencja do istniejącego kontrahenta; `null` = brak powiązania | + +**Worker (alternatywa, ta sama operacja):** `Soneta.Kadry.PowiazZKontrahentemWorker` +(`[Action("Powiąż z kontrahentem")]`, metoda `Powiaz()`): + +| Składnik | Sygnatura | Uwaga | +|---|---|---| +| Pracownik | `Pracownik { get; set; }` | `[Context]` — pracownik do powiązania | +| Parametry | `Prms: Soneta.Kadry.MyParams` | `MyParams.Kontrahent: Soneta.CRM.Kontrahent` — kontrahent docelowy | +| Akcja | `void Powiaz()` | ustawia powiązanie (działa na danych sesji workera) | + +**Snippet (bezpośrednio — zalecane w kodzie biznesowym):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var kontrahent = session.GetCRM().Kontrahenci.WgKodu["KLIENT01"]; // istniejący kontrahent + +using (var t = session.Logout(editMode: true)) +{ + pracownik.PowiazanyKontrahent = kontrahent; // relacja na rootcie pracownika + t.Commit(); +} +session.Save(); + +// Odczyt powiązania: +Kontrahent powiazany = pracownik.PowiazanyKontrahent; // null gdy brak +``` + +**Snippet (przez worker — gdy chcesz przejść tą samą ścieżką co UI):** + +```csharp +using (var t = session.Logout(editMode: true)) +{ + var worker = new PowiazZKontrahentemWorker + { + Pracownik = pracownik, + Prms = new MyParams(context) { Kontrahent = kontrahent }, + }; + worker.Powiaz(); + t.CommitUI(); // worker uruchamiany „jak z UI" +} +session.Save(); +``` + +**Pułapki:** +- `Kontrahent` i `Pracownik` muszą pochodzić z **tej samej sesji** (safe-code §2.1) — kontrahenta z innej + sesji przepuść przez `session.Get(...)`. +- Relacja wskazuje **istniejący** rekord kontrahenta — nie twórz kontrahenta „w locie" w tym scenariuszu. +- `MyParams` ma konstruktor `MyParams(Context context)` — wymaga `Context` (dlatego ścieżka workera ma sens + głównie z UI). W czystym kodzie biznesowym prościej ustawić `PowiazanyKontrahent` wprost. +- W teście jednostkowym preferuj bezpośrednie pole `PowiazanyKontrahent` — nie wymaga `Context`. + +### A17 — Przeniesienie do archiwum i przywrócenie (★) + +**Cel:** przenieść pracownika do archiwum (po zakończeniu zatrudnienia) oraz przywrócić go z archiwum. +**Operacja przenoszenia/przywracania jest dostępna wyłącznie przez workery** — manager `Archiwum` +udostępnia tylko odczyt statusu. + +**Publiczny kontrakt odczytu — `Pracownik`:** + +| Składnik | Typ | Rodzaj | Uwaga | +|---|---|---|---| +| `Archiwum` | `Pracownik.ArchiwumManager` | manager (read-only API) | `Archiwum.Status: InformacjeOArchiwum`, `Archiwum.Anonimizowany: bool`, `Archiwum.Okresy: Periods` | +| `ArchiwumInfo` | `Soneta.Kadry.InformacjeOArchiwum` | bazodanowe | bieżąca informacja o archiwizacji | +| `InformacjeOArchiwum` | `FromToSubTable` | kolekcja | historia okresów w archiwum (`PracownikWArchiwum.Okres: FromTo`) | + +> **`ArchiwumManager` nie ma publicznej metody Przenieś/Przywróć** — wystawia jedynie właściwości +> tylko-do-odczytu (`Status`, `Anonimizowany`, `Okresy`). Zmiana stanu archiwum następuje wyłącznie +> przez workery poniżej. + +**Workery (jedyna droga zmiany stanu):** + +| Worker | Akcja (menu) | Metoda | Parametry | +|---|---|---|---| +| `Pracownik.PrzenieśDoArchiwumWorker` | „Archiwum/Przenieś do archiwum" | `void PrzenieśDoArchiwum()` | `Pracownik { get; set; }` (`[Context]`, pojedynczy) | +| `Pracownik.PrzywróćZArchiwumWorker` | „Archiwum/Przywróć z archiwum" | `void PrzywróćZArchiwum()` | `Pracownik { get; set; }` (`[Context]`, pojedynczy) | +| `Pracownik.PrzenieśDoArchiwumLstWorker` | „Operacje seryjne/Archiwum/Przenieś do archiwum…" | `void PrzenieśDoArchiwum()` | `Pracownicy: Pracownik[]` (grupowo) | +| `Pracownik.PrzywróćZArchiwumLstWorker` | „Operacje seryjne/Archiwum/Przywróć z archiwum…" | `void PrzywróćZArchiwum()` | `Pracownicy: Pracownik[]` (grupowo) | + +**Snippet (programowe wywołanie workera — pojedynczy pracownik):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +// Przeniesienie do archiwum: +using (var t = session.Logout(editMode: true)) +{ + var worker = new Pracownik.PrzenieśDoArchiwumWorker { Pracownik = pracownik }; + worker.PrzenieśDoArchiwum(); + t.CommitUI(); +} +session.Save(); + +// Odczyt stanu archiwizacji: +InformacjeOArchiwum status = pracownik.Archiwum.Status; +bool zanonimizowany = pracownik.Archiwum.Anonimizowany; + +// Przywrócenie z archiwum: +using (var t = session.Logout(editMode: true)) +{ + var worker = new Pracownik.PrzywróćZArchiwumWorker { Pracownik = pracownik }; + worker.PrzywróćZArchiwum(); + t.CommitUI(); +} +session.Save(); +``` + +**Snippet (operacja seryjna — wielu pracowników):** + +```csharp +var lista = session.GetKadry().Pracownicy + .Cast() + .Where(p => /* kryterium */ true) + .ToArray(); + +using (var t = session.Logout(editMode: true)) +{ + var worker = new Pracownik.PrzenieśDoArchiwumLstWorker { Pracownicy = lista }; + worker.PrzenieśDoArchiwum(); + t.CommitUI(); +} +session.Save(); +``` + +**Pułapki:** +- **Brak publicznej metody na managerze** — nie szukaj `pracownik.Archiwum.Przenieś(...)`; jedyne + publiczne API zmiany to workery `PrzenieśDoArchiwumWorker`/`PrzywróćZArchiwumWorker`. +- Workery archiwizacji modyfikują dane → wywołuj w transakcji edycyjnej i `Save()`. Worker uruchamiany + „jak z UI" → `CommitUI()` (worker-extender §3, pkt 4). +- `Archiwum`, `ArchiwumInfo`, `InformacjeOArchiwum` służą **tylko do odczytu** stanu/historii archiwum. +- Pracownik z workera i `Pracownicy[]` muszą być z bieżącej sesji (safe-code §2.1). +- Archiwizacja bywa powiązana z anonimizacją (`Archiwum.Anonimizowany`) — to oddzielny stan; przeniesienie + do archiwum nie musi oznaczać anonimizacji. + +### A18 — Wyrejestrowanie / zwolnienie pracownika (★) + +**Cel:** zakończyć zatrudnienie — ustawić rozwiązanie umowy (data, tryb, inicjatywa, podstawa prawna), +ewentualnie okres wypowiedzenia, oraz wygenerować wyrejestrowanie z ZUS (ZWUA) workerem. + +**Publiczny kontrakt — `PracHistoria.Etat` (dane historyczne zatrudnienia):** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Koniec okresu zatrudnienia | `Etat.Okres: Soneta.Types.FromTo` | `Okres.To` = ostatni dzień zatrudnienia (zmiana „od daty" → A14) | +| Rozwiązanie umowy (subrow) | `Etat.RozwiazanieUmowy: Soneta.Kadry.RozwiazanieUmowy` | zbiorczy subrow trybu zwolnienia | +| Inicjatywa zwolnienia | `Etat.RozwiazanieUmowy.Inicjatywa: KodInicjatywyZwolnienia` | enum | +| Kod zwolnienia (ZUS) | `Etat.RozwiazanieUmowy.KodZwolnienia: KodZwolnienia` | kod trybu rozwiązania | +| Podstawa prawna | `Etat.RozwiazanieUmowy.PodstawaPrawna: KodPodstawyPrawnejZwolnienia` | enum | +| Przyczyna rozwiązania | `Etat.RozwiazanieUmowy.PrzyczynaRozwUmowy: PrzyczynaRozwUmowy` | rekord słownika; opis: `PrzyczynaRozwUmowyOpis: string` | +| Za odszkodowaniem | `Etat.RozwiazanieUmowy.ZaOdszkodowaniem: bool` | — | +| Okres wypowiedzenia (subrow) | `Etat.OkresWypowiedzenia: Soneta.Kadry.OkresWypowiedzenia` | parametry wypowiedzenia | +| Długość wypowiedzenia | `Etat.OkresWypowiedzenia.Dni / .Tygodnie / .Miesiace: int` | składowe okresu | +| Data złożenia wypowiedzenia | `Etat.OkresWypowiedzenia.DataZlozenia: Date` | — | +| Skrócony okres | `Etat.OkresWypowiedzenia.Skrocony: bool` | — | +| Zwolnienie z obowiązku pracy od | `Etat.OkresWypowiedzenia.ZwolnionyZObowiazkuPracyOd: Date` | — | +| Data upływu wypowiedzenia | `Etat.OkresWypowiedzenia.Uplywa: Date` | wyliczana data rozwiązania (`DataRozwiązaniaUmowy` read-only) | + +**Worker ZUS (wyrejestrowanie ZWUA):** `Soneta.Kadry.Pracownik.WyrejestrujPracownikaWorker` +(`[Action("Operacje seryjne/Wyrejestruj pracowników...")]`, metoda `Wyrejestruj()`): + +| Składnik | Sygnatura | Uwaga | +|---|---|---| +| Ctor (parametry z `Context`) | `WyrejestrujPracownikaWorker(WyrejestrujPracownikaParams pars)` | `pars` inicjowany z `Context` | +| Data wyrejestrowania | `WyrejestrujPracownikaParams.Data: Date` | data zdarzenia ZWUA | +| Pracownicy | `Pracownicy: Pracownik[]` (`[Context]`) | lista do wyrejestrowania | +| Bieżąca data | `Current: Date` | data robocza | +| Akcja | `void Wyrejestruj()` | tworzy wyrejestrowania ZUS (ZWUA) | + +**Snippet (ustawienie rozwiązania umowy — nowy zapis „od daty", A14):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var dataRozwiazania = new Date(2026, 6, 30); + +using (var t = session.Logout(editMode: true)) +{ + var ph = pracownik[dataRozwiazania]; // zapis obowiązujący na dzień rozwiązania (A15) + var etat = ph.Etat; + + // Zamknięcie okresu zatrudnienia ostatnim dniem pracy: + etat.Okres = new FromTo(etat.Okres.From, dataRozwiazania); + + // Tryb rozwiązania (subrow RozwiazanieUmowy): + etat.RozwiazanieUmowy.Inicjatywa = KodInicjatywyZwolnienia.Pracownik; + etat.RozwiazanieUmowy.PodstawaPrawna = KodPodstawyPrawnejZwolnienia._550; // kody numeryczne wg słownika (NieDotyczy, _400.._463, _550) + + // Opcjonalnie okres wypowiedzenia: + etat.OkresWypowiedzenia.DataZlozenia = new Date(2026, 5, 31); + etat.OkresWypowiedzenia.Miesiace = 1; + + t.Commit(); +} +session.Save(); +``` + +**Snippet (wyrejestrowanie z ZUS — worker):** + +```csharp +using (var t = session.Logout(editMode: true)) +{ + var pars = new Pracownik.WyrejestrujPracownikaWorker.WyrejestrujPracownikaParams(context) + { + Data = new Date(2026, 7, 1), + }; + var worker = new Pracownik.WyrejestrujPracownikaWorker(pars) + { + Pracownicy = new[] { pracownik }, + Current = Date.Today, + }; + worker.Wyrejestruj(); + t.CommitUI(); +} +session.Save(); +``` + +**Pułapki:** +- `RozwiazanieUmowy` i `OkresWypowiedzenia` to **subrowy** `Etat` — modyfikuj ich pola, nie przypisuj całych + obiektów. +- Konkretne wartości enumów (`KodInicjatywyZwolnienia`, `KodPodstawyPrawnejZwolnienia`, `PrzyczynaRozwUmowy`) + zależą od słownika danej bazy — w teście pobierz/odczytaj realne wartości z Demo zamiast zgadywać. +- `WyrejestrujPracownikaWorker` ma **konstruktor** przyjmujący `WyrejestrujPracownikaParams`, który z kolei + wymaga `Context` (`WyrejestrujPracownikaParams(Context cx)`) — worker jest praktycznie wywoływalny tylko + z dostępnym `Context`. Bez `Context` operację wyrejestrowania ZUS zrealizujesz tylko częściowo (samo + ustawienie `Etat.Okres`/`RozwiazanieUmowy` nie tworzy dokumentu ZWUA). +- `Uplywa`/`DataRozwiązaniaUmowy` bywają wyliczane — nie nadpisuj pól read-only. +- Zmiana warunków „od dnia" to nowy zapis (A14); samo zamknięcie `Etat.Okres.To` na bieżącym zapisie jest + korektą całego okresu — używaj świadomie. + +### A19 — Przerejestrowanie pracownika (★) + +**Cel:** zmienić kod tytułu ubezpieczenia (`Tyub4`) lub jednostkę (`Wydzial`) od konkretnego dnia — +co skutkuje **wyrejestrowaniem ze starym kodem i ponownym zgłoszeniem z nowym** (ZUS ZWUA + ZUA). +Realizacja: nowy zapis historii „od daty" (A14) z innym `Etat.Ubezpieczenia.Tyub4`/`Etat.Wydzial`, +a generowanie deklaracji ZUS — workerem przerejestrowania. + +**Publiczny kontrakt — pola do zmiany (na nowym zapisie `PracHistoria`):** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Tytuł ubezpieczenia | `Etat.Ubezpieczenia.Tyub4: Soneta.Kadry.TytulUbezpieczenia4` | rekord słownika; `session.GetKadry().TytulyUbezpiecz4.WgKodu[int]` (klucz `int`, np. `110`) | +| Jednostka organizacyjna | `Etat.Wydzial: Soneta.Kadry.Wydzial` | referencja do istniejącego wydziału | + +**Worker ZUS (przerejestrowanie):** `Soneta.Deklaracje.UI.PrzerejestrowaniePracownikaWorker` +(`[Action("Przerejestrowanie pracownika …")]`, metoda `PrzerejestrowaniePracownika()`): + +| Składnik (`PrzerejestrowaniePracownikaWorker.Params`) | Typ | Uwaga | +|---|---|---| +| `DataRejestracji` | `Soneta.Types.Date` | data ponownego zgłoszenia | +| `DataWypełnienia` | `Soneta.Types.Date` | data wypełnienia deklaracji | +| `Kedu` | `Soneta.Deklaracje.ZUS.KEDU` | zbiór deklaracji ZUS (KEDU) | +| `Przyczyna` | `Soneta.Kadry.Wyrejestrowanie` | przyczyna wyrejestrowania (do ZWUA) | + +**Snippet (zmiana kodu tytułu ubezpieczenia / wydziału „od daty"):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var odDnia = new Date(2026, 7, 1); + +using (var t = session.Logout(editMode: true)) +{ + // Nowy zapis historii „od daty" (A14): Update klonuje + skraca poprzedni, AddRow dopina klon. + var nowy = (PracHistoria)pracownik.Historia.Update(odDnia); + pracownik.Module.PracHistorie.AddRow(nowy); + + // Zmiana kodu tytułu ubezpieczenia (przerejestrowanie ubezpieczeniowe): + nowy.Etat.Ubezpieczenia.Tyub4 = session.GetKadry().TytulyUbezpiecz4.WgKodu[110]; + + // Lub/oraz zmiana jednostki organizacyjnej: + nowy.Etat.Wydzial = session.GetKadry().Wydzialy.Firma; + + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- Przerejestrowanie to **nowy zapis historii** (A14: `Update(odDnia)` + `PracHistorie.AddRow(nowy)`) — nie + nadpisuj `Tyub4`/`Wydzial` na bieżącym zapisie (to zmieniłoby cały okres wstecz). +- `Tyub4` pobierasz ze słownika `TytulyUbezpiecz4` po **`int`** (`WgKodu[110]`), nie po stringu i nie „w locie". +- `Wydzial` to referencja do istniejącego wydziału (korzeń: `session.GetKadry().Wydzialy.Firma`). +- `PrzerejestrowaniePracownikaWorker` żyje w `Soneta.Deklaracje.UI` i jego `Params` wymaga m.in. `KEDU` + (zbiór deklaracji) oraz `Context` — generowanie ZWUA+ZUA jest realnie wykonalne tylko w środowisku z + `Context`/`KEDU`. Sama zmiana danych kadrowych (`Tyub4`/`Wydzial`) jest w pełni wykonalna publicznym API + bez workera; deklaracje ZUS — tylko przez worker UI. +- `Update(odDnia)` rzuca `DateDuplicateException`, jeśli na `odDnia` już zaczyna się zapis (A14). + +## B. Etat — zatrudnienie etatowe + +### B1 — Definiowanie etatu (umowa o pracę) (★) + +**Cel:** ustalić warunki zatrudnienia etatowego pracownika — rodzaj umowy o pracę, okres, daty +zawarcia/rozpoczęcia pracy, stanowisko, jednostkę organizacyjną oraz stawkę zaszeregowania +(wymiar etatu, rodzaj/typ stawki, kwota). Warunki etatu są **historyczne**: siedzą w polu +`Etat` konkretnego zapisu `PracHistoria`. Etat ustawiamy albo na świeżo utworzonym pracowniku +(`pracownik.Last.Etat`, patrz A1), albo na nowym zapisie historii „od daty" (patrz A14). + +**Gdzie leżą pola — `PracHistoria.Etat: Soneta.Kadry.Etat` (subrow zapisu historii):** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Rodzaj umowy o pracę | `Etat.TypUmowy: Soneta.Kadry.TypUmowyOPrace` | enum: `NaCzasNieokreślony`, `NaOkresPróbny`, `NaCzasOkreślony`, `NaOkresZastępstwa`, `DoDniaPorodu`, `NaCzasWykonywniaPracy`, … (`Brak = 0` = nie dotyczy) | +| Okres etatu (od–do) | `Etat.Okres: Soneta.Types.FromTo` | okres obowiązywania warunków zatrudnienia | +| Data zawarcia umowy | `Etat.DataZawarcia: Soneta.Types.Date` | data podpisania umowy | +| Data rozpoczęcia pracy | `Etat.DataRozpPracy: Soneta.Types.Date` | data faktycznego rozpoczęcia | +| Stanowisko (opis tekstowy) | `Etat.Stanowisko: string` | **wymagane dla etatu** (weryfikator przy `Save()`) | +| Jednostka organizacyjna (wydział) | `Etat.Wydzial: Soneta.Kadry.Wydzial` | **wymagane dla etatu**; pobierz istniejący wydział, korzeń struktury: `session.GetKadry().Wydzialy.Firma` | +| Oddział firmy | `Etat.Oddzial: Soneta.Core.OddzialFirmy` | opcjonalny oddział | +| Miejsce wykonywania pracy | `Etat.MiejscePracy: string` | tekst | +| Podstawa stosunku pracy | `Etat.Podstawa: Soneta.Kadry.StosPracyNaPodstawie` | enum | + +**Stawka — subrow `Etat.Zaszeregowanie: Soneta.Kadry.Zaszeregowanie`:** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Rodzaj stawki | `Zaszeregowanie.RodzajStawki: Soneta.Kadry.RodzajStawkiZaszeregowania` | enum: `Godzinowa = 0`, `Miesieczna = 1`, `DochodDeklarowany = 2` | +| Typ stawki | `Zaszeregowanie.TypStawki: Soneta.Kadry.TypStawkiZaszeregowania` | enum: `Dowolna = 0`, `Minimalna = 1`, `ZZakresu = 2`, `WgWskaźnika = 3`, `Nieokreślona = 10` | +| Wymiar etatu (ułamek) | `Zaszeregowanie.Wymiar: Soneta.Types.Fraction` | `Fraction.One` = pełny etat; `new Fraction(1, 2)` = ½ etatu | +| Kwota stawki | `Zaszeregowanie.Stawka: Soneta.Types.Currency` | kwota brutto (miesięczna lub godzinowa wg `RodzajStawki`) | +| Grupa zaszeregowania | `Zaszeregowanie.Grupa: Soneta.Kadry.GrupaZaszeregowania` | rekord słownika (opcjonalny) | +| Definicja elementu wynagrodzenia | `Zaszeregowanie.Element: Soneta.Place.DefinicjaElementu` | element płacowy wiązany ze stawką (opcjonalny) | + +**Pułapki:** +- **Kolejność ma znaczenie — `Etat.Okres` ustaw jako PIERWSZE.** Na świeżo utworzonym + pracowniku (lub świeżym zapisie historii) **cały subrow `Etat` jest w trybie tylko-do-odczytu**, + dopóki nie ustawisz `Etat.Okres` (zakres zatrudnienia). Próba ustawienia `Etat.TypUmowy`, + `Etat.Podstawa` czy `Zaszeregowanie.RodzajStawki`/`Wymiar` przed `Etat.Okres` rzuca + `Soneta.Business.ColReadOnlyException` (np. „'Etat.Typ umowy' — pole w trybie 'tylko do odczytu'"). + Po ustawieniu `Etat.Okres` pozostałe pola (w tym `Zaszeregowanie.Wymiar`) są zapisywalne — + kolejność wśród nich nie ma już znaczenia. +- **Pola wymagane dla etatu:** po ustawieniu `Etat.Okres` (pracownik staje się etatowy) `Save()` + wymaga `Etat.Wydzial` **oraz** `Etat.Stanowisko` — bez nich zapis rzuca wyjątek weryfikatora. +- `Etat` to **subrow** zapisu `PracHistoria` — modyfikujesz jego pola (`Last.Etat.Okres = …`), nie + przypisujesz całego obiektu `Etat`. +- `Zaszeregowanie` to z kolei subrow `Etat` — analogicznie modyfikujesz pola + (`Last.Etat.Zaszeregowanie.Stawka = …`). +- `Etat.Wymiar` i `Etat.TypStawki` istnieją także na poziomie `Etat` (delegaty/odczyt) — **kanonicznie + ustawiamy je na `Etat.Zaszeregowanie`** (`Zaszeregowanie.Wymiar`, `Zaszeregowanie.RodzajStawki`, + `Zaszeregowanie.TypStawki`), bo to one są polami bazodanowymi tej struktury. +- `Etat.Wydzial` i `Etat.Oddzial` to **referencje** do istniejących rekordów — nie twórz „w locie"; + korzeń struktury organizacyjnej pobierzesz przez `session.GetKadry().Wydzialy.Firma`. +- Zmiana warunków etatu **od konkretnego dnia** to nowy zapis historii (`Historia.Update(date)` + + `PracHistorie.AddRow`, patrz A14), a nie nadpisanie bieżącego zapisu (to byłaby korekta całego okresu). +- `TypUmowyOPrace` to enum, nie string; `Okres`/`DataZawarcia`/`DataRozpPracy` to typy biznesowe + `FromTo`/`Date`, nie `DateTime` (safe-code §10.1). + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); + +using (var t = session.Logout(editMode: true)) +{ + // Nowy pracownik (A1) — AddRow tworzy pierwszy zapis historii (Last) + kalendarz. + var pracownik = session.AddRow(new PracownikFirmy()); + pracownik.Kod = "555"; + pracownik.Last.Nazwisko = "Kowalska"; + pracownik.Last.Imie = "Gabriela"; + + // Warunki etatu — na Etat bieżącego (pierwszego) zapisu historii. + // KLUCZOWE: Etat.Okres MUSI być pierwszy — odblokowuje (z trybu read-only) resztę pól Etat. + var etat = pracownik.Last.Etat; + etat.Okres = new FromTo(new Date(2026, 1, 1), Date.MaxValue); // 1) NAJPIERW okres + etat.TypUmowy = TypUmowyOPrace.NaCzasNieokreślony; + etat.DataZawarcia = new Date(2025, 12, 20); + etat.DataRozpPracy = new Date(2026, 1, 1); + etat.Stanowisko = "Specjalista"; // wymagane dla etatu + etat.Wydzial = kadry.Wydzialy.Firma; // wymagane dla etatu (korzeń struktury) + + // Stawka zaszeregowania (po ustawieniu Etat.Okres pola są zapisywalne): + var z = etat.Zaszeregowanie; + z.RodzajStawki = RodzajStawkiZaszeregowania.Miesieczna; + z.TypStawki = TypStawkiZaszeregowania.Dowolna; + z.Wymiar = Fraction.One; // pełny etat + z.Stawka = (Currency)6000m; // kwota brutto miesięcznie + + t.Commit(); +} +session.Save(); +``` + +> **Zmiany warunków zatrudnienia (B2–B7).** Warunki zatrudnienia etatowego siedzą w polu `PracHistoria.Etat: Soneta.Kadry.Etat` +> (subrow zapisu historii). `Etat` jest **historyczny** wraz z całym `PracHistoria` — okres +> obowiązywania warunków trzyma `Etat.Okres: FromTo`, a okres zapisu historii `PracHistoria.Aktualnosc`. +> Zmiana warunków „od dnia" to **nowy zapis historii** (`Historia.Update(date)` + `PracHistorie.AddRow`, +> wzorzec z A14) — modyfikacja bieżącego zapisu byłaby korektą całego jego okresu. +> +> **Bramka edycji etatu (B1).** Na świeżym zapisie cały subrow `Etat` jest tylko-do-odczytu, dopóki +> nie ustawisz `Etat.Okres` — ustaw go **PIERWSZY**, inaczej dotknięcie `TypUmowy`/`Zaszeregowanie.*` +> rzuca `Soneta.Business.ColReadOnlyException`. Pola wymagane przy zapisie etatu: `Etat.Wydzial` +> **oraz** `Etat.Stanowisko`. Po `Update(date)` klon ma już ustawiony `Etat.Okres` (sklonowany ze +> starego zapisu) — zwykle nie trzeba go ustawiać ponownie, ale jeśli zmieniasz okres etatu, rób to +> jako pierwsze. + +--- + +### B2 — Zmiana warunków zatrudnienia (aneks) + +**Cel:** zarejestrować aneks do umowy o pracę — zmianę warunków obowiązującą **od wskazanego dnia** +(np. zmiana stanowiska, miejsca pracy, wymiaru, jednostki organizacyjnej). Realizuje się przez +**nowy zapis historyczny** etatu „od daty", nie przez nadpisanie bieżącego. + +**Pola `Etat` (subrow `PracHistoria.Etat: Soneta.Kadry.Etat`):** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Okres etatu (od–do) | `Etat.Okres: Soneta.Types.FromTo` | okres obowiązywania warunków; po `Update` zwykle już ustawiony | +| Rodzaj umowy o pracę | `Etat.TypUmowy: Soneta.Kadry.TypUmowyOPrace` | enum | +| Data zawarcia aneksu | `Etat.DataZawarcia: Soneta.Types.Date` | data podpisania | +| Stanowisko (opis) | `Etat.Stanowisko: string` | wymagane dla etatu | +| Jednostka organizacyjna | `Etat.Wydzial: Soneta.Kadry.Wydzial` | wymagane dla etatu; referencja (`session.GetKadry().Wydzialy.Firma`) | +| Oddział firmy | `Etat.Oddzial: Soneta.Core.OddzialFirmy` | opcjonalny | +| Miejsce wykonywania pracy | `Etat.MiejscePracy: string` | tekst | +| Podstawa stosunku pracy | `Etat.Podstawa: Soneta.Kadry.StosPracyNaPodstawie` | enum | +| Forma organizacji pracy | `Etat.FormaOrganizacjiPracy: Soneta.Kadry.FormaOrganizacjiPracy` | enum | +| Wymiar / stawka (na `Zaszeregowanie`) | `Etat.Zaszeregowanie.Wymiar: Fraction`, `Etat.Zaszeregowanie.Stawka: Currency` | patrz B3 | + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var odDnia = new Date(2026, 7, 1); + +using (var t = session.Logout(editMode: true)) +{ + // Nowy zapis historii „od daty" — klonuje zapis aktualny na `odDnia`, skraca stary do dnia + // poprzedniego i zwraca nowy klon (okres od `odDnia`). Klon MUSI trafić do tabeli zapisów. + var nowy = (PracHistoria)pracownik.Historia.Update(odDnia); + pracownik.Module.PracHistorie.AddRow(nowy); + + // Etat na klonie ma już Okres (sklonowany) — pola Etat są zapisywalne. Aneksowane warunki: + var etat = nowy.Etat; + etat.Stanowisko = "Starszy specjalista"; + etat.MiejscePracy = "Oddział Kraków"; + etat.DataZawarcia = new Date(2026, 6, 20); + etat.Wydzial = session.GetKadry().Wydzialy.Firma; // wymagane (referencja) + + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- `Update(date)` + `PracHistorie.AddRow(nowy)` to **nierozłączna para** — sam `Update` zwraca odpięty + klon; bez `AddRow` zmiana nie zostanie zapisana. +- `Update(date)` rzuca `HistorySubTable.DateDuplicateException`, gdy na `date` już zaczyna się zapis + (`Aktualnosc.From == date`) — wtedy modyfikuj istniejący zapis (`pracownik[date]`). +- Nie ustawiaj `PracHistoria.Aktualnosc` ani (zwykle) `Etat.Okres` ręcznie — zarządza nimi historia. + Jeśli aneks zmienia długość okresu etatu, ustaw `Etat.Okres` **przed** pozostałymi polami (bramka B1). +- `Etat` to subrow — modyfikuj jego pola, nie przypisuj całego obiektu. + +--- + +### B3 — Przeszeregowanie (zmiana stawki / grupy zaszeregowania) + +**Cel:** zmienić wynagrodzenie zasadnicze — stawkę i/lub grupę zaszeregowania, od wskazanego dnia. + +**Pola `Etat.Zaszeregowanie: Soneta.Kadry.Zaszeregowanie` (subrow `Etat`):** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Rodzaj stawki | `Zaszeregowanie.RodzajStawki: Soneta.Kadry.RodzajStawkiZaszeregowania` | enum: `Godzinowa = 0`, `Miesieczna = 1`, `DochodDeklarowany = 2` | +| Typ stawki | `Zaszeregowanie.TypStawki: Soneta.Kadry.TypStawkiZaszeregowania` | enum: `Dowolna`, `Minimalna`, `ZZakresu`, `WgWskaźnika`, `Nieokreślona` | +| Kwota stawki | `Zaszeregowanie.Stawka: Soneta.Types.Currency` | brutto (miesięczna/godzinowa wg `RodzajStawki`) | +| Wymiar etatu | `Zaszeregowanie.Wymiar: Soneta.Types.Fraction` | `Fraction.One` = pełny etat | +| Grupa zaszeregowania | `Etat.Grupa: Soneta.Kadry.GrupaZaszeregowania` | **leży na `Etat`, nie na `Zaszeregowanie`**; referencja do słownika `session.GetKadry().GrupyZaszer` (opcjonalna) | +| Element wynagrodzenia | `Zaszeregowanie.Element: Soneta.Place.DefinicjaElementu` | element płacowy wiązany ze stawką (opcjonalny) | +| Wskaźnik (wg wskaźnika) | `Zaszeregowanie.WskaznikNazwa: string`, `Zaszeregowanie.WskaznikKrotnosc: double` | gdy `TypStawki = WgWskaźnika` | + +**Snippet (bezpośrednia zmiana, „od daty"):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var odDnia = new Date(2026, 7, 1); + +using (var t = session.Logout(editMode: true)) +{ + var nowy = (PracHistoria)pracownik.Historia.Update(odDnia); + pracownik.Module.PracHistorie.AddRow(nowy); + + var etat = nowy.Etat; // subrow zapisu; Okres ustawiony przez Update + etat.Zaszeregowanie.Stawka = (Currency)7200m; // podwyżka stawki zasadniczej + // etat.Grupa = session.GetKadry().GrupyZaszer... // ewentualna zmiana grupy (leży na Etat, nie na Zaszeregowanie) + + t.Commit(); +} +session.Save(); +``` + +**Worker platformy (alternatywa seryjna):** przeszeregowania realizuje moduł +`Soneta.Przeszeregowania` — dokument `Soneta.Kadry.Przeszeregowanie` z elementami +`ElementPrzeszeregowania` (m.in. `Soneta.Kadry.ZmianaStawki`), wykonywany czynnością +`ZmianaStawkiWorker` (zmiana kwoty / procentowa / grupa) dla zaznaczonej grupy pracowników. Element +`ZmianaStawki` ma pola `Grupa: GrupaZaszeregowania`, `Kwota: Currency` i zapisuje wynik do +`Etat.Zaszeregowanie` właściwego zapisu historii. + +**Pułapki:** +- `Wymiar`/`Stawka`/`RodzajStawki` na **świeżym** zapisie są zapisywalne dopiero po `Etat.Okres` + (bramka B1); po `Update` okres jest już sklonowany, więc pola są zapisywalne. +- `Stawka` to `Currency` (nie `decimal`), `Wymiar` to `Fraction` (nie `double`) — safe-code §10.1. +- `Etat.Grupa`/`Zaszeregowanie.Element` to **referencje** do istniejących rekordów — nie twórz „w locie". + **Uwaga:** `Grupa` jest polem `Etat` (pobierasz ze słownika `session.GetKadry().GrupyZaszer`), a **nie** + polem `Zaszeregowanie` — `Zaszeregowanie` nie ma property `Grupa`. +- Kanonicznie ustawiasz pola stawki na `Etat.Zaszeregowanie` (pola bazodanowe), nie na delegatach + `Etat.Wymiar`/`Etat.TypStawki`. + +--- + +### B4 — Rozwiązanie / wygaśnięcie umowy o pracę + +**Cel:** zakończyć stosunek pracy z dniem rozwiązania — ustawić datę końca okresu etatu, dane +wypowiedzenia oraz przyczynę/kod rozwiązania (na potrzeby świadectwa pracy i deklaracji ZUS). + +**Wzorzec (zgodny z czynnością „Zwolnij zaznaczonych pracowników"):** +1. skróć `Etat.Okres.To` do dnia rozwiązania (na bieżącym zapisie albo na nowym zapisie „od daty"), +2. ustaw dane wypowiedzenia (`Etat.OkresWypowiedzenia.*`) i przyczynę (`Etat.RozwiazanieUmowy.*`), +3. opcjonalnie oznacz `Etat.PracownikZwolniony = true` i wyrejestruj z ubezpieczeń. + +**Pola — `Etat.OkresWypowiedzenia: Soneta.Kadry.OkresWypowiedzenia` (subrow):** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Data złożenia wypowiedzenia | `OkresWypowiedzenia.DataZlozenia: Soneta.Types.Date` | data wręczenia wypowiedzenia | +| Długość — dni / tygodnie / miesiące | `OkresWypowiedzenia.Dni: int`, `.Tygodnie: int`, `.Miesiace: int` | okres wypowiedzenia | +| Data upływu | `OkresWypowiedzenia.Uplywa: Soneta.Types.Date` | data upływu okresu wypowiedzenia | +| Skrócony | `OkresWypowiedzenia.Skrocony: bool` | skrócony okres wypowiedzenia | +| Zwolnienie z obowiązku pracy od | `OkresWypowiedzenia.ZwolnionyZObowiazkuPracyOd: Date` | | +| Data rozwiązania umowy (odczyt) | `OkresWypowiedzenia.DataRozwiązaniaUmowy: Date` | kalkulowane | + +**Pola — `Etat.RozwiazanieUmowy: Soneta.Kadry.RozwiazanieUmowy` (subrow):** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Przyczyna rozwiązania | `RozwiazanieUmowy.PrzyczynaRozwUmowy: Soneta.Kadry.PrzyczynaRozwUmowy` | **referencja do słownika** `session.GetKadry().PrzyczRozwUmow` (indeks `WgNazwy` lub iteracja; **brak indeksera `WgKodu`**); rekord ma `Typ: TypPrzyczynyRozwUmowy` | +| Opis przyczyny | `RozwiazanieUmowy.PrzyczynaRozwUmowyOpis: string` | tekst | +| Podstawa prawna | `RozwiazanieUmowy.PodstawaPrawna: Soneta.Kadry.KodPodstawyPrawnejZwolnienia` | enum (tryb rozwiązania: za wypowiedzeniem, porozumienie, wygaśnięcie itd.) | +| Kod zwolnienia (ZUS) | `RozwiazanieUmowy.KodZwolnienia: Soneta.Kadry.KodZwolnienia` | enum (kod do ZUS RA/świadectwa) | +| Inicjatywa | `RozwiazanieUmowy.Inicjatywa: Soneta.Kadry.KodInicjatywyZwolnienia` | enum (pracodawca/pracownik) | +| Za odszkodowaniem | `RozwiazanieUmowy.ZaOdszkodowaniem: bool` | | +| Pracownik zwolniony (flaga) | `Etat.PracownikZwolniony: bool` | znacznik zakończenia | + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var dataRozwiazania = new Date(2026, 9, 30); + +using (var t = session.Logout(editMode: true)) +{ + var ph = pracownik.Last; + var etat = ph.Etat; + + // 1) skrócenie okresu etatu do dnia rozwiązania + etat.Okres = new FromTo(etat.Okres.From, dataRozwiazania); + + // 2) dane wypowiedzenia + etat.OkresWypowiedzenia.DataZlozenia = new Date(2026, 8, 31); + etat.OkresWypowiedzenia.Miesiace = 1; + + // 3) przyczyna / tryb rozwiązania + // PrzyczynaRozwUmowy to rekord słownika — pobierz po nazwie (WgNazwy) albo iteracją (brak WgKodu): + etat.RozwiazanieUmowy.PrzyczynaRozwUmowy = session.GetKadry().PrzyczRozwUmow.WgNazwy["Wypowiedzenie przez pracownika"]; // referencja + // PodstawaPrawna to enum kodów: NieDotyczy, _400.._463, _550 (kody GUS/ZUS) — wybierz właściwy kod: + etat.RozwiazanieUmowy.PodstawaPrawna = KodPodstawyPrawnejZwolnienia._400; + etat.RozwiazanieUmowy.Inicjatywa = KodInicjatywyZwolnienia.Pracownik; + + etat.PracownikZwolniony = true; // znacznik zakończenia zatrudnienia + + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- **Wygaśnięcie** vs **rozwiązanie** rozróżnia `PodstawaPrawna` (enum trybu) oraz `KodZwolnienia` — + to one trafiają do świadectwa pracy i deklaracji ZUS. +- `PrzyczynaRozwUmowy` to **rekord słownika** (referencja), nie enum — pobierz istniejący wpis z + `session.GetKadry().PrzyczRozwUmow` (indeks `WgNazwy` lub iteracja — **słownik nie ma indeksera + `WgKodu`**). Pomyłka: `RozwiazanieUmowy.PrzyczynaRozwUmowy` (referencja) ≠ + `PrzyczynaRozwUmowy.Typ` (enum `TypPrzyczynyRozwUmowy` na rekordzie słownika). +- `KodPodstawyPrawnejZwolnienia` to enum kodów GUS/ZUS o wartościach `NieDotyczy`, `_400`..`_463`, + `_550` (nazwy z prefiksem `_`) — **nie ma** wartości opisowych typu + `RozwiazanieZaWypowiedzeniemPrzezPracownika`. `KodInicjatywyZwolnienia`: `NieDotyczy`, `Pracownik`, + `Pracodawca`. +- Skrócenie `Etat.Okres.To` zmienia warunki w **całym** bieżącym okresie zapisu. Jeśli rozwiązanie + ma obowiązywać od konkretnego dnia z zachowaniem poprzedniego okresu, użyj nowego zapisu + (`Historia.Update(data)` + `PracHistorie.AddRow`), a zmiany rób na klonie. +- Wyrejestrowanie z ubezpieczeń (`IUbezpieczenie.Wyrejestrowany`/daty `Do`) to osobny krok — patrz A7. +- `Okres`/`DataZlozenia`/`Uplywa` to `FromTo`/`Date`, nie `DateTime`. + +--- + +### B5 — Obniżenie / przywrócenie wymiaru etatu + +**Cel:** czasowo obniżyć wymiar etatu i stawkę (operacje typu COVID / seryjne), a następnie +przywrócić warunki. Stan obniżenia jest **odczytowo** widoczny w subrowie +`Etat.ObnizenieEtatu: Soneta.Kadry.ObniżenieWymiaruEtatu` (delegat do zapisu historii etatu). + +> **Ważne (zweryfikowane na DLL):** subrow `ObniżenieWymiaruEtatu` jest **w całości tylko-do-odczytu** +> — wszystkie jego property (`Wymiar`, `Stawka`, `RodzajStawki`, `TypStawki`, `Element`, `Kalendarz`, +> `Info`) mają `CanWrite == false`, a klasa **nie udostępnia publicznej metody `Save(...)`**. +> Z poziomu kodu biznesowego **nie da się** ustawić tych pól ani „utrwalić" obniżenia przez ten subrow. +> Pełny zapis stanu obniżenia (z metadanymi `ObniżenieWymiaruEtatuInfo`) realizują **workery platformy**. +> W zwykłym kodzie obniżenie sprowadzasz do ustawienia docelowego `Etat.Zaszeregowanie.Wymiar` +> (i ewentualnie `Stawka`) na nowym zapisie „od daty". + +**Pola odczytowe — `Etat.ObnizenieEtatu: Soneta.Kadry.ObniżenieWymiaruEtatu` (subrow, read-only):** + +| Dana | Pole / typ | Uwaga | +|---|---|---| +| Obniżony wymiar etatu | `ObnizenieEtatu.Wymiar: Soneta.Types.Fraction` | **read-only** | +| Obniżona stawka | `ObnizenieEtatu.Stawka: Soneta.Types.Currency` | **read-only** | +| Rodzaj / typ stawki | `ObnizenieEtatu.RodzajStawki`, `.TypStawki` | enumy, **read-only** | +| Kalendarz | `ObnizenieEtatu.Kalendarz: Soneta.Kalend.KalendarzBase` | referencja, **read-only** | +| Element wynagrodzenia | `ObnizenieEtatu.Element: Soneta.Place.DefinicjaElementu` | referencja, **read-only** | +| Zakres obniżenia (przełącznik) | `ObnizenieEtatu.Info: Soneta.Kadry.ObniżenieWymiaruEtatuInfo` | enum (`Brak`/`Wymiar`/`Stawka`/`Zaszeregowanie`/`Kalendarz`/…), **read-only** | + +**Snippet (obniżenie wymiaru „od daty" w kodzie biznesowym):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var odDnia = new Date(2026, 7, 1); + +using (var t = session.Logout(editMode: true)) +{ + var ph = (PracHistoria)pracownik.Historia.Update(odDnia); + pracownik.Module.PracHistorie.AddRow(ph); + + // Subrow ObnizenieEtatu jest read-only — NIE ustawiamy go bezpośrednio. + // Docelowy wymiar po obniżeniu utrwalamy na Etat.Zaszeregowanie.Wymiar (pole zapisywalne): + ph.Etat.Zaszeregowanie.Wymiar = new Fraction(4, 5); // np. obniżenie do 4/5 etatu + + t.Commit(); +} +session.Save(); +``` + +**Workery platformy (seryjne, na zaznaczonych pracownikach, tabela `Pracownicy`):** +`ObniżWymiarEtatuWorker` (obniżenie: proporcjonalnie / do / o), `ZmianaStawkiZaszeregowaniaWorker`, +`ZmianaKalendarzaWorker`, `PrzywróćWarunkiZatrudnieniaWorker` (przywrócenie warunków sprzed +obniżenia). To one zakładają nowy zapis historii „od daty" i zapisują pełny stan obniżenia +(`ObniżenieWymiaruEtatuInfo`), którego nie da się ustawić przez publiczny kontrakt subrowa. + +**Pułapki:** +- `Etat.ObnizenieEtatu` to **odczytowy delegat** do zapisu historii etatu — wszystkie property są + read-only i klasa nie ma metody `Save(...)`. W kodzie biznesowym obniżenie wymiaru realizujesz + ustawiając `Etat.Zaszeregowanie.Wymiar` (i `Stawka`) na nowym zapisie; pełny zapis stanu obniżenia + z `ObniżenieWymiaruEtatuInfo` zostaw workerom platformy. +- Operacja jest „od daty" — zawsze przez nowy zapis (`Update` + `AddRow`); inaczej zmienisz wymiar + wstecz w całym bieżącym okresie. +- Przywrócenie warunków to osobna operacja (`PrzywróćWarunkiZatrudnieniaWorker`) — nie polega na + usuwaniu obniżenia, lecz na nowym zapisie z przywróconym wymiarem. + +--- + +### B6 — Podzielniki kosztów (rozdział kosztów wynagrodzenia) + +**Cel:** rozdzielić koszty wynagrodzenia pracownika na wydziały/projekty/centra kosztów wg +współczynników. Struktura: `Pracownik` jako **źródło** podzielnika → +`pracownik.Podzielniki: SubTable` → każdy podzielnik ma **historię** +`PodzielnikKosztow.Historia: HistorySubTable` → a zapis historii ma kolekcję +`HistoriaPodzielnika.Elementy: SubTable` (poszczególne udziały). + +> Uwaga: `Pracownik.ElementyPodzielnika: SubTable` to widok zbiorczy elementów +> ze wszystkich podzielników pracownika (do odczytu). **Tworzysz** elementy na konkretnym zapisie +> `HistoriaPodzielnika`, nie przez tę kolekcję. + +**Tworzenie obiektów (konstruktory + AddRow):** + +| Obiekt | Konstruktor | Tabela / AddRow | +|---|---|---| +| Podzielnik | `new PodzielnikKosztow(pracownik)` (pracownik jako `IZrodloPodzielnikaKosztow`) | `session.GetCore().PodzielKosztow.AddRow(p)` | +| Zapis historii | `podzielnik.Historia.Update(odDnia)` | `session.GetCore().HistPodzielnikow.AddRow(h)` | +| Element udziału | `new ElementPodzielnika(historia)` | `session.GetCore().ElemPodzielnikow.AddRow(e)` | + +**Pola — `PodzielnikKosztow`:** `Nazwa: string`, `Definicja: Soneta.Core.DefinicjaPodzielnikaKosztow`, +`Zrodlo: IZrodloPodzielnikaKosztow` (pracownik, ustawiany ctorem), `Last/Historia`. +**Pola — `HistoriaPodzielnika`:** `Aktualnosc: FromTo` (okres zapisu, zarządzany), `Podstawa: decimal`, +`Elementy: SubTable`. +**Pola — `ElementPodzielnika`:** `ElementPodzialowy: Soneta.Core.IElementSlownika` (cel rozdziału — +m.in. `Wydzial`, `Projekt`, `CentrumKosztow`, `OddzialFirmy` — iface-ref), `Wspolczynnik: double`, +`Procent: Percent` (kalkulowany z współczynników). + +**Snippet:** + +```csharp +var core = session.GetCore(); +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var odDnia = new Date(2026, 1, 1); +var wydzialA = session.GetKadry().Wydzialy.Firma; // referencja do celu rozdziału (IElementSlownika) + +using (var t = session.Logout(editMode: true)) +{ + var podzielnik = new PodzielnikKosztow(pracownik); // ctor wiąże ze źródłem (pracownik) + core.PodzielKosztow.AddRow(podzielnik); + podzielnik.Nazwa = "Rozdział kosztów"; + // podzielnik.Definicja = ... // referencja do definicji (opcjonalnie) + + // zapis historii „od daty" (klon + AddRow) + var historia = podzielnik.Historia.Update(odDnia); + core.HistPodzielnikow.AddRow(historia); + + // udział: cel rozdziału + współczynnik + var element = new ElementPodzielnika(historia); + core.ElemPodzielnikow.AddRow(element); + element.ElementPodzialowy = wydzialA; + element.Wspolczynnik = 100d; // Procent wyliczany z współczynników + + t.Commit(); +} +session.Save(); + +// Odczyt zbiorczy elementów podzielnika pracownika: +foreach (ElementPodzielnika e in pracownik.ElementyPodzielnika) +{ + IElementSlownika cel = e.ElementPodzialowy; // np. Wydzial / Projekt + double wsp = e.Wspolczynnik; +} +``` + +**Pułapki:** +- Trójpoziomowa struktura — `PodzielnikKosztow` (root, źródło = pracownik) → `HistoriaPodzielnika` + (historia „od daty") → `ElementPodzielnika` (udziały). Każdy poziom: konstruktor **+** `AddRow` do + właściwej tabeli `Core`. Sam konstruktor nie wystarczy. +- `Historia.Update(odDnia)` + `HistPodzielnikow.AddRow` — para jak w A14; zmiana udziałów „od dnia" + to nowy zapis historii (a wcześniej zwykle usunięcie/`Delete()` elementów starego zapisu przy + aktualizacji tego samego okresu — patrz worker pracy zdalnej). +- `ElementPodzialowy` to **referencja interfejsowa** (`IElementSlownika`) — przypisz istniejący + rekord (`Wydzial`, `Projekt`, `CentrumKosztow`, …), nie twórz „w locie". +- `Procent` jest kalkulowany z `Wspolczynnik` poszczególnych elementów — ustawiasz współczynniki, + nie procenty. + +--- + +### B7 — Aktualizacja danych wg definicji stanowiska (matrycy) + +**Cel:** powiązać etat z definicją stanowiska i przejąć z niej parametry (stawka/grupa/wymiar, +kalendarz, kod zawodu). Definicja stanowiska to **matryca** — wzorzec wartości dla etatu. + +**Pole na etacie:** `Etat.Definicja: Soneta.HR.DefinicjaStanowiska` (referencja do słownika +konfiguracyjnego `session.GetHR().DefStanowisk`). Pokrewne: `Etat.DefinicjaFunkcji: DefinicjaFunkcji`. + +**Pola `DefinicjaStanowiska` (matryca, do skopiowania na etat):** + +| Dana | Pole / typ | +|---|---| +| Nazwa / stanowisko | `Nazwa: string`, `Stanowisko: string`, `StanowiskoPelne: string` | +| Funkcja | `Funkcja: string`, `DefinicjaFunkcji: Soneta.HR.DefinicjaFunkcji` | +| Zaszeregowanie (wzorzec) | `Zaszeregowanie: Soneta.Kadry.Zaszeregowanie` (`Stawka`, `Wymiar`, `RodzajStawki`, `Element`, `WskaznikNazwa/Krotnosc`) | +| Typ stawki / grupa | `TypStawki: TypStawkiZaszeregowania`, `Grupa: Soneta.Kadry.GrupaZaszeregowania` | +| Kalendarz | `Kalendarz: Soneta.Kalend.Kalendarz`, `NieNadpisujKalendarza: bool` | +| Kod zawodu / praca w szcz. warunkach | `KodWykonywanegoZawodu`, `KodPracyWSzczWarunkach`, `InterpretacjaKalendarza` | + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var def = session.GetHR().DefStanowisk.WgNazwa["Specjalista ds. kadr"]; // matryca (referencja; klucz WgNazwa) +var odDnia = new Date(2026, 7, 1); + +using (var t = session.Logout(editMode: true)) +{ + var nowy = (PracHistoria)pracownik.Historia.Update(odDnia); + pracownik.Module.PracHistorie.AddRow(nowy); + + var etat = nowy.Etat; + etat.Definicja = def; // powiązanie z definicją stanowiska + etat.Stanowisko = def.Stanowisko; // przeniesienie wartości z matrycy + etat.Zaszeregowanie.Wymiar = def.Zaszeregowanie.Wymiar; + etat.Zaszeregowanie.Stawka = def.Zaszeregowanie.Stawka; + + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- `Etat.Definicja` to **referencja** do rekordu konfiguracyjnego `DefStanowisk` — pobierz istniejącą + (`session.GetHR().DefStanowisk`), nie twórz „w locie". Indeks po nazwie to `WgNazwa` + (**nie `WgNazwy`**); w bazie Demo słownik bywa pusty — zabezpiecz się na brak definicji. +- Definicja jest matrycą — przeniesienie wartości (stawka/wymiar/kalendarz) na etat zrób jawnie; + samo wskazanie `Etat.Definicja` nie nadpisuje automatycznie wszystkich pól etatu w kodzie biznesowym. +- Dostępność definicji potrafi zależeć od konfiguracji (`DefinicjeStanowiskDlaWydziałów`) — definicja + może być filtrowana po wydziale. +- Zmiana stanowiska „od dnia" to nowy zapis historii (A14), nie nadpisanie bieżącego. + +## C. Dodatki, potrącenia, akordy + +### C1 — Dodatki / stałe elementy wynagrodzenia (★) + +**Cel:** przypisać pracownikowi stały element wynagrodzenia (dodatek — np. premia, dodatek +funkcyjny), oparty o definicję elementu płacowego, z okresem obowiązywania i parametrami +(podstawa/procent/czas). W UI: menu czynności *Dodatki i potrącenia → Dodaj nowy*. + +**Klasa i model:** `Soneta.Kadry.Dodatek` — `GuidedRow` root, tabela `Dodatki`, obiekt +**historyczny** (kolekcja `Dodatek.Historia: HistorySubTable`, parametry +„od–do" siedzą w zapisach `DodHistoria`). Dodatek jest childem pracownika i pojawia się w +`pracownik.Dodatki: SubTable`. + +**Tworzenie:** `new Dodatek(pracownik)` + `session.GetKadry().Dodatki.AddRow(d)`. Dodanie do tabeli +tworzy **pierwszy zapis** `DodHistoria` — dostępny od razu jako `d.Last`. Parametry ustawiamy na +`d.Last`. + +**Pola i typy (`DodHistoria` — `d.Last`):** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Element` | `Soneta.Place.DefinicjaElementu` | definicja elementu wynagrodzenia (wymagana); pobierz istniejącą z `session.GetPlace().DefElementow.WgNazwy[nazwa]` | +| `Okres` | `Soneta.Types.FromTo` | okres obowiązywania dodatku | +| `Podstawa` | `Soneta.Types.Currency` | kwota podstawy (gdy algorytm definicji jej wymaga) | +| `Procent` | `Soneta.Types.Percent` | procent (gdy algorytm definicji go wymaga) | +| `Czas` | `Soneta.Types.Time` | czas (gdy algorytm definicji go wymaga) | +| `Ulamek` | `Soneta.Types.Fraction` | ułamek (zależnie od definicji) | +| `Dni` | `int` | liczba dni (zależnie od definicji) | +| `Aktualnosc` | `Soneta.Types.FromTo` | okres zapisu historycznego (zarządzany przez historię — nie ustawiaj ręcznie) | + +**Pola na rootcie `Dodatek`:** `Nazwa: string`, `Pracownik: Soneta.Kadry.Pracownik` (właściciel, +ustawiany ctorem), `DataZakonczeniaWyplaty: Date`, `Last: DodHistoria`, +`Historia: HistorySubTable`, `Dodatki` (tabela: `session.GetKadry().Dodatki`). + +**Pobranie definicji elementu:** słownik `session.GetPlace().DefElementow` (kolekcja konfiguracyjna). +Indeksowanie po nazwie: `DefElementow.WgNazwy["Premia"]`. Definicje dodatków mają +`RodzajZrodla == Soneta.Place.RodzajŹródłaWypłaty.Dodatek` — można nimi filtrować dostępne +definicje. W bazie Demo istnieją gotowe definicje, m.in. `"Premia"`, `"Premia procentowa"`. + +**Pułapki:** +- **`new Dodatek(pracownik)` + `Dodatki.AddRow(d)` to para** — sam konstruktor nie włącza dodatku do + sesji ani nie tworzy zapisu historii. Pierwszy `DodHistoria` powstaje przy `AddRow`; dopiero potem + istnieje `d.Last`. +- `Podstawa`/`Procent`/`Czas` **mogą być tylko-do-odczytu** w zależności od algorytmu wskazanej + `DefinicjaElementu` — element kwotowy udostępnia `Podstawa`, element procentowy `Procent` itd. + Ustawiaj tylko te pola, których wymaga definicja (próba zapisu pola read-only rzuci wyjątek). +- `Element` jest **wymagany** — bez wskazania definicji elementu dodatek nie ma sensu płacowego. + Definicję pobierasz z istniejącego słownika (`DefElementow`), nie tworzysz „w locie" w tym scenariuszu. +- Zmiana parametrów dodatku **od konkretnego dnia** to nowy zapis historii dodatku + (`d.Historia.Update(date)` + `Dodatki.Module.DodHistorie.AddRow(nowy)`), analogicznie do A14 — nie + nadpisuj bieżącego zapisu, jeśli chcesz zachować poprzedni okres. +- `DodHistoria.Aktualnosc` (okres zapisu) zarządza mechanizm historii — sam ustawiasz `Okres`, + `Aktualnosc` zostaw historii. + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["006"]; + +// Definicja elementu wynagrodzenia ze słownika konfiguracyjnego (po nazwie): +var definicjaPremii = session.GetPlace().DefElementow.WgNazwy["Premia"]; + +using (var t = session.Logout(editMode: true)) +{ + // new Dodatek(pracownik) + AddRow — AddRow tworzy pierwszy zapis DodHistoria (d.Last): + var dodatek = new Dodatek(pracownik); + kadry.Dodatki.AddRow(dodatek); + + var h = dodatek.Last; // pierwszy zapis historii dodatku + h.Element = definicjaPremii; // definicja elementu (wymagana) + h.Okres = new FromTo(new Date(2026, 1, 1), Date.MaxValue); + h.Podstawa = (Currency)500m; // gdy algorytm definicji wymaga podstawy + + t.Commit(); +} +session.Save(); + +// Odczyt dodatków pracownika i ich definicji elementu (kolekcja childów): +foreach (Dodatek d in pracownik.Dodatki) +{ + DefinicjaElementu element = d.Last.Element; + FromTo okres = d.Last.Okres; +} +``` + +### C2 — Potrącenia (stałe i jednorazowe) (★) + +**Cel:** przypisać pracownikowi potrącenie z wynagrodzenia (np. składka związkowa, spłata +rozliczana ręcznie, potrącenie dobrowolne). W modelu płacowym **potrącenie nie ma osobnej klasy** — +to **`Soneta.Kadry.Dodatek`** (jak C1), tyle że oparty o **definicję elementu o charakterze +potrącenia**. O „minusowym" charakterze decyduje wyłącznie wskazana `DefinicjaElementu` (jej algorytm), +a nie typ obiektu po stronie pracownika. + +**Jak rozpoznać definicję potrącenia (`Soneta.Place.DefinicjaElementu`, słownik `DefElementow`):** + +| Pole definicji | Typ | Znaczenie dla potrącenia | +|---|---|---| +| `Algorytm.Potracenie` | `bool` | **kluczowy znacznik** — `true` dla elementu potrącającego (element pomniejsza wynagrodzenie) | +| `Algorytm.LimitPotracenia` | `Soneta.Place.TypLimituPotrącenia` | rodzaj limitu (np. do kwoty wolnej) — gdy potrącenie limitowane | +| `Algorytm.TylkoPelnePotracenie` | `bool` | potrącać tylko w pełnej kwocie (bez częściowego) | +| `RodzajZrodla` | `Soneta.Place.RodzajŹródłaWypłaty` | dla potrącenia przez dodatek **musi być** `Dodatek` (= `6`); enum **nie ma** wartości „Potrącenie" (ma natomiast m.in. `ZajęcieKomornicze` = 23, `Świadczenie` = 12, `Pożyczka` = 18, `PożyczkaSpłata` = 19). Minus realizuje algorytm, ale `DodHistoria.Element` **odrzuca** definicje o `RodzajZrodla != Dodatek` (np. „Alimenty" jako `ZajęcieKomornicze`) — patrz pułapki | + +**Mechanizm — identyczny jak C1 (Dodatek + DodHistoria):** +- `new Dodatek(pracownik)` + `session.GetKadry().Dodatki.AddRow(d)` → powstaje pierwszy `DodHistoria` + (`d.Last`). +- Na `d.Last` ustawiamy `Element` (definicja potrącenia), `Okres` oraz `Podstawa`/`Procent`/`Kwota` + zależnie od algorytmu definicji. +- **Potrącenie stałe**: `Okres` otwarty (do `Date.MaxValue`) lub na czas określony — naliczane w każdej + wypłacie z okresu. +- **Potrącenie jednorazowe**: `Okres` zawężony do jednego miesiąca rozliczeniowego (tylko ten miesiąc + obejmie naliczenie). +- Zakończenie potrącenia: `d.DataZakonczeniaWyplaty` + ewentualnie `d.PrzyczynaZakonczenia`, albo nowy + zapis historii „od daty" (`d.Historia.Update(date)`), analogicznie do C1/A14. + +**Pułapki:** +- Nie szukaj klasy „Potrącenie" — jej **nie ma**. Potrącenie = `Dodatek` z definicją, w której + `Algorytm.Potracenie == true`. Dobór definicji jest jedynym wyróżnikiem. +- **Filtruj po DWÓCH warunkach** (zweryfikowane testem): `d.Algorytm.Potracenie && d.RodzajZrodla == + RodzajŹródłaWypłaty.Dodatek`. Sam `Algorytm.Potracenie` **nie wystarcza** — przy ustawianiu + `DodHistoria.Element` definicja o innym `RodzajZrodla` (np. „Alimenty" jako `ZajęcieKomornicze`) + rzuca `System.Exception: "Zły rodzaj źródła wypłaty elementu …"`. Element zajęcia komorniczego ma + `RodzajZrodla == ZajęcieKomornicze` i podpinasz go pod `ZajęcieKomornicze`, nie pod `Dodatek` (C4). +- `Podstawa`/`Procent`/`Czas` na `DodHistoria` bywają tylko-do-odczytu zależnie od algorytmu definicji + (jak w C1) — ustawiaj tylko te, których definicja wymaga. +- `Element` wymagany; pobierany z istniejącego słownika `DefElementow`, nie tworzony „w locie". + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["006"]; + +// Definicja potrącenia ze słownika — DWA warunki: Potracenie ORAZ RodzajZrodla == Dodatek: +var def = session.GetPlace().DefElementow.Cast() + .First(d => d.RodzajZrodla == RodzajŹródłaWypłaty.Dodatek + && d.Algorytm != null && d.Algorytm.Potracenie); + +using (var t = session.Logout(editMode: true)) +{ + var potracenie = new Dodatek(pracownik); + kadry.Dodatki.AddRow(potracenie); // tworzy pierwszy DodHistoria + + var h = potracenie.Last; + h.Element = def; // definicja o Algorytm.Potracenie == true + h.Okres = new FromTo(new Date(2026, 1, 1), Date.MaxValue); // stałe + h.Podstawa = (Currency)50m; // gdy algorytm definicji wymaga kwoty + + t.Commit(); +} +session.Save(); +``` + +--- + +### C3 — Akordy (★) + +**Cel:** przypisać pracownikowi pracę akordową (rozliczaną wg ilości/strefy), z okresem i definicją +akordu; zakończyć akord. W UI: menu czynności *Akordy → Dodaj nowy / Zakończ*. + +**Klasa i model:** `Soneta.Kadry.Akord` — `GuidedRow` root, tabela `Akordy`, obiekt **historyczny** +(`Akord.Historia: HistorySubTable`; parametry „od–do" w zapisach +`AkordHistoria`, dostęp do bieżącego przez `Akord.Last`). Akord jest childem pracownika: +`pracownik.Akordy: SubTable`. + +**Pola root `Akord`:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Pracownik` | `Soneta.Kadry.Pracownik` | właściciel (relacja) | +| `Definicja` | `Soneta.Kadry.DefinicjaAkordu` | definicja akordu (słownik `DefinicjeAkordow`) | +| `Okres` | `Soneta.Types.FromTo` | okres obowiązywania akordu | +| `Typ` | `Soneta.Kadry.TypAkordu` | typ akordu | +| `Wydzial` | `Soneta.Kadry.Wydzial` | jednostka organizacyjna realizacji | +| `Last` | `Soneta.Kadry.AkordHistoria` | bieżący zapis historii | +| `Dni` | `DateSubTable` | dzienna realizacja akordu | + +**Pola `AkordHistoria` (`Akord.Last`):** `Okres: FromTo`, `Algorytm: Soneta.Kadry.AlgorytmAkordu` +(subrow z `Algorytm.Element: DefinicjaElementu`, `Algorytm.Wspolczynnik`, `Algorytm.Progi`, +`Algorytm.WgCzasu`/`Progresywny` itd.), `Jednostka: string`, `Aktualnosc: FromTo` (zarządzane przez +historię), `Progi: SubTable`. + +**Tworzenie — brak publicznego konstruktora `Akord(pracownik)`.** Akord dodaje się **workerem** +operacyjnym (kanonicznie), nie `new`. Konstruktor `Akord` jest niepubliczny (poza `RowCreator`). +Worker jest „jak z UI" (`Params` dziedziczy z `ContextBase`, ctor wymaga `Context`) — uruchamiaj go w +transakcji `CommitUI`. + +**Workery (zagnieżdżone w `Pracownik`):** ctor `(Session)`, parametry przez właściwości `Pars`/`Pracownicy`; +`Params` ma ctor `(Context)`. + +| Worker | Metoda | Wzorzec użycia | +|---|---|---| +| `Soneta.Kadry.Pracownik.DodajAkordWorker` | `DodajAkord` | `new Params(ctx) { Definicja, OdDnia, DoDnia, DodajKolejny }`; `new DodajAkordWorker(session) { Pars = par, Pracownicy = tab }` | +| `Soneta.Kadry.Pracownik.ZakończAkordWorker` | `ZakończAkord` | `new Params(ctx) { Definicja, DoDnia, ZakończWszystkie }`; `new ZakończAkordWorker(session) { Pars = par, Pracownicy = tab }` | + +**Pułapki:** +- Akordu **nie twórz przez `new Akord(...)`** — kanoniczna ścieżka to `DodajAkordWorker` (analogicznie + `ZakończAkordWorker` do zakończenia). Workery przyjmują **tablicę pracowników**, więc nadają się też do + operacji grupowej. +- `Definicja` (akordu) to rekord słownika `DefinicjeAkordow` — pobierz istniejący, nie twórz „w locie". + Sam akord wiąże dopiero z `DefinicjaElementu` (płacowym) przez `Algorytm.Element` definicji akordu. +- Akord jest historyczny — zmiana parametrów „od daty" to nowy zapis `AkordHistoria` + (`Historia.Update(date)`), analogicznie do C1/A14. +- Tabela `Akordy` to dane operacyjne — przy przeglądaniu poprzecznym filtruj zakresem (safe-code §6.3); + w zakresie jednego pracownika korzystaj z `pracownik.Akordy`. + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["006"]; +var defAkordu = kadry.DefinicjeAkordow.WgNazwa["Akord prosty"]; // klucz WgNazwa (l.poj.) +var context = login.CreateEmptyContext().Clone(session); + +using (var t = session.Logout(editMode: true)) +{ + var par = new Pracownik.DodajAkordWorker.Params(context) // Params: ctor (Context) + { + Definicja = defAkordu, + OdDnia = new Date(2026, 1, 1), + DoDnia = new Date(2026, 12, 31), + }; + // ctor (Session); parametry przez właściwości Pars/Pracownicy: + new Pracownik.DodajAkordWorker(session) { Pars = par, Pracownicy = new[] { pracownik } }.DodajAkord(); + t.CommitUI(); +} +session.Save(); + +// Odczyt akordów pracownika: +foreach (Akord a in pracownik.Akordy) +{ + DefinicjaAkordu def = a.Definicja; + FromTo okres = a.Okres; + DefinicjaElementu element = a.Last.Algorytm.Element; +} +``` + +--- + +### C4 — Zajęcia wynagrodzenia (komornicze, alimentacyjne) (★) + +**Cel:** zarejestrować zajęcie wynagrodzenia (egzekucja komornicza lub alimentacyjna) z numerem sprawy, +kwotą, priorytetem i wierzycielem (komornikiem/rachunkiem odbiorcy); anulować/przywrócić zajęcie. + +**Klasa i model:** `Soneta.Kadry.ZajęcieKomornicze` — `GuidedRow` root, tabela `ZajKomornicze`, obiekt +**historyczny** (`Historia: HistorySubTable`; limity i kwoty +„od–do" w zapisach historii, bieżący przez `Last`). Child pracownika: +`pracownik.ZajęciaKomornicze: SubTable`. **Konstruktor publiczny:** +`new ZajęcieKomornicze(pracownik)`. + +**Pola root `ZajęcieKomornicze`:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Pracownik` | `Soneta.Kadry.Pracownik` | właściciel | +| `Rodzaj` | `Soneta.Kadry.RodzajeZajęciaWynagrodzenia` | enum: `Kwota = 0`, `KwotaMiesięczna = 1` (jednorazowa kwota vs miesięczna) | +| `Element` | `Soneta.Place.DefinicjaElementu` | element płacowy zajęcia — **wymagany**; musi mieć `RodzajZrodla == ZajęcieKomornicze` (= 23) | +| `NumerSprawy` | `string` | numer sprawy egzekucyjnej | +| `Data` | `Soneta.Types.Date` | data zajęcia | +| `DataSplaty` | `Soneta.Types.Date` | data spłaty/zakończenia | +| `Rozliczenie.Odbiorca` | `Soneta.Kasa.IPodmiotKasowy` | **wierzyciel** — komornik/odbiorca (iface; może być `Kontrahent`, `Bank`, `Pracownik`, `UrzadSkarbowy`…) | +| `Rozliczenie.RachunekOdbiorcy` | `Soneta.Kasa.RachunekBankowyPodmiotu` | rachunek wierzyciela do przelewu | +| `Splacono` | `Soneta.Types.Currency` | kwota spłacona (kalkulowane/narastające) | +| `Pozostało` | `Soneta.Types.Currency` | kwota pozostała (kalkulowane) | +| `SplataZakonczona` | `bool` | spłata zakończona | +| `Anulowane` | `bool` | zajęcie anulowane (patrz workery) | +| `Korekty` | `SubTable` | korekty zajęcia | +| `OpisPrzelewu` | `string` | tytuł przelewu | + +**Limity i kwoty — na zapisie `ZajęcieKomorniczeHistoria` (`Last`):** kwota do potrącenia, limity +procentowe i kwotowe, zawieszenie spłaty, priorytet, ustawienia potrąceń z zasiłków/świadczeń (zmiana +„od daty" = nowy zapis historii). + +**Workery (zagnieżdżone w `ZajęcieKomornicze`):** ctor **bezparametrowy**, parametr przez właściwość `Zajęcie`. + +| Worker | Metoda | Wzorzec użycia | +|---|---|---| +| `Soneta.Kadry.ZajęcieKomornicze.AnulujWorker` | `Anuluj` | `new ZajęcieKomornicze.AnulujWorker { Zajęcie = zaj }.Anuluj()` | +| `Soneta.Kadry.ZajęcieKomornicze.PrzywrócWorker` | `Przywróć` | `new ZajęcieKomornicze.PrzywrócWorker { Zajęcie = zaj }.Przywróć()` | + +**Pułapki:** +- **Pole `Priorytet` NIE istnieje** na `ZajęcieKomornicze` (sprostowanie). **Alimentacyjne vs + niealimentacyjne** rozstrzyga konfiguracja: wskazana `DefinicjaElementu` (`RodzajZrodla == + ZajęcieKomornicze`) i parametry zapisu historii (limity), nie osobny typ klasy — to **jedna klasa** + `ZajęcieKomornicze`. +- `Anulowane` jest **tylko-do-odczytu** (brak publicznego settera) — anuluj **workerem** `AnulujWorker`. +- `Rozliczenie.Odbiorca` jest **interfejsem** `IPodmiotKasowy` — wskaż istniejący podmiot (zwykle + `Kontrahent`-komornik); nie twórz odbiorcy „w locie" w tym scenariuszu. +- Faktyczne **kwoty potrącenia (`Splacono`, `Pozostało`) wyliczają się przy naliczeniu wypłaty** — po + samym dodaniu zajęcia są zerowe/wyjściowe. Pełne rozliczenie wymaga naliczonej wypłaty (patrz sekcja + „niewykonalne publicznym API bez naliczenia"). +- Anulowanie/przywracanie realizuj **workerami** (`AnulujWorker`/`PrzywrócWorker`), nie ręcznym + ustawianiem `Anulowane` — workery dbają o storna i spójność rozliczenia. +- Tabela operacyjna — przegląd poprzeczny z filtrem (safe-code §6.3). + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["006"]; +// Element zajęcia — definicja o RodzajZrodla == ZajęcieKomornicze (nie zwykłe potrącenie-Dodatek): +var elementZajecia = session.GetPlace().DefElementow.Cast() + .First(d => d.RodzajZrodla == RodzajŹródłaWypłaty.ZajęcieKomornicze); +var komornik = session.GetCRM().Kontrahenci.WgKodu["KOMORNIK1"]; // wierzyciel (IPodmiotKasowy) + +using (var t = session.Logout(editMode: true)) +{ + var zajecie = new ZajęcieKomornicze(pracownik); // ctor publiczny + kadry.ZajKomornicze.AddRow(zajecie); + + zajecie.Rodzaj = RodzajeZajęciaWynagrodzenia.KwotaMiesięczna; + zajecie.Element = elementZajecia; // wymagany (RodzajZrodla == ZajęcieKomornicze) + zajecie.NumerSprawy = "KM 123/2026"; + zajecie.Data = new Date(2026, 1, 1); + zajecie.Rozliczenie.Odbiorca = komornik; // wierzyciel + zajecie.Rozliczenie.RachunekOdbiorcy = komornik.Rachunki.WgKodu["GŁÓWNY"]; + + t.Commit(); +} +session.Save(); + +// Anulowanie zajęcia (worker bezparametrowy + property Zajęcie, nie ręczna flaga): +using (var t = session.Logout(editMode: true)) +{ + var zaj = pracownik.ZajęciaKomornicze.First(); + new ZajęcieKomornicze.AnulujWorker { Zajęcie = zaj }.Anuluj(); + t.CommitUI(); +} +session.Save(); +``` + +--- + +### C5 — Operacje seryjne na dodatkach (moduł Przeszeregowania) (★) + +**Cel:** dodać / zmienić / zakończyć dodatek (oraz zmienić stawkę) dla **grupy pracowników** jedną +operacją. Realizuje to moduł **`Soneta.Przeszeregowania.PrzeszeregowaniaModule`**. Dokumentem zbiorczym +jest `Przeszeregowanie` (tabela `Przeszeregowania`, root) z pozycjami `ElementPrzeszeregowania` +(tabela `ElementyPrzeszer`, child). Pracownik widzi swoje pozycje przez +`pracownik.ElementyPrzeszeregowania`. + +**Workery operacyjne** — ctor **bezparametrowy**, parametry przez właściwości `Pars` (typu `Params`, +ctor `(Context)`) i `Pracownicy: Pracownik[]`. Uruchamiaj w transakcji `CommitUI`. **Uwaga:** workery +te w bezgłowym hoście testowym (bez operatora/kontekstu UI) rzucają `NullReferenceException` — wymagają +realnego środowiska aplikacji. + +| Worker | Metoda | Params (publiczne pola) | Działanie | +|---|---|---|---| +| `Soneta.Przeszeregowania.NowyDodatekWorker` | `NowyDodatek` | `Definicja: DefinicjaElementu, Podstawa: Currency, Procent: Percent` | wypłata/nadanie nowego dodatku grupie | +| `Soneta.Przeszeregowania.ZmianaDodatkuWorker` | `ZmianaDodatku` | `Definicja, Podstawa, ZmianaPodstawy: Currency, ProcentowaZmianaPodstawy: Percent, Procent, ZmianaProcentu: Percent, DataStawki: Date, PodstawaPrecyzja, PodstawaSposob` | zmiana parametrów istniejącego dodatku | +| `Soneta.Przeszeregowania.ZakończDodatekWorker` | `ZakończDodatek` | `Definicja: DefinicjaElementu` | zakończenie wypłaty dodatku | +| `Soneta.Przeszeregowania.DodajZmienDodatekWorker` | `DodajZmienDodatek` | `Params` (dodanie lub zmiana łącznie) | dodanie albo zmiana dodatku | +| `Soneta.Przeszeregowania.DodajNagrodęWorker` | (nagroda) | — | seryjne nagrody | +| `Soneta.Przeszeregowania.ZmianaStawkiWorker` | `ZmianaStawki` | — | seryjna zmiana stawki zaszeregowania | + +**Dokument `Przeszeregowanie` (alternatywa: zbuduj dokument i wykonaj).** Tworzenie: `new +Przeszeregowanie()` + `session.GetPrzeszeregowania().Przeszeregowania.AddRow(doc)` (kolekcja **nie ma** +`AddNew` — to standardowy `GuidedRow` root z publicznym ctorem bezparametrowym). + +| Pole | Typ | +|---|---| +| `Data` | `Soneta.Types.Date` (data przeszeregowania) | +| `DataWykonania` | `Soneta.Types.Date` | +| `Nazwa` | `string` | +| `Realizacja` | `Soneta.Przeszeregowania.RealizacjaPrzeszeregowania` (stan) | +| `Pracownicy` | `ICollection` | +| `Elementy` | `SubTable` | +| `ZarzadzaneWnioskiem` | `bool` | + +`ElementPrzeszeregowania` (child) niesie zmianę per pracownik: `Definicja: DefinicjaElementu`, +`Kwota`/`ZmianaKwoty`/`ProcentowaZmianaKwoty`, `Procent`/`ZmianaProcentu`, `Grupa: GrupaZaszeregowania`, +`Krotnosc`/`ZmianaKrotnosci`, `RodzajPrzeszergowania`, `Pracownik`, `PracHistoria`. + +**Wykonanie dokumentu:** `Soneta.Przeszeregowania.Przeszeregowanie.WykonajWorker` (metoda `Wykonaj`, +`Params { Wykonaj: bool }`) — materializuje dokument na danych pracowników (tworzy/zmienia dodatki). +`ElementPrzeszeregowania.Wykonaj(Log)` realizuje pojedynczą pozycję. + +**Pułapki:** +- To **operacja seryjna na danych operacyjnych** — trzymaj transakcje krótkie, duże grupy dziel na paczki + (safe-code §13.1). Workery przyjmują tablicę pracowników — przekaż przefiltrowaną listę (po stronie + serwera, safe-code §6). +- Workery `NowyDodatek`/`ZmianaDodatku`/`ZakończDodatek` operują na **definicji elementu** (`Definicja`), + więc wybór właściwej `DefinicjaElementu` jest kluczowy (po nazwie / `RodzajZrodla == Dodatek`). +- Sam dokument `Przeszeregowanie` **nie zmienia danych** dopóki nie zostanie wykonany (`WykonajWorker`); + do tego momentu to plan. Po `Wykonaj` zmiany trafiają w dodatki/etat pracowników. +- Indywidualne (jednostkowe) odpowiedniki to workery z C2/C1 na pojedynczym pracowniku + (`Pracownik.DodajDodatekWorker`/`ZmieńDodatekWorker`/`ZabierzDodatekWorker`); moduł Przeszeregowania + jest dla **grupy**. + +**Snippet (operacja seryjna — nowy dodatek dla grupy):** + +```csharp +var kadry = session.GetKadry(); +var def = session.GetPlace().DefElementow.WgNazwy["Premia"]; + +// Grupa pracowników — filtr serwerowy (np. po wydziale), nie pełny skan: +Pracownik[] grupa = kadry.Pracownicy[(Pracownik p) => p.Last.Etat.Okres.Contains(Date.Today)] + .Cast().ToArray(); + +var context = login.CreateEmptyContext().Clone(session); + +using (var t = session.Logout(editMode: true)) +{ + var par = new NowyDodatekWorker.Params(context) // Params: ctor (Context) + { + Definicja = def, + Podstawa = (Currency)300m, + }; + // ctor bezparametrowy; parametry przez właściwości Pars/Pracownicy: + new NowyDodatekWorker { Pars = par, Pracownicy = grupa }.NowyDodatek(); + t.CommitUI(); +} +session.Save(); +``` + +--- + +### C6 — Świadczenia socjalne (ZFŚS) i ich rozliczenie (★) + +**Cel:** przyznać pracownikowi świadczenie socjalne z ZFŚS (zapomoga, dopłata do wypoczynku, paczka) +i ustawić jego rozliczenie płacowe (element, kwota, okres). + +**Klasa i model:** `Soneta.Kadry.SwiadczSocjalne` — `GuidedRow` root, tabela `SwiadczeniaSoc`. Child +pracownika: `pracownik.Swiadczenia: SubTable`. **Konstruktor publiczny:** +`new SwiadczSocjalne(pracownik)`. + +**Pola `SwiadczSocjalne`:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Pracownik` | `Soneta.Kadry.Pracownik` | właściciel | +| `Definicja` | `Soneta.Kadry.DefinicjaŚwiadczeniaSocjalnego` | rodzaj świadczenia (słownik `DefSwiadczSocjal`) | +| `Data` | `Soneta.Types.Date` | data przyznania | +| `Nazwa` | `string` | nazwa | +| `Opis` | `Soneta.Business.MemoText` | opis | +| `Rozliczenie` | `Soneta.Kadry.RozliczenieSwiadczenia` (subrow) | dane rozliczeniowe (poniżej) | + +**Subrow `Rozliczenie` (`RozliczenieSwiadczenia`):** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Rozliczenie.Element` | `Soneta.Place.DefinicjaElementu` | element płacowy do rozliczenia świadczenia | +| `Rozliczenie.Kwota` | `Soneta.Types.Currency` | kwota świadczenia | +| `Rozliczenie.Okres` | `Soneta.Types.FromTo` | okres rozliczenia | +| `Rozliczenie.Data` | `Soneta.Types.Date` | data rozliczenia | +| `Rozliczenie.Rozliczone` | `bool` | czy rozliczone (po naliczeniu wypłaty) | + +**Definicja (`DefinicjaŚwiadczeniaSocjalnego`, słownik `DefSwiadczSocjal`):** `Nazwa: string`, +`Element: DefinicjaElementu` (domyślny element rozliczenia), `Kwota: Currency` (domyślna kwota). Z niej +dziedziczy świadczenie domyślny element i kwotę. + +**Worker seryjny:** `Soneta.Kadry.SwiadczSocjalne.DodajŚwiadczenieWorker` (metoda `DodajŚwiadczenie`) — +ctor **bezparametrowy**, parametry przez właściwości `Pars` i `Pracownicy: Pracownik[]`; `Params` ma +ctor `(Context)`: `Params { Definicja: DefinicjaŚwiadczeniaSocjalnego, DataPrzyznania: Date, Kwota: +Currency, Element: DefinicjaElementu, DataRozliczenia: Date }` — nadaje świadczenie grupie (menu +*Operacje seryjne / Dodaj świadczenia socjalne*). Wzorzec: +`new DodajŚwiadczenieWorker { Pars = new …Params(ctx){…}, Pracownicy = tab }.DodajŚwiadczenie()`. + +**Pułapki:** +- `Definicja` (świadczenia) pobierana ze słownika `DefSwiadczSocjal`; jej `Element`/`Kwota` są domyślne — + na konkretnym świadczeniu nadpisujesz przez `Rozliczenie.Element`/`Rozliczenie.Kwota`. +- **Faktyczne rozliczenie (wypłata świadczenia, `Rozliczenie.Rozliczone == true`) następuje przy + naliczeniu wypłaty** — samo dodanie świadczenia tworzy tylko zlecenie rozliczenia. +- Dla grupy używaj `DodajŚwiadczenieWorker`; pojedynczo — `new SwiadczSocjalne(pracownik)` + `AddRow`. + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["006"]; +var defSwiadcz = kadry.DefSwiadczSocjal.WgNazwy["Dopłata do wypoczynku"]; +var element = session.GetPlace().DefElementow.WgNazwy["Świadczenie socjalne"]; + +using (var t = session.Logout(editMode: true)) +{ + var sw = new SwiadczSocjalne(pracownik); + kadry.SwiadczeniaSoc.AddRow(sw); + + sw.Definicja = defSwiadcz; + sw.Data = new Date(2026, 6, 1); + sw.Rozliczenie.Element = element; // element płacowy rozliczenia + sw.Rozliczenie.Kwota = (Currency)1000m; + sw.Rozliczenie.Okres = FromTo.Month(new YearMonth(2026, 6)); + + t.Commit(); +} +session.Save(); + +// Odczyt świadczeń pracownika: +foreach (SwiadczSocjalne s in pracownik.Swiadczenia) +{ + Currency kwota = s.Rozliczenie.Kwota; + bool rozliczone = s.Rozliczenie.Rozliczone; +} +``` + +--- + +### C7 — Pożyczki (KZP / ZFM) (★) + +**Cel:** zarejestrować członkostwo pracownika w funduszu pożyczkowym, udzielić pożyczki, zbudować +harmonogram rat i potrącać raty z wynagrodzenia. + +**Hierarchia obiektów (wszystkie `GuidedRow` root, childy pracownika):** +- `Soneta.Kadry.FundPozyczkowy` (tabela `FundPozyczkowe`) — **członkostwo** w funduszu; + `pracownik.FunduszePozyczkowe: SubTable`. Ctor: + `new FundPozyczkowy(pracownik, definicja)`. +- `Soneta.Kadry.Pozyczka` (tabela `Pozyczki`) — **pożyczka** udzielona w ramach funduszu; kolekcja + `fundusz.Pozyczki: SubTable`. Ctor: `new Pozyczka(fundusz)`. +- `Soneta.Kadry.RataPozyczki` (tabela `RatyPozyczek`) — **rata** harmonogramu; `pozyczka.Raty: + SubTable`. Raty pracownik widzi przez `pracownik.SplacaneRaty` + (oraz `ZyrowaneRaty` jako żyrant). Ctor: `new RataPozyczki(pozyczka)`. +- `Soneta.Kadry.DefinicjaFunduszuPozyczkowego` (słownik `DefFundPozycz`, konfiguracyjny) — zasady + funduszu (oprocentowanie, elementy płacowe wpisowego/składki/wycofania). + +**Pola `Pozyczka` (kluczowe):** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Fundusz` | `Soneta.Kadry.FundPozyczkowy` | fundusz, w ramach którego udzielono | +| `Data` | `Soneta.Types.Date` | data udzielenia | +| `Kwota` | `Soneta.Types.Currency` | kwota pożyczki | +| `Element` | `Soneta.Place.DefinicjaElementu` | element wypłaty pożyczki | +| `ElementRaty` | `Soneta.Place.DefinicjaElementu` | element potrącenia raty | +| `IloscRat` | `int` | liczba rat | +| `KwotaRaty` | `Soneta.Types.Currency` | kwota raty | +| `SplatyOd` | `Soneta.Types.YearMonth` | miesiąc rozpoczęcia spłat | +| `Procent` | `Soneta.Types.Percent` | oprocentowanie | +| `Sposob` | `Soneta.Kadry.SposóbSpłatyOdsetek` | sposób spłaty odsetek | +| `AlgorytmRaty` | `Soneta.Kadry.AlgorytmRatyPożyczki` | algorytm wyliczania raty | +| `Raty` | `SubTable` | harmonogram rat | +| `Stan` | `Soneta.Kadry.StanSpłat` | enum: `NieSpłacona = 0`, `Częściowo = 1`, `Całkowicie = 2` | +| `Splacona` | `bool` | spłacona w całości | + +**Pola `RataPozyczki`:** `Pozyczka`, `Data: Date`, `Miesiąc: YearMonth`, `Kapital: Currency`, +`Odsetki: Currency`, `Element: DefinicjaElementu` (potrącenie raty), `Stan: StanSpłat`, +`Pozostaje`/`PozostajeKapitał`/`PozostajeOdsetki` (kalkulowane), `Zyrant: Pracownik`, +`Splacajacy: Pracownik`. + +**Generowanie harmonogramu (workery):** + +| Worker | Metoda | Params / sygnatura | +|---|---|---| +| `Soneta.Kadry.Pozyczka.UzgodnijRatyWorker` | `UzgodnijRaty` | ctor bezparam.; `Pars = new Params(ctx) { UzgodnijRaty = true }` (uwaga: `PrzeliczRaty` jest **tylko-do-odczytu**), `Pożyczka = pozyczka` — **buduje/przelicza harmonogram rat** wg `IloscRat`/`KwotaRaty`/`SplatyOd` | +| `Soneta.Kadry.Pozyczka.PożyczkaWorker` | `Pożyczka` | podsumowanie spłat (props: `Razem`, `Spłaty`, `Pozostaje`, `RazemOdsetki`…) | +| `Soneta.Kadry.Pozyczka.ElementWypłatyWorker` | `Pokaż` | podgląd elementu wypłaty pożyczki | + +Metody na samym `Pozyczka`: `pozyczka.UpdatePozyczka()` (przelicz), `pozyczka.Rata(YearMonth, +DefinicjaElementu)`, `pozyczka.RatyZaMiesiąc(YearMonth)`, `pozyczka.SąRaty(YearMonth)`. + +**Pułapki:** +- Ścieżka tworzenia jest **trzystopniowa**: `FundPozyczkowy(pracownik, definicja)` → `Pozyczka(fundusz)` + → harmonogram. Pożyczki **nie da się** utworzyć bez funduszu (ctor wymaga `FundPozyczkowy`). +- Harmonogram rat generuj **workerem** `UzgodnijRatyWorker` (albo `UpdatePozyczka()`), nie ręcznym + dodawaniem `RataPozyczki` — worker rozkłada kapitał/odsetki wg algorytmu. +- `Element` (wypłaty) i `ElementRaty` (potrącenia) to **różne** definicje elementów — `ElementRaty` + realizuje potrącenie raty w wypłacie. +- **Faktyczne potrącenie raty następuje przy naliczeniu wypłaty** — `Stan`/`Splacono`/`Pozostaje` + aktualizują się po naliczeniu. Samo udzielenie pożyczki ich nie zmienia. +- `DefinicjaFunduszuPozyczkowego` to słownik konfiguracyjny — pobierz istniejący wpis, nie twórz „w locie". + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["006"]; +var defFunduszu = kadry.DefFundPozycz.WgNazwy["KZP"]; +var elWyplata = session.GetPlace().DefElementow.WgNazwy["Pożyczka"]; +var elRata = session.GetPlace().DefElementow.WgNazwy["Spłata pożyczki"]; + +using (var t = session.Logout(editMode: true)) +{ + // 1) Członkostwo w funduszu + var fundusz = new FundPozyczkowy(pracownik, defFunduszu); + kadry.FundPozyczkowe.AddRow(fundusz); + fundusz.Okres = new FromTo(new Date(2026, 1, 1), Date.MaxValue); + + // 2) Pożyczka w ramach funduszu + var pozyczka = new Pozyczka(fundusz); + kadry.Pozyczki.AddRow(pozyczka); + pozyczka.Data = new Date(2026, 1, 10); + pozyczka.Kwota = (Currency)12000m; + pozyczka.Element = elWyplata; + pozyczka.ElementRaty = elRata; + pozyczka.IloscRat = 12; + pozyczka.SplatyOd = new YearMonth(2026, 2); + + // 3) Harmonogram rat (worker bezparametrowy; Params: ctor (Context); PrzeliczRaty read-only) + var context = login.CreateEmptyContext().Clone(session); + var par = new Pozyczka.UzgodnijRatyWorker.Params(context) { UzgodnijRaty = true }; + new Pozyczka.UzgodnijRatyWorker { Pars = par, Pożyczka = pozyczka }.UzgodnijRaty(); + + t.CommitUI(); +} +session.Save(); + +// Odczyt harmonogramu: +foreach (FundPozyczkowy f in pracownik.FunduszePozyczkowe) + foreach (Pozyczka p in f.Pozyczki) + foreach (RataPozyczki r in p.Raty) + { + YearMonth m = r.Miesiąc; + Currency kapital = r.Kapital, odsetki = r.Odsetki; + StanSpłat stan = r.Stan; + } +``` + +## D. Nieobecności i czas pracy + +### 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 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 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 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 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()`. + +--- + +### 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. + +--- + +### 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()`. + +### 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 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 D2/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 D2. + +--- + +### 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. + +--- + +### 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. + +--- + +### 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 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 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. + +--- + +### D8 — Naliczanie i przeliczanie limitów nieobecności + +**Cel:** naliczyć limit nieobecności (jak 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 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 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 D7). +- Workery wykonują własne transakcje — wywołuj poza otwartą transakcją edycyjną; obsłuż wyjątki z `Save()`. + +--- + +### 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]`. + +--- + +### 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 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 A14/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 + (D8) i okres zasiłkowy (D6) dopiero przy przeliczeniu/naliczeniu. + +--- + +### 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 (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 (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 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. + +--- + +### 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. + +## E. Plan pracy i kalendarz + +> **Model kalendarza pracownika.** Każdy `Pracownik` ma kalendarz roboczy +> (`pracownik.Etat.Kalendarz : Soneta.Kalend.Kalendarz`), którego dni leżą w tabeli +> `DniKalendarza` (`DzienKalendarzaBase`, child kalendarza). Pracownik wystawia trzy +> niezależne kolekcje dni typu `DateSubTable` (indeksator po dacie `[Date]`, **tylko do +> odczytu** — element tworzysz konstruktorem + `AddRow`): +> - `pracownik.DniPlanu : DateSubTable` — **plan/harmonogram** (dni `DzienPlanu : DzienKalendarzaBase`); to `pracownik.Etat.Kalendarz.Dni`. +> - `pracownik.DniPracy : DateSubTable` — **ewidencja** (realizacja) czasu pracy. +> - `pracownik.DniRCP : DateSubTable` — **zarejestrowany** czas pracy (RCP) — patrz sekcja F. +> +> Wszystkie dni współdzielą subrow `Praca : Soneta.Kalend.CzasPracy` z polami +> `OdGodziny`/`DoGodziny`/`Czas : Soneta.Types.Time`. Definicja dnia (`Definicja : +> Soneta.Kalend.DefinicjaDnia`) to rekord **konfiguracyjny** (słownik `DefinicjeDni`, +> indeksator `[Kod]`). +> +> **Ograniczenie wykonalności.** Plan i ewidencja są normalnie wyliczane przez kalkulator +> czasu pracy z definicji kalendarza/serii — ręczne tworzenie pojedynczego dnia jest możliwe +> publicznym kontraktem (ctor `(Pracownik, Date)` + `AddRow`), ale **wymaga zdefiniowanego +> `DefinicjaDnia` w konfiguracji**. Operacje masowe (przeliczenie planu na okres) są zaszyte +> w workerach/kalkulatorach UI — patrz E2. + +### E1 — Wprowadzanie planowanego czasu pracy (★) + +**Cel:** odczytać lub ustawić plan pracy (harmonogram) pracownika na konkretny dzień — +godziny od–do, normę dobową oraz typ dnia. + +**Pola i typy:** + +| Element | Lokalizacja | Typ | Uwaga | +|---|---|---|---| +| Plan pracy (cała kolekcja) | `pracownik.DniPlanu` | `Soneta.Business.DateSubTable` | == `pracownik.Etat.Kalendarz.Dni`; indeksator `[Date]` (get) | +| Dzień planu | `pracownik.DniPlanu[data]` | `Soneta.Kalend.DzienPlanu` (`DzienKalendarzaBase`) | `null`, gdy dla daty brak dnia planu | +| Data dnia | `DzienPlanu.Data` | `Soneta.Types.Date` | bazodanowe; ustawiane przez ctor | +| Godziny pracy (subrow) | `DzienPlanu.Praca` | `Soneta.Kalend.CzasPracy` | `Praca.OdGodziny`, `Praca.DoGodziny`, `Praca.Czas : Time` (zapisywalne) | +| Czas (norma dnia, odczyt) | `DzienPlanu.Czas` | `Soneta.Types.Time` | kalkulowane (czas pracy dnia) | +| Od (odczyt) | `DzienPlanu.OdGodziny` | `Soneta.Types.Time` | kalkulowane | +| Definicja dnia | `DzienPlanu.Definicja` | `Soneta.Kalend.DefinicjaDnia` | rekord słownika konfiguracyjnego `DefinicjeDni` | +| Tolerancja wejścia | `DzienPlanu.TolerancjaWe` | `Soneta.Types.Time` | bazodanowe | +| Norma dobowa kalendarza | `pracownik.Etat.Kalendarz.NormaDobowa` | `Soneta.Types.Time` | poziom kalendarza, nie dnia | +| Słownik definicji dni | `session.GetKalend().DefinicjeDni` | `DefinicjeDni` | indeksator `[kod: string]`; skróty `WolnaSobota`, `Niedziela` | + +**Snippet:** + +```csharp +var kalend = session.GetKalend(); +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +// --- Odczyt planu na dzień (bezpiecznie: indeksator zwraca null dla braku dnia) --- +var data = new Date(2026, 6, 1); +var dzienPlanu = (DzienPlanu)pracownik.DniPlanu[data]; +if (dzienPlanu is not null) +{ + Time odGodz = dzienPlanu.Praca.OdGodziny; // np. 8:00 + Time doGodz = dzienPlanu.Praca.DoGodziny; // np. 16:00 + Time normaDnia = dzienPlanu.Czas; // wyliczona norma dnia (kalkulowane) + DefinicjaDnia typDnia = dzienPlanu.Definicja; +} + +// --- Ustawienie/utworzenie dnia planu (wymaga DefinicjaDnia z konfiguracji) --- +using (var t = session.Logout(editMode: true)) +{ + var dp = (DzienPlanu)pracownik.DniPlanu[data]; + if (dp is null) + { + dp = session.AddRow(new DzienPlanu(pracownik, data)); // ctor (Pracownik, Date) + dp.Definicja = kalend.DefinicjeDni["RB"]; // typ dnia ze słownika (np. dzień roboczy) + } + dp.Praca.OdGodziny = new Time(8, 0); + dp.Praca.DoGodziny = new Time(16, 0); // Czas dnia wylicza się z od–do + + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- `DniPlanu` to `DateSubTable` **nietypowany** (zwraca `Row`) — rzutuj na `DzienPlanu`. Indeksator + `[Date]` jest **tylko do odczytu**: nowego dnia nie „przypiszesz", tworzysz go ctorem + `new DzienPlanu(pracownik, data)` + `session.AddRow(...)`. +- Godziny ustawiasz na **subrowie** `Praca` (`dp.Praca.OdGodziny = …`), nie na `dp.OdGodziny` — + to ostatnie jest kalkulowane (read-only). Po ustawieniu od–do `Praca.Czas`/`Czas` przeliczają się. +- `Definicja` to rekord **konfiguracyjnego** słownika `DefinicjeDni` — pobierz istniejący wpis + (`kalend.DefinicjeDni[kod]`), nie twórz „w locie". Bez przypisanego `Definicja` świeży dzień planu + może nie przejść weryfikatorów. +- Plan jest zwykle generowany przez kalkulator z definicji kalendarza (serie dni, święta) — + ręczne nadpisywanie pojedynczego dnia to korekta, nie sposób budowy całego harmonogramu (do tego + służy operacja seryjna / kopiowanie planu, E2). +- Norma dobowa to atrybut **kalendarza** (`Etat.Kalendarz.NormaDobowa`), nie pojedynczego dnia. + +### E2 — Planowanie czasu pracy grupy (kopiowanie planu) (★) + +**Cel:** skopiować wyliczony plan pracy (harmonogram) na wskazany okres — dla jednego pracownika +albo dla grupy, oraz seryjnie zaktualizować kalendarz pracowników (zmiana kalendarza docelowego). + +**Publiczny kontrakt — dwie drogi:** + +| Operacja | API | Charakter | +|---|---|---| +| Kopiowanie **planu** pracownika na okres | `Soneta.Kalend.KalendarzPlanuKopia.Kopiuj(Pracownik pracownik, FromTo okres)` (**public static**) | bez UI — proste API | +| Kopiowanie **pracy/realizacji** na okres | `Soneta.Kalend.KalendarzPracyKopia.Kopiuj(Pracownik pracownik, FromTo okres)` (**public static**) | bez UI — proste API | +| Kopiowanie grupy (worker UI) | `KalendarzPlanuKopia.KopiujWorker` / `KalendarzPracyKopia.KopiujWorker` | wymaga `Context` z zaznaczeniem | +| Aktualizacja kalendarza grupy | `Soneta.Kadry.AktualizujKalendarzWorker` | wymaga `Params` z `Context` | + +**Worker `KopiujWorker` (BI/„Kopiuj plan…", „Kopiuj pracę…"):** klasa `ContextBase` z ctorem +`(Context context)`; pola `[Context] FromTo Okres`, `[Context] Pracownik[] Pracownicy`; metoda +`void Kopiuj()`. Działa **wyłącznie** z kontekstem UI (zaznaczona lista pracowników) i jest gardzona +licencją BI/BI_PL/PL oraz `IsVisibleKopiuj` (niedostępny na mobile). + +**Worker `AktualizujKalendarzWorker`:** pola `[Context] Pracownik[] Pracownicy`, +`Params Pars` (`Pars.Data`, `Pars.TylkoOstatni: bool`, `Pars.PowodAktualizacji: string`, +`Pars.Kalendarze: KalendarzBase[]`, `Pars.Docelowy: Kalendarz`, `Pars.Zmiana: bool`, +`Pars.Interpretacja`), metoda `void Aktualizuj()`. `Params` to `ContextBase` (ctor `(Context)`). + +**Snippet (proste API dla jednego pracownika — bez UI):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var okres = new FromTo(new Date(2026, 6, 1), new Date(2026, 6, 30)); + +using (var t = session.Logout(editMode: true)) +{ + // Wylicza plan z kalendarza i zapisuje do kopii planu pracownika za wskazany okres: + KalendarzPlanuKopia.Kopiuj(pracownik, okres); // public static + // analogicznie realizacja: KalendarzPracyKopia.Kopiuj(pracownik, okres); + t.Commit(); +} +session.Save(); +``` + +**Snippet (grupa — przez worker; wymaga Context z zaznaczeniem):** + +```csharp +// Tylko w warstwie UI/Czynności — Context dostarcza zaznaczonych pracowników. +var worker = new KalendarzPlanuKopia.KopiujWorker(context) +{ + Okres = new FromTo(new Date(2026, 6, 1), new Date(2026, 6, 30)), + Pracownicy = context.Get() +}; +worker.Kopiuj(); // wewnątrz: Session.Logout + Commit +``` + +**Pułapki:** +- **Kopiowanie grupy nie ma „czystego" API bezkontekstowego** — `KopiujWorker` i + `AktualizujKalendarzWorker.Params` dziedziczą po `ContextBase` i wymagają `Context` (zaznaczenie z + listy UI). Dla kodu serwerowego/testów używaj **publicznej statycznej** `KalendarzPlanuKopia.Kopiuj(pracownik, okres)` + w pętli po pracownikach — to ona realizuje właściwą logikę (worker w `KopiujInt` woła ją per pracownik). +- `KopiujWorker.Kopiuj()` jest gardzony licencją (BI/BI_PL/PL) i `IsVisibleKopiuj` (m.in. blokada na + mobile) — to logika UI, nie wywołuj jej z kodu biznesowego. +- Kopia planu/pracy trafia do **osobnych** kolekcji `pracownik.DniPlanuKopia`/`pracownik.DniPracyKopia` + (`DateSubTable`), powiązanych z `KalendarzPlanuKopia`/`KalendarzPracyKopia` — to bufor kopii, odrębny + od właściwego `DniPlanu`/`DniPracy`. +- `okres` jest normalizowany przez setter workera do pełnych miesięcy (otwarty `From`/`To` → + pierwszy/ostatni dzień miesiąca); przy statycznym `Kopiuj` podawaj zamknięty `FromTo`. +- Operacja seryjna na grupie pracowników = długa transakcja → dziel na paczki, trzymaj transakcje + krótkie (safe-code §13.1). + +### E3 — Aktualizacja kalendarza pracownika (operacja seryjna „Zaktualizuj kalendarz pracownika") + +**Cel:** seryjnie zmienić kalendarz roboczy zaznaczonych pracowników (zmiana kalendarza +docelowego, przeliczenie planu na nowy kalendarz od wskazanej daty) — operacja z menu +„Czynności" na liście pracowników. + +**Publiczny kontrakt — worker `Soneta.Kadry.AktualizujKalendarzWorker`:** + +| Element | Sygnatura / typ | Uwaga | +|---|---|---| +| Konstruktor | `new AktualizujKalendarzWorker()` | bezparametrowy; worker UI | +| Pracownicy (wejście) | `Pracownicy : Pracownik[]` | **set-only**; karmione z `Context` (zaznaczenie listy) | +| Parametry | `Pars : Params` | **set-only**; `Params` to `ContextBase`, ctor `(Context context)` | +| Wykonanie | `void Aktualizuj()` | właściwa operacja seryjna (Logout + Commit wewnątrz) | + +**`Soneta.Kadry.AktualizujKalendarzWorker.Params` (`: ContextBase`, ctor `(Context)`):** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Data` | `Soneta.Types.Date` | data, od której obowiązuje nowy kalendarz | +| `TylkoOstatni` | `bool` | aktualizuj tylko ostatni (bieżący) zapis historyczny | +| `PowodAktualizacji` | `string` | opis powodu (do dokumentu aktualizacji) | +| `Kalendarze` | `KalendarzBase[]` | kalendarze źródłowe objęte zmianą; lista przez `GetListKalendarze()` | +| `Docelowy` | `Soneta.Kalend.Kalendarz` | kalendarz docelowy; lista przez `GetListDocelowy()` | +| `Zmiana` | `bool` | flaga: czy zmienić kalendarz (a nie tylko przeliczyć) | +| `Interpretacja` | `Soneta.Kadry.InterpretacjaKalendarza` | `WgPlanu` / `WgObecnosci` / `WgZestawien`; `IsReadOnlyInterpretacja()` | + +**Snippet (warstwa UI/Czynności — wymaga `Context` z zaznaczeniem):** + +```csharp +// Tylko w warstwie UI: Context dostarcza zaznaczonych pracowników. +var worker = new AktualizujKalendarzWorker +{ + Pracownicy = context.Get(), + Pars = new AktualizujKalendarzWorker.Params(context) + { + Data = new Date(2026, 7, 1), + Docelowy = session.GetKalend().Kalendarze.WgKodu["PODSTAWOWY"], + Zmiana = true, + Interpretacja = InterpretacjaKalendarza.WgPlanu, + PowodAktualizacji = "Zmiana systemu czasu pracy" + } +}; +worker.Aktualizuj(); // wewnątrz: Session.Logout + Commit +``` + +**Pułapki:** +- `Params` dziedziczy po `ContextBase` (ctor `(Context)`) — **nie da się go zbudować bez `Context`**. + Dlatego E3 nie ma „czystego" API bezkontekstowego; to operacja UI/serwerowa z zaznaczeniem. +- `Pracownicy` i `Pars` są **set-only** — nie odczytasz ich z powrotem; ustaw przed `Aktualizuj()`. +- Operacja seryjna = długa transakcja na wielu pracownikach → w realnym użyciu dziel na paczki + (safe-code §13.1). Sam worker zarządza transakcją wewnętrznie. +- Zmiana kalendarza jest **historyczna** (operuje na zapisach `Etat`) — `TylkoOstatni`/`Data` + decydują, których zapisów historycznych dotyczy. + +--- + +### E4 — Uzgodnienie doby pracowniczej (model doby; godziny rozpoczęcia doby) + +**Cel:** przesunąć granicę doby pracowniczej dla dnia ewidencji — gdy zmiana zaczyna się w jednej +dobie kalendarzowej, a kończy w następnej (nocna), uzgodnienie „przenosi" początek/koniec pracy do +właściwej doby pracowniczej. Operacja na pojedynczym dniu (`DzienPracy`) lub seryjnie na grupie. + +**Model doby (publiczny kontrakt):** + +| Element | Lokalizacja | Typ | Uwaga | +|---|---|---|---| +| Początek doby w niedziele/święta | `pracownik.Last.Etat.ConfigPoczątekDobyNiedzieledIŚwięta` | `Soneta.Types.Time` | **read-only** (konfiguracyjne); godzina startu doby | +| Norma dobowa | `pracownik.Last.Etat.NormaDobowa` | `Soneta.Types.Time` | bazodanowe; norma czasu doby | +| Norma dobowa kalendarza | `pracownik.Last.Etat.Kalendarz.NormaDobowa` | `Soneta.Types.Time` | poziom kalendarza | +| Interpretacja kalendarza | `pracownik.Last.Etat.InterpretacjaKalendarza` | `Soneta.Kadry.InterpretacjaKalendarza` | `WgPlanu`/`WgObecnosci`/`WgZestawien` — jak interpretować dobę | + +> **Uwaga:** `Etat` leży na bieżącym **zapisie historycznym** (`pracownik.Last.Etat : Soneta.Kadry.Etat`, +> gdzie `Last : PracHistoria`) — nie ma property `pracownik.Etat` bezpośrednio na roocie pracownika. +| Godziny pracy dnia | `DzienPracy.Praca` | `Soneta.Kalend.CzasPracy` | `OdGodziny`/`DoGodziny`/`Czas` — granice realizacji w dobie | + +**Worker pojedynczego dnia — `Soneta.Kalend.DzienPracy.UzgodnijDobePracowniczaWorker`:** + +| Element | Sygnatura | Uwaga | +|---|---|---| +| Konstruktor | `new DzienPracy.UzgodnijDobePracowniczaWorker()` | | +| Dzień (wejście) | `Dzień : DzienPracy` | **set-only** | +| Warunek dostępności | `static bool IsEnabledUzgodnijDobePracownicza(DzienPracy dzień)` | czy operacja ma sens dla dnia | +| Uzgodnienie | `object UzgodnijDobePracownicza()` | przelicza dobę | +| Przeniesienie początku | `DzienPracy PrzenieśPoczątek()` | przenosi początek pracy do poprz. doby | +| Przeniesienie końca | `DzienPracy PrzenieśKoniec()` | przenosi koniec pracy do nast. doby | +| Dokument aktualizacji | `DokumentAktualizacjiKalendarza : IDokumentAktualizacjiKalendarza`, `DataAktualizacji : System.DateTime` | kontekst historii | + +**Worker seryjny (grupa) — `Soneta.Kadry.UzgodnijDobePracowniczaPracownikowWorker`:** + +| Element | Sygnatura / typ | Uwaga | +|---|---|---| +| Konstruktor | `new UzgodnijDobePracowniczaPracownikowWorker()` | | +| Pracownicy | `Pracownicy : Pracownik[]` | **set-only**; z `Context` | +| Parametry | `Pars : Params` (`ContextBase`, ctor `(Context)`); pole `Okres : FromTo` | **set-only** | +| Wykonanie | `UzgodnijDobePracowniczaResult UzgodnijDobePracownicza()` | zwraca wynik | + +**Snippet (pojedynczy dzień):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var dzien = pracownik.DniPracy[new Date(2026, 6, 1)]; // DzienPracy lub null +if (dzien is not null && DzienPracy.UzgodnijDobePracowniczaWorker.IsEnabledUzgodnijDobePracownicza(dzien)) +{ + using (var t = session.Logout(editMode: true)) + { + var worker = new DzienPracy.UzgodnijDobePracowniczaWorker { Dzień = dzien }; + worker.UzgodnijDobePracownicza(); + t.Commit(); + } + session.Save(); +} +``` + +**Pułapki:** +- Godzina rozpoczęcia doby to atrybut **konfiguracyjny `Etat`** (`ConfigPoczątekDobyNiedzieledIŚwięta`, + read-only) i normy `Etat.NormaDobowa`/`Etat.Kalendarz.NormaDobowa` — nie ma osobnego, edytowalnego + pola „początek doby" na pojedynczym `DzienPracy`. +- `Dzień` workera pojedynczego jest **set-only**; `Pracownicy`/`Pars` workera grupowego również. +- Worker grupowy `Params` to `ContextBase` (ctor `(Context)`) — **wymaga `Context`** (zaznaczenie UI), + brak czystego API bezkontekstowego. +- Uzgodnienie modyfikuje `DzienPracy.Praca` (od–do) i może rozbić pracę na dwie doby — wykonuj w + transakcji (`Logout(editMode:true)` + `Commit`) i zapisz `Save()`. + +--- + +### E5 — Odczyt normy czasu pracy i czasu przepracowanego za okres (★ testowalne) + +**Cel:** dla pracownika odczytać za zadany okres (`FromTo`/`YearMonth`): normę czasu pracy +(planowaną), czas przepracowany (zrealizowany), nadgodziny, czas nocny, liczbę/normę nieobecności — +bez modyfikacji danych (czysty odczyt statystyk). + +**Punkt wejścia — `pracownik.Czasy : Soneta.Kalend.KalkulatorPracownika`:** + +| Metoda (publiczna, instancyjna) | Zwraca | Znaczenie | +|---|---|---| +| `Norma(FromTo okres, params Item[] condition)` | `CzasDni` | norma (planowana) czasu pracy za okres | +| `Norma(FromTo okres, DefinicjaStrefy def, params Item[] condition)` | `CzasDni` | norma w obrębie strefy | +| `NormaKodeksowa(YearMonth miesiąc)` | `CzasDni` | norma kodeksowa miesiąca (pełny etat) | +| `NormaKodeksowaWym(Fraction wymiar, Time normaDobowa, YearMonth miesiąc)` | `CzasDni` | norma kodeksowa wg wymiaru etatu | +| `Praca(FromTo okres, params Item[] condition)` | `CzasDni` | czas **przepracowany** (zrealizowany) za okres | +| `Praca(FromTo okres, DefinicjaStrefy def, params Item[] condition)` | `CzasDni` | przepracowany w obrębie strefy | +| `PracaRozliczana(FromTo okres, params Item[] condition)` | `CzasDni` | czas pracy rozliczany (do nadgodzin) | +| `PracaZatr(FromTo okres, bool usprPłatne)` | `CzasDni` | praca w okresie zatrudnienia | +| `Nadgodziny(YearMonth okres)` / `Nadgodziny(FromTo okres)` | `ZestawienieNadgodzin` | nadgodziny | +| `NadgodzinyDobaOkres(FromTo okres)` | `ZestawienieNadgodzin` | nadgodziny dobowe/okresowe | +| `Nocne(YearMonth\|FromTo okres)` | `Time` | czas nocny | +| `NormaNie(YearMonth\|FromTo okres, params Item[] condition)` | `CzasDni` | norma nieobecności | +| `DniNie(YearMonth\|FromTo okres, params Item[] condition)` | `int` | liczba dni nieobecności | +| `Nieobecność(Date data[, bool clip])` | `INieobecnosc` | nieobecność w danym dniu | + +**`Soneta.Kalend.CzasDni` (typ wyniku):** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Czas` | `Soneta.Types.Time` | sumaryczny czas (read-only) | +| `Dni` | `int` | liczba dni (read-only) | +| `CzasDni.Empty`, `CzasDni.Invalid` | `CzasDni` | wartości specjalne; operatory `+`/`-`/`==` | + +**`Soneta.Kalend.ZestawienieNadgodzin` (struct):** `N50`, `N100`, `NSW`, `N100Doba`, `N100Okres`, +`Razem` — wszystkie `Time` (read-only); `ZestawienieNadgodzin.Zero`. + +**Snippet (czysty odczyt):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var kalk = pracownik.Czasy; // KalkulatorPracownika +var okres = new FromTo(new Date(2026, 6, 1), new Date(2026, 6, 30)); + +CzasDni norma = kalk.Norma(okres); // norma planowana +CzasDni przepracowano = kalk.Praca(okres); // czas zrealizowany +ZestawienieNadgodzin nadg = kalk.Nadgodziny(new YearMonth(2026, 6)); +Time nocne = kalk.Nocne(okres); + +Time normaCzas = norma.Czas; int normaDni = norma.Dni; +Time pracaCzas = przepracowano.Czas; Time nadgRazem = nadg.Razem; +``` + +**Pułapki:** +- `KalkulatorPracownika` **nie jest `Row`** — to obiekt liczący (zwykły `object`). Nie zapisuje się, + nie wymaga transakcji; to czysty odczyt. Pobieraj go zawsze przez `pracownik.Czasy` (ma kontekst + pracownika), nie twórz ręcznie ctorem chyba że masz `Pracownik` + ewentualny `Log`. +- Parametr `condition` to **serwerowy filtr** (`Item[]`, RowCondition) — można zawęzić np. do strefy; + zwykle pusty. +- `Norma` = plan, `Praca` = realizacja; nie myl `Praca(okres)` (statystyka) z `DzienPracy` (rekord dnia). +- Wynik `CzasDni.Invalid` sygnalizuje brak danych/błąd okresu — sprawdzaj zanim policzysz różnice. + +--- + +## F. RCP — rejestracja czasu pracy + +> **Dwie tabele.** Zarejestrowany (surowy) czas pracy z czytników RCP leży w +> `pracownik.DniRCP : DateSubTable` (tabela `DniRCP`). Pojedyncze zdarzenia +> wejścia/wyjścia (`Soneta.Kalend.WejscieWyjscie`, tabela `WejsciaWyjscia`) są **childem `DzienPracy`** +> (pole `WejscieWyjscie.Dzien : DzienPracy`, kolekcja `dzienPracy.WeWy`), a `DzienPracy` to +> ewidencja w `pracownik.DniPracy`. `DzienRCP` jest stanem zweryfikowanym RCP (z polem +> `StanRCP : StanWeryfikacjiRCP`), powstaje z importu/przeliczenia. + +### F1 — Rejestracja czasu pracy pracownika (★) + +**Cel:** odczytać zarejestrowany/zewidencjonowany czas pracy pracownika za dzień oraz (gdy trzeba) +utworzyć dzień ewidencji. + +**Pola i typy:** + +| Element | Lokalizacja | Typ | Uwaga | +|---|---|---|---| +| Ewidencja (kolekcja) | `pracownik.DniPracy` | `DateSubTable` | indeksator `[Date]` (get); element ctorem | +| Dzień ewidencji | `pracownik.DniPracy[data]` | `Soneta.Kalend.DzienPracy` | `null` przy braku | +| RCP zweryfikowane (kolekcja) | `pracownik.DniRCP` | `DateSubTable` | analogicznie | +| Dzień RCP | `pracownik.DniRCP[data]` | `Soneta.Kalend.DzienRCP` | `null` przy braku | +| Przepracowany czas (subrow) | `DzienPracy.Praca` / `DzienRCP.Praca` | `Soneta.Kalend.CzasPracy` | `Praca.OdGodziny`, `Praca.DoGodziny`, `Praca.Czas : Time` | +| Czas/Od (odczyt) | `Dzien*.Czas`, `Dzien*.OdGodziny` | `Soneta.Types.Time` | kalkulowane | +| Stan weryfikacji RCP | `DzienRCP.StanRCP` | `Soneta.Kalend.StanWeryfikacjiRCP` | zapisywalne | +| Flaga importu RCP | `Dzien*.RcpOK` | `bool` | zapisywalne; stan rekordu po imporcie | +| Zdarzenia we/wy dnia | `DzienPracy.WeWy` | `LpSubTable` | patrz F2 | +| Uwagi / błędy (RCP) | `DzienRCP.Uwagi`, `DzienRCP.Bledy` | `Soneta.Business.MemoText` | zapisywalne | + +**Snippet:** + +```csharp +var kalend = session.GetKadry().Session.GetKalend(); +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var data = new Date(2026, 6, 1); + +// --- Odczyt zaewidencjonowanego czasu (ewidencja) --- +var dzienPracy = pracownik.DniPracy[data]; // typowane: DzienPracy lub null +if (dzienPracy is not null) +{ + Time przepracowano = dzienPracy.Praca.Czas; // suma czasu pracy dnia + Time od = dzienPracy.Praca.OdGodziny; + Time @do = dzienPracy.Praca.DoGodziny; +} + +// --- Odczyt stanu RCP (zweryfikowany rejestr) --- +var dzienRcp = pracownik.DniRCP[data]; // DzienRCP lub null +if (dzienRcp is not null) +{ + Time czasRcp = dzienRcp.Praca.Czas; + StanWeryfikacjiRCP stan = dzienRcp.StanRCP; +} + +// --- Utworzenie dnia ewidencji (gdy potrzebny ręczny wpis) --- +using (var t = session.Logout(editMode: true)) +{ + var dp = pracownik.DniPracy[data]; + if (dp is null) + { + dp = session.AddRow(new DzienPracy(pracownik, data)); // ctor (Pracownik, Date) + kalend.DniPracy.AddRow(dp); // alternatywnie przez Module.DniPracy + } + dp.Praca.OdGodziny = new Time(8, 0); + dp.Praca.DoGodziny = new Time(16, 0); + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- `DniPracy`/`DniRCP` są **typowane** (`DateSubTable` / ``) — indeksator + `[Date]` zwraca od razu właściwy typ lub `null`. Nie iteruj całej kolekcji, by „znaleźć" dzień — + użyj indeksatora po dacie albo `[FromTo]` dla zakresu. +- Czas pracy ustawiaj na subrowie `Praca` (od–do); `Dzien*.Czas`/`Dzien*.OdGodziny` na rootcie dnia są + kalkulowane (read-only). +- `DzienRCP` to **wynik weryfikacji** importu RCP (z czytników) — w normalnym przepływie nie tworzysz + go ręcznie, lecz odczytujesz po imporcie/przeliczeniu. `DzienPracy` (ewidencja) to właściwe miejsce + na ręczny wpis. +- Świeży `DzienPracy` z `new DzienPracy(pracownik, data)` trzeba dodać do tabeli + (`Module.DniPracy.AddRow(...)` lub `session.AddRow(...)`) — sam ctor go nie rejestruje. + +### F2 — Rejestracja wejścia/wyjścia (RCP) (★) + +**Cel:** dodać zdarzenie wejścia/wyjścia do dnia oraz odczytać listę zdarzeń RCP danego dnia. + +**Pola i typy (`Soneta.Kalend.WejscieWyjscie`, tabela `WejsciaWyjscia`):** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Dzien` | `Soneta.Kalend.DzienPracy` | właściciel (guided-parent); ustawiany przez ctor `(DzienPracy)` | +| `Godzina` | `Soneta.Types.Time` | godzina zdarzenia (zapisywalne) | +| `Typ` | `Soneta.Kalend.TypWejsciaWyjscia` | enum: `Niezdefiniowany`, `Wejscie`, `Wyjscie`, `WejscieSluzbowe`, `WyjscieSluzbowe`, `WejsciePrywatne`, `WyjsciePrywatne` | +| `Operacja` | `int` | kod operacji urządzenia (zapisywalne) | +| `Lp` | `int` | liczba porządkowa zdarzeń w dniu (bazodanowe) | +| `DefinicjaZdarzenia` | `Soneta.Kalend.DefinicjaZdarzeniaRCP` | opcjonalna definicja zdarzenia ze słownika `DefZdarzenRCP` | +| Kolekcja zdarzeń dnia | `DzienPracy.WeWy : LpSubTable` | uporządkowana po `Lp` | + +**Snippet:** + +```csharp +var kalend = session.GetKadry().Session.GetKalend(); +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var data = new Date(2026, 6, 1); + +using (var t = session.Logout(editMode: true)) +{ + // Upewnij się, że istnieje dzień ewidencji (właściciel zdarzeń): + var dp = pracownik.DniPracy[data]; + if (dp is null) + { + dp = session.AddRow(new DzienPracy(pracownik, data)); + kalend.DniPracy.AddRow(dp); + } + + // Wejście 8:00 + var we = new WejscieWyjscie(dp); // ctor wiąże zdarzenie z dniem + kalend.WejsciaWyjscia.AddRow(we); + we.Godzina = new Time(8, 0); + we.Typ = TypWejsciaWyjscia.Wejscie; + + // Wyjście 16:00 + var wy = new WejscieWyjscie(dp); + kalend.WejsciaWyjscia.AddRow(wy); + wy.Godzina = new Time(16, 0); + wy.Typ = TypWejsciaWyjscia.Wyjscie; + + t.Commit(); +} +session.Save(); + +// --- Odczyt zdarzeń dnia --- +var dzien = pracownik.DniPracy[data]; +if (dzien is not null) +{ + foreach (WejscieWyjscie wewy in dzien.WeWy) // posortowane po Lp + { + // wewy.Godzina, wewy.Typ, wewy.Operacja + } +} +``` + +**Pułapki:** +- `WejscieWyjscie` jest childem **`DzienPracy`**, nie `DzienRCP` — najpierw potrzebujesz dnia + ewidencji (`pracownik.DniPracy[data]`); zdarzenia wiążesz ctorem `new WejscieWyjscie(dzienPracy)` + i dodajesz do tabeli `kalend.WejsciaWyjscia.AddRow(...)`. +- `Typ` to enum `TypWejsciaWyjscia` (`Wejscie`/`Wyjscie`/…), nie string ani `int`. Para + wejście+wyjście jest podstawą wyliczenia czasu dnia z surowych zdarzeń. +- `DefinicjaZdarzenia` jest **opcjonalna** — przy ręcznym wpisie wystarczą `Godzina` + `Typ`. Jeśli + używasz definicji, pobierz wpis ze słownika konfiguracyjnego `kalend.DefZdarzenRCP` (nie twórz w locie). +- `WeWy` to `LpSubTable` — kolejność zdarzeń wynika z `Lp` (nadawane automatycznie); nie ustawiaj `Lp` + ręcznie. Do usunięcia wszystkich zdarzeń dnia (przy ponownym imporcie) służy kasowanie elementów kolekcji. +- Surowe zdarzenia są przeliczane na czas pracy/RCP przez kalkulator i import — samo dodanie + wejść/wyjść nie aktualizuje automatycznie `DzienRCP` (to robi przeliczenie/import RCP). + +### F3 — Import danych z RCP (bezpośredni i przez tabelę pośrednią) + +**Cel:** wczytać surowe odbicia z czytników RCP i przeliczyć je na ewidencję/zweryfikowany RCP. +**UWAGA: operacja plikowa/sieciowa — opis modelu; samego importu z pliku/urządzenia NIE testujemy.** + +**Model danych:** + +| Element | Lokalizacja | Typ | Uwaga | +|---|---|---|---| +| Tabela pośrednia (surowe odbicia) | `DzienPracy.WeWy` | `LpSubTable` | zdarzenia we/wy (godzina, typ) — patrz F2 | +| Zarejestrowany RCP (zweryfikowany) | `pracownik.DniRCP[data]` | `Soneta.Kalend.DzienRCP` | wynik importu/przeliczenia | +| Ewidencja | `pracownik.DniPracy[data]` | `Soneta.Kalend.DzienPracy` | docelowa realizacja | +| Flaga importu | `DzienPracy.RcpOK` / `DzienRCP.RcpOK` | `bool` | „stan rekordu po imporcie z RCP" | +| Stan weryfikacji | `DzienRCP.StanRCP` | `StanWeryfikacjiRCP` | patrz F4 | + +**Workery przeliczające (po wczytaniu odbić — operują na obiektach sesji):** + +| Worker | Sygnatura | Rola | +|---|---|---| +| `Soneta.Kalend.ImportDniaWorker` | ctor `()`, `DzienPracy DzienPracy {get;set;}`, `void Przelicz()` | przelicza pojedynczy dzień z we/wy na czas pracy | +| `Soneta.Kalend.RCPWeryfikatorWorker` | `Dopasuj()`, `DopasujDlaZaznaczonych()`, `Dodaj()`, `Usun()` (+ `IsVisible*`), props `rw : RCPWeryfikator`, `Strefy`, `Wybrana` | dopasowanie odbić do plan/strefy (UI) | + +**Snippet (przeliczenie dnia z już wczytanych we/wy — bez pliku):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var dzien = pracownik.DniPracy[new Date(2026, 6, 1)]; // dzień z wpisanymi WeWy (F2) +if (dzien is not null) +{ + using (var t = session.Logout(editMode: true)) + { + new ImportDniaWorker { DzienPracy = dzien }.Przelicz(); // we/wy -> czas pracy + t.Commit(); + } + session.Save(); +} +``` + +**Pułapki / wykonalność:** +- **Sam import z pliku/urządzenia (czytnik, sieć, format) jest poza zakresem testu** — wymaga + zewnętrznego źródła (plik/serwis), brak czystego API w tym kontrakcie. +- **Testowalny fragment**: przygotowanie tabeli pośredniej `DzienPracy.WeWy` (ctor `WejscieWyjscie(dp)`, + patrz F2) + `ImportDniaWorker.Przelicz()` — to przelicza już-wczytane odbicia bez I/O. +- `RCPWeryfikatorWorker` jest mocno UI (metody `IsVisible*`, `Strefy`/`Wybrana`) — to dopasowanie + ręczne; nie wywoływać z kodu biznesowego. +- `DzienRCP` powstaje z importu/przeliczenia — w teście nie twórz go „z palca"; odczytuj po `Przelicz()`. + +--- + +### F4 — Weryfikacja i korekta danych RCP (★ testowalne) + +**Cel:** odczytać i skorygować zweryfikowany rekord RCP — zmienić stan weryfikacji oraz poprawić +godziny pracy / opisać błędy i uwagi. + +**Pola `Soneta.Kalend.DzienRCP` (tabela `DniRCP`, child `Pracownik`):** + +| Pole | Typ | Rodzaj | Uwaga | +|---|---|---|---| +| `Data` | `Soneta.Types.Date` | bazodanowe | data dnia (ctor) | +| `Pracownik` | `Soneta.Kadry.Pracownik` | bazodanowe, guided-parent | właściciel | +| `Praca` | `Soneta.Kalend.CzasPracy` | bazodanowe | `Praca.OdGodziny`/`Praca.DoGodziny`/`Praca.Czas : Time` (zapisywalne) | +| `Czas`, `OdGodziny` | `Soneta.Types.Time` | kalkulowane | read-only (z `Praca`) | +| `StanRCP` | `Soneta.Kalend.StanWeryfikacjiRCP` | bazodanowe | stan weryfikacji (zapisywalne) | +| `RcpOK` | `bool` | bazodanowe | stan rekordu po imporcie (zapisywalne) | +| `Uwagi` | `Soneta.Business.MemoText` | bazodanowe | uwagi do weryfikacji | +| `Bledy` | `Soneta.Business.MemoText` | bazodanowe | opis błędów | +| `Strefy` | `SubTable` | | strefy zarejestrowane | +| `StrefyOrg` | `Soneta.Business.MemoText` | bazodanowe | strefy źródłowe (org.) | + +**`Soneta.Kalend.StanWeryfikacjiRCP` (enum):** `DoWeryfikacji`, `WymagaWeryfikacji`, +`PrzekazanyDoWyjaśnienia`, `DoZatwierdzenia`, `Modyfikowany`, `Naniesiony`, `Poprawny`, `Błędny`, +`Wszystkie`. + +**Snippet (korekta godzin + zmiana stanu):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var dzienRcp = pracownik.DniRCP[new Date(2026, 6, 1)]; // DzienRCP lub null +if (dzienRcp is not null) +{ + using (var t = session.Logout(editMode: true)) + { + dzienRcp.Praca.OdGodziny = new Time(8, 0); // korekta na subrowie Praca + dzienRcp.Praca.DoGodziny = new Time(16, 0); + dzienRcp.StanRCP = StanWeryfikacjiRCP.Poprawny; // zatwierdzenie weryfikacji + dzienRcp.Uwagi = (MemoText)"Skorygowano wyjście"; + t.Commit(); + } + session.Save(); +} +``` + +**Pułapki:** +- `DniRCP` jest **typowane** (`DateSubTable`) — indeksator `[Date]` zwraca `DzienRCP`/`null`; + do zakresu użyj `[FromTo]`. Nie iteruj kolekcji w poszukiwaniu dnia. +- Godziny koryguj na **subrowie `Praca`** (`Praca.OdGodziny`/`DoGodziny`); `DzienRCP.Czas`/`OdGodziny` + na rootcie są kalkulowane (read-only). +- `StanRCP` to enum `StanWeryfikacjiRCP` — nie string. Zmiana stanu może podlegać weryfikatorom. +- W Demo `DzienRCP` istnieje tylko gdy był import/przeliczenie — test korekty zakłada istniejący dzień + (sprawdzaj `is not null`), nie twórz `DzienRCP` ręcznie. + +--- + +### F5 — Rozliczenie pracy hybrydowej / aktualizacja podzielników na podstawie pracy hybrydowej + +**Cel:** rozliczyć czas pracy hybrydowej (podział na strefy: stacjonarna / praca zdalna / zdalna +okazjonalna) i zaktualizować podzielniki (elementy rozliczenia czasu pracy / strefy dnia), na +podstawie których naliczane są składniki płacowe i koszty. + +**Model danych (publiczny kontrakt):** + +| Element | Lokalizacja | Typ | Uwaga | +|---|---|---|---| +| Strefy pracy dnia | `DzienPracy.Strefy` | `SubTable` | podział dnia na strefy | +| Strefa pracy | `Soneta.Kalend.StrefaPracy` (ctor `(DzienPracy dzien)`) | — | `Definicja : DefinicjaStrefy`, `CzasRozliczany : Time`, `OdGodziny`/`Czas` (kalk.) | +| Definicja strefy | `Soneta.Kalend.DefinicjaStrefy` | konfiguracja | `Typ : TypStrefy`, `Wchodzi`, `Rozliczana`; stałe `Praca_Zdalna`, `PracaZdalnaOkazjonalna : Guid` | +| Dokumenty rozliczenia | `pracownik.RozliczeniaCzasuPracy` | `SubTable` | dokumenty rozliczenia czasu (podzielniki) | +| Elementy rozliczenia | `pracownik.ElementyRozliczeniaCzasuPracy` | `SubTable` | pozycje podzielnika | +| Dokument rozliczenia | `Soneta.Kalend.RozliczenieCzasuPracy` (ctor `(Pracownik, DefinicjaRozliczeniaCzasuPracy)`) | root | `Data`, `Seria`, `Stan : StanyRozliczeniaCzasuPracy` | +| Pozycja rozliczenia | `Soneta.Kalend.ElementRozliczeniaCzasuPracy` (ctor `(RozliczenieCzasuPracy dokument)`) | child | `Definicja : DefinicjaStrefy`, `Data`, `OdGodziny`, `Czas`, `CzasPozostały`/`CzasDostępny`/`Zrealizowane` (kalk.) | + +**`Soneta.Kalend.TypStrefy` (enum):** `NieWplywa`, `Zwieksza`, `Zmniejsza`. + +**Workery (UI/extendery — praca zdalna/hybrydowa):** +- `Soneta.Kalend.StrefaPracy.PracaZdalnaWorker` (`Strefa : StrefaPracy`) — oznaczenie strefy jako + praca zdalna. +- `Soneta.Kadry.PracaZdalna.DzienStrefaExtWorker`, `ElementRozliczeniaCzasuPracyExtWorker`, + `DzienZestawienieExtender` — extendery zestawień/dni dla pracy zdalnej. + +**Snippet (odczyt rozkładu na strefy + dokument rozliczenia):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +// Odczyt podziału dnia na strefy (stacjonarna / zdalna): +var dzien = pracownik.DniPracy[new Date(2026, 6, 1)]; +if (dzien is not null) +{ + foreach (StrefaPracy s in dzien.Strefy) + { + DefinicjaStrefy def = s.Definicja; // strefa (np. praca zdalna) + Time rozliczany = s.CzasRozliczany; // czas rozliczany w strefie + } +} + +// Pozycje podzielnika (elementy rozliczenia czasu pracy): +foreach (ElementRozliczeniaCzasuPracy el in pracownik.ElementyRozliczeniaCzasuPracy) +{ + DefinicjaStrefy def = el.Definicja; + Time czas = el.Czas; +} +``` + +**Pułapki / wykonalność:** +- Rozkład pracy hybrydowej to **strefy** (`DzienPracy.Strefy` / `DefinicjaStrefy` z flagą zdalna) + + dokument `RozliczenieCzasuPracy` z pozycjami `ElementRozliczeniaCzasuPracy` (podzielniki). +- `RozliczenieCzasuPracy` to **root** (ctor `(Pracownik, DefinicjaRozliczeniaCzasuPracy)`) — utworzenie + wymaga istniejącej `DefinicjaRozliczeniaCzasuPracy` z konfiguracji; pozycje ctorem + `new ElementRozliczeniaCzasuPracy(dokument)`. Czysty odczyt jest bezpieczny i bez transakcji. +- Aktualizacja podzielników na podstawie pracy hybrydowej przebiega **głównie przez extendery/UI** + (`DzienStrefaExtWorker`, `ElementRozliczeniaCzasuPracyExtWorker`, `PracaZdalnaWorker`) zależne od + `Context`/wniosków e-pracownika — brak prostego, czystego API operacyjnego. +- Wymaga skonfigurowanych `DefinicjaStrefy` (Praca_Zdalna / PracaZdalnaOkazjonalna) — w Demo strefy + mogą nie być włączone, co czyni budowę rozliczenia kruchą do testu (raczej odczyt niż zapis). + +## G. Umowy cywilnoprawne + +### G1 — Dodawanie umów cywilnoprawnych (zlecenie, o dzieło) (★) + +**Cel:** utworzyć dla pracownika umowę cywilnoprawną (zlecenie / o dzieło / ryczałtowa) z kompletem +danych pozwalającym na `Session.Save()`: definicja elementu płacowego (rodzaj umowy), okres, wartość, +sposób rozliczenia i typ wartości (brutto/netto). + +**Mechanizm (kluczowy):** `Soneta.Kadry.Umowa` to **root historyczny** (tabela `Umowy`), child +pracownika. Dodanie umowy do tabeli (`Module.Umowy.AddRow(umowa)`) w `OnAdded` **automatycznie tworzy +pierwszy zapis** `Soneta.Kadry.UmowaHistoria` (tabela `UmowaHistorie`) oraz domyślną definicję +elementu, okres, datę i numerację. Dlatego **nie tworzymy `UmowaHistoria` ręcznie** — bezpośrednio po +`AddRow` istnieje już `umowa.Last` (pierwszy zapis), na którym ustawiamy **wartość umowy**. + +> **Gdzie co siedzi.** Dane „nagłówkowe" umowy (definicja elementu, okres, sposób rozliczenia, typ +> wartości) są na **roocie** `Umowa`. **Kwota/wartość umowy** jest **historyczna** i siedzi na +> `UmowaHistoria.Wartosc` — ustawiasz ją przez `umowa.Last.Wartosc`. Property `umowa.Brutto`/ +> `umowa.Wartosc` na roocie oraz `UmowaHistoria.Brutto` są **wyliczane** (read-only). + +**Warianty (rodzaj umowy = `DefinicjaElementu`):** + +| Rodzaj umowy | Jak wskazać definicję | +|---|---| +| Zlecenie | `DefElementow[DefinicjaElementu.UmowaZlecenie]` (to też wartość domyślna konfiguracji) | +| O dzieło (20% KUP) | `DefElementow[DefinicjaElementu.Umowa20]` | +| Ryczałtowa | `DefElementow[DefinicjaElementu.UmowaRyczałtowa]` | +| Inna (po kodzie/nazwie) | `DefElementow[""]` (indeksator string = wg kodu) — pod warunkiem że jej `RodzajZrodla == RodzajŹródłaWypłaty.Umowa` | + +**Pola i typy:** + +| Pole | Gdzie | Typ | Uwaga | +|---|---|---|---| +| `Element` | `Umowa` (root) | `Soneta.Place.DefinicjaElementu` | definicja elementu = rodzaj umowy; akceptowana tylko gdy `RodzajZrodla == RodzajŹródłaWypłaty.Umowa` | +| `Data` | `Umowa` (root) | `Soneta.Types.Date` | data zawarcia/dokumentu | +| `Okres` | `Umowa` (root) | `Soneta.Types.FromTo` | okres obowiązywania umowy | +| `Tytul` | `Umowa` (root) | `string` | tytuł/temat umowy | +| `RodzajRozliczenia` | `Umowa` (root) | `Soneta.Kadry.RodzajeRozliczeniaUmowy` | `KwotaDoWypłaty` / `StawkaZaOkres` / `StawkaZaGodzinę` | +| `TypWartosci` | `Umowa` (root) | `Soneta.Kadry.TypWartosciUmowy` | `Brutto` / `Netto` | +| `Wydzial` | `Umowa` (root) | `Soneta.Kadry.Wydzial` | jednostka organizacyjna — **wymagana** (weryfikator przy `Save()`); ustaw `kadry.Wydzialy.Firma` | +| `Wartosc` | `UmowaHistoria` (`Last`) | `Soneta.Types.Currency` | **kwota/wartość umowy** — ustawiana na zapisie historycznym | + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); +var place = session.GetPlace(); +var pracownik = kadry.Pracownicy.WgKodu["006"]; + +using (var t = session.Logout(editMode: true)) +{ + // 1) Utworzenie umowy + dodanie do tabeli; w OnAdded powstaje pierwszy UmowaHistoria. + var umowa = session.AddRow(new Umowa(pracownik)); + + // 2) Definicja elementu = rodzaj umowy (tu: zlecenie). Indeksator Guid + stała publiczna. + umowa.Element = place.DefElementow[DefinicjaElementu.UmowaZlecenie]; + + // 3) Dane nagłówkowe na roocie: + umowa.Data = new Date(2026, 1, 1); + umowa.Okres = new FromTo(new Date(2026, 1, 1), new Date(2026, 12, 31)); + umowa.Tytul = "Umowa zlecenie - obsługa projektu"; + umowa.RodzajRozliczenia = RodzajeRozliczeniaUmowy.KwotaDoWypłaty; + umowa.TypWartosci = TypWartosciUmowy.Brutto; + umowa.Wydzial = kadry.Wydzialy.Firma; // wymagane przy Save() + + // 4) KWOTA umowy — na zapisie historycznym Last (UmowaHistoria.Wartosc): + umowa.Last.Wartosc = new Currency(5000m); + + t.Commit(); // Commit() w kodzie biznesowym; CommitUI() w workerze/UI +} +session.Save(); // tu wykrywane konflikty/duplikaty +``` + +**Pułapki:** +- **Nie** twórz ręcznie pierwszego `UmowaHistoria` — robi to `OnAdded` przy `AddRow`. Ręczny nowy + zapis dotyczy dopiero *zmiany/aneksu* „od daty" (G2). +- **Kwotę ustawiaj na `umowa.Last.Wartosc`**, nie na roocie — `umowa.Brutto`/`umowa.Wartosc` oraz + `Last.Brutto` są wyliczane (read-only). `Wartosc` to `Soneta.Types.Currency`, nie `decimal` + (safe-code §10.1). +- `Element` przyjmie tylko definicję o `RodzajZrodla == RodzajŹródłaWypłaty.Umowa`. Definicje + „etatowe" (np. `EtatMies`) zostaną zignorowane (umowa dostanie domyślną definicję z konfiguracji). +- Jeśli `Element` **nie** zostanie ustawiony, umowa przyjmuje + `Module.Config.Ogólne.DomyślnaDefinicjaUmowy` (domyślnie = `UmowaZlecenie`). +- Dodanie umowy do pracownika **w archiwum** rzuca wyjątek (`WArchiwumException`) z `OnAdded`. +- `RodzajRozliczenia`/`TypWartosci` bywają w UI tylko-do-odczytu zależnie od definicji elementu — + w kodzie biznesowym ustawiasz je wprost (nie używaj `IsReadOnlyXxx`, safe-code §7.1). +- Całość w transakcji (`session.Logout(editMode: true)`); brak `Commit()` = rollback przy `Dispose()`. + +### G2 — Zmiana/aneks umowy (★) + +**Cel:** zmienić warunki istniejącej umowy. Trzy rozłączne przypadki: **(a) korekta** danych +nagłówkowych w bieżącym okresie; **(b) aneks „od daty"** — nowy zapis historyczny `UmowaHistoria` +obowiązujący od wskazanego dnia (analogicznie do `PracHistoria`, sekcja A14); **(c) seryjna +aktualizacja stawki** workerem. + +**Mechanizm `HistorySubTable`** (`umowa.Historia`): + +| Operacja | API | Efekt | +|---|---|---| +| Odczyt zapisu na dzień | `umowa[date]` (== `(UmowaHistoria)Historia[date]`) | zapis, którego `Aktualnosc` zawiera `date` | +| Ostatni (bieżący) zapis | `umowa.Last` (== `Historia.GetPrev()`) | najświeższy zapis | +| Pierwszy zapis | `umowa.Historia.GetFirst()` | najstarszy zapis | +| **Nowy zapis „od daty"** | `(UmowaHistoria)umowa.Historia.Update(date)` | **klonuje** zapis aktualny na `date`, skraca stary do `date-1`, zwraca **nowy** klon (okres od `date`); klon trzeba dodać do tabeli | +| Okres obowiązywania | `UmowaHistoria.Aktualnosc: FromTo` | „od–do" zapisu (zarządzany przez historię) | + +**(a) Korekta danych nagłówkowych umowy (bez nowego okresu):** + +```csharp +var umowa = pracownik.Umowy.First(); // lub wyszukanie po polu/numerze + +using (var t = session.Logout(editMode: true)) +{ + umowa.Tytul = "Umowa zlecenie - aneks zakresu prac"; + umowa.Okres = new FromTo(umowa.Okres.From, new Date(2027, 6, 30)); // przedłużenie + t.Commit(); +} +session.Save(); +``` + +**(b) Aneks „od daty" — zmiana wartości umowy nowym zapisem historycznym:** + +```csharp +var odDnia = new Date(2026, 7, 1); + +using (var t = session.Logout(editMode: true)) +{ + // 1) Update klonuje zapis aktualny na `odDnia` i zwraca nowy klon (okres od `odDnia`); + // stary zapis zostaje skrócony do dnia poprzedniego. + var nowy = (UmowaHistoria)umowa.Historia.Update(odDnia); + + // 2) Klon trzeba dodać do tabeli zapisów historii umowy. + umowa.Module.UmowaHistorie.AddRow(nowy); + + // 3) Na nowym zapisie ustawiamy zmienioną wartość (od `odDnia`): + nowy.Wartosc = new Currency(6000m); + // Uwaga: UmowaHistoria.PowodAktualizacji jest tylko do odczytu (ustawiane wewnętrznie) — nie przypisuj. + + t.Commit(); +} +session.Save(); +``` + +**(c) Seryjna aktualizacja stawki — worker `Umowa.AktualizacjaStawkiWorker`:** + +```csharp +var worker = new Umowa.AktualizacjaStawkiWorker +{ + Umowy = new[] { umowa }, + Pars = ... // Umowa.AktualizacjaStawkiWorker.Params: Data: Date, Wartosc: Currency +}; +// uruchomienie zgodnie z konwencją workerów (patrz worker-extender.md) +``` + +> Pokrewne workery (wywoływane jak każdy worker enova): `Umowa.KopiujUmowe2Worker` +> (`Umowa Umowa` — kopiuje umowę), `Umowa.WyrejestrujUmoweWorker` (wyrejestrowanie umowy). + +**Pułapki:** +- **`Update(date)` + `UmowaHistorie.AddRow(nowy)` to nierozłączna para.** Sam `Update` tworzy + „odpięty" klon — bez `AddRow` zmiana nie zostanie zapisana. +- `Update(date)` rzuca wyjątek duplikatu, gdy na `date` już zaczyna się zapis (`Aktualnosc.From == date`) + — nie da się „aktualizować" dwa razy tego samego dnia; wtedy modyfikuj istniejący zapis (`umowa[date]`). +- **Korekta** (`umowa.Last.Wartosc = …` lub modyfikacja `umowa[date]`) zmienia dane w **całym** okresie + tego zapisu — używaj jej do poprawy błędu, nie do „zmiany od dnia"; do zmiany od dnia → wariant (b). +- `Aktualnosc` (okres zapisu) jest zarządzany przez historię — **nie ustawiaj go ręcznie**; do + skrócenia/wstawienia okresu służy `Update`. +- Wartość zawsze jako `Soneta.Types.Currency`, nie `decimal` (safe-code §10.1); daty jako + `Soneta.Types.Date`/`Date.Today` (§10.2). +- Obsłuż `RowConflictException` z `Save()` (safe-code §4); transakcje trzymaj krótkie (§13.1). + +### G3 — Operacja seryjna „Dodaj umowy" dla grupy osób (★) + +**Cel:** dodać jednakową umowę cywilnoprawną (zlecenie / o dzieło / ryczałtowa) **naraz dla wielu +zaznaczonych pracowników** — operacja seryjna z listy osób. W UI: menu *Operacje seryjne → Dodaj +umowy…*. Każdej osobie z zaznaczenia tworzona jest osobna `Umowa` z tymi samymi danymi nagłówkowymi +(definicja elementu, okres, wartość, sposób rozliczenia), analogicznie do G1. + +**Worker (publiczny kontrakt):** `Soneta.Kadry.Pracownik.DodajUmowęWorker` — worker **przypisany do +`Pracownik`** (`DataType = Pracownik`). Udostępnia akcję `DodajUmowę` w menu czynności listy +pracowników. + +| Składowa | Typ / sygnatura | Uwaga | +|---|---|---| +| Konstruktor | `DodajUmowęWorker(Session session)` | worker ma **ctor z `Session`** (nie bezparametrowy) | +| Zaznaczone osoby | `DodajUmowęWorker.Pracownicy: Pracownik[]` | `[Context]` — tablica pracowników z zaznaczenia listy | +| Parametry | `DodajUmowęWorker.Pars: DodajUmowęWorker.Params` | `[Context]` — okno parametrów operacji; `Params(Context)` | +| Akcja | `void DodajUmowę()` | tworzy umowy dla wszystkich `Pracownicy` (zwraca **`void`**, nie `object`) | + +**Parametry operacji (`DodajUmowęWorker.Params`):** + +| Pole | Typ | Odpowiednik na `Umowa` (G1) | +|---|---|---| +| `Definicja` | `Soneta.Core.DefinicjaDokumentu` | definicja dokumentu umowy (numeracja/seria) | +| `Seria` | `string` | seria numeracji | +| `Wydział` | `Soneta.Kadry.Wydzial` | `Umowa.Wydzial` (wymagany) | +| `Data` | `Soneta.Types.Date` | `Umowa.Data` (data zawarcia) | +| `Okres` | `Soneta.Types.FromTo` | `Umowa.Okres` | +| `Tytuł` | `string` | `Umowa.Tytul` | +| `Element` | `Soneta.Place.DefinicjaElementu` | `Umowa.Element` (rodzaj umowy) | +| `RodzajRozliczenia` | `Soneta.Kadry.RodzajeRozliczeniaUmowy` | `Umowa.RodzajRozliczenia` | +| `Wartość` | `Soneta.Types.Currency` | `umowa.Last.Wartosc` (kwota umowy) | +| `TypWartości` | `Soneta.Kadry.TypWartosciUmowy` | `Umowa.TypWartosci` (`Brutto`/`Netto`) | + +**Wariant A — wywołanie workera platformy (zalecane):** zainicjuj `DodajUmowęWorker`, ustaw +`Pracownicy` i `Pars`, wywołaj `DodajUmowę()` (worker uruchamia się jak każdy worker — patrz +[`worker-extender.md`](../worker-extender.md), sekcja *Programowe użycie workera*). + +```csharp +var kadry = session.GetKadry(); +var place = session.GetPlace(); + +// Grupa osób (np. z zaznaczenia listy). Tu: kilku pracowników po kodzie: +var osoby = new[] +{ + kadry.Pracownicy.WgKodu["006"], + kadry.Pracownicy.WgKodu["007"], + kadry.Pracownicy.WgKodu["008"], +}; + +var pars = new Pracownik.DodajUmowęWorker.Params(context); +pars.Element = place.DefElementow[DefinicjaElementu.UmowaZlecenie]; +pars.Okres = new FromTo(new Date(2026, 1, 1), new Date(2026, 12, 31)); +pars.Data = new Date(2026, 1, 1); +pars.Tytuł = "Umowa zlecenie - projekt grupowy"; +pars.RodzajRozliczenia = RodzajeRozliczeniaUmowy.KwotaDoWypłaty; +pars.TypWartości = TypWartosciUmowy.Brutto; +pars.Wartość = new Currency(4000m); +pars.Wydział = kadry.Wydzialy.Firma; // wymagany + +// Worker ma konstruktor z Session (nie bezparametrowy); Pracownicy/Pars przez inicjalizator: +var worker = new Pracownik.DodajUmowęWorker(session) { Pracownicy = osoby, Pars = pars }; +worker.DodajUmowę(); // void +session.Save(); +``` + +**Wariant B — pętla po pracownikach (jawne tworzenie, jak G1):** gdy nie chcesz przechodzić przez +worker — dla każdej osoby twórz `new Umowa(p)` i ustaw te same pola co w G1. To jawnie pokazuje, że +operacja seryjna = G1 powtórzone w pętli. + +```csharp +var kadry = session.GetKadry(); +var place = session.GetPlace(); +var defElementu = place.DefElementow[DefinicjaElementu.UmowaZlecenie]; +var okres = new FromTo(new Date(2026, 1, 1), new Date(2026, 12, 31)); + +using (var t = session.Logout(editMode: true)) +{ + foreach (var p in osoby) + { + var umowa = session.AddRow(new Umowa(p)); // OnAdded tworzy pierwszy UmowaHistoria + umowa.Element = defElementu; + umowa.Data = okres.From; + umowa.Okres = okres; + umowa.Tytul = "Umowa zlecenie - projekt grupowy"; + umowa.RodzajRozliczenia = RodzajeRozliczeniaUmowy.KwotaDoWypłaty; + umowa.TypWartosci = TypWartosciUmowy.Brutto; + umowa.Wydzial = kadry.Wydzialy.Firma; // wymagany przy Save() + umowa.Last.Wartosc = new Currency(4000m); // kwota na zapisie historycznym + } + t.Commit(); // Commit() w kodzie biznesowym; CommitUI() w workerze/UI +} +session.Save(); +``` + +**Pułapki:** +- `Pracownik.DodajUmowęWorker` jest workerem na typie `Pracownik`, a tworzy obiekty `Umowa` — nie myl + go z workerami na `Umowa` (G2: `AktualizacjaStawkiWorker`, `KopiujUmowe2Worker`). +- W wariancie B obowiązują wszystkie pułapki G1: kwota na `umowa.Last.Wartosc` (root `Brutto`/`Wartosc` + są wyliczane), `Element` tylko o `RodzajZrodla == RodzajŹródłaWypłaty.Umowa`, `Wydzial` wymagany, + dodanie umowy pracownikowi **w archiwum** rzuca `WArchiwumException`. +- Pętlę edycyjną trzymaj krótką (safe-code §13.1); konflikty/duplikaty wykrywane w `Save()` (§4). +- Wartość zawsze jako `Soneta.Types.Currency`, daty jako `Date`/`FromTo`, nie `decimal`/`DateTime` + (safe-code §10). + +--- + +### G4 — Rachunek do umowy zlecenia (★) + +**Cel:** wystawić **rachunek do umowy zlecenia** — czyli rozliczyć (naliczyć i wypłacić) umowę +cywilnoprawną. W modelu Soneta „rachunek do umowy zlecenia" **nie jest osobnym rekordem** na `Umowa` +ani w `pracownik.Rachunki` — to **wypłata z umowy** typu `Soneta.Place.WyplataUmowa`, naliczana +mechanizmem płac (jak H2). `pracownik.Rachunki: SubTable` to +**rachunki bankowe** pracownika (numer konta), a nie rachunki do umów — nie myl tych pojęć. + +> **Gdzie to siedzi.** Każda umowa ma wstecz powiązane rozliczenia/wypłaty: +> - `Umowa.RozliczeniaWynagrodzenia: LpSubTable` — rozliczenia +> wynagrodzenia z umowy, +> - `Umowa.Elementy: SubTable` — naliczone składniki wypłat tej umowy, +> - sama wypłata to `Soneta.Place.WyplataUmowa` (konkretny typ `Wyplata`), z polem zwrotnym +> `WyplataUmowa.Umowa: Soneta.Kadry.Umowa`. +> Stan rozliczenia umowy odczytasz z `Umowa.Stan: Soneta.Kadry.StanUmowy` +> (`Niewypłacona` / `WypłaconaCzęściowo` / `WypłaconaCałkowicie` / `Anulowana`) oraz z +> `Umowa.Splacono`, `Umowa.Pozostało` (`Soneta.Types.Currency`). + +**Tworzenie rachunku (wypłaty) do umowy — wykonawca naliczania `NaliczanieSeryjne.Umowy` (jak H2):** + +| Element | Typ / sygnatura | Uwaga | +|---|---|---| +| Parametry | `new NaliczanieSeryjne.UmowaParams(context)` | `Naliczanie` na sztywno `PłatnaZDołu` (setter rzuca `NotSupportedException`) | +| Data rachunku / listy | `UmowaParams.DataWypłaty`, `.DataListy` | daty rachunku | +| Wykonawca | `new NaliczanieSeryjne.Umowy(UmowaParams) { Umowa = umowa }` | ustawienie `Umowa` ustawia też `Pracownik` z `umowa.Pracownik` | +| Uruchomienie | `Umowy.Nalicz(): NaliczanieWypłat` | tworzy `WyplataUmowa` i liczy składniki | +| Wynik | `NaliczanieWypłat.WszystkieWypłaty: IList` | elementy `Wyplata` (tu `WyplataUmowa`) | + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["006"]; + +// Umowa zlecenie pracownika (np. utworzona w G1): +Umowa umowa = pracownik.Umowy.Cast() + .First(u => u.Element == session.GetPlace().DefElementow[DefinicjaElementu.UmowaZlecenie]); + +var pars = new NaliczanieSeryjne.UmowaParams(context); +pars.DataWypłaty = new Date(2026, 2, 10); // data wystawienia rachunku +pars.DataListy = pars.DataWypłaty; + +var naliczanie = new NaliczanieSeryjne.Umowy(pars) { Umowa = umowa }; +NaliczanieWypłat wynik = naliczanie.Nalicz(); // tworzy WyplataUmowa (rachunek) + +foreach (Wyplata w in wynik.WszystkieWypłaty) +{ + // w.Typ == TypWyplaty.Umowa; w to WyplataUmowa; w.Elementy = składniki rachunku +} +session.Save(); + +// Po naliczeniu — stan rozliczenia umowy: +StanUmowy stan = umowa.Stan; // np. WypłaconaCałkowicie +Currency splacono = umowa.Splacono; // kwota rozliczona +Currency pozostalo = umowa.Pozostało; // pozostała do wypłaty +``` + +**Odczyt rachunków (wypłat) wystawionych do umowy:** + +```csharp +// Wypłaty (rachunki) tej umowy — przez wypłaty pracownika filtrowane po umowie: +foreach (WyplataUmowa w in pracownik.Wyplaty.OfType().Where(x => x.Umowa == umowa)) +{ + // w.Data, w.Elementy (WypElement.Wartosc / .Netto / .Podatki.*) +} + +// Składniki naliczone bezpośrednio z umowy: +foreach (WypElement e in umowa.Elementy) +{ + // e.Wartosc, e.Netto +} +``` + +**Pułapki:** +- „Rachunek do umowy zlecenia" = `WyplataUmowa`, a nie rekord w `pracownik.Rachunki` (to rachunki + bankowe). Tworzysz go naliczaniem (`NaliczanieSeryjne.Umowy.Nalicz()`), nie `AddRow` po wypłacie. +- **Nie ustawiaj `UmowaParams.Naliczanie`** — umowy są zawsze „płatne z dołu" (setter rzuca + `NotSupportedException`). +- Ustawienie `Umowy.Umowa` nadpisuje `Pracownik` właścicielem umowy — nie ustawiaj `Pracownik` ręcznie. +- `Nalicz()` wewnętrznie otwiera własną transakcję i zatwierdza zmiany w sesji — po nim wołasz tylko + `Session.Save()`; nie owijaj go w dodatkowy `Logout(editMode: true)`. +- `Wyplata` nie ma agregatów `Brutto`/`Netto` — sumuj składniki z `Wyplata.Elementy` (jak w H2/H4). +- Kwoty jako `Soneta.Types.Currency`, daty jako `Date` (safe-code §10). + +--- + +### G5 — Zgłoszenia ZUS zleceniobiorców (ZUA / ZZA na podstawie umowy) (★) + +**Cel:** przygotować zgłoszenie zleceniobiorcy do ZUS — **ZUA** (zgłoszenie do ubezpieczeń +społecznych + zdrowotnego) albo **ZZA** (tylko zdrowotne) — **na podstawie schematu ubezpieczeń +umowy**, oraz wyrejestrowanie (**ZWUA**) po jej zakończeniu. O tym, czy powstaje ZUA czy ZZA, decyduje +**schemat ubezpieczeń zapisu umowy**, a nie odrębne pole „rodzaj zgłoszenia". + +> **Schemat ubezpieczeń umowy — gdzie siedzi.** Ubezpieczenia umowy są **historyczne** i leżą na +> zapisie `UmowaHistoria.Ubezpieczenia: Soneta.Kadry.Ubezpieczenia` (analogicznie do +> `PracHistoria.Etat.Ubezpieczenia` z A7; ta sama struktura `Spoleczne`/`Zdrowotne`). Z roota dostępne +> przez `umowa.Last.Ubezpieczenia` (oraz `umowa.Ubezpieczenia` jako delegat). Kluczowe pola: +> - `Ubezpieczenia.Tyub4: Soneta.Kadry.TytulUbezpieczenia4` — **tytuł ubezpieczenia** (decyduje o ZUA +> vs ZZA); pobierany ze słownika `session.GetKadry().TytulyUbezpiecz4.WgKodu[int]`, +> - `Ubezpieczenia.ObowiazkoweOd: Date` — data objęcia ubezpieczeniami społecznymi obowiązkowymi, +> - `Ubezpieczenia.Emerytalne` / `Rentowe` / `Chorobowe` / `Wypadkowe : Soneta.Kadry.Spoleczne` — +> poszczególne społeczne (`Obowiazkowe`, `Dobrowolne`, `DobrowolneOd`, `Do`; `Od` read-only), +> - `Ubezpieczenia.Zdrowotne: Soneta.Kadry.Zdrowotne` — zdrowotne (`ObowiazkoweOd` zapisywalne). +> +> **Reguła ZUA vs ZZA:** zleceniobiorca podlegający ubezpieczeniom **społecznym** (emerytalne/rentowe +> obowiązkowe) → **ZUA**; podlegający **tylko zdrowotnemu** (np. uczeń/student do 26 r.ż., zbieg +> tytułów) → **ZZA**. Worker rozpoznaje to automatycznie po schemacie `UmowaHistoria.Ubezpieczenia`. +> +> **Uwaga (zweryfikowane testem):** świeży zapis ubezpieczeń umowy zlecenie ma **domyślnie** +> `Emerytalne.Obowiazkowe == true` i `Rentowe.Obowiazkowe == true` (schemat ZUA). Aby uzyskać **ZZA**, +> trzeba je **jawnie wyłączyć** (`ub.Emerytalne.Obowiazkowe = false; ub.Rentowe.Obowiazkowe = false;`) +> — samo ustawienie `Zdrowotne.ObowiazkoweOd` nie wystarcza. + +**Worker (publiczny kontrakt):** `Soneta.Deklaracje.ZUS.ZarejestrujUmowyWorker` — worker +**przypisany do `Umowa`** (`DataType = Umowa`). Operuje na zaznaczonych umowach i generuje deklaracje +zgłoszeniowe ZUS. W UI: menu czynności listy umów *Deklaracje ZUS → Przygotuj ZUA i ZZA* (oraz +wyrejestrowanie). + +| Składowa | Typ / sygnatura | Uwaga | +|---|---|---| +| Worker | `ZarejestrujUmowyWorker()` | ctor **bezparametrowy**; `Umowy: Umowa[]` jest **set-only** (ustaw przez inicjalizator) | +| Zaznaczone umowy | `ZarejestrujUmowyWorker.Umowy: Umowa[]` | `[Context]` — umowy do zgłoszenia (write-only) | +| Akcja: zgłoszenie | `object ZarejestrujUmowyWorker.Rejestracja.ZarejestrujUmowy()` | tworzy ZUA/ZZA (i ZCNA dla rodziny — `Pars.ZarejestrujRodzinę`); `Rejestracja()` ctor bezparam. | +| Akcja: wyrejestrowanie | `object ZarejestrujUmowyWorker.Wyrejestrowanie.WyrejestrujUmowy()` | tworzy ZWUA | +| Parametry zgłoszenia | `Rejestracja.Pars: ParamsZ` | **set-only**; `ParamsZ(Context)`; pola bazowe `Okres`/`DataDokumentu`/`DataWypełnienia`/`Kedu` (write-only) + własne `ZarejestrujRodzinę: bool` | +| Parametry wyrejestrowania | `Wyrejestrowanie.Pars: ParamsW` | **set-only**; `ParamsW(Context)`; `Okres`/`DataDokumentu`/`DataWypełnienia`/`Kedu` + `WyrejestrujRodzinę: bool` | + +**Wspólny kontrakt bazowy `ZarejestrujBaseWorker`** (do odczytu wyniku i sterowania okresem): + +| Pole / metoda | Typ / sygnatura | Uwaga | +|---|---|---| +| `Okres` | `Soneta.Types.FromTo` | okres deklaracji | +| `DataDokumentu`, `DataWypełnienia` | `Soneta.Types.Date` | daty na dokumencie | +| `KEDU` | `Soneta.Deklaracje.ZUS.KEDU` | zestaw dokumentów ZUS, do którego trafiają wygenerowane bloki | +| `Deklaracje` | `System.Collections.Generic.IList` | wygenerowane deklaracje (do odczytu po akcji) | +| `CzyJestZUA()`, `CzyJestZZA()` | — | rozpoznanie typu zgłoszenia ze schematu ubezpieczeń | + +**Schemat ubezpieczeń umowy + zgłoszenie ZUA/ZZA:** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["006"]; +Umowa umowa = pracownik.Umowy.Cast().First(); + +// 1) Schemat ubezpieczeń umowy (historyczny) — ZUA: społeczne obowiązkowe + zdrowotne: +using (var t = session.Logout(editMode: true)) +{ + var ub = umowa.Last.Ubezpieczenia; // UmowaHistoria.Ubezpieczenia + ub.Tyub4 = kadry.TytulyUbezpiecz4.WgKodu[kodTytulu]; // tytuł zleceniobiorcy (klucz int, ze słownika) + ub.ObowiazkoweOd = umowa.Okres.From; // data objęcia społecznymi + ub.Emerytalne.Obowiazkowe = true; + ub.Rentowe.Obowiazkowe = true; + ub.Zdrowotne.ObowiazkoweOd = umowa.Okres.From; + // (ZZA = tylko zdrowotne: JAWNIE ustaw Emerytalne.Obowiazkowe = false i Rentowe.Obowiazkowe = false + // — domyślnie są true; samo zdrowotne nie wystarcza) + t.Commit(); +} +session.Save(); + +// 2) Zgłoszenie ZUA/ZZA na podstawie umowy — worker (DataType Umowa): +// Uwaga: Umowy oraz Pars są SET-ONLY (brak gettera) — ustawiamy je przez inicjalizator, +// a parametry budujemy jako osobny obiekt ParamsZ(context) i przypisujemy do Pars. +var worker = new Soneta.Deklaracje.ZUS.ZarejestrujUmowyWorker { Umowy = new[] { umowa } }; + +var pars = new Soneta.Deklaracje.ZUS.ZarejestrujBaseWorker.ParamsZ(context); +pars.Okres = new FromTo(umowa.Okres.From, Date.MaxValue); +pars.DataDokumentu = umowa.Okres.From; +pars.DataWypełnienia = Date.Today; +pars.ZarejestrujRodzinę = false; + +var rejestracja = new Soneta.Deklaracje.ZUS.ZarejestrujUmowyWorker.Rejestracja { Pars = pars }; +rejestracja.ZarejestrujUmowy(); // generuje ZUA lub ZZA wg schematu ubezpieczeń umowy +session.Save(); +``` + +**Wyrejestrowanie po zakończeniu umowy (ZWUA):** + +```csharp +var parsW = new Soneta.Deklaracje.ZUS.ZarejestrujBaseWorker.ParamsW(context); +parsW.Okres = new FromTo(umowa.Okres.To, umowa.Okres.To); +parsW.DataDokumentu = umowa.Okres.To; +parsW.DataWypełnienia = Date.Today; + +var wyrej = new Soneta.Deklaracje.ZUS.ZarejestrujUmowyWorker.Wyrejestrowanie { Pars = parsW }; +wyrej.WyrejestrujUmowy(); // generuje ZWUA +session.Save(); +``` + +**Pułapki:** +- **Typ zgłoszenia (ZUA vs ZZA) wynika ze schematu `UmowaHistoria.Ubezpieczenia`**, nie z parametru + workera — ustaw poprawnie `Tyub4` + flagi `Spoleczne.Obowiazkowe`/`Zdrowotne` **przed** zgłoszeniem. +- `Ubezpieczenia` jest **historyczne** — zmiana schematu „od daty" to nowy zapis `UmowaHistoria` + (`umowa.Historia.Update(date)` + `UmowaHistorie.AddRow`, jak G2/A14), nie nadpisywanie bieżącego. +- `Spoleczne.Od` jest **tylko do odczytu** (wyliczane) — datę objęcia społecznymi obowiązkowymi + ustawiasz zbiorczo przez `Ubezpieczenia.ObowiazkoweOd`; na `Zdrowotne` `ObowiazkoweOd` jest + zapisywalne wprost (asymetria — jak w A7). +- `Tyub4` to rekord **konfiguracyjnego** słownika `TytulyUbezpiecz4`, klucz `WgKodu[int]` — pobierz + istniejący tytuł zleceniobiorcy, nie twórz „w locie". +- `ZarejestrujUmowyWorker` jest na `Umowa` (umowy), a `ZarejestrujPracownikówWorker` na `Pracownik` + (etatowi) — do zleceniobiorców używaj wersji „Umowy". +- Workery deklaracji uruchamiaj jak każdy worker enova (Context z tej samej sesji); po akcji wołasz + `Session.Save()`. Obsłuż `RowConflictException` z `Save()` (safe-code §4). +- `ZarejestrujRodzinę`/`WyrejestrujRodzinę` sterują dołączeniem ZCNA dla członków rodziny + (`pracownik.Rodzina`, A9) — dla zleceniobiorcy zgłoszenie rodziny działa analogicznie do etatu. + +## H. Płace — naliczanie wypłat + +> **Model danych.** `Wyplata` (`Soneta.Place.Wyplata`) jest klasą **abstrakcyjną**, root `GuidedRow`, +> tabela `Wyplaty`. Konkretne typy: `WyplataEtat` (etat), `WyplataUmowa` (umowy), `WyplataInne` +> (pozostałe). Każda wypłata należy do jednej **listy płac** (`ListaPlac`, tabela `ListyPlac`) i do +> jednego pracownika. Składniki wynagrodzenia to **elementy** (`WypElement`, tabela `WypElementy`, +> root guided) w kolekcji `Wyplata.Elementy: SubTable`. +> +> **Naliczanie** realizuje publiczny worker `Soneta.Place.NaliczanieSeryjne` (klasa abstrakcyjna +> `partial`) z zagnieżdżonymi klasami: +> - parametry: `NaliczanieSeryjne.Params` (bazowa), `NaliczanieSeryjne.PracownikParams : Params` +> (etat + pozostałe), `NaliczanieSeryjne.UmowaParams : Params` (umowy); +> - wykonawcy: `NaliczanieSeryjne.Pracownika : NaliczanieSeryjne` (wypłaty pracownika), +> `NaliczanieSeryjne.Umowy : NaliczanieSeryjne` (wypłaty z umów). +> +> Wynik to obiekt `Soneta.Place.NaliczanieWypłat` z kolekcją `WszystkieWypłaty: IList` (elementy są +> typu `Wyplata`). **Naliczanie samo zatwierdza zmiany w sesji** (`Nalicz()` wewnętrznie otwiera i +> commituje transakcję edycyjną na sesji pracownika) — utrwalenie w bazie wymaga osobnego +> `session.Save()`. + +### H1 — Naliczanie wypłat etatowych (★) + +**Cel:** naliczyć wypłatę etatową (wynagrodzenie zasadnicze etatu + dodatki/potrącenia) dla jednego +pracownika za wskazany okres rozliczeniowy. + +**Klasy, pola i typy:** + +| Element | Typ / sygnatura | Uwaga | +|---|---|---| +| Parametry | `new NaliczanieSeryjne.PracownikParams(Context)` | ctor przyjmuje `Context` (sesja operacyjna) | +| Data wypłaty | `PracownikParams.DataWypłaty: Date` | ustawienie **automatycznie** wylicza `Okres` (z konfiguracji listy) i `MiesiącDeklaracji` | +| Data listy | `PracownikParams.DataListy: Date` | data dokumentu listy płac | +| Okres naliczania | `PracownikParams.Okres: FromTo` | zwykle wyliczony z `DataWypłaty`; można nadpisać | +| Typ naliczenia | `PracownikParams.Naliczanie: TypNaliczenia` | `PłatnaZGóry`/`PłatnaZDołu`; **domyślnie `PłatnaZDołu`** — patrz Pułapki (licencja) | +| Filtr typu wypłaty | `PracownikParams.TypWypłaty: TypWyplaty` | `Wszystkie`/`Etat`/`Umowa`/`Inne` — dla etatu `Etat` lub `Wszystkie` | +| Wykonawca | `new NaliczanieSeryjne.Pracownika(PracownikParams)` | | +| Pracownik | `NaliczanieSeryjne.Pracownika.Pracownik: Pracownik` | komu naliczamy (z tej samej sesji co `Context`) | +| Uruchomienie | `NaliczanieSeryjne.Pracownika.Nalicz(): NaliczanieWypłat` | nalicza i zatwierdza w sesji | +| Wynik | `NaliczanieWypłat.WszystkieWypłaty: IList` (elementy `Wyplata`) | naliczone wypłaty | +| Błędy naliczania | `NaliczanieWypłat.Nienaliczeni: IEnumerable` | pracownicy, dla których się nie udało | + +`TypNaliczenia` (`Soneta.Place`): `PłatnaZGóry = 1`, `PłatnaZDołu = 2`. +`TypWyplaty` (`Soneta.Place`): `Wszystkie = 0`, `Etat = 1`, `Umowa = 2`, `Inne = 3`. + +**Snippet:** + +```csharp +var place = session.GetPlace(); +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +// Parametry naliczania — Context z tej samej sesji co pracownik: +var pars = new NaliczanieSeryjne.PracownikParams(context); +pars.DataWypłaty = new Date(2024, 6, 28); // ustawia Okres i MiesiącDeklaracji automatycznie +pars.DataListy = pars.DataWypłaty; +// pars.Naliczanie pozostaje domyślnie PłatnaZDołu (nie ustawiamy — patrz Pułapki) +pars.TypWypłaty = TypWyplaty.Etat; // tylko wypłaty etatowe + +var naliczanie = new NaliczanieSeryjne.Pracownika(pars) { Pracownik = pracownik }; +NaliczanieWypłat wynik = naliczanie.Nalicz(); // nalicza + commit w sesji + +foreach (Wyplata w in wynik.WszystkieWypłaty) +{ + // w.Pracownik, w.ListaPlac, w.Data, w.MiesiacDeklaracji, w.Wartosc (Currency, do wypłaty) +} + +session.Save(); // utrwalenie w bazie (opcjonalne — bez tego zmiany żyją tylko w sesji) +``` + +**Pułapki:** +- **`Context` musi pochodzić z tej samej sesji co pracownik.** `PracownikParams(Context)` wiąże się z + `Context.Session`; pracownik pobrany z innej sesji spowoduje niespójność. +- **Nie ustawiaj `Naliczanie` jawnie, jeśli nie masz pewności co do licencji.** Setter + `Params.Naliczanie` rzuca wyjątek, gdy licencja nie jest „PL Złoty/Platynowy" — getter wtedy i tak + zwraca `PłatnaZDołu`. Pozostawienie wartości domyślnej (`PłatnaZDołu`) jest bezpieczne. +- `Nalicz()` **otwiera własną transakcję** na sesji pracownika i commituje ją — **nie owijaj** wywołania + w dodatkowy `session.Logout(true)`. Po naliczeniu zmiany są w sesji; do bazy idą dopiero w `Save()`. +- `WszystkieWypłaty` to `IList` nietypowana — iteruj jako `foreach (Wyplata w in ...)`. +- Pracownik w archiwum (`Pracownik.ArchiwumInfo == InformacjeOArchiwum.WArchiwum`) jest pomijany — + `WszystkieWypłaty` będzie puste, bez wyjątku. +- Naliczanie to operacja na danych operacyjnych — sprawdź `wynik.Nienaliczeni` zamiast łapać ogólny + wyjątek; przy `KontynacjaNaliczenia` (tryb seryjny) błędy lądują tam, a nie w `throw`. + +### H2 — Naliczanie wypłat z umów (★) + +**Cel:** naliczyć wypłatę z konkretnej umowy cywilnoprawnej (`Soneta.Kadry.Umowa`). + +**Klasy, pola i typy:** + +| Element | Typ / sygnatura | Uwaga | +|---|---|---| +| Parametry | `new NaliczanieSeryjne.UmowaParams(Context)` | jak `PracownikParams`, ale `Naliczanie` jest na sztywno `PłatnaZDołu` (setter rzuca `NotSupportedException`) | +| Data wypłaty / listy / okres | `UmowaParams.DataWypłaty`, `.DataListy`, `.Okres` | jak w H1 | +| Wykonawca | `new NaliczanieSeryjne.Umowy(UmowaParams)` | w ctorze ustawia `TypWypłaty = Umowa` | +| Umowa | `NaliczanieSeryjne.Umowy.Umowa: Umowa` | ustawienie umowy ustawia też `Pracownik` z `umowa.Pracownik` | +| Uruchomienie | `NaliczanieSeryjne.Umowy.Nalicz(): NaliczanieWypłat` | | + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +Umowa umowa = pracownik.Umowy.Cast().First(); // przykładowa umowa pracownika + +var pars = new NaliczanieSeryjne.UmowaParams(context); +pars.DataWypłaty = new Date(2024, 6, 28); +pars.DataListy = pars.DataWypłaty; + +var naliczanie = new NaliczanieSeryjne.Umowy(pars) { Umowa = umowa }; +NaliczanieWypłat wynik = naliczanie.Nalicz(); + +foreach (Wyplata w in wynik.WszystkieWypłaty) +{ + // w.Typ == TypWyplaty.Umowa; w.Wartosc; w.Elementy +} +session.Save(); +``` + +**Pułapki:** +- **Nie ustawiaj `UmowaParams.Naliczanie`** — setter rzuca `NotSupportedException` (umowy zawsze + „płatne z dołu"). +- Ustawienie `Umowy.Umowa` nadpisuje `Pracownik` na właściciela umowy — nie ustawiaj `Pracownik` ręcznie. +- Pozostałe pułapki jak w H1 (Context z tej samej sesji, własna transakcja w `Nalicz()`, `Save()`). + +### H3 — Naliczanie pozostałych wypłat (★) + +**Cel:** naliczyć wypłaty „pozostałe" — pojedynczy dodatek/potrącenie (np. premia, zasiłek +jednorazowy) poza zasadniczym wynagrodzeniem etatu, bądź wypłaty typu `Inne`. + +**Mechanizm:** używamy tego samego wykonawcy co H1 — `NaliczanieSeryjne.Pracownika` — sterując +zakresem przez `PracownikParams`: +- `PracownikParams.TypWypłaty = TypWyplaty.Inne` — naliczanie tylko składników typu „inne", +- `PracownikParams.Dodatek: DefinicjaElementu` — **zawężenie do jednej definicji** dodatku/potrącenia + (naliczany jest tylko wskazany składnik). + +**Pola i typy:** + +| Element | Typ / sygnatura | Uwaga | +|---|---|---| +| Filtr typu | `PracownikParams.TypWypłaty: TypWyplaty` | `Inne` — pozostałe; `Wszystkie` — łącznie z etatem | +| Pojedynczy składnik | `PracownikParams.Dodatek: DefinicjaElementu` | definicja konkretnego dodatku/potrącenia; `null` = bez zawężenia | + +**Snippet:** + +```csharp +var place = session.GetPlace(); +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +// Definicja konkretnego dodatku/potrącenia (rekord konfiguracyjny): +DefinicjaElementu defDodatku = place.DefElementow.WgKodu["PREMIA"]; // przykładowy kod + +var pars = new NaliczanieSeryjne.PracownikParams(context); +pars.DataWypłaty = new Date(2024, 6, 28); +pars.DataListy = pars.DataWypłaty; +pars.TypWypłaty = TypWyplaty.Inne; // pozostałe wypłaty +pars.Dodatek = defDodatku; // tylko ten składnik + +var naliczanie = new NaliczanieSeryjne.Pracownika(pars) { Pracownik = pracownik }; +NaliczanieWypłat wynik = naliczanie.Nalicz(); + +foreach (Wyplata w in wynik.WszystkieWypłaty) +{ + foreach (WypElement e in w.Elementy) + { + // e.Definicja, e.Nazwa, e.Wartosc (decimal), e.Okres + } +} +session.Save(); +``` + +**Pułapki:** +- `Dodatek` to rekord **konfiguracyjny** `DefinicjaElementu` — pobierz istniejącą definicję + (np. przez klucz kodu w `place.DefElementow`), nie twórz „w locie". +- `TypWyplaty.Inne` i `TypWyplaty.Etat` są rozłączne — by naliczyć etat + dodatki łącznie użyj + `Wszystkie`. +- Pozostałe pułapki jak w H1. + +### H4 — Przeglądanie/odczyt wypłat za rok (★) + +**Cel:** odczytać naliczone wypłaty pracownika za dany rok i zagregować wartości (suma do wypłaty, +brutto/netto/składki/podatek, sumy składników). + +**Dostęp do wypłat (publiczny kontrakt):** + +| Punkt wejścia | Typ | Uwaga | +|---|---|---| +| `pracownik.Wyplaty` | `SubTable` | wszystkie wypłaty pracownika (klucz `WgPracownik`) | +| `session.GetPlace().Wyplaty.WgPracownik[pracownik]` | `SubTable` | równoważnie z modułu | +| `session.GetPlace().Wyplaty.WgData[date]` | `SubTable` | wypłaty z datą `date` | +| `listaPlac.Wyplaty` | `SubTable` | wypłaty danej listy płac | + +**Pola wypłaty (`Wyplata`) do odczytu:** + +| Pole | Typ | Opis | +|---|---|---| +| `Pracownik` | `Pracownik` | właściciel | +| `ListaPlac` | `ListaPlac` | lista płac (`ListaPlac.Okres: FromTo`, `ListaPlac.DataWyplaty: Date`, `ListaPlac.Zatwierdzona: bool`) | +| `Data` | `Date` | data wypłaty (klucz `WgData`) | +| `MiesiacDeklaracji` | `YearMonth` | miesiąc rozliczenia PIT | +| `MiesiacZUS` | `YearMonth` | miesiąc rozliczenia ZUS | +| `Wartosc` | `Currency` | kwota **do wypłaty** (netto) w PLN | +| `Numer` | `NumerDokumentu` | numer dokumentu (`Numer.NumerPelny`) | +| `Typ` | `TypWyplaty` | etat / umowa / inne | +| `Bufor` | `bool` | wypłata w buforze (niezatwierdzona) | +| `Elementy` | `SubTable` | składniki wynagrodzenia | + +**Kwoty na poziomie wypłaty (`Soneta.Place.Wyplata`, typ `Soneta.Types.Currency`):** `Wartosc` +(kwota **do wypłaty**, PLN), `WartoscCy` (w walucie listy), `DoWypłaty`, `Gotówka`, `Inne`. +Aby otrzymać `decimal`, użyj **`.Value`** (`w.Wartosc.Value`) — `Currency` nie ma jawnego rzutowania +na `decimal`. + +> **Uwaga:** `Wyplata`/`WyplataEtat` **nie udostępnia** publicznych agregatów typu `Brutto`, `Netto`, +> `SkładkiZUS`, `Podatek` jako gotowych właściwości. Brutto/netto/składki/podatek **liczymy sumując +> składniki** z kolekcji `Wyplata.Elementy` (`WypElement.Wartosc`, `WypElement.Netto`, `WypElement.Podatki.*`). + +**Składniki (`WypElement`) i ich struktura podatkowo-składkowa:** + +| Pole | Typ | Opis | +|---|---|---| +| `Definicja` | `DefinicjaElementu` | definicja składnika | +| `Nazwa` | `string` | nazwa składnika | +| `Wartosc` | `decimal` | wartość składnika | +| `Okres` | `FromTo` | okres, za który naliczono | +| `Podatki` | `Podatki` (subrow) | struktura podatków/składek | +| `Podatki.PodstawaZUS` | `decimal` | podstawa ZUS | +| `Podatki.Emerytalna` / `Rentowa` / `Chorobowa` / `Wypadkowa` / `Zdrowotna` | `SkladkaZUS` (subrow) | każda z polami `Podstawa`, `Prac`, `Firma: decimal` | +| `Podatki.Koszty`, `Podatki.Ulga`, `Podatki.ZalFIS` | `decimal` | koszty, ulga, zaliczka PIT | + +**Snippet:** + +```csharp +var place = session.GetPlace(); +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +int rok = 2024; +var od = new Date(rok, 1, 1); +var doD = new Date(rok, 12, 31); + +// Filtr serwerowy po dacie wypłaty (zakres roku) — bez pełnego skanu: +decimal sumaDoWypłaty = 0m; +decimal sumaBrutto = 0m; + +foreach (Wyplata w in pracownik.Wyplaty[(Wyplata x) => x.Data >= od && x.Data <= doD]) +{ + sumaDoWypłaty += w.Wartosc.Value; // kwota do wypłaty (Currency -> decimal przez .Value) + + // brutto/składki/podatek liczymy z elementów (nie ma gotowych agregatów na wypłacie): + foreach (WypElement e in w.Elementy) + { + sumaBrutto += e.Wartosc; // WypElement.Wartosc to decimal + decimal netto = e.Netto; + decimal podstawaZUS = e.Podatki.PodstawaZUS; + decimal zaliczkaPit = e.Podatki.ZalFIS; + } +} +``` + +**Pułapki:** +- `Wyplaty` to tabela **operacyjna guided** — zawsze ograniczaj zakresem czasowym (rok), nie iteruj + całości (`safe-code §6.3`). Filtruj serwerowo przez `SubTable[condition]` po `Data`, nie w pamięci. +- `Wartosc` to `Currency` (kwota do wypłaty); konwersja na `decimal` przez `.Value`. Składnik + `WypElement.Wartosc`/`WypElement.Netto` to już `decimal` — nie myl typów ani znaczeń. +- **Nie ma** gotowych właściwości agregujących (`Brutto`/`Netto`/`SkładkiZUS`/`Podatek`) na `Wyplata` + ani `WyplataEtat` — sumuj składniki z `Wyplata.Elementy` (i ich `Podatki.*`). +- `SkladkaZUS` ma pola `Podstawa`, `Prac`, `Firma` (część pracownika i pracodawcy) oraz właściwość + pomocniczą `Składka` (suma) — wybierz właściwą do potrzeb. +- Filtruj po `Data` (data wypłaty) lub `MiesiacDeklaracji`/`MiesiacZUS` zależnie od potrzeby + raportowej — to różne pojęcia roku (rok wypłaty vs rok deklaracji). + +### H5 — Odczyt elementów wypłaty (brutto/składki/podatek/netto) (★) + +**Cel:** odczytać składniki konkretnej **naliczonej** wypłaty (`Soneta.Place.Wyplata`) i wyliczyć +agregaty: brutto, składki ZUS (część pracownika i firmy), zaliczka PIT, netto. + +**Model.** Składniki to `Wyplata.Elementy: SubTable` (`Soneta.Place.WypElement`, tabela +operacyjna guided `WypElementy`). `Wyplata` **nie** ma gotowych agregatów `Brutto`/`Netto`/`SkładkiZUS`/ +`Podatek` — liczymy je z elementów albo przez worker `Wyplata.PITInfoWorker` (patrz niżej). + +**Pola składnika `WypElement` (do odczytu):** + +| Pole | Typ | Opis | +|---|---|---| +| `Definicja` | `DefinicjaElementu` | definicja składnika (konfiguracja) | +| `Nazwa` | `string` | nazwa składnika | +| `Wartosc` | `decimal` | wartość brutto składnika (kwota elementu) | +| `Netto` | `decimal` | wartość netto składnika | +| `DoWypłaty` | `decimal` | kwota do wypłaty z tego składnika | +| `Okres` | `FromTo` | okres, za który naliczono | +| `MiesiacDeklaracji` | `YearMonth` | miesiąc rozliczenia PIT | +| `MiesiacZUS` | `YearMonth` | miesiąc rozliczenia ZUS | +| `Podatki` | `Podatki` (subrow) | struktura podatkowo-składkowa | + +**Subrow `WypElement.Podatki` (`Soneta.Place.Podatki`) — pola istotne:** + +| Pole | Typ | Opis | +|---|---|---| +| `PodstawaZUS` | `decimal` | podstawa wymiaru składek ZUS | +| `Emerytalna` / `Rentowa` / `Chorobowa` / `Wypadkowa` / `Zdrowotna` | `SkladkaZUS` (subrow) | każda z polami `Podstawa`, `Prac`, `Firma: decimal` oraz wyliczanym `Składka` (suma) | +| `Koszty` | `decimal` | koszty uzyskania przychodu | +| `Ulga` | `decimal` | ulga podatkowa (kwota wolna) | +| `ZalFIS` | `decimal` | zaliczka na podatek dochodowy (fiskus) | +| `ZdrowotneDoOdliczenia` | `decimal` | składka zdrowotna do odliczenia | + +Subrow `SkladkaZUS` (`Soneta.Place.SkladkaZUS`): `Podstawa` (podstawa), `Prac` (część pracownika, +`decimal`), `Firma` (część pracodawcy, `decimal`), wyliczane `Składka` (suma) i `JestMinus` (`bool`). + +**Worker-agregator `Wyplata.PITInfoWorker`** (klasa publiczna, `[Context] Wypłata`) — udostępnia gotowe +sumy podatkowe dla wypłaty: + +| Właściwość | Typ | Opis | +|---|---|---| +| `DoOpodatkowania` | `Currency` | suma elementów opodatkowanych (brutto opodatkowane) | +| `Nieopodatkowane` | `Currency` | suma elementów nieopodatkowanych | +| `Razem` | `decimal` | opodatkowane + nieopodatkowane (przychód razem) | +| `NettoRazem` | `decimal` | wynagrodzenie netto razem | +| `NettoOpodat` | `Currency` | netto opodatkowane | +| `SkładkiZUS` | `decimal` | suma składek ZUS pracownika | +| `SkładkaZdrow` | `decimal` | składka zdrowotna | +| `Koszty` | `decimal` | koszty uzyskania razem | +| `Ulga` | `decimal` | ulga podatkowa | +| `ZalFIS` | `decimal` | zaliczka PIT | +| `Dochód_Bez26` / `Dochód_26` | `decimal` | dochód (z podziałem na ulgę „do 26 lat") | + +> `PITInfoWorker.Brutto` i `PITInfoWorker.Netto` są oznaczone `[Obsolete]` — używaj `DoOpodatkowania`, +> `Nieopodatkowane`, `Razem`, `NettoRazem`. Worker przyjmuje też kolekcję `Elementy: IEnumerable` +> (zamiast `Wypłata`) i `WykluczoneElementy: DefinicjaElementu[]`. + +**Snippet (agregacja ręczna z elementów):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +decimal brutto = 0m, netto = 0m, zusPrac = 0m, zusFirma = 0m, zalPit = 0m; + +// jedna konkretna wypłata pracownika (np. ostatnia z czerwca): +var od = new Date(2024, 6, 1); +var doD = new Date(2024, 6, 30); +Wyplata wyplata = pracownik.Wyplaty[(Wyplata x) => x.Data >= od && x.Data <= doD].Cast().First(); + +foreach (WypElement e in wyplata.Elementy) +{ + brutto += e.Wartosc; // WypElement.Wartosc to decimal + netto += e.Netto; + zalPit += e.Podatki.ZalFIS; + + zusPrac += e.Podatki.Emerytalna.Prac + e.Podatki.Rentowa.Prac + + e.Podatki.Chorobowa.Prac + e.Podatki.Zdrowotna.Prac; + zusFirma += e.Podatki.Emerytalna.Firma + e.Podatki.Rentowa.Firma + + e.Podatki.Wypadkowa.Firma; +} + +decimal doWyplaty = wyplata.Wartosc.Value; // Currency -> decimal przez .Value +``` + +**Snippet (przez worker — gotowe agregaty):** + +```csharp +var pit = new Wyplata.PITInfoWorker { Wypłata = wyplata }; +decimal brutto = pit.Razem; // przychód razem +decimal nettoR = pit.NettoRazem; +decimal zus = pit.SkładkiZUS; +decimal zdrow = pit.SkładkaZdrow; +decimal zaliczka = pit.ZalFIS; +``` + +**Pułapki:** +- `WypElement.Wartosc`/`Netto`/`DoWypłaty` to `decimal`; `Wyplata.Wartosc` (do wypłaty) to `Currency` — + konwersja przez `.Value` (§10.1). +- `SkladkaZUS.Prac` to część pracownika, `SkladkaZUS.Firma` to część pracodawcy — wybierz właściwą + zależnie od potrzeby (koszt pracownika vs koszt pracodawcy). +- `Wyplaty`/`WypElementy` to tabele operacyjne guided — pobieraj zakresem czasowym (§6.3), nie iteruj + całości. +- Pomijaj elementy stornowane przy sumowaniu, jeśli liczysz stan bieżący — patrz `WypElement.RozliczenieStorna` + (H10); naliczona wypłata po korekcie zawiera zarówno element pierwotny (`Wystornowany`) jak i `Stornujący`. + +--- + +### H6 — Wypłata zaliczki / dołączenie zaliczki (★) + +**Cel:** naliczyć i wypłacić zaliczkę (wypłata środków „na poczet" przyszłego wynagrodzenia), tworząc +dokument `Soneta.Place.Zaliczka` i element realizacji zaliczki na wypłacie. + +**Model.** Zaliczka to rekord operacyjny `Soneta.Place.Zaliczka` (root guided, tabela `Zaliczki`, +`session.GetPlace().Zaliczki`), implementuje `IBazaZrodlaWyplaty` i `IPowiązanieWypłaty`. Element +realizujący zaliczkę to `WypElementZaliczka.Realizacja : WypElementZaliczka : WypElement`, spłata to +`WypElementZaliczka.Spłata`. Powiązanie elementu z zaliczką: `WypElement.BazaZrodla = Zaliczka`, +`RodzajŹródłaWypłaty.Zaliczka`. + +**Ścieżka publiczna — worker `Soneta.Place.WypłaćZaliczkęWorker`** (na `Soneta.Kadry.Pracownicy`): + +| Element | Typ / sygnatura | Uwaga | +|---|---|---| +| Parametry | `WypłaćZaliczkęWorker.ZalParams : WypElement.Params` | ctor `(Context)`; `Rodzaj == RodzajŹródłaWypłaty.Zaliczka` | +| Definicja | `ZalParams.Definicja: DefinicjaElementu` | definicja elementu zaliczki (z `WypElement.Params`); **musi mieć** `DefinicjaElementu.RodzajZrodla == RodzajŹródłaWypłaty.Zaliczka` — inaczej worker rzuca `WypElement.RodzajDefinicjiException` (np. „Korekta zaliczki podatku" ma `Dodatek`) | +| Data | `ZalParams.Data: Date` | data wypłaty zaliczki (wymagana) | +| Kwota | `ZalParams.Kwota: Currency` | kwota zaliczki (wymagana) | +| Pracownicy | `WypłaćZaliczkęWorker.Pracownicy: Pracownik[]` | dla kogo | +| Akcja | `[Action("Wypłać zaliczkę")] object WypłataZaliczki()` | tworzy `Zaliczka`, nalicza element realizacji | + +**Stan zaliczki (`Zaliczka`):** `Wartosc: Currency`, `Splacono: Currency`, `Pozostaje: Currency` +(`= Wartosc - Splacono`), `Stan: StanZaliczki` (`NieSpłacona`/`CzęściowoSpłacona`/`CałkowicieSpłacona`), +`Realizacje: SubTable` (elementy realizacji), `Spłaty: SubTable` (elementy spłaty). + +**Mechanizm naliczenia** (realizowany przez worker): dla każdego pracownika tworzony jest +`new Zaliczka(pracownik)`, dodawany przez `Zaliczki.AddRow(zaliczka)`, a następnie niskopoziomowy +obiekt `Soneta.Place.NaliczanieWypłat` z `NaliczŹródłoWypłaty = zaliczka` wykonuje `Nalicz()`. +Dołączenie/spłata zaliczki w kolejnej wypłacie etatowej dzieje się automatycznie podczas zwykłego +naliczania (H1) — naliczanie wyszukuje niespłacone zaliczki pracownika i generuje element +`WypElementZaliczka.Spłata`. + +**Snippet (uruchomienie workera zaliczki):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +// definicję zaliczki rozpoznajemy po RodzajZrodla (nie po Kodzie/Nazwie — „Korekta zaliczki podatku" +// to RodzajZrodla.Dodatek, którego worker NIE przyjmie): +DefinicjaElementu defZaliczki = session.GetPlace().DefElementow.Cast() + .First(d => d.RodzajZrodla == RodzajŹródłaWypłaty.Zaliczka); + +var pars = new WypłaćZaliczkęWorker.ZalParams(context) { + Data = new Date(2024, 6, 15), + Kwota = new Currency(1000m, Currency.SystemSymbol), +}; +pars.Definicja = defZaliczki; + +var worker = new WypłaćZaliczkęWorker { Params = pars, Pracownicy = new[] { pracownik } }; +object wynik = worker.WypłataZaliczki(); // tworzy Zaliczka + nalicza; otwiera własną transakcję +session.Save(); +``` + +**Pułapki:** +- `ZalParams.Definicja` to **istniejąca** definicja elementu o `RodzajZrodla == RodzajŹródłaWypłaty.Zaliczka` — + pobierz z `place.DefElementow` (filtruj po `RodzajZrodla`, nie po `Kod`/`Nazwa`), nie twórz w locie. +- Baza Demo może nie zawierać definicji o `RodzajZrodla == Zaliczka` — wtedy worker jest niewykonalny + (w teście: `Assert.Ignore`). +- `Zaliczka.SetWartość(...)` jest `internal` — wartości nie ustawiaj ręcznie; przekaż `ZalParams.Kwota` + do workera. +- `Zaliczka` nie kasuje się bezpośrednio, gdy ma realizacje/spłaty (`OnDeleting` rzuca `RowException`). +- Worker otwiera własną transakcję (`Session.Logout(true)` + `CommitUI`) — nie owijaj dodatkowo; + utrwalenie w bazie przez `Save()`. + +--- + +### H7 — Korekta podatków i składek; „Przelicz składki ZUS i podatki" (★) + +**Cel:** ponownie przeliczyć (skorygować) składki ZUS i zaliczki PIT na już naliczonych elementach +wypłat pracownika za dany miesiąc deklaracji — np. po zmianie progu, tytułu ubezpieczenia, korekcie +danych kadrowych. + +**Worker `Soneta.Place.NaliczaniePodatkówMiesięcznie`** (na `Pracownik`/`PracHistoria`): + +| Element | Typ / sygnatura | Uwaga | +|---|---|---| +| Miesiąc | publiczny ctor `(YearMonth miesiącDeklaracji)` (atrybut `[Context(typeof(MiesiącDeklaracji),"Miesiąc")]`) | przy ręcznym wywołaniu przekaż `YearMonth` (np. `pars.Miesiąc`); property odczytu `MiesiącDeklaracji: YearMonth` (get) | +| Klasa parametru | `Soneta.Place.MiesiącDeklaracji : ContextBase` | `MiesiącDeklaracji.Miesiąc: YearMonth` (domyślnie `YearMonth.Today`) | +| Pracownik | `NaliczaniePodatkówMiesięcznie.Pracownik: Pracownik` | `[Context]` | +| `NoTrace` | `bool` | wyłączenie śladu (logu) operacji | +| Akcja | `[Action("Przelicz składki ZUS i podatki")] void PrzeliczPodatki()` | przelicza elementy z danego miesiąca | + +**Mechanizm:** worker iteruje elementy (`WypElementy.WgDaty`) wszystkich pracowników powiązanych +(`Pracownik.PracownicyPowiązani`) w okresie `MiesiącDeklaracji.ToFromTo()`, dla niezablokowanych +(`!element.Podatki.Korekta && element.Wyplata.Bufor`) wykonuje przeliczenie flag i naliczenie +podatków (`NaliczaniePodatków.NaliczRozrzuć()`). Wszystko w transakcji `Session.Logout(true)` + +`Commit()`. + +**Snippet:** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +var pars = new MiesiącDeklaracji(context) { Miesiąc = new YearMonth(2024, 6) }; +var worker = new NaliczaniePodatkówMiesięcznie(pars.Miesiąc) { Pracownik = pracownik }; +worker.PrzeliczPodatki(); // przelicza składki ZUS i zaliczki PIT za czerwiec 2024 +session.Save(); +``` + +**Pułapki:** +- Elementy z ręczną korektą podatków (`element.Podatki.Korekta == true`) oraz elementy z wypłat + zatwierdzonych (`!Wyplata.Bufor`) są **pomijane** — przeliczane są tylko elementy z bufora. +- `MiesiącDeklaracji.Miesiąc` to `YearMonth` — to miesiąc deklaracji, nie data wypłaty. +- Worker przelicza także pracowników powiązanych (`PracownicyPowiązani`) — operacja może objąć więcej + niż jedną kartotekę. + +--- + +### H8 — Rozliczenie pracownika; dochód / roczny dochód (★) + +**Cel:** odczytać dochód z naliczonej wypłaty oraz (dla właścicieli) pobrać roczny dochód do rozliczeń; +opcjonalnie uruchomić rozliczenie pracownika. + +**A. Dochód z wypłaty — `Wyplata.PITInfoWorker`** (publiczny, jak w H5). Dochód podatkowy: + +| Właściwość | Typ | Opis | +|---|---|---| +| `Dochód_Bez26` | `decimal` | dochód poza ulgą „do 26 lat" (`= przychód + przychód50 − koszty − koszty50`) | +| `Dochód_26` | `decimal` | dochód objęty ulgą „do 26 lat" | +| `DoOpodatkowania` | `Currency` | podstawa opodatkowania (brutto opodatkowane) | +| `Podstawa` | `decimal` | podstawa naliczenia zaliczki | +| `ZalFIS` | `decimal` | zaliczka PIT | + +Dochód roczny pracownika sumuje się iterując wypłaty roku (H4/H5) i sumując `Dochód_Bez26 + Dochód_26` +(lub `DoOpodatkowania`) z `PITInfoWorker` każdej wypłaty. + +**B. „Pobierz roczny dochód" — worker `Soneta.Kadry.PobierzDochodRocznyWorker`** (na `Pracownik`/ +`PracHistoria`) — **tylko dla właściciela** (`Pracownik is Wlasciciel`): + +| Element | Typ / sygnatura | Uwaga | +|---|---|---| +| Parametry | property `PobierzDochodRocznyWorker.Pars : PobierzDochodRocznyWorker.Params` | `Pars.Rok: int` (domyślnie rok ubiegły) | +| Pracownik | `PobierzDochodRocznyWorker.Pracownik: Pracownik` | `[Context]` | +| Akcja | `[Action("Pobierz roczny dochód")] void Pobierz()` | zapisuje `PrzychodRyczalt` (RoczDochSkala/RoczDochLiniowy/RoczDochRyczalt) za rok | + +Korzysta z serwisu `IDochódWłaściciela.KwotaDochoduStraty(pracownik, YearMonth, FormaOpodatkowania)`. + +**C. „Rozlicz pracownika" — worker `Soneta.Place.RozliczaniePracownikowWorker`** (na `Pracownik`) — +**tylko dla folderu pracowników zewnętrznych** (`KadryIPlace/Kadry/PracownicyZewnetrzni`): + +| Element | Typ / sygnatura | Uwaga | +|---|---|---| +| Parametry | `RozliczeniePracownikowParams : RozliczanieUmowZewnetrznychParams` | `Okres: FromTo`, `Data: Date` | +| Akcja | `[Action("Rozlicz pracownika")] RozliczanieUmowZewnetrznych Rozlicz()` | rozlicza umowy zewnętrzne pracownika | + +**Snippet (dochód roczny z wypłat):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var od = new Date(2024, 1, 1); var doD = new Date(2024, 12, 31); + +decimal dochodRoczny = 0m; +foreach (Wyplata w in pracownik.Wyplaty[(Wyplata x) => x.Data >= od && x.Data <= doD]) +{ + var pit = new Wyplata.PITInfoWorker { Wypłata = w }; + dochodRoczny += pit.Dochód_Bez26 + pit.Dochód_26; +} +``` + +**Pułapki:** +- `PobierzDochodRocznyWorker` działa wyłącznie dla `Wlasciciel` i form opodatkowania ogólnych/ryczałtu — + dla zwykłego pracownika nie ma zastosowania (zwraca bez efektu). +- „Rozlicz pracownika" (`RozliczaniePracownikowWorker`) dotyczy **pracowników zewnętrznych** (umowy + zewnętrzne), nie standardowego rozliczenia płacowego. +- Wewnętrzny `Wyplata.RozliczenieManager` (rozliczanie płatności/należności) jest **niepubliczny** — + rozliczenie płatności inicjuje setter `Wyplata.Bufor` (zejście z bufora), nie wywołuj go bezpośrednio. + +--- + +### H9 — Kalkulator wynagrodzeń (brutto↔netto, koszt pracodawcy) (★) + +**Cel:** wyliczyć netto z brutto (lub odwrotnie) oraz całkowity koszt pracodawcy. + +**Brak dedykowanej publicznej klasy „kalkulatora wynagrodzeń"** w publicznym kontrakcie (patrz sekcja +„niewykonalne"). Wyliczenie realizujemy przez **naliczenie próbne** (H1/H3 — `NaliczanieSeryjne`) i +odczyt agregatów workera `Wyplata.PITInfoWorker` oraz `Wyplata.KosztyUzyskaniaPrzychoduWorker`. + +**Koszt pracodawcy — `Wyplata.PITInfoWorker` + składki firmy z elementów:** +- brutto: `pit.Razem` / `pit.DoOpodatkowania`, +- netto: `pit.NettoRazem`, +- składki pracownika: `pit.SkładkiZUS`, `pit.SkładkaZdrow`, +- zaliczka PIT: `pit.ZalFIS`, +- składki firmy (narzuty pracodawcy): suma `WypElement.Podatki.{Emerytalna,Rentowa,Wypadkowa}.Firma` + (plus FP/FGŚP/FEP) — patrz `WyplataSkładkiWorker` niżej. + +**Agregator składek — `Soneta.Place.WyplataSkładkiWorker`** (publiczny, `[Context] Wypłata` lub +`Elementy: IEnumerable`): udostępnia `Razem: ZestawienieSkładek` z m.in.: + +| Właściwość `ZestawienieSkładek` | Typ | Opis | +|---|---|---| +| `KosztyZUS` | `decimal` | składki ZUS pracownika (emer.+rent.+chor.+wyp., część `Prac`) | +| `FirmaZUS` | `decimal` | składki ZUS pracodawcy (część `Firma`) | +| `Narzuty` | `decimal` | narzuty pracodawcy (`FirmaZUS` + FP.Firma + FGSP.Firma + FEP.Firma) | +| `ZUS` | `decimal` | `KosztyZUS + FirmaZUS` | +| `Emerytalna`/`Rentowa`/… | `ISkładka` | pojedyncze składki (`Podstawa`/`Prac`/`Firma`/`Składka`) | + +Koszt pracodawcy ≈ brutto (`pit.DoOpodatkowania`/`Razem`) + `skladki.Razem.Narzuty`. + +**Snippet (kalkulacja przez naliczenie próbne):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +var pars = new NaliczanieSeryjne.PracownikParams(context); +pars.DataWypłaty = new Date(2024, 6, 28); +pars.DataListy = pars.DataWypłaty; +pars.TypWypłaty = TypWyplaty.Etat; + +var nal = new NaliczanieSeryjne.Pracownika(pars) { Pracownik = pracownik }; +NaliczanieWypłat wynik = nal.Nalicz(); + +Wyplata w = (Wyplata)wynik.WszystkieWypłaty[0]; +var pit = new Wyplata.PITInfoWorker { Wypłata = w }; +var skl = new WyplataSkładkiWorker { Wypłata = w }; + +decimal brutto = pit.Razem; +decimal netto = pit.NettoRazem; +decimal kosztPracod = brutto + skl.Razem.Narzuty; // brutto + narzuty pracodawcy +// (jeśli to tylko kalkulacja — nie wywołuj Save(), wynik istnieje w sesji) +``` + +**Pułapki:** +- Brak osobnego „kalkulatora" — wynik zawsze powstaje przez naliczenie i workery agregujące. +- Kalkulacja brutto↔netto zależy od pełnej konfiguracji pracownika (etat, ulgi, koszty, PPK) — nie ma + bezstanowej funkcji „brutto→netto" w publicznym API. +- Jeśli naliczenie ma być tylko próbne, nie wywołuj `Save()` (zmiany zostaną w sesji i znikną z `Dispose`), + albo wykonaj na osobnej sesji „brudnopisowej". + +--- + +### H10 — Stornowanie elementów wypłaty; obsługa elementów stornowanych (★) + +**Cel:** zastornować (wycofać/skorygować) element już zatwierdzonej wypłaty i poprawnie odczytać stan +storna. + +**Model.** Storno opisuje rekord `Soneta.Place.StornoElementu` (tabela `StornaElementow`). Element +ma stan `WypElement.StanStorna: StanStornaElementu` oraz dostęp do storna `WypElement.Storno: StornoElementu`. + +Enum `Soneta.Place.StanStornaElementu`: `NieDotyczy=0`, `DoStornowania=1`, `Wystornowany=2`, +`Stornujący=3`, `WycofaneStorno=10` (tylko wyliczany). +Enum `Soneta.Place.RodzajStornaElementu`: `NieDotyczy=0`, `Anulowanie=1`, `Przeliczenie=2`. + +**Pola `WypElement` związane ze storno:** + +| Pole | Typ | Opis | +|---|---|---| +| `StanStorna` | `StanStornaElementu` | bieżący stan storna elementu | +| `StanStornaEx` | `StanStornaElementu` | jw. + `WycofaneStorno` gdy `Wystornowany` historyczny | +| `Storno` | `StornoElementu` | powiązany rekord storna (lub `null`) | +| `RozliczenieStorna` | `bool` | `true` gdy `Wystornowany` lub `Stornujący` (element nie liczy się do bieżącego stanu) | +| `Wystornowany` | `bool` | do elementu naliczono element stornujący | +| `Stornowane` / `Stornujące` | `SubTable` | relacje storn | +| `Korekta` | `bool` | element zmodyfikowany ręcznie przez operatora | +| `UtwórzStorno()` | `WypElement` | (wirtualna) tworzy element stornujący danego typu | + +**Workery oznaczania (publiczne, na `WypElement` / `Wyplata`):** +- `StornoElementu.ElementDoPrzeliczeniaWorker` (na `WypElement`): + - `[Action("Oznacz element do przeliczenia")] ZaznaczElementDoPrzeliczenia()` — `RodzajStornaElementu.Przeliczenie`, + - `[Action("Oznacz element do anulowania")] ZaznaczElementDoAnulowania()` — `RodzajStornaElementu.Anulowanie`, + - `[Action("Wycofaj oznaczenie anulowania lub przeliczenia")] WycofajZaznaczenie()` — kasuje `Storno`. +- `StornoElementu.WypłataDoPrzeliczeniaWorker` (na `Wyplata`): + - `ZaznaczElementyDoPrzeliczenia()` / `WycofajZaznaczenie()` — dla wszystkich elementów wypłaty. +- `StornoElementu.ListaPłacDoPrzeliczeniaWorker` (na `ListaPlac`, z `Params.Definicja` / `WszystkieElementy`). + +**Mechanizm.** Oznaczenie tworzy `StornoElementu` i ustawia element na `DoStornowania`. Właściwe +wytworzenie elementu stornującego (`UtwórzStornujący()`, stan `Wystornowany` na pierwotnym + +`Stornujący` na nowym) następuje przy ponownym naliczeniu wypłaty (H1) lub przeliczeniu. Wymagane: +wypłata zatwierdzona (`Wyplata.Zatwierdzona`) i element w stanie `NieDotyczy`. + +**Snippet (oznaczenie do anulowania + przeliczenie):** + +```csharp +Wyplata w = ...; // zatwierdzona wypłata pracownika 006 +WypElement element = w.Elementy.Cast().First(e => e.Definicja.Kod == "PREMIA"); + +// oznacz element do anulowania: +var worker = new StornoElementu.ElementDoPrzeliczeniaWorker { Element = element }; +worker.ZaznaczElementDoAnulowania(); // otwiera własną transakcję +// element.StanStorna == StanStornaElementu.DoStornowania, element.Storno.RodzajStorna == Anulowanie + +// ponowne naliczenie wypłaty (H1) wygeneruje element stornujący: +var pars = new NaliczanieSeryjne.PracownikParams(context) { DataWypłaty = w.Data, DataListy = w.Data }; +new NaliczanieSeryjne.Pracownika(pars) { Pracownik = w.Pracownik }.Nalicz(); +session.Save(); +``` + +**Odczyt elementów stornowanych:** + +```csharp +foreach (WypElement e in w.Elementy) +{ + if (e.RozliczenieStorna) continue; // pomiń wystornowane i stornujące przy sumowaniu stanu bieżącego + // ... e to element „żywy" +} +``` + +**Pułapki:** +- Oznaczać można tylko elementy wypłaty **zatwierdzonej** i w stanie `NieDotyczy` (`IsEnabled...` to + egzekwuje); na buforze storno nie ma sensu. +- Storno samo w sobie tylko **oznacza** (`DoStornowania`) — wystornowanie (`Wystornowany`/`Stornujący`) + powstaje dopiero przy ponownym naliczeniu/przeliczeniu. +- Przy sumowaniu kwot bieżących pomijaj `RozliczenieStorna == true`, inaczej policzysz element pierwotny + i jego storno podwójnie. +- Nie można przenieść do bufora wypłaty z elementami `DoStornowania`/`Wystornowany` (rzuca `RowException` + — patrz H11). + +--- + +### H11 — Anulowanie/usunięcie naliczonej wypłaty (bufor, ponowne naliczenie) (★) + +**Cel:** „cofnąć" naliczoną i zatwierdzoną wypłatę do edycji (bufor) lub usunąć, by naliczyć ponownie. + +**Model.** Wypłata ma flagi `Wyplata.Bufor: bool` (niezatwierdzona/edytowalna) oraz `Wyplata.Zatwierdzona: bool` +(odwrotność `Bufor`). Zejście z bufora = zatwierdzenie; powrót do bufora = otwarcie do edycji. + +**Workery (publiczne, na `Wyplata`):** + +| Worker / akcja | Sygnatura | Efekt | +|---|---|---| +| `Wyplata.ZatwierdźWorker` | property `Lista: Wyplata`; `[Action("Zatwierdź wypłatę")] void Zatwierdź()` | `Zatwierdzona = true` (zejście z bufora) | +| `Wyplata.OtwórzWorker` | property `Wypłata: Wyplata`; `[Action("Przenieś do bufora")] void Otwórz()` | `Zatwierdzona = false` (powrót do bufora) | + +Obie akcje działają w transakcji `Session.Logout(true)` + `Commit()`. **Uwaga na nazwy property:** +worker zatwierdzania przypina wypłatę przez `ZatwierdźWorker.Lista`, a otwierania — przez +`OtwórzWorker.Wypłata`. `IsEnabled...` wymaga `Wyplata.CanBufor` — ale `CanBufor` jest **`protected`** +(niedostępny z dodatku); stan czytaj przez publiczne `Wyplata.Bufor` / `Wyplata.Zatwierdzona`. + +**Bezpośrednia flaga `Wyplata.Bufor`:** +- setter `Bufor` rzuca `ColReadOnlyException`, gdy `!CanBufor`; +- zejście z bufora (`Bufor=false`) wyzwala rozliczenie płatności (wewnętrzny `RozliczenieManager`); +- `IsReadOnlyBufor()` true gdy brak praw / `!CanBufor` / wyłączone „ZatwierdzanieFlagą" / lista zatwierdzona. + +**Usunięcie / ponowne naliczenie.** Aby przeliczyć od nowa: przenieś wypłatę do bufora +(`OtwórzWorker.Otwórz()`), a następnie wykonaj ponowne naliczenie (H1 — `NaliczanieSeryjne`), które +nadpisze elementy. Usunięcie samej wypłaty realizuje standardowe `Row.Delete()` w transakcji (gdy +dozwolone — wypłata w buforze, bez powiązań blokujących). + +**Snippet (powrót do bufora + ponowne naliczenie):** + +```csharp +Wyplata w = ...; // zatwierdzona wypłata pracownika 006 + +// 1) przenieś do bufora: +new Wyplata.OtwórzWorker { Wypłata = w }.Otwórz(); // Zatwierdzona = false + +// 2) ponowne naliczenie (H1): +var pars = new NaliczanieSeryjne.PracownikParams(context) { + DataWypłaty = w.Data, DataListy = w.Data, TypWypłaty = TypWyplaty.Etat, +}; +new NaliczanieSeryjne.Pracownika(pars) { Pracownik = w.Pracownik }.Nalicz(); +session.Save(); +``` + +**Snippet (usunięcie wypłaty z bufora):** + +```csharp +using (ITransaction t = session.Logout(true)) { + w.Bufor = true; // upewnij się, że w buforze (lub OtwórzWorker) + w.Delete(); + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- `Otwórz()` rzuca `RowException`, gdy wypłata nie jest zatwierdzona; `Zatwierdź()` — gdy już + zatwierdzona. Sprawdzaj `IsEnabled...` / stan przed wywołaniem. +- `UpdateBufor()` rzuca `RowException`, gdy na wypłacie są elementy `DoStornowania`/`Wystornowany` — + najpierw wycofaj oznaczenia storna (H10) lub dokończ przeliczenie. +- Zejście z bufora wykonuje rozliczenie płatności i kopiowanie kursu — nie traktuj go jak zwykłej + zmiany pola. +- Operacje płacowe to dane operacyjne — łap `RowException`/`RowConflictException` z `Save()` (§4, §9), + nie ogólny `Exception`. + +## I. Listy płac, przelewy, wydruki + +> **Model.** Lista płac to dokument operacyjny `Soneta.Place.ListaPlac` (root `GuidedRow`, +> tabela `ListyPlac`, `session.GetPlace().ListyPlac`). Trzyma kolekcję wypłat +> `ListaPlac.Wyplaty: SubTable`. Każda `Wyplata` (root `GuidedRow`, tabela `Wyplaty`) +> wskazuje wstecz listę (`Wyplata.ListaPlac: IRow`) i pracownika (`Wyplata.Pracownik: IRow`). +> Wzorzec listy to `DefinicjaListyPlac` (tabela konfiguracyjna `DefListPlac`, +> `session.GetPlace().DefListPlac`, dostęp `WgSymbolu`/`WgNazwy`). +> +> **`Wyplata` jest abstrakcyjna** — konkretne typy: `WyplataEtat`, `WyplataUmowa`, `WyplataInne` +> (ctor `(ListaPlac listaplac, Pracownik pracownik)` oraz wariant z `IPowiązanieWypłaty`). +> W praktyce wypłat **nie tworzy się ręcznie** — robi to worker naliczania. + +### I1 — Naliczanie/generowanie list płac (★) + +**Cel:** utworzyć listę płac dla wybranego okresu i naliczyć na niej wypłaty pracowników +(etat/umowy), tak by `ListaPlac.Wyplaty` zawierała policzone `Wyplata`. + +**Warianty:** + +| Wariant | Mechanizm | Uwaga | +|---|---|---| +| Ręczne utworzenie pustej listy | `new ListaPlac()` + `ListyPlac.AddRow(lp)` + pola | sterujesz wszystkim sam | +| Naliczanie wypłat na istniejącej liście | worker `Soneta.Place.NaliczanieWypłat` (akcja `Nalicz`) | tworzy `Wyplata*` i liczy elementy | +| Naliczanie planowanych list (zbiorczo) | worker `Soneta.Place.NaliczaniePlanowanychListPłacWorker` (akcja `Nalicz`) | wg `DefinicjaPlanowanejListyPłac` | + +**Pola i typy (`ListaPlac`, kolejność ustawiania jest istotna):** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Definicja` | `Soneta.Place.DefinicjaListyPlac` | wzorzec listy; ustawić **pierwsze** po `AddRow` | +| `Wydzial` | `Soneta.Kadry.Wydzial` | tylko gdy `Definicja.Wydzial == true` | +| `Seria` | `string` | tylko gdy `Definicja.Seria == true` | +| `Data` | `Soneta.Types.Date` | data naliczania listy | +| `Naliczanie` | `Soneta.Place.TypNaliczenia` | wartości: `PłatnaZGóry`/`PłatnaZDołu`; **nie ustawiaj** — setter rzuca bez licencji „PL Złoty" | +| `DataWyplaty` | `Soneta.Types.Date` | data postawienia środków; wyznacza mies./rok | +| `MiesiacZUS` | `Soneta.Types.YearMonth` | miesiąc rozliczenia ZUS | +| `Okres` | `Soneta.Types.FromTo` | okres listy; **po** `DataWyplaty` i `Naliczanie` | +| `MiesWstecz` | `int` | | +| `Wyplaty` | `SubTable` | wypełniana przez worker naliczania | +| `Numer` | `Soneta.Core.NumerDokumentu` | nadawany automatycznie | +| `Bufor` / `Zatwierdzona` | `bool` | stan dokumentu | + +**Worker `Soneta.Place.NaliczanieWypłat`** — `[Context]`: `Context`, `ListaPłac: ListaPlac`, +`Pracownik: Soneta.Kadry.Pracownik`; akcja `NaliczanieWypłat Nalicz()`; właściwości +wynikowe m.in. `Wypłaty: IList`, `Nienaliczeni: IEnumerable`, +`DataWypłaty/DataListy/DataZUS: Date`, `Okres: FromTo`, `Naliczanie: TypNaliczenia`. + +**Worker `Soneta.Place.NaliczaniePlanowanychListPłacWorker`** — `[Context]`: +`Pracownik: Pracownik[]`; `Params Pars` z polami `Definicja: DefinicjaPlanowanejListyPłac`, +`DataWypłaty: Date`, `Okres: FromTo`, `Naliczanie: TypNaliczenia`, `TypWypłaty: TypWyplaty`, +`MiesiącZUS/MiesiącDeklaracji: YearMonth`, `Seria: string`, `MiesWstecz: int`, +`UwzgledniajNieZatwierdzoneListyPlac/EdycjaMiesiącaZUS: bool`; +akcja `NaliczaniePlanowanychListPłac Nalicz()`. + +**Snippet (ręczne utworzenie listy + naliczenie wypłaty pracownika):** + +```csharp +using Soneta.Business; +using Soneta.Place; +using Soneta.Kadry; +using Soneta.Types; + +var place = session.GetPlace(); + +// 1. Wzorzec listy płac (definicja konfiguracyjna). +var def = place.DefListPlac.WgSymbolu["ETAT"] + ?? throw new BusException("Brak definicji listy płac".Translate()); + +// 2. Pusta lista płac — KOLEJNOŚĆ: AddRow → Definicja → daty/naliczanie → Okres. +var lp = new ListaPlac(); +place.ListyPlac.AddRow(lp); +lp.Definicja = def; // pierwsze po AddRow +lp.Data = new Date(2026, 6, 30); +lp.DataWyplaty = new Date(2026, 6, 30); // wyznacza miesiąc/rok +lp.MiesiacZUS = new YearMonth(2026, 6); +lp.Okres = new FromTo(new Date(2026, 6, 1), new Date(2026, 6, 30)); // po DataWyplaty +// Uwaga: NIE ustawiaj lp.Naliczanie — setter rzuca bez licencji „PL Złoty"; getter ma sensowny domyślny. + +// 3. Naliczenie wypłaty pracownika — sprawdzona ścieżka to NaliczanieSeryjne (patrz sekcja H); +// naliczona wypłata zostaje automatycznie powiązana z bieżącą listą płac. +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var pars = new NaliczanieSeryjne.PracownikParams(context) // context: w UI z workera, w teście z TestBase +{ + DataWypłaty = new Date(2026, 6, 30), + DataListy = new Date(2026, 6, 30), + TypWypłaty = TypWyplaty.Etat, +}; +var wynik = new NaliczanieSeryjne.Pracownika(pars) { Pracownik = pracownik }.Nalicz(); + +// 4. Powiązanie wypłaty z listą jest dwukierunkowe (Wyplata.ListaPlac / Wyplata.Pracownik): +foreach (Wyplata w in wynik.WszystkieWypłaty) +{ + // w.ListaPlac, w.Pracownik +} + +session.Save(); +``` + +**Pułapki:** +- **Kolejność pól krytyczna:** `Okres` i `MiesWstecz` ustaw **po** `DataWyplaty` i `Naliczanie` + (wzajemne zależności wyliczeń) — patrz wzorzec w kodzie naliczania list. +- `Wydzial`/`Seria` ustawiaj **warunkowo** wg `Definicja.Wydzial`/`Definicja.Seria` — inaczej + ryzyko niespójności klucza `WgDefinicja`. +- Wypłat **nie twórz przez `new WyplataEtat(...)` ręcznie** — naliczaj. Sprawdzoną ścieżką + naliczania jest **`NaliczanieSeryjne.Pracownika(...).Nalicz()`** (sekcja H); sam worker + `NaliczanieWypłat { ListaPłac, Pracownik }.Nalicz()` w bazie Demo potrafi zwrócić pustą listę. +- `Wyplata.ListaPlac`/`Wyplata.Pracownik` to relacje **tylko do odczytu** — powiązania nie ustawisz + setterem; powstają w trakcie naliczania. +- `ListyPlac` to tabela operacyjna guided — przy odczycie filtruj zakresem (`WgDatyWyplaty`, + `WgOkresu`, `WgDefinicja`), nie skanuj całości (safe-code §6.3). +- `Wyplata.ListaPlac`/`Wyplata.Pracownik` to `IRow` (relacje interfejsowe) — porównuj/rzutuj + świadomie. + +### I2 — Drukowanie/PDF kwitków (pasków) wypłaty (★) + +**Cel:** wygenerować pasek (kwitek) wypłaty pracownika do PDF. + +**Mechanizm.** Wydruk realizuje serwis **`IReportService`** (namespace `Soneta.Business.UI`, +identycznie jak wydruki handlowe — patrz `dokument-handlowy.md` rozdz. 12). Wzorce pasków to +szablony `*.repx` zarejestrowane atrybutem `[DxReport]` w assembly +**`Soneta.KadryPlace.Reports`** dla `DataType = typeof(Soneta.Place.Wyplata)`: + +| Wzorzec (ReportName) | Plik szablonu (`TemplateFileName`) | `DataType` | +|---|---|---| +| „Pasek wypłaty" | `PasekWyplaty.repx` | `Soneta.Place.Wyplata` | +| „Duży pasek wypłaty" | `DuzyPasekWyplaty.repx` | `Soneta.Place.Wyplata` | +| „Paski wypłat" (zbiorczy) | `PaskiWyplaty.repx` | `Soneta.Place.ListaPlac` | + +**API (`IReportService` / `ReportResult` — `Soneta.Business.UI`):** +`Stream GenerateReport(ReportResult rr)`, +`ReportResult.TemplateFileName: string`, `.DataType: Type`, +`.OutputFormat: ReportFormats` (`PDF`), `.Context: Context`, `.Target: ReportTargets`. + +**Snippet (pasek jednej wypłaty do strumienia PDF):** + +```csharp +using Soneta.Business.UI; // IReportService, ReportResult, ReportFormats +using Soneta.Place; + +var raporty = session.GetRequiredService(); + +var context = new Context(session.Context); +context.Set(wyplata); // pojedyncza Wyplata + +var rr = new ReportResult { + TemplateFileName = "PasekWyplaty.repx", + DataType = typeof(Wyplata), + OutputFormat = ReportFormats.PDF, + Context = context, +}; + +using Stream pdf = raporty.GenerateReport(rr); // pierwsze 4 bajty == "%PDF" +``` + +**Pułapki:** +- `IReportService` pobierasz z kontenera: `session.GetRequiredService()` + (potrzebne `using Microsoft.Extensions.DependencyInjection;`). Serwis i silnik raportów + (DevExpress) oraz szablony pasków z `Soneta.KadryPlace.Reports` są dostępne **transytywnie** — + generowanie PDF działa bez dodatkowych referencji (wzorzec jak w `dokument-handlowy.md` rozdz. 12). +- Poprawny PDF zaczyna się od bajtów `"%PDF"` — to wygodna asercja w teście. +- Druk na fizyczną drukarkę (`Target = Printer`, `PrintReport`) wymaga sprzętu — NIE testować. + +### I3 — Drukowanie/PDF list płac (★) + +**Cel:** wygenerować wydruk całej listy płac (pełna lista, zestawienie wypłat) do PDF. + +**Mechanizm.** Identyczny jak I2 — `IReportService.GenerateReport`, szablony `[DxReport]` +w `Soneta.KadryPlace.Reports`, dla `DataType = typeof(Soneta.Place.ListaPlac)` / +`typeof(Soneta.Place.ListyPlac)`: + +| Wzorzec (ReportName) | Plik szablonu | `DataType` | +|---|---|---| +| „Pełna lista płac" | `PelnaListaPlac.repx` | `Soneta.Place.ListaPlac` | +| „Wspólna pełna lista płac" | `Wspolnapelnalistaplac.repx` | `Soneta.Place.ListyPlac` (zbiór) | +| „Paski wypłat" | `PaskiWyplaty.repx` | `Soneta.Place.ListaPlac` | +| Zestawienie wypłat | `ZestawienieWyplat.repx` | `Soneta.Place.ListaPlac` | + +**Snippet (pełna lista płac → PDF):** + +```csharp +using Soneta.Business.UI; +using Soneta.Place; + +var raporty = session.GetRequiredService(); + +var context = new Context(session.Context); +context.Set(listaPlac); // ListaPlac + +var rr = new ReportResult { + TemplateFileName = "PelnaListaPlac.repx", + DataType = typeof(ListaPlac), + OutputFormat = ReportFormats.PDF, + Context = context, +}; + +using Stream pdf = raporty.GenerateReport(rr); +``` + +**Pułapki:** +- Mechanizm i dostępność serwisu — jak w I2 (działa transytywnie, bez dodatkowych referencji). +- Lista musi być policzona (mieć `Wyplaty`) — inaczej wydruk będzie pusty. +- **Niektóre szablony list wymagają pełnego kontekstu danych.** W bazie Demo wzorzec + `PelnaListaPlac.repx` potrafi rzucić `InvalidOperationException` („Problem z przygotowaniem + raportu") na sztucznie utworzonej liście — to ograniczenie konkretnego szablonu/kontekstu, nie + brak referencji (pasek wypłaty `PasekWyplaty.repx` z I2 generuje się poprawnie). +- Do wydruku zbiorczego wielu list ustaw `DataType = typeof(Soneta.Place.ListyPlac)` i przekaż + zbiór przez `Context.Set(...)` / `ReportResult.Rows`. + + +### I4 — Generowanie przelewów wynagrodzeń (przygotowanie przelewów) (★) + +**Cel:** z naliczonej, zatwierdzonej listy płac wygenerować dokumenty przelewu wynagrodzeń +(do paczki przelewów), tak by wypłaty pracowników trafiły do zapłaty/preliminarza i mogły zostać +wyeksportowane do banku (I5). + +> **Dwie różne klasy `Wyplata` — nie myl ich.** W domenie współistnieją: +> - **`Soneta.Place.Wyplata`** (moduł `PlaceModule`, tabela `Wyplaty`) — *naliczona wypłata +> pracownika* (wynik naliczania z sekcji H/I1); to dokument **płacowy** ze składnikami +> (`Elementy`), powiązany z listą płac (`Wyplata.ListaPlac`). +> - **`Soneta.Kasa.Wyplata`** (moduł `KasaModule`, tabela `Wyplaty`/`Zaplaty`) — *zapłata kasowa* +> (rozchód środków). To **ona** implementuje `IDokumentPlatny`/`IDokumentKsiegowalny`, ma pola +> rozliczeniowe (`DoRozliczenia`, `Stan`, `StanRozliczenia`, `KwotaRozliczona`, `Rozliczono`, +> `Rozrachunki`, `Zaplaty`, `PreliminarzPoz`, `PozycjePrzelewu`, `BlokadaPrzelewow`). +> +> Mechanizm „z wypłaty do przelewu” łączy oba światy: worker płacowy czyta `Place.Wyplata` z listy +> płac i tworzy dokumenty przelewu w module Kasa (`Soneta.Kasa.PrzelewBase`, w paczce `PaczkaPrzelewow`). + +**Mechanizm (publiczny kontrakt — worker płacowy):** sprawdzoną ścieżką tworzenia przelewów z +wynagrodzeń jest worker **`Soneta.Place.ListaPlac.PrzygotujPrzelewyWorker`** (assembly +`Soneta.KadryPlace`, akcja menu *„Przygotuj przelewy”* na liście/listach płac). Kontekstem +działania jest **lista płac** (`Soneta.Place.ListaPlac`) — przygotowuje przelewy dla zatwierdzonych +wypłat tej listy. + +**Parametry — `PrzygotujPrzelewyWorker.Params`:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Data` | `Soneta.Types.Date` | data dokumentów przelewu | +| `Paczka` | `Soneta.Kasa.PaczkaPrzelewow` | istniejąca paczka, do której trafią przelewy (opcjonalnie) | +| `DefinicjaPaczki` | `Soneta.Kasa.DefinicjaPaczkiPrzelewu` | definicja, wg której utworzyć nową paczkę (gdy `Paczka == null`) | +| `ZRachunku` | `Soneta.Kasa.RachunekBankowyFirmy` | rachunek firmy obciążany przelewami | +| `Łączone` | `bool` | łączenie przelewów do jednego podmiotu w jeden dokument | +| `ListyPłac` | `string` | opis/oznaczenie list płac (informacyjnie w tytule) | +| `ModyfikacjaTytułów` | `bool` | czy nadpisać tytuły przelewu (`Tytułem1`/`Tytułem2`) | +| `Tytułem1`, `Tytułem2` | `string` | tytuł przelewu (gdy `ModyfikacjaTytułów == true`) | +| `ZEwidencjiZrodlowej` | `bool` | bierz dane rachunku z ewidencji źródłowej | + +**Akcja:** `object PrzygotujPrzelewy()` — tworzy w sesji dokumenty `Soneta.Kasa.PrzelewBase` +(tabela `Przelewy`) w paczce `PaczkaPrzelewow`; utrwalenie w bazie wymaga `session.Save()`. + +**Model dokumentu przelewu (`Soneta.Kasa.PrzelewBase`, tabela `Przelewy`, root `GuidedRow`):** + +| Pole | Typ | Opis | +|---|---|---| +| `Kwota` | `Soneta.Types.Currency` | kwota przelewu | +| `Podmiot` | `Soneta.Kasa.IPodmiotKasowy` | odbiorca (m.in. `Pracownik`, `ZUS`, `UrzadSkarbowy`, `Bank`, `Kontrahent`) | +| `Rachunek` | `Soneta.Kasa.RachunekBankowyPodmiotu` | rachunek odbiorcy | +| `RachunekZleceniodawcy` | `Soneta.Kasa.NumerRachunku` | rachunek firmy (obciążany) | +| `Data` | `Soneta.Types.Date` | data przelewu | +| `Definicja` | `Soneta.Core.DefinicjaDokumentu` | definicja dokumentu | +| `Numer` | `Soneta.Core.NumerDokumentu` | numer (nadawany automatycznie) | +| `Tytulem1`, `Tytulem2` | `string` | tytuł przelewu | +| `Typ2` | `Soneta.Kasa.TypPrzelewu2` | wariant przelewu (zwykły / **MPP** / itp.) | +| `PaczkaPrzelewow` | `Soneta.Kasa.PaczkaPrzelewow` | paczka, do której należy przelew | +| `Bufor` / `Zatwierdzony` | `bool` | stan dokumentu | +| `Exported` | `bool` | czy wyeksportowany (po I5 — `true`, blokuje edycję) | + +**Przelewy okresowe / MPP:** +- **MPP (mechanizm podzielonej płatności)** to *wariant* przelewu — wyrażany przez + `PrzelewBase.Typ2: Soneta.Kasa.TypPrzelewu2` (oraz na `Kasa.Wyplata` polem `KwotaMPP`, + `MozliweMechanizmyMPP`). Dla wynagrodzeń MPP zwykle nie dotyczy (to mechanizm faktur VAT), ale + kontrakt go przewiduje. +- **Przelewy okresowe** (cykliczne płatności np. składek z list) realizuje osobny worker + księgowy `Soneta.Ksiega.Kasowe.NaliczaniePrzelewowOkresowych` (poza zakresem płac pracownika). + +**Powiązanie z wypłatą / preliminarzem (publiczne kolekcje na `Pracownik`):** + +| Kolekcja na `Pracownik` | Typ | Zawiera | +|---|---|---| +| `Pracownik.Przelewy` | `SubTable` | przelewy pracownika | +| `Pracownik.DokumentyPreliminarza` | `SubTable` | dokumenty preliminarza | +| `Pracownik.DokumentyRozliczeniowe` | `SubTable` | dokumenty rozliczeniowe | +| `Pracownik.Rozrachunki` | `SubTable` | rozrachunki | +| `Pracownik.Rachunki` | `SubTable` | rachunki bankowe pracownika | + +> **Korekta (zweryfikowane kompilacją + skanem DLL):** `Pracownik.Platnosci` **nie istnieje** w publicznym +> kontrakcie kartoteki pracownika — kolekcja `Platnosci` występuje tylko na interfejsie +> `Soneta.Kasa.IDokumentPlatny` (np. `Kasa.Wyplata.Platnosci`), nie na `Pracownik`. Płatności podmiotu +> czytaj przez `Pracownik.Rozrachunki` / `Pracownik.DokumentyRozliczeniowe`. + +**Snippet (worker — w UI/teście z dostępnym `Context`):** + +```csharp +using Soneta.Business; +using Soneta.Place; // ListaPlac, ListaPlac.PrzygotujPrzelewyWorker +using Soneta.Kasa; // PaczkaPrzelewow, PrzelewBase, RachunekBankowyFirmy +using Soneta.Types; + +// listaPlac: zatwierdzona lista płac z naliczonymi wypłatami (sekcja I1) +var pars = new ListaPlac.PrzygotujPrzelewyWorker.Params +{ + Data = Date.Today, + // Paczka = istniejacaPaczka, // albo nowa wg DefinicjaPaczki: + // DefinicjaPaczki = session.GetKasa().DefPaczekPrzelewow.WgSymbolu["..."], + // ZRachunku = rachunekFirmy, // RachunekBankowyFirmy + Łączone = false, +}; + +var worker = new ListaPlac.PrzygotujPrzelewyWorker { Pars = pars }; +// kontekstem workera jest lista płac; uruchomienie akcji: +worker.PrzygotujPrzelewy(); + +session.Save(); // utrwalenie dokumentów przelewu w bazie +``` + +**Pułapki / ograniczenia (bądź szczery):** +- **`Place.Wyplata` ≠ `Kasa.Wyplata`** — pola rozliczeniowe (`DoRozliczenia`, `Stan`, + `StanRozliczenia`, `Rozrachunki`, `BlokadaPrzelewow`) są na **kasowej** `Soneta.Kasa.Wyplata` + (`IDokumentPlatny`), nie na płacowej. Skanując „Wyplata” trafia się na kasową. +- **Lista płac musi być zatwierdzona i naliczona** — `PrzygotujPrzelewy` na pustej/niezatwierdzonej + liście nie ma czego przelać. +- **Wymaga konfiguracji modułu Kasa** — definicji paczki przelewów (`DefinicjaPaczkiPrzelewu`), + rachunku firmy (`RachunekBankowyFirmy`) oraz rachunku pracownika (`Pracownik.Rachunki`). Brak + rachunku odbiorcy → przelew nie powstanie albo będzie niekompletny. **W bazie Demo te elementy + mogą nie być skonfigurowane**, dlatego generowanie przelewów w teście jednostkowym jest + niepewne (patrz spec testowy). +- Worker **sam zatwierdza zmiany w sesji** (otwiera transakcję) — nie owijaj w dodatkowy + `session.Logout(true)`; do bazy idą w `Save()`. +- `PrzelewBase.Podmiot`/`Powiazanie` to relacje **interfejsowe** (`IRow`/`IPodmiotKasowy`) — + rzutuj świadomie. +- `Przelewy` to tabela operacyjna guided — przy odczycie filtruj zakresem (safe-code §6.3). + +--- + +### I5 — Eksport wynagrodzeń do banku / pliku przelewów (★) + +> **UWAGA — operacja plikowa/integracyjna.** Eksport zapisuje **fizyczny plik** w formacie +> bankowym (Elixir, MT940-pochodne, formaty walutowe). To wejście/wyjście do systemu zewnętrznego — +> **nie jest to przedmiot testu jednostkowego** (zależy od ścieżki na dysku, formatu banku, +> sterownika eksportu i — przy wysyłce online — od sieci). Dokumentujemy **model i publiczny +> kontrakt**, a sam eksport pliku oznaczamy jako nietestowalny jednostkowo. + +**Cel:** wyeksportować przygotowane przelewy (I4) do pliku przelewów dla systemu bankowości +elektronicznej. + +**Mechanizm (publiczny kontrakt — worker Kasa):** worker **`Soneta.Kasa.EksportPrzelewowWorker`** +(akcja menu *„Eksport przelewów”*, metoda `Eksport()`), sterowany przez +`Soneta.Kasa.EksportPrzelewowParams`. + +> **Korekta (zweryfikowane kompilacją):** `EksportPrzelewowParams` **nie ma konstruktora +> bezparametrowego** — wymaga `EksportPrzelewowParams(Context ctx, RachunekBankowyFirmy rachunek, PrzelewBase[] przelewy)`. +> Co więcej, **sam konstruktor waliduje rachunek** i rzuca `System.ApplicationException` +> („Eksport niemożliwy. Nie wskazano rachunku w filtrach listy.”), gdy `rachunek == null`. Dlatego nie da się +> utworzyć parametrów samym inicjalizatorem obiektu. W teście jednostkowym kontrakt API weryfikuj **refleksją** +> (istnienie typu, sygnatura konstruktora, property `FileName`/`Params`, metoda `Eksport`), bez instancjonowania. + +**Parametry — `Soneta.Kasa.EksportPrzelewowParams`:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `FileName` | `string` | **ścieżka pliku wyjściowego** — operacja na dysku | +| `AppendToFile` | `bool` | dopisanie do istniejącego pliku | +| `PrzelewyZgodne` | `IList` | przelewy do wyeksportowania | +| `Rachunek` | `Soneta.Kasa.RachunekBankowyFirmy` | rachunek firmy (zleceniodawca) | +| `PrmDataPrzelewow` | `Soneta.Types.Date` | data realizacji | +| `PrmNumerPaczki` | `string` | numer paczki | +| `PrmZakres` | `Soneta.Kasa.ZakresEksportuPrzelewow` | zakres (wszystkie / wg paczki / zaznaczone) | +| `EksportujWBuforze` | `bool` | uwzględnij przelewy w buforze | +| `InfoBank`, `InfoFormatKraj`, `InfoFormatWalutowy`, `InfoRachunekBankowy` | `string` | parametry formatu/banku | +| `WithoutHelper` | `bool` | tryb bez kreatora | + +**Akcja:** `object Eksport()` — zapisuje plik wg `FileName`. Po eksporcie przelewy są oznaczane +jako wyeksportowane (`PrzelewBase.Exported == true`, blokada dalszej edycji). + +**Powiązane (kontekst):** +- Eksport całych **paczek**: worker `Soneta.Kasa.EksportPaczekPrzelewowWorker`. +- Eksport przelewów PPK z pulpitu KBR: `Soneta.EI.UI.PulpitKBR.Workers.PulpitKBEksportPrzelewowWorker`. + +**Snippet (kontrakt — w realnej integracji, nie w teście jednostkowym):** + +```csharp +using Soneta.Kasa; // EksportPrzelewowWorker, EksportPrzelewowParams, PrzelewBase +using System.Collections.Generic; + +PrzelewBase[] przelewy = /* przelewy z I4, np. z paczki */; + +// Konstruktor jest WYMAGANY (brak ctora bezparametrowego) i waliduje rachunek (rzuca, gdy null): +var par = new EksportPrzelewowParams(context, rachunekFirmy, przelewy) // rachunekFirmy: RachunekBankowyFirmy +{ + FileName = @"C:\przelewy\wynagrodzenia.txt", // ŚCIEŻKA PLIKU — operacja I/O + PrmDataPrzelewow = Date.Today, + EksportujWBuforze = false, +}; + +var worker = new EksportPrzelewowWorker { Params = par }; +worker.Eksport(); // zapis pliku na dysk — efekt uboczny poza sesją +``` + +**Pułapki / ograniczenia (bądź szczery):** +- **Eksport pliku NIE nadaje się do testu jednostkowego** — pisze na dysk, zależy od formatu banku + i sterownika eksportu; w teście co najwyżej dokumentujemy istnienie API + (`EksportPrzelewowWorker`, `EksportPrzelewowParams.FileName`), bez wywołania `Eksport()`. +- Format pliku zależy od **konfiguracji formatu eksportu** danego banku — nie ma jednego + uniwersalnego formatu; `InfoFormat*`/`InfoBank` parametryzują wynik. +- Wysyłka online (bankowość elektroniczna / API banku) to dodatkowo operacja **sieciowa** — poza + zakresem testów jednostkowych. +- Po eksporcie `PrzelewBase.Exported = true` blokuje edycję — ponowny eksport wymaga + `EksportujWBuforze`/zmiany stanu. + +--- + +### I6 — Wystawienie faktury / faktury zbiorczej z zapłaty (rozliczenia) (★) + +> **Zakres i szczerość.** Faktura jest dokumentem **handlowym** (`Soneta.Handel.DokumentHandlowy`), +> nie płacowym — to nie jest funkcja kartoteki pracownika ani list płac. Powiązanie „z zapłaty” +> dotyczy **rozrachunków/rozliczeń** (moduł Kasa): zapłata (`Soneta.Kasa.Wyplata`/`Wplata` — +> `IDokumentPlatny`) jest **rozliczana** z dokumentem płatnym (np. fakturą) przez rozrachunki. +> Wystawianie faktury z poziomu pracownika/płac w publicznym kontrakcie **nie istnieje**; +> tutaj dokumentujemy model rozliczeń, który łączy zapłatę z fakturą. + +**Cel:** powiązać zapłatę z dokumentem płatnym (fakturą) na poziomie rozrachunków/rozliczeń — +oraz wskazać, gdzie w publicznym API leży rozliczanie należności/zobowiązań pracownika. + +**Model rozliczeń (publiczny kontrakt, moduł `KasaModule`):** + +| Element | Typ / kolekcja | Rola | +|---|---|---| +| Zapłata (rozchód/wpływ) | `Soneta.Kasa.Wyplata` / `Soneta.Kasa.Wplata` | dokument płatny (`IDokumentPlatny`) | +| Płatność (zobowiązanie/należność) | `Soneta.Kasa.Platnosc` (tabela `Platnosci`, `IRozliczalny`) | to z nią rozlicza się zapłatę | +| Rozliczenie (powiązanie SP) | `Soneta.Kasa.RozliczenieSP` (tabela `RozliczeniaSP`, `IRozliczenie`) | wiąże zapłatę z płatnością/dokumentem | +| Rozrachunek | `Soneta.Kasa.RozrachunekIdx` (tabela `RozrachunkiIdx`) | indeks rozrachunkowy podmiotu | +| Stan rozliczenia zapłaty | `Wyplata.StanRozliczenia: Soneta.Kasa.StanRozliczenia`, `Wyplata.DoRozliczenia`, `Wyplata.KwotaRozliczona`, `Wyplata.Rozliczono` | ile pozostało / czy rozliczono | + +**Kolekcje na zapłacie (`Soneta.Kasa.Wyplata`):** +- `Wyplata.Zaplaty: SubTable` oraz `Wyplata.Dokumenty: SubTable` — + rozliczenia, +- `Wyplata.Rozrachunki: SubTable` — rozrachunki, +- `Wyplata.PreliminarzPoz: PreliminarzPozycja` — pozycja preliminarza. + +**Kolekcje na `Pracownik` (rozrachunki/faktury podmiotu):** +- `Pracownik.Rozrachunki`, `Pracownik.DokumentyRozliczeniowe`, + `Pracownik.DokumentyPreliminarza` (jak w tabeli I4). **Uwaga:** `Pracownik.Platnosci` **nie istnieje** — + kolekcja `Platnosci` jest tylko na `IDokumentPlatny` (np. `Kasa.Wyplata.Platnosci`). + +**Workery rozliczeniowe (publiczny kontrakt, akcje menu):** + +| Worker | Rola | +|---|---| +| `Soneta.Kasa.RozliczWgPrzelewowWyplataWorker` | rozliczenie zapłaty wg przelewów | +| `Soneta.Kasa.RozliczPreliminarzIdxWorker` / `...TblWorker` / `...FrmWorker` | rozliczenie z preliminarzem | +| `Soneta.Kasa.PreliminarzPozycja.DodajRozliczenieWorker` | dodanie rozliczenia do pozycji preliminarza | +| `Soneta.Ksiega.UtworzPlatnoscZZapisuWorker` | utworzenie płatności z zapisu (księga) | + +**Faktura zbiorcza:** powstaje po stronie **handlowej** — z wielu zapłat/płatności tworzy się jeden +dokument handlowy (faktura) zbiorąc je jako rozliczenia. To domena `dokument-handlowy.md` +(wystawianie i rozliczanie faktur), nie kartoteki pracownika. Z poziomu rozliczeń pracownika +publiczny kontrakt udostępnia **odczyt i rozliczanie** rozrachunków, a nie „wystaw fakturę”. + +**Snippet (odczyt stanu rozliczenia zapłat — publiczny kontrakt):** + +```csharp +using Soneta.Kasa; // Wyplata, StanRozliczenia +using Soneta.Types; + +// Zapłaty pracownika rozliczane z dokumentami (np. fakturami) — odczyt stanu rozliczeń. +// Iteruj zawsze w zakresie/okresie (tabela operacyjna guided — safe-code §6.3). +foreach (RozrachunekIdx r in pracownik.Rozrachunki) +{ + // r — pozycja rozrachunkowa pracownika (powiązanie zapłata ↔ dokument) +} + +// Stan rozliczenia konkretnej zapłaty kasowej: +// Wyplata zaplata = ...; +// var doRozl = zaplata.DoRozliczenia; // ile pozostało do rozliczenia (Currency) +// var stan = zaplata.StanRozliczenia; // StanRozliczenia (enum) +// var czyRozl = zaplata.Rozliczono; // bool +``` + +**Pułapki / ograniczenia (bądź szczery):** +- **„Wystaw fakturę z pracownika/płac” nie istnieje w publicznym kontrakcie.** Faktura to dokument + handlowy; powiązanie z zapłatą realizują **rozrachunki/rozliczenia** (moduł Kasa), nie kartoteka + pracownika. To zadanie jest z pogranicza domen — opis kierujemy do `dokument-handlowy.md`. +- Pola rozliczeniowe (`DoRozliczenia`, `Stan`, `StanRozliczenia`, `KwotaRozliczona`, `Rozliczono`, + `Rozrachunki`) są na **`Soneta.Kasa.Wyplata`** (`IDokumentPlatny`), a nie na płacowej + `Soneta.Place.Wyplata`. +- Rozliczanie/tworzenie faktury zbiorczej **wymaga skonfigurowanego modułu Kasa/Handel** (definicje + dokumentów, rachunki, płatności). W bazie Demo część konfiguracji może nie być gotowa — operacje + zapisujące są niepewne w teście (patrz spec testowy). +- `Platnosc`/`RozliczenieSP`/`RozrachunekIdx` to obiekty operacyjne — przy odczycie filtruj zakresem + i nie skanuj całych tabel (safe-code §6.3). + +--- + +#### Spec testowy (zwarty) — I4 / I5 / I6 + +Konwencja: `Soneta.Skills.Test/KadryPlace/Pracownik/`, klasa `RozdzialI_ListyWydrukiTest` +(lub nowa `RozdzialI_PrzelewyRozliczeniaTest : PracownikTestBase`); baza Demo + rollback; +operujemy wyłącznie na publicznym kontrakcie. + +**I4 — `I4_PrzygotujPrzelewy_ZListyPlac`** +- *Co testowalne:* naliczenie wypłaty etatowej (`NaliczanieSeryjne.Pracownika`, jak I1b) → uzyskanie + `ListaPlac` z `Wyplata.ListaPlac`; **konstrukcja** `ListaPlac.PrzygotujPrzelewyWorker` z `Params` + (asercja, że worker i typ `Params` istnieją w publicznym API; że pola `Data`/`Paczka`/`ZRachunku` + są dostępne). Odczyt kolekcji `Pracownik.Przelewy`, `Pracownik.DokumentyPreliminarza`, + `Pracownik.Rozrachunki` (asercja: kolekcje dostępne, iterowalne). +- *Niepewne / `[Ignore]`/`Assert.Ignore`:* faktyczne **wywołanie** `worker.PrzygotujPrzelewy()` i + powstanie dokumentów `PrzelewBase` — zależy od konfiguracji modułu Kasa (definicja paczki, + `RachunekBankowyFirmy`, rachunek pracownika `Pracownik.Rachunki`), której baza Demo nie gwarantuje. + Owinąć w `try/catch` + `Assert.Ignore` z opisem (wzorzec jak I2/I3) i asercję na powstaniu + przelewu robić tylko, gdy się udało. + +**I5 — `I5_EksportPrzelewow_KontraktApi`** +- *Co testowalne:* **istnienie publicznego API** — weryfikacja **refleksją** (NIE instancjonuj!): + typ `EksportPrzelewowParams`, konstruktor `(Context, RachunekBankowyFirmy, PrzelewBase[])`, + property `FileName`; typ `EksportPrzelewowWorker`, property `Params`, metoda `Eksport`. + **Nie używaj inicjalizatora `new EksportPrzelewowParams { ... }`** — nie ma ctora bezparametrowego, + a ctor `(ctx, rachunek, przelewy)` rzuca `ApplicationException`, gdy `rachunek == null` (brak konfiguracji w Demo). +- *Niewykonalne w teście jednostkowym → `[Ignore]`:* wywołanie `worker.Eksport()` — **operacja + plikowa** (zapis na dysk wg `FileName`), zależna od formatu banku/sterownika; wysyłka online = + operacja sieciowa. **Nie wołać `Eksport()`** w teście; udokumentować jako `[Ignore("operacja + plikowa/sieciowa — poza testem jednostkowym")]`. + +**I6 — `I6_Rozliczenia_OdczytStanu`** +- *Co testowalne:* odczyt kolekcji rozliczeniowych pracownika — `Pracownik.Rozrachunki`, + `Pracownik.DokumentyRozliczeniowe`, `Pracownik.DokumentyPreliminarza` + (asercja: dostępne, iterowalne, typy zgodne — `RozrachunekIdx`, `DokRozliczBase`, + `PreliminarzDokument`). **`Pracownik.Platnosci` NIE istnieje** — pomiń (kolekcja `Platnosci` jest tylko na + `IDokumentPlatny`); odczyt pól rozliczeniowych z `Soneta.Kasa.Wyplata` (`DoRozliczenia`, + `Stan`, `StanRozliczenia`, `Rozliczono`) — gdy istnieje zapłata kasowa w Demo. +- *Niewykonalne / `[Ignore]`:* **wystawienie faktury (zbiorczej) z zapłaty** — funkcja handlowa, + brak w kontrakcie pracownika; rozliczanie zapisujące (`RozliczWgPrzelewowWyplataWorker`, + `RozliczPreliminarz*Worker`) wymaga konfiguracji Kasa/Handel → `Assert.Ignore` przy braku danych. + Dla wystawiania faktur kierować do testów domeny handlowej (`dokument-handlowy.md`). + +**Dokładne nazwy (do użycia w testach):** +- Worker płacowy: `Soneta.Place.ListaPlac.PrzygotujPrzelewyWorker` (+ zagn. `.Params`; + akcja `PrzygotujPrzelewy`). +- Worker eksportu: `Soneta.Kasa.EksportPrzelewowWorker` + `Soneta.Kasa.EksportPrzelewowParams` + (akcja `Eksport`); paczki: `Soneta.Kasa.EksportPaczekPrzelewowWorker`. +- Dokumenty: `Soneta.Kasa.PrzelewBase` (tabela `Przelewy`), `Soneta.Kasa.PaczkaPrzelewow` + (tabela `PaczkiPrzelewow`), `Soneta.Kasa.DefinicjaPaczkiPrzelewu`, `Soneta.Kasa.RachunekBankowyFirmy`. +- Rozliczenia: `Soneta.Kasa.Platnosc`, `Soneta.Kasa.RozliczenieSP`, `Soneta.Kasa.RozrachunekIdx`, + `Soneta.Kasa.PreliminarzDokument`, `Soneta.Kasa.PreliminarzPozycja`. +- Zapłata kasowa (`IDokumentPlatny`): `Soneta.Kasa.Wyplata` (NIE `Soneta.Place.Wyplata`). +- Kolekcje na `Pracownik`: `Przelewy`, `Rozrachunki`, `DokumentyPreliminarza`, + `DokumentyRozliczeniowe`, `Rachunki` (**bez `Platnosci`** — ta kolekcja jest tylko na `IDokumentPlatny`). + +## J. Deklaracje (ZUS, PIT, PFRON, PPK) + +> **Moduł.** `Soneta.Deklaracje.DeklaracjeModule` — dostęp z sesji przez `session.GetDeklaracje()`. +> Wszystkie deklaracje (ZUS, PIT, PFRON, PPK) to wiersze tabeli `Deklaracje`, dziedziczące po +> abstrakcyjnej klasie root `Soneta.Deklaracje.Deklaracja` (`GuidedRow`, implementuje m.in. +> `IDeklaracja`, `IDokumentPlatny`, `IDokumentKsiegowalny`). Konkretne typy żyją w podprzestrzeniach: +> `Soneta.Deklaracje.ZUS.*`, `Soneta.Deklaracje.PIT.*`, `Soneta.Deklaracje.PFRON.*`, +> `Soneta.Deklaracje.PPK.*`. +> +> **Rozróżnienie kluczowe dla testów — NALICZENIE/UTWORZENIE vs E-WYSYŁKA.** +> - **Naliczenie/utworzenie deklaracji** (workery `*Worker` z akcjami „Przygotuj…/Nalicz…/Przelicz”, +> operacje PPK) tworzy **wiersze w bazie** — to operacja lokalna, w zasadzie testowalna na Demo, +> ale **wymaga `Context`** (i dla ZUS zwykle obiektu `KEDU`). Workery nie mają konstruktorów +> bezparametrowych dających pełny kontrakt — `Params` budujemy z `Context`/`Session`. +> - **E-wysyłka** to osobne typy: `EDeklaracja` (tabela `EDeklaracje` — XML, podpis, UPO) oraz +> `ETransmisja` (tabela `ETransmisje` — pojedyncze transmisje do bramki). Eksport KEDU/PUE realizują +> workery `Soneta.Deklaracje.UI.KeduEksportForm.EksportWorker` (akcje „Eksport KEDU”, „Pobierz KEDU”) +> i `Soneta.Deklaracje.UI.PUEEksportForm.EksportWorker` (akcja „Eksport PUE (RUD)”), a uruchomienie +> Programu Płatnika — `Soneta.Deklaracje.ZUS.DeklaracjaZUS.UruchomPPWorker` (akcja +> „Uruchom 'Program Płatnika'”). **To operacje sieciowe/plikowe/zewnętrzne — NIE do testu** (nawet +> utworzenie `EDeklaracja` wymaga podpisu i bramki ZUS/US). +> +> **`KEDU` (`Soneta.Deklaracje.ZUS.KEDU`)** — „zestaw deklaracji”: kontener (komplet dokumentów ZUS), +> do którego workery zgłoszeniowe i rozliczeniowe dopinają wygenerowane bloki. Praktycznie każdy worker +> ZUS przyjmuje `Kedu` w swoich `Params`; bez przekazanego `KEDU` generowanie deklaracji ZUS nie ma +> gdzie zapisać wyniku. KEDU nie jest tworzony „w locie” w sposób trywialny — jest częścią mechanizmu +> deklaracji rozliczeniowych ZUS i jego zbudowanie wymaga środowiska modułu Deklaracje (`Context`). + +--- + +### J1 — Zgłoszenia ZUS (ZUA/ZZA, ZCNA, ZWUA) + +**Cel:** zgłosić/wyrejestrować pracownika i jego umowy w ZUS oraz zgłosić członków rodziny do +ubezpieczenia zdrowotnego. Typy zgłoszeń to wiersze deklaracji: `ZUA` (społeczne + zdrowotne), +`ZZA` (tylko zdrowotne), `ZCNA` (rodzina), `ZWUA` (wyrejestrowanie), `ZIUA` (zmiana danych +identyfikacyjnych), `ZCZA` (zmiana danych członka rodziny) — wszystkie w `Soneta.Deklaracje.ZUS`. + +**Workery — poziom `Pracownicy` (klasy zagnieżdżone `Soneta.Deklaracje.ZUS.ZarejestrujPracownikówWorker`):** + +| Worker (akcja) | `Params` (typ) | Pola `Params` | Metoda akcji | +|---|---|---|---| +| `ZarejestrujPracownikówWorker.Rejestracja` — „Deklaracje ZUS/Przygotuj ZUA i ZZA” | `ZarejestrujBaseWorker.ParamsKor` | `Okres: FromTo`, `DataDokumentu`/`DataWypełnienia: Date`, `Kedu: KEDU`, `KorektaZmiana: ZgloszenieZUS.KorektaZmiana`, `ZarejestrujRodzinę: bool` | `object ZarejestrujPracowników()` | +| `ZarejestrujPracownikówWorker.Rodzina` — „Deklaracje ZUS/Przygotuj ZCNA” | `ZarejestrujBaseWorker.Params` | `Okres`, `DataDokumentu`, `DataWypełnienia`, `Kedu` | `object ZarejestrujRodzinę()` | +| `ZarejestrujPracownikówWorker.Wyrejestrowanie` — „Deklaracje ZUS/Przygotuj ZWUA” | `Wyrejestrowanie.ParamsWR` | `Okres`, `DataDokumentu`, `DataWypełnienia`, `Kedu`, `RIA: bool`, `WyrejestrujRodzinę: bool` | `object WyrejestrujPracowników()` | +| `ZarejestrujPracownikówWorker.ZgloszenieUmow` — „Deklaracje ZUS/Przygotuj RUD” | `ZgloszenieUmow.UParams` | `Okres`, `DataWypełnienia`, `Kedu`, `Trwajace: bool` | `object ZgłośUmowy()` | + +> Worker przyjmuje zaznaczone osoby przez `Pracownicy: Pracownik[]` (`[Context]`). Wszystkie `Params` +> mają ctor `(Context)`. Po akcji wynik (lista wygenerowanych deklaracji) odczytasz z bazowego +> `Deklaracje: View`, a `Save()` zatwierdza. + +**Workery — poziom `Umowy` (zleceniobiorcy), `Soneta.Deklaracje.ZUS.ZarejestrujUmowyWorker`** — +opisane w **G5** (`Rejestracja.ZarejestrujUmowy()` → ZUA/ZZA wg schematu `UmowaHistoria.Ubezpieczenia`, +`Wyrejestrowanie.WyrejestrujUmowy()` → ZWUA). `ParamsZ`/`ParamsW` mają ctor `(Context)`; pola +bazowe `Okres`/`DataDokumentu`/`DataWypełnienia`/`Kedu` + `ZarejestrujRodzinę`/`WyrejestrujRodzinę`. + +**ZCNA na rodzinie (A9).** Zgłoszenie członka rodziny do ubezpieczenia zdrowotnego startuje z danych +`CzlonekRodziny` (`Ubezpieczony = true`, `UbezpieczenieOkres`, `StPokrewienstwa` — patrz A9), a samą +deklarację ZCNA generuje `ZarejestrujPracownikówWorker.Rodzina` (lub `Rejestracja` z +`Pars.ZarejestrujRodzinę = true`). Dla zleceniobiorcy analogicznie przez `ZarejestrujUmowyWorker`. + +**Przerejestrowanie (A19).** `Soneta.Deklaracje.UI.PrzerejestrowaniePracownikaWorker` (DataType +`PracHistoria`) oraz `Soneta.Deklaracje.UI.PrzerejestrowanieZleceniobiorcyWorker` (DataType +`UmowaHistoria`) — generują ZWUA+ZUA przy zmianie tytułu/wydziału. `Params` wymaga `KEDU` + `Context`. + +**Snippet (przygotowanie ZUA/ZZA dla zaznaczonych pracowników):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +var pars = new Soneta.Deklaracje.ZUS.ZarejestrujBaseWorker.ParamsKor(context) +{ + Okres = new FromTo(new Date(2026, 1, 1), Date.MaxValue), + DataDokumentu = new Date(2026, 1, 1), + DataWypełnienia = Date.Today, + Kedu = kedu, // KEDU z modułu Deklaracje (Context) + ZarejestrujRodzinę = false, +}; +var rejestracja = new Soneta.Deklaracje.ZUS.ZarejestrujPracownikówWorker.Rejestracja +{ + Pracownicy = new[] { pracownik }, + Pars = pars, +}; +rejestracja.ZarejestrujPracowników(); // tworzy ZUA/ZZA (i ZCNA, gdy ZarejestrujRodzinę) +session.Save(); +``` + +**Pułapki:** +- Typ zgłoszenia (ZUA vs ZZA) wynika ze schematu ubezpieczeń (`Etat.Ubezpieczenia` / `UmowaHistoria.Ubezpieczenia`, + A7/G5) — nie z parametru workera. Ustaw `Tyub4` i flagi `Spoleczne`/`Zdrowotne` przed zgłoszeniem. +- Każdy `Params` wymaga `Context` (ctor `(Context)`) i pola `Kedu` — bez `KEDU` deklaracja nie ma + kontenera docelowego. Operacja jest **lokalna** (zapis wiersza), ale niewykonalna bez `Context`/`KEDU`. +- `ZWUA` z `RIA = true` powiązany jest z mechanizmem RIA (J2). +- Workery zgłoszeniowe na `Pracownicy` dotyczą etatowych; na `Umowy` — zleceniobiorców (G5). + +--- + +### J2 — Deklaracje rozliczeniowe ZUS (DRA, RIA, IMIR, RUD, IWA; KEDU) + +**Cel:** naliczyć/utworzyć deklaracje rozliczeniowe i informacyjne ZUS. Typy (`Soneta.Deklaracje.ZUS`, +wiersze tabeli `Deklaracje`): `DRA` (deklaracja rozliczeniowa z załącznikami RCA/RSA/RZA; ctor `(KEDU)`), +`RIA` (informacja roczna / raport po ustaniu zatrudnienia; ctor `(Pracownik, KEDU)`), `RMUA` — +informacja miesięczna dla ubezpieczonego, potocznie **IMIR** (ctor `(Pracownik, RMUA.TypOkresuDeklaracji)`; +**brak osobnego typu `IMIR` w CLR — to `RMUA`**), `RUD` (zgłoszenie umowy o dzieło), `IWA` (informacja o wypadkach/składce wypadkowej), +`OSW` (oświadczenie), `Z3`/`Z3a` (zaświadczenia płatnika ERP-7 — patrz niżej), `KEDU` (zestaw). + +**Naliczanie seryjne — poziom `Pracownicy`:** + +| Worker (akcja) | `Params` (typ) | Pola `Params` | Metoda | +|---|---|---|---| +| `Soneta.Deklaracje.ZUS.NaliczanieSeryjneRIAWorker` — „Deklaracje ZUS/Przygotuj RIA” | `…RIAWorker.Params` | `DataDokumentu`/`DataWypełnienia: Date`, `Kedu: KEDU`, `Wydział: Wydzial`, `Wszystkie: bool`, `Zerowa: bool` | `object NaliczRMUA(Context)` | +| `Soneta.Deklaracje.ZUS.NaliczanieSeryjneRMUAWorker` — „Deklaracje ZUS/Przygotuj IMIR” | `…RMUAWorker.Params` | `DataDokumentu`/`DataWypełnienia: Date`, `Miesiac: YearMonth`, `Rok: int`, `TypOkresu: RMUA.TypOkresuDeklaracji`, `Oskladkowani: bool`, `Wydział`, `Wszystkie` | `object NaliczRMUA(Context)` | + +> Oba workery mają **ctor bezparametrowy**, przyjmują `Pracownicy: Pracownik[]` (`[Context]`) i mają w props +> `Context`, `Kedu`, `Deklaracje: View`. Metoda akcji `NaliczRMUA(Context)` (ta sama nazwa dla RIA i RMUA). +> `Params` są property `Pars` (setter); na workerze `RMUAWorker` pola `Params` są też wystawione bezpośrednio jako property. + +**Przeliczenie pojedynczej deklaracji — `Soneta.Deklaracje.DeklaracjaWorker`** (DataType `Deklaracja`, +więc działa dla **dowolnej** deklaracji ZUS/PIT/PFRON): akcja **„Przelicz”** → `void Przelicz()`; +parametr `Deklaracja: Soneta.Deklaracje.Deklaracja` (`[Context]`). + +**RUD** generuje `ZarejestrujPracownikówWorker.ZgloszenieUmow` (J1) lub jest dostępna na liście umów. +**DRA z załącznikami** to root `DeklaracjaZUS`; nalicza się przez mechanizm KEDU + `Przelicz`. + +**E-wysyłka (NIE testować):** eksport KEDU — `KeduEksportForm.EksportWorker` („Eksport KEDU”, +„Pobierz KEDU”); eksport PUE/RUD — `PUEEksportForm.EksportWorker` („Eksport PUE (RUD)”); Program +Płatnika — `DeklaracjaZUS.UruchomPPWorker`. + +**Pułapki:** +- `KEDU` jest osią całego rozliczenia ZUS — wszystkie workery rozliczeniowe wpisują wynik do + przekazanego `Kedu`. Bez modułu Deklaracje (`Context`) i `KEDU` operacji nie złożysz. +- `DeklaracjaWorker.Przelicz()` przelicza **istniejący** wiersz deklaracji — najpierw musi powstać + (np. z naliczania seryjnego), więc to nie jest „utworzenie od zera”. + +--- + +### J3 — Deklaracje PIT (PIT-11, PIT-4R, PIT-8AR, PIT-R, IFT-1/IFT-1R, PIT-8C) + +**Cel:** naliczyć imienne i zbiorcze deklaracje podatkowe. Typy (`Soneta.Deklaracje.PIT`, wiersze +tabeli `Deklaracje`): `PIT11`, `PIT4`/PIT-4R (rozliczeniowa zaliczek), `PIT8A`/PIT-8AR (zryczałtowany), +`PITR` (PIT-R), `IFT1`/`IFT1R`, `PIT8C`, `PIT40`, plus `ZbiorczaPIT`/`IEDeklaracjaZbiorczaItem` +(deklaracje zbiorcze). + +**Naliczanie seryjne — poziom `Pracownicy` (klasy zagnieżdżone `Soneta.Deklaracje.PIT.NaliczanieSeryjne`):** + +| Worker (akcja) | Ctor | `Params` — pola | Metoda | +|---|---|---|---| +| `NaliczanieSeryjne.PIT_11Worker` — „Deklaracje PIT/Nalicz PIT 11” | `(Session session)` | `Okres: FromTo`, `Data: Date`, `Naliczaj: NaliczanieDeklaracje`, `BezPotwierdzenia: bool`, dane podpisującego (`Imię`/`Nazwisko`/`Stanowisko` + `…Odp`), `TreśćUzasadnienia: string` | `object Nalicz_PIT_11()` | +| `NaliczanieSeryjne.PIT_RWorker` — „Deklaracje PIT/Nalicz PIT R” | `(Session)` | jw. (`Params`) | `Nalicz…()` | +| `NaliczanieSeryjne.PIT_8CWorker` — „Deklaracje PIT/Nalicz PIT 8C” | `(Session)` | jw. | `Nalicz…()` | +| `NaliczanieSeryjne.IFT_1Worker` / `IFT_1RWorker` — „Deklaracje PIT/Nalicz IFT-1 / IFT-1R” | `(Session)` | jw. | `Nalicz…()` | + +> `Params` mają ctor `(Context)`; worker `PIT_11Worker` dodatkowo ma ctor `(Session)`. Zaznaczeni +> pracownicy przez `[Context]`. + +**Deklaracje płatnika (PIT-4R/PIT-8AR)** są zbiorcze na poziomie podmiotu/oddziału (`PIT4`/`PIT8A`, +`ZbiorczaPIT`) — tworzone/dodawane workerami zbiorczymi (`DodajDoZbiorczejPITWorker`, +`WybierzDeklaracjeDoZbiorczejPITWorker`) i przeliczane `DeklaracjaWorker.Przelicz()` (J2) lub +dedykowanymi `…PrzeliczWorker` (np. `PITR.PrzeliczWorker`, `PIT8S.PrzeliczWorker`). + +**Snippet (naliczenie PIT-11 dla zaznaczonych pracowników):** + +```csharp +var pracownicy = new[] { session.GetKadry().Pracownicy.WgKodu["006"] }; + +var worker = new Soneta.Deklaracje.PIT.NaliczanieSeryjne.PIT_11Worker(session) +{ + Pracownicy = pracownicy, +}; +worker.Pars.Okres = FromTo.Year(2025); // rok podatkowy +worker.Pars.Data = Date.Today; +worker.Nalicz_PIT_11(); // tworzy wiersze PIT11 w tabeli Deklaracje +session.Save(); +``` + +**Pułapki:** +- Naliczenie PIT bazuje na naliczonych wypłatach (H) i bilansach otwarcia PIT (J6) — bez danych + źródłowych deklaracja będzie zerowa. +- Sygnatury `Params` PIT mają ctor `(Context)`; `PIT_11Worker` ma też ctor `(Session)` — w teście + użyj `(session)` + ustaw `Pracownicy`/`Pars`. +- **E-wysyłka PIT to `EDeklaracja`/`ETransmisja` (bramka MF) — NIE testować.** Samo naliczenie + wiersza PIT jest lokalne (zapis do bazy). + +--- + +### J4 — Deklaracje PFRON (Wn-D, INF-2, DEK-R, INF-D-P) + +**Cel:** utworzyć/naliczyć deklaracje PFRON. Typy (`Soneta.Deklaracje.PFRON`, wiersze tabeli +`Deklaracje`): `WN_D` (Wn-D — wniosek o dofinansowanie), `WN_U` (Wn-U), `INF_D`/`INF_D_P` +(informacje o pracownikach niepełnosprawnych — załączniki do Wn-D), `INF_2` (informacja roczna), +`DEK_R` (deklaracja roczna wpłat). + +**Workery:** +- `Soneta.Deklaracje.DeklaracjaWorker` — akcja **„Przelicz”** (`Przelicz()`) dla każdego z typów PFRON + (są DataType `Deklaracja`). +- `Soneta.Deklaracje.PFRON.INF_D.InfoWorker`, `…INF_D_P.InfoWorker` — properties informacyjne (UI). +- **E-wysyłka SOD (NIE testować):** `Soneta.Deklaracje.UI.SODEksportForm.EksportWorker` (DataType + `WN_D`/`WN_U`/`INF_D`) — eksport do systemu SODiR. + +**Dane źródłowe** PFRON pochodzą z `PracHistoria.PFRON` (A13: stopień niepełnosprawności, efekt +zachęty, schorzenia SOD) — bez nich deklaracja będzie pusta. + +**Pułapki:** +- PFRON nie ma dedykowanego „NaliczanieSeryjne” na `Pracownicy` — deklarację (`WN_D` itd.) tworzy się + w module Deklaracje, a przelicza `DeklaracjaWorker.Przelicz()`. Tworzenie/edycja wymaga `Context`. +- Konfiguracja procentów/odpisu PFRON to workery na `OddzialFirmy` + (`Soneta.Deklaracje.Config.*PFRON*Worker`) — to dane konfiguracyjne, nie deklaracje. + +--- + +### J5 — Operacje PPK + +**Cel:** obsłużyć cykl życia uczestnictwa w PPK — kwalifikacja/auto-zapis, rejestracja uczestnika, +rezygnacja, wznowienie, zmiana danych, zakończenie zatrudnienia, dokumenty i rozliczenie składek. +Typy dokumentów PPK (`Soneta.Deklaracje.PPK`, wiersze tabeli `Deklaracje`): `RejestracjaUczestnikaPPK`, +`DeklaracjaUczestnikaPPK`, `ZmianaDanychIdentyfikacyjnychUczestnikaPPK`, +`ZmianaDanychKontaktowychUczestnikaPPK`, `ZakończenieZatrudnieniaUczestnikaPPK`, `TransferPPK`, +`WypłataTransferowaPPK`, `WypłataŚrodkówPrzezUczestnikaPPK`, `ZwrotŚrodkówPPK`, `RozliczenieSkładekPPK`, +`RozliczenieNadpłatPPK`, `ZwrotNadpłatyPPK`, `NadanieUczestnikowiNumeruPPK`, +`DokumentyPracodawcyPPK`, `DokumentyInstytucjiFinansowejPPK`. + +**Workery operacji PPK — poziom `Pracownicy` (zagnieżdżone `Soneta.Deklaracje.PPK.DeklaracjePPKPracownikówWorker`), +wspólny `Params = DeklaracjePPKBaseWorker.Params` (`Okres: FromTo`, `DokumentPPK: DokumentyPracodawcyPPK`):** + +| Worker (akcja) | Metoda | +|---|---| +| `…Worker.Rejestracja` — „Operacje PPK/Rejestracja uczestnika” | `object RejestracjaPracownikow()` | +| `…Worker.Rezygnacja` — „Operacje PPK/Rezygnacja uczestnika” | `object RezygnacjaPracownikow()` | +| `…Worker.Wznowienie` — „Operacje PPK/Automatyczne wznowienie uczestnictwa” | `object WznowieniePracownikow()` | +| `…Worker.ZakończenieZatrudnienia` — „Operacje PPK/Zakończenie zatrudnienia uczestnika” | `object ZakończenieZatrudnieniaPracownikow()` | +| `…Worker.ZmianaDanychIdentyfikacyjnych` — „Operacje PPK/Zmiana danych identyfikacyjnych” | `object ZmianaDanychIdentyfikacyjnychPracownikow()` | + +> Przystąpienie/auto-zapis i zmiana procentu składki realizowane są na poziomie **pracownika** +> (dane PPK pracownika), nie tymi workerami zbiorczymi. + +**Workery na pracowniku (kwalifikacja PPK) — `Soneta.Kadry.Pracownik`:** + +| Worker | Ctor | Wybrane pola/props | +|---|---|---| +| `Pracownik.PPKWorker` (alias `PPK`) | `(Context context)` | `Data: Date`, `Idx: Pracownik`; `Kwalifikacja: PPKWorker.RodzajZgłoszenia`, `DataKwalifikacji[/Min/Max]: Date`, `Kwalifikacja[Min/Max]` | +| `Pracownik.AutoZapisPPKWorker` (alias `AutoZapisPPK`) | `(Context context)` | `Data: Date`, `Pracownik: Pracownik`; `Kwalifikacja: AutoZapisPPKWorker.CzyAutoZapisPPK` | + +> Te workery służą do **odczytu kwalifikacji** (czy/kiedy pracownik podlega przystąpieniu lub +> auto-zapisowi do PPK na dany dzień) — mają ctor `(Context)`. + +**Przeliczanie/rozliczenie PPK:** +- `Soneta.Deklaracje.PPK.PrzeliczPPKWorker` (DataType m.in. `RozliczenieNadpłatPPK`, + `WypłataTransferowaPPK`, `WypłataŚrodkówPrzezUczestnikaPPK`, `ZwrotŚrodkówPPK`, + `NadanieUczestnikowiNumeruPPK`) — przelicza dokument rozliczeniowy PPK. +- `Soneta.Deklaracje.PPK.NadanieNumeruPPKWorker` (DataType `NadanieUczestnikowiNumeruPPK`). +- `RozliczenieSkładekPPK` / `RejestracjaUczestnikaPPK` / `DeklaracjaUczestnikaPPK` przeliczane przez + `DeklaracjaWorker.Przelicz()` (J2, DataType `Deklaracja`). + +**E-wysyłka / import-eksport PPK (NIE testować):** +- `Soneta.Deklaracje.PPK.DokumentyPPKEksportWorker` (DataType `DokumentyPracodawcyPPK`, + `DokumentyInstytucjiFinansowejPPK`) — eksport do instytucji finansowej. +- `Soneta.Deklaracje.PPK.DokumentyPPKImportWorker` (DataType `DokumentyInstytucjiFinansowejPPK`) — + import zwrotny. + +**Snippet (rejestracja uczestnika PPK dla zaznaczonych):** + +```csharp +var pracownicy = new[] { session.GetKadry().Pracownicy.WgKodu["006"] }; + +var pars = new Soneta.Deklaracje.PPK.DeklaracjePPKBaseWorker.Params(context) +{ + Okres = FromTo.Year(2026), + // DokumentPPK = … (DokumentyPracodawcyPPK z modułu Deklaracje) +}; +var rej = new Soneta.Deklaracje.PPK.DeklaracjePPKPracownikówWorker.Rejestracja +{ + Pracownicy = pracownicy, + Pars = pars, +}; +rej.RejestracjaPracownikow(); // tworzy dokumenty rejestracji uczestnika PPK +session.Save(); +``` + +**Pułapki:** +- Zmiana procentu składki PPK / przystąpienie to dane **pracownika** (deklaracja uczestnika PPK, + `DeklaracjaUczestnikaPPK`) — workery zbiorcze obejmują rejestrację, rezygnację, wznowienie, zmianę + danych identyfikacyjnych i zakończenie zatrudnienia. +- `DeklaracjePPKBaseWorker.Params` ma ctor `(Context)`; operacja jest lokalna (tworzy wiersze + dokumentów PPK), ale niewykonalna bez `Context` i zwykle `DokumentPPK`. +- `PPKWorker`/`AutoZapisPPKWorker` na pracowniku są **diagnostyczne** (kwalifikacja na dzień), nie + tworzą dokumentów — i wymagają `Context`. + +--- + +### J6 — Bilanse otwarcia deklaracji (PIT, ZUS, ERP-7) przy wdrożeniu + +**Cel:** wprowadzić dane historyczne sprzed startu systemu, potrzebne do poprawnego naliczenia +deklaracji w pierwszym okresie. Bilanse są **kolekcjami na pracowniku** (`SubTable`) — tworzy się je +i odczytuje czystym API biznesowym, **bez `Context`/`KEDU`/sieci**. + +**Kolekcje na `Soneta.Kadry.Pracownik`:** + +| Kolekcja | Typ | Przeznaczenie | +|---|---|---| +| `Pracownik.BilansyOtwarciaPIT` | `SubTable` | bilans otwarcia PIT (przychody/koszty/składki na start) | +| `Pracownik.WynagrodzeniaERP7` | `SubTable` | wynagrodzenia do ERP-7 / Z-3 | +| `Pracownik.NieobecnosciERP7` | `SubTable` | nieobecności do ERP-7 / Z-3 | +| `Pracownik.DeklaracjePodmiotu` | `SubTable` | deklaracje powiązane z pracownikiem-podmiotem | + +**Typ `Soneta.Place.BilansOtwarciaPIT`** (root `GuidedRow`, tabela `BilansyOtwPIT`) jest +**ABSTRAKCYJNY** — instancjonuje się jedną z konkretnych wersji odpowiadających wartościom enuma +`Soneta.Place.WersjaBilansuOtwarciaPIT` (`PIT11_11`, `PIT11_29`): +`Soneta.Place.BilansOtwarciaPIT_11` (Wersja = `PIT11_11`) lub `Soneta.Place.BilansOtwarciaPIT_29` +(Wersja = `PIT11_29`). Konkretne klasy mają publiczny ctor `(Pracownik pracownik)`; bazowy +`BilansOtwarciaPIT` ma ctor `(Pracownik, WersjaBilansuOtwarciaPIT)`, ale jest abstrakcyjny. +Property `Pracownik` i `Wersja` są **read-only** (ustawiane przez ctor; brak ctora bezparametrowego). +Pola bazodanowe m.in.: `Data: Date`, kwoty przychodów/kosztów/składek w rozbiciu +etat/umowa/macierzyński (`Przychod26ZwolEtat`, `Przychod26ZwolUmowa`, `PrzychodUlgaEtat`, +`PrzychodUlgaUmowa`, `Spoleczne`, `Spoleczne26`, `Zdrowotne9Procent`, `SkladkiCzlonkowskie` itd.) +oraz kolekcja `Elementy: SubTable`. + +**ERP-7** (wcześniej druk ZUS Rp-7) opiera się na `WynagrodzeniaERP7`/`NieobecnosciERP7` pracownika +oraz zaświadczeniach `Soneta.Deklaracje.ZUS.Z3`/`Z3a` (workery `ZUSZ3.Z3Worker`/`Z3aWorker` na +`Nieobecnosc`) — sam druk Z-3/ERP-7 to generowanie dokumentu w module Deklaracje. + +**Snippet (dodanie bilansu otwarcia PIT i odczyt):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +using (var t = session.Logout(editMode: true)) +{ + // BilansOtwarciaPIT jest abstrakcyjny — tworzymy konkretną wersję (_29 => PIT11_29, _11 => PIT11_11). + // Pracownik ustawia ctor (property read-only), więc NIE używamy inicjalizatora obiektu na Pracownik. + var bo = session.AddRow(new Soneta.Place.BilansOtwarciaPIT_29(pracownik)); + bo.Data = new Date(2026, 1, 1); + bo.PrzychodUlgaEtat = 12000m; + bo.Spoleczne = 1645.20m; + t.Commit(); +} +session.Save(); + +// Odczyt bilansów otwarcia PIT pracownika (typ kolekcji: SubTable): +foreach (Soneta.Place.BilansOtwarciaPIT bo in pracownik.BilansyOtwarciaPIT) +{ + // bo.Data, bo.PrzychodUlgaEtat, bo.Spoleczne, bo.Wersja +} +``` + +**Pułapki:** +- `BilansOtwarciaPIT` ma kolekcję `Elementy` — niektóre kwoty są wyliczane z elementów; sprawdź na + Demo, czy ustawiasz pola root, czy elementy. +- Bilanse są **danymi wdrożeniowymi** (jednorazowe na start) — nie myl z naliczonymi deklaracjami. +- ERP-7 (Z-3/Z-3a) wymaga modułu Deklaracje i `KEDU`/PUE do eksportu — samo wprowadzenie + `WynagrodzeniaERP7`/`NieobecnosciERP7` jest lokalne, ale wygenerowanie druku — nie. + +## K. Ewidencje pracownicze + +> **Wzorzec wspólny.** Wszystkie ewidencje pracownicze to **kolekcje `SubTable` na rootcie +> `Pracownik`** (nie na `PracHistoria`). Każdy element jest osobnym `GuidedRow` (child pracownika) +> z polem `Pracownik: Soneta.Kadry.Pracownik` ustawianym automatycznie przez konstruktor +> `new Xxx(pracownik)`. Schemat dodania jest jednolity: +> +> ```csharp +> using (var t = session.Logout(editMode: true)) { +> var wpis = session.AddRow(new Xxx(pracownik)); // ctor wiąże wpis z pracownikiem +> // ... ustaw pola ... +> t.Commit(); // Commit() w kodzie biznesowym +> } +> session.Save(); +> ``` +> +> `session.AddRow(new Xxx(pracownik))` i `pracownik.Kolekcja.AddRow(new Xxx(pracownik))` są +> równoważne — wpis trafia do tej samej tabeli i do `SubTable` pracownika. Większość typów wymaga +> wskazania **definicji** (rekord słownikowy, tabela konfiguracyjna) — definicję pobierasz przez +> `WgNazwy[...]` z odpowiedniego modułu, **nie** tworzysz jej w teście operacyjnym. + +| Ewidencja | Kolekcja na `Pracownik` | Typ elementu | Tabela | +|---|---|---|---| +| K1 Badania lekarskie | `BadaniaLekarskie: SubTable` | `Soneta.Kadry.BadanieLekarskie` | `BadaniaLekarskie` | +| K2 Szkolenia BHP | `SzkoleniaBHP: SubTable` | `Soneta.Kadry.SzkolenieBHP` | `SzkoleniaBHP` | +| K3 Wnioski o szkolenia | `WnioskiOSzkolenia: SubTable` | `Soneta.HR.WniosekOSzkolenie` | `WnioskiOSzkol` | +| K3 Ukończone szkolenia | `UkończoneSzkolenia: SubTable` | `Soneta.HR.UkończoneSzkolenie` | `UkonczSzkolenia` | +| K3 Uprawnienia | `Uprawnienia: SubTable` | `Soneta.HR.UprawnieniePracownika` | `UprawnieniaPrac` | +| K4 Nagrody i kary | `NagrodyKary: SubTable` | `Nagroda` / `Kara` (`NagrodaKara` abstr.) | `NagrodyKary` | +| K4 Oświadczenia | `Oświadczenia: SubTable` | `Soneta.Kadry.OświadczeniePracownika` | `OswiadczeniaPrac` | +| K5 Wypadki przy pracy | `Wypadki: SubTable` | `Soneta.Kadry.Wypadek` | `Wypadki` | + +--- + +### K1 — Badania lekarskie + +**Cel:** zarejestrować badanie lekarskie pracownika (wstępne/okresowe/kontrolne) wraz z terminami +ważności i datą następnego badania; ewentualnie wykonać operację seryjną dla grupy osób. + +**Mechanizm:** `BadanieLekarskie` ma publiczny konstruktor `BadanieLekarskie(Pracownik pracownik)`. +Wpis wymaga `Definicja: DefinicjaBadaniaLekarskiego` (słownik, tabela konfiguracyjna `DefBadanLek`, +pobierana przez `WgNazwy[...]`). Jeśli definicja jest **cykliczna** (`Definicja.Cykliczne == true`, +ma `NastepneDefinicja`/`NastepneTermin`), platforma wylicza termin kolejnego badania — +udostępniony jako wyliczane `NastępneTermin`/`NastępneDefinicja`. + +**Pola i typy (rekord `BadanieLekarskie`):** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Definicja` | `Soneta.Kadry.DefinicjaBadaniaLekarskiego` | wymagana; słownik `DefBadanLek` | +| `Data` | `Soneta.Types.Date` | data wykonania badania | +| `Termin` | `Soneta.Types.Date` | termin badania — **read-only** (wyliczany z `Data`+definicji); ustawienie rzuca `ColReadOnlyException` | +| `WazneDo` | `Soneta.Types.Date` | „Ważne do" — koniec ważności (ustawialny) | +| `PracaWOkularach` | `bool` | adnotacja medyczna | +| `KwotaDofinansowania` | `decimal`, `DataDofinansowania: Date` | dofinansowanie badania | +| `Opis` | `Soneta.Business.MemoText` | opis/uwagi | +| `Anulowany` | `bool` | flaga anulowania | +| `Pracownik` | `Soneta.Kadry.Pracownik` | ustawiany przez ctor | +| `NastępneTermin`, `NastępneDefinicja`, `Następne` | (wyliczane) | termin/def./wpis następnego badania | + +**Manager:** `pracownik.Badania: Pracownik.BadaniaLekarskieManager` — pomocnik tylko do odczytu; +`pracownik.Badania.ZNajkrótszymTerminem(definicja = null): BadanieLekarskie` zwraca badanie z +najbliższym terminem wygaśnięcia (do raportów „badania okresowe do wykonania"). + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["006"]; +var definicja = kadry.DefBadanLek.WgNazwy["Wstępne"]; // słownik konfiguracyjny + +using (var t = session.Logout(editMode: true)) +{ + var badanie = session.AddRow(new BadanieLekarskie(pracownik)); // ctor wiąże z pracownikiem + badanie.Definicja = definicja; + badanie.Data = Date.Today; + // UWAGA: badanie.Termin jest read-only (wyliczany) — NIE ustawiaj go ręcznie. + badanie.WazneDo = new Date(Date.Today.Year + 2, Date.Today.Month, Date.Today.Day); + + t.Commit(); +} +session.Save(); +``` + +**Operacja seryjna (grupa pracowników):** w warstwie UI istnieje worker +`DodajBadaniaLekarskieWorker` (warianty `ZListyBadań`, `ZListyPracowników`) z akcją menu +„Operacje seryjne/Dodaj badania lekarskie" — iteruje po wybranych pracownikach i dla każdego robi +`new BadanieLekarskie(pracownik)` + `BadaniaLekarskie.AddRow(...)`. W kodzie biznesowym +seryjność realizujesz tą samą pętlą `foreach (var p in wybrani) { … AddRow … }` w jednej transakcji. + +**Pułapki:** +- `Definicja` jest **wymagana** — bez niej `Save()` rzuci `RowException`. +- `Data`/`WazneDo` to `Soneta.Types.Date`, nie `DateTime`. `Termin` jest **read-only** (wyliczany) — + próba ustawienia rzuca `ColReadOnlyException`. Reguła w weryfikatorach: `WazneDo` nie może być + wcześniejsze niż `Termin`; termin następnego badania musi być **późniejszy** niż termin badania + bieżącego — naruszenie wybucha jako `RowException` przy zapisie. +- `pracownik.Badania` to manager (odczyt), a kolekcją CRUD jest `pracownik.BadaniaLekarskie` + (`SubTable`). Nie myl tych dwóch. + +--- + +### K2 — Szkolenia BHP + +**Cel:** zarejestrować odbyte szkolenie BHP (wstępne/okresowe) z terminem ważności i datą szkolenia +następnego (analogicznie do badań lekarskich). + +**Mechanizm:** konstruktor `SzkolenieBHP(Pracownik pracownik)`; kolekcja `pracownik.SzkoleniaBHP`. +Wymagana `Definicja: DefinicjaSzkoleniaBHP` (słownik konfiguracyjny `DefSzkolenBHP`, `WgNazwy[...]`). +Cykliczność (`Definicja.Cykliczne`) wylicza `NastępneTermin`/`NastępneDefinicja`. + +**Pola i typy (rekord `SzkolenieBHP`):** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Definicja` | `Soneta.Kadry.DefinicjaSzkoleniaBHP` | wymagana; słownik `DefSzkolenBHP` | +| `Data` | `Soneta.Types.Date` | data szkolenia | +| `Termin` | `Soneta.Types.Date` | termin — **read-only** (wyliczany); ustawienie rzuca `ColReadOnlyException` | +| `WażneDo` | `Soneta.Types.Date` | koniec ważności (wyliczane) | +| `Zakres` | `string` | zakres szkolenia | +| `Osoba` | `string` | prowadzący / osoba szkoląca | +| `Opis` | `Soneta.Business.MemoText` | uwagi | +| `Anulowany` | `bool` | flaga anulowania | +| `Pracownik` | `Soneta.Kadry.Pracownik` | ustawiany przez ctor | +| `NastępneTermin`, `NastępneDefinicja`, `Następne` | (wyliczane) | następne szkolenie | + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["007"]; +var definicja = kadry.DefSzkolenBHP.WgNazwy["Wstępne"]; + +using (var t = session.Logout(editMode: true)) +{ + var szkolenie = session.AddRow(new SzkolenieBHP(pracownik)); + szkolenie.Definicja = definicja; + szkolenie.Data = Date.Today; + // UWAGA: szkolenie.Termin jest read-only (wyliczany) — NIE ustawiaj go ręcznie. + szkolenie.Zakres = "Instruktaż ogólny"; + + t.Commit(); +} +session.Save(); +``` + +**Operacja seryjna:** UI udostępnia `DodajSzkolenieBHPWorker` (akcja menu, lista pracowników) — +w kodzie biznesowym pętla `foreach` + `new SzkolenieBHP(p)` + `AddRow` w jednej transakcji. + +**Pułapki:** +- `Definicja` wymagana (jak w K1). +- Uwaga na pisownię: pole nazywa się `WażneDo` (z „ż"), a w `BadanieLekarskie` — `WazneDo` (bez). +- `Termin` jest **read-only** (wyliczany) — ustawienie rzuca `ColReadOnlyException`. +- `Termin` następnego szkolenia musi być późniejszy niż bieżący — inaczej `RowException`. + +--- + +### K3 — Szkolenia i uprawnienia (moduł HR/HR2) + +**Cel:** obsłużyć cykl rozwoju kompetencji: **wniosek o szkolenie** → **ukończone szkolenie** → +**uprawnienie/certyfikat**, wraz z kosztem i budżetem szkoleń. Typy leżą w module `Soneta.HR` +(`session.GetHR()`). + +**K3a — Wniosek o szkolenie** — `WniosekOSzkolenie([Required] Pracownik pracownik)`; kolekcja +`pracownik.WnioskiOSzkolenia`. Pola: + +| Pole | Typ | Uwaga | +|---|---|---| +| `Definicja` | `Soneta.HR.DefinicjaSzkolenia` | rodzaj szkolenia (słownik HR) | +| `Etap` | `Soneta.HR.EtapRealizacjiSzkolenia` | np. „Wniosek zatwierdzony" (`hr.EtapRealizSzkol.WgNazwy[...]`) | +| `Realizacja` | `Soneta.HR.RealizacjaSzkolenia` | konkretna realizacja | +| `Budzet` | `Soneta.HR.BudżetSzkoleń` | budżet, z którego finansowane | +| `Koszt` | `Soneta.Types.Currency` | koszt szkolenia | +| `DataZgloszenia`, `Termin`, `DataAnulowania` | `Soneta.Types.Date` | daty cyklu wniosku | +| `Kierownik` | `Soneta.Kadry.Pracownik` | akceptujący | +| `SkierowanyPrzezZaklad` | `bool` | skierowanie pracodawcy | +| `Ocena` | `string`, `Opis: MemoText` | ocena/uwagi | + +**K3b — Ukończone szkolenie** — dwa ctory: `UkończoneSzkolenie([Required] Pracownik pracownik)` +oraz `UkończoneSzkolenie(WniosekOSzkolenie wniosek)` (przepina pracownika z wniosku). Kolekcja +`pracownik.UkończoneSzkolenia`. Pola: `Nazwa: string`, `Okres: FromTo`, `Ocena: string`, +`Opis: MemoText`, `Wniosek: WniosekOSzkolenie` (powiązanie). + +**K3c — Uprawnienie / certyfikat** — `UprawnieniePracownika([Required] Pracownik pracownik)`; +kolekcja `pracownik.Uprawnienia`. Pola: + +| Pole | Typ | Uwaga | +|---|---|---| +| `Definicja` | `Soneta.HR.DefinicjaUprawnienia` | rodzaj uprawnienia | +| `Numer` | `string` | numer uprawnienia/certyfikatu | +| `DataUzyskania`, `DataUtraty`, `TerminWaznosci` | `Soneta.Types.Date` | daty ważności | +| `Okres` | `Soneta.Types.FromTo` | okres obowiązywania | +| `WydanePrzez` | `string` | organ wydający | +| `Zrodlo` | `Soneta.HR.IŹródłoUzyskaniaUprawnienia` | źródło (np. ukończone szkolenie) | + +**Snippet (wniosek → koszt z budżetu):** + +```csharp +var hr = session.GetHR(); +var pracownik = session.GetKadry().Pracownicy.WgKodu["008"]; + +using (var t = session.Logout(editMode: true)) +{ + var wniosek = session.AddRow(new WniosekOSzkolenie(pracownik)); + wniosek.Definicja = hr.DefinicjeSzkolen.WgNazwy["Kurs zawodowy"]; + wniosek.Etap = hr.EtapRealizSzkol.WgNazwy["Wniosek zatwierdzony"]; + wniosek.DataZgloszenia = Date.Today; + wniosek.Koszt = new Currency(1500m); + + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- Typy K3 są w `Soneta.HR` (`session.GetHR()`), nie w `Soneta.Kadry`. +- `Etap`/`Definicja` to wpisy słownikowe HR — pobieraj `WgNazwy[...]`, nie twórz w teście. +- `Koszt`/`Budżet` używają `Soneta.Types.Currency` (waluta), nie `decimal`. + +--- + +### K4 — Nagrody i kary; oświadczenia (PIT-2, RODO, zgody) + +**K4a — Nagrody i kary.** Klasa bazowa `Soneta.Kadry.NagrodaKara` jest **abstrakcyjna** — używaj +konkretnych podtypów: `Soneta.Kadry.Nagroda(Pracownik)` i `Soneta.Kadry.Kara(Pracownik)`. Oba ctory +delegują do `NagrodaKara(pracownik, TypNagrodyKary)` ustawiając `Typ` na `Nagroda`/`Kara`. Kolekcja +`pracownik.NagrodyKary: SubTable`. Pola: + +| Pole | Typ | Uwaga | +|---|---|---| +| `Definicja` | `Soneta.Kadry.DefinicjaNagrodyKary` | słownik `DefNagrodKar`; ma własne pole `Typ` (Nagroda/Kara) — musi zgadzać się z podtypem wpisu, inaczej `set_Definicja` rzuca `ArgumentException`; może nieść `Element`/`Kwota` | +| `Typ` | `Soneta.Kadry.TypNagrodyKary` | `Nagroda`/`Kara` (ustawia ctor podtypu) | +| `Data` | `Soneta.Types.Date` | data nadania | +| `DataAnulowania` | `Soneta.Types.Date` | anulowanie | +| `Rozliczenie` | `Soneta.Kadry.RozliczenieSwiadczenia` (subrow) | `Rozliczenie.Kwota: Currency`, `Rozliczenie.Element: DefinicjaElementu`, `Rozliczenie.Okres: FromTo` — powiązanie z wypłatą | +| `Opis` | `Soneta.Business.MemoText` | treść nagrody/kary | +| `Pracownik` | `Soneta.Kadry.Pracownik` | ustawiany przez ctor | + +**K4b — Oświadczenia (PIT-2, RODO, zgody).** `Soneta.Kadry.OświadczeniePracownika` — trzy ctory: +`OświadczeniePracownika([Required] Pracownik pracownik, [Required] DefinicjaOświadczenia definicja)`, +wariant z `Date dataZłożenia`, oraz `(RowCreator)`. Kolekcja `pracownik.Oświadczenia`. Rodzaj +oświadczenia (PIT-2, zgoda RODO, zgoda na e-doręczenia itp.) określa `Definicja` (słownik +konfiguracyjny `DefOswiadczen`). Pola: + +| Pole | Typ | Uwaga | +|---|---|---| +| `Definicja` | `Soneta.Kadry.DefinicjaOświadczenia` | wymagana w ctorze; słownik `DefOswiadczen` | +| `DataZlozenia` | `Soneta.Types.Date` | data złożenia | +| `DataWycofania` | `Soneta.Types.Date` | data wycofania | +| `Okres` | `Soneta.Types.FromTo` | okres obowiązywania (z `Definicja.OkresWaznosci`/`OkresIlosc`) | +| `Tresc` | `Soneta.Business.MemoText` | treść | +| `TrescOswiadczenia` | `Soneta.Kadry.TreśćOświadczenia` | treść strukturalna | +| `Pracownik` | `Soneta.Kadry.Pracownik` | ustawiany przez ctor | + +**Snippet (nagroda + oświadczenie PIT-2):** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["009"]; + +using (var t = session.Logout(editMode: true)) +{ + // nagroda — konkretny podtyp, NIE abstrakcyjna NagrodaKara + var nagroda = session.AddRow(new Nagroda(pracownik)); + nagroda.Definicja = kadry.DefNagrodKar.WgNazwy["Nagroda uznaniowa"]; + nagroda.Data = Date.Today; + + // oświadczenie — definicja jest wymagana w konstruktorze + var defPit2 = kadry.DefOswiadczen.WgNazwy["PIT-2"]; + var oswiadczenie = session.AddRow(new OświadczeniePracownika(pracownik, defPit2, Date.Today)); + + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- **Nie** rób `new NagrodaKara(...)` — typ abstrakcyjny. Używaj `Nagroda`/`Kara`. +- `Definicja` musi mieć **`Typ` zgodny** z podtypem wpisu (`Nagroda` → def. o `Typ==Nagroda`, `Kara` → + def. o `Typ==Kara`); przypisanie niezgodnej typem definicji rzuca `ArgumentException` w `set_Definicja`. + Filtruj słownik: `DefNagrodKar.Cast().FirstOrDefault(d => d.Typ == TypNagrodyKary.Nagroda)`. +- `OświadczeniePracownika` **nie ma** ctora samego `(Pracownik)` — definicja jest `[Required]` + w konstruktorze; bez niej kod się nie skompiluje. +- `Rozliczenie.*` na nagrodzie/karze to subrow powiązania z wypłatą (`Currency`, `DefinicjaElementu`) + — wypełniane przy rozliczaniu w płacach, nie przy samym wpisie. + +--- + +### K5 — Wypadki przy pracy + +**Cel:** zarejestrować wypadek przy pracy wraz z dokumentacją powypadkową (protokół, decyzja, +okoliczności, skutki) i ewentualnym świadczeniem. + +**Mechanizm:** `Soneta.Kadry.Wypadek(Pracownik pracownik)`; kolekcja `pracownik.Wypadki`. Wpis jest +numerowany (`Numer: Soneta.Core.NumerDokumentu`) i wymaga `Definicja: Soneta.Core.DefinicjaDokumentu` +(definicja dokumentu wypadku). + +**Pola i typy (rekord `Wypadek`):** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Definicja` | `Soneta.Core.DefinicjaDokumentu` | definicja dokumentu (numeracja) | +| `Numer` | `Soneta.Core.NumerDokumentu` (subrow) | `Numer.Pelny`, `Numer.Symbol`, `Numer.Numer` | +| `Data` | `Soneta.Types.Date` | data wypadku | +| `Godzina` | `Soneta.Types.Time` | godzina wypadku | +| `DataZgloszenia` | `Soneta.Types.Date` | data zgłoszenia | +| `Miejsce` | `string` | miejsce wypadku | +| `Rodzaj` | `Soneta.Kadry.RodzajWypadku` | klasyfikacja wypadku | +| `PrzyPracy`, `Ciezki`, `Smiertelny`, `Niezdolnosc` | `bool` | kwalifikacja skutków | +| `Okolicznosci`, `Skutki`, `Odmowa` | `Soneta.Business.MemoText` | dokumentacja opisowa | +| `ProtokolNumer`, `ProtokolData` | `string` / `Date` | protokół powypadkowy | +| `DecyzjaNumer`, `DecyzjaData` | `string` / `Date` | decyzja | +| `PismoNumer`, `PismoData` | `string` / `Date` | pismo | +| `SKW` | `string` | statystyczna karta wypadku | +| `Kwota` | `decimal` | kwota świadczenia | +| `PracHistoria` | `Soneta.Kadry.PracHistoria` | (wyliczane) zapis kadrowy na datę | +| `Pracownik` | `Soneta.Kadry.Pracownik` | ustawiany przez ctor | + +**Snippet:** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["010"]; + +using (var t = session.Logout(editMode: true)) +{ + var wypadek = session.AddRow(new Wypadek(pracownik)); + wypadek.Data = Date.Today; + wypadek.Godzina = new Time(10, 30); + wypadek.DataZgloszenia = Date.Today; + wypadek.Miejsce = "Hala produkcyjna"; + wypadek.PrzyPracy = true; + wypadek.Okolicznosci = new MemoText("Poślizgnięcie na mokrej posadzce."); + + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- `Numer` jest subrowem nadawanym wg `Definicja` (numeracja) — nie ustawiaj `Numer.Pelny` ręcznie, + numer nadaje platforma; gdy `Definicja` ma własną numerację, podpięcie definicji wystarcza. +- `Godzina` to `Soneta.Types.Time`, `Data` to `Soneta.Types.Date` — nie `DateTime`. +- Pola opisowe (`Okolicznosci`, `Skutki`, `Odmowa`) to `MemoText`, nie `string`. + +### K6 — RODO/GIODO: oświadczenia, uprawnienia do przetwarzania, wymiana danych + +**Cel:** ewidencjonować zgody/oświadczenia RODO pracownika, uprawnienia do przetwarzania danych +osobowych oraz fakty wymiany danych (pozyskanie / udostępnienie / powierzenie). Pracownik jest +hostem GIODO — implementuje `IGIODOOświadczenieHost`, `IGIODOUprawnienieHost`, `IGIODOWymianaDanychHost`, +`IGIODOZgodnyHost`. Zapis „teczki" personalnej do pliku jest operacją plikową (poza zakresem testów). + +**Publiczny kontrakt — kolekcje na `Pracownik` (moduł `Soneta.Core`):** + +| Kolekcja | Typ elementu | Zawartość | +|---|---|---| +| `GIODOOświadczenia` | `SubTable` | oświadczenia / zgody RODO | +| `GIODOUprawnienia` | `SubTable` | uprawnienia do przetwarzania danych | +| `GIODOUdostępnienia` | `SubTable` | pozyskanie / udostępnienie / powierzenie danych | +| `PotwierdzeniaGIODO` | `SubTable` | potwierdzenia zgodności; `ZgodnoscGIODOPotwierdzona: bool` (kalkulowane) | + +**`GIODOOświadczenie` (tabela `GIODOOswiadcz`, root) — pola bazodanowe:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Host` | `IGIODOOświadczenieHost` | składający oświadczenie (= `Pracownik`) | +| `Definicja` | `Soneta.Core.GIODODefinicjaOświadczenia` | **referencja konfiguracyjna** (wymagana przez ctor) | +| `Data` | `Soneta.Types.Date` | data oświadczenia (zapisywalne) | +| `Okres` | `Soneta.Types.FromTo` | okres obowiązywania zgody — **read-only** (wyliczane z definicji) | +| `Rodzaj` | `Soneta.Core.RodzajeOświadczeńGIODO` | `Oświadczenie`, `UdzielenieZgody`, `WycofanieZgody` — **read-only** (wynika z definicji) | +| `Oswiadczenie` | `bool` | flaga oświadczenia | +| `Tresc` | `Soneta.Business.MemoText` | treść | +| `SposobPozyskania` | `string` | — | +| `DataWycofaniaZgody` | `Soneta.Types.Date` | — | +| `WycofanieZgody` | `GIODOOświadczenie` | powiązanie z zapisem wycofania | +| `Bufor` | `bool` | zatwierdzenie | + +Ctor: `new GIODOOświadczenie(IGIODOOświadczenieHost host, GIODODefinicjaOświadczenia definicja)`. + +**`GIODOUprawnienie` (tabela `GIODOUprawnienia`, root) — pola bazodanowe:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Uprawniony` | `IGIODOUprawnienieHost` | = `Pracownik` | +| `Definicja` | `Soneta.Core.GIODODefinicjaUprawnienia` | **referencja konfiguracyjna** (wymagana przez ctor) | +| `Data`, `Przyznane`, `Odebrane` | `Soneta.Types.Date` | data zapisu / od kiedy przyznane / od kiedy odebrane | +| `Okres` | `Soneta.Types.FromTo` | okres przyznania | +| `Tresc` | `Soneta.Business.MemoText` | — | +| `WycofanieUprawnienia` | `GIODOUprawnienie` | powiązanie z wycofaniem | + +Ctor: `new GIODOUprawnienie(IGIODOUprawnienieHost uprawniony, GIODODefinicjaUprawnienia definicja)`. + +**`GIODOWymianaDanych` (tabela `GIODOWymDanych`, root) — pola bazodanowe:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Host` | `IGIODOWymianaDanychHost` | = `Pracownik` | +| `Kierunek` | `Soneta.Core.GIODOKierunekWymianyDanych` | `Powierzenie`, `Pozyskanie`, `PowierzenieZbioru`, `PozyskanieZbioru`, `Udostępnienie` | +| `Podmiot` | `Soneta.Core.IKontrahent` | druga strona wymiany | +| `Data` | `Soneta.Types.Date` | data wymiany | +| `Zakres` | `Soneta.Business.MemoText` | zakres danych | +| `SposobPozyskania` | `string` | — | +| `PozyskaneOdOsoby`, `UdostepnioneOsobie`, `NaWniosekOsoby`, `TylkoDostep` | `bool` | flagi | +| `Definicja` | `Soneta.Core.DefinicjaDokumentu` | def. numeracji dokumentu | +| `ZbiorDanych` | `Soneta.Core.GIODO.GIODOZbiorDanych` | zbiór danych | + +`GIODOWymianaDanych` **nie ma publicznego konstruktora** — rekordy tworzą wyłącznie workery poniżej +(zwracają konkretne podtypy `GIODOPozyskanieDanych` / `GIODOUdostępnienieDanych` / `GIODOPowierzenieDanych`). + +**Workery RODO (jedyna droga dodania przez API; klasy zagnieżdżone w `Soneta.Kadry.Pracownicy`):** + +| Worker | Metoda | Zwraca | Parametry (`Pars` / `Params`) | +|---|---|---|---| +| `Pracownicy.DodajOświadczeniaWorker` | `GIODOOświadczenie DodajOświadczenia()` | oświadczenie | `Pars`: `Definicja: GIODODefinicjaOświadczenia`, `Data`, `Oddział`, `SposobPozyskania`, `Zatwierdź: bool` | +| `Pracownicy.DodajUprawnieniaWorker` | `GIODOUprawnienie DodajUprawnienia()` | uprawnienie | `Pars`: `Definicja: GIODODefinicjaUprawnienia`, `Data`, `Przyznane`, `Odebrane`, `Oddział`, `Zatwierdź: bool` | +| `Pracownicy.DodajPozyskanieDanychWorker` | `GIODOPozyskanieDanych DodajPozyskanieDanych()` | wymiana (pozyskanie) | `Pars`: `Podmiot: IKontrahent`, `Data`, `Zakres: string`, `Oddział`, `SposobPozyskania`, `Zatwierdź: bool` | +| `Pracownicy.DodajUdostępnienieDanychWorker` | `GIODOUdostępnienieDanych DodajUdostępnienieDanych()` | wymiana (udostępnienie) | `Pars`: `Podmiot: IKontrahent`, `Data`, `Zakres: string`, `Oddział`, `Zatwierdź: bool` | +| `Pracownicy.DodajPowierzenieDanychWorker` | `GIODOPowierzenieDanych DodajPowierzenieDanych()` | wymiana (powierzenie) | `Pars` (analogicznie) | + +Wszystkie workery RODO mają bezparametrowy ctor oraz property `Hosts: Pracownik[]` (`[Context]`, lista +pracowników, których dotyczy operacja) i `Session`. + +**Zapis teczki personalnej do pliku — `Pracownik.ZapiszTeczkęDoPlikuWorker`** (akcja +„Teczka.../Zapisz teczkę do pliku", metoda `ZapiszTeczkeDoPliku()`, property `Param`) — to +**operacja plikowa** (serializacja dokumentacji do plików XML/katalogu na dysku). **Poza zakresem +testów jednostkowych → `[Ignore]`** (zależność od systemu plików). + +**Snippet (dodanie oświadczenia GIODO workerem):** + +```csharp +var kadry = session.GetKadry(); +var pracownik = kadry.Pracownicy.WgKodu["006"]; + +// Definicja oświadczenia z konfiguracji (musi istnieć w bazie): +var defOswiadczenia = session.ExecuteConfig(s => + s.GetCore().GIODODefinicjeOświadczeń.WgNazwy["Zgoda na przetwarzanie danych"]); + +using (var t = session.Logout(editMode: true)) +{ + var worker = new Pracownik.Pracownicy.DodajOświadczeniaWorker { Hosts = new[] { pracownik } }; + worker.Pars.Definicja = session.Get(defOswiadczenia); + worker.Pars.Data = Date.Today; + worker.Pars.Zatwierdź = true; + GIODOOświadczenie oswiadczenie = worker.DodajOświadczenia(); + t.CommitUI(); +} +session.Save(); + +// Odczyt oświadczeń pracownika: +foreach (GIODOOświadczenie o in pracownik.GIODOOświadczenia) +{ + // o.Definicja, o.Okres, o.Rodzaj, o.Data +} +``` + +**Snippet (dodanie oświadczenia bez workera — bezpośrednim ctorem):** + +```csharp +using (var t = session.Logout(editMode: true)) +{ + var o = session.AddRow(new GIODOOświadczenie(pracownik, session.Get(defOswiadczenia))); + // host i Definicja wynikają z ctora; Rodzaj/Okres są WYLICZANE (read-only) z definicji — nie ustawiaj ich. + o.Data = Date.Today; + o.SposobPozyskania = "Formularz papierowy"; + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- `GIODOOświadczenie`/`GIODOUprawnienie` wymagają **referencji do definicji konfiguracyjnej** + (`GIODODefinicjaOświadczenia` / `GIODODefinicjaUprawnienia`) — pobierz istniejący rekord + (`ExecuteConfig`), nie twórz „w locie". Bez definicji w bazie scenariusz wymaga uprzedniej + konfiguracji modułu RODO/GIODO. +- `GIODOWymianaDanych` **nie ma publicznego ctora** — dodawaj wyłącznie workerami + `DodajPozyskanieDanychWorker` / `DodajUdostępnienieDanychWorker` / `DodajPowierzenieDanychWorker`. +- Workery RODO modyfikują dane i są uruchamiane „jak z UI" → transakcja edycyjna + `CommitUI()` + + `Save()`. `Hosts`/`Podmiot` muszą pochodzić z bieżącej sesji (safe-code §2.1). +- Obowiązywanie zgody jest „na dzień" — czytaj `Okres`/`Data`, nie zakładaj bezterminowości. +- Dane wrażliwe (treść oświadczeń, podmioty) — nie loguj nadmiarowo (safe-code §12). +- Workery RODO wymagają praw do obszaru GIODO; w teście biznesowym egzekucji praw nie sprawdzamy + (safe-code §7.2). + +### K7 — Struktura organizacyjna: przypisanie do wydziału/struktury, powiązania + +**Cel:** przypisać pracownika do jednostki organizacyjnej (wydziału) oraz do elementów struktury +organizacyjnej (np. stanowiska w strukturze, relacje przełożony–podwładny). Wydział wynika z warunków +etatu (`Etat.Wydzial`, historyczne — patrz sekcja B), a powiązania ze strukturą trzyma osobna kolekcja. + +**Publiczny kontrakt:** + +| Składnik | Typ | Rodzaj | Uwaga | +|---|---|---|---| +| `Etat.Wydzial` | `Soneta.Kadry.Wydzial` | bazodanowe (na `PracHistoria.Etat`) | jednostka organizacyjna; korzeń: `session.GetKadry().Wydzialy.Firma` (zmiana „od daty" — A14) | +| `PowiązaniaStrOrg` | `SubTable` | kolekcja na `Pracownik` | powiązania ze strukturą organizacyjną | +| `StrukturaOraganizacyjna` | `Pracownik.StrukturaOraganizacyjnaManager` | manager (read-only API) | nawigacja przełożeni/podwładni | +| Pracownik implementuje | `IŹródłoPowiązaniaStrukturyOrganizacyjnej` | interfejs | jest źródłem powiązań | + +**`PowiązanieStrukturyOrganizacyjnej` (tabela `PowiazaniaStrOrg`, child przez `Zrodlo`) — pola:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Zrodlo` | `IŹródłoPowiązaniaStrukturyOrganizacyjnej` | guided-parent (= `Pracownik`) | +| `Element` | `Soneta.Core.ElementStrukturyOrganizacyjnej` | referencja do **instancji** elementu struktury z tabeli `CoreModule.ElementyStrOrg` (NIE z `DefElStrukturOrg`, która trzyma `DefinicjaElementuStrukturyOrganizacyjnej`); `ElementStrukturyOrganizacyjnej` nie ma publicznego ctora — pobierz istniejący rekord | +| `Okres` | `Soneta.Types.FromTo` | okres obowiązywania powiązania (zapisywalne) | + +Ctor: `new PowiązanieStrukturyOrganizacyjnej(ElementStrukturyOrganizacyjnej element, IŹródłoPowiązaniaStrukturyOrganizacyjnej zrodlo)`. + +**Manager `StrukturaOraganizacyjnaManager` (tylko odczyt nawigacyjny):** + +| Metoda / property | Sygnatura | Zwraca | +|---|---|---| +| `Przełożony(...)` | `Pracownik Przełożony(StrukturaOrganizacyjna, Date, bool, Func<...>)` | bezpośredni przełożony | +| `PrzełożonyWgPodległości(...)` | `Pracownik PrzełożonyWgPodległości(Date, bool, Func<...>)` | przełożony wg podległości | +| `Przełożeni(...)` | `IEnumerable …` | przełożeni | +| `Podwładni(...)` | `IEnumerable Podwładni(FromTo, bool, Func<...>)` | podwładni w okresie | +| `GetDomyślnyPrzełożony(naDzień[, bezpośredni, warunek])` | `Pracownik GetDomyślnyPrzełożony(Date, bool=…, Func=…)` | domyślny przełożony na dzień (property `DomyślnyPrzełożony` jest **przestarzała** — używaj metody) | + +**Workery zmiany powiązań (klasy zagnieżdżone w `Soneta.Kadry.Pracownik`):** + +| Worker | Akcja (menu) | Metoda | Parametry | +|---|---|---|---| +| `Pracownik.DodajPowiązanieStrukturyWorker` | „Struktura organizacyjna/Dodaj lub modyfikuj powiązanie…" | `object DodajPowiązanieStruktury()` | `Params: WybórElementuContext`, `Pracownicy: Pracownik[]` (`[Context]`) | +| `Pracownik.UsuńPowiązanieStrukturyWorker` | „Struktura organizacyjna/Usuń powiązanie…" | `void DodajPowiązanieStruktury()` | `Params: WybórElementuContext`, `Pracownicy: Pracownik[]` | + +**Snippet (dodanie powiązania ze strukturą — bezpośrednim ctorem):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; + +// Instancja elementu struktury (musi istnieć w bazie — tabela ElementyStrOrg, NIE DefElStrukturOrg): +ElementStrukturyOrganizacyjnej element = + session.GetCore().ElementyStrOrg.Cast().FirstOrDefault(); + +using (var t = session.Logout(editMode: true)) +{ + var p = session.AddRow(new PowiązanieStrukturyOrganizacyjnej(element, pracownik)); + p.Okres = new FromTo(Date.Today, Date.MaxValue); + t.Commit(); +} +session.Save(); + +// Odczyt nawigacyjny struktury: +Pracownik przelozony = pracownik.StrukturaOraganizacyjna.GetDomyślnyPrzełożony(Date.Today); +``` + +**Snippet (zmiana wydziału — nowy zapis „od daty", A14):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var kadry = session.GetKadry(); + +using (var t = session.Logout(editMode: true)) +{ + var ph = pracownik[Date.Today]; // zapis obowiązujący na dzień (A15) + ph.Etat.Wydzial = kadry.Wydzialy.Firma; // referencja do istniejącego wydziału (korzeń struktury) + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- `Etat.Wydzial` jest **danymi historycznymi** (na `PracHistoria.Etat`) i jest **wymagany dla etatu** — + zmieniaj nowym zapisem „od daty" (A14), nie nadpisuj bieżącego (zmieniłoby cały okres wstecz). +- `PowiązanieStrukturyOrganizacyjnej.Element` to **referencja konfiguracyjna** — pobierz istniejący + element struktury; bez zdefiniowanej struktury organizacyjnej scenariusz wymaga konfiguracji. +- `StrukturaOraganizacyjnaManager` jest **tylko do odczytu** — zmiany realizują workery + `DodajPowiązanieStrukturyWorker` / `UsuńPowiązanieStrukturyWorker` lub bezpośredni zapis do + `PowiązaniaStrOrg`. +- Workery struktury modyfikują dane „jak z UI" → transakcja + `CommitUI()` + `Save()`; rekordy z + bieżącej sesji (safe-code §2.1). + +### K8 — Oceny okresowe: arkusze ocen, cele okresowe, karty kompetencji + +**Cel:** prowadzić oceny okresowe pracownika (arkusz oceny z elementami), cele okresowe wraz z ich +realizacją, karty kompetencji i karty opisu stanowiska. Funkcjonalność należy do modułów **HR** +(`session.GetHR()`, `OcenyPracownikow`, `EtapyRekrutacji`) i **HR2** (`session.GetHR2()`, +`CeleOkresowePrac`, `KartyKompPrac`, `KartyOpStanowisk`). Pracownik implementuje `IOceniany`, +`IOceniający`, `IOdpowiedzialnyZaOcenę`, `IŹródłoKartyOpisuStanowiska`. + +**Publiczny kontrakt — kolekcje na `Pracownik`:** + +| Kolekcja | Typ elementu | Zawartość | +|---|---|---| +| `Oceny` | `SubTable` | arkusze ocen okresowych (root) | +| `ElementyOceny` | `SubTable` | pojedyncze elementy/pozycje arkuszy ocen | +| `CeleOkresowe` | `SubTable` | cele okresowe | +| `KartyKompetencji` | `SubTable` | karty kompetencji | +| `KartyOpisuStanowiska` | `SubTable` | karty opisu stanowiska | +| `KartyRealizacjiCelu` | `SubTable` | karty realizacji celów | +| `Oceniani` / `Oceniający` | `SubTable` | role pracownika w ocenie | + +**`OcenaPracownika` (tabela `OcenyPracownikow`, root; impl. `IOcenaPracownika`) — pola:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Pracownik` | `Soneta.Kadry.Pracownik` | oceniany | +| `Nazwa` | `string` | nazwa arkusza | +| `Data`, `Termin` | `Soneta.Types.Date` | data oceny / termin | +| `Opis` | `Soneta.Business.MemoText` | — | +| `Anulowany` | `bool` | — | +| `ElementyOceny` | `SubTable` | pozycje arkusza | + +Ctor: `new OcenaPracownika(Pracownik pracownik)`. + +**`ElementOcenyPracownika` (tabela `ElementyOcenPrac`, child przez `Ocena`) — pola:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Ocena` | `IOcenaPracownika` | guided-parent (arkusz oceny lub etap rekrutacji) | +| `Pracownik` | `Soneta.Kadry.Pracownik` | — | +| `Definicja` | `Soneta.HR.DefElementuOcenyPracownika` | **referencja konfiguracyjna** (z tabeli `HRModule.DefElemOcenPrac`); zapisywalna i wymagana do zapisu | +| `Typ` | `Soneta.HR.TypyElementowOceny` | `Historyczny`, `Aktualny` — **read-only** (wynika z definicji) | +| `Data` | `Soneta.Types.Date` | **read-only** (wyliczane) | +| `Wartosc` | `decimal` | wartość liczbowa oceny (zapisywalna) | + +Ctor: `new ElementOcenyPracownika(IOcenaPracownika ocena)`. Dodawaj przez `session.AddRow(new ElementOcenyPracownika(ocena))` (NIE `ocena.ElementyOceny.AddRow(...)` — `SubTable` nie udostępnia `AddRow`). + +**`CelOkresowyPracownika` (tabela `CeleOkresowePrac`, root) — pola:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Pracownik` | `Soneta.Kadry.Pracownik` | — | +| `Nazwa` | `string` | nazwa celu | +| `Data`, `Termin` | `Soneta.Types.Date` | — | +| `Opis` | `Soneta.Business.MemoText` | — | +| `Definicja` | `Soneta.Oceny.DefinicjaElementuOceny` | **referencja konfiguracyjna** (opcjonalna) | +| `Anulowany` | `bool` | — | +| `Realizacja` | `Soneta.HR2.RealizacjaCelu` | bieżąca realizacja (subrow) | +| `Realizacje` | `SubTable` | realizacje celu | + +> `CelOkresowyPracownika` **nie ma pola `Wartosc`** — postęp/ocena celu jest reprezentowana przez `Realizacja`/`Realizacje` (`Soneta.HR2.RealizacjaCelu`). Pole `Wartosc` (typu decimal) ma natomiast `ElementOcenyPracownika`. + +Ctor: `new CelOkresowyPracownika(Pracownik pracownik)`. + +**`KartaOpisuStanowiskaBase` (tabela `KartyOpStanowisk`, root) — pola:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Zrodlo` | `IŹródłoKartyOpisuStanowiska` | = `Pracownik` / `DefinicjaStanowiska` / wakat / oferta | +| `Typ` | `Soneta.HR2.TypyKartOpisuStanowiska` | `KartaOpisuStanowiska`, `OgłoszenieOPracę` | +| `Data` | `Soneta.Types.Date` | — | +| `Elementy` | `SubTable` | elementy opisu | +| `Kompetencje` | `SubTable` | kompetencje | + +`KartaOpisuStanowiskaBase` i `KartaKompetencjiPracownika` **nie mają publicznego ctora bezparametrowego**; +`KartaKompetencjiPracownika` ma ctor `(Pracownik pracownik, IŹródłoKartyCharakterystykiPracownika zrodlo)`. +Karty zwykle tworzone są workerami kopiującymi (`KopiujKartęOpisuStanowiskaWorker.KopiujZDefinicjiStanowiska()`, +`KopiujKartęKompetencjiWorker.KopiujZKOS()`/`KopiujZPoprzedniej()`). + +**Workery oceniania (klasy w `Soneta.HR` / `Soneta.HR2`):** + +| Worker | Metoda | Parametry | +|---|---|---| +| `Soneta.HR.OcenaPracownikowWorker` | `Oceń()` | `Pars`, `Idxs: Pracownik[]` (`[Context]`); ctor `(Context)` | +| `Soneta.HR.WzorOcenyPracownika.ZainicjujOcenęWorker` | `Zainicjuj()` | `Ocena: IOcenaPracownika`, `Pars`; ctor `(Session)` | +| `Soneta.HR2.KopiujKartęOpisuStanowiskaWorker` | `KopiujZDefinicjiStanowiska()`, `KopiujZPoprzedniej()` | `Karta: KartaOpisuStanowiskaBase` | +| `Soneta.HR2.KopiujKartęKompetencjiWorker` | `KopiujZKOS()`, `KopiujZPoprzedniej()` | `Karta: KartaKompetencjiPracownika` | + +**Snippet (dodanie celu okresowego — wymaga definicji elementu oceny w bazie HR2):** + +```csharp +var pracownik = session.GetKadry().Pracownicy.WgKodu["006"]; +var hr2 = session.GetHR2(); + +// Definicja elementu oceny z konfiguracji modułu Oceny (musi istnieć): +var defElementu = session.ExecuteConfig(s => + /* pobranie DefinicjaElementuOceny z modułu Oceny */ default); + +using (var t = session.Logout(editMode: true)) +{ + var cel = new CelOkresowyPracownika(pracownik); + hr2.CeleOkresowePrac.AddRow(cel); + cel.Nazwa = "Wdrożenie nowego modułu"; + cel.Data = Date.Today; + cel.Termin = new Date(2026, 12, 31); + cel.Definicja = session.Get(defElementu); + t.Commit(); +} +session.Save(); + +// Odczyt celów okresowych: +foreach (CelOkresowyPracownika c in pracownik.CeleOkresowe) +{ + // c.Nazwa, c.Termin, c.Wartosc.Punktacja +} +``` + +**Snippet (utworzenie arkusza oceny i dodanie elementu):** + +```csharp +using (var t = session.Logout(editMode: true)) +{ + var ocena = new OcenaPracownika(pracownik); + session.GetHR().OcenyPracownikow.AddRow(ocena); + ocena.Nazwa = "Ocena roczna 2026"; + ocena.Data = Date.Today; + + var el = session.AddRow(new ElementOcenyPracownika(ocena)); // ocena jako IOcenaPracownika + el.Definicja = defElementu; // wymagana (z HRModule.DefElemOcenPrac); Typ/Data są wyliczane (read-only) + el.Wartosc = 4m; // Wartosc to decimal + t.Commit(); +} +session.Save(); +``` + +**Pułapki:** +- Cele/elementy ocen wymagają **referencji do definicji konfiguracyjnych** (`DefElementuOcenyPracownika`, + `Soneta.Oceny.DefinicjaElementuOceny`) — bez nich scenariusz wymaga uprzedniej konfiguracji modułu + Oceny/HR/HR2. W bazie Demo te definicje **mogą nie istnieć** — najpierw sprawdź dostępność. +- Karty opisu stanowiska / kompetencji nie mają prostego ctora — twórz je workerami kopiującymi + (`KopiujKartę…Worker`) z definicji stanowiska lub poprzedniej karty. +- `ElementOcenyPracownika.Ocena` to `IOcenaPracownika` — może to być arkusz oceny **lub etap + rekrutacji** (`EtapRekrutacji` także implementuje `IOcenaPracownika`, patrz K9). +- `CelOkresowyPracownika` **nie ma pola `Wartosc`** — postęp/wynik celu reprezentują `Realizacja`/`Realizacje` + (`RealizacjaCelu`). Liczbową wartość ma `ElementOcenyPracownika.Wartosc` (`decimal`). +- `ElementOcenyPracownika`: `Typ`/`Data` są **read-only** (wyliczane z definicji), a do tabeli dodawaj przez + `session.AddRow(...)` — `SubTable` nie ma metody `AddRow`. +- Workery oceniania uruchamiane „jak z UI" → transakcja + `CommitUI()` + `Save()`. + +### K9 — Rekrutacja: wakaty, ogłoszenia, aplikacje, etapy, stan zatrudnienia + +**Cel:** prowadzić proces rekrutacji — wakaty (zapotrzebowanie), oferty/ogłoszenia o pracę, aplikacje +kandydatów oraz etapy rekrutacji z ocenami, aż do zatrudnienia kandydata. Funkcjonalność należy do +modułów **HR2** (`session.GetHR2()`, `RekrutAplikacje`, `RekrutWakaty`) i **HR** +(`session.GetHR()`, `Rekrutacje`, `EtapyRekrutacji`). + +**Publiczny kontrakt — kolekcje na `Pracownik` (kandydat jest reprezentowany rekordem `Pracownik`):** + +| Kolekcja | Typ elementu | Zawartość | +|---|---|---| +| `Aplikacje` | `SubTable` | aplikacje kandydata | +| `Wakaty` | `SubTable` | wakaty | +| `Rekrutacje` / `Kandydatury` | `SubTable` | rekrutacje (kandydatury) | +| `EtapyRekrutacji` | `SubTable` | etapy rekrutacji | + +**`RekrutacjaAplikacja` (tabela `RekrutAplikacje`, root; impl. `IŹródłoRekrutacji`) — pola:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Pracownik` | `Soneta.Kadry.Pracownik` | kandydat | +| `Stanowisko` | `Soneta.HR.DefinicjaStanowiska` | **referencja konfiguracyjna** stanowiska | +| `Wydzial` | `Soneta.Kadry.Wydzial` | jednostka organizacyjna | +| `Oferta` | `Soneta.HR2.OfertaPracy` | oferta, na podstawie której wpłynęła aplikacja | +| `Stan` | `Soneta.HR2.StanAplikacji` | `Wprowadzona`, `Zakończona`, `Anulowana` | +| `Data` | `Soneta.Types.Date` | data aplikacji | +| `PlanowanaDataZatrudnienia` | `Soneta.Types.Date` | — | + +Ctor: `new RekrutacjaAplikacja(Pracownik pracownik, Soneta.HR.WydziałDefinicjiStanowiska stanowisko)`. +`WydziałDefinicjiStanowiska` jest w module **`Soneta.HR`** (NIE `Soneta.HR2`) i ma ctor +`new WydziałDefinicjiStanowiska(DefinicjaStanowiska definicjaStanowiska)` — definicję pobierz +z `session.GetHR().DefStanowisk`. `RekrutacjaAplikacja.Stanowisko` zwraca tę `DefinicjaStanowiska`. + +**`EtapRekrutacji` (tabela `EtapyRekrutacji`, root; impl. `IOcenaPracownika`) — pola:** + +| Pole | Typ | Uwaga | +|---|---|---| +| `Rekrutacja` | `Soneta.HR.Rekrutacja` | rekrutacja nadrzędna | +| `Definicja` | `Soneta.HR.DefinicjaEtapuRekrutacji` | **referencja konfiguracyjna** | +| `Lp` | `int` | numer etapu | +| `Data`, `Termin` | `Soneta.Types.Date` | — | +| `Odpowiedzialny` | `Soneta.Oceny.IOceniający` | osoba odpowiedzialna | +| `Opis` | `Soneta.Business.MemoText` | — | +| `ElementyOceny` | `SubTable` | oceny etapu (etap jest `IOcenaPracownika`) | + +Ctor: `new EtapRekrutacji(Rekrutacja rekrutacja)`. + +**`Rekrutacja` (tabela; impl. `IOcenaPracownika`) — ctory:** +`new Rekrutacja(Pracownik pracownik)`, `new Rekrutacja(Pracownik pracownik, IŹródłoRekrutacji źródło)`. + +**`RekrutacjaWakat` (tabela `RekrutWakaty`, root) — ctory:** +`new RekrutacjaWakat(WydziałDefinicjiStanowiska stanowisko)`, +`new RekrutacjaWakat(DefinicjaStanowiska definicjaStanowiska, Wydzial wydzial)`. + +**`OfertaPracy` (tabela; ogłoszenie o pracę) — ctory:** +`new OfertaPracy(WydziałDefinicjiStanowiska stanowisko)`, `new OfertaPracy(RekrutacjaWakat wakat)`. + +**Workery rekrutacji (klasy zagnieżdżone):** + +| Worker | Metoda | Parametry | +|---|---|---| +| `Soneta.HR2.RekrutacjaAplikacja.NowaRekrutacjaWorker` | rozpoczęcie rekrutacji z aplikacji | `Aplikacje: RekrutacjaAplikacja[]` | +| `Soneta.HR2.RekrutacjaWakat.NowaRekrutacjaWorker` | rozpoczęcie rekrutacji z wakatu | `Wakat: RekrutacjaWakat`, `Pracownicy: Pracownik[]` | +| `Soneta.HR2.OfertaPracy.NowaRekrutacjaWorker` | rozpoczęcie rekrutacji z oferty | `Oferta: OfertaPracy`, `Pracownicy: Pracownik[]` | +| `Soneta.HR.OcenaKandydatowWorker` | `Oceń()` | `Pars`, `Elementy: Rekrutacja[]`; ctor `(Context)` | +| `Soneta.HR.Rekrutacja.ZatrudnijWorker` | `PracHistoria Zatrudnij()` | `Pars`, `Rekrutacja: Rekrutacja` — tworzy zatrudnienie (zapis historii) | + +**Snippet (dodanie aplikacji kandydata — wymaga def. stanowiska w bazie):** + +```csharp +var hr2 = session.GetHR2(); +var kandydat = session.GetKadry().Pracownicy.WgKodu["006"]; + +// Definicja stanowiska z konfiguracji HR (musi istnieć w session.GetHR().DefStanowisk): +var defStanowiska = session.GetHR().DefStanowisk + .Cast().FirstOrDefault(); +var wydzialDefStanowiska = new Soneta.HR.WydziałDefinicjiStanowiska(defStanowiska); + +using (var t = session.Logout(editMode: true)) +{ + var aplikacja = session.AddRow(new RekrutacjaAplikacja(kandydat, wydzialDefStanowiska)); + aplikacja.Data = Date.Today; + aplikacja.Stan = StanAplikacji.Wprowadzona; + t.Commit(); +} +session.Save(); + +// Odczyt aplikacji kandydata: +foreach (RekrutacjaAplikacja a in kandydat.Aplikacje) +{ + // a.Stanowisko, a.Stan, a.Data, a.Oferta +} +``` + +**Pułapki:** +- Cały proces rekrutacji wymaga **konfiguracji HR/HR2** (`DefinicjaStanowiska`, + `DefinicjaEtapuRekrutacji`, `WydziałDefinicjiStanowiska`). W bazie Demo te definicje **mogą nie + istnieć** — przed scenariuszem sprawdź dostępność, inaczej `Save()` rzuci wyjątek weryfikatora. +- `RekrutacjaAplikacja` przyjmuje w ctorze `WydziałDefinicjiStanowiska`, nie samą `DefinicjaStanowiska` + (wydział definicji powstaje z `new WydziałDefinicjiStanowiska(definicjaStanowiska)`). +- `EtapRekrutacji` i `Rekrutacja` implementują `IOcenaPracownika` — oceny etapów trzyma + `EtapRekrutacji.ElementyOceny` (te same `ElementOcenyPracownika` co w K8). +- `new Rekrutacja(pracownik)` ustawia pole `Pracownik` i dodaje rekord do **roota** `HRModule.Rekrutacje` + (oraz `EtapRekrutacji` do `HRModule.EtapyRekrutacji`). Kolekcje na `Pracownik` (`Rekrutacje`/`Kandydatury`/ + `EtapyRekrutacji`) to `ChildTable` wiązane przez relacje — do weryfikacji w teście pewniejszy jest root + `session.GetHR().Rekrutacje` niż `pracownik.Rekrutacje` (zależnie od relacji może być pusta dla samego `Pracownik`). +- Zatrudnienie kandydata realizuje `Rekrutacja.ZatrudnijWorker.Zatrudnij()` (zwraca `PracHistoria`) — + spina rekrutację z zatrudnieniem (sekcja A). Worker modyfikuje dane → transakcja + `CommitUI()` + `Save()`. +- `Stan` aplikacji (`Wprowadzona`/`Zakończona`/`Anulowana`) steruje cyklem życia — nie usuwaj aplikacji + z historią, oznaczaj `Anulowana`.