Files
2026-06-06 22:33:15 +02:00

384 lines
20 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Linq;
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.Business;
using Soneta.Core;
using Soneta.HR;
using Soneta.HR2;
using Soneta.Kadry;
using Soneta.Types;
using Prac = Soneta.Kadry.Pracownik;
namespace Soneta.Skills.Test.KadryPlace.Pracownik;
/// <summary>
/// Rozdział K (część druga) — RODO/GIODO, struktura organizacyjna, oceny, rekrutacja (receptury K6K9).
/// <para>
/// Testy to <b>wykonywalna dokumentacja</b> publicznego kontraktu platformy Soneta dla zaawansowanych
/// obszarów kadrowych. Wszystkie te obszary łączy jedna cecha: rekordy operacyjne wymagają
/// <b>referencji do definicji konfiguracyjnych</b> (słowników GIODO, struktury organizacyjnej, ocen,
/// stanowisk/etapów rekrutacji), które w bazie Demo (GoldStandard) <b>mogą nie istnieć</b>. Strategia
/// jest jednolita: definicję pobieramy dynamicznie (pierwszy rekord z tabeli konfiguracyjnej); gdy
/// jej brak — test jest oznaczany <c>Assert.Ignore</c> z powodem. Tam, gdzie da się przetestować
/// realnie (odczyt kolekcji, dodanie wpisu przy dostępnej definicji), robimy to na żywych danych.
/// </para>
/// <list type="bullet">
/// <item><b>K6</b> — RODO/GIODO: <c>new GIODOOświadczenie(pracownik, def)</c>, <c>new GIODOUprawnienie(pracownik, def)</c>;
/// kolekcje <c>GIODOOświadczenia</c>/<c>GIODOUprawnienia</c>/<c>GIODOUdostępnienia</c>;
/// <c>GIODOWymianaDanych</c> bez publicznego ctora → tylko odczyt + [Ignore]; zapis teczki do pliku → [Ignore].</item>
/// <item><b>K7</b> — struktura organizacyjna: <c>new PowiązanieStrukturyOrganizacyjnej(element, pracownik)</c>,
/// <c>Etat.Wydzial</c> (dane historyczne), manager <c>StrukturaOraganizacyjna</c> (odczyt).</item>
/// <item><b>K8</b> — oceny: <c>new OcenaPracownika(pracownik)</c> + <c>new ElementOcenyPracownika(ocena)</c>,
/// <c>new CelOkresowyPracownika(pracownik)</c>.</item>
/// <item><b>K9</b> — rekrutacja: <c>new RekrutacjaAplikacja(pracownik, wydziałDefStanowiska)</c>,
/// <c>new Rekrutacja(pracownik)</c>, <c>new EtapRekrutacji(rekrutacja)</c>.</item>
/// </list>
/// <para>
/// Operujemy wyłącznie na <b>publicznym kontrakcie</b> — jak dodatek programisty zewnętrznego bez
/// dostępu do źródeł. Baza Demo z rollbackiem po teście (helper <c>InTransaction</c> z <c>TestBase</c>).
/// </para>
/// </summary>
[TestFixture]
public class RozdzialK2_RodoZzlTest : PracownikTestBase
{
// Pracownik-host dla wpisów — dowolny etatowy z Demo (stabilny punkt wejścia).
private Prac Host() => Pracownik(Pracownik_.Andrzejewski) ?? PierwszyPracownik();
// Pierwszy rekord z tabeli konfiguracyjnej (lub null) — bez twardej zależności od nazwy słownika.
private static T Pierwsza<T>(Table tabela) where T : Row =>
tabela.Cast<T>().FirstOrDefault();
// ============================== K6 — RODO/GIODO ==============================
[Test]
[Description("K6: new GIODOOświadczenie(pracownik, definicja) — Host wynika z ctora (Pracownik implementuje " +
"IGIODOOświadczenieHost), Definicja (GIODODefOswiadcz) jest WYMAGANA przez ctor; pole Data to Date; " +
"Rodzaj/Okres są WYLICZANE (read-only) z definicji; wpis trafia do pracownik.GIODOOświadczenia. " +
"Gdy w Demo brak definicji oświadczenia lub brak prawa zapisu do obszaru RODO → Ignore.")]
public void K6_GIODOOswiadczenie_DodanieZDefinicja_TrafiaDoKolekcji()
{
// Tabela konfiguracyjna czytana wprost z sesji operacyjnej (jak słowniki w K1).
var def = Pierwsza<GIODODefinicjaOświadczenia>(Session.GetCore().GIODODefOswiadcz);
if (def == null)
Assert.Ignore("Brak definicji oświadczenia GIODO (CoreModule.GIODODefOswiadcz) w bazie Demo — wpisu nie można utworzyć (Definicja jest wymagana w ctorze).");
var pracownik = Host();
GIODOOświadczenie oswiadczenie = null;
try
{
InTransaction(() =>
{
// Definicja wynika z ctora; Rodzaj/Okres są wyliczane przez platformę — nie ustawiamy ich ręcznie.
oswiadczenie = Session.AddRow(new GIODOOświadczenie(pracownik, def));
oswiadczenie.Data = Date.Today;
oswiadczenie.SposobPozyskania = "Formularz papierowy";
});
}
catch (AccessWriteDeniedException)
{
// Egzekucji praw nie testujemy (safe-code §7.2) — rola Demo blokuje zapis do obszaru RODO/GIODO.
Assert.Ignore("Rola bazy Demo nie ma prawa zapisu do GIODOOświadczenie — egzekucji praw nie testujemy (safe-code §7.2).");
}
oswiadczenie.Host.Should().Be(pracownik, "ctor (host, definicja) ustawia Host na pracownika");
oswiadczenie.Definicja.Should().Be(def, "Definicja przekazywana jest w ctorze");
oswiadczenie.Data.Should().Be(Date.Today);
pracownik.GIODOOświadczenia.Cast<GIODOOświadczenie>()
.Should().Contain(oswiadczenie, "wpis trafia do kolekcji SubTable pracownika");
}
[Test]
[Description("K6: new GIODOUprawnienie(pracownik, definicja) — Uprawniony z ctora (IGIODOUprawnienieHost), " +
"Definicja (GIODODefUprawn) wymagana; pola Data/Przyznane/Odebrane to Date (Okres jest wyliczany, " +
"read-only); wpis trafia do pracownik.GIODOUprawnienia. Gdy brak definicji w Demo → Ignore.")]
public void K6_GIODOUprawnienie_DodanieZDefinicja_TrafiaDoKolekcji()
{
var def = Pierwsza<GIODODefinicjaUprawnienia>(Session.GetCore().GIODODefUprawn);
if (def == null)
Assert.Ignore("Brak definicji uprawnienia GIODO (CoreModule.GIODODefUprawn) w bazie Demo — wpisu nie można utworzyć.");
var pracownik = Host();
GIODOUprawnienie uprawnienie = null;
try
{
InTransaction(() =>
{
uprawnienie = Session.AddRow(new GIODOUprawnienie(pracownik, def));
uprawnienie.Data = Date.Today;
uprawnienie.Przyznane = Date.Today; // Okres jest wyliczany — nie ustawiamy go bezpośrednio.
});
}
catch (AccessWriteDeniedException)
{
Assert.Ignore("Rola bazy Demo nie ma prawa zapisu do GIODOUprawnienie — egzekucji praw nie testujemy (safe-code §7.2).");
}
uprawnienie.Uprawniony.Should().Be(pracownik, "ctor (uprawniony, definicja) ustawia Uprawniony");
uprawnienie.Definicja.Should().Be(def);
uprawnienie.Przyznane.Should().Be(Date.Today);
pracownik.GIODOUprawnienia.Cast<GIODOUprawnienie>().Should().Contain(uprawnienie);
}
[Test]
[Description("K6: GIODOWymianaDanych (pozyskanie/udostępnienie/powierzenie) NIE ma publicznego ctora — " +
"rekordy tworzą wyłącznie workery (DodajPozyskanieDanychWorker itd.). Kolekcja GIODOUdostępnienia " +
"jest jednak dostępna do ODCZYTU jako część publicznego kontraktu.")]
public void K6_GIODOUdostepnienia_KolekcjaDostepnaDoOdczytu()
{
var pracownik = Host();
// GIODOUdostępnienia to SubTable<GIODOWymianaDanych> — odczyt jest częścią kontraktu,
// nawet gdy w Demo nie ma żadnych zapisów wymiany danych.
pracownik.GIODOUdostępnienia.Should().NotBeNull("kolekcja wymiany danych jest zawsze dostępna (odczyt)");
pracownik.GIODOUdostępnienia.Cast<GIODOWymianaDanych>().Should().OnlyContain(w => w != null);
}
[Test]
[Ignore("Dodanie GIODOWymianaDanych wymaga workera (DodajPozyskanieDanychWorker/DodajUdostępnienieDanychWorker/" +
"DodajPowierzenieDanychWorker) oraz podmiotu (IKontrahent) i — w zależności od kierunku — definicji " +
"dokumentu/zbioru danych z konfiguracji modułu RODO, których baza Demo może nie mieć. Brak publicznego " +
"ctora uniemożliwia deterministyczny zapis bez tej konfiguracji.")]
[Description("K6: dodanie zapisu wymiany danych GIODO przez DodajPozyskanieDanychWorker (CommitUI + Save).")]
public void K6_GIODOWymianaDanych_DodaniePrzezWorker()
{
}
[Test]
[Ignore("Zapis teczki personalnej (Pracownik.ZapiszTeczkęDoPlikuWorker.ZapiszTeczkeDoPliku()) to operacja " +
"plikowa — serializuje dokumentację pracownika do plików/katalogu na dysku. Poza zakresem testów " +
"jednostkowych (zależność od systemu plików).")]
[Description("K6: zapis teczki personalnej RODO do pliku (operacja plikowa).")]
public void K6_ZapisTeczkiDoPliku()
{
}
// ============================== K7 — Struktura organizacyjna ==============================
[Test]
[Description("K7: new PowiązanieStrukturyOrganizacyjnej(element, pracownik) — Zrodlo z ctora (Pracownik " +
"implementuje IŹródłoPowiązaniaStrukturyOrganizacyjnej), Element to istniejący element struktury " +
"(CoreModule.ElementyStrOrg — NIE definicja DefElStrukturOrg); Okres to FromTo; wpis trafia do " +
"pracownik.PowiązaniaStrOrg. Gdy brak elementów struktury w Demo lub brak prawa zapisu → Ignore.")]
public void K7_PowiazanieStruktury_DodanieZElementem_TrafiaDoKolekcji()
{
// Elementy struktury (instancje) są w ElementyStrOrg; DefElStrukturOrg trzyma DEFINICJE elementów.
var element = Pierwsza<ElementStrukturyOrganizacyjnej>(Session.GetCore().ElementyStrOrg);
if (element == null)
Assert.Ignore("Brak elementów struktury organizacyjnej (CoreModule.ElementyStrOrg) w bazie Demo — powiązania nie można utworzyć.");
var pracownik = Host();
PowiązanieStrukturyOrganizacyjnej powiazanie = null;
try
{
InTransaction(() =>
{
powiazanie = Session.AddRow(new PowiązanieStrukturyOrganizacyjnej(element, pracownik));
powiazanie.Okres = new FromTo(Date.Today, Date.MaxValue);
});
}
catch (AccessWriteDeniedException)
{
Assert.Ignore("Rola bazy Demo nie ma prawa zapisu do PowiązanieStrukturyOrganizacyjnej — egzekucji praw nie testujemy (safe-code §7.2).");
}
powiazanie.Zrodlo.Should().Be(pracownik, "ctor (element, zrodlo) ustawia Zrodlo na pracownika");
powiazanie.Element.Should().Be(element);
pracownik.PowiązaniaStrOrg.Cast<PowiązanieStrukturyOrganizacyjnej>().Should().Contain(powiazanie);
}
[Test]
[Description("K7: pracownik.StrukturaOraganizacyjna to manager (StrukturaOraganizacyjnaManager) — API tylko " +
"do odczytu nawigacji przełożeni/podwładni. Jest zawsze dostępny, niezależnie od konfiguracji struktury.")]
public void K7_StrukturaOrganizacyjna_ManagerOdczytuJestDostepny()
{
var pracownik = Host();
pracownik.StrukturaOraganizacyjna.Should().NotBeNull("manager struktury jest zawsze dostępny (odczyt)");
pracownik.StrukturaOraganizacyjna.Should().BeOfType<Prac.StrukturaOraganizacyjnaManager>();
// Przełożony „na dzień" może być null (brak skonfigurowanej struktury) — czytamy bez wyjątku.
var _ = pracownik.StrukturaOraganizacyjna.GetDomyślnyPrzełożony(Date.Today);
}
[Test]
[Description("K7: Etat.Wydzial to dane HISTORYCZNE (na PracHistoria.Etat) i jednostka organizacyjna pracownika. " +
"Dla etatowego pracownika z Demo wydział na zapisie obowiązującym dziś jest ustawiony (wymagany dla etatu).")]
public void K7_EtatWydzial_JestUstawionyDlaEtatowca()
{
var pracownik = Host();
var ph = pracownik[Date.Today]; // zapis historii obowiązujący na dzień (A15)
ph.Should().NotBeNull("etatowy pracownik z Demo ma zapis historii obowiązujący dziś");
// Wydzial jest wymagany dla etatu — odczyt jako część kontraktu (referencja do Soneta.Kadry.Wydzial).
ph.Etat.Should().NotBeNull();
ph.Etat.Wydzial.Should().NotBeNull("Etat.Wydzial (jednostka organizacyjna) jest wymagany dla etatu");
}
// ============================== K8 — Oceny okresowe ==============================
[Test]
[Description("K8: new OcenaPracownika(pracownik) (arkusz, root w HR.OcenyPracownikow) + new ElementOcenyPracownika(ocena) " +
"gdzie ocena jest IOcenaPracownika; ElementOcenyPracownika.Wartosc to decimal (Typ/Data są wyliczane, read-only). " +
"Element wymaga Definicja (HR.DefElemOcenPrac) — gdy brak w Demo, sam arkusz i pusta kolekcja elementów wystarczają.")]
public void K8_OcenaPracownika_ArkuszZElementem_TrafiaDoKolekcji()
{
var hr = Session.GetHR();
var pracownik = Host();
var defElementu = Pierwsza<DefElementuOcenyPracownika>(hr.DefElemOcenPrac);
OcenaPracownika ocena = null;
ElementOcenyPracownika element = null;
InTransaction(() =>
{
ocena = Session.AddRow(new OcenaPracownika(pracownik));
ocena.Nazwa = "Ocena roczna 2026";
ocena.Data = Date.Today;
// Element dodajemy tylko gdy istnieje definicja (Definicja jest wymagana do zapisu elementu).
if (defElementu != null)
{
element = Session.AddRow(new ElementOcenyPracownika(ocena)); // ocena jako IOcenaPracownika
element.Definicja = defElementu;
element.Wartosc = 4m; // Wartosc to decimal (Typ/Data ustawia platforma na podstawie definicji)
}
});
ocena.Pracownik.Should().Be(pracownik, "ctor (Pracownik) ustawia ocenianego");
ocena.Nazwa.Should().Be("Ocena roczna 2026");
pracownik.Oceny.Cast<OcenaPracownika>().Should().Contain(ocena, "arkusz trafia do kolekcji pracownika");
if (defElementu != null)
{
element.Ocena.Should().Be(ocena, "ctor (IOcenaPracownika) wiąże element z arkuszem");
element.Wartosc.Should().Be(4m);
ocena.ElementyOceny.Cast<ElementOcenyPracownika>().Should().Contain(element);
}
else
{
Assert.Warn("Brak definicji elementu oceny (HR.DefElemOcenPrac) w Demo — przetestowano sam arkusz oceny bez pozycji.");
}
}
[Test]
[Description("K8: new CelOkresowyPracownika(pracownik) (root w HR2.CeleOkresowePrac); pola Nazwa/Data/Termin/Opis; " +
"Definicja to Soneta.Oceny.DefinicjaElementuOceny (opcjonalna referencja konfiguracyjna); wpis trafia " +
"do pracownik.CeleOkresowe.")]
public void K8_CelOkresowy_Dodanie_TrafiaDoKolekcji()
{
var pracownik = Host();
CelOkresowyPracownika cel = null;
InTransaction(() =>
{
cel = Session.AddRow(new CelOkresowyPracownika(pracownik));
cel.Nazwa = "Wdrożenie nowego modułu";
cel.Data = Date.Today;
cel.Termin = new Date(2026, 12, 31);
cel.Opis = (MemoText)"Cel rozwojowy na bieżący okres oceny.";
});
cel.Pracownik.Should().Be(pracownik, "ctor (Pracownik) ustawia pracownika celu");
cel.Nazwa.Should().Be("Wdrożenie nowego modułu");
cel.Termin.Should().Be(new Date(2026, 12, 31));
pracownik.CeleOkresowe.Cast<CelOkresowyPracownika>().Should().Contain(cel);
}
// ============================== K9 — Rekrutacja ==============================
[Test]
[Description("K9: new RekrutacjaAplikacja(kandydat, wydziałDefStanowiska) — kandydat to Pracownik, ctor przyjmuje " +
"WydziałDefinicjiStanowiska (powstaje z new WydziałDefinicjiStanowiska(DefinicjaStanowiska) — typ z Soneta.HR). " +
"Stan to StanAplikacji; wpis trafia do kandydat.Aplikacje. Gdy brak definicji stanowiska (HR.DefStanowisk) → Ignore.")]
public void K9_RekrutacjaAplikacja_DodanieZeStanowiskiem_TrafiaDoKolekcji()
{
var hr = Session.GetHR();
var defStanowiska = Pierwsza<DefinicjaStanowiska>(hr.DefStanowisk);
if (defStanowiska == null)
Assert.Ignore("Brak definicji stanowiska (HR.DefStanowisk) w bazie Demo — aplikacji rekrutacyjnej nie można utworzyć (ctor wymaga WydziałDefinicjiStanowiska).");
var kandydat = Host();
RekrutacjaAplikacja aplikacja = null;
InTransaction(() =>
{
// WydziałDefinicjiStanowiska powstaje z DefinicjaStanowiska (ctor w Soneta.HR).
var wydzialDef = new WydziałDefinicjiStanowiska(defStanowiska);
aplikacja = Session.AddRow(new RekrutacjaAplikacja(kandydat, wydzialDef));
aplikacja.Data = Date.Today;
aplikacja.Stan = StanAplikacji.Wprowadzona;
});
aplikacja.Pracownik.Should().Be(kandydat, "ctor (Pracownik, …) ustawia kandydata");
aplikacja.Stanowisko.Should().Be(defStanowiska, "WydziałDefinicjiStanowiska niesie referencję do DefinicjaStanowiska");
aplikacja.Stan.Should().Be(StanAplikacji.Wprowadzona);
kandydat.Aplikacje.Cast<RekrutacjaAplikacja>().Should().Contain(aplikacja);
}
[Test]
[Description("K9: new Rekrutacja(kandydat) (root w HR.Rekrutacje; impl. IOcenaPracownika) ustawia pole Pracownik; " +
"+ new EtapRekrutacji(rekrutacja) wiąże etap przez pole Rekrutacja; Etap.Definicja to HR.DefEtaRekrutacji " +
"(wymagana do zapisu etapu), Etap.Lp/Data. Gdy brak definicji etapu w Demo, testujemy samą rekrutację (warn). " +
"Gdy brak prawa zapisu → Ignore.")]
public void K9_RekrutacjaIEtap_Dodanie_TrafiaDoKolekcji()
{
var hr = Session.GetHR();
var kandydat = Host();
var defEtapu = Pierwsza<DefinicjaEtapuRekrutacji>(hr.DefEtaRekrutacji);
Rekrutacja rekrutacja = null;
EtapRekrutacji etap = null;
try
{
InTransaction(() =>
{
rekrutacja = Session.AddRow(new Rekrutacja(kandydat));
if (defEtapu != null)
{
etap = Session.AddRow(new EtapRekrutacji(rekrutacja));
etap.Definicja = defEtapu;
etap.Lp = 1;
etap.Data = Date.Today;
}
});
}
catch (AccessWriteDeniedException)
{
Assert.Ignore("Rola bazy Demo nie ma prawa zapisu do Rekrutacja/EtapRekrutacji — egzekucji praw nie testujemy (safe-code §7.2).");
}
rekrutacja.Should().NotBeNull("ctor (Pracownik) tworzy rekrutację dla kandydata");
rekrutacja.Pracownik.Should().Be(kandydat, "ctor (Pracownik) ustawia kandydata rekrutacji");
// Rekrutacja jest rootem w HR.Rekrutacje (kolekcje na Pracowniku wiążą się przez relacje child).
hr.Rekrutacje.Cast<Rekrutacja>().Should().Contain(rekrutacja, "rekrutacja trafia do tabeli głównej HR.Rekrutacje");
if (defEtapu != null)
{
etap.Rekrutacja.Should().Be(rekrutacja, "ctor (Rekrutacja) wiąże etap z rekrutacją");
etap.Lp.Should().Be(1);
hr.EtapyRekrutacji.Cast<EtapRekrutacji>().Should().Contain(etap, "etap trafia do tabeli głównej HR.EtapyRekrutacji");
}
else
{
Assert.Warn("Brak definicji etapu rekrutacji (HR.DefEtaRekrutacji) w Demo — przetestowano samą rekrutację bez etapów.");
}
}
[Test]
[Description("K9: kandydat.Aplikacje / Rekrutacje / EtapyRekrutacji / Kandydatury to kolekcje SubTable dostępne " +
"do odczytu jako część publicznego kontraktu — niezależnie od stanu konfiguracji rekrutacji.")]
public void K9_KolekcjeRekrutacji_DostepneDoOdczytu()
{
var kandydat = Host();
kandydat.Aplikacje.Should().NotBeNull();
kandydat.Rekrutacje.Should().NotBeNull();
kandydat.EtapyRekrutacji.Should().NotBeNull();
kandydat.Kandydatury.Should().NotBeNull();
}
}