# HANDEL13 — Tematy specjalistyczne (KSeF, fiskalizacja, kompletacja, Intrastat) > Wspólne fakty o typie, podstawowe typy i szablon wzorca: [../handel.md](../handel.md). > Rozdział obejmuje obszary, które łączą dokument handlowy z systemami zewnętrznymi (KSeF), urządzeniami > (drukarka fiskalna) oraz specjalistyczną logiką magazynową (kompletacja) i sprawozdawczą (Intrastat). > > **Ważne — co jest, a co nie jest testowalne jednostkowo.** Część operacji wymaga **sieci** (komunikacja > z bramką KSeF, wysyłka e-mail e-paragonu) albo **sprzętu** (drukarka fiskalna). Tych fragmentów **nie** > da się odtworzyć w teście jednostkowym — testuj wyłącznie **ustawienie pól i parametrów** oraz **strukturę** > (np. `XmlValidated`, parametry workera, pola `KSeFKomunikat`). Każdy wzorzec poniżej oznacza, która część > jest „offline" (testowalna), a która „online/sprzętowa" (NIE testuj — patrz `dh-facts.md`, „Reguły testów"). > > Wszystkie workery wymienione w tym rozdziale są **publiczne** i mogą być wywołane z dodatku zewnętrznego. > Operacje modyfikujące dokument wykonuj w transakcji (`session.Logout(true)` + `Commit`/`CommitUI`), potem > `session.Save()`. Kod zgodny z C# 10. --- ### HANDEL-W67 — Wysłanie faktury do KSeF (pojedynczo i zbiorczo) **Cel:** wysłać zatwierdzony dokument sprzedaży do Krajowego Systemu e-Faktur — pojedynczo (`KSeFWyslijWorker`) albo wsadowo dla wielu dokumentów naraz (`KSeFWysylkaWsadowaWorker`). Sama wysyłka to operacja **online** (NIE testuj); offline (testowalne) jest przygotowanie dokumentu: wygenerowanie XML, walidacja struktury i ustawienie parametrów autoryzacji. **Warianty:** | Wariant | Worker / akcja | Uwaga | |---|---|---| | Wysyłka pojedyncza | `KSeFWyslijWorker.Wyslij(dok)` (akcja „KSeF/Wyślij") | dla jednego dokumentu | | Wysyłka zbiorcza | `KSeFWysylkaWsadowaWorker.WyslijZbiorczo()` (akcja „KSeF/Wyślij zbiorczo") | `Dokumenty[]`, generuje XML brakującym, pomija zaimportowane/odrzucone | | Faktura offline (awaria/tryb 24h) | wysyłka z `KSeFKomunikat.Offline == true` | używa tokenu i kontekstu zapisanych na komunikacie | | Data wystawienia ≠ dziś | weryfikator `KSeFWyslijWorker.Weryfikator(dok)` | rzuca wyjątek wg konfiguracji i uprawnień (data przyszła/przeszła) | **Pola i typy:** - Parametry: `KSeFWyslijParams : ContextBase` — `SystemZewn: SystemZewnPlatformaEDI` (`[Required]`), `Token: SysZewToken` (`[Required]`, „Sposób autoryzacji"), `KontekstAutentykacjiKSeF` („Kontekst autoryzacji"). Listy: `GetListSystemZewn()`, `GetListToken()`, `GetListKontekstAutentykacjiKSeF()`. - `KSeFWyslijWorker`: `[Context] Dokument: DokumentHandlowy`, `[Context] Parametry: KSeFWyslijParams`, `[Context] Context: Context`. Akcja `object Wyslij(DokumentHandlowy dok)`. - `KSeFWysylkaWsadowaWorker`: `[Context] Parametry: KsefEksportIWyslijParams`, `[Context(Required=false)] Dokumenty: DokumentHandlowy[]`, `[Context(Required=false)] Dokument: DokumentHandlowy`, `[Context] Context: Context`. - Warunek wstępny (sprawdzany przez `WeryfikatorPolaXmlValidated`): każdy dokument musi mieć `dok.ImportExportKSeF.XmlValidated == ThreeStateBoolean.True` (czyli wcześniej wykonaną walidację struktury — HANDEL-W69). **Snippet:** ```csharp using Microsoft.Extensions.DependencyInjection; using Soneta.KSeF.Workers; var hm = session.GetHandel(); var dok = hm.DokHandlowe.WgNumer[...]; // zatwierdzona faktura sprzedaży // 1) Walidacja daty wystawienia (offline, testowalne) — rzuca wyjątek dla daty != dziś // wg konfiguracji KSeF i uprawnień operatora: KSeFWyslijWorker.Weryfikator(dok); // 2) Przygotowanie parametrów autoryzacji (offline). System i token wybierane z list: var ctx = session.GetEmptyContext(); ctx.TryAdd(() => dok); var parametry = new KSeFWyslijParams(ctx); // konstruktor sam wybiera domyślny system/token // parametry.SystemZewn / parametry.Token można ustawić jawnie z GetListSystemZewn()/GetListToken() // 3) Wysyłka pojedyncza — OPERACJA ONLINE (NIE testuj jednostkowo): var worker = new KSeFWyslijWorker { Dokument = dok, Parametry = parametry, Context = ctx }; object wynik = worker.Wyslij(dok); // wewnątrz: SesjaWysylki + WyslijDokument, zapis KSeFKomunikat // Wysyłka zbiorcza — ONLINE; Dokument musi być pierwszym elementem tablicy Dokumenty: DokumentHandlowy[] doks = hm.DokHandlowe.WgNumer[...].ToArray(); var workerZb = new KSeFWysylkaWsadowaWorker { Dokument = doks[0], Dokumenty = doks, Context = ctx, Parametry = paramsZb }; workerZb.WyslijZbiorczo(); ``` **Pułapki:** - **Tylko dokumenty zatwierdzone** podlegają wysyłce (`IsVisible*` wymagają `dok.Zatwierdzony`). Bufor i dokument anulowany nie są wysyłane. - Przed wysyłką dokument **musi mieć zwalidowany XML** (`XmlValidated == True`) — inaczej `WeryfikatorPolaXmlValidated` rzuci wyjątek „nie posiada zweryfikowanego pliku XML". Najpierw wykonaj HANDEL-W69 (Sprawdź strukturę pliku) lub wygeneruj XML (wysyłka zbiorcza robi to automatycznie dla statusu `Brak`). - Wysyłka zbiorcza **pomija** dokumenty: zaimportowane z KSeF (`ImportExportKSeF.Rodzaj == Import`), o nieprawidłowym/niezweryfikowanym XML, wygenerowane inną definicją niż w parametrach, w trybie offline z innym tokenem — wszystkie pominięcia trafiają do logu „KSeF". - Cała komunikacja z bramką (`IKSeFAPIv2Service`/`IKSeFAPIService`) **wymaga sieci** → **NIE testuj jednostkowo**. W teście weryfikuj jedynie: utworzenie `KSeFWyslijParams`, dobór systemu/tokenu z list, `Weryfikator` oraz że XML jest zwalidowany. - Po wysyłce na dokumencie zatwierdzonym ustawiana jest flaga `Session.SaveImmediatelyIfPossible = true` (natychmiastowy zapis komunikatu KSeF). --- ### HANDEL-W68 — Sprawdzenie statusu KSeF i odczyt numeru KSeF **Cel:** po wysyłce sprawdzić w bramce, czy dokument został przyjęty, i pobrać nadany **numer KSeF** (`KSeFSprawdzStatusWorker`). Sprawdzenie statusu to operacja **online** (NIE testuj); odczyt już zapisanego statusu/numeru jest **offline** (testowalne — pola kalkulowane na dokumencie). **Warianty:** | Wariant | Mechanizm | |---|---| | Sprawdzenie statusu po sesji wysyłki | `KSeFSprawdzStatusWorker.SprawdzStatus(dok)` (akcja „KSeF/Sprawdź status") — ONLINE | | Odczyt aktualnego statusu | `dok.StatusKSeF: KSeFState` — offline, kalkulowane | | Odczyt numeru KSeF / nr referencyjnych | `dok.KSeFKomunikat.NumerDokumentuKSeF` itd. — offline | | Czy dokument w ogóle podlega KSeF | `dok.PodlegaKSeF`, `dok.PosiadaKSeF` — offline | **Pola i typy:** - `dok.StatusKSeF: Soneta.Core.KSeF.KSeFState` (kalkulowane). Wartości: `NieDotyczy=1, Brak=2, DoWyslania=4, Wyslany=8, Przyjety=16, Odrzucony=32` (oraz `Robocze=14`, `Razem=31` zachowane dla kompatybilności). Status wyliczany z zawartości `KSeFKomunikat` i stanu dokumentu (Bufor/Anulowany ⇒ `NieDotyczy`). - `dok.KSeFKomunikat` (rekord `KSeFKomunikat`): `NumerDokumentuKSeF: string` (numer nadany przez KSeF — ustawiony ⇒ status `Przyjety`), `NumerReferencyjnyKSeF: string`, `NumerReferencyjnySesjiKSeF: string`, `OpisBledu: string` (niepusty ⇒ status `Odrzucony`), `Offline: bool`, `TokenKSeF: SysZewToken`, `DataPrzeslaniaKSeF`, `DataPrzyjeciaKSeF`. - `dok.PosiadaKSeF: bool` (ma plik `ImportExportKSeF`), `dok.PodlegaKSeF: bool`, `dok.QRCodeLink: string`. **Snippet:** ```csharp // Sprawdzenie statusu w bramce — OPERACJA ONLINE (NIE testuj jednostkowo): var worker = new KSeFSprawdzStatusWorker(); MessageBoxInformation wynik = worker.SprawdzStatus(dok); // pobiera status z sesji wysyłki // Odczyt zapisanego statusu i numeru — OFFLINE, w pełni testowalne: KSeFState status = dok.StatusKSeF; if (status == KSeFState.Przyjety) { string numerKSeF = dok.KSeFKomunikat.NumerDokumentuKSeF; // numer nadany przez KSeF string nrSesji = dok.KSeFKomunikat.NumerReferencyjnySesjiKSeF; } else if (status == KSeFState.Odrzucony) { string blad = dok.KSeFKomunikat.OpisBledu; // przyczyna odrzucenia } ``` **Pułapki:** - `StatusKSeF` jest **kalkulowane** — nie da się go ustawić; zmienia się przez sam `KSeFKomunikat`. - Sprawdzenie statusu działa tylko, gdy `dok.StatusKSeF != Przyjety` i istnieje `KSeFKomunikat` z numerem referencyjnym sesji; dokument w stanie `DoWyslania` nie ma jeszcze czego sprawdzać. - Worker odczytuje status **wszystkich** dokumentów z tej samej sesji wysyłki (`NumerReferencyjnySesjiKSeF`) i każdemu z nich uzupełnia numer KSeF — to operacja zbiorcza po stronie bramki. - Wywołanie `IKSeFAPIv2Service.SprawdzStatusDokumentowZSesji` **wymaga sieci** → **NIE testuj jednostkowo**. W teście weryfikuj jedynie wyliczanie `StatusKSeF` z różnych ustawień `KSeFKomunikat`. --- ### HANDEL-W69 — UPO, numer KSeF z duplikatu, walidacja struktury XML **Cel:** trzy operacje pomocnicze KSeF: pobranie **UPO** (urzędowego poświadczenia odbioru) dla przyjętej faktury, **odzyskanie numeru KSeF z duplikatu** (gdy bramka odrzuciła dokument kodem 440 = duplikat) oraz **walidacja struktury XML** względem schematu (XSD). Walidacja XML jest **offline** (testowalna); pobranie UPO jest **online** (NIE testuj). Pobranie numeru z duplikatu jest **offline** (parsuje istniejący `OpisBledu`). **Warianty:** | Wariant | Worker / akcja | Online? | |---|---|---| | Walidacja struktury XML | `KSeFSprawdzXMLWorker.Check()` (akcja „KSeF/Sprawdź strukturę pliku") | OFFLINE (lokalny XSD) | | Pobranie UPO dla dokumentu | `KSeFSprawdzUPODokumentuWorker.SprawdzUPO()` (akcja „KSeF/Sprawdź UPO...") | ONLINE | | Numer KSeF z duplikatu (błąd 440) | `PobierzNumerKSeFZDuplikatuWorker.PobierzNumerDokumentuKSeF(dok)` | OFFLINE (parsuje `OpisBledu`) | **Pola i typy:** - `KSeFSprawdzXMLWorker`: `[Context] Dokument: DokumentHandlowy`, metoda `void Check()`. Ustawia `dok.ImportExportKSeF.XmlValidated: ThreeStateBoolean` (`True`/`False`). Walidator publiczny: `KSeFSchemaVerifier.Verify(DokumentHandlowy dok)` (rzuca wyjątek przy niezgodności ze schematem). - `KSeFSprawdzUPODokumentuWorker`: `[Context] Dokument`, `void SprawdzUPO()`. Wymaga `dok.StatusKSeF == Przyjety` i tokenu w wersji API v2 (`KSeFKomunikat.TokenKSeF.KSeFAPIv2`), inaczej rzuca `RowException`. Zapisuje rekord `KSeFUPO` i daty `DataPrzeslaniaKSeF`/`DataPrzyjeciaKSeF`. - `PobierzNumerKSeFZDuplikatuWorker`: akcja `void PobierzNumerDokumentuKSeF(DokumentHandlowy dok)`. Aktywna, gdy `dok.KSeFKomunikat.OpisBledu` zawiera „440"; z opisu wyłuskuje numer dokumentu i sesji, ustawia `NumerDokumentuKSeF` / `NumerReferencyjnySesjiKSeF` (status przechodzi na `Przyjety`). **Snippet:** ```csharp // 1) Walidacja struktury XML — OFFLINE (lokalny XSD), w pełni testowalne: var xmlWorker = new KSeFSprawdzXMLWorker { Dokument = dok }; xmlWorker.Check(); bool poprawny = dok.ImportExportKSeF.XmlValidated == ThreeStateBoolean.True; // Alternatywnie sam weryfikator (rzuca wyjątek przy błędzie struktury): KSeFSchemaVerifier.Verify(dok); // 2) Numer KSeF z duplikatu — OFFLINE (parsuje OpisBledu z błędu 440): var dupWorker = new PobierzNumerKSeFZDuplikatuWorker(); dupWorker.PobierzNumerDokumentuKSeF(dok); // ustawia NumerDokumentuKSeF, jeśli OpisBledu zawiera "440" // 3) Pobranie UPO — OPERACJA ONLINE (NIE testuj jednostkowo): var upoWorker = new KSeFSprawdzUPODokumentuWorker { Dokument = dok }; upoWorker.SprawdzUPO(); // wymaga StatusKSeF == Przyjety oraz API v2 ``` **Pułapki:** - `Check()` opiera się o **lokalny XSD** (`ImportExportKSeF.DefinicjaXmlNag.LocalXSD`) — nie potrzebuje sieci, dlatego jest **testowalny**. Wymaga jednak wcześniej wygenerowanego XML (`ImportExportKSeF.Xml` niepusty — `IsEnabledCheck`). - `SprawdzUPO()` rzuca `RowException`, gdy dokument nie jest `Przyjety` albo nie był wysłany w API v2 — obsłuż to przed wywołaniem. Samo pobranie UPO **wymaga sieci** → NIE testuj. - `PobierzNumerDokumentuKSeF` ustawia w `OpisBledu` znacznik `DokumentHandlowy.PobranoNumerKSeFZDuplikatuOpis` (link QR z duplikatu może nie działać) — to celowy efekt uboczny, nie błąd. - Walidacja statusu „440 = duplikat" działa wyłącznie na tekście `OpisBledu` — jeśli opis nie zawiera „440", worker nic nie robi. --- ### HANDEL-W70 — Import faktur z KSeF (dokumenty zakupu) **Cel:** pobrać z KSeF faktury zakupowe (oraz sprzedażowe), zapisać je jako pliki KSeF (`KSeFPlik`) w bazie, a następnie utworzyć z nich dokumenty zakupu. **Cały proces pobierania jest online** (komunikacja z bramką) i operuje na rekordach **konfiguracyjno-systemowych** (`KSeFZapytanieOFa`, `KSeFPlik`), a tworzenie dokumentów zakupu z plików KSeF realizowane jest w module księgowym — **NIE testuj jednostkowo** części sieciowej. **Warianty:** | Wariant | Mechanizm | |---|---| | Zapytanie o faktury za okres | rekord `KSeFZapytanieOFa` + parametry `ParametryPobieraniaFakturKSeF` (`DataOd`, `DataDo`, `PodmiotTworzeniaZapytaniaKSeF`) | | Pobranie paczek wyników | `KSeFDownloadPartWorker.Pobierz()` (akcja „Pobierz pakiety") — ONLINE; tworzy `KSeFPlik` | | Kwalifikacja kierunku (zakup/sprzedaż) | wg porównania NIP z pieczątki firmy z NIP-em Podmiot1 w XML | | Utworzenie dokumentu zakupu | z `KSeFPlik` (import XML do dokumentu) — obszar księgowy | **Pola i typy:** - `KSeFDownloadPartWorker`: `[Context] KSeFZapytanieOFa: KSeFZapytanieOFa`, akcja `object Pobierz()`. Pobiera tylko, gdy `KSeFZapytanieOFa.StatusZapytania == StatusZapytania.Przetworzono` i nie pobrano jeszcze wszystkich paczek (`PobraneWszystkie`). - `ParametryPobieraniaFakturKSeF`: `DataOd: DateTimeOffset`, `DataDo: DateTimeOffset`, `PodmiotTworzeniaZapytaniaKSeF`, `PobieranieSamofakturowania`. - Wynik: rekordy `KSeFPlik` (z `RodzajDokumentuKSeFZapytanieOFa`: `Sprzedaz`/`Zakup`/`Razem`) tworzone przez `KSeFPlik.CreateKSefPlik(...)`. Formularz `FA_RR` jest pomijany. **Snippet:** ```csharp // Pobranie paczek z wynikami zapytania — OPERACJA ONLINE (NIE testuj jednostkowo): var worker = new KSeFDownloadPartWorker { KSeFZapytanieOFa = zapytanie }; object wynik = worker.Pobierz(); // tworzy rekordy KSeFPlik dla faktur z bramki // Po pobraniu pliki KSeF są dostępne w module Core (KSeFPliki) i mogą zostać // zaimportowane jako dokumenty zakupu (obszar księgowy). Kierunek (Zakup/Sprzedaz) // kwalifikowany jest automatycznie przez porównanie NIP-u z pieczątki firmy z NIP-em // nadawcy (Podmiot1) w pliku XML. ``` **Pułapki:** - Pobranie paczek **wymaga sieci** (`IKSeFAPIv2Service.PobierzFakturyZPaczek`) → **NIE testuj jednostkowo**. - Import opiera się o rekordy `KSeFZapytanieOFa`/`KSeFPlik`, a nie bezpośrednio o `DokumentHandlowy` — dokument zakupu powstaje dopiero w kolejnym kroku (import XML), poza zakresem prostego workera na dokumencie. - Pliki o tym samym numerze KSeF są **pomijane** (deduplikacja po numerze), tak samo formularz `FA_RR`. - Z poziomu dodatku zewnętrznego operuj na publicznych `ParametryPobieraniaFakturKSeF` i statusie zapytania; testuj wyłącznie logikę przygotowania parametrów (okres, podmiot), nie samo pobieranie. --- ### HANDEL-W71 — Fiskalizacja dokumentu (paragon fiskalny) **Cel:** oznaczyć / wydrukować dokument sprzedaży jako paragon na drukarce fiskalnej. **Wydruk na drukarce to operacja sprzętowa** (NIE testuj). Worker `FiskalizacjaDokumentuWorker` ma jednak rolę „oznacz jako zafiskalizowane" — ustawienie `SymbolKasy` na zatwierdzonym dokumencie jest **offline** (testowalne). **Warianty:** | Wariant | Mechanizm | Sprzęt? | |---|---|---| | Oznacz jako zafiskalizowane | `FiskalizacjaDokumentuWorker.Execute()` (akcja „Narzędziowe/Oznacz jako zafiskalizowane") | NIE (tylko ustawia `SymbolKasy`) | | Symbol drukarki tekstowo | `ParametryFiskalizacjiDokumentu.SymbolKasy: string` (max 12) | — | | Symbol drukarki z listy (z bazy) | `ParametryFiskalizacjiDokumentu.SymbolKasyEnum` + `GetListSymbolKasyEnum()` | — | | Faktyczny wydruk fiskalny | `Fiscalizer` (klasa fiskalizatora) | TAK | **Pola i typy:** - `FiskalizacjaDokumentuWorker`: `[Context] Dokument: DokumentHandlowy`, `[Context] Parametry: ParametryFiskalizacjiDokumentu`, metoda `void Execute()`. - `ParametryFiskalizacjiDokumentu : ContextBase` — `SymbolKasy: string` (`[MaxLength(12)]`, „Symbol drukarki"), `SymbolKasyEnum: string` (combo, gdy dane drukarki w bazie), `GetListSymbolKasyEnum(): List`. - Pola dokumentu: `dok.SymbolKasy: string` (ustawiane przez `UstawSymbolKasy`), `dok.Kategoria: KategoriaHandlowa`. - `IsVisibleExecute`: tylko `Sprzedaż`/`KorektaSprzedaży`. `IsEnabledExecute`: dokument **zatwierdzony** i z **pustym** `SymbolKasy`. **Snippet:** ```csharp // Oznaczenie dokumentu jako zafiskalizowanego (OFFLINE — ustawia tylko SymbolKasy): var ctx = session.GetEmptyContext(); ctx.TryAdd(() => dok); var parametry = new FiskalizacjaDokumentuWorker.ParametryFiskalizacjiDokumentu(ctx) { SymbolKasy = "DRUK1" // symbol drukarki, max 12 znaków }; var worker = new FiskalizacjaDokumentuWorker { Dokument = dok, Parametry = parametry }; worker.Execute(); // wykona się tylko gdy dok zatwierdzony i SymbolKasy pusty // Po operacji: string symbol = dok.SymbolKasy; // "DRUK1" // Faktyczny wydruk na drukarce fiskalnej — OPERACJA SPRZĘTOWA (NIE testuj): // var fiscalizer = new Fiscalizer(dok); // fiscalizer.Fiscalize(false); ``` **Pułapki:** - `Execute()` z `FiskalizacjaDokumentuWorker` **nie drukuje** — jedynie ustawia `SymbolKasy` i dopisuje informację o fiskalizacji do zmian zapisu. Faktyczny wydruk realizuje klasa `Fiscalizer` (sprzęt) → NIE testuj. - Operacja działa wyłącznie dla dokumentów **zatwierdzonych** o pustym `SymbolKasy` (`IsEnabledExecute`) i kategorii `Sprzedaż`/`KorektaSprzedaży` (`IsVisibleExecute`). - `SymbolKasy` jest przycinany (`Trim`) i ograniczony do 12 znaków; wybór z listy (`SymbolKasyEnum`) dostępny tylko, gdy konfiguracja trzyma dane drukarek w bazie (`Config.DrukarkaFiskalna.DaneDrukarkiZapisywaneWBazie`). - W teście weryfikuj jedynie ustawienie `dok.SymbolKasy` i warunki `IsEnabled/IsVisible` — nie symuluj wydruku. --- ### HANDEL-W72 — E-paragon (e-mail) i ponowny wydruk paragonu **Cel:** obsłużyć **e-paragon** (paragon w formie elektronicznej wysyłany e-mailem) oraz **ponowny wydruk** paragonu na drukarce fiskalnej. Ustawienie pól e-paragonu (`EParagon`, adres e-mail) jest **offline** (testowalne); wysyłka e-mail i wydruk na drukarce są **online/sprzętowe** (NIE testuj). **Warianty:** | Wariant | Mechanizm | Online/sprzęt? | |---|---|---| | Oznaczenie dokumentu jako e-paragon | `dok.EParagon: bool`, `dok.EParagonAdresEmail: string` | NIE (pola) | | Polityka e-paragonu na definicji | `Definicja.OznaczJakoEParagon: OznaczJakoEParagon` | NIE | | Odczyt danych wysłanego e-paragonu | `dok.DaneEParagonu: DaneEParagonu`, `dok.OtworzUrlEParagonu()` | NIE (odczyt) | | Ponowny wydruk paragonu | `PonownyWydrukParagonuWorker.Drukuj()` (akcja „Narzędziowe/Wydrukuj ponownie...") | TAK (sprzęt) | **Pola i typy:** - `dok.EParagon: bool` — czy dokument jest e-paragonem; ustawienie `EParagonAdresEmail` automatycznie ustawia `EParagon` (poza polityką `OznaczJakoEParagon.Zawsze`). - `dok.EParagonAdresEmail: string` — adres e-mail odbiorcy e-paragonu (walidowany; przy `EParagon==true` nie może być pusty). - `Definicja.OznaczJakoEParagon: Soneta.Handel.OznaczJakoEParagon` — `Nigdy=0, Zawsze=1, WgKontrahenta=2`. - `dok.DaneEParagonu: DaneEParagonu`, `dok.OtworzUrlEParagonu(): HyperlinkResult`. - `PonownyWydrukParagonuWorker`: `[Context] Paragon: DokumentHandlowy`, akcja `object Drukuj()`. `IsVisibleDrukuj`: definicja `Fiskalizowany`, dokument zatwierdzony, niepusty `SymbolKasy`. **Snippet:** ```csharp // Oznaczenie dokumentu jako e-paragon i ustawienie adresu e-mail (OFFLINE — testowalne): using (var t = session.Logout(true)) { dok.EParagonAdresEmail = "klient@example.com"; // ustawia też EParagon = true t.Commit(); } session.Save(); bool jestEParagonem = dok.EParagon; // true // Odczyt danych wysłanego e-paragonu (offline): DaneEParagonu dane = dok.DaneEParagonu; // Ponowny wydruk na drukarce fiskalnej — OPERACJA SPRZĘTOWA (NIE testuj jednostkowo): var worker = new PonownyWydrukParagonuWorker { Paragon = dok }; object wynik = worker.Drukuj(); // pyta o potwierdzenie, następnie Fiscalizer.Fiscalize(false) ``` **Pułapki:** - Ustawienie `EParagonAdresEmail` ma efekt uboczny: dla polityki innej niż `Zawsze` automatycznie ustawia `EParagon = !string.IsNullOrWhiteSpace(value)`. Przy `EParagon==true` pusty adres e-mail nie przejdzie walidacji (`EParagonVerifier`/`EParagonEmailVerifier`). - **Sama wysyłka e-paragonu e-mailem wymaga sieci**, a ponowny wydruk — drukarki fiskalnej → **NIE testuj jednostkowo**. Testuj jedynie ustawienie `EParagon`/`EParagonAdresEmail` i wyliczanie polityki `OznaczJakoEParagon`. - `PonownyWydrukParagonuWorker.Drukuj()` wyświetla pytanie „czy wysłać ponownie" (`MessageBoxInformation`) — faktyczny wydruk dzieje się w handlerze `YesHandler` przez `Fiscalizer`. - Ponowny wydruk dostępny tylko dla dokumentu z definicji **fiskalizowanej**, zatwierdzonego, z niepustym `SymbolKasy` (czyli już raz zafiskalizowanego). --- ### HANDEL-W73 — Dokument kompletacji (złożenie / rozłożenie kompletu) **Cel:** obsłużyć kompletację „w locie" — rozbicie pozycji-kompletu na składniki (rozchód składników, przychód wyrobu) wg kartoteki kompletacji. Worker `DokumentKompletacjaWorker` udostępnia przeliczenie pozycji wg kartoteki, wycofujące ręczne zmiany użytkownika. To operacja **w pełni lokalna** (offline) — testowalna, choć wymaga poprawnie skonfigurowanej definicji kompletacji i magazynu. **Warianty:** | Wariant | Mechanizm | |---|---| | Przelicz składniki/produkty wg kartoteki | `DokumentKompletacjaWorker.PrzeliczWgKartoteki(dok)` (akcja w menu Czynności) | | Definicja dokumentu kompletacji | `Definicja.SposobEdycjiKompletacji: SposobEdycjiKompletacji` (≠ `None`) | | Powiązanie składniki ↔ wyrób | dokumenty kompletacji rozchodu/przychodu z `DefDokHandlowych` | | Powiązanie z obrotami | obroty rozchodowe składników i przychodowy wyrobu po `Save` | **Pola i typy:** - `DokumentKompletacjaWorker`: akcja `void PrzeliczWgKartoteki(DokumentHandlowy dokument)`. Wycofuje relacje podrzędne pozycji (`pozycja.PodrzędneRelacje`) i przelicza kompletację wg kartoteki. - `dok.Definicja.SposobEdycjiKompletacji: Soneta.Handel.SposobEdycjiKompletacji` — gdy `None`, akcja niewidoczna (`IsVisiblePrzeliczWgKartoteki`). - Definicje kompletacji w module: `hm.DefDokHandlowych.Kompletacja`, `.KompletacjaRozchód`, `.KompletacjaPrzychód` (typu `DefDokHandlowego`). - Powiązania składników/wyrobu: pozycje dokumentu (`dok.Pozycje`) i ich relacje (`PozycjaDokHandlowego.PodrzędneRelacje` typu `PozycjaRelacjiHandlowej`). **Snippet:** ```csharp using Soneta.Handel.Kompletacje; // Dokument kompletacji „w locie" — definicja musi mieć SposobEdycjiKompletacji != None: var dok = hm.DokHandlowe.WgNumer[...]; // Przeliczenie składników i wyrobu wg kartoteki kompletacji (OFFLINE, w transakcji wew. workera): var worker = new DokumentKompletacjaWorker(); worker.PrzeliczWgKartoteki(dok); // wycofuje zmiany użytkownika, odtwarza komplet z kartoteki session.Save(); // obroty składników (rozchód) i wyrobu (przychód) księgowane przy Save // Sprawdzenie, czy dokument w ogóle obsługuje kompletację: bool kompletacja = dok.Definicja.SposobEdycjiKompletacji != SposobEdycjiKompletacji.None; ``` **Pułapki:** - `PrzeliczWgKartoteki` **kasuje ręczne zmiany użytkownika** w kompletacji i odtwarza komplet z kartoteki — to operacja jednokierunkowa, nie „aktualizacja przyrostowa". - Worker steruje wewnętrzną flagą `dok.BezKopiowania` (włącza/wyłącza w `try/finally`) — nie ustawiaj jej samodzielnie obok wywołania workera. - Akcja jest niewidoczna dla `SposobEdycjiKompletacji == None` oraz dla dokumentu w stanie `Detached`/`Deleted` (`IsVisiblePrzeliczWgKartoteki`). - Obroty magazynowe (rozchód składników, przychód wyrobu) powstają dopiero po `Session.Save()` — w teście zastosuj wzorzec „zapis → `SaveDispose()` → odczyt na świeżej sesji" i pamiętaj o blokadzie stanu ujemnego w bazie Demo (składniki muszą mieć wcześniejszy zapisany przychód). --- ### HANDEL-W74 — Intrastat (dane statystyczne i wyszukanie dokumentów do deklaracji) **Cel:** uzupełnić na pozycjach dokumentu dane potrzebne do deklaracji Intrastat (kod CN, masa, kraj pochodzenia, ilość w jednostkach uzupełniających) za pomocą `DokumentHandlowyZmienIntrastatWorker`, oraz wyszukać dokumenty kwalifikujące się do deklaracji przywozu/wywozu za okres. Operacja jest **w pełni lokalna** (offline) — testowalna. **Warianty:** | Wariant | Mechanizm | |---|---| | Aktualizacja danych Intrastat na pozycjach | `DokumentHandlowyZmienIntrastatWorker.Update()` (akcja „Aktualizuj dane dla Intrastatu ...") | | Wybór aktualizowanych danych | `DokumentHandlowyZmienIntrastatParams`: `KodCN`, `Masa`, `Kraj`, `Przelicznik` (bool) | | Rodzaj Intrastat na definicji | `Definicja.Intrastat: RodzajIntrastat` (`NieUwzględniaj`/`Przywóz`/`Wywóz`/`PrzywózWPodrzędnym`) | | Typ deklaracji (przywóz/wywóz) | `TypDeklaracji.IntrastatPrzywóz` / `IntrastatWywóz` | | Okres dokumentu do deklaracji | `dok.OkresIntrastat` (miesiąc deklaracji) | **Pola i typy:** - `DokumentHandlowyZmienIntrastatWorker`: konstruktor `(DokumentHandlowyZmienIntrastatParams @params)` z `[Context]`; `[Context] Dokument: DokumentHandlowy`, `[Context(Required=false)] Dokumenty: DokumentHandlowy[]`, `Params` (read-only). Akcja `object Update()`. - `DokumentHandlowyZmienIntrastatParams : ContextBase` — `KodCN: bool` („Kod CN"), `Masa: bool` („Masa"), `Kraj: bool` („Kraj pochodzenia"), `Przelicznik: bool` („Ilość w jedn. uzupełn."). - `dok.Definicja.Intrastat: Soneta.Magazyny.RodzajIntrastat`. `dok.KierunekMagazynu: Soneta.Magazyny.KierunekPartii` (kraj pochodzenia aktualizowany tylko, gdy `KierunekMagazynu != Brak`). - Wykonawcza metoda dokumentu: `dok.UaktualnijIntrastat(bool kodCN, bool masa, bool kraj, bool przelicznik): int` (zwraca liczbę zaktualizowanych pozycji). **Snippet:** ```csharp using Soneta.Deklaracje.UE; using static Soneta.Deklaracje.UE.DokumentHandlowyZmienIntrastatWorker; // Aktualizacja danych Intrastat na pozycjach dokumentu (OFFLINE — testowalne): var ctx = session.GetEmptyContext(); ctx.TryAdd(() => dok); var parametry = new DokumentHandlowyZmienIntrastatParams(ctx) { KodCN = true, // przepisz kod CN z kartoteki towaru Masa = true, // przelicz masę pozycji Kraj = true, // ustaw kraj pochodzenia Przelicznik = true // ilość w jednostce uzupełniającej }; var worker = new DokumentHandlowyZmienIntrastatWorker(parametry) { Dokument = dok }; worker.Update(); // aktualizuje pozycje; pomija dokumenty z Definicja.Intrastat == NieUwzględniaj session.Save(); // Wyszukanie dokumentów do deklaracji za okres — filtr serwerowy po rodzaju Intrastatu i okresie: var hm = session.GetHandel(); var okres = new FromTo(Date.Today.FirstDayMonth(), Date.Today.LastDayMonth()); foreach (DokumentHandlowy d in hm.DokHandlowe.WgNumer[(DokumentHandlowy d) => d.OkresIntrastat >= okres.From && d.OkresIntrastat <= okres.To]) { bool przywoz = d.Definicja.Intrastat == RodzajIntrastat.Przywóz || d.Definicja.Intrastat == RodzajIntrastat.PrzywózWPodrzędnym; // przywoz == true ⇒ TypDeklaracji.IntrastatPrzywóz, w przeciwnym razie IntrastatWywóz } ``` **Pułapki:** - `Update()` rzuca `ApplicationException`, gdy dokument zawiera **koszty dodatkowe z podziałem wg masy** (`PodzialKosztuDodatkowego == Masa`) a zaznaczono aktualizację masy — nie da się wtedy przeliczyć masy. - Dokumenty z `Definicja.Intrastat == RodzajIntrastat.NieUwzględniaj` są **pomijane** (akcja niewidoczna — `IsVisibleUpdate`). - Kraj pochodzenia aktualizowany jest tylko, gdy `dok.KierunekMagazynu != KierunekPartii.Brak` — sam parametr `Kraj=true` nie wystarczy dla dokumentu bez ruchu magazynowego. - Jeśli istnieje już **zatwierdzona deklaracja** Intrastat za dany okres (`OkresIntrastat.LastDayMonth()`), worker dopisze do logu ostrzeżenie, że dane nie zmienią się w zatwierdzonej deklaracji (trzeba wygenerować korektę) — aktualizacja na dokumencie i tak się wykona. - Wyszukiwanie dokumentów do deklaracji filtruj **serwerowo** po `OkresIntrastat` i rodzaju Intrastatu z definicji — nie ładuj całej tabeli `DokHandlowe` do pamięci (safe-code §6). ---