110 lines
5.2 KiB
Markdown
110 lines
5.2 KiB
Markdown
# 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.
|
|
|
|
---
|
|
|