using System.Linq;
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.Business;
using Soneta.HR;
using Soneta.Kadry;
using Soneta.Types;
using Prac = Soneta.Kadry.Pracownik;
namespace Soneta.Skills.Test.KadryPlace.Pracownik;
///
/// Rozdział K (część pierwsza) — „Ewidencje pracownicze" (receptury K1–K5).
///
/// Testy są wykonywalną dokumentacją publicznego kontraktu platformy Soneta dla ewidencji
/// pracowniczych. Wszystkie ewidencje mają wspólny wzorzec: są kolekcjami SubTable na rootcie
/// Pracownik (nie na PracHistoria), a każdy wpis to osobny GuidedRow tworzony
/// konstruktorem new Xxx(pracownik), który wiąże wpis z pracownikiem. Dodanie realizujemy
/// przez Session.AddRow(new Xxx(pracownik)) (równoważne pracownik.Kolekcja.AddRow(...)).
/// Każda metoda mapuje się 1:1 do receptury z dokumentu skilla pracownik.md:
///
/// - K1 — badania lekarskie (new BadanieLekarskie(pracownik), pracownik.BadaniaLekarskie; pole WazneDo bez „ż");
/// - K2 — szkolenia BHP (new SzkolenieBHP(pracownik), pracownik.SzkoleniaBHP; pole WażneDo z „ż");
/// - K3 — szkolenia i uprawnienia HR (WniosekOSzkolenie/UkończoneSzkolenie/UprawnieniePracownika — moduł Soneta.HR);
/// - K4 — nagrody/kary (new Nagroda/Kara(pracownik), abstr. NagrodaKara) i oświadczenia (OświadczeniePracownika(pracownik, def[, data]));
/// - K5 — wypadki przy pracy (new Wypadek(pracownik), pracownik.Wypadki).
///
///
///
/// 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. Większość wpisów wymaga definicji (rekord słownikowy z tabeli
/// konfiguracyjnej) — definicję pobieramy dynamicznie (pierwsza z tabeli / po nazwie), a gdy w Demo
/// brak wymaganej definicji, test jest oznaczany Assert.Ignore z powodem.
///
///
[TestFixture]
public class RozdzialK1_EwidencjeTest : PracownikTestBase
{
// Pracownik-host dla wpisów ewidencyjnych — dowolny etatowy z Demo.
private Prac Host() => Pracownik(Pracownik_.Andrzejewski) ?? PierwszyPracownik();
// Pierwsza definicja z tabeli konfiguracyjnej (lub null) — bez twardej zależności od nazwy słownika.
private static T Pierwsza(Table tabela) where T : Row =>
tabela.Cast().FirstOrDefault();
// ============================== K1 — Badania lekarskie ==============================
[Test]
[Description("K1: new BadanieLekarskie(pracownik) wiąże wpis z pracownikiem; Definicja (DefBadanLek) " +
"jest wymagana; Data/Termin/WazneDo to Soneta.Types.Date (WazneDo BEZ z-kreska); wpis trafia " +
"do pracownik.BadaniaLekarskie.")]
public void K1_BadanieLekarskie_DodanieZDefinicja_TrafiaDoKolekcji()
{
var definicja = Pierwsza(Kadry.DefBadanLek);
if (definicja == null)
Assert.Ignore("Brak definicji badania lekarskiego (DefBadanLek) w bazie Demo — wpisu nie można utworzyć.");
var pracownik = Host();
Soneta.Kadry.BadanieLekarskie badanie = null;
InTransaction(() =>
{
// Konstruktor (Pracownik) wiąże wpis z pracownikiem; AddRow == pracownik.BadaniaLekarskie.AddRow.
badanie = Session.AddRow(new Soneta.Kadry.BadanieLekarskie(pracownik));
badanie.Definicja = definicja; // WYMAGANA — bez niej Save() rzuci RowException
badanie.Data = Date.Today;
// Termin jest WYLICZANY (read-only) z Data + definicji — nie ustawiamy go ręcznie.
// Uwaga na pisownię: w BadanieLekarskie pole nazywa się WazneDo (BEZ „ż").
badanie.WazneDo = new Date(Date.Today.Year + 2, Date.Today.Month, Date.Today.Day);
});
badanie.Pracownik.Should().Be(pracownik, "ctor (Pracownik) ustawia pole Pracownik");
badanie.Definicja.Should().Be(definicja);
pracownik.BadaniaLekarskie.Cast()
.Should().Contain(badanie, "wpis trafia do kolekcji SubTable pracownika");
}
[Test]
[Description("K1: pracownik.Badania to manager (BadaniaLekarskieManager) tylko do odczytu — inny obiekt " +
"niż kolekcja CRUD pracownik.BadaniaLekarskie (SubTable).")]
public void K1_Badania_ManagerOdczytu_RozniSieOdKolekcjiCrud()
{
var pracownik = Host();
pracownik.Badania.Should().NotBeNull("manager Badania jest zawsze dostępny (odczyt)");
pracownik.Badania.Should().BeOfType();
// Kolekcja CRUD to osobne API — SubTable.
pracownik.BadaniaLekarskie.Should().NotBeNull();
}
// ============================== K2 — Szkolenia BHP ==============================
[Test]
[Description("K2: new SzkolenieBHP(pracownik) + Definicja (DefSzkolenBHP, wymagana); pole ważności to " +
"WażneDo (Z z-kreska) - w przeciwieństwie do K1; wpis trafia do pracownik.SzkoleniaBHP.")]
public void K2_SzkolenieBHP_DodanieZDefinicja_TrafiaDoKolekcji()
{
var definicja = Pierwsza(Kadry.DefSzkolenBHP);
if (definicja == null)
Assert.Ignore("Brak definicji szkolenia BHP (DefSzkolenBHP) w bazie Demo — wpisu nie można utworzyć.");
var pracownik = Host();
Soneta.Kadry.SzkolenieBHP szkolenie = null;
InTransaction(() =>
{
szkolenie = Session.AddRow(new Soneta.Kadry.SzkolenieBHP(pracownik));
szkolenie.Definicja = definicja;
szkolenie.Data = Date.Today;
// Termin jest WYLICZANY (read-only) z Data + definicji — nie ustawiamy go ręcznie.
szkolenie.Zakres = "Instruktaż ogólny";
szkolenie.Osoba = "Prowadzący BHP";
});
szkolenie.Pracownik.Should().Be(pracownik);
szkolenie.Definicja.Should().Be(definicja);
szkolenie.Zakres.Should().Be("Instruktaż ogólny");
pracownik.SzkoleniaBHP.Cast().Should().Contain(szkolenie);
}
// ============================== K3 — Szkolenia i uprawnienia (HR) ==============================
[Test]
[Description("K3a: WniosekOSzkolenie([Required] Pracownik) z modułu Soneta.HR (session.GetHR()); Definicja " +
"(DefinicjeSzkolen) + Etap (EtapRealizSzkol) to słowniki HR; Koszt to Soneta.Types.Currency.")]
public void K3a_WniosekOSzkolenie_DodanieZBudzetemIKosztem_TrafiaDoKolekcji()
{
var hr = Session.GetHR();
var definicja = Pierwsza(hr.DefinicjeSzkolen);
if (definicja == null)
Assert.Ignore("Brak definicji szkolenia HR (DefinicjeSzkolen) w bazie Demo — wniosku nie można utworzyć.");
var pracownik = Host();
WniosekOSzkolenie wniosek = null;
InTransaction(() =>
{
wniosek = Session.AddRow(new WniosekOSzkolenie(pracownik));
wniosek.Definicja = definicja;
// Etap jest opcjonalny do zapisu — ustawiamy gdy słownik niepusty.
var etap = Pierwsza(hr.EtapRealizSzkol);
if (etap != null)
wniosek.Etap = etap;
wniosek.DataZgloszenia = Date.Today;
wniosek.Koszt = new Currency(1500m); // Currency, nie decimal
});
wniosek.Pracownik.Should().Be(pracownik);
wniosek.Definicja.Should().Be(definicja);
wniosek.Koszt.Value.Should().Be(1500m);
pracownik.WnioskiOSzkolenia.Cast().Should().Contain(wniosek);
}
[Test]
[Description("K3b: UkończoneSzkolenie([Required] Pracownik) — moduł HR; pola Nazwa/Okres(FromTo)/Ocena; " +
"wpis trafia do pracownik.UkończoneSzkolenia. Drugi ctor (WniosekOSzkolenie) przepina pracownika.")]
public void K3b_UkonczoneSzkolenie_DodanieZPracownika_TrafiaDoKolekcji()
{
var pracownik = Host();
UkończoneSzkolenie ukonczone = null;
InTransaction(() =>
{
ukonczone = Session.AddRow(new UkończoneSzkolenie(pracownik));
ukonczone.Nazwa = "Kurs BHP – aktualizacja";
ukonczone.Okres = new FromTo(Date.Today, Date.Today);
ukonczone.Ocena = "bardzo dobry";
});
ukonczone.Pracownik.Should().Be(pracownik);
ukonczone.Nazwa.Should().Be("Kurs BHP – aktualizacja");
pracownik.UkończoneSzkolenia.Cast().Should().Contain(ukonczone);
}
[Test]
[Description("K3c: UprawnieniePracownika([Required] Pracownik) — moduł HR; Definicja (DefUprawnien, słownik), " +
"Numer, DataUzyskania/TerminWaznosci (Date); wpis trafia do pracownik.Uprawnienia.")]
public void K3c_UprawnieniePracownika_DodanieZDefinicja_TrafiaDoKolekcji()
{
var hr = Session.GetHR();
var definicja = Pierwsza(hr.DefUprawnien);
if (definicja == null)
Assert.Ignore("Brak definicji uprawnienia HR (DefUprawnien) w bazie Demo — uprawnienia nie można utworzyć.");
var pracownik = Host();
UprawnieniePracownika uprawnienie = null;
InTransaction(() =>
{
uprawnienie = Session.AddRow(new UprawnieniePracownika(pracownik));
uprawnienie.Definicja = definicja;
uprawnienie.Numer = "UP/2026/001";
uprawnienie.DataUzyskania = Date.Today;
uprawnienie.TerminWaznosci = new Date(Date.Today.Year + 5, Date.Today.Month, Date.Today.Day);
});
uprawnienie.Pracownik.Should().Be(pracownik);
uprawnienie.Definicja.Should().Be(definicja);
uprawnienie.Numer.Should().Be("UP/2026/001");
pracownik.Uprawnienia.Cast().Should().Contain(uprawnienie);
}
// ============================== K4 — Nagrody/kary; oświadczenia ==============================
[Test]
[Description("K4a: NagrodaKara jest ABSTRAKCYJNA — używamy podtypu new Nagroda(pracownik); ctor ustawia " +
"Typ na Nagroda; Definicja to słownik DefNagrodKar; wpis trafia do pracownik.NagrodyKary.")]
public void K4a_Nagroda_DodaniePodtypuKonkretnego_UstawiaTypNagroda()
{
// Definicja musi zgadzać się typem z wpisem — dla Nagrody bierzemy definicję o Typ == Nagroda
// (przypisanie niezgodnej typem definicji rzuca ArgumentException w set_Definicja).
var definicja = Kadry.DefNagrodKar.Cast()
.FirstOrDefault(d => d.Typ == TypNagrodyKary.Nagroda);
if (definicja == null)
Assert.Ignore("Brak definicji typu Nagroda (DefNagrodKar) w bazie Demo — wpisu nie można utworzyć.");
var pracownik = Host();
Nagroda nagroda = null;
InTransaction(() =>
{
// NIE new NagrodaKara(...) — typ abstrakcyjny. Konkretny podtyp ustawia Typ.
nagroda = Session.AddRow(new Nagroda(pracownik));
nagroda.Definicja = definicja;
nagroda.Data = Date.Today;
});
nagroda.Pracownik.Should().Be(pracownik);
nagroda.Typ.Should().Be(TypNagrodyKary.Nagroda, "ctor podtypu Nagroda ustawia pole Typ");
pracownik.NagrodyKary.Cast().Should().Contain(nagroda);
}
[Test]
[Description("K4a: konkretny podtyp Kara ustawia Typ na Kara; oba podtypy trafiają do tej samej kolekcji " +
"pracownik.NagrodyKary (SubTable).")]
public void K4a_Kara_DodaniePodtypuKonkretnego_UstawiaTypKara()
{
// Dla Kary bierzemy definicję o Typ == Kara (analogicznie do Nagrody).
var definicja = Kadry.DefNagrodKar.Cast()
.FirstOrDefault(d => d.Typ == TypNagrodyKary.Kara);
if (definicja == null)
Assert.Ignore("Brak definicji typu Kara (DefNagrodKar) w bazie Demo — wpisu nie można utworzyć.");
var pracownik = Host();
Kara kara = null;
InTransaction(() =>
{
kara = Session.AddRow(new Kara(pracownik));
kara.Definicja = definicja;
kara.Data = Date.Today;
});
kara.Typ.Should().Be(TypNagrodyKary.Kara, "ctor podtypu Kara ustawia pole Typ");
pracownik.NagrodyKary.Cast().Should().Contain(kara);
}
[Test]
[Description("K4b: OświadczeniePracownika NIE ma ctora samego (Pracownik) — Definicja jest [Required] " +
"w konstruktorze; wariant (pracownik, definicja, Date) ustawia DataZlozenia; słownik DefOswiadczen.")]
public void K4b_Oswiadczenie_DodanieZWymaganaDefinicjaIData_TrafiaDoKolekcji()
{
// Preferuj PIT-2, ale dowolna definicja oświadczenia wystarcza (ctor wymaga definicji).
var definicja = Kadry.DefOswiadczen.Cast().FirstOrDefault(d => d.Nazwa == "PIT-2")
?? Pierwsza(Kadry.DefOswiadczen);
if (definicja == null)
Assert.Ignore("Brak definicji oświadczenia (DefOswiadczen) w bazie Demo — oświadczenia nie można utworzyć (definicja jest [Required] w ctorze).");
var pracownik = Host();
OświadczeniePracownika oswiadczenie = null;
InTransaction(() =>
{
// Definicja przekazywana w konstruktorze (nie ustawiana po fakcie); wariant z datą złożenia.
oswiadczenie = Session.AddRow(new OświadczeniePracownika(pracownik, definicja, Date.Today));
});
oswiadczenie.Pracownik.Should().Be(pracownik);
oswiadczenie.Definicja.Should().Be(definicja, "definicja jest przekazywana w ctorze");
oswiadczenie.DataZlozenia.Should().Be(Date.Today, "wariant ctora z Date ustawia DataZlozenia");
pracownik.Oświadczenia.Cast().Should().Contain(oswiadczenie);
}
// ============================== K5 — Wypadki przy pracy ==============================
[Test]
[Description("K5: new Wypadek(pracownik); Data to Date, Godzina to Soneta.Types.Time; pola opisowe " +
"(Okolicznosci/Skutki) to MemoText; flagi skutków to bool; wpis trafia do pracownik.Wypadki.")]
public void K5_Wypadek_DodanieZDanymiPodstawowymi_TrafiaDoKolekcji()
{
var pracownik = Host();
Soneta.Kadry.Wypadek wypadek = null;
InTransaction(() =>
{
wypadek = Session.AddRow(new Soneta.Kadry.Wypadek(pracownik));
wypadek.Data = Date.Today;
wypadek.Godzina = new Time(10, 30); // Soneta.Types.Time, nie DateTime
wypadek.DataZgloszenia = Date.Today;
wypadek.Miejsce = "Hala produkcyjna";
wypadek.PrzyPracy = true;
wypadek.Okolicznosci = (MemoText)"Poślizgnięcie na mokrej posadzce."; // MemoText (konwersja ze string), nie string
});
wypadek.Pracownik.Should().Be(pracownik);
wypadek.Miejsce.Should().Be("Hala produkcyjna");
wypadek.PrzyPracy.Should().BeTrue();
wypadek.Godzina.Should().Be(new Time(10, 30));
pracownik.Wypadki.Cast().Should().Contain(wypadek);
}
[Test]
[Description("K5: Wypadek wymaga Definicja (Soneta.Core.DefinicjaDokumentu) do numeracji — Numer " +
"(NumerDokumentu) nadaje platforma. Sprawdzamy, że pole Definicja jest częścią kontraktu.")]
public void K5_Wypadek_PoleDefinicjaJestCzesciaKontraktu()
{
var pracownik = Host();
Soneta.Kadry.Wypadek wypadek = null;
InTransaction(() =>
{
wypadek = Session.AddRow(new Soneta.Kadry.Wypadek(pracownik));
wypadek.Data = Date.Today;
});
// Numer jest subrowem nadawanym wg Definicja — nie ustawiamy Numer.Pelny ręcznie.
wypadek.Numer.Should().NotBeNull("Numer to subrow NumerDokumentu zawsze obecny na wpisie");
}
}