kontrahent.md

This commit is contained in:
Marcin Wojas
2026-06-05 15:48:46 +02:00
parent 67fcc9e996
commit 01de89b7b5
15 changed files with 1596 additions and 0 deletions
@@ -0,0 +1,64 @@
using AwesomeAssertions;
using NUnit.Framework;
namespace Soneta.Skills.Test.CRM.Kontrahenci;
/// <summary>
/// W6 — Adres kontrahenta.
/// Test pokazuje, że <c>Adres</c> to property zwracająca obiekt złożony (nie da się przypisać
/// całego adresu) — modyfikujemy jego pola. Uwaga na typ <c>KodPocztowy</c> = <c>int</c>
/// (do formatu „00-000" służy <c>KodPocztowyS</c>).
/// </summary>
[TestFixture]
public class AdresKontrahentaTest : KontrahentTestBase
{
[Test]
[Description("Ustawienie pól adresu głównego (ulica, kod pocztowy, miejscowość) jest zapisywane.")]
public void UstawienieAdresuGlownego_JestZapisywane()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Firma Z Adresem");
SaveDispose();
var k = Crm.Kontrahenci.WgKodu[kod];
InTransaction(() =>
{
var a = k.Adres; // edytujemy pola obiektu adresu
a.Ulica = "Wadowicka";
a.NrDomu = "8A";
a.NrLokalu = "2";
a.KodPocztowyS = "30-415"; // string z myślnikiem; pole int KodPocztowy = 30415
a.Miejscowosc = "Kraków";
a.Poczta = "Kraków";
a.Kraj = "Polska";
});
SaveDispose();
var a2 = Crm.Kontrahenci.WgKodu[kod].Adres;
a2.Ulica.Should().Be("Wadowicka");
a2.NrDomu.Should().Be("8A");
a2.Miejscowosc.Should().Be("Kraków");
a2.KodPocztowy.Should().Be(30415);
}
[Test]
[Description("Adres do korespondencji jest odrębnym obiektem od adresu głównego.")]
public void AdresDoKorespondencji_JestOdrebnyOdGlownego()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Firma Z Korespondencja");
SaveDispose();
var k = Crm.Kontrahenci.WgKodu[kod];
InTransaction(() =>
{
k.Adres.Miejscowosc = "Kraków";
k.AdresDoKorespondencji.Miejscowosc = "Warszawa";
});
SaveDispose();
var zapisany = Crm.Kontrahenci.WgKodu[kod];
zapisany.Adres.Miejscowosc.Should().Be("Kraków");
zapisany.AdresDoKorespondencji.Miejscowosc.Should().Be("Warszawa");
}
}
@@ -0,0 +1,68 @@
using System.Linq;
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.Core;
namespace Soneta.Skills.Test.CRM.Kontrahenci;
/// <summary>
/// W7 — Dane kontaktowe i adresy WWW.
/// Testy pokazują dodanie kanału e-mail do kolekcji <c>Kontakty</c> (typ rodzaju pobierany ze
/// słownika <c>RodzajeKontaktow</c>) oraz dodanie adresu WWW (konstruktor z hostem
/// <c>new AdresWWW(kontrahent)</c>, pole URL nazywa się <c>Adres</c>).
/// </summary>
[TestFixture]
public class DaneKontaktoweTest : KontrahentTestBase
{
[Test]
[Description("Dodanie domyślnego kontaktu e-mail pojawia się w kolekcji Kontakty kontrahenta.")]
public void DodanieEmaila_PojawiaSieWKolekcjiKontakty()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Firma Z Mailem");
SaveDispose();
var email = "kontakt@firma-" + kod + ".pl";
var k = Crm.Kontrahenci.WgKodu[kod];
var rodzajEmail = Session.GetCore().RodzajeKontaktow[RodzajeKontaktow.AdresEmail];
InUITransaction(() =>
{
var dk = Add(new DaneKontaktowe { Host = k });
dk.Rodzaj = rodzajEmail;
dk.Kontakt = email;
dk.Domyslny = true;
});
SaveDispose();
var zapisany = Crm.Kontrahenci.WgKodu[kod];
zapisany.Kontakty.Cast<DaneKontaktowe>()
.Any(d => d.Kontakt == email)
.Should().BeTrue();
}
[Test]
[Description("Dodanie adresu WWW (new AdresWWW(host)) pojawia się w kolekcji AdresyWWW.")]
public void DodanieAdresuWWW_PojawiaSieWKolekcji()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Firma Z WWW");
SaveDispose();
var url = "https://www.firma-" + kod + ".pl";
var k = Crm.Kontrahenci.WgKodu[kod];
InUITransaction(() =>
{
var www = Add(new AdresWWW(k)); // ctor przyjmuje IAdresyWWWHost
www.Adres = url; // pole URL nazywa się Adres
www.Domyslny = true;
});
SaveDispose();
var zapisany = Crm.Kontrahenci.WgKodu[kod];
zapisany.AdresyWWW.Cast<AdresWWW>()
.Any(w => w.Adres == url)
.Should().BeTrue();
}
}
@@ -0,0 +1,47 @@
using System.Linq;
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.CRM;
namespace Soneta.Skills.Test.CRM.Kontrahenci;
/// <summary>
/// W13/W14 — Klasyfikacja i powiązania (odczyt kontraktu publicznego).
/// Testy dokumentują dostęp do kolekcji klasyfikacyjnych (<c>Kategorie</c>, <c>Branze</c>,
/// <c>Features</c>) oraz powiązań (<c>Opiekunowie</c>, <c>Podrzedni</c>, <c>PodmiotNadrzedny</c>).
/// Świeżo utworzony, samodzielny kontrahent ma te kolekcje puste i brak podmiotu nadrzędnego —
/// co czyni asercje deterministycznymi.
/// </summary>
[TestFixture]
public class KlasyfikacjaIPowiazaniaTest : KontrahentTestBase
{
[Test]
[Description("Świeży kontrahent ma dostępne i puste kolekcje klasyfikacyjne; Features != null.")]
public void NowyKontrahent_KolekcjeKlasyfikacjiSaPusteAleDostepne()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Firma Klasyfikacja");
SaveDispose();
var k = Crm.Kontrahenci.WgKodu[kod];
k.Features.Should().NotBeNull(); // cechy definiowalne — dostęp po nazwie
k.Kategorie.Cast<KategoriaKth>().Should().BeEmpty();
k.Branze.Cast<BranzaKth>().Should().BeEmpty();
}
[Test]
[Description("Świeży kontrahent nie ma opiekunów, podmiotów podrzędnych ani nadrzędnego.")]
public void NowyKontrahent_BrakPowiazan()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Firma Powiazania");
SaveDispose();
var k = Crm.Kontrahenci.WgKodu[kod];
k.Opiekunowie.Cast<Opiekun>().Should().BeEmpty();
k.Podrzedni.Cast<RelacjaPodmiotu>().Should().BeEmpty();
k.PodmiotNadrzedny.Should().BeNull();
}
}
@@ -0,0 +1,50 @@
using System;
using Soneta.Core;
using Soneta.CRM;
using Soneta.Test;
namespace Soneta.Skills.Test.CRM.Kontrahenci;
/// <summary>
/// Wspólna baza testów kontrahenta. Dziedziczy z <see cref="TestBase"/>, dzięki czemu:
/// <list type="bullet">
/// <item>udostępnia gotową sesję operacyjną (<c>Session</c>) powiązaną z testową bazą Demo,</item>
/// <item>automatycznie wycofuje (rollback) wszystkie zmiany w bazie po zakończeniu testu,</item>
/// <item>daje metody pomocnicze <c>InTransaction</c>/<c>SaveDispose</c> do pracy w transakcjach.</item>
/// </list>
/// Baza dodaje skróty często powtarzane w testach kontrahenta (dostęp do modułu CRM,
/// generowanie unikalnego kodu, utworzenie minimalnego kontrahenta).
/// </summary>
public abstract class KontrahentTestBase : TestBase
{
/// <summary>Moduł CRM bieżącej sesji operacyjnej.</summary>
protected CRMModule Crm => Session.GetCRM();
/// <summary>Generuje krótki, unikalny kod kontrahenta (na potrzeby testów).</summary>
protected static string UnikalnyKod() => Guid.NewGuid().ToString("N").Substring(0, 10);
/// <summary>
/// Tworzy w bieżącej sesji nowego kontrahenta z minimalnym kompletem danych
/// (kod, nazwa, status i rodzaj podmiotu) wewnątrz transakcji edycyjnej.
/// Zwrócony obiekt żyje w bieżącej sesji — pozostaje ważny do czasu <c>SaveDispose</c>.
/// </summary>
protected Kontrahent UtworzKontrahenta(
string kod,
string nazwa = null,
StatusPodmiotu status = StatusPodmiotu.PodmiotGospodarczy,
RodzajPodmiotu rodzaj = RodzajPodmiotu.Krajowy)
{
Kontrahent k = null;
InTransaction(() =>
{
// AddRow MUSI poprzedzać ustawianie pól — obiekt najpierw trafia do tabeli.
k = new Kontrahent();
Session.AddRow(k);
k.Kod = kod;
k.Nazwa = nazwa ?? kod;
k.StatusPodmiotu = status;
k.RodzajPodmiotu = rodzaj;
});
return k;
}
}
@@ -0,0 +1,50 @@
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.CRM;
namespace Soneta.Skills.Test.CRM.Kontrahenci;
/// <summary>
/// W4 — Modyfikacja danych i statusów kontrahenta.
/// Testy pokazują zmianę nazwy oraz ustawienie statusów dostępności/handlowych:
/// <c>Blokada</c> (ukrycie na listach) i <c>BlokadaSprzedazy</c> (zakaz dokumentów rozchodu).
/// </summary>
[TestFixture]
public class ModyfikacjaIStatusyTest : KontrahentTestBase
{
[Test]
[Description("Zmiana nazwy kontrahenta jest trwale zapisywana.")]
public void ZmianaNazwy_JestZapisywana()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Nazwa Pierwotna");
SaveDispose();
var k = Crm.Kontrahenci.WgKodu[kod];
InTransaction(() => k.Nazwa = "Nazwa Zmieniona");
SaveDispose();
Crm.Kontrahenci.WgKodu[kod].Nazwa.Should().Be("Nazwa Zmieniona");
}
[Test]
[Description("Ustawienie Blokada i BlokadaSprzedazy jest trwale zapisywane.")]
public void UstawienieBlokad_JestZapisywane()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Do Zablokowania");
SaveDispose();
var k = Crm.Kontrahenci.WgKodu[kod];
InTransaction(() =>
{
k.Blokada = true; // ukrycie na listach
k.BlokadaSprzedazy = true; // zakaz wystawiania dokumentów rozchodu
});
SaveDispose();
var zapisany = Crm.Kontrahenci.WgKodu[kod];
zapisany.Blokada.Should().BeTrue();
zapisany.BlokadaSprzedazy.Should().BeTrue();
}
}
@@ -0,0 +1,44 @@
using System.Linq;
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.CRM;
namespace Soneta.Skills.Test.CRM.Kontrahenci;
/// <summary>
/// W8 — Osoby kontaktowe.
/// Test pokazuje dodanie osoby kontaktowej i powiązanie jej z kontrahentem przez
/// <c>KontaktOsoba.Kontrahent</c> — osoba pojawia się wtedy w kolekcji <c>Osoby</c> kontrahenta.
/// </summary>
[TestFixture]
public class OsobyKontaktoweTest : KontrahentTestBase
{
[Test]
[Description("Dodana i powiązana osoba kontaktowa pojawia się w kolekcji Osoby kontrahenta.")]
public void DodanieOsoby_PojawiaSieWKolekcjiOsoby()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Firma Z Osoba");
SaveDispose();
var email = "a.nowak@firma-" + kod + ".pl";
var k = Crm.Kontrahenci.WgKodu[kod];
InTransaction(() =>
{
var os = new KontaktOsoba();
Session.AddRow(os);
os.Kontrahent = k; // powiązanie osoby z kontrahentem
os.Imie = "Anna";
os.Nazwisko = "Nowak";
os.Stanowisko = "Kierownik zakupów";
os.EMAIL = email;
});
SaveDispose();
var zapisany = Crm.Kontrahenci.WgKodu[kod];
zapisany.Osoby.Cast<KontaktOsoba>()
.Any(o => o.Nazwisko == "Nowak" && o.Imie == "Anna")
.Should().BeTrue();
}
}
@@ -0,0 +1,71 @@
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.Core;
using Soneta.CRM;
namespace Soneta.Skills.Test.CRM.Kontrahenci;
/// <summary>
/// W3 — Tworzenie kontrahenta.
/// Testy pokazują utworzenie rekordu z minimalnym kompletem danych w transakcji edycyjnej
/// oraz trwały zapis (SaveDispose) i ponowny odczyt z nowej sesji. Pokrywają warianty:
/// podmiot gospodarczy krajowy, podmiot unijny oraz osoba fizyczna (finalny).
/// </summary>
[TestFixture]
public class TworzenieKontrahentaTest : KontrahentTestBase
{
[Test]
[Description("Tworzy krajowy podmiot gospodarczy z NIP i zapisuje go trwale w bazie.")]
public void TworzeniePodmiotuKrajowego_ZapisujeRekord()
{
var kod = UnikalnyKod();
var k = UtworzKontrahenta(kod, "Krajowa Firma Sp. z o.o.");
InTransaction(() =>
{
k.PodatnikVAT = true;
k.NIP = "1234563218"; // ustawienie NIP synchronizuje EuVAT
});
SaveDispose();
// Ponowny odczyt z nowej sesji potwierdza trwały zapis.
var zapisany = Crm.Kontrahenci.WgKodu[kod];
zapisany.Should().NotBeNull();
zapisany.Nazwa.Should().Be("Krajowa Firma Sp. z o.o.");
zapisany.StatusPodmiotu.Should().Be(StatusPodmiotu.PodmiotGospodarczy);
zapisany.RodzajPodmiotu.Should().Be(RodzajPodmiotu.Krajowy);
zapisany.PodatnikVAT.Should().BeTrue();
}
[Test]
[Description("Tworzy podmiot unijny (RodzajPodmiotu.Unijny).")]
public void TworzeniePodmiotuUnijnego_UstawiaRodzajUnijny()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "EU Trading GmbH",
status: StatusPodmiotu.PodmiotGospodarczy,
rodzaj: RodzajPodmiotu.Unijny);
SaveDispose();
var zapisany = Crm.Kontrahenci.WgKodu[kod];
zapisany.Should().NotBeNull();
zapisany.RodzajPodmiotu.Should().Be(RodzajPodmiotu.Unijny);
}
[Test]
[Description("Tworzy osobę fizyczną (StatusPodmiotu.Finalny).")]
public void TworzenieOsobyFizycznej_UstawiaStatusFinalny()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Jan Kowalski",
status: StatusPodmiotu.Finalny,
rodzaj: RodzajPodmiotu.Krajowy);
SaveDispose();
var zapisany = Crm.Kontrahenci.WgKodu[kod];
zapisany.Should().NotBeNull();
zapisany.StatusPodmiotu.Should().Be(StatusPodmiotu.Finalny);
}
}
@@ -0,0 +1,48 @@
using AwesomeAssertions;
using NUnit.Framework;
namespace Soneta.Skills.Test.CRM.Kontrahenci;
/// <summary>
/// W5 — Bezpieczne usuwanie kontrahenta.
/// Test pokazuje czyste usunięcie świeżo utworzonego rekordu (brak powiązań) oraz alternatywę
/// „miękkiego" wycofania (<c>Blokada=true</c>), zalecaną gdy istnieją dokumenty/rozrachunki.
/// </summary>
[TestFixture]
public class UsuwanieKontrahentaTest : KontrahentTestBase
{
[Test]
[Description("Usunięcie kontrahenta bez powiązań (DeleteRow) usuwa rekord z bazy.")]
public void CzysteUsuniecie_UsuwaRekord()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Do Usuniecia");
SaveDispose();
var k = Crm.Kontrahenci.WgKodu[kod];
k.Should().NotBeNull();
InTransaction(() => k.Delete());
SaveDispose();
// Po usunięciu indeksator zwraca null.
Crm.Kontrahenci.WgKodu[kod].Should().BeNull();
}
[Test]
[Description("Miękkie wycofanie: zamiast usuwać, ustawiamy Blokada=true (rekord pozostaje).")]
public void MiekkieWycofanie_UstawiaBlokade()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Do Wycofania");
SaveDispose();
var k = Crm.Kontrahenci.WgKodu[kod];
InTransaction(() => k.Blokada = true);
SaveDispose();
var zapisany = Crm.Kontrahenci.WgKodu[kod];
zapisany.Should().NotBeNull();
zapisany.Blokada.Should().BeTrue();
}
}
@@ -0,0 +1,56 @@
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.Core;
namespace Soneta.Skills.Test.CRM.Kontrahenci;
/// <summary>
/// W2 — Walidacja NIP / REGON / EU VAT przed zapisem.
/// Testy weryfikują publiczne, statyczne walidatory z <c>Soneta.Core</c>
/// (<see cref="Nip"/>, <see cref="Regon"/>, <see cref="EuVat"/>) oraz normalizację numerów.
/// Walidatory sprawdzają format i sumę kontrolną — to NIE jest weryfikacja w MF/VIES (patrz W15).
/// </summary>
[TestFixture]
public class WalidacjaNipRegonTest : KontrahentTestBase
{
[Test]
[Description("Nip.Test akceptuje poprawny NIP (10 cyfr i format z myślnikami), odrzuca błędny.")]
public void NipTest_RozrozniaPoprawnyIBledny()
{
// 1234563218 ma poprawną sumę kontrolną.
Nip.Test("1234563218").Should().BeTrue();
Nip.Test("123-456-32-18").Should().BeTrue();
// Zmiana ostatniej cyfry psuje sumę kontrolną.
Nip.Test("1234563219").Should().BeFalse();
Nip.Test("123").Should().BeFalse();
// Normalizacja: Flat usuwa myślniki, Format dodaje.
Nip.Flat("123-456-32-18").Should().Be("1234563218");
Nip.Format("1234563218").Should().Be("123-456-32-18");
}
[Test]
[Description("Regon.Test akceptuje poprawny REGON 9-znakowy, odrzuca błędny i o złej długości.")]
public void RegonTest_RozrozniaPoprawnyIBledny()
{
// 123456785 ma poprawną sumę kontrolną dla 9-znakowego REGON.
Regon.Test("123456785").Should().BeTrue();
Regon.Test("123456784").Should().BeFalse();
Regon.Test("12345").Should().BeFalse();
}
[Test]
[Description("EuVat.Test akceptuje krajowy numer z prefiksem PL nad poprawnym NIP, odrzuca błędny.")]
public void EuVatTest_PrefiksPL_DzialaNaPoprawnymNip()
{
// EuVat.Test wymaga ISessionable (sprawdza listę krajów UE w bazie).
EuVat.Test("PL1234563218", Session).Should().BeTrue();
EuVat.Test("PL1234563219", Session).Should().BeFalse();
// Rozbicie numeru na kod kraju + identyfikator.
EuVat.Split("PL1234563218", out var kraj, out var numer);
kraj.Should().Be("PL");
numer.Should().Be("1234563218");
}
}
@@ -0,0 +1,56 @@
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.CRM;
using Soneta.Kasa;
namespace Soneta.Skills.Test.CRM.Kontrahenci;
/// <summary>
/// W9 — Warunki płatności i limity kredytowe.
/// Testy pokazują ustawienie sposobu zapłaty (rekord <c>FormaPlatnosci</c> z modułu Kasa),
/// terminu płatności oraz typu limitu kredytowego. Pola kalkulowane (np.
/// <c>LimitNieograniczony</c>) są tylko do odczytu i wynikają z ustawień.
/// </summary>
[TestFixture]
public class WarunkiPlatnosciTest : KontrahentTestBase
{
[Test]
[Description("Ustawienie sposobu zapłaty (Przelew) i terminu płatności jest zapisywane.")]
public void WarunkiPlatnosci_SposobIZaplatyTermin_SaZapisywane()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Firma Z Platnosciami");
SaveDispose();
var k = Crm.Kontrahenci.WgKodu[kod];
var przelew = Session.GetKasa().FormyPlatnosci[FormaPlatnosci.Przelew];
InTransaction(() =>
{
k.SposobZaplaty = przelew;
k.Termin = 14; // dni
});
SaveDispose();
var zapisany = Crm.Kontrahenci.WgKodu[kod];
zapisany.SposobZaplaty.Should().NotBeNull();
zapisany.Termin.Should().Be(14);
}
[Test]
[Description("Typ limitu kredytowego = Nieograniczony skutkuje kalkulowanym LimitNieograniczony=true.")]
public void LimitKredytowy_Nieograniczony_UstawiaFlageKalkulowana()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Firma Bez Limitu");
SaveDispose();
var k = Crm.Kontrahenci.WgKodu[kod];
InTransaction(() => k.TypLimituKredytowego = TypLimituKredytowego.Nieograniczony);
SaveDispose();
var zapisany = Crm.Kontrahenci.WgKodu[kod];
zapisany.TypLimituKredytowego.Should().Be(TypLimituKredytowego.Nieograniczony);
zapisany.LimitNieograniczony.Should().BeTrue(); // pole kalkulowane (read-only)
}
}
@@ -0,0 +1,70 @@
using System.Linq;
using AwesomeAssertions;
using NUnit.Framework;
using Soneta.Core;
using Soneta.CRM;
namespace Soneta.Skills.Test.CRM.Kontrahenci;
/// <summary>
/// W1 — Wyszukiwanie i identyfikacja kontrahenta.
/// Testy pokazują trzy podstawowe sposoby odnajdywania kontrahenta używane w kodzie dodatków:
/// po kodzie (klucz unikalny), po nazwie (klucz nieunikalny) oraz po NIP (filtr serwerowy
/// <c>SubTable[condition]</c>, zamiast iteracji całej tabeli w pamięci).
/// </summary>
[TestFixture]
public class WyszukiwanieKontrahentaTest : KontrahentTestBase
{
[Test]
[Description("Wyszukanie po kodzie (indeks WgKodu) zwraca dokładnie utworzony rekord.")]
public void WyszukiwaniePoKodzie_ZwracaUtworzonyRekord()
{
var kod = UnikalnyKod();
UtworzKontrahenta(kod, "Firma Po Kodzie");
SaveDispose();
// WgKodu to klucz unikalny — indeksator zwraca pojedynczy rekord lub null.
var znaleziony = Crm.Kontrahenci.WgKodu[kod];
znaleziony.Should().NotBeNull();
znaleziony.Nazwa.Should().Be("Firma Po Kodzie");
}
[Test]
[Description("Wyszukanie po nazwie (indeks WgNazwy, nieunikalny) zwraca zbiór z rekordem.")]
public void WyszukiwaniePoNazwie_ZwracaRekordWZbiorze()
{
var kod = UnikalnyKod();
var nazwa = "Wyszukiwarka " + kod;
UtworzKontrahenta(kod, nazwa);
SaveDispose();
// WgNazwy jest kluczem nieunikalnym — zwraca zbiór, z którego bierzemy pierwszy.
var znaleziony = Crm.Kontrahenci.WgNazwy[nazwa].FirstOrDefault();
znaleziony.Should().NotBeNull();
znaleziony.Kod.Should().Be(kod);
}
[Test]
[Description("Wyszukanie po NIP filtrem serwerowym SubTable[condition] zwraca rekord; " +
"dedup wykrywa istniejący podmiot.")]
public void WyszukiwaniePoNip_FiltrSerwerowy_ZnajdujeISygnalizujeDuplikat()
{
var kod = UnikalnyKod();
var nip = "1234563218"; // poprawny NIP (suma kontrolna)
var k = UtworzKontrahenta(kod, "Firma Z NIP");
InTransaction(() => k.NIP = nip);
SaveDispose();
// Filtr po stronie serwera (klauzula WHERE w SQL), nie iteracja w pamięci.
// Warunek aplikujemy na indeksie tabeli (WgNIP); porównania tekstowe są case-insensitive.
var znaleziony = Crm.Kontrahenci.WgNIP[(Kontrahent x) => x.NIP == nip].FirstOrDefault();
znaleziony.Should().NotBeNull();
znaleziony.Kod.Should().Be(kod);
// Typowy dedup przed dodaniem nowego kontrahenta:
bool juzIstnieje = Crm.Kontrahenci.WgNIP[(Kontrahent x) => x.NIP == nip].Any();
juzIstnieje.Should().BeTrue();
}
}