using System; using System.Linq; using AwesomeAssertions; using NUnit.Framework; using Soneta.Business; using Soneta.Kadry; using Soneta.Place; using Soneta.Types; using Prac = Soneta.Kadry.Pracownik; namespace Soneta.Skills.Test.KadryPlace.Pracownik; /// /// Rozdział B+C — „Etat (umowa o pracę)" i „Dodatki / stałe elementy wynagrodzenia" /// (receptury B1 i C1 z dokumentu skilla pracownik.md). /// /// Testy są wykonywalną dokumentacją publicznego kontraktu platformy Soneta. Pokazują: /// /// B1 — warunki etatu siedzą w subrowie PracHistoria.Etat; stawkę ustawiamy na /// subrowie Etat.Zaszeregowanie w wymaganej KOLEJNOŚCI (najpierw RodzajStawki, potem /// Wymiar) — odwrócenie kolejności rzuca ; /// C1 — dodatek (stały element wynagrodzenia) jest obiektem historycznym; tworzymy go /// przez new Dodatek(pracownik) + Kadry.Dodatki.AddRow, a parametry (Element, Okres) /// ustawiamy na pierwszym zapisie d.Last. /// /// /// /// 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. /// /// [TestFixture] public class RozdzialBC_EtatDodatkiTest : PracownikTestBase { // ============================== B1 — Definiowanie etatu (umowa o pracę) ============================== [Test] [Description("B1: warunki etatu ustawiamy na subrowie Etat zapisu historii. KOLEJNOŚĆ: najpierw " + "Etat.Okres (odblokowuje pozostałe pola etatu), potem TypUmowy/Podstawa/Stanowisko/Wydzial " + "oraz stawka na subrowie Zaszeregowanie. Wydzial to referencja do korzenia (Wydzialy.Firma).")] public void B1_DefiniowanieEtatu_NaNowymPracowniku_UstawiaWarunkiIStawke() { Guid guid = Guid.Empty; var kod = "B1_" + Guid.NewGuid().ToString("N").Substring(0, 6); var okres = new FromTo(new Date(2026, 1, 1), Date.MaxValue); InTransaction(() => { // A1: AddRow tworzy pierwszy zapis historii (Last) + kalendarz — warunki etatu ustawiamy // na Etat tego pierwszego zapisu. var pracownik = Session.AddRow(new PracownikFirmy()); pracownik.Kod = kod; pracownik.Last.Nazwisko = "Etatowy"; pracownik.Last.Imie = "Robert"; // Etat to SUBROW zapisu PracHistoria — modyfikujemy jego pola, nie przypisujemy obiektu. var etat = pracownik.Last.Etat; // KLUCZOWA KOLEJNOŚĆ: na świeżym (auto-utworzonym) zapisie cały Etat jest read-only, // dopóki nie ustawimy zakresu zatrudnienia Etat.Okres. Okres MUSI być pierwszy — // dopiero on odblokowuje TypUmowy/Podstawa/Stanowisko/Zaszeregowanie. etat.Okres = okres; // FromTo, nie DateTime — USTAWIAMY PIERWSZE etat.TypUmowy = TypUmowyOPrace.NaCzasNieokreślony; // enum, nie string etat.Podstawa = StosPracyNaPodstawie.UmowyOPrace; // podstawa stosunku pracy (enum) etat.DataZawarcia = new Date(2025, 12, 20); etat.DataRozpPracy = new Date(2026, 1, 1); etat.Stanowisko = "Specjalista"; etat.Wydzial = Kadry.Wydzialy.Firma; // referencja do istniejącego wydziału (korzeń) // Stawka — subrow Zaszeregowanie. Po ustawieniu Etat.Okres wszystkie pola stawki są // zapisywalne; ustawiamy je w czytelnej kolejności RodzajStawki -> TypStawki -> Wymiar -> Stawka. var z = etat.Zaszeregowanie; z.RodzajStawki = RodzajStawkiZaszeregowania.Miesieczna; // rodzaj stawki z.TypStawki = TypStawkiZaszeregowania.Dowolna; // typ stawki z.Wymiar = Fraction.One; // pełny etat z.Stawka = (Currency)6000m; // kwota brutto miesięcznie guid = pracownik.Guid; }); SaveDispose(); // Odczyt na świeżej sesji po Guid — potwierdza utrwalenie warunków etatu i stawki. var etat2 = Get(guid).Last.Etat; etat2.TypUmowy.Should().Be(TypUmowyOPrace.NaCzasNieokreślony); etat2.Podstawa.Should().Be(StosPracyNaPodstawie.UmowyOPrace); etat2.Stanowisko.Should().Be("Specjalista"); etat2.Wydzial.Should().NotBeNull("Wydzial wskazuje na istniejący wydział (korzeń struktury)"); // FromTo implementuje IEnumerable — porównujemy granice okresu, nie cały obiekt. etat2.Okres.From.Should().Be(okres.From); var z2 = etat2.Zaszeregowanie; z2.RodzajStawki.Should().Be(RodzajStawkiZaszeregowania.Miesieczna); z2.Wymiar.Should().Be(Fraction.One, "pełny etat"); z2.Stawka.Should().Be((Currency)6000m, "kwota brutto miesięcznie"); } [Test] [Description("B1 (pułapka kolejności): na świeżym zapisie historii cały Etat jest tylko-do-odczytu " + "dopóki nie ustawimy Etat.Okres. Próba ustawienia TypUmowy/RodzajStawki/Wymiar PRZED " + "Etat.Okres rzuca ColReadOnlyException; po ustawieniu Okres pola stają się zapisywalne.")] public void B1_Pulapka_PolaEtatuReadOnlyDopokiNieUstawionoOkresu() { InTransaction(() => { var pracownik = Session.AddRow(new PracownikFirmy()); pracownik.Kod = "B1x_" + Guid.NewGuid().ToString("N").Substring(0, 6); pracownik.Last.Nazwisko = "Pulapka"; pracownik.Last.Imie = "Karol"; var etat = pracownik.Last.Etat; // PRZED ustawieniem Etat.Okres pola etatu są tylko-do-odczytu — przypisanie rzuca wyjątek. System.Action typUmowyPrzedOkresem = () => etat.TypUmowy = TypUmowyOPrace.NaCzasNieokreślony; typUmowyPrzedOkresem.Should().Throw( "TypUmowy jest read-only dopóki nie ustawiono Etat.Okres"); System.Action rodzajStawkiPrzedOkresem = () => etat.Zaszeregowanie.RodzajStawki = RodzajStawkiZaszeregowania.Miesieczna; rodzajStawkiPrzedOkresem.Should().Throw( "Zaszeregowanie.RodzajStawki też jest read-only przed Etat.Okres"); System.Action wymiarPrzedOkresem = () => etat.Zaszeregowanie.Wymiar = new Fraction(1, 2); wymiarPrzedOkresem.Should().Throw( "Zaszeregowanie.Wymiar też jest read-only przed Etat.Okres"); // Ustawienie Etat.Okres ODBLOKOWUJE pozostałe pola etatu i stawki. etat.Okres = new FromTo(new Date(2026, 1, 1), Date.MaxValue); System.Action poOkresie = () => { etat.TypUmowy = TypUmowyOPrace.NaCzasNieokreślony; etat.Zaszeregowanie.RodzajStawki = RodzajStawkiZaszeregowania.Miesieczna; etat.Zaszeregowanie.Wymiar = new Fraction(1, 2); }; poOkresie.Should().NotThrow("po ustawieniu Etat.Okres pola etatu i stawki są zapisywalne"); etat.Zaszeregowanie.Wymiar.Should().Be(new Fraction(1, 2), "½ etatu"); // Nie commitujemy realnych danych — pracownik bez kompletnych warunków; // mechanizm testów i tak wycofuje transakcję, ale dla jasności nie utrwalamy. }); } // ============================== C1 — Dodatki / stałe elementy wynagrodzenia ============================== [Test] [Description("C1: dodatek tworzymy przez new Dodatek(pracownik) + Kadry.Dodatki.AddRow (para); " + "AddRow tworzy pierwszy zapis DodHistoria (d.Last), na którym ustawiamy Element " + "(z Place.DefElementow.WgNazwy[\"Premia\"]) oraz Okres. Odczyt z pracownik.Dodatki.")] public void C1_Dodatek_TworzonyZDefinicjaElementu_IOkresem() { // Definicja elementu wynagrodzenia ze słownika KONFIGURACYJNEGO (po nazwie). // W bazie Demo istnieje gotowa definicja "Premia". var definicjaPremii = Place.DefElementow.WgNazwy["Premia"] as DefinicjaElementu; definicjaPremii.Should().NotBeNull("baza Demo zawiera definicję elementu \"Premia\""); Guid guidPrac = Guid.Empty; var okres = new FromTo(new Date(2026, 1, 1), Date.MaxValue); InTransaction(() => { // Tworzymy świeżego pracownika z etatem (świeży = nie ma jeszcze żadnych dodatków, // w odróżnieniu od pracowników z Demo, którym już przypisano premie/składki). var pracownik = Session.AddRow(new PracownikFirmy()); pracownik.Kod = "C1_" + Guid.NewGuid().ToString("N").Substring(0, 6); pracownik.Last.Nazwisko = "Premiowany"; pracownik.Last.Imie = "Lucjan"; // Etat.Okres najpierw — odblokowuje warunki etatu (patrz B1). Po ustawieniu Okres // weryfikator wymaga jednostki organizacyjnej (Wydzial) przy Save. pracownik.Last.Etat.Okres = okres; pracownik.Last.Etat.Wydzial = Kadry.Wydzialy.Firma; pracownik.Last.Etat.Stanowisko = "Specjalista"; // new Dodatek(pracownik) + AddRow — PARA. Sam ctor nie włącza dodatku do sesji ani // nie tworzy zapisu historii; pierwszy DodHistoria powstaje przy AddRow. var dodatek = new Dodatek(pracownik); Kadry.Dodatki.AddRow(dodatek); // Parametry ustawiamy na pierwszym zapisie historii dodatku (d.Last). var h = dodatek.Last; h.Should().NotBeNull("AddRow tworzy pierwszy zapis DodHistoria (Last)"); h.Element = definicjaPremii; // definicja elementu (wymagana) h.Okres = okres; guidPrac = pracownik.Guid; }); SaveDispose(); // Odczyt: dodatek pojawia się w kolekcji childów pracownika (pracownik.Dodatki). var pracownik2 = Get(guidPrac); var dodatki = pracownik2.Dodatki.Cast().ToList(); dodatki.Should().ContainSingle("dodaliśmy jeden dodatek do świeżego pracownika"); var d = dodatki[0]; d.Last.Element.Should().NotBeNull("Element jest wymagany"); d.Last.Element.Nazwa.Should().Be("Premia"); d.Last.Okres.From.Should().Be(okres.From, "okres obowiązywania dodatku"); } [Test] [Description("C1 (definicja elementu): definicje dodatków pobieramy ze słownika Place.DefElementow; " + "definicja \"Premia\" istnieje w bazie Demo i jest źródłem typu Dodatek.")] public void C1_DefinicjaElementu_PobieranaZeSlownika_PoNazwie() { // DefElementow to kolekcja konfiguracyjna; indeksowanie WgNazwy zwraca definicję po nazwie. var premia = Place.DefElementow.WgNazwy["Premia"] as DefinicjaElementu; premia.Should().NotBeNull("baza Demo zawiera definicję \"Premia\""); premia.Nazwa.Should().Be("Premia"); // Definicje przeznaczone na dodatki mają RodzajZrodla == RodzajŹródłaWypłaty.Dodatek — // tym kryterium można filtrować dostępne definicje dodatków. premia.RodzajZrodla.Should().Be(RodzajŹródłaWypłaty.Dodatek, "Premia jest definicją źródła typu Dodatek"); } }