Files

19 KiB
Raw Permalink Blame History

KADRY05 — Plan pracy i kalendarz

Wspólne fakty o typie, podstawowe typy i szablon wzorca: ../kadry.md.

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 : DateSubTableplan/harmonogram (dni DzienPlanu : DzienKalendarzaBase); to pracownik.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.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 KADRY-E2.

KADRY-E1 — Wprowadzanie planowanego czasu pracy (★)

Cel: odczytać lub ustawić plan pracy (harmonogram) pracownika na konkretny dzień — godziny oddo, 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 oddo

    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 oddo 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, 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 bezkontekstowegoKopiujWorker 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).

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:

  • Params dziedziczy po ContextBase (ctor (Context)) — nie da się go zbudować bez Context. Dlatego KADRY-E3 nie ma „czystego" API bezkontekstowego; to operacja UI/serwerowa z zaznaczeniem.
  • Pracownicy i Parsset-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.

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: 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ń):

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 (oddo) i może rozbić pracę na dwie doby — wykonuj w transakcji (Logout(editMode:true) + Commit) i zapisz Save().

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:

  • 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.