Wystawianie Faktur i Paragony
Przewodnik wyjaśniający, jak system zarządza preferencjami klientów dotyczącymi fakturowania oraz jak pomaga pracownikom na recepcji podczas rozliczania opłat za zajęcia.
👤 Instrukcja dla pracownika (Recepcja / Administracja)
System AcePark automatyzuje zarządzanie preferencjami klientów w zakresie dokumentów sprzedażowych (faktury VAT, faktury imienne, paragony fiskalne), ułatwiając i standaryzując proces rozliczania opłat na recepcji.
1. Deklaracja preferencji fakturowania przez klienta
Każdy klient podczas logowania do panelu (lub w procesie rejestracji) ma możliwość trwałego zdefiniowania preferowanego dokumentu księgowego.
Ścieżka dla klienta: Portal Klienta ➔ Profil ➔ Dane do faktury
- Faktura VAT (B2B): Użytkownik wprowadza NIP oraz nazwę przedsiębiorstwa.
- Faktura Imienna (B2C): Użytkownik wprowadza dane osoby fizycznej.
- Zastosuj dla wszystkich uczestników: Opcja ta jest wykorzystywana w przypadku kont rodzinnych. Pozwala na automatyczne powielenie danych fakturowych (np. jednego NIP-u) na wszystkie konta podopiecznych (dzieci). Należy pamiętać, że administrator z poziomu panelu pracowniczego również posiada uprawnienia do globalnego ujednolicenia tych danych.
2. Rozliczanie klienta i obsługa paragonów
Podczas przyjmowania płatności stacjonarnej, uruchomienie formularza "Rozliczenie rezerwacji" wyzwala zautomatyzowane procedury weryfikacyjne:
Ścieżka dla pracownika: Dashboard ➔ Kalendarz (lub Uczestnicy) ➔ Rozliczenie rezerwacji ➔ Odznacz/zaznacz "Drukuj paragon"
- Weryfikacja preferencji: Interfejs wyświetla wyraźny, kolorowy komunikat ostrzegawczy (np. "Uwaga, klient wymaga faktury na firmę: ABC Sp. z o.o., NIP 123..."), jeśli profil klienta posiada aktywne dane fakturowe.
- Zabezpieczenie przed błędem fiskalnym: Jeżeli klient zadeklarował chęć otrzymania faktury VAT na firmę, system automatycznie odznacza opcję wydruku paragonu na zintegrowanej kasie fiskalnej. Blokada ta zabezpiecza przed nieintencjonalnym zarejestrowaniem sprzedaży detalicznej (bez wpisanego NIP-u nabywcy) dla transakcji typu B2B.
- Funkcjonalność ta eliminuje konieczność manualnej weryfikacji profilu każdego klienta przed dokonaniem transakcji stacjonarnej.
3. Faktury za półkolonie
Rejestracja na turnusy (np. półkolonie) jest w pełni zautomatyzowana pod kątem księgowym. Jeżeli klient w przeszłości zdefiniował profil fakturowy, podczas kolejnych zapisów system domyślnie użyje zachowanych danych przedsiębiorstwa.
4. Gdy Fakturownia odrzuci fakturę
Fakturownia sprawdza nabywcę ostrzej niż formularze zapisu — najczęściej chodzi o NIP,
którego nie uznaje (błędny nr NIP nabywcy). Odrzucenie przychodzi już po zapłacie
klienta, więc system zachowuje się tak:
- płatność nie zostaje bez dokumentu — zamiast faktury wystawiany jest paragon,
- na
Dashboard ➔ Powiadomienia krytycznepojawia się wpis „Fakturownia odrzuciła fakturę dla płatności #…” z numerami płatności.
Zadanie recepcji: poprawić dane do faktury w profilu klienta (najczęściej NIP lub adres)
i wystawić fakturę ręcznie. Sam formularz zapisu na próbne i stałe zajęcia nie przepuści
już NIP-u z błędną sumą kontrolną ani numeru z jednej powtórzonej cyfry
(np. 9999999999), więc ta sytuacja dotyczy głównie danych wprowadzonych wcześniej.
5. Faktura imienna a paragon
Klient z zaznaczoną fakturą imienną dostaje dwa dokumenty: paragon (sprzedaż detaliczna rejestrowana na kasie) oraz fakturę imienną wystawioną do tego paragonu. W Fakturowni faktura jest z paragonem powiązana — na jej podglądzie widać, do którego paragonu została wystawiona, a dokument nosi oznaczenie FP, dzięki czemu ta sama sprzedaż nie trafia do JPK dwa razy.
Czego szukać przy weryfikacji w Fakturowni: otwórz fakturę imienną — w sekcji dokumentów powiązanych powinien być widoczny paragon. Jeżeli go nie ma (dotyczy dokumentów wystawionych przed sierpniem 2026), oba dokumenty są osobnymi bytami i powiązanie trzeba uzupełnić ręcznie w Fakturowni.
6. Nazwy pozycji na e-paragonie
Na paragonie fiskalnym nazwa pozycji wygląda inaczej niż na fakturze — i tak ma być:
| Faktura VAT / imienna | Paragon fiskalny (e-Paragon) |
|---|---|
Zajęcia tenisowe — wrzesień 2026 (Leon Nowak) | Zajecia tenisowe 09.2026 (Leon Nowak) |
Półkolonia Tenisowa | Polkolonia Tenisowa |
Powód: drukarka fiskalna Fakturowni psuje polskie znaki (Zajęcia drukowała jako
Zaj'cia, Półkolonia jako P 'kolonia) i ucina nazwę na 40 znakach — przez co z
paragonu znikało nazwisko uczestnika. Dlatego na paragony trafia nazwa bez polskich
znaków, z miesiącem skróconym do MM.RRRR, a przy bardzo długich danych nazwisko
skraca się do inicjału (Aleksandra K.). Faktury zostają bez zmian — pełna polszczyzna.
Jeżeli klient pyta, dlaczego na paragonie nie ma polskich liter: to ograniczenie fiskalizacji po stronie Fakturowni, a nie błąd w danych — kwoty i uczestnik są poprawne.
Wycofanie preferencji fakturowych
W przypadku rezygnacji klienta z otrzymywania faktur B2B, modyfikacja wymaga edycji jego profilu. Należy usunąć zaznaczenie z opcji faktur, upewnić się, że funkcja "Zastosuj dla wszystkich uczestników" jest aktywna, a następnie zapisać zmiany. Od tego momentu system zaprzestanie wymuszania faktur dla danego konta rodzinnego.
🛠️ Dokumentacja techniczna
Sekcja opisująca relacyjne aspekty oraz Server Actions w procesie Fakturowania.
Architektura danych bazy
Dane fakturowe są zapisywane w dedykowanej tabeli invoice. Relacja zachodzi z tabelą player (player_id jest kluczem obcym). Dzięki polu owner_email system jest w stanie grupować "podopiecznych" (np. kilkoro dzieci jednego użytkownika Auth0).
Model invoice przechowuje flagi:
classic_invoice(BOOLEAN) – Czy wystawiana jest pełna faktura VAT na firmę (wymaga NIP i company name).nominal_invoice(BOOLEAN) – Czy wystawiana jest faktura imienna.
Kluczowe Funkcje w API
Znajdują się głównie w Server Actions:
fetchUserInvoiceData: Ciągnie dane logiki i sprawdza, czy wszyscy uczestnicy podpięci pod ten sam e-mail mają dokładnie ten sam stan rekordu invoice (wylicza z tego dynamicznie flagęisAppliedToAllna potrzeby UI).fetchPlayerInvoiceDataInternal: Podstawowa funkcja odczytująca dane invoice używana przez obsługę bez weryfikacji JWT tokenu sesji właściciela (odczyt na uprawnieniach STAFF).upsertInvoicesForUserClient: Zbiorcza operacja dla klienta modyfikująca ustawienia invoice dla wieluplayer_idw jednej transakcji bazy danych.applyInvoiceDataToAllPlayersByPlayerId: Odpowiednik dla Managera, umożliwiający wzięcieplayer_iddziecka #1 i nadpisanie jego firmowego NIPu na profil dziecka #2 tego samego opiekuna.
Walidacja NIP-u
lib/utils/nip.ts (isValidPolishTaxId) to jedno źródło prawdy: 10 cyfr, poprawna suma
kontrolna (wagi 6 5 7 2 3 4 5 6 7, reszta z dzielenia przez 11) i odrzucenie numerów
złożonych z jednej powtórzonej cyfry — te przechodzą sumę kontrolną, ale Fakturownia ich
nie przyjmuje. Używają jej validateInvoiceDetails
(lib/trial-registration-payment.ts, wspólna dla zapisu na próbne i stałe) oraz oba
kroki płatności w interfejsie, więc zły numer zatrzymuje się przed hand-offem do
Przelewy24.
Odrzucenie faktury przez Fakturownię
generateInvoiceFromPayment i generateBulkInvoiceFromPayments
(lib/actions/invoice-generation.ts) łapią błąd z POST /invoices.json osobno od
reszty przetwarzania i wywołują fallbackToReceipt:
reportInvoiceRejectionloguje błąd i zakłada powiadomienie krytyczne (type: 'sync',severity: 'critical') z listąpaymentIds,- dla tej samej listy pozycji wystawiany jest paragon i zapisywany na płatnościach
przez
recordReceiptGeneration.
Bez tego wyjątek kończył się samym logError — płatność zostawała z invoice_id i
receipt_id równym NULL, a nikt nie dostawał sygnału, że dokumentu nie ma.
Powiązanie faktury imiennej z paragonem
Fakturownia traktuje fakturę i paragon jako niezależne dokumenty, dopóki przy tworzeniu
faktury nie dostanie kompletu parametrów wiążących. createInvoiceLinkedToReceipt
(lib/actions/invoice-generation.ts) wysyła je razem:
| Parametr | Wartość | Rola |
|---|---|---|
from_invoice_id | id paragonu | wskazuje dokument źródłowy |
additional_params | for_receipt | przełącza tworzenie w tryb „faktura do paragonu” |
issued_to_receipt | true | nadaje oznaczenie FP wymagane w JPK |
Konsekwencje dla przepływu w obu ścieżkach (generateInvoiceFromPayment i
generateBulkInvoiceFromPayments):
- Paragon powstaje pierwszy — jego
idjest potrzebne do wystawienia faktury, więc kolejność wywołań jest odwrotna niż wcześniej.recordReceiptGenerationzapisuje go na płatnościach od razu, zanim ruszy faktura. - Faktura jest wystawiana z linkiem. Jeżeli Fakturownia odrzuci samo powiązanie,
createInvoiceLinkedToReceiptponawia to samo żądanie bez parametrów wiążących (ten samidempotencyKey, więcoidnie kolidują) — klient dostaje fakturę nawet wtedy, gdy powiązanie się nie uda. - Odrzucenie całej faktury wchodzi w
fallbackToReceipt, który korzysta z już wystawionego paragonu zamiast tworzyć drugi. - Nieudany paragon nie blokuje faktury — powstaje wtedy dokument niepowiązany, tak jak przed zmianą.
Faktura klasyczna (B2B) tej ścieżki nie dotyczy: dla niej paragon celowo nie jest wystawiany, więc nie ma czego wiązać.
Nazwy pozycji bezpieczne dla fiskalizacji
Fiskator Fakturowni (paragony.pl) gubi każdy znak spoza ASCII w nazwie pozycji i przycina
ją do 40 znaków. Dokument JPK e-paragonu zawierał więc Zaj'cia tenisowe --- wrzesie· 2026 (Leon
mimo że w API Fakturowni zapisana jest poprawna nazwa Zajęcia tenisowe — wrzesień 2026 (Leon Nowak).
toFiscalPositionName (lib/utils/fiscal-position-name.ts) buduje wersję nazwy odporną na
ten krok, kolejno:
- transliteracja polskich liter i myślników do ASCII, usunięcie reszty znaków spoza ASCII,
- skrócenie
wrzesień 2026→09.2026(wraz z myślnikiem separatora); pełna data dzienna (15 maja 2026) zostaje nietknięta, - skrócenie nazwiska w nawiasie do inicjału (
Aleksandra K.), potem do samego imienia, - twarde przycięcie do 40 znaków — jeżeli nawias się nie mieści, znika w całości, zamiast zostać przecięty w środku.
Punkt wejścia jest jeden: toFiscalDocument (lib/fakturownia.ts) podmienia nazwy pozycji
tylko dla dokumentów kind: 'receipt'. Wywołuje go createInvoiceIdempotent (a więc
wszystkie ścieżki wystawiania paragonów: pojedyncza, zbiorcza, płatności dzielone, sklep)
oraz handlery proxy POST /api/fakturownia/receipts, PUT /api/fakturownia/receipts/[id]
i POST /api/fakturownia/invoices. Faktury (kind: 'vat') przechodzą bez zmian, mimo że w
generateInvoiceFromPayment dzielą obiekt pozycji z paragonem.
Zmiana dotyczy tylko nowych dokumentów — paragony wystawione wcześniej zostają z popsutą nazwą, bo dokument fiskalny jest niezmienny.
Logika synchronizacji interfejsu (Dialogi płatności)
Gdy pracownik otwiera okienko ReservationSettleDialog (lub zmiania cel w selekcie activePlayerId), w tle uruchamiana jest operacja pobrania danych profilu fakturowego:
- Component dispatchuje
fetchPlayerInvoiceDataInternal. - Jeżeli API zwróci, że
classic_invoice === true(oraz nominal_invoice jest false, co wskazuje stricte B2B), stan systemowy zmienia domyślną konfigurację: mutacja wymusza ustawienie checkboxaprintReceiptnafalse. - To zapobiega nieintencjonalnemu wywołaniu żądania do zintegrowanego systemu kasy fiskalnej, chroniąc obiekt przed potrzebą pisania fizycznych zwrotów do raportu dobowego kasy.