Files
soneta-erp-skills/soneta-programming/references/domeny/kontrahent.md
T
Marcin Wojas 01de89b7b5 kontrahent.md
2026-06-05 15:48:46 +02:00

39 KiB

Kontrahent — receptury kodu biznesowego (Soneta / enova365)

Zbiór gotowych wzorców kodu dla obiektu biznesowego Soneta.CRM.Kontrahent (tabela Kontrahenci). Dokument jest częścią skilla soneta-programming. Celem jest, aby agent pisał bezbłędny kod biznesowy operujący na kontrahencie — trafiający w realne pola, kolekcje i workery platformy.

Format zwarty: każdy wzorzec opisuje ogólny przypadek + tabelę wariantów, zamiast wielu wąskich pozycji. Fundamenty (sesja, transakcja, blokada optymistyczna, praca z SubTable, obsługa błędów) są opisane w safe-code.md, session-login.md oraz worker-extender.md — tutaj się do nich odwołujemy, nie powtarzamy ich.

Cały kod w tym dokumencie jest zgodny z C# 10 (target-typed new, var, wyrażenia switch, nazwane parametry bool). Snippety operują wyłącznie na publicznym kontrakcie platformy — nie ma odwołań do prywatnych klas ani kodu źródłowego aplikacji.

Fakty o typie (zweryfikowane skanem DLL — scan-props.csx)

  • Klasa biznesowa: Soneta.CRM.KontrahentGuidedRow (root), tabela Soneta.CRM.Kontrahenci.
  • Implementuje: IPodmiot, IKontrahent, IPodmiotKasowy, IElementSlownika, IAdresHost, IKodowany, IAdresyWWWHost, IDaneKontaktoweHost, IEmailElement, IRegonHost, IGIODOZgodnyHost, IGIODOWymianaDanychHost, IGIODOOświadczenieHost.
  • Pola: 75 bazodanowych + 142 kalkulowane.
  • Moduł: Soneta.CRM.CRMModule, dostęp session.GetCRM(). Tabela: crm.Kontrahenci.
  • Kluczowe pola bazodanowe (zapisywalne): Kod: string, Nazwa: string, NIP: string, EuVAT: string, PESEL: string, REGON: string, KRS: string, StatusPodmiotu: Soneta.Core.StatusPodmiotu, RodzajPodmiotu: Soneta.Core.RodzajPodmiotu (= „Rodzaj VAT dla sprzedaży"), RodzajPodmiotuZakup: Soneta.Core.RodzajPodmiotu, PodatnikVAT: bool, VATLiczonyOd: Soneta.CRM.VatKontahentaLiczonyOd, FormaPrawna: Soneta.CRM.FormaPrawna, Waluta: Soneta.Waluty.Waluta, SposobZaplaty: Soneta.Kasa.FormaPlatnosci, Termin: int, TerminPlanowany: int, LimitKredytu: Currency, TypLimituKredytowego: Soneta.CRM.TypLimituKredytowego, KontrolaKwota: Currency, KontrolaDni: int, TypPrzeterminowania: Soneta.CRM.TypLimituKredytowego, Blokada: bool, BlokadaSprzedazy: bool, Zamiennik: Kontrahent, EFaktura: Soneta.Core.EFaktura, EFakturaOkres: FromTo, NieWindykowac: bool, DefinicjaSprawyWindykacyjnej, OddzialFirmy, Region, Rabat: Percent, DomyslnySzablonPolOpcjonalnychKSeF, KSeFSposobObslugiWysylkiCeny.
  • Pola złożone: Adres: Soneta.Core.Adres, AdresDoKorespondencji: Soneta.Core.Adres, Kontakt: Soneta.Core.Kontakt (Kontakt.EMAIL, Kontakt.TelefonKomorkowy, Kontakt.WWW, Kontakt.SkrytkaPocztowa, Kontakt.Skype), OdsKarne: Soneta.Kasa.OdsetkiKarne.
  • Pola kalkulowane (tylko do odczytu): Nazwa jest zapisywalna, ale NazwaFormatowana, NazwaPierwszaLinia, KodKraju, JestIncydentalny, IsStandard, DomyslnyRachunek, Platnik, LimitNieograniczony, PrzeterminowanieNieograniczone, KontrolaAktywna, AktualnyStatusVAT, AktualnyStatusVATMF, AktualnyStatusVATViesnie ustawiaj ich bezpośrednio.
  • Kolekcje (SubTable): Osoby (SubTable<KontaktOsoba>), Kontakty (SubTable<DaneKontaktowe>), AdresyWWW (SubTable<AdresWWW>), Kategorie (SubTable<KategoriaKth>), Branze (SubTable<BranzaKth>), Opiekunowie (SubTable<Opiekun>), Rachunki (SubTable<Soneta.Kasa.RachunekBankowyPodmiotu>), Rozrachunki (SubTable<Soneta.Kasa.RozrachunekIdx>), Podrzedni (SubTable<RelacjaPodmiotu>), StatusyVAT (SubTable<StatusVAT>), KodyKreskowe (SubTable<KodKreskowy>), GIODOOświadczenia (SubTable<GIODOOświadczenie>), GIODOUdostępnienia (SubTable<GIODOWymianaDanych>), PotwierdzeniaGIODO (SubTable<GIODOZgodny>).
  • Cechy: Features: Soneta.Business.FeatureCollection (indeksator po nazwie definicji cechy).

Szablon wzorca

Każdy wzorzec (Wn) ma stałą strukturę:

  • Cel — co robi i kiedy go użyć.
  • Warianty — tabela odmian przypadku.
  • Pola i typy — realne właściwości/kolekcje i ich typy.
  • Snippet — kod C# 10.
  • Pułapki — typowe błędy i zasady safe-code.

1. Wyszukiwanie i identyfikacja

W1 — Wyszukiwanie kontrahenta

Cel: odnaleźć istniejącego kontrahenta po wybranym kluczu, zanim zaczniemy go modyfikować lub zanim utworzymy nowy rekord.

Warianty:

Wariant Klucz Uwaga
Po kodzie Kod indeks WgKodu, klucz unikalny — zwraca pojedynczy rekord
Po nazwie / fragmencie Nazwa indeks WgNazwy (nieunikalny) lub SubTable[pattern]
Po NIP / EU VAT NIP, EuVAT normalizacja: Nip.Flat / EuVat.Flat przed porównaniem
Po adresie Adres.* miejscowość, kod pocztowy, ulica
Po PESEL / REGON / KRS PESEL, REGON, KRS osoby fizyczne / podmioty
Dedup przed dodaniem NIP sprawdzenie, czy podmiot już istnieje
Kontrahent incydentalny JestIncydentalny systemowy rekord (Kontrahent.INCYDENTALNY)

Pola i typy: Kod: string, NIP: string, EuVAT: string, Nazwa: string, Adres: Soneta.Core.Adres, PESEL/REGON/KRS: string, JestIncydentalny: bool.

Snippet:

var crm = session.GetCRM();

// 1. Po kodzie — klucz unikalny, zwraca pojedynczy rekord lub null
Kontrahent poKodzie = crm.Kontrahenci.WgKodu["ABC"];

// 2. Po nazwie — indeks nieunikalny, zwraca zbiór; bierzemy pierwszy
Kontrahent poNazwie = crm.Kontrahenci.WgNazwy["Firma XYZ"].FirstOrDefault();

// 3. Po NIP — filtr serwerowy; warunek aplikujemy na indeksie. Porównania tekstowe są case-insensitive
var nip = Nip.Flat("123-456-32-18");           // usuwa myślniki
Kontrahent poNip = crm.Kontrahenci.WgNIP[(Kontrahent k) => k.NIP == nip].FirstOrDefault();

// 4. Po fragmencie nazwy / mieście — serwerowy LIKE (warunek na indeksie WgNazwy)
foreach (Kontrahent k in crm.Kontrahenci.WgNazwy[(Kontrahent k) =>
             k.Nazwa.Contains("bud") && k.Adres.Miejscowosc == "Kraków"])
{
    // ...
}

// 5. Dedup przed dodaniem nowego kontrahenta
bool juzIstnieje = crm.Kontrahenci.WgNIP[(Kontrahent k) => k.NIP == nip].Any();

Pułapki:

  • WgKodu[...] zwraca pojedynczy rekord (klucz unikalny) — może być null. WgNazwy[...] zwraca zbiór (klucz nieunikalny), trzeba .FirstOrDefault()/iterację.
  • Nie iteruj całej tabeli Kontrahenci z if w pamięci — to tabela kartotekowa (rośnie z biznesem). Filtruj przez warunek aplikowany na indeksie, np. crm.Kontrahenci.WgKodu[(Kontrahent k) => …] (warunek wykonywany przez SQL). Indeksator samej tabeli (crm.Kontrahenci[…]) służy do dostępu po ID/kluczu, nie przyjmuje wyrażenia LINQ. Patrz rowcondition.md i safe-code.md §6.
  • W RowCondition (wyrażeniu LINQ) wolno użyć tylko pól bazodanowych. NazwaFormatowana, KodKraju, Platnik są kalkulowane → rzucą LinqConditionException.
  • Porównania tekstowe w warunku są case-insensitive — nie dubluj ToLower().
  • Przed porównaniem NIP/EU VAT normalizuj wejście (Nip.Flat, EuVat.Flat), bo w bazie bywają formaty z myślnikami i bez.

W2 — Walidacja NIP / REGON / EU VAT

Cel: sprawdzić poprawność NIP/REGON (suma kontrolna) i EU VAT (format/kraj) przed zapisem, niezależnie od weryfikacji online (W15).

Warianty:

Wariant Wejście Metoda publiczna
NIP krajowy 10 cyfr lub DDD-DDD-DD-DD Soneta.Core.Nip.Test(string)
REGON 9/14 9 lub 14 cyfr Soneta.Core.Regon.Test(string)
EU VAT prefiks kraju + numer Soneta.Core.EuVat.Test(string, ISessionable)
Normalizacja usunięcie myślników/spacji Nip.Flat, Nip.Format, EuVat.Flat
Rozbicie EU VAT kraj + numer EuVat.Split(value, out country, out nip)

Pola i typy: NIP: string, REGON: string, EuVAT: string. Walidatory są statyczne; EuVat.Test wymaga ISessionable (sprawdza listę krajów UE w bazie).

Snippet:

// Walidatory rzucają NullReferenceException dla null — najpierw odsiej puste wejście.
if (!nip.IsNullOrEmpty() && Nip.Test(nip)) { /* NIP poprawny */ }
if (!regon.IsNullOrEmpty() && Regon.Test(regon)) { /* REGON poprawny */ }
if (!euVat.IsNullOrEmpty() && EuVat.Test(euVat, session)) { /* EU VAT poprawny */ }

// Rozbicie EU VAT "PL1234563218" -> kraj "PL", numer "1234563218"
EuVat.Split(euVat, out string kodKraju, out string numer);

// Walidacja w event-handlerze zapisu (rzut PRZED Commit/Save):
if (!kontrahent.NIP.IsNullOrEmpty() && !Nip.Test(kontrahent.NIP))
    throw new RowException(kontrahent, "Nieprawidłowy NIP".Translate(), nameof(kontrahent.NIP));

Pułapki:

  • Nip.Test, Regon.Test, EuVat.Test rzucają NullReferenceException dla null (odwołują się do .Length). Zawsze najpierw sprawdź IsNullOrEmpty.
  • To walidacja formatu/sumy kontrolnej, a nie weryfikacja w MF/VIES — patrz W15.
  • Komunikaty walidacyjne rzucaj jako RowException(row, "…".Translate(), nameof(Pole)) przed Commit() (safe-code §5.1). Wyjątek po Commit() nie wycofa zmiany z sesji.
  • Ustawienie NIP/EuVAT na samym Kontrahent uruchamia wbudowaną synchronizację (NIP↔EuVAT, auto-zmiana RodzajPodmiotu) — własna walidacja jest dodatkiem, nie zastępstwem.

2. Tworzenie, modyfikacja, usuwanie

W3 — Tworzenie kontrahenta

Cel: utworzyć nowy rekord kontrahenta z poprawnym minimalnym zestawem pól i wartościami domyślnymi.

Warianty:

Wariant Charakterystyka Pola krytyczne
Podmiot gospodarczy krajowy firma w PL StatusPodmiotu=PodmiotGospodarczy, RodzajPodmiotu=Krajowy, NIP
Unijny / zagraniczny sprzedaż wewn.-unijna / eksport EuVAT, RodzajPodmiotu=Unijny/Eksportowy
Osoba fizyczna / finalny konsument StatusPodmiotu=Finalny, PESEL

Pola i typy: Kod: string, Nazwa: string, StatusPodmiotu: Soneta.Core.StatusPodmiotu (PodmiotGospodarczy=0, Finalny=1), RodzajPodmiotu: Soneta.Core.RodzajPodmiotu (Krajowy=0, Eksportowy=1, EksportowyPodróżny=2, Unijny=3, UnijnyTrójstronny=4, BezVAT=5), PodatnikVAT: bool, FormaPrawna: Soneta.CRM.FormaPrawna.

Nadawanie kodu / numeracji: Kod jest polem tekstowym ustawianym jawnie. Może być wymagana jego unikalność (zależnie od konfiguracji modułu CRM); w razie kolizji Save() zgłosi RowException z DuplicateKeyException w InnerException.

Snippet:

var crm = session.GetCRM();

using (var t = session.Logout(editMode: true))
{
    var k = new Kontrahent();
    crm.Kontrahenci.AddRow(k);                 // najpierw dodaj do tabeli, potem ustawiaj pola

    k.Kod = "FIRMA001";
    k.Nazwa = "Firma XYZ Sp. z o.o.";
    k.StatusPodmiotu = StatusPodmiotu.PodmiotGospodarczy;
    k.RodzajPodmiotu = RodzajPodmiotu.Krajowy;
    k.PodatnikVAT = true;
    k.NIP = "1234563218";                      // ustawienie NIP synchronizuje EuVAT

    t.Commit();                                // Commit() w kodzie biznesowym
}
session.Save();                                // zapis do bazy; tu wykryte konflikty/duplikaty

Pułapki:

  • Tworzenie wyłącznie w transakcji (session.Logout(editMode: true)). AddRow przed ustawianiem pól.
  • W workerze/extenderze (uruchamianym z UI) używaj t.CommitUI() zamiast t.Commit() (safe-code, worker-extender.md).
  • Nazwa jest zapisywalna; NazwaFormatowana/NazwaPierwszaLinia są kalkulowane — nie ustawiaj.
  • Dla podmiotu unijnego ustaw EuVAT (z prefiksem kraju) — platforma sama dostosuje RodzajPodmiotu.
  • Brak Commit() = automatyczny rollback przy Dispose().

W4 — Modyfikacja i statusy

Cel: zmienić dane istniejącego kontrahenta lub jego status dostępności/handlowy.

Warianty:

Wariant Pole / operacja
Edycja danych identyfikacyjnych Kod, Nazwa, NIP, … (blokada optymistyczna)
Ukrycie na listach Blokada: bool
Blokada sprzedaży BlokadaSprzedazy: bool
Zmiana formy prawnej FormaPrawna (poj. lub masowo: worker ZmienFormePrawnaKontrahentowWorker)
Zastąpienie (zamiennik) Zamiennik: Kontrahent (ustawia automatycznie Blokada=true)
Kopiowanie kontrahenta worker Soneta.CRM.KopiujKontrahentaWorker (akcja „Kopiuj kontrahenta...")

Pola i typy: Blokada: bool, BlokadaSprzedazy: bool, FormaPrawna: Soneta.CRM.FormaPrawna, Zamiennik: Soneta.CRM.Kontrahent.

Snippet:

var crm = session.GetCRM();
var k = crm.Kontrahenci.WgKodu["FIRMA001"];
if (k == null) return;

using (var t = session.Logout(editMode: true))
{
    k.Nazwa = "Firma XYZ S.A.";
    k.BlokadaSprzedazy = true;                 // zakaz wystawiania dokumentów rozchodu
    k.Blokada = true;                          // ukrycie na listach
    t.Commit();
}
session.Save();

// Kopiowanie kontrahenta — programowe użycie workera (bez UI):
var kopiarka = new KopiujKontrahentaWorker { Kontrahent = k };
using (var t = session.Logout(editMode: true))
{
    Kontrahent nowy = kopiarka.Kopiuj();
    t.Commit();
}
session.Save();

Pułapki:

  • Blokada optymistyczna: konflikt edycji (ktoś inny zapisał ten rekord) wybucha w session.Save() jako RowConflictException — obsłuż go (refresh + retry lub eskalacja), nie połykaj (safe-code §4).
  • Nie nadpisuj Kod rekordów standardowych (IsStandard == true) ani incydentalnego (JestIncydentalny == true).
  • Zamiennik ma efekt uboczny — ustawienie zamiennika włącza Blokada=true. Do rozwiązania „aktualnego" kontrahenta służy Kontrahent.Coalesce(k) (zwraca zamiennika albo sam rekord).
  • Worker KopiujKontrahentaWorker ma property [Context] Kontrahent — przy ręcznym użyciu ustaw ją przed wywołaniem Kopiuj(); operacja musi być w transakcji.

W5 — Bezpieczne usuwanie

Cel: usunąć kontrahenta albo świadomie odmówić usunięcia, gdy istnieją powiązania.

Warianty:

Wariant Sytuacja Zalecenie
Usunięcie czyste brak dokumentów/rozrachunków/zadań/zdarzeń dozwolone (DeleteRow)
Usunięcie zablokowane są dokumenty/rozrachunki/zapisy zamiast usuwać → Blokada=true
Kontrahent systemowy IsStandard / JestIncydentalny nie usuwać

Pola i typy: DokumentyHandlowe, Rozrachunki, Zadania, Zdarzenia (kolekcje SubTable), IsStandard: bool, JestIncydentalny: bool, Blokada: bool.

Snippet:

var crm = session.GetCRM();
var k = crm.Kontrahenci.WgKodu["FIRMA001"];
if (k == null) return;

if (k.IsStandard || k.JestIncydentalny)
    throw new BusException("Nie można usunąć kontrahenta systemowego.".Translate());

bool maPowiazania = !k.DokumentyHandlowe.IsEmpty || !k.Rozrachunki.IsEmpty
                    || !k.Zadania.IsEmpty || !k.Zdarzenia.IsEmpty;

using (var t = session.Logout(editMode: true))
{
    if (maPowiazania)
        k.Blokada = true;                      // miękkie wycofanie zamiast usunięcia
    else
        k.Delete();                            // twarde usunięcie tylko gdy brak powiązań
    t.Commit();
}
session.Save();

Pułapki:

  • Sprawdź powiązania przed DeleteRow(). Próba usunięcia powiązanego rekordu i tak zostanie odrzucona przez integralność (wyjątek w Save()), ale lepiej zdecydować świadomie.
  • Preferuj Blokada=true (kontrahent znika z list, dane pozostają) zamiast kasowania, gdy są powiązania historyczne.
  • IsEmpty/Any na kolekcji SubTable to właściwości (test serwerowy exists …, bez nawiasów) — nie materializuj kolekcji do pamięci (.ToList().Count).

3. Adres, kontakt, osoby

W6 — Adres

Cel: wprowadzić lub zaktualizować adres kontrahenta.

Warianty:

Wariant Pole
Adres główny Adres: Soneta.Core.Adres
Adres do korespondencji AdresDoKorespondencji: Soneta.Core.Adres
Telefon / faks na adresie Adres.Telefon, Adres.Faks
Dane rozszerzone / nietypowa lokalizacja / GLN Adres.NietypowaLokalizacja, Adres.GLN

Pola i typy (Soneta.Core.Adres): Ulica: string, NrDomu: string, NrLokalu: string, KodPocztowy: int, KodPocztowyS: string (sformatowany, np. "31-000"), Poczta: string, Miejscowosc: string, Gmina: string, Powiat: string, Wojewodztwo: Soneta.Core.Wojewodztwa (enum), Kraj: string, KodKraju: string, GLN: string, Telefon: string, Faks: string, ZagranicznyKodPocztowy: string.

Snippet:

var k = session.GetCRM().Kontrahenci.WgKodu["FIRMA001"];

using (var t = session.Logout(editMode: true))
{
    var a = k.Adres;                           // property zwraca obiekt adresu — edytujemy jego pola
    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.Wojewodztwo = Wojewodztwa.małopolskie;
    a.Kraj = "Polska";
    a.Telefon = "+48 12 345 67 89";

    // Adres do korespondencji (gdy różny od głównego)
    k.AdresDoKorespondencji.Ulica = "Skrytka pocztowa 15";
    t.Commit();
}
session.Save();

Pułapki:

  • Adres to property kalkulowana zwracająca obiekt złożony — nie da się przypisać k.Adres = …; modyfikuj jego pola.
  • KodPocztowy jest typu int (np. 30415). Do wartości z myślnikiem używaj KodPocztowyS (string), które samo rozkłada/składa kod.
  • Wojewodztwo to enum Soneta.Core.Wojewodztwa, nie string.
  • KodKraju adresu bywa kalkulowane z Kraj — ustawiaj Kraj/KodKraju spójnie.

W7 — Dane kontaktowe i adresy WWW

Cel: odczytać i zapisać kanały kontaktu (e-mail, telefon, faks, WWW) z oznaczeniem domyślnego.

Warianty:

Wariant Kolekcja / pole
Odczyt domyślnego e-maila/telefonu/WWW Kontakt.EMAIL, Kontakt.TelefonKomorkowy, Kontakt.WWW
Dodanie kanału kontaktu Kontakty: SubTable<DaneKontaktowe> (Rodzaj, Kontakt, Domyslny)
Adresy WWW AdresyWWW: SubTable<AdresWWW> (Adres, Domyslny)
e-faktura EFaktura: Soneta.Core.EFaktura, EFakturaOkres: FromTo

Pola i typy: Kontakt: Soneta.Core.Kontakt (zsumowany „domyślny" kontakt — EMAIL, TelefonKomorkowy, WWW, SkrytkaPocztowa, Skype). DaneKontaktowe: Host: IDaneKontaktoweHost, Rodzaj: RodzajKontaktu, Kontakt: string, Domyslny: bool. AdresWWW: Adres: string, Domyslny: bool.

Snippet:

var k = session.GetCRM().Kontrahenci.WgKodu["FIRMA001"];

// Odczyt domyślnych kanałów (do automatyzacji wysyłek):
string email = k.Kontakt.EMAIL;
string tel   = k.Kontakt.TelefonKomorkowy;
string www   = k.Kontakt.WWW;

// Dodanie nowego kanału e-mail i oznaczenie go jako domyślny:
var rodzajEmail = session.GetCore().RodzajeKontaktow[RodzajeKontaktow.AdresEmail];
using (var t = session.Logout(editMode: true))
{
    var dk = new DaneKontaktowe { Host = k };  // Host = kontrahent (IDaneKontaktoweHost)
    session.AddRow(dk);
    dk.Rodzaj = rodzajEmail;
    dk.Kontakt = "kontakt@firma-xyz.pl";
    dk.Domyslny = true;

    // Dodanie adresu WWW:
    var strona = new AdresWWW(k);              // ctor przyjmuje IAdresyWWWHost
    session.AddRow(strona);
    strona.Adres = "https://www.firma-xyz.pl";
    strona.Domyslny = true;
    t.Commit();
}
session.Save();

Pułapki:

  • DaneKontaktowe.Rodzaj to rekord słownika RodzajKontaktu — pobierz go po stałej Guid przez session.GetCore().RodzajeKontaktow[RodzajeKontaktow.AdresEmail] (analogicznie TelefonKomórkowy, TelefonStacjonarny, Faks, Skype).
  • Tylko jeden kontakt domyślny w obrębie rodzaju — ustawienie Domyslny=true na nowym zwykle zdejmuje flagę z poprzedniego.
  • k.Kontakt.* to zagregowany widok domyślnych kontaktów (do odczytu w automatyzacji). Pełna lista kanałów jest w kolekcji k.Kontakty.
  • AdresWWW tworzymy konstruktorem z hostem (new AdresWWW(k)); pole adresu URL nazywa się Adres (nie Url).

W8 — Osoby kontaktowe

Cel: zarządzać osobami kontaktowymi przypisanymi do kontrahenta.

Warianty:

Wariant Operacja
Odczyt listy Osoby: SubTable<KontaktOsoba> (Imie, Nazwisko, Stanowisko, EMAIL, Nieaktualny)
Dodanie osoby nowy KontaktOsoba, ustaw Kontrahent
Edycja osoby zmiana pól
Oznaczenie nieaktualnej flaga Nieaktualny (zamiast usuwania)
Dołącz / odłącz istniejącą workery DolaczOsobeKontrahentaWorker, RozlaczKontrahentaWorker

Pola i typy (KontaktOsoba): Imie: string, Nazwisko: string, Stanowisko: string, EMAIL: string, Nieaktualny: bool, Kontrahent: IKontrahent (powiązanie).

Snippet:

var k = session.GetCRM().Kontrahenci.WgKodu["FIRMA001"];

// Odczyt aktualnych osób:
foreach (KontaktOsoba os in k.Osoby[(KontaktOsoba o) => !o.Nieaktualny])
    Console.WriteLine($"{os.Imie} {os.Nazwisko} — {os.Stanowisko}");

// Dodanie osoby kontaktowej:
using (var t = session.Logout(editMode: true))
{
    var os = new KontaktOsoba();
    session.AddRow(os);
    os.Kontrahent = k;                         // powiązanie z kontrahentem
    os.Imie = "Anna";
    os.Nazwisko = "Nowak";
    os.Stanowisko = "Kierownik zakupów";
    os.EMAIL = "a.nowak@firma-xyz.pl";
    t.Commit();
}
session.Save();

Pułapki:

  • Powiązanie osoby z kontrahentem ustawiamy przez os.Kontrahent = k (pod spodem powstaje rekord relacji w OsobyKontaktowe); osoba pojawia się wtedy w k.Osoby.
  • Nie usuwaj osób, których dotyczyła historia kontaktu — oznaczaj Nieaktualny=true. Uwaga: ustawienie Nieaktualny ma efekty uboczne (kaskada na powiązania, integracja z kontem webowym) — rób to tylko w pełnej, zalogowanej sesji aplikacyjnej.
  • Filtruj aktualne/nieaktualne serwerowo: k.Osoby[(KontaktOsoba o) => !o.Nieaktualny].

4. Warunki handlowe i finanse

W9 — Warunki płatności i limity kredytowe

Cel: ustawić warunki płatności i parametry kontroli kredytu kupieckiego.

Warianty:

Wariant Pola
Warunki płatności SposobZaplaty: FormaPlatnosci, Termin: int, TerminPlanowany: int, Waluta
Limit kredytu TypLimituKredytowego, LimitKredytu: Currency
Kontrola przeterminowania TypPrzeterminowania, KontrolaKwota: Currency, KontrolaDni: int
Odczyt stanu (kalkulowane) LimitNieograniczony, PrzeterminowanieNieograniczone, KontrolaAktywna
e-faktura EFaktura: Soneta.Core.EFaktura, EFakturaOkres: FromTo
Odsetki / windykacja OdsKarne (złożone), NieWindykowac, DefinicjaSprawyWindykacyjnej
Rachunki bankowe Rachunki: SubTable<RachunekBankowyPodmiotu>, DomyslnyRachunek (kalkulowane)

Pola i typy: SposobZaplaty: Soneta.Kasa.FormaPlatnosci, Termin: int, LimitKredytu: Soneta.Types.Currency, TypLimituKredytowego: Soneta.CRM.TypLimituKredytowego (Kwota=0, Nieograniczony=1), KontrolaKwota: Currency, KontrolaDni: int, TypPrzeterminowania: Soneta.CRM.TypLimituKredytowego.

Snippet:

var crm = session.GetCRM();
var k = crm.Kontrahenci.WgKodu["FIRMA001"];

using (var t = session.Logout(editMode: true))
{
    // Warunki płatności:
    k.SposobZaplaty = session.GetKasa().FormyPlatnosci[FormaPlatnosci.Przelew];
    k.Termin = 14;                             // dni

    // Limit kredytu kupieckiego:
    k.TypLimituKredytowego = TypLimituKredytowego.Kwota;
    k.LimitKredytu = new Currency(50000m, "PLN");   // kwota + symbol waluty

    // Kontrola przeterminowania:
    k.TypPrzeterminowania = TypLimituKredytowego.Kwota;
    k.KontrolaKwota = new Currency(5000m, "PLN");
    k.KontrolaDni = 7;
    t.Commit();
}
session.Save();

// Odczyt pól kalkulowanych (tylko do odczytu):
bool bezLimitu = k.LimitNieograniczony;
RachunekBankowyPodmiotu domyslny = k.DomyslnyRachunek;

Pułapki:

  • Kwoty to Currency (kwota + waluta), nie decimal/double (safe-code §10). Twórz new Currency(kwota, waluta).
  • LimitNieograniczony, PrzeterminowanieNieograniczone, KontrolaAktywna, DomyslnyRachunekkalkulowane — tylko do odczytu.
  • SposobZaplaty to rekord FormaPlatnosci — pobierz go z session.GetKasa().FormyPlatnosci[…] (np. stała FormaPlatnosci.Przelew), nie ustawiaj „z palca".
  • Ustawienie TypLimituKredytowego = Nieograniczony czyni LimitKredytu polem nieaktywnym (w UI read-only) — ustawiaj kwotę tylko dla typu Kwota.

W10 — Konto księgowe / rozrachunkowe

Cel: odczytać/ustawić powiązanie kontrahenta z rozliczeniami (kontrahent jako IPodmiotKasowy).

Warianty:

Wariant Mechanizm
Kontrahent jako podmiot kasowy rzutowanie/użycie przez interfejs Soneta.Kasa.IPodmiotKasowy
Domyślny płatnik Platnik: IPodmiotKasowy (kalkulowane — nadrzędny z relacji lub sam podmiot)
Rachunki podmiotu Rachunki: SubTable<RachunekBankowyPodmiotu>

Pola i typy: Platnik: Soneta.Kasa.IPodmiotKasowy (kalkulowane), Rachunki, DomyslnyRachunek (kalkulowane). Kontrahent implementuje IPodmiotKasowy.

Snippet:

var k = session.GetCRM().Kontrahenci.WgKodu["FIRMA001"];

// Kontrahent jest podmiotem kasowym — można go podać tam, gdzie wymagany jest IPodmiotKasowy:
IPodmiotKasowy podmiot = k;

// Domyślny płatnik (gdy kontrahent jest podrzędny, zwraca nadrzędnego z relacji):
IPodmiotKasowy platnik = k.Platnik;

Pułapki:

  • Platnik jest kalkulowany (zależny od relacji podmiotów, W14) — nie zapisuj go bezpośrednio.
  • Konta księgowe rozrachunkowe należą do modułu księgowego; z poziomu kontrahenta operuj przez interfejs IPodmiotKasowy i kolekcje rozrachunków (W11), nie przez prywatne pola księgowe.

W11 — Rozrachunki i płatności

Cel: odczytać należności/zobowiązania i ostatnie płatności kontrahenta.

Warianty:

Wariant Źródło
Należności i zobowiązania Rozrachunki: SubTable<Soneta.Kasa.RozrachunekIdx>
Płatności / zapłaty Platnosci: SubTable<Platnosc>, Zaplaty: SubTable<Zaplata>
Dokumenty rozliczeniowe DokumentyRozliczeniowe: SubTable<DokRozliczBase>
Przelewy Przelewy: SubTable<PrzelewBase>

Pola i typy: wszystkie powyższe to kolekcje SubTable na Kontrahent. RozrachunekIdx ma m.in. pola kwotowe i datę rozrachunku.

Snippet:

var k = session.GetCRM().Kontrahenci.WgKodu["FIRMA001"];

// Rozrachunki nierozliczone — filtr serwerowy po kolekcji:
foreach (RozrachunekIdx r in k.Rozrachunki)
{
    // r.* — kwota, waluta, data, kierunek (należność/zobowiązanie)
}

// Ostatnie zapłaty (zawężaj zakresem czasu — to dane operacyjne!):
var od = Date.Today.AddMonths(-3);
foreach (Zaplata z in k.Zaplaty)
{
    // ...
}

Pułapki:

  • Rozrachunki to dane wyliczane/operacyjne — przy szerszych analizach zawężaj zakres czasowy i nie ładuj całej historii (safe-code §6.3).
  • Saldo/przeterminowanie na dany dzień to wynik wyliczeń — czytaj przez dedykowane pola/kolekcje, nie sumuj „ręcznie" całej tabeli.
  • RozrachunekIdx / Platnosc / Zaplata żyją w module Soneta.Kasa — wymagana referencja do Soneta.Kasa.

5. Sprzedaż i dokumenty

W12 — Dokumenty i dane sprzedażowe

Cel: odczytać dokumenty handlowe kontrahenta oraz (opcjonalnie) utworzyć dokument.

Warianty:

Wariant Źródło / worker
Dokumenty, w których kontrahent jest nabywcą DokumentyHandlowe: SubTable
Dokumenty, w których jest odbiorcą DokumentyHandloweOdbiorcy: SubTable
Dokumenty ewidencji DokumentyEwidencji: SubTable<DokEwidencji>
Utworzenie dokumentu przez moduł Handel (definicja dokumentu + ustawienie Kontrahent)

Pola i typy: DokumentyHandlowe, DokumentyHandloweOdbiorcy, DokumentyEwidencji — kolekcje SubTable na Kontrahent.

Snippet:

var k = session.GetCRM().Kontrahenci.WgKodu["FIRMA001"];

// Ostatnie dokumenty handlowe kontrahenta jako nabywcy:
foreach (var d in k.DokumentyHandlowe)
{
    // d.* — numer, data, wartości
}

Pułapki:

  • Tworzenie dokumentu handlowego realizuje moduł Handel (definicja DefDokHandlowych, new DokumentHandlowy, ustawienie Kontrahent) — to osobny obszar; z poziomu kontrahenta korzystaj z jego kolekcji do odczytu.
  • DokHandlowe to tabela operacyjna guided — przy iteracji poprzecznej zawężaj zakres czasowy (safe-code §6.3). Kolekcja k.DokumentyHandlowe jest już zawężona do jednego kontrahenta.

6. Klasyfikacja

W13 — Cechy, kategorie, branże, GUS

Cel: uzupełnić cechy definiowalne i klasyfikacje kontrahenta.

Warianty:

Wariant Kolekcja / mechanizm
Cecha definiowalna Features: FeatureCollection (odczyt/zapis po nazwie definicji)
Kategorie Kategorie: SubTable<KategoriaKth> (poj. lub worker KontrahenciPrzypiszKategorieWorker)
Branże Branze: SubTable<BranzaKth>
PKD / dane GUS worker DaneZGusBirWorker (online; pobiera też kody PKD)

Pola i typy: Features: Soneta.Business.FeatureCollection (indeksator Features["NazwaCechy"] zwraca/przyjmuje object), Kategorie: SubTable<KategoriaKth>, Branze: SubTable<BranzaKth>. KategoriaKth tworzymy konstruktorem new KategoriaKth(kontrahent, defKategorii).

Snippet:

var crm = session.GetCRM();
var k = crm.Kontrahenci.WgKodu["FIRMA001"];

using (var t = session.Logout(editMode: true))
{
    // Cecha definiowalna — dostęp po nazwie definicji (cecha musi być wcześniej zdefiniowana):
    k.Features["Segment"] = "Premium";

    // Przypisanie do kategorii (defKat: DefKategKth z konfiguracji CRM, indeks WgNazwy):
    var defKat = crm.DefKategoriiKth.WgNazwy["VIP"];
    if (defKat != null && crm.KategorieKth.WgKontrahent[k, defKat] == null)
        crm.KategorieKth.AddRow(new KategoriaKth(k, defKat));
    t.Commit();
}
session.Save();

// Odczyt cechy:
object segment = k.Features["Segment"];

Pułapki:

  • Cecha jest dostępna po nazwie definicji; odwołanie do niezdefiniowanej cechy rzuca wyjątek — upewnij się, że definicja istnieje (cechy vs pola natywne to dwie różne rzeczy).
  • Przed dodaniem kategorii sprawdź duplikat: crm.KategorieKth.WgKontrahent[k, defKat].
  • Masowe przypisanie kategorii: worker KontrahenciPrzypiszKategorieWorker ([Context] Kontrahent[]
    • Params.Kategoria).
  • Pobranie kodów PKD odbywa się online z GUS-BIR (worker DaneZGusBirWorker) — patrz W15.

7. Powiązania

W14 — Powiązania i opiekunowie

Cel: zarządzać opiekunami i relacjami między kontrahentami.

Warianty:

Wariant Operacja / worker
Opiekun (dodanie / główny) Opiekunowie: SubTable<Opiekun>, worker UstawOpiekunaGlownegoWorker
Sprawdzenie opieki na dzień metody JestOpiekunemNaDzis(...), OpiekunowieWOkresie(...)
Podmiot nadrzędny / podrzędny workery NowyPodmiotNadrzednyWorker, NowyPodmiotPodrzednyWorker
Relacje podmiotów Podrzedni: SubTable<RelacjaPodmiotu>, PodmiotNadrzedny: IPodmiot
Połącz / rozłącz workery PolaczKontrahentowWorker, RozlaczKontrahentaWorker

Pola i typy (Opiekun): Kontrahent: Kontrahent, Operator: Operator, Typ: TypOpiekuna (Glówny=0, Zastępca=1), Rola: RolaOpiekun, OddzialFirmy, DataOd: Date, DataDo: Date, Aktywny: bool.

Snippet:

var crm = session.GetCRM();
var k = crm.Kontrahenci.WgKodu["FIRMA001"];

using (var t = session.Logout(editMode: true))
{
    var op = new Opiekun();
    crm.Opiekunowie.AddRow(op);
    op.Kontrahent = k;
    op.Operator = oper;                        // Operator pobrany z modułu Business
    op.Typ = TypOpiekuna.Glówny;
    op.DataOd = Date.Today;
    op.DataDo = Date.MaxValue;
    op.Aktywny = true;
    t.Commit();
}
session.Save();

// Odczyt relacji podmiotów:
foreach (RelacjaPodmiotu r in k.Podrzedni)
{
    // r.Nadrzedny, r.PowiazaniePodmiotu.Rola, r.PowiazaniePodmiotu.RodzajPowiazania
}
IPodmiot nadrzedny = k.PodmiotNadrzedny;

Pułapki:

  • Opiekun.Operator to rekord operatora (dane konfiguracyjne) — w kodzie biznesowym pobieraj go spójnie z bieżącą sesją; nie mieszaj rekordów z różnych sesji (safe-code §2.1, użyj session.Get(...)).
  • Do sprawdzania opieki „na dziś"/„w okresie" używaj metod publicznych JestOpiekunemNaDzis, OpiekunowieWOkresie zamiast ręcznego filtrowania dat.
  • Relacje podmiotów (nadrzędny/podrzędny, płatnik/odbiorca) zakładaj workerami NowyPodmiotNadrzednyWorker/NowyPodmiotPodrzednyWorker — mają walidatory spójności.

8. Weryfikacja statusu

W15 — Weryfikacja VAT (GUS / MF / VIES)

Cel: zweryfikować dane i status podatnika w rejestrach zewnętrznych. Wszystkie operacje są online (wymagają połączenia i bywają limitowane).

Warianty:

Wariant Worker (jednostkowo / masowo) Wynik na kontrahencie
Dane z GUS-BIR (też PKD) DaneZGusBirWorker / DaneZGusBirMultipleWorker nazwa, adres, REGON, KRS, PKD
Status MF / biała lista DaneZMfWorker, KontrahentBialaListaWorker / KontrahenciBialaListaWorker AktualnyStatusVATMF
Status VIES DataFromViesWorker / KontrahenciDaneZViesWorker AktualnyStatusVATVies
Historia statusu VAT kolekcja StatusyVAT: SubTable<StatusVAT>

Pola i typy (odczyt wyniku): AktualnyStatusVAT, AktualnyStatusVATMF, AktualnyStatusVATVies (typ Soneta.CRM.StatusNumeruVAT, kalkulowane), AktStatusVATData/DataMF/DataVIES: DateTime?, StatusyVAT: SubTable<StatusVAT>.

Snippet:

var k = session.GetCRM().Kontrahenci.WgKodu["FIRMA001"];

// Odczyt ostatnio zapisanych statusów (offline — bez sieci):
StatusNumeruVAT statusMF   = k.AktualnyStatusVATMF;
StatusNumeruVAT statusVies = k.AktualnyStatusVATVies;
DateTime? dataMF           = k.AktStatusVATDataMF;

// Historia statusów:
foreach (StatusVAT s in k.StatusyVAT) { /* ... */ }

// Weryfikacja online — przez worker (przykład: status MF):
// var w = new DaneZMfWorker { Kontrahent = k, Context = context };
// w.DaneZMf();   // WYMAGA SIECI — obuduj obsługą braku połączenia/limitów

Pułapki:

  • Operacje GUS/MF/VIES wymagają sieci — obuduj je obsługą błędów połączenia i limitów; nie testuj ich w testach jednostkowych (zależność od usług zewnętrznych).
  • Status VAT z rejestru to dane „na dzień" — zapisuj datę weryfikacji (AktStatusVATData*).
  • W kodzie offline czytaj wyłącznie pola kalkulowane (AktualnyStatusVAT*) i historię StatusyVAT.
  • Nie loguj nadmiarowo numerów NIP/PESEL (safe-code §12).

9. RODO/GIODO i KSeF

W16 — RODO / GIODO

Cel: obsłużyć zgody i wymianę danych osobowych kontrahenta.

Warianty:

Wariant Mechanizm / worker Kolekcja
Oświadczenia KontrahentDodajOswiadczeniaWorker GIODOOświadczenia: SubTable<GIODOOświadczenie>
Pozyskanie danych KontrahentDodajPozyskanieDanychWorker GIODOUdostępnienia
Udostępnienie danych KontrahentDodajUdostepnienieDanychWorker GIODOUdostępnienia
Powierzenie danych KontrahentDodajPowierzenieDanychWorker GIODOUdostępnienia
Potwierdzenia zgodności PotwierdzeniaGIODO: SubTable<GIODOZgodny>

Pola i typy: GIODOOświadczenia: SubTable<GIODOOświadczenie>, GIODOUdostępnienia: SubTable<GIODOWymianaDanych>, PotwierdzeniaGIODO: SubTable<GIODOZgodny>, ZgodnoscGIODOPotwierdzona: bool (kalkulowane).

Snippet:

var k = session.GetCRM().Kontrahenci.WgKodu["FIRMA001"];

// Odczyt oświadczeń RODO kontrahenta:
foreach (GIODOOświadczenie o in k.GIODOOświadczenia)
{
    // o.* — definicja oświadczenia, okres obowiązywania, status zgody
}

// Dodawanie oświadczeń realizują workery RODO (dziedziczą po bazowych z Soneta.Core):
// new KontrahentDodajOswiadczeniaWorker(...).DodajOświadczenia();

Pułapki:

  • Obowiązywanie zgody jest „na dzień" — czytaj okresy z rekordów GIODOOświadczenie, nie zakładaj bezterminowości.
  • Dane osobowe (PESEL, e-mail osób) są wrażliwe — nie loguj ich (safe-code §12).
  • Workery RODO mają tryb ConfirmSave i wymagają praw do obszaru GIODO.

W17 — KSeF

Cel: ustawić parametry KSeF kontrahenta.

Warianty:

Wariant Pole
Szablon pól opcjonalnych DomyslnySzablonPolOpcjonalnychKSeF: Soneta.Core.KSeFSzablonPolOpcjonalnych
Sposób wysyłki ceny KSeFSposobObslugiWysylkiCeny: Soneta.Core.SposobObslugiWysylkiCenyDoKSeF
Powiązanie z e-fakturą EFaktura, EFakturaOkres (patrz W7)

Pola i typy: jak w tabeli powyżej (oba pola bazodanowe, zapisywalne).

Snippet:

var k = session.GetCRM().Kontrahenci.WgKodu["FIRMA001"];

using (var t = session.Logout(editMode: true))
{
    k.KSeFSposobObslugiWysylkiCeny = SposobObslugiWysylkiCenyDoKSeF.CenaPoRabacie;
    // k.DomyslnySzablonPolOpcjonalnychKSeF = ... // rekord szablonu z konfiguracji
    t.Commit();
}
session.Save();

Pułapki:

  • DomyslnySzablonPolOpcjonalnychKSeF to referencja do rekordu konfiguracyjnego — pobierz istniejący szablon, nie twórz „w locie".
  • Konfiguracja KSeF współgra z EFaktura (W7) — ustawiaj je spójnie.

10. Operacje masowe

W18 — Operacje na zbiorze kontrahentów

Cel: wykonać operację na wielu kontrahentach efektywnie i bezpiecznie.

Warianty:

Wariant Mechanizm
Iteracja z warunkiem serwerowy LINQ crm.Kontrahenci[(Kontrahent k) => …] (patrz rowcondition.md)
Masowa aktualizacja jedna transakcja, paczki (patrz safe-code.md)
Masowa zmiana formy prawnej worker ZmienFormePrawnaKontrahentowWorker
Masowe przypisanie kategorii worker KontrahenciPrzypiszKategorieWorker
Masowa weryfikacja VAT/VIES (online) KontrahenciBialaListaWorker, KontrahenciDaneZMfWorker, KontrahenciDaneZViesWorker
Eksport / import datapack / business.xml (patrz datapack-guidedrow.md)

Snippet:

var crm = session.GetCRM();

// Masowa zmiana: ustaw blokadę sprzedaży dla kontrahentów bez NIP — filtr serwerowy + 1 transakcja
using (var t = session.Logout(editMode: true))
{
    foreach (Kontrahent k in crm.Kontrahenci.WgKodu[(Kontrahent k) =>
                 k.NIP == null && k.StatusPodmiotu == StatusPodmiotu.PodmiotGospodarczy])
    {
        k.BlokadaSprzedazy = true;
    }
    t.Commit();
}
session.Save();

Pułapki:

  • Nie ładuj całej tabeli do pamięci — filtr serwerowy (SubTable[condition]).
  • Duże operacje dziel na paczki (krótkie transakcje), by nie blokować innych użytkowników i nie zwiększać ryzyka konfliktu optymistycznego (safe-code §13.1).
  • Workery masowe (*Worker na typie Kontrahenci) mają property [Context] Kontrahent[] — przy użyciu programowym ustaw tablicę zaznaczonych rekordów.

Powiązane dokumenty