using System; using System.Linq; using AwesomeAssertions; using NUnit.Framework; using Soneta.Business; using Soneta.Kalend; using Soneta.Types; using Prac = Soneta.Kadry.Pracownik; namespace Soneta.Skills.Test.KadryPlace.Pracownik; /// /// Rozdział E/F — „Plan pracy i kalendarz" (E1, E2) oraz „RCP — rejestracja czasu pracy" (F1, F2). /// /// Testy są wykonywalną dokumentacją publicznego kontraktu platformy Soneta dla planu pracy /// i rejestracji czasu. Model: pracownik wystawia trzy niezależne kolekcje dni typu /// (indeksator po , tylko do odczytu — element tworzysz /// konstruktorem + AddRow): /// /// DniPlanu — plan/harmonogram (dni : ), /// DniPracy — ewidencja czasu pracy (), /// DniRCP — zarejestrowany (zweryfikowany) czas pracy () — wynik importu RCP. /// /// Wszystkie dni współdzielą subrow Praca : CzasPracy z polami OdGodziny/DoGodziny/Czas. /// Zdarzenia wejścia/wyjścia () są childem (kolekcja WeWy). /// /// /// Operujemy wyłącznie na publicznym kontrakcie (jak dodatek zewnętrzny), na bazie Demo /// (GoldStandard) z automatycznym rollbackiem. Daty Demo dla planu/pracy są nieznane, więc odczyty /// istniejących danych traktujemy defensywnie (kolekcja istnieje / indeksator nie rzuca), a scenariusze /// zapisu budujemy na własnych, jawnych datach dla pracownika "006". /// /// [TestFixture] public class RozdzialEF_PlanRcpTest : PracownikTestBase { // Data biznesowa do scenariuszy zapisu (jawna, nie Date.Today — data biznesowa Demo bywa inna). private static readonly Date Dzien = new(2026, 6, 1); /// /// Definicja dnia (typ dnia) ze słownika konfiguracyjnego DefinicjeDni. Demo zawiera kilka /// definicji; bierzemy pierwszą z brzegu (dowolny istniejący typ dnia), aby świeży dzień planu/pracy /// miał wymaganą Definicja. Skróty WolnaSobota/Niedziela też są dostępne. /// private DefinicjaDnia DowolnaDefinicjaDnia() { return Kalend.DefinicjeDni.Rows.Cast().FirstOrDefault(); } // ============================== E1 — Plan pracy (harmonogram) ============================== [Test] [Description("E1 (odczyt): DniPlanu to DateSubTable nietypowany (zwraca Row, rzutujemy na DzienPlanu); " + "DniPlanu == Etat.Kalendarz.Dni; indeksator [Date] jest tylko do odczytu i zwraca null dla braku dnia.")] public void E1_DniPlanu_OdczytIndeksatoremPoDacie_ZwracaDzienPlanuLubNull() { var p = Pracownik(Pracownik_.Andrzejewski); p.Should().NotBeNull("pracownik '006' istnieje w bazie Demo"); // DniPlanu jest DateSubTable (nietypowany) — element zwracany jako Row, rzutujemy na DzienPlanu. p.DniPlanu.Should().NotBeNull("kolekcja planu (harmonogramu) zawsze istnieje"); // Indeksator [Date] to odczyt — nie rzuca; dla daty bez dnia planu zwraca null. System.Action odczyt = () => { var dp = (DzienPlanu)p.DniPlanu[Dzien]; if (dp is not null) { // Godziny pracy leżą na subrowie Praca; Czas/OdGodziny na rootcie dnia są kalkulowane. Time _ = dp.Praca.OdGodziny; Time __ = dp.Czas; DefinicjaDnia ___ = dp.Definicja; } }; odczyt.Should().NotThrow("indeksator [Date] na DniPlanu jest bezpiecznym odczytem"); // DzienPlanu dziedziczy z DzienKalendarzaBase (dzień kalendarza pracownika). typeof(DzienKalendarzaBase).IsAssignableFrom(typeof(DzienPlanu)) .Should().BeTrue("DzienPlanu jest dniem kalendarza (DzienKalendarzaBase)"); } [Test] [Description("E1 (zapis): nowy dzień planu tworzymy ctorem DzienPlanu(pracownik, data) + AddRow, " + "ustawiamy Definicja (ze słownika DefinicjeDni) i godziny na subrowie Praca; po zapisie " + "indeksator DniPlanu[data] zwraca utworzony dzień.")] public void E1_UtworzenieDniaPlanu_UstawiaGodzinyNaSubrowiePraca() { var def = DowolnaDefinicjaDnia(); def.Should().NotBeNull("Demo zawiera definicje dni (słownik DefinicjeDni)"); Guid guidPrac = Guid.Empty; InTransaction(() => { var p = Pracownik(Pracownik_.Andrzejewski); guidPrac = p.Guid; // Indeksator [Date] jest read-only — nowego dnia nie „przypiszemy", tworzymy ctorem. var dp = (DzienPlanu)p.DniPlanu[Dzien]; if (dp is null) { dp = Session.AddRow(new DzienPlanu(p, Dzien)); // ctor (Pracownik, Date) dp.Definicja = def; // typ dnia ze słownika (wymagany dla weryfikatorów) } // Godziny ustawiamy na subrowie Praca; Czas dnia wylicza się z od–do. dp.Praca.OdGodziny = new Time(8, 0); dp.Praca.DoGodziny = new Time(16, 0); }); SaveDispose(); // Odczyt po zapisie: dzień planu istnieje na wskazanej dacie i ma ustawione godziny. var p2 = Get(guidPrac); var dp2 = (DzienPlanu)p2.DniPlanu[Dzien]; dp2.Should().NotBeNull("po zapisie dzień planu jest dostępny przez indeksator [Date]"); dp2.Data.Should().Be(Dzien); dp2.Praca.OdGodziny.Should().Be(new Time(8, 0)); dp2.Praca.DoGodziny.Should().Be(new Time(16, 0)); } // ============================== E2 — Kopiowanie planu / pracy (publiczne static) ============================== [Test] [Description("E2: KalendarzPlanuKopia.Kopiuj(pracownik, okres) to publiczna metoda STATYCZNA " + "(bez Context) — kopiuje wyliczony plan na okres do bufora DniPlanuKopia. Test wykonuje " + "wywołanie w transakcji i sprawdza, że nie rzuca oraz że bufor DniPlanuKopia jest dostępny.")] public void E2_KalendarzPlanuKopia_Kopiuj_StaticNaOkres_NieRzuca() { var okres = new FromTo(new Date(2026, 6, 1), new Date(2026, 6, 30)); InTransaction(() => { var p = Pracownik(Pracownik_.Andrzejewski); // Publiczny static Kopiuj(Pracownik, FromTo) — właściwa droga dla kodu serwerowego/testów // (worker KopiujWorker wymaga Context/zaznaczenia i jest gardzony licencją BI — patrz E2). System.Action kopiuj = () => KalendarzPlanuKopia.Kopiuj(p, okres); kopiuj.Should().NotThrow("Kopiuj(Pracownik, FromTo) to publiczne statyczne API bez Context"); // Kopia trafia do osobnego bufora DniPlanuKopia (DateSubTable), odrębnego od DniPlanu. p.DniPlanuKopia.Should().NotBeNull("bufor kopii planu (DniPlanuKopia) jest dostępny"); }); SaveDispose(); } [Test] [Description("E2: KalendarzPracyKopia.Kopiuj(pracownik, okres) — analogiczny publiczny static dla " + "kopiowania realizacji (pracy) na okres; kopia trafia do bufora DniPracyKopia.")] public void E2_KalendarzPracyKopia_Kopiuj_StaticNaOkres_NieRzuca() { var okres = new FromTo(new Date(2026, 6, 1), new Date(2026, 6, 30)); InTransaction(() => { var p = Pracownik(Pracownik_.Andrzejewski); System.Action kopiuj = () => KalendarzPracyKopia.Kopiuj(p, okres); kopiuj.Should().NotThrow("Kopiuj(Pracownik, FromTo) to publiczne statyczne API bez Context"); p.DniPracyKopia.Should().NotBeNull("bufor kopii pracy (DniPracyKopia) jest dostępny"); }); SaveDispose(); } // ============================== F1 — Odczyt zarejestrowanego/ewidencjonowanego czasu ============================== [Test] [Description("F1 (odczyt): DniPracy i DniRCP to DateSubTable TYPOWANE (DzienPracy / DzienRCP); " + "indeksator [Date] zwraca właściwy typ lub null i nie rzuca. DzienRCP testujemy tylko " + "ODCZYTOWO — jest wynikiem importu/weryfikacji RCP, nie tworzymy go ręcznie.")] public void F1_DniPracyIDniRCP_OdczytIndeksatoremPoDacie_NieRzuca() { var p = Pracownik(Pracownik_.Andrzejewski); p.DniPracy.Should().NotBeNull("kolekcja ewidencji (DniPracy) istnieje"); p.DniRCP.Should().NotBeNull("kolekcja zweryfikowanego RCP (DniRCP) istnieje"); System.Action odczyt = () => { // DniPracy jest typowane — indeksator [Date] zwraca DzienPracy lub null. DzienPracy dzienPracy = p.DniPracy[Dzien]; if (dzienPracy is not null) { Time _ = dzienPracy.Praca.Czas; // przepracowany czas dnia (subrow Praca) Time __ = dzienPracy.Praca.OdGodziny; } // DniRCP jest typowane — DzienRCP lub null; odczyt stanu weryfikacji RCP. DzienRCP dzienRcp = p.DniRCP[Dzien]; if (dzienRcp is not null) { StanWeryfikacjiRCP ___ = dzienRcp.StanRCP; Time ____ = dzienRcp.Praca.Czas; } }; odczyt.Should().NotThrow("indeksatory [Date] na DniPracy/DniRCP to bezpieczny odczyt"); } [Test] [Description("F1 (zapis ewidencji): dzień ewidencji tworzymy ctorem DzienPracy(pracownik, data) + AddRow " + "(sam ctor nie rejestruje wiersza); godziny ustawiamy na subrowie Praca. Po zapisie " + "DniPracy[data] zwraca utworzony dzień.")] public void F1_UtworzenieDniaPracy_UstawiaGodzinyNaSubrowiePraca() { Guid guidPrac = Guid.Empty; InTransaction(() => { var p = Pracownik(Pracownik_.Andrzejewski); guidPrac = p.Guid; var dp = p.DniPracy[Dzien]; if (dp is null) { // ctor (Pracownik, Date) + AddRow — sam ctor nie włącza wiersza do tabeli. dp = Session.AddRow(new DzienPracy(p, Dzien)); } dp.Praca.OdGodziny = new Time(8, 0); dp.Praca.DoGodziny = new Time(16, 0); }); SaveDispose(); var p2 = Get(guidPrac); var dp2 = p2.DniPracy[Dzien]; dp2.Should().NotBeNull("po zapisie dzień ewidencji jest dostępny przez indeksator [Date]"); dp2.Data.Should().Be(Dzien); dp2.Praca.OdGodziny.Should().Be(new Time(8, 0)); dp2.Praca.DoGodziny.Should().Be(new Time(16, 0)); } // ============================== F2 — Wejścia/wyjścia (zdarzenia RCP na dniu pracy) ============================== [Test] [Description("F2: zdarzenie WejscieWyjscie jest childem DzienPracy — ctor WejscieWyjscie(dzienPracy) + " + "AddRow do kalend.WejsciaWyjscia; ustawiamy Godzina i Typ (enum TypWejsciaWyjscia). " + "Odczyt przez DzienPracy.WeWy (LpSubTable, posortowane po Lp).")] public void F2_WejscieWyjscie_DodanieWejsciaIWyjscia_DoDniaPracy() { Guid guidPrac = Guid.Empty; InTransaction(() => { var p = Pracownik(Pracownik_.Andrzejewski); guidPrac = p.Guid; // Najpierw potrzebny dzień ewidencji (właściciel zdarzeń we/wy). var dp = p.DniPracy[Dzien]; if (dp is null) dp = Session.AddRow(new DzienPracy(p, Dzien)); // Wejście 8:00 — ctor wiąże zdarzenie z dniem; AddRow do tabeli WejsciaWyjscia. var we = new WejscieWyjscie(dp); Kalend.WejsciaWyjscia.AddRow(we); we.Godzina = new Time(8, 0); we.Typ = TypWejsciaWyjscia.Wejscie; // enum, nie string/int // Wyjście 16:00. var wy = new WejscieWyjscie(dp); Kalend.WejsciaWyjscia.AddRow(wy); wy.Godzina = new Time(16, 0); wy.Typ = TypWejsciaWyjscia.Wyjscie; }); SaveDispose(); // Odczyt zdarzeń dnia przez kolekcję WeWy (LpSubTable — kolejność wg Lp). var p2 = Get(guidPrac); var dzien = p2.DniPracy[Dzien]; dzien.Should().NotBeNull("dzień ewidencji z dodanymi zdarzeniami istnieje"); var zdarzenia = dzien.WeWy.Cast().OrderBy(w => w.Lp).ToList(); zdarzenia.Should().HaveCount(2, "dodaliśmy wejście i wyjście"); zdarzenia.Should().Contain(w => w.Typ == TypWejsciaWyjscia.Wejscie && w.Godzina == new Time(8, 0)); zdarzenia.Should().Contain(w => w.Typ == TypWejsciaWyjscia.Wyjscie && w.Godzina == new Time(16, 0)); // Dzien (właściciel) ustawiony przez ctor — wszystkie zdarzenia wskazują nasz dzień pracy. zdarzenia.Should().OnlyContain(w => w.Dzien.Data == Dzien); } }