using System.Linq;
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.Business;
using Soneta.Kadry;
using Soneta.Kalend;
using Soneta.Place;
using Soneta.Types;
using Prac = Soneta.Kadry.Pracownik;
namespace Soneta.Skills.Test.KadryPlace.Pracownik;
///
/// Rozdział D (część dalsza) — „Nieobecności i czas pracy" (receptury D3–D12).
///
/// Testy są wykonywalną dokumentacją publicznego kontraktu platformy Soneta dla zaawansowanej
/// obsługi nieobecności pracownika: zwolnień ZUS (e-ZLA), deklaracji Z-3/Z-3a, przestoju, parametrów
/// okresu zasiłkowego, naliczania limitów, podstaw nieobecności, bilansu otwarcia, wniosków urlopowych
/// i pracy zdalnej. Każda metoda mapuje się 1:1 do receptury z dokumentu skilla pracownik.md:
///
/// - D3 — model danych e-ZLA (Nieobecnosc.Zwolnienie: ZwolnienieZUS, Nieobecnosc.ZLA: ZLA); sam import sieciowy → [Ignore];
/// - D4 — deklaracje Z-3 / Z-3a (workery Z3Worker/Z3aWorker — wymagają naliczonej podstawy);
/// - D5 — przestój (DodajPrzestojWorker, IndywidualnyProcentWynagrPrzestojowegoWorker);
/// - D6 — parametry okresu zasiłkowego (Zwolnienie.KontynuacjaOkrZas/PrzedluzenieOkrZas);
/// - D8 — naliczanie + przeliczanie limitów (NaliczanieLimitow.DodajLimit(), PrzeliczWykorzystaneWorker);
/// - D9 — podstawy nieobecności (pracownik.PodstawyNieobecności — odczyt; dodawanie → [Ignore]);
/// - D10 — bilans otwarcia (PracHistoria.ChorobowyBO, PracHistoria.DodatkowyBO);
/// - D11 — wnioski urlopowe (WniosekUrlopowy, PlanowanaNieobecność);
/// - D12 — praca zdalna (PracHistoria.PracaZdalna, LokalizacjaPracyZdalnej).
///
///
///
/// Wszystko działa na bazie Demo (GoldStandard) z automatycznym rollbackiem po teście. Operujemy
/// wyłącznie na publicznym kontrakcie — tak jak dodatek programisty zewnętrznego bez dostępu
/// do kodu źródłowego aplikacji. Operacje wymagające sieci (import e-ZLA) lub naliczonej wypłaty
/// (kwoty zasiłku/przestoju, sensowne kwoty deklaracji Z-3, dodawanie podstaw) są oznaczone [Ignore]
/// z asercją na model danych tam, gdzie się da.
///
///
[TestFixture]
public class RozdzialDrest_NieobecnosciTest : PracownikTestBase
{
private const string DefZwolnienieChor = "Zwolnienie chorobowe";
private const string DefUrlopWyp = "Urlop wypoczynkowy";
// Definicja nieobecności NIEwymagająca naliczonego limitu — bezpieczna dla wniosków bez naliczania limitu.
private const string DefBezplatny = "Urlop bezpłatny (art 174 kp)";
// ============================== D3 — Import e-ZLA (model danych) ==============================
[Test]
[Description("D3: dane ZUS zwolnienia leżą w subrowie Nieobecnosc.Zwolnienie typu ZwolnienieZUS, " +
"a dane dokumentu ZLA w subrowie Nieobecnosc.ZLA typu ZLA — odwzorowujemy e-ZLA jako " +
"NieobecnośćPracownika z definicją zasiłkową i ustawiamy pola subrowów (bez sieci).")]
public void D3_ModelDanychEZLA_ZwolnienieIZLAToSubrowyNieobecnosci()
{
var pracownik = Pracownik(Pracownik_.Andrzejewski);
pracownik.Should().NotBeNull("pracownik z Demo istnieje");
var defChor = Kalend.DefNieobecnosci.WgNazwy[DefZwolnienieChor] as DefinicjaNieobecnosci;
defChor.Should().NotBeNull($"definicja zasiłkowa '{DefZwolnienieChor}' istnieje w bazie Demo");
var okres = new FromTo(new Date(2026, 5, 4), new Date(2026, 5, 10));
InTransaction(() =>
{
var nieob = Session.AddRow(new NieobecnośćPracownika(pracownik));
nieob.Definicja = defChor;
nieob.Okres = okres;
// Subrowy Zwolnienie / ZLA są częścią rekordu — nie tworzymy ich osobno, ustawiamy pola.
nieob.Zwolnienie.Numer = "ZLA000001"; // pole Numer ma limit 9 znaków
nieob.Zwolnienie.KodChoroby = "A";
nieob.Zwolnienie.Przyczyna = PrzyczynaZwolnienia.ZwolnienieLekarskie;
nieob.ZLA.Zrodlo = "Import PUE (odwzorowanie testowe)";
});
SaveDispose();
var pracownik2 = Pracownik(Pracownik_.Andrzejewski);
var maj = new FromTo(new Date(2026, 5, 1), new Date(2026, 5, 31));
var zapisana = pracownik2.Nieobecnosci.GetIntersectedRows(maj).Cast().Single();
zapisana.Definicja.Nazwa.Should().Be(DefZwolnienieChor);
zapisana.Zwolnienie.Numer.Should().Be("ZLA000001", "dane ZUS z subrowa Zwolnienie zostały utrwalone");
zapisana.Zwolnienie.KodChoroby.Should().Be("A");
zapisana.Zwolnienie.Przyczyna.Should().Be(PrzyczynaZwolnienia.ZwolnienieLekarskie);
zapisana.ZLA.Zrodlo.ToString().Should().Contain("Import PUE", "dane dokumentu ZLA z subrowa ZLA zostały utrwalone");
}
[Test]
[Ignore("Sam import e-ZLA z PUE ZUS jest operacją SIECIOWĄ (uwierzytelnienie + bramka ZUS) — nie da się " +
"go odtworzyć w teście jednostkowym na bazie Demo. Model danych (subrowy Zwolnienie/ZLA) jest " +
"pokryty przez D3_ModelDanychEZLA_ZwolnienieIZLAToSubrowyNieobecnosci.")]
[Description("D3: import e-ZLA z PUE — niewykonalny bez sieci.")]
public void D3_ImportEZLA_ZPUE_WymagaSieci_Niewykonalne()
{
}
// ============================== D4 — Deklaracje Z-3 / Z-3a ==============================
[Test]
[Ignore("Sensowny Z-3 wymaga NALICZONEJ wypłaty/podstawy zasiłku — na czystej Demo z rollbackiem, bez " +
"pełnego scenariusza naliczenia listy płac, deklaracja powstałaby z pustymi kwotami, a worker " +
"Z3Worker przyjmuje dane przez Context (KeduContext + Z3ParamContext) i wykonuje logikę KEDU. " +
"Testowalny jest jedynie fakt istnienia workera (sprawdzane przez D4_Z3Worker_TypIstnieje).")]
[Description("D4: generowanie deklaracji Z-3 przez worker — niewykonalne bez naliczonej podstawy zasiłku.")]
public void D4_GenerowanieZ3_PrzezWorker_Niewykonalne()
{
}
[Test]
[Description("D4: workery deklaracji Z-3 / Z-3a istnieją w publicznym kontrakcie (typy " +
"Soneta.Deklaracje.ZUS.ZUSZ3.Z3Worker / Z3aWorker) — dokumentujemy ich dostępność.")]
public void D4_Z3Worker_TypIstnieje()
{
// Workery są w osobnym assembly Soneta.Deklaracje.ZUS — sprawdzamy obecność typu po pełnej nazwie.
var z3 = System.Type.GetType("Soneta.Deklaracje.ZUS.ZUSZ3.Z3Worker, Soneta.Deklaracje.ZUS")
?? FindByFullName("Soneta.Deklaracje.ZUS.ZUSZ3.Z3Worker");
var z3a = System.Type.GetType("Soneta.Deklaracje.ZUS.ZUSZ3.Z3aWorker, Soneta.Deklaracje.ZUS")
?? FindByFullName("Soneta.Deklaracje.ZUS.ZUSZ3.Z3aWorker");
z3.Should().NotBeNull("worker Z-3 (Z3Worker) jest dostępny w publicznym kontrakcie");
z3a.Should().NotBeNull("worker Z-3a (Z3aWorker) jest dostępny w publicznym kontrakcie");
z3!.GetMethod("UtworzDeklaracjeZ3").Should().NotBeNull("Z3Worker eksponuje akcję UtworzDeklaracjeZ3");
}
private static System.Type FindByFullName(string fullName) =>
System.AppDomain.CurrentDomain.GetAssemblies()
.Select(a => { try { return a.GetType(fullName); } catch { return null; } })
.FirstOrDefault(t => t != null);
// ============================== D5 — Przestój ==============================
[Test]
[Description("D5: przestój dodajemy workerem DodajPrzestojWorker — ustawiamy Pracownicy oraz " +
"Pars (DefinicjaStrefy + Okres); worker wykonuje własną transakcję. Strefę przestoju " +
"pobieramy dynamicznie ze słownika DefinicjeStref danej bazy.")]
public void D5_DodajPrzestoj_PrzezWorker()
{
var pracownik = Pracownik(Pracownik_.Bednarek);
// Definicja strefy przestoju — słownik danej bazy; nazwa może się różnić, więc szukamy elastycznie.
var defStrefa = Kalend.DefinicjeStref.Cast()
.FirstOrDefault(d => d.Nazwa != null && d.Nazwa.Contains("rzestój"));
if (defStrefa == null)
{
Assert.Ignore("Brak strefy przestoju w słowniku DefinicjeStref bazy Demo — receptura D5 niewykonalna na tej bazie.");
return;
}
var worker = new DodajPrzestojWorker
{
Pracownicy = new[] { pracownik },
Pars = new DodajPrzestojWorker.Params(Context)
{
DefinicjaStrefy = defStrefa,
Okres = new FromTo(new Date(2026, 6, 1), new Date(2026, 6, 5))
}
};
// Worker wykonuje własną transakcję — wywołujemy poza otwartą transakcją edycyjną.
worker.DodajPrzestoj();
SaveDispose();
// Przestój materializuje się jako strefa w planie pracy — weryfikujemy spójnie, że operacja
// nie rzuciła wyjątku i pracownik jest nadal odczytywalny (skutki płacowe liczą się przy wypłacie).
Pracownik(Pracownik_.Bednarek).Should().NotBeNull("przestój dodany bez błędu");
}
[Test]
[Description("D5: indywidualny procent wynagrodzenia przestojowego (przestój ekonomiczny) ustawiamy " +
"workerem IndywidualnyProcentWynagrPrzestojowegoWorker — Pars.Data + Pars.Procent (ułamek). " +
"Procent jest też trzymany na etacie: PracHistoria.Etat.Postojowe.Procent.")]
public void D5_ProcentWynagrPrzestojowego_PrzezWorker_OdkladaSieNaEtacie()
{
var pracownik = Pracownik(Pracownik_.Bujak);
var worker = new IndywidualnyProcentWynagrPrzestojowegoWorker
{
Pracownicy = new[] { pracownik },
Pars = new IndywidualnyProcentWynagrPrzestojowegoWorker.Params(Context)
{
Data = new Date(2026, 6, 1),
Procent = new Percent(0.5m) // 50% — Percent przyjmujemy jako ułamek, nie liczbę 50
}
};
worker.Aktualizuj();
SaveDispose();
// Procent przestojowego odkłada się na etacie (PracHistoria.Etat.Postojowe).
var pracownik2 = Pracownik(Pracownik_.Bujak);
var historia = pracownik2.Historia[new Date(2026, 6, 1)];
historia.Should().NotBeNull("istnieje zapis historyczny na czerwiec 2026");
historia.Etat.Postojowe.Procent.Should().Be(new Percent(0.5m),
"procent wynagrodzenia przestojowego został zapisany na etacie jako 50%");
}
// ============================== D6 — Parametry okresu zasiłkowego ==============================
[Test]
[Description("D6: parametry okresu zasiłkowego są bazodanowymi polami subrowa Nieobecnosc.Zwolnienie: " +
"KontynuacjaOkrZas (enum), PrzedluzenieOkrZas (bool), PrzedluzeniaData (Date) oraz " +
"flaga PonownieUstalPodstawe ustawiana metodą SetPonownieUstalPodstawe(bool).")]
public void D6_ParametryOkresuZasilkowego_ZapisNaSubrowieZwolnienie()
{
var pracownik = Pracownik(Pracownik_.Strzelecki);
var defChor = Kalend.DefNieobecnosci.WgNazwy[DefZwolnienieChor] as DefinicjaNieobecnosci;
var okres = new FromTo(new Date(2026, 5, 4), new Date(2026, 5, 31));
InTransaction(() =>
{
var nieob = Session.AddRow(new NieobecnośćPracownika(pracownik));
nieob.Definicja = defChor;
nieob.Okres = okres;
});
SaveDispose();
// Zmiana parametrów okresu zasiłkowego wprost na rekordzie.
InTransaction(() =>
{
var pracownikE = Pracownik(Pracownik_.Strzelecki);
var nieob = (Nieobecnosc)pracownikE.Nieobecnosci.GetIntersectedRows(okres)[0];
nieob.Zwolnienie.KontynuacjaOkrZas = KontynuacjaOkrZas.Tak;
nieob.Zwolnienie.PrzedluzenieOkrZas = true;
nieob.Zwolnienie.PrzedluzeniaData = new Date(2026, 5, 31);
nieob.Zwolnienie.SetPonownieUstalPodstawe(true);
});
SaveDispose();
var pracownik2 = Pracownik(Pracownik_.Strzelecki);
var zapisana = pracownik2.Nieobecnosci.GetIntersectedRows(okres).Cast().Single();
zapisana.Zwolnienie.KontynuacjaOkrZas.Should().Be(KontynuacjaOkrZas.Tak);
zapisana.Zwolnienie.PrzedluzenieOkrZas.Should().BeTrue("okres zasiłkowy oznaczono jako przedłużony");
zapisana.Zwolnienie.PrzedluzeniaData.Should().Be(new Date(2026, 5, 31));
zapisana.Zwolnienie.PonownieUstalPodstawe.Should().BeTrue("flaga ponownego ustalenia podstawy ustawiona");
}
// ============================== D8 — Naliczanie i przeliczanie limitów ==============================
[Test]
[Description("D8: limit naliczamy NaliczanieLimitow.DodajLimit(), a liczbę wykorzystanych dni " +
"przeliczamy workerem LimitNieobecnosci.Pracownicy.PrzeliczWykorzystaneWorker " +
"(Pars.Definicja + Pars.Okres). Po przeliczeniu arytmetyka limitu pozostaje spójna.")]
public void D8_NaliczenieIPrzeliczenieLimitu()
{
var defLimit = Kalend.DefinicjeLimitow.WgNazwy[DefUrlopWyp] as DefinicjaLimitu;
defLimit.Should().NotBeNull($"definicja limitu '{DefUrlopWyp}' istnieje w bazie Demo");
var rok = FromTo.Year(new Date(2026, 1, 1));
// 1) Naliczenie limitu (jak D7).
InTransaction(() =>
{
var pracownikE = Pracownik(Pracownik_.Andrzejewski);
var naliczanie = new NaliczanieLimitow
{
Pars = new NaliczanieLimitow.Params(Context)
{
Definicja = defLimit,
Okres = rok,
KopiujKorekty = true
},
Pracownicy = new[] { pracownikE }
};
naliczanie.DodajLimit();
});
SaveDispose();
// 2) Przeliczenie wykorzystanych — worker wykonuje własną transakcję.
var przelicz = new LimitNieobecnosci.Pracownicy.PrzeliczWykorzystaneWorker
{
Pracownicy = new[] { Pracownik(Pracownik_.Andrzejewski) },
Pars = new LimitNieobecnosci.Pracownicy.PrzeliczWykorzystaneWorker.Params(Context)
{
Definicja = Kalend.DefinicjeLimitow.WgNazwy[DefUrlopWyp] as DefinicjaLimitu,
Okres = rok
}
};
przelicz.PrzeliczWykorzystane();
SaveDispose();
// 3) Odczyt limitu — arytmetyka spójna (Razem bywa 0 dla syntetycznych pracowników Demo).
var pracownik2 = Pracownik(Pracownik_.Andrzejewski);
var defLimit2 = Kalend.DefinicjeLimitow.WgNazwy[DefUrlopWyp] as DefinicjaLimitu;
var lim = pracownik2.Limity[(LimitNieobecnosci l) => l.Definicja == defLimit2]
.Cast()
.FirstOrDefault(l => l.Okres.From == rok.From);
lim.Should().NotBeNull("limit urlopu wypoczynkowego na 2026 został naliczony");
lim!.Wykorzystane.Should().Be(lim.Razem - lim.Pozostalo,
"po przeliczeniu wykorzystany jest spójny z Razem - Pozostalo");
lim.Wykorzystane.Should().BeGreaterThanOrEqualTo(0, "wykorzystane nie jest ujemne");
}
// ============================== D9 — Podstawy nieobecności (odczyt) ==============================
[Test]
[Description("D9 (odczyt): podstawy nieobecności ZUS / urlopu leżą w kolekcji child " +
"pracownik.PodstawyNieobecności (typ Soneta.Place.PodstawaNieobecnosci); filtrujemy " +
"serwerowo po polu Typ (Chorobowa / Wypoczynkowy). Na czystej Demo kolekcja może być pusta.")]
public void D9_OdczytPodstawNieobecnosci_FiltrPoTyp()
{
var pracownik = Pracownik(Pracownik_.Andrzejewski);
// Filtr serwerowy po Typ — nie iterujemy całości z if w pamięci.
var chorobowe = pracownik.PodstawyNieobecności[
(PodstawaNieobecnosci x) => x.Typ == TypyPodstawNieobecnosci.Chorobowa]
.Cast().ToList();
// Asercja na MODEL/spójność: każda zwrócona pozycja faktycznie ma Typ == Chorobowa,
// a relacja do pracownika jest spełniona (Pracownik to guided-parent, read-only).
chorobowe.Should().OnlyContain(p => p.Typ == TypyPodstawNieobecnosci.Chorobowa,
"filtr serwerowy zwraca wyłącznie podstawy chorobowe");
chorobowe.Should().OnlyContain(p => p.Pracownik == pracownik,
"podstawa należy do pracownika (relacja child)");
// Na czystej Demo (bez naliczonej wypłaty z zasiłkiem) kolekcja bywa pusta — to dopuszczalne.
}
[Test]
[Ignore("PodstawaNieobecnosci NIE ma publicznego ctora (jedynie (RowCreator) i (Pracownik, " +
"TypyPodstawNieobecnosci) — niepubliczne). Rekordy podstaw powstają z NALICZENIA WYPŁATY, " +
"więc ręczne dodanie podstawy nie jest możliwe przez publiczny kontrakt; testowalny jest " +
"wyłącznie odczyt (D9_OdczytPodstawNieobecnosci_FiltrPoTyp).")]
[Description("D9: ręczne dodanie podstawy nieobecności — niewykonalne (brak publicznego ctora).")]
public void D9_DodanieRecznePodstawy_Niewykonalne()
{
}
// ============================== D10 — Bilans otwarcia ==============================
[Test]
[Description("D10: bilans otwarcia chorobowy leży w subrowie zapisu PracHistoria.ChorobowyBO " +
"(okres zasiłkowy, dni). Edytujemy pola subrowa na właściwym zapisie historycznym " +
"'na dzień' (pracownik.Historia[data]).")]
public void D10_BilansOtwarcia_ChorobowyBO()
{
var data = new Date(2026, 1, 1);
InTransaction(() =>
{
var pracownikE = Pracownik(Pracownik_.Bednarek);
var historia = pracownikE.Historia[data];
historia.Should().NotBeNull("istnieje zapis historyczny obowiązujący na 2026-01-01");
// BO chorobowy / okres zasiłkowy
historia.ChorobowyBO.DniZasilkowe = 33;
historia.ChorobowyBO.ZasilekOdDnia = data;
historia.ChorobowyBO.PrzedluzenieOZ = true;
});
SaveDispose();
var pracownik2 = Pracownik(Pracownik_.Bednarek);
var historia2 = pracownik2.Historia[data];
historia2.ChorobowyBO.DniZasilkowe.Should().Be(33, "BO chorobowy: dni zasiłkowe");
historia2.ChorobowyBO.ZasilekOdDnia.Should().Be(data, "BO chorobowy: zasiłek od dnia");
historia2.ChorobowyBO.PrzedluzenieOZ.Should().BeTrue("BO chorobowy: przedłużenie okresu zasiłkowego");
}
[Test]
[Ignore("DodatkowyBO.UPoprzednich/BezPierwszego/Wykorzystany rzucają ColReadOnlyException na zwykłym " +
"zapisie historycznym z Demo (pole 'tylko do odczytu'). BO urlopowy jest zapisywalny tylko na " +
"zapisie historycznym oznaczonym jako bilans otwarcia / start zatrudnienia — czego nie da się " +
"odtworzyć na gotowych pracownikach Demo bez ingerencji w historię zatrudnienia. Pole ChorobowyBO " +
"jest pokryte przez D10_BilansOtwarcia_ChorobowyBO.")]
[Description("D10: bilans otwarcia urlopowy (DodatkowyBO) — niezapisywalny na zwykłym zapisie historii Demo.")]
public void D10_BilansOtwarcia_DodatkowyBO_ReadOnlyNaHistoriiDemo()
{
}
// ============================== D11 — Wnioski o urlop ==============================
[Test]
[Description("D11: wniosek urlopowy tworzymy ctorem WniosekUrlopowy(pracownik, definicja) + AddRow; " +
"ustawiamy Okres, Data, Stan (StanWnioskuUrlopowego). Wniosek trafia do kolekcji " +
"pracownik.WnioskiUrlopowe; akceptacja to zmiana Stan na Zaakceptowany + DataDecyzji. " +
"Używamy definicji NIEwymagającej limitu — akceptacja wniosku urlopu wypoczynkowego " +
"wyzwoliłaby przeliczenie limitu i LimitNotFoundException bez wcześniejszego naliczenia limitu.")]
public void D11_WniosekUrlopowy_RejestracjaIAkceptacja()
{
var pracownik = Pracownik(Pracownik_.Bujak);
var defUrlop = Kalend.DefNieobecnosci.WgNazwy[DefBezplatny] as DefinicjaNieobecnosci;
defUrlop.Should().NotBeNull($"definicja '{DefBezplatny}' istnieje w bazie Demo");
InTransaction(() =>
{
var wniosek = Session.AddRow(new WniosekUrlopowy(pracownik, defUrlop));
wniosek.Okres = new FromTo(new Date(2026, 8, 3), new Date(2026, 8, 7));
wniosek.Data = new Date(2026, 7, 20);
wniosek.Stan = StanWnioskuUrlopowego.Oczekujący;
wniosek.Pracownik.Should().BeSameAs(pracownik, "ctor wiąże wniosek z pracownikiem");
wniosek.Definicja.Should().BeSameAs(defUrlop, "ctor ustawia definicję nieobecności");
});
SaveDispose();
// Odczyt z kolekcji child + akceptacja (zmiana stanu).
InTransaction(() =>
{
var pracownikE = Pracownik(Pracownik_.Bujak);
var wniosek = pracownikE.WnioskiUrlopowe.Cast()
.First(w => w.Stan == StanWnioskuUrlopowego.Oczekujący);
wniosek.Stan = StanWnioskuUrlopowego.Zaakceptowany;
wniosek.DataDecyzji = new Date(2026, 7, 21);
});
SaveDispose();
var pracownik2 = Pracownik(Pracownik_.Bujak);
var zapisany = pracownik2.WnioskiUrlopowe.Cast().Single();
zapisany.Stan.Should().Be(StanWnioskuUrlopowego.Zaakceptowany, "wniosek został zaakceptowany");
zapisany.DataDecyzji.Should().Be(new Date(2026, 7, 21));
zapisany.Definicja.Nazwa.Should().Be(DefBezplatny);
}
[Test]
[Description("D11: planowana nieobecność (osobny model planu urlopów) — ctor PlanowanaNieobecność(pracownik) " +
"+ AddRow; Definicja, Okres. Pole Stan jest READ-ONLY (StanPlanowanejNieobecności) — zmieniamy " +
"je metodami przejść stanu (StanWprowadzona/StanZatwierdzona/StanAnulowana/StanOczekująca). " +
"Trafia do kolekcji pracownik.PlanowaneNieobecności (FromToSubTable).")]
public void D11_PlanowanaNieobecnosc_Rejestracja()
{
var pracownik = Pracownik(Pracownik_.Strzelecki);
// Definicja planowanej nieobecności MUSI mieć zaznaczone pole 'Planowana' — pobieramy dynamicznie.
var defPlan = Kalend.DefNieobecnosci.Cast().FirstOrDefault(d => d.Planowana);
if (defPlan == null)
{
Assert.Ignore("Brak definicji nieobecności z flagą 'Planowana' w bazie Demo — receptura niewykonalna.");
return;
}
var okres = new FromTo(new Date(2026, 9, 1), new Date(2026, 9, 5));
InTransaction(() =>
{
var plan = Session.AddRow(new PlanowanaNieobecność(pracownik));
plan.Definicja = defPlan;
plan.Okres = okres;
// Stan jest read-only — przejście stanu wykonujemy metodą domenową, nie przypisaniem.
plan.StanWprowadzona();
});
SaveDispose();
var pracownik2 = Pracownik(Pracownik_.Strzelecki);
var wrzesien = new FromTo(new Date(2026, 9, 1), new Date(2026, 9, 30));
var plany = pracownik2.PlanowaneNieobecności.GetIntersectedRows(wrzesien)
.Cast().ToList();
plany.Should().ContainSingle("dodaliśmy jedną planowaną nieobecność we wrześniu 2026")
.Which.Stan.Should().Be(StanPlanowanejNieobecności.Wprowadzona,
"po StanWprowadzona() plan jest w stanie Wprowadzona");
}
[Test]
[Description("D11: wniosek o delegację jest subrowem wniosku urlopowego (WniosekUrlopowy.Delegacja " +
"typu WniosekODelegację) — ustawiamy pola delegacji na tym subrowie.")]
public void D11_WniosekODelegacje_JestSubrowemWniosku()
{
var pracownik = Pracownik(Pracownik_.Andrzejewski);
var defUrlop = Kalend.DefNieobecnosci.WgNazwy[DefUrlopWyp] as DefinicjaNieobecnosci;
InTransaction(() =>
{
var wniosek = Session.AddRow(new WniosekUrlopowy(pracownik, defUrlop));
wniosek.Okres = new FromTo(new Date(2026, 10, 5), new Date(2026, 10, 9));
wniosek.Data = new Date(2026, 9, 30);
// Delegacja to subrow — ustawiamy jego pola (cel, planowana zaliczka).
wniosek.Delegacja.Cel = "Spotkanie z klientem";
wniosek.Delegacja.WnioskowanaZaliczka = new Currency(500m);
});
SaveDispose();
var pracownik2 = Pracownik(Pracownik_.Andrzejewski);
var zapisany = pracownik2.WnioskiUrlopowe.Cast().Single();
zapisany.Delegacja.Cel.ToString().Should().Contain("klientem", "cel delegacji zapisany na subrowie");
zapisany.Delegacja.WnioskowanaZaliczka.Should().Be(new Currency(500m));
}
// ============================== D12 — Praca zdalna ==============================
[Test]
[Description("D12: parametry pracy zdalnej (model pracy, oświadczenie o warunkach) leżą na " +
"historycznym zapisie etatu: PracHistoria.PracaZdalna (typ PracZdalna). Edytujemy je " +
"na właściwym zapisie 'na dzień' (pracownik.Historia[data]).")]
public void D12_ModelPracyZdalnej_NaHistoriiEtatu()
{
var data = new Date(2026, 6, 1);
InTransaction(() =>
{
var pracownikE = Pracownik(Pracownik_.Bednarek);
var historia = pracownikE.Historia[data];
historia.PracaZdalna.ModelPracy = ModelPracy.PracaHybrydowa;
historia.PracaZdalna.OswiadczenieWarunki = true;
});
SaveDispose();
var pracownik2 = Pracownik(Pracownik_.Bednarek);
var historia2 = pracownik2.Historia[data];
historia2.PracaZdalna.ModelPracy.Should().Be(ModelPracy.PracaHybrydowa, "ustawiono model pracy hybrydowej");
historia2.PracaZdalna.OswiadczenieWarunki.Should().BeTrue("oświadczenie o warunkach lokalowych ustawione");
}
[Test]
[Description("D12: lokalizacja pracy zdalnej ma publiczny ctor LokalizacjaPracyZdalnej(pracownik) — " +
"tworzymy ją + AddRow i ustawiamy adres (subrow Adres). Trafia do kolekcji " +
"pracownik.LokalizacjePracyZdalnej.")]
public void D12_LokalizacjaPracyZdalnej_PublicznyCtor()
{
var pracownik = Pracownik(Pracownik_.Bujak);
InTransaction(() =>
{
var lok = Session.AddRow(new LokalizacjaPracyZdalnej(pracownik));
lok.Adres.Miejscowosc = "Kraków";
lok.Adres.Ulica = "Wadowicka";
});
SaveDispose();
var pracownik2 = Pracownik(Pracownik_.Bujak);
var lokalizacje = pracownik2.LokalizacjePracyZdalnej.Cast().ToList();
lokalizacje.Should().ContainSingle("dodaliśmy jedną lokalizację pracy zdalnej")
.Which.Adres.Miejscowosc.Should().Be("Kraków", "adres lokalizacji został zapisany");
}
[Test]
[Description("D12 (odczyt): ewidencję pracy zdalnej okazjonalnej prezentuje worker ODCZYTOWY " +
"Soneta.Kadry.Pracownik.PracaZdalnaWorker (property bez akcji modyfikującej): " +
"DniPracyZdalnejRazem, LimitPracaZdalnaOkazjonalna, PozostaloPracaZdalnaOkazjonalna. " +
"Inicjujemy Pracownik + Okres i odczytujemy spójne, nieujemne wartości.")]
public void D12_PracaZdalnaWorker_OdczytEwidencji()
{
var pracownik = Pracownik(Pracownik_.Andrzejewski);
var worker = new Prac.PracaZdalnaWorker
{
Pracownik = pracownik,
Okres = FromTo.Year(new Date(2026, 1, 1))
};
// Worker odczytowy — property liczone z planu/ewidencji; weryfikujemy spójność wartości.
worker.DniPracyZdalnejRazem.Should().BeGreaterThanOrEqualTo(0, "liczba dni pracy zdalnej nie jest ujemna");
worker.LimitPracaZdalnaOkazjonalna.Should().BeGreaterThanOrEqualTo(0, "limit pracy zdalnej okazjonalnej nie jest ujemny");
worker.PozostaloPracaZdalnaOkazjonalna.Should().BeLessThanOrEqualTo(worker.LimitPracaZdalnaOkazjonalna,
"pozostały limit nie przekracza limitu całkowitego");
}
[Test]
[Ignore("WniosekPracyZdalnej ma NIEPUBLICZNE ctory — w teście jednostkowym nie utworzysz go przez new; " +
"zlecenie pracy zdalnej idzie przez worker GrupoweZleceniePracyZdalnejWorker (czynność Net/UI " +
"wymagająca pełnego Contextu Pulpitu). Testowalne wprost: ModelPracy/OswiadczenieWarunki na " +
"PracHistoria.PracaZdalna (D12_ModelPracyZdalnej_NaHistoriiEtatu) oraz LokalizacjaPracyZdalnej " +
"(D12_LokalizacjaPracyZdalnej_PublicznyCtor).")]
[Description("D12: rejestracja wniosku o pracę zdalną — niewykonalna przez new (ctory niepubliczne).")]
public void D12_WniosekPracyZdalnej_NiepublicznyCtor_Niewykonalne()
{
}
}