SKILL: Uporządkowanie skills domenowych - podział na mniejsze pliki i wspólna numeracja

This commit is contained in:
Marcin Wojas
2026-06-07 08:31:37 +02:00
parent fb2f2695a3
commit 5605ad2915
42 changed files with 13500 additions and 13526 deletions
@@ -0,0 +1,109 @@
# CRM01 — Wyszukiwanie i identyfikacja
> Wspólne fakty o typie, podstawowe typy i szablon wzorca: [../crm.md](../crm.md).
### CRM-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:**
```csharp
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`](../rowcondition.md) i [`safe-code.md`](../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.
### CRM-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 (CRM-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:**
```csharp
// 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 CRM-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.
---