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 wsafe-code.md,session-login.mdorazworker-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żeniaswitch, nazwane parametrybool). 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.Kontrahent—GuidedRow(root), tabelaSoneta.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ępsession.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):
Nazwajest zapisywalna, aleNazwaFormatowana,NazwaPierwszaLinia,KodKraju,JestIncydentalny,IsStandard,DomyslnyRachunek,Platnik,LimitNieograniczony,PrzeterminowanieNieograniczone,KontrolaAktywna,AktualnyStatusVAT,AktualnyStatusVATMF,AktualnyStatusVATVies— nie 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
Kontrahencizifw 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 poID/kluczu, nie przyjmuje wyrażenia LINQ. Patrzrowcondition.mdisafe-code.md§6. - W
RowCondition(wyrażeniu LINQ) wolno użyć tylko pól bazodanowych.NazwaFormatowana,KodKraju,Platniksą 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.TestrzucająNullReferenceExceptiondlanull(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))przedCommit()(safe-code §5.1). Wyjątek poCommit()nie wycofa zmiany z sesji. - Ustawienie
NIP/EuVATna samymKontrahenturuchamia wbudowaną synchronizację (NIP↔EuVAT, auto-zmianaRodzajPodmiotu) — 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)).AddRowprzed ustawianiem pól. - W workerze/extenderze (uruchamianym z UI) używaj
t.CommitUI()zamiastt.Commit()(safe-code,worker-extender.md). Nazwajest zapisywalna;NazwaFormatowana/NazwaPierwszaLiniasą kalkulowane — nie ustawiaj.- Dla podmiotu unijnego ustaw
EuVAT(z prefiksem kraju) — platforma sama dostosujeRodzajPodmiotu. - Brak
Commit()= automatyczny rollback przyDispose().
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()jakoRowConflictException— obsłuż go (refresh + retry lub eskalacja), nie połykaj (safe-code §4). - Nie nadpisuj
Kodrekordów standardowych (IsStandard == true) ani incydentalnego (JestIncydentalny == true). Zamiennikma efekt uboczny — ustawienie zamiennika włączaBlokada=true. Do rozwiązania „aktualnego" kontrahenta służyKontrahent.Coalesce(k)(zwraca zamiennika albo sam rekord).- Worker
KopiujKontrahentaWorkerma property[Context] Kontrahent— przy ręcznym użyciu ustaw ją przed wywołaniemKopiuj(); 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 wSave()), ale lepiej zdecydować świadomie. - Preferuj
Blokada=true(kontrahent znika z list, dane pozostają) zamiast kasowania, gdy są powiązania historyczne. IsEmpty/Anyna kolekcjiSubTableto właściwości (test serwerowyexists …, 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:
Adresto property kalkulowana zwracająca obiekt złożony — nie da się przypisaćk.Adres = …; modyfikuj jego pola.KodPocztowyjest typuint(np.30415). Do wartości z myślnikiem używajKodPocztowyS(string), które samo rozkłada/składa kod.Wojewodztwoto enumSoneta.Core.Wojewodztwa, nie string.KodKrajuadresu bywa kalkulowane zKraj— ustawiajKraj/KodKrajuspó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.Rodzajto rekord słownikaRodzajKontaktu— pobierz go po stałej Guid przezsession.GetCore().RodzajeKontaktow[RodzajeKontaktow.AdresEmail](analogicznieTelefonKomórkowy,TelefonStacjonarny,Faks,Skype).- Tylko jeden kontakt domyślny w obrębie rodzaju — ustawienie
Domyslny=truena 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 kolekcjik.Kontakty.AdresWWWtworzymy konstruktorem z hostem (new AdresWWW(k)); pole adresu URL nazywa sięAdres(nieUrl).
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 wOsobyKontaktowe); osoba pojawia się wtedy wk.Osoby. - Nie usuwaj osób, których dotyczyła historia kontaktu — oznaczaj
Nieaktualny=true. Uwaga: ustawienieNieaktualnyma 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), niedecimal/double(safe-code §10). Twórznew Currency(kwota, waluta). LimitNieograniczony,PrzeterminowanieNieograniczone,KontrolaAktywna,DomyslnyRachuneksą kalkulowane — tylko do odczytu.SposobZaplatyto rekordFormaPlatnosci— pobierz go zsession.GetKasa().FormyPlatnosci[…](np. stałaFormaPlatnosci.Przelew), nie ustawiaj „z palca".- Ustawienie
TypLimituKredytowego = NieograniczonyczyniLimitKredytupolem nieaktywnym (w UI read-only) — ustawiaj kwotę tylko dla typuKwota.
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:
Platnikjest 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
IPodmiotKasowyi 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 moduleSoneta.Kasa— wymagana referencja doSoneta.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(definicjaDefDokHandlowych,new DokumentHandlowy, ustawienieKontrahent) — to osobny obszar; z poziomu kontrahenta korzystaj z jego kolekcji do odczytu. DokHandloweto tabela operacyjna guided — przy iteracji poprzecznej zawężaj zakres czasowy (safe-code §6.3). Kolekcjak.DokumentyHandlowejest 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.Operatorto 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żyjsession.Get(...)).- Do sprawdzania opieki „na dziś"/„w okresie" używaj metod publicznych
JestOpiekunemNaDzis,OpiekunowieWOkresiezamiast 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
ConfirmSavei 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:
DomyslnySzablonPolOpcjonalnychKSeFto 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 (
*Workerna typieKontrahenci) mają property[Context] Kontrahent[]— przy użyciu programowym ustaw tablicę zaznaczonych rekordów.
Powiązane dokumenty
safe-code.md— sesja, transakcje, blokada optymistyczna, zasady bezpiecznego kodu.session-login.md—Session,Login,Database.worker-extender.md— workery, akcje menu Czynności, bindowanie.rowcondition.md— serwerowy LINQ,RowCondition,SubTable[condition].datapack-guidedrow.md— eksport/import,GuidedRow.scan-props.md/scan-workers.md— inwentaryzacja pól i workerów.