Files
soneta-erp-skills/Soneta.Skills.Test/KadryPlace/Pracownik/RozdzialBC_EtatDodatkiTest.cs
T
2026-06-06 22:33:15 +02:00

215 lines
11 KiB
C#

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;
/// <summary>
/// Rozdział B+C — „Etat (umowa o pracę)" i „Dodatki / stałe elementy wynagrodzenia"
/// (receptury B1 i C1 z dokumentu skilla <c>pracownik.md</c>).
/// <para>
/// Testy są <b>wykonywalną dokumentacją</b> publicznego kontraktu platformy Soneta. Pokazują:
/// <list type="bullet">
/// <item><b>B1</b> — warunki etatu siedzą w subrowie <c>PracHistoria.Etat</c>; stawkę ustawiamy na
/// subrowie <c>Etat.Zaszeregowanie</c> w wymaganej KOLEJNOŚCI (najpierw <c>RodzajStawki</c>, potem
/// <c>Wymiar</c>) — odwrócenie kolejności rzuca <see cref="ColReadOnlyException"/>;</item>
/// <item><b>C1</b> — dodatek (stały element wynagrodzenia) jest obiektem historycznym; tworzymy go
/// przez <c>new Dodatek(pracownik)</c> + <c>Kadry.Dodatki.AddRow</c>, a parametry (Element, Okres)
/// ustawiamy na pierwszym zapisie <c>d.Last</c>.</item>
/// </list>
/// </para>
/// <para>
/// Wszystko działa na bazie Demo (GoldStandard) z automatycznym rollbackiem po teście. Operujemy
/// wyłącznie na <b>publicznym kontrakcie</b> — tak jak dodatek programisty zewnętrznego bez dostępu
/// do kodu źródłowego aplikacji.
/// </para>
/// </summary>
[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<Prac>(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<Date> — 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<ColReadOnlyException>(
"TypUmowy jest read-only dopóki nie ustawiono Etat.Okres");
System.Action rodzajStawkiPrzedOkresem = () => etat.Zaszeregowanie.RodzajStawki = RodzajStawkiZaszeregowania.Miesieczna;
rodzajStawkiPrzedOkresem.Should().Throw<ColReadOnlyException>(
"Zaszeregowanie.RodzajStawki też jest read-only przed Etat.Okres");
System.Action wymiarPrzedOkresem = () => etat.Zaszeregowanie.Wymiar = new Fraction(1, 2);
wymiarPrzedOkresem.Should().Throw<ColReadOnlyException>(
"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<Prac>(guidPrac);
var dodatki = pracownik2.Dodatki.Cast<Dodatek>().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");
}
}