19 KiB
KADRY05 — Plan pracy i kalendarz
Wspólne fakty o typie, podstawowe typy i szablon wzorca: ../kadry.md.
Model kalendarza pracownika. Każdy
Pracownikma kalendarz roboczy (pracownik.Etat.Kalendarz : Soneta.Kalend.Kalendarz), którego dni leżą w tabeliDniKalendarza(DzienKalendarzaBase, child kalendarza). Pracownik wystawia trzy niezależne kolekcje dni typuDateSubTable(indeksator po dacie[Date], tylko do odczytu — element tworzysz konstruktorem +AddRow):
pracownik.DniPlanu : DateSubTable— plan/harmonogram (dniDzienPlanu : DzienKalendarzaBase); topracownik.Etat.Kalendarz.Dni.pracownik.DniPracy : DateSubTable<Soneta.Kalend.DzienPracy>— ewidencja (realizacja) czasu pracy.pracownik.DniRCP : DateSubTable<Soneta.Kalend.DzienRCP>— zarejestrowany czas pracy (RCP) — patrz sekcja F.Wszystkie dni współdzielą subrow
Praca : Soneta.Kalend.CzasPracyz polamiOdGodziny/DoGodziny/Czas : Soneta.Types.Time. Definicja dnia (Definicja : Soneta.Kalend.DefinicjaDnia) to rekord konfiguracyjny (słownikDefinicjeDni, 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 zdefiniowanegoDefinicjaDniaw konfiguracji. Operacje masowe (przeliczenie planu na okres) są zaszyte w workerach/kalkulatorach UI — patrz KADRY-E2.
KADRY-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:
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:
DniPlanutoDateSubTablenietypowany (zwracaRow) — rzutuj naDzienPlanu. Indeksator[Date]jest tylko do odczytu: nowego dnia nie „przypiszesz", tworzysz go ctoremnew DzienPlanu(pracownik, data)+session.AddRow(...).- Godziny ustawiasz na subrowie
Praca(dp.Praca.OdGodziny = …), nie nadp.OdGodziny— to ostatnie jest kalkulowane (read-only). Po ustawieniu od–doPraca.Czas/Czasprzeliczają się. Definicjato rekord konfiguracyjnego słownikaDefinicjeDni— pobierz istniejący wpis (kalend.DefinicjeDni[kod]), nie twórz „w locie". Bez przypisanegoDefinicjaś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, KADRY-E2).
- Norma dobowa to atrybut kalendarza (
Etat.Kalendarz.NormaDobowa), nie pojedynczego dnia.
KADRY-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):
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):
// 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<Pracownik[]>()
};
worker.Kopiuj(); // wewnątrz: Session.Logout + Commit
Pułapki:
- Kopiowanie grupy nie ma „czystego" API bezkontekstowego —
KopiujWorkeriAktualizujKalendarzWorker.Paramsdziedziczą poContextBasei wymagająContext(zaznaczenie z listy UI). Dla kodu serwerowego/testów używaj publicznej statycznejKalendarzPlanuKopia.Kopiuj(pracownik, okres)w pętli po pracownikach — to ona realizuje właściwą logikę (worker wKopiujIntwoła ją per pracownik). KopiujWorker.Kopiuj()jest gardzony licencją (BI/BI_PL/PL) iIsVisibleKopiuj(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 zKalendarzPlanuKopia/KalendarzPracyKopia— to bufor kopii, odrębny od właściwegoDniPlanu/DniPracy. okresjest normalizowany przez setter workera do pełnych miesięcy (otwartyFrom/To→ pierwszy/ostatni dzień miesiąca); przy statycznymKopiujpodawaj zamkniętyFromTo.- Operacja seryjna na grupie pracowników = długa transakcja → dziel na paczki, trzymaj transakcje krótkie (safe-code §13.1).
KADRY-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):
// Tylko w warstwie UI: Context dostarcza zaznaczonych pracowników.
var worker = new AktualizujKalendarzWorker
{
Pracownicy = context.Get<Pracownik[]>(),
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:
Paramsdziedziczy poContextBase(ctor(Context)) — nie da się go zbudować bezContext. Dlatego KADRY-E3 nie ma „czystego" API bezkontekstowego; to operacja UI/serwerowa z zaznaczeniem.PracownicyiParssą set-only — nie odczytasz ich z powrotem; ustaw przedAktualizuj().- 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/Datadecydują, których zapisów historycznych dotyczy.
KADRY-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:
Etatleży na bieżącym zapisie historycznym (pracownik.Last.Etat : Soneta.Kadry.Etat, gdzieLast : PracHistoria) — nie ma propertypracownik.Etatbezpoś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ń):
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 normyEtat.NormaDobowa/Etat.Kalendarz.NormaDobowa— nie ma osobnego, edytowalnego pola „początek doby" na pojedynczymDzienPracy. Dzieńworkera pojedynczego jest set-only;Pracownicy/Parsworkera grupowego również.- Worker grupowy
ParamstoContextBase(ctor(Context)) — wymagaContext(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 zapiszSave().
KADRY-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):
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:
KalkulatorPracownikanie jestRow— to obiekt liczący (zwykłyobject). Nie zapisuje się, nie wymaga transakcji; to czysty odczyt. Pobieraj go zawsze przezpracownik.Czasy(ma kontekst pracownika), nie twórz ręcznie ctorem chyba że maszPracownik+ ewentualnyLog.- Parametr
conditionto serwerowy filtr (Item[], RowCondition) — można zawęzić np. do strefy; zwykle pusty. Norma= plan,Praca= realizacja; nie mylPraca(okres)(statystyka) zDzienPracy(rekord dnia).- Wynik
CzasDni.Invalidsygnalizuje brak danych/błąd okresu — sprawdzaj zanim policzysz różnice.