ksef2lms · LMS × KSeF 2.0

LMS z KSeF

ksef2lms — darmowa instrukcja: jak podłączyć LMS do Krajowego Systemu e-Faktur

Wersja LMS28-git (sierpień 2026)
KSeF2.0 produkcja
Obowiązkowy od01.04.2026
Autor@cocoban78
Aktualna wersja paczki: 30.08.2026 · ↓ Pobierz

Słowo wstępne

To jest ksef2lms — darmowy łącznik LMS z KSeF 2.0 (LMS w KSeF, wysyłka faktur z LMS do Krajowego Systemu e-Faktur). Działa z LMS Chilka, nie jest częścią oficjalnego repozytorium.

Cześć! Na samym wstępie muszę się przyznać — jestem wieloletnim i oddanym fanem systemu LMS :D Poniższy projekt powstawał momentami w bólach, prawdopodobnie bywa pełen chaosu i z pewnością nie wygra konkursu na najlepiej udokumentowany kod w historii. Początkowo tworzyłem to wyłącznie na swoje własne, absolutnie prywatne potrzeby, żeby jakoś okiełznać nadchodzącego potwora zwanego KSeF. Nie ukrywam! W dużej mierze pomogła tutaj sztuczna inteligencja...

Jednak, ulegając presji tłumu i na prośbę wielu z Was, postanowiłem podzielić się tym dziełem zupełnie nieodpłatnie. Musisz jednak wiedzieć o jednym: nie działam w porozumieniu z autorami ani programistami oficjalnego LMS-a. Oznacza to, że wszelkie ich poprawki i aktualizacje mogą znacząco wpłynąć na (nie)działanie niniejszej integracji. Mój projekt nie jest oficjalną częścią systemu. Oczywiście bardzo się starałem, by całość była jak najbardziej spójna i niczego nie zepsuła, ale korzystasz z tego na własną odpowiedzialność! Oczywiście udostępniony kod może być także inspiracją dla innych, śmiało korzystaj! Zachęcam również do dzielenia się :P

Szukasz rozwiązania w pełni profesjonalnego? Jeśli nie lubisz dreszczyku emocji i zależy Ci na stabilnym, w 100% profesjonalnie wspieranym zintegrowaniu KSeF z systemem LMS, z czystym sumieniem polecam zapoznanie się z ofertą LMS+ (https://lms.plus/).
Od czego zacząć Łącznik pierwszy raz — Krok 1–3 (certyfikat, lms.ini, lms-npi.sh), potem skrypty ręcznie.
Łącznik już masz, tylko poprawka --retry-errors — Pobierz A. Gita nie ruszasz.
Skok LMS Chilka z kwietnia/czerwca na 30.08 — Pobierz B (git) albo Pobierz C (zip). Nie mieszaj tych procedur.

Wymagania

SkładnikMinimalna wersjaSprawdź
LMS28-git, migracje ≥ 2026081100SELECT keyvalue FROM dbinfo WHERE keytype='dbversion'
PHP8.1+php -v
Composerdowolnacomposer --version
Biblioteka KSeFn1ebieski/ksef-php-clientls vendor/n1ebieski/
Certyfikat KSeFPlik .p12 tworzony z plików .crt, .key oraz hasła wygenerowanych w portalu KSeF—
ext-zipwymagane przez PHPphp -m | grep zip

Jeśli brak biblioteki ksef-php-client (pierwsza instalacja, nie aktualizacja):

cd /twoja/sciezka/lms
composer require n1ebieski/ksef-php-client --no-dev

Po aktualizacji Chilka — konkretna paczka, nie goły composer update:

cd /twoja/sciezka/lms
composer update n1ebieski/ksef-php-client --with-dependencies --no-dev
composer show n1ebieski/ksef-php-client | grep versions

Aktualna wersja biblioteki: 1.9.0. jQuery UI (js/jqueryui/dist/) jest w procedurze B/C przy pobieraniu — bez tego GUI kręci kółko, to nie jest KSeF.

Krok 1 Certyfikat PKCS12

Aby uwierzytelnić się w systemie KSeF, potrzebujesz certyfikatu. W portalu KSeF Ministerstwa Finansów (Aplikacja Podatnika → Certyfikaty) generujesz parę plików o rozszerzeniach .crt oraz .key, a także podajesz dla nich hasło, które musisz zapamiętać i wpisać do pliku lms.ini. Z tych danych generujesz docelowy plik .p12 (PKCS#12) tym poleceniem:

# Połącz .crt i .key w plik .p12
openssl pkcs12 -export \
  -in certyfikat.crt \
  -inkey certyfikat.key \
  -out /etc/lms/ksef_cert.p12   # lub inna wybrana ścieżka \
  -passout pass:TwojeHaslo123!

# Zabezpiecz plik
chmod 640 /etc/lms/ksef_cert.p12
chown root:www-data /etc/lms/ksef_cert.p12
⚠ Bezpieczeństwo Plik .p12 i hasło do niego to dostęp do wystawiania faktur w Twoim imieniu. Upewnij się że plik nie jest dostępny publicznie ani dla innych użytkowników serwera. Zalecana lokalizacja: /etc/lms/ksef_cert.p12 z uprawnieniami 640.
Certyfikat offline (opcjonalny — awaria MF, kod QR)

Ministerstwo Finansów może ogłosić awaryjny tryb offline KSeF — wtedy faktury wystawia się bez połączenia z systemem MF. Na PDF pojawia się kod QR podpisany kluczem ECDSA. Osobny certyfikat: w portalu KSeF przeznaczenie „Offline". Pliki .crt i .key łączysz w .pem:

# Certyfikat offline musi być ECDSA (EC P-256) — nie RSA
cat ksef_offline.key ksef_offline.crt > /etc/lms/ksef_offline.pem
chmod 640 /etc/lms/ksef_offline.pem

# Weryfikacja że to EC (a nie RSA):
openssl pkey -in /etc/lms/ksef_offline.pem -text -noout | grep "EC\|prime256"
# Oczekiwany wynik: id-ecPublicKey, prime256v1

W lms.ini w [ksef]:

offline_certificate = /etc/lms/ksef_offline.pem
offline_password    =       # hasło do pliku .pem (zazwyczaj puste)
offline_support     = 1     # zezwól na wysyłkę e-mail faktur bez numeru KSeF

Do samego wystawienia faktury offline ten certyfikat nie jest wymagany. Typ 2 jest potrzebny, gdy chcesz drugi kod QR (tożsamość wystawcy) przy offline24 / niedostępności / trybie awaryjnym. Bez niego faktura offline nadal przejdzie, tylko bez tego QR.

Nie testowałem tego na prawdziwej awarii KSeF — nie było kiedy. Zrobione wg dokumentacji MF i LMS. W realu może wymagać korekty.

Krok 2 Konfiguracja lms.ini

Edytuj swój plik lms.ini (zazwyczaj /etc/lms/lms.ini lub inna ścieżka podana przy instalacji LMS) i dodaj / uzupełnij poniższe sekcje. Pamiętaj, by we wszystkich komendach zamienić /sciezka/do/lms.ini na własną ścieżkę:

[phpui]
plugins = KSeFSubmit           # dodaj do istniejącej listy pluginów

[ksef]
environment    = prod          # prod / test / demo
auth_method    = certificate   # certificate (zalecane) lub token
certificate    = /etc/lms/ksef_cert.p12
password       = "TwojeHaslo123!"
boundary_date  = 2026/04/01    # data od której wysyłamy faktury
encryption_key = Wpisz32ZnakowyLosowyKluczAES256!
encryption_iv  = 16ZnakowIV1234
debug          = 0              # 1 = loguj plugin do PHP error_log, 0 = cisza

[invoices]
issuer            = 'Dział obsługi rozliczeń'        ; podpis wystawcy na fakturze PDF
show_balance_summary = 0                              ; 0 = nie pokazuj salda klienta na fakturze PDF (zalecane dla ISP)
show_pricing_method  = 0
info_box_text     = '<center><b>Informacja</b>\nFaktura wystawiona w KSeF.\nDokument w KSeF stanowi fakturę właściwą.\nPDF ma charakter informacyjny.</center>'
;                  ↑ ramka na fakturach B2B — obsługuje HTML, \n = nowa linia
info_box_text_b2c = '<center>Dziękujemy za korzystanie z naszych usług</center>'
;                  ↑ ramka na fakturach B2C — jeśli brak: używana info_box_text

[sendinvoices]            ; faktury B2B — firmy, klienci z NIPem
smtp_host             = poczta.twojadomena.pl     ; serwer SMTP
smtp_port             = 587                       ; 587=STARTTLS, 465=SSL, 25=plain
smtp_auth             = PLAIN                     ; PLAIN lub LOGIN
smtp_user             = nadawca@twojadomena.pl    ; login SMTP
smtp_pass             = "HasloDoSMTP!"            ; haslo SMTP
smtp_ssl_verify_peer  = 0                        ; 0 = nie weryfikuj certyfikatu SSL (webstorage itp.)
smtp_ssl_verify_peer_name = 0
smtp_ssl_allow_self_signed = 0
lms_url              = https://adres.twojego.lms/ ; URL do LMS (do linkow w mailach)
lms_user             = uzytkownik_lms            ; uzytkownik LMS do generowania PDF
lms_password         = "HasloUzytkownikaLMS!"
customergroups       = nazwa-grupy               ; grupy klientow do wysylki (spacja = separator)
sender_name          = Nazwa Twojej Firmy
sender_email         = biuro@twojadomena.pl
invoice_filename     = Faktura_%number           ; nazwa pliku PDF w zalacznik
mail_subject         = Faktura VAT %invoice
mail_body            = /etc/lms/mail_body_b2b.txt
mail_format          = html
; debug_email = twoj@email.pl  ; jesli ustawione — mail idzie TYLKO na ten adres (do testow)

[sendinvoices-b2c]        ; faktury B2C — osoby fizyczne
smtp_host             = poczta.twojadomena.pl
smtp_port             = 587
smtp_auth             = PLAIN
smtp_user             = nadawca@twojadomena.pl
smtp_pass             = "HasloDoSMTP!"
smtp_ssl_verify_peer  = 0
smtp_ssl_verify_peer_name = 0
smtp_ssl_allow_self_signed = 0
lms_url              = https://adres.twojego.lms/
lms_user             = uzytkownik_lms
lms_password         = "HasloUzytkownikaLMS!"
customergroups       = nazwa-grupy
sender_name          = Nazwa Twojej Firmy
sender_email         = biuro@twojadomena.pl
invoice_filename     = Faktura_%number
mail_subject         = Faktura %invoice
mail_body            = /etc/lms/mail_body_b2c.txt
mail_format          = html

Przykładowe pliki treści maili

Pliki wskazane w mail_body to szablony HTML. Zmienne dostępne w treści:

ZmiennaZnaczenie
%invoiceNumer faktury (np. FV/001/04/2026)
%ksef-numberNumer KSeF (np. 8441240447-20260401-...)
%grossKwota brutto faktury
%bankaccountNumer konta bankowego
%paytimeTermin płatności
%customeridID klienta w LMS

/etc/lms/mail_body_b2b.txt — dla firm (B2B):

Szanowni Państwo,<br><br>
informujemy, że została wystawiona faktura VAT nr <b>%invoice</b>.<br><br>
<b>Numer KSeF: </b> %ksef-number<br>
Faktura została wystawiona w KSeF.<br>
Dokument dostępny w KSeF stanowi fakturę właściwą.<br><br>
Załączony plik PDF ma charakter poglądowy.<br><br>
W razie pytań prosimy o kontakt.<br><br>
Z poważaniem,<br>
<b>Twoja Firma</b>

/etc/lms/mail_body_b2c.txt — dla osób fizycznych (B2C):

Szanowny Kliencie,<br><br>
w załączniku przesyłamy fakturę nr <b>%invoice</b>.<br><br>
Dziękujemy za korzystanie z naszych usług.<br><br>
Pozdrawiamy,<br>
<b>Twoja Firma</b>
ℹ Ramka na fakturze PDF (opcjonalnie) info_box_text / info_box_text_b2c w [invoices] to moja prywatna dopiska do szablonu — w standardowym LMS-ie jej nie ma. Łącznik KSeF działa bez niej. Jeśli zostawiasz oryginalny PDF Chilka, te opcje po prostu nic nie zrobią. Ramka pojawia się na fakturach PDF wystawionych po dacie granicznej KSeF (boundary_date). Dla faktur starszych — nie wyświetla się. Obsługuje HTML: <b>, <center>, <i> oraz \n jako nową linię. Jeśli ustawiona tylko info_box_text — pojawia się dla wszystkich klientów. Jeśli ustawione oba — info_box_text dla B2B (firmy), info_box_text_b2c dla B2C (osoby fizyczne).

Generowanie kluczy szyfrowania

# encryption_key — 32 znaki losowe
openssl rand -base64 24 | tr -d '=+/' | head -c 32

# encryption_iv — 16 znaków losowych
openssl rand -base64 12 | tr -d '=+/' | head -c 16

Krok 3 Instalacja łącznika

LMS musi już działać. Tu dokładasz tylko łącznik KSeF: plugin, skrypty w bin/ i jedną linijkę w pliku Chilka (setlocale). Który zip pobrać i czego nie wklejać — sekcja Pobierz. Tam są trzy osobne przypadki, nie mieszaj ich.

Instalator lms-npi.sh z paczki 30.08 kopiuje plugin i skrypty, potem nakłada setlocale. Nie rusza lib/KSeF/KSeF.php (zamiast kopiowania — patcher) ani szablonu PDF. Najpierw dry-run:

# 1. Wgraj paczkę na serwer i rozpakuj
unzip lms-ksef-netlink-*.zip -d /tmp/ksef-pack

# 2. Dry-run — podgląd, nic nie zmienia
bash /tmp/ksef-pack/lms-npi.sh -c /etc/lms/lms.ini --dry-run

# 3. Właściwa instalacja
bash /tmp/ksef-pack/lms-npi.sh -c /etc/lms/lms.ini

W lms.ini w [phpui] plugins musi być KSeFSubmit (krok 2). Bibliotekę KSeF dociągasz composerem z sekcji wymagań, jeśli jej jeszcze nie ma. Po instalacji: cd /sciezka/lms && bash bin/check-netlink-patches.sh — brak ramki info_box_text na PDF Chilka to nie błąd.

Opcje instalatora

bash /tmp/ksef-pack/lms-npi.sh -c /etc/lms/lms.ini --dry-run
bash /tmp/ksef-pack/lms-npi.sh -c /etc/lms/lms.ini
bash /tmp/ksef-pack/lms-npi.sh -c /etc/lms/lms.ini --no-backup      # bez tar i dumpa
bash /tmp/ksef-pack/lms-npi.sh -c /etc/lms/lms.ini --no-backup-dir  # bez tar katalogu LMS
bash /tmp/ksef-pack/lms-npi.sh -c /etc/lms/lms.ini --no-backup-db   # bez dumpa bazy
Instalacja ręczna — dla zaawansowanych

Jeśli wolisz kopiować pliki samodzielnie — plugin i skrypty tak, KSeF.php i PDF z paczki nie:

lms/
├── lib/KSeF/
│ └── KSeF.php ← nie kopiować z paczki; tylko patcher
├── lib/LMSDocuments/
│ └── LMSTcpdfInvoice.php ← nie kopiować z paczki (prywatny PDF)
├── plugins/KSeFSubmit/ ← NOWY katalog
│ ├── KSeFSubmit.php
│ ├── handlers/
│ │ └── KSeFSubmitHandler.php
│ └── lib/
│ └── KSeFApiService.php
└── bin/
├── lms-ksef.php ← batch wysyłka (cron)
├── lms-ksef-download.php ← pobieranie faktur zakupowych
├── lms-ksef-sync.php ← jednorazowy sync historyczny
└── lms-ksef-bulk-correct.php
PACK=/tmp/ksef-pack      # katalog z rozpakowaną paczką
LMS=/twoja/sciezka/lms      # katalog instalacji LMS

mkdir -p $LMS/plugins/KSeFSubmit/handlers $LMS/plugins/KSeFSubmit/lib
cp $PACK/plugins/KSeFSubmit/KSeFSubmit.php                 $LMS/plugins/KSeFSubmit/
cp $PACK/plugins/KSeFSubmit/handlers/KSeFSubmitHandler.php $LMS/plugins/KSeFSubmit/handlers/
cp $PACK/plugins/KSeFSubmit/lib/KSeFApiService.php         $LMS/plugins/KSeFSubmit/lib/

cp $PACK/bin/lms-ksef.php $PACK/bin/lms-ksef-download.php \
   $PACK/bin/lms-ksef-sync.php $PACK/bin/lms-ksef-bulk-correct.php \
   $PACK/bin/lms-ksef-upo-download.php $PACK/bin/gen-xml.php \
   $PACK/bin/patch-netlink.py $LMS/bin/
cp $PACK/bin/check-netlink-patches.sh $LMS/bin/ 2>/dev/null || \
  cp $PACK/check-netlink-patches.sh $LMS/bin/
chmod +x $LMS/bin/lms-ksef*.php $LMS/bin/patch-netlink.py
python3 $LMS/bin/patch-netlink.py $LMS

Skrypty

To jest w paczce. Cron i GUI wołają te same rzeczy.

PlikPo co
bin/lms-ksef.phpWysyłka sprzedaży do KSeF. Szuka faktur od boundary_date bez numeru i je wysyła. Można odpalać ile razy — drugi raz nic nie zrobi. --status-only tylko odbiera numery. --retry-errors ponawia status 400 (paczka 30.08: pomija te, które numer już mają).
bin/lms-ksef-download.phpFaktury zakupowe (ktoś wystawił na Twój NIP). Cron codzienny. Bez parametrów: wczoraj+dziś. Przy 429 czeka ile każe MF.
bin/lms-ksef-sync.phpTo samo co download, ale Export API: jeden zaszyfrowany ZIP zamiast setek zapytań. Jednorazowo, na zaległości. Nie do crona.
plugins/KSeFSubmit/Wysyłka z GUI zaraz po zapisie faktury. Jak MF nie zdąży w 45s — cron --status-only odbierze numer.
bin/patch-netlink.pyPo git pull Chilek zjada setlocale. Patcher wstawia z powrotem. Nie kopiuj całego KSeF.php z paczki — npi też go nie kopiuje.
bin/check-netlink-patches.shCzy setlocale i skrypty siedzą. Brak ramki info_box_text na PDF Chilka to nie błąd.
Jak to działa od środka (fazy, 429, plugin)

Jak działa lms-ksef.php?

Zasada działania: Skrypt szuka w bazie wszystkich faktur bez numeru KSeF wystawionych od boundary_date (ustawiane w lms.ini). Nie wie nic o miesiącach ani datach uruchomienia — patrzy tylko na tabelę ksefdocuments i pyta: "czy ta faktura ma już numer KSeF?". Jeśli nie — wysyła.

FAZA 1 — Status oczekujących sesji

Zawsze najpierw sprawdza czy są otwarte sesje batch (status 0 lub 100) i pobiera dla nich numery KSeF od MF. To jest ważne — jeśli poprzednie uruchomienie wysłało faktury ale nie zdążyło odebrać numerów (np. skrypt się wysypał), ta faza je nadrobi.

FAZA 2 — Zbieranie faktur

Zapytanie SQL szuka faktur gdzie:

  • d.cdate >= boundary_date (np. 2026-04-01)
  • d.type IN (DOC_INVOICE, DOC_CNOTE, DOC_DNOTE)
  • kd.id IS NULL — brak rekordu w ksefdocuments = jeszcze nie wysłana

Potem filtruje B2B/B2C — firmy zawsze, osoby fizyczne tylko gdy mają zgodę.

Scenariusze:

Wystawiasz faktury 1. dnia, uruchamiasz skrypt 5. dnia tego miesiąca:
Działa dokładnie tak samo jak gdybyś uruchomił 1. dnia. Skrypt znajdzie wszystkie faktury z kd.id IS NULL — czyli wszystkie które w międzyczasie nie zostały wysłane — i wyśle je w jednej sesji batch. Nic się nie traci.

Nie uruchamiasz 2 miesiące:
Skrypt znajdzie faktury z obu miesięcy naraz i wyśle je wszystkie w jednym przebiegu. Przykład: masz 200 faktur za marzec i 200 za kwiecień → skrypt wyśle 400 faktur w jednej lub kilku paczkach ZIP (max 50MB per paczka). Jedyne ograniczenie: MF ma limit sesji batch — ale przy 400 fakturach ISP to żaden problem.

Co jeśli skrypt uruchomisz dwa razy z rzędu:
Drugie uruchomienie nic nie zrobi. Faktury już mają rekordy w ksefdocuments → warunek kd.id IS NULL zwróci pustą listę → skrypt zakończy się komunikatem "Brak nowych faktur".

Co jeśli MF odrzuci fakturę (status 400):
Faktura trafia do ksefdocuments ze statusem 400. Kolejne normalne uruchomienie jej nie ruszy — bo kd.id IS NULL już nie jest prawdą. Żeby ponownie wysłać — trzeba użyć flagi: --retry-errors:
./lms-ksef.php -C /sciezka/do/lms.ini --retry-errors

Co jeśli skrypt padnie w połowie wysyłki:
FAZA 1 przy następnym uruchomieniu sprawdzi otwarte sesje i odbierze numery KSeF dla tego co zdążyło dotrzeć do MF. Faktury które nie zostały w ogóle wysłane — mają nadal kd.id IS NULL i wejdą w kolejnym uruchomieniu.

--status-only (w cronie co godzinę):
Wykonuje tylko FAZĘ 1 — sprawdza otwarte sesje i odbiera numery KSeF. Nie wysyła nic nowego. Używaj tego w cronie między 1. a ostatnim dniem miesiąca, żeby faktury dostały numery KSeF szybciej niż czekając na kolejne miesięczne uruchomienie.

Podsumowanie jednym zdaniem:
Skrypt jest idempotentny i bezpieczny do wielokrotnego uruchamiania — zawsze wysyła tylko to czego jeszcze nie ma w KSeF, nigdy dwa razy to samo.

lms-ksef-download.php

Co robi: pobiera faktury które dostawcy wystawili NA Twój NIP i wrzucili do KSeF. To są Twoje faktury zakupowe — Shell, Orange, Allegro itp.

Opcje uruchomienia:

  • brak parametrów → pobiera wczoraj + dziś (overlap żeby nic nie pominąć)
  • --from=2026/04/01 → od konkretnej daty
  • --days=7 → ostatnie N dni
  • --full-sync → od boundary_date do dziś (pierwsze uruchomienie)

Jak działa krok po kroku:

  1. Wyznacza zakres dat i dzieli go na okresy max 3-miesięczne (limit API MF).
  2. Dla każdego oddziału z NIPem buduje klienta API.
  3. Odpytuje MF o listę faktur (POST /invoices/query/metadata) z paginacją po 100 sztuk — powtarza dopóki hasMore = true.
  4. Dla każdej faktury z listy sprawdza czy ksef_number już jest w bazie — jeśli tak, pomija.
  5. Dla każdej nowej faktury pobiera XML z adaptive delay (im szerszy zakres dat, tym dłuższa pauza między zapytaniami):
    • zakres ≤ 3 dni → 700ms (cron codzienny)
    • ≤ 30 dni → 3s
    • ≤ 90 dni → 7s
    • powyżej 90 dni → 10s
  6. Parsuje XML — wyciąga numer konta, termin płatności, formę płatności.
  7. Zapisuje rekord do ksefinvoices i plik XML na dysk.
  8. Na końcu uruchamia fillMissingXml() — dla faktur które dostały 429 przy pierwszym pobraniu i nie mają XML, próbuje jeszcze raz.

Idempotentny? Tak — sprawdza ksef_number w bazie, nigdy nie zapisze dwa razy tej samej faktury.
Przy 429: czyta Retry-After z odpowiedzi MF, czeka tyle ile MF każe + bufor, zwiększa globalny odstęp do minimum 5s na kolejne zapytania.

lms-ksef-sync.php

Co robi: to samo co download.php ale innym mechanizmem — przez Export API. Zamiast pobierać faktury jedna po drugiej (64 zapytania/h), prosi MF o spakowanie wszystkiego do jednego zaszyfrowanego ZIPa.

Kiedy używać: jednorazowo — do nadrobienia zaległości historycznych. Do crona codziennego używaj lms-ksef-download.php.

Jak działa krok po kroku:

  1. Dzieli zakres dat na okresy max 3-miesięczne.
  2. Dla każdego okresu: POST /invoices/exports → MF zaczyna pakować faktury asynchronicznie.
  3. Polling co 15 sekund (GET /invoices/exports/{ref}) aż MF powie że gotowe (status 200).
  4. Pobiera zaszyfrowany plik .zip.aes.
  5. Odszyfrowuje AES-256-CBC kluczem z lms.ini (encryption_key, encryption_iv).
  6. Rozpakowuje ZIP — w środku są pliki XML + _metadata.json (pomijany).
  7. Każdy XML parsuje i zapisuje do bazy identycznie jak lms-ksef-download.php.

Zaleta: brak throttlingu — zamiast setek pojedynczych zapytań MF pakuje wszystko do jednego zaszyfrowanego ZIPa (AES-256-CBC). Skrypt odszyfrowuje go kluczami z lms.ini (encryption_key + encryption_iv) i przetwarza wszystkie faktury lokalnie.

Plugin KSeFSubmit (GUI)

Aktywacja w lms.ini: plugin wymaga wpisu w sekcji [phpui]:

[phpui]
plugins = KSeFSubmit           ; dodaj do istniejącej listy pluginów

Aktywacja w GUI LMS: po wpisaniu do lms.ini przejdź do Administracja → Konfiguracja → KSeF — pojawi się zakładka konfiguracji pluginu. Poniżej widok przycisku wysyłki na fakturze:

Przycisk KSeF na fakturze w GUI LMS

Co robi: wysyła fakturę do KSeF natychmiast po zapisaniu jej w GUI — bez czekania na cron 1. dnia miesiąca. Klikasz "Zapisz fakturę" → w ciągu kilku sekund masz numer KSeF.

Jak jest zbudowany:

  • KSeFSubmit.php — rejestruje plugin i hooki
  • KSeFSubmitHandler.php — łapie zdarzenia z GUI
  • KSeFApiService.php — komunikacja z API

Które zdarzenia łapie: invoicenew_save_after_submit (nowa faktura), invoiceedit_save_after_submit (edycja faktury), invoicenote_save_after_submit (faktura korygująca).

Jak działa krok po kroku:

  1. Po zapisaniu faktury LMS wywołuje hook.
  2. Handler sprawdza: czy faktura jest po boundary_date? Czy to nie proforma? Czy klient B2B albo ma zgodę B2C? Czy KSeF jest dostępny (Latarnia)?
  3. Jeśli OK — wywołuje DB->UnLockTables() (faktura już zapisana, unlock bezpieczny).
  4. Sprawdza czy faktura nie była już wysłana (ksefdocuments).
  5. Pobiera pełne dane faktury przez GetInvoiceContent().
  6. Generuje XML przez KSeF::getInvoiceXml().
  7. Wysyła XML przez Batch API — sessions()->batch()->openAndSend().
  8. Zamyka sesję batch — sessions()->batch()->close() — wymagane żeby MF zaczął przetwarzać fakturę.
  9. Zapisuje sesję do ksefbatchsessions i fakturę do ksefdocuments (status 0 = oczekuje).
  10. Polling co 3 sekundy, max 15 prób (~45s) — sprawdza status sesji przez sessions()->status().
  11. Po otrzymaniu kodu 200 — pobiera UPO z URL z odpowiedzi MF, parsuje XML, wyciąga numer KSeF i zapisuje UPO na dysk.

Zarówno plugin jak i lms-ksef.php używają Batch API — plugin wysyła jedną fakturę na sesję, CLI może wysłać wiele faktur naraz.
Co jeśli plugin nie zdąży w 45s (MF wolno przetwarza): faktura zostaje w ksefdocuments ze statusem 0. FAZA 1 w lms-ksef.php --status-only odbierze numer i UPO przy kolejnym uruchomieniu crona.

Poprawki w lib/KSeF/KSeF.php

Chilek trzyma lib/KSeF/KSeF.php w swoim repo. Po git pull / rsync ten plik wraca do wersji Chilka i znika setlocale. W zipie łącznika ten plik może leżeć, ale go nie kopiuj na ślepo — npi z 30.08 go nie wgrywa. Setlocale dokłada bin/patch-netlink.py (instalator odpala go sam na końcu).

P1 — setlocale(LC_NUMERIC, 'C')

LMS przestawia separator dziesiętny na przecinek (locale pl_PL). KSeF wymaga kropki w XML — bez tej poprawki pola P_13_*, P_14_*, P_15 (podstawy VAT, kwoty VAT, suma) generowane przez sprintf('%.2f', ...) zawierają przecinki i MF odrzuca fakturę.

⚠ smartFormatNumber (od 22.04.2026) to NIE jest pełna zastępnia P1 Chilek dodał smartFormatNumber() z NumberFormatter('en_US'), ale używa go tylko dla ilości, cen jednostkowych i kursu waluty. Pola sum i podatków (P_13_*, P_14_*, P_15) nadal używają sprintf('%.2f', ...) — czyli są nadal wrażliwe na locale. Poprawka P1 (setlocale) jest nadal wymagana.
# Co robi poprawka P1:
public function getInvoiceXml(array $invoice)
{
    setlocale(LC_NUMERIC, 'C'); // NETLink P1
    ...
✓ P2 — ksef_balance_in_xml — nie jest już potrzebna Od wersji 13.04.2026 Chilek wprowadził własny mechanizm obsługi sekcji <Rozliczenie> przez tabelę ksefconfig i GUI LMS. Saldo klienta pojawia się tylko wtedy, gdy w konfiguracji oddziału w GUI LMS jest zaznaczony odpowiedni checkbox (showbalancesummary). Domyślnie jest wyłączone — co jest poprawnym zachowaniem. Zgodnie ze schematem FA(3) sekcja <Rozliczenie> ma minOccurs="0" — jest opcjonalna i MF jej nie wymaga.

Saldo na fakturze — jak skonfigurować?

Jeśli chcesz żeby faktury PDF i XML zawierały saldo klienta (sekcja <Rozliczenie>) — skonfiguruj to w GUI LMS:

Administracja → Konfiguracja → KSeF → Ustawienia oddziałów → Pokazuj rozliczenie salda

⚠ Nie dodawaj ksef_balance_in_xml do lms.ini Ta opcja nie istnieje w aktualnej wersji — jest ignorowana. Saldo sterowane jest wyłącznie przez GUI LMS.

Krok 4 Po instalacji — weryfikacja

Sprawdź czy patche zostały poprawnie nałożone:

# Uruchom z katalogu LMS
cd /twoja/sciezka/lms && bash bin/check-netlink-patches.sh
⚠ Co musisz zrobić ręcznie po instalacji
  1. Dodaj sekcję [ksef] do lms.ini ze środowiskiem (jeśli jeszcze nie ma): environment = prod
  2. Sprawdź tabelę ksefconfig — jeśli pusta, wstaw rekord (SQL jest w Archiwum zmian → 18.04.2026). Instalator z 30.08 tego nie robi.
  3. Jeśli używasz Composera w LMS: cd /twoja/sciezka/lms && composer dump-autoload

UPO — Urzędowe Poświadczenie Odbioru

UPO to dokument XML wystawiony przez MF potwierdzający że faktura przeszła walidację i została przyjęta przez KSeF. MF przechowuje UPO przez 10 lat — możesz je pobrać w każdej chwili, nie ma pośpiechu.

✓ Automatyczne zbieranie UPO przez lms-ksef.php Od wersji 2026-04-09 skrypt lms-ksef.php automatycznie zapisuje UPO na dysk podczas pobierania statusów sesji wsadowych. Pliki lądują w:
{sys_dir}/storage/ksef/upo/{NIP}/{YYYYMMDD}/{numerKSeF}.xml
⚠ Wymagany katalog UPO Katalog jest tworzony automatycznie przez upgradedb.php Chilka. Jeśli jest pusty lub go brakuje — sprawdź i utwórz ręcznie:
ls -la /twoja/sciezka/lms/storage/ksef/
# Powinno byc: drwxr-xr-x apache apache ... upo

# Jesli brak:
mkdir -p /twoja/sciezka/lms/storage/ksef/upo
chown apache:apache /twoja/sciezka/lms/storage/ksef/upo
chmod 750 /twoja/sciezka/lms/storage/ksef/upo

Plugin GUI (KSeFSubmit) od wersji 17.04.2026 fix3 pobiera i zapisuje UPO automatycznie — razem z numerem KSeF, w tym samym żądaniu HTTP co wysyłka faktury. UPO trafia do tego samego katalogu co przy wysyłce przez CLI:
{sys_dir}/storage/ksef/upo/{NIP}/{YYYYMMDD}/{numerKSeF}.xml
Jeśli plugin nie zdążył pobrać UPO (timeout 45s) — lms-ksef.php --status-only pobierze je automatycznie przy kolejnym uruchomieniu crona.

Krok 5 Sync historycznych faktur zakupowych

Pierwsze uruchomienie — pobierz wszystkie faktury które wystawcy przesłali do KSeF na Twój NIP. Skrypt używa Export API (jeden zaszyfrowany ZIP zamiast setek osobnych zapytań):

cd /twoja/sciezka/lms/bin

# Najpierw dry-run — sprawdź ile faktur zostanie pobranych
./lms-ksef-sync.php -C /sciezka/do/lms.ini --from=2026/01/01 --dry-run

# Właściwy sync (zakres maksymalnie 3 miesiące, podziel jeśli większy)
./lms-ksef-sync.php -C /sciezka/do/lms.ini --from=2026/01/01 --to=2026/03/31
./lms-ksef-sync.php -C /sciezka/do/lms.ini --from=2026/04/01
ℹ Skrypt czeka kilkanaście sekund MF przetwarza eksport asynchronicznie — skrypt odpytuje co 15 sekund aż eksport będzie gotowy, potem pobiera i rozpakowuje zaszyfrowany ZIP. Przy kilkudziesięciu fakturach trwa ok. 30 sekund.

Wysyłka historycznych faktur sprzedaży do KSeF

# Dry-run — sprawdź ile faktur do wysłania
./lms-ksef.php -C /sciezka/do/lms.ini --dry-run

# Właściwa wysyłka
./lms-ksef.php -C /sciezka/do/lms.ini

Krok 6 Konfiguracja Cron

Zamień /twoja/sciezka/lms i /sciezka/do/lms.ini na własne ścieżki.

ℹ Kiedy wystawiasz faktury — to zależy od Ciebie Skrypty nie wiedzą nic o miesiącach ani dniach — szukają wszystkich faktur bez numeru KSeF i je wysyłają. Możesz generować faktury i uruchamiać lms-ksef.php kiedy chcesz — 1. dnia, 5. dnia, codziennie. To wynika z Twojej konfiguracji taryf w LMS, nie ze skryptów.

Wariant A — faktury generowane 1. dnia miesiąca

Klasyczny model: LMS generuje faktury 1. dnia, skrypty wysyłają je do KSeF i e-mailem tego samego dnia. Ważne: lms-ksef.php startuje 20 minut po generowaniu — żeby faktury były już gotowe w bazie.

# crontab -e

# Generowanie faktur i płatności — 1. dnia o 02:00
 0  2 1 * *  /twoja/sciezka/lms/bin/lms-payments.php -C /sciezka/do/lms.ini

# Wysyłka do KSeF — 20 minut po generowaniu
20  2 1 * *  php /twoja/sciezka/lms/bin/lms-ksef.php -C /sciezka/do/lms.ini >> /var/log/lms-ksef.log 2>&1

# Wysyłka e-mail B2B (tylko faktury z numerem KSeF)
40  2 1 * *  /twoja/sciezka/lms/bin/lms-sendinvoices.php -C /sciezka/do/lms.ini --ksef

# Wysyłka e-mail B2C (osoby fizyczne — nie wymagają KSeF)
45  2 1 * *  /twoja/sciezka/lms/bin/lms-sendinvoices.php -C /sciezka/do/lms.ini --without-ksef --section=sendinvoices-b2c

# Sprawdzanie statusów KSeF co godzinę (przez cały miesiąc)
 0  * * * *  php /twoja/sciezka/lms/bin/lms-ksef.php -C /sciezka/do/lms.ini --status-only >> /var/log/lms-ksef.log 2>&1

# Pobieranie faktur zakupowych od dostawców — codziennie o 06:15
15  6 * * *  php /twoja/sciezka/lms/bin/lms-ksef-download.php -C /sciezka/do/lms.ini -q >> /var/log/lms-ksef-download.log 2>&1

Wariant B — faktury generowane codziennie (różne dni dla różnych klientów)

Jeśli Twoi klienci mają zobowiązania rozłożone na różne dni miesiąca, lms-payments.php generuje faktury codziennie tylko dla tych którzy mają zobowiązanie na dany dzień. Wtedy warto też codziennie wysyłać do KSeF:

# Generowanie faktur — codziennie o 02:00
 0  2 * * *  /twoja/sciezka/lms/bin/lms-payments.php -C /sciezka/do/lms.ini

# Wysyłka do KSeF — 20 min po generowaniu, codziennie
20  2 * * *  php /twoja/sciezka/lms/bin/lms-ksef.php -C /sciezka/do/lms.ini >> /var/log/lms-ksef.log 2>&1

# Wysyłka e-mail B2B
40  2 * * *  /twoja/sciezka/lms/bin/lms-sendinvoices.php -C /sciezka/do/lms.ini --ksef

# Wysyłka e-mail B2C
45  2 * * *  /twoja/sciezka/lms/bin/lms-sendinvoices.php -C /sciezka/do/lms.ini --without-ksef --section=sendinvoices-b2c

# Sprawdzanie statusów KSeF co godzinę
 0  * * * *  php /twoja/sciezka/lms/bin/lms-ksef.php -C /sciezka/do/lms.ini --status-only >> /var/log/lms-ksef.log 2>&1

# Pobieranie faktur zakupowych — codziennie
15  6 * * *  php /twoja/sciezka/lms/bin/lms-ksef-download.php -C /sciezka/do/lms.ini -q >> /var/log/lms-ksef-download.log 2>&1
⚠ Ważna kolejność: generowanie → KSeF → e-mail lms-payments.php generuje faktury w LMS.
lms-ksef.php wysyła je do KSeF (uruchom minimum 20 minut po generowaniu).
lms-sendinvoices.php --ksef wysyła e-maile tylko fakturom z numerem KSeF — musi startować po lms-ksef.php.

Jeśli MF przetwarza wolno — cron --status-only (co godzinę) odbierze numery automatycznie. E-mail pójdzie przy następnym uruchomieniu lms-sendinvoices.php.

Wariant C — bez crona (ręczne uruchamianie)

Możesz uruchamiać wszystkie skrypty ręcznie kiedy chcesz — cron tylko to automatyzuje:

php /twoja/sciezka/lms/bin/lms-ksef.php -C /sciezka/do/lms.ini --dry-run  # sprawdź ile faktur czeka
php /twoja/sciezka/lms/bin/lms-ksef.php -C /sciezka/do/lms.ini             # wyślij do KSeF
php /twoja/sciezka/lms/bin/lms-ksef.php -C /sciezka/do/lms.ini --status-only # odbierz numery
/twoja/sciezka/lms/bin/lms-sendinvoices.php -C /sciezka/do/lms.ini --ksef  # wyślij e-maile
lms-payments.phpGeneruje faktury w LMS na dany dzień (wg taryf)zależy od taryf
lms-ksef.phpWysyła faktury do KSeF, odbiera numery KSeFpo generowaniu
lms-ksef.php --status-onlySprawdza statusy i odbiera numery KSeF dla oczekującychco godzinę
lms-sendinvoices.php --ksefWysyła e-mail z PDF do B2B (tylko z numerem KSeF)po wysyłce do KSeF
lms-sendinvoices.php --without-ksefWysyła e-mail z PDF do B2C (osoby fizyczne)po generowaniu
lms-ksef-download.phpPobiera faktury zakupowe od dostawcówcodziennie

Szablony e-mail

/sciezka/do/mail_body_b2b.txt — dla firm (B2B)

Szanowni Państwo,<br>
<br>
w załączniku faktura VAT <b>%invoice</b>.<br>
Numer KSeF: <b>%ksef-number</b><br>
Kwota do zapłaty: <b>%value PLN</b><br>
Numer konta: %bankaccount<br>
<br>
Z poważaniem

/sciezka/do/mail_body_b2c.txt — dla osób fizycznych (B2C)

Szanowny Kliencie,<br>
<br>
w załączniku faktura <b>%invoice</b>.<br>
Kwota do zapłaty: <b>%value PLN</b><br>
Numer konta: %bankaccount<br>
<br>
Dziękujemy!
ZmiennaZnaczenie
%invoiceNumer faktury (np. LMS_001/04/2026)
%ksef-numberNumer KSeF (po potwierdzeniu przez MF)
%valueKwota brutto
%bankaccountNumer konta bankowego
%customeridID klienta w LMS
%pinPIN klienta

Test generowania XML

Przed wysyłką do KSeF warto sprawdzić czy XML faktury wygląda poprawnie. Skrypt gen-xml.php generuje XML bez wysyłki:

cd /twoja/sciezka/lms
php bin/gen-xml.php -C /sciezka/do/lms.ini ID_FAKTURY

# XML zapisywany jest do /tmp/ID_FAKTURY.xml
cat /tmp/ID_FAKTURY.xml

Sprawdź w wygenerowanym XML:

Awarie KSeF i tryb offline

KSeF bywa niedostępny — czy to przez kilka godzin, czy przez kilka dni. Skrypty są zaprojektowane tak, żeby nic nie ginęło i żebyś nie musiał robić niczego ręcznie w większości przypadków.

Scenariusz 1 — KSeF chwilowo nie odpowiada (kilka godzin)

Co się dzieje automatycznie: skrypty przed wysyłką sprawdzają dostępność KSeF przez oficjalny endpoint MF (tzw. Latarnia). Jeśli KSeF nie działa — skrypt kończy pracę z komunikatem i faktury czekają w kolejce.

Co Ty robisz: nic. Po powrocie MF cron automatycznie wyśle wszystkie zaległe faktury i odbierze numery KSeF. Możesz też uruchomić ręcznie:

# Sprawdź ile faktur czeka
php /twoja/sciezka/lms/bin/lms-ksef.php -C /sciezka/do/lms.ini --dry-run

# Wyślij zaległe i odbierz numery KSeF
php /twoja/sciezka/lms/bin/lms-ksef.php -C /sciezka/do/lms.ini

Scenariusz 2 — MF ogłasza oficjalny tryb offline

Czasem Ministerstwo Finansów oficjalnie ogłasza tryb offline — oznacza to, że faktury można wystawiać bez wysyłania ich do KSeF, a po powrocie systemu masz kilka dni na uzupełnienie. W tym czasie:

# Wyślij e-mailem faktury które czekają na numer KSeF (tryb offline MF)
/twoja/sciezka/lms/bin/lms-sendinvoices.php -C /sciezka/do/lms.ini --ksef-offline

Jeśli masz skonfigurowany certyfikat offline — na fakturach PDF pojawi się specjalny kod QR podpisany Twoim kluczem ECDSA, który można zweryfikować bez internetu. Po powrocie KSeF wystarczy uruchomić lms-ksef.php — skrypt sam wyśle zaległe faktury.

ℹ Skąd wiedzieć że MF ogłosiło tryb offline? Oficjalne komunikaty pojawiają się na stronie ksef.podatki.gov.pl. Nasze skrypty sprawdzają dostępność automatycznie przed każdą wysyłką — przez tzw. Latarnię KSeF.

Co to jest Latarnia KSeF?

Latarnia (ang. lighthouse) to oficjalny publiczny endpoint Ministerstwa Finansów, który informuje o aktualnym stanie systemu KSeF. Nie wymaga logowania ani certyfikatu — każdy może go odpytać.

Nasze skrypty pytają Latarnię przed każdą próbą wysyłki faktur. Jeśli KSeF jest niedostępny — skrypt nie próbuje wysyłać, tylko loguje informację i kończy pracę. Faktury czekają bezpiecznie w bazie. Przy kolejnym uruchomieniu crona (lub ręcznym) — procedura powtarza się.

Możliwe stany Latarni:

StatusZnaczenieCo robi skrypt
Available KSeF działa normalnie Wysyła faktury
Planned_Maintenance Planowana przerwa techniczna Przerywa — cron nadrobi później
Unavailable Awaria nieplanowana Przerywa — cron nadrobi później
Error Błąd systemu MF Przerywa — cron nadrobi później
brak odpowiedzi Latarnia sama niedostępna (problem sieciowy) Próbuje wysyłać — jeśli KSeF też nie odpowie, obsługuje błąd normalnie
✓ Nie musisz nic robić przy awarii W każdym scenariuszu awaryjnym faktury są bezpieczne w bazie danych LMS. Skrypt nigdy nie zgubi faktury — albo wysyła, albo zostawia do następnej próby. Jedyna sytuacja wymagająca Twojej interwencji to faktura odrzucona przez KSeF z powodu błędnych danych (status 400).

Scenariusz 3 — Faktura odrzucona przez KSeF (błąd danych)

Jeśli MF odrzuci fakturę (np. brakujący NIP, błędna stawka VAT) — faktura trafia do bazy ze statusem błędu. Nie zniknie i nie zostanie wysłana ponownie automatycznie — musisz to naprawić ręcznie:

# Sprawdź które faktury mają błąd (w bazie danych)
SELECT d.fullnumber, kd.status, kd.statusdescription
FROM ksefdocuments kd
JOIN documents d ON d.id = kd.docid
WHERE kd.status >= 400
ORDER BY d.cdate DESC;

# Po poprawieniu danych klienta/faktury — ponów wysyłkę
php /twoja/sciezka/lms/bin/lms-ksef.php -C /sciezka/do/lms.ini --retry-errors

Tabela typowych problemów

SytuacjaRozwiązanie
Po aktualizacji LMS (kwiecień → sierpień) sidebar działa, środek kręci się w nieskończoność; F12: 404 na jquery-ui.min.js To normalne przy skoku na master 30.08. Szablon woła już js/jqueryui/dist/jquery-ui.min.js, a stary composer zostawił UI 1.12 obok, bez dist/. Z katalogu LMS, potem Ctrl+F5:
composer update components/jqueryui --with-dependencies --no-dev
mkdir -p js/jqueryui/dist
if [ -f vendor/components/jqueryui/dist/jquery-ui.min.js ]; then
  cp -a vendor/components/jqueryui/dist/jquery-ui.min.js js/jqueryui/dist/
else
  ln -sf ../jquery-ui.min.js js/jqueryui/dist/jquery-ui.min.js
fi
Faktury wysłane, brak numerów KSeF (status 100) Poczekaj — cron --status-only odpytuje co godzinę. Albo ręcznie: lms-ksef.php --status-only
Faktury z błędem (status 400) Popraw dane klienta/faktury, potem: lms-ksef.php --retry-errors
Błąd 429 Too Many Requests MF limit zapytań na godzinę. Odczekaj. Do historycznego sync używaj lms-ksef-sync.php
Kwoty w XML z przecinkami (KSeF odrzuca) Brak poprawki P1. Uruchom: python3 bin/patch-netlink.py /twoja/sciezka/lms
Uwaga: smartFormatNumber w KSeF.php od 22.04.2026 nie zastępuje P1 — pola P_13_*/P_14_*/P_15 nadal używają sprintf i są wrażliwe na locale.
Błędna kwota w KSeF (niedopłata) Sprawdź ustawienia oddziału w GUI LMS (ksefconfig.showbalancesummary). Korekty: lms-ksef-bulk-correct.php --dry-run
--retry-errors znowu wysyła faktury, które już mają numer KSeF; w SQL pojawia się nowy wiersz ze status=0 Bug w łączniku NETLink (nie w LMS Chilka), zgłoszony z testów. Stary warunek retry patrzył na dowolny historyczny wiersz z błędem, a nie na to czy dokument już dostał numer. Fix: paczka 30.08.2026 — podmień bin/lms-ksef.php. Nie odpalaj --retry-errors na produkcji na starszej wersji.
Błąd certyfikatu openssl pkcs12 -in /etc/lms/ksef_cert.p12 -info — sprawdź ważność i hasło
Faktury sprzed boundary_date mają "BRAK NUMERU KSEF" na PDF Tabela ksefconfig ma boundarydate=0. Napraw SQL:
MySQL: UPDATE ksefconfig SET boundarydate=UNIX_TIMESTAMP('2026-04-01') WHERE boundarydate=0 OR boundarydate IS NULL;
PostgreSQL: UPDATE ksefconfig SET boundarydate=1775001600 WHERE boundarydate=0 OR boundarydate IS NULL;
Albo wstaw rekord ręcznie (SQL w archiwum zmian → 18.04.2026). Instalator z paczki 30.08 tego nie robi.
Po git pull przestały działać faktury lub KSeF Chilek nadpisał lib/KSeF/KSeF.php — znika setlocale. Nie rób git reset --hard. Po fast-forward: python3 bin/patch-netlink.py /sciezka/lms, wyczyść templates_c, odpal checker. Własnego starego PDF-a nie wgrywaj z powrotem, jeśli nie musisz — oryginał Chilka wystarczy do KSeF.
Starsze problemy (instalator, P2, ramka PDF, plugin v1.x)
SytuacjaRozwiązanie
Plugin wysyła fakturę ale timeout 45s — brak numeru KSeF w GUI, pojawia się dopiero po --status-only Prawie na pewno skok biblioteki n1ebieski/ksef-php-client ze starszej wersji na v1.x. W v1.x kod statusu sesji jest w statusData->status->code, a starsze wersje używały statusData->processingCode. Plugin czyta zawsze null, polling nigdy nie widzi kodu 200 i odpada timeoutem. Faktura i tak trafia do KSeF — tylko numer KSeF i UPO nie wróciły do GUI od razu.
Fix: zainstaluj paczkę 17.04.2026 fix3 lub nowszą — poprawka jest już w KSeFApiService.php.
Instalator wychodzi bez żadnego komunikatu Odpalasz sam plik .sh bez reszty paczki albo z katalogu bin/. Rozpakuj całość i odpal z katalogu zipa:
unzip lms-ksef-netlink-*.zip -d /tmp/ksef-pack
bash /tmp/ksef-pack/lms-npi.sh -c /etc/lms/lms.ini --dry-run
Instalator: configparser.ParsingError lub Brak nazwy bazy Stara wersja instalatora (przed v2026-04-09). Pobierz aktualną paczkę — nowy instalator czyta lms.ini przez grep, odporny na wieloliniowe wartości takie jak mail_body w sekcji [sendinvoices].
Instalator: Nie znaleziono katalogu LMS Skrypt nie widzi katalogu LMS. Sprawdź czy w lms.ini jest:
[directories]
sys_dir = /sciezka/do/lms
check-netlink-patches.sh: BRAK P2 Stary checker z wersji przed 13.04.2026. Wgraj aktualną paczkę — nowy checker sprawdza showbalancesummary
lib/KSeF/KSeF.php lokalnie zmodyfikowany — instalator pomija, P1 nie jest naniesiona To dotyczy starego instalatora (przed 30.08), który pomijał pliki o innym SHA. Npi z paczki 30.08 nadpisuje plugin i skrypty (zostawia .bak), a KSeF.php i PDF i tak nie kopiuje. P1 nakłada patcher:
python3 /twoja/sciezka/lms/bin/patch-netlink.py /twoja/sciezka/lms
bash /twoja/sciezka/lms/bin/check-netlink-patches.sh
Po aktualizacji zniknęła ramka na PDF / wystawca z ini / alias logo To była moja lokalna wariacja szablonu, nie część LMS-a. Jak używasz zwykłego PDF Chilka — nic nie rób, info_box_text = 0 jest w porządku. Jak jednak dogrywałeś mój plik: nie wgrywaj go w całości na nowy master (zginie PDF Chilka z sierpnia). Albo zostań przy oryginale, albo dołóż tylko te kilka bloków na aktualnym pliku.

Co nowego — 30.08.2026

30.08.2026 — master Chilka + poprawka --retry-errors

Chilek dał master z 30.08.2026, baza 2026081100. W środku m.in. poprawka logowania login#admin i kilka zmian bezpieczeństwa z ostatnich miesięcy.

Samo git pull nie wystarczy. Kolejność jest w sekcji Pobierz.

Po aktualizacji LMS pokazuje menu, ale środek strony się nie ładuje

Jeżeli aktualizujesz ze starszego mastera (kwiecień / czerwiec), LMS wstaje, menu jest, a zawartość strony kręci się w kółko.

To nie jest problem z KSeF ani z bazą danych.

W nowszym masterze jQuery UI leży już w js/jqueryui/dist/. U Ciebie po starym composerze pliki mogą nadal siedzieć bezpośrednio w js/jqueryui/.

W F12 będą 404 na plikach jQuery UI. Komendy są w instrukcji aktualizacji i w tabeli problemów wyżej.

Biblioteka KSeF

n1ebieski/ksef-php-client idzie teraz na 1.9.0. Wpis o 1.5.1 zostawiam niżej jako historię.

Po aktualizacji LMS:

composer update n1ebieski/ksef-php-client --with-dependencies --no-dev

Nie odpalaj gołego composer update bez nazwy paczki — pociągnie też inne zależności.

Szablon PDF faktury

W mojej paczce jest zmodyfikowany przeze mnie szablon PDF — m.in. B2B/B2C, issuer i alias logo. Do KSeF2LMS nie jest potrzebny.

Jak masz standardowy szablon Chilka — zostań przy nim. Wgranie mojego starszego PDF-a na aktualnego mastera nadpisze to, co Chilek dodał ostatnio, m.in. drugi adres odbiorcy.

setlocale nakłada patcher, niezależnie od szablonu PDF.

Poprawka lms-ksef.php --retry-errors

Poprawiłem --retry-errors.

Wcześniej skrypt mógł ponownie wysłać fakturę, która numer KSeF już miała, jeśli w bazie wisiał stary wpis błędu przy tym dokumencie.

Przy problemach z sesją błąd ląduje też na dokumentach, nie tylko na sesji. Stary warunek SQL brał taki wpis jako powód do ponownej wysyłki XML-a do MF.

Teraz --retry-errors pomija dokumenty, które mają już numer KSeF.

Jeżeli łącznik już Ci działa i chcesz tylko tę poprawkę — nie ruszaj całego LMS-a. Wystarczy podmienić bin/lms-ksef.php.

Master LMS i ta poprawka --retry-errors to dwie osobne sprawy.

Archiwum zmian — kwiecień–czerwiec 2026

05.06.2026

  • KSeF.php — synchronizacja z master Chilka 05.06.2026: obsługa drugiego adresu odbiorcy (recipient_address_id2) — nowy blok <Podmiot3> #2 w XML dla faktur z dwoma odbiorcami
  • KSeF.php — faktury korygujące: nowe elementy <IDNabywcy> (UUID) w Podmiot2/Podmiot3 oraz blok <Podmiot2K> gdy dane nabywcy różnią się między oryginałem a korektą
  • KSeF.php — bugfix korekt: bezpieczne isset() dla taxest zapobiega błędowi "undefined index" gdy stawka VAT występuje tylko po jednej stronie korekty
  • LMSTcpdfInvoice.php — synchronizacja z master Chilka 05.06.2026: wyświetlanie drugiego adresu odbiorcy na PDF faktury
  • Migracje bazy — 5 nowych migracji: 2026050500 (sessions), 2026050600 (voip_cdr), 2026050700 (odsetki), 2026052500 (kolumny recipient_address_id2/ten2/type2 w assignments i documents), 2026060100 (ksefinvoiceitems: net_price, net_value + backfill)
  • n1ebieski/ksef-php-client — nowa wersja 1.5.1 (poprzednia: 1.1.3); zaktualizuj przez composer update n1ebieski/ksef-php-client

24.04.2026 — 13.05.2026

  • 24.04 — KSeF.php: P_18A bazuje na DOC_FLAG_SPLIT_PAYMENT, latarnia pomijana na test/demo, fix XSD JST
  • 28.04 — konsolidacja ksefconfig, LMSTcpdfInvoice.php sync
  • 30.04 — KSeF.php: TerminOpis dla walut obcych
  • 13.05 — KSeF.php: IDNabywcy/Podmiot2K dla korekt, n1ebieski 1.3.0 (paczki z tego okresu nie były opublikowane)

30.04.2026

  • KSeF.php — faktury walutowe (EUR, USD i inne): do XML generowany jest teraz tag <TerminOpis> z opisem kwoty do zapłaty w PLN, kwoty w walucie i kursu przeliczenia
  • KSeF.php — synchronizacja z master Chilka 30.04.2026

28.04.2026

  • KSeF.php — nowa opcja xmladdallvalues w konfiguracji oddziału (ksefconfig) — generuje w XML wartości <P_11> / <P_11A> dla wszystkich pozycji
  • Baza danych — konsolidacja konfiguracji KSeF: 4 tabele (ksefdelays, ksefallconsumers, ksefboundarydates, ksefshowbalancesummaries) zastąpione jedną tabelą ksefconfig
  • LMSTcpdfInvoice.php — synchronizacja z master Chilka 28.04.2026

24.04.2026

  • KSeF.php — bugfix Chilka: <P_18A> (mechanizm podzielonej płatności) bazuje teraz na fladze DOC_FLAG_SPLIT_PAYMENT zamiast automatycznego progu kwotowego ≥ 15 000 zł
  • Latarnia KSeF — sprawdzanie dostępności pomijane na środowiskach demo i test (Latarnia działa tylko na PROD; wcześniej blokować wysyłkę na środowiskach testowych)
  • JST / Podmiot3 — fix błędu XSD: gdy adres odbiorcy nie ma NIP-u, blok <Podmiot3> nie jest generowany (wcześniej <BrakID>1</BrakID> powodował odrzucenie faktury przez walidator XSD)
  • Nowy instalator lms-npi.sh — zastępuje lms-netlink-install.sh; wykrywa katalog LMS z lms.ini. (Wpis z kwietnia. Aktualny npi z 30.08 kopiuje plugin i skrypty oraz odpala patcher — nie rusza ksefconfig, composera ani upgradedb.)

22.04.2026

  • KSeF.php — smartFormatNumber() dla ilości i cen jednostkowych (merge z master 22.04); poprawka P1 (setlocale) nadal wymagana dla pól sum/VAT
  • KSeF.php — kraj kupującego (KodKraju) pobierany z bazy danych (countries), wcześniej na stałe PL
  • lms-sendinvoices.php — parametr part=auto (merge z master)
  • LMSTcpdfInvoice.php — nowe funkcje faktury PDF:
    • NIP/PESEL klienta (PIN/ID) wyświetlany po lewej stronie (obok danych nabywcy), nie po prawej
    • Logo faktury: obsługa aliasu invoices.logo_path jako zamiennik invoices.header_image
    • Wystawca z konfiguracji: invoices.issuer w lms.ini nadpisuje pole wystawcy z bazy danych
    • Ramka informacyjna KSeF na fakturze: invoices.info_box_text (B2B) i invoices.info_box_text_b2c (B2C) — pojawia się tylko na fakturach po ksef.boundary_date
  • Bugfixy: LMSEventManager, numberplanedit (merge z master)

21.04.2026

  • KSeF.php — smartFormatNumber() z NumberFormatter (merge z master 21.04)

18.04.2026

  • Plugin KSeFSubmit zaktualizowany pod n1ebieski/ksef-php-client v1.x — po aktualizacji biblioteki plugin nie pobierał numeru KSeF ani UPO
  • lms-ksef.php gotowy na PostgreSQL (wcześniej działał tylko z MySQL)
  • Faktura korygująca — dwie poprawki:
    • Plugin GUI nie wysyłał korekty w ogóle — LMS nie przekazywał ID dokumentu do pluginu, plugin widział pustkę i cicho odpuszczał. Teraz plugin sam wyszukuje ID po numerze i dacie faktury.
    • Korekta wysyłana była jako zwykła faktura (dotyczy GUI i lms-ksef.php) — funkcja pobierająca dane faktury nie zwracała typu dokumentu, przez co XML był generowany błędnie. Teraz typ jest dociągany osobno z bazy.
  • lms-ksef.php — domyślne okno skanowania 60 dni wstecz zamiast od 1.04 na zawsze; opcja --from-date=YYYY-MM-DD
  • lms-ksef.php — stan Latarni TOTAL_FAILURE blokuje wysyłkę (wcześniej był ignorowany)
  • lms-ksef.php — czytelniejszy log po filtracji B2B/B2C

Wymagane po instalacji — tabela ksefconfig

Jeśli tabela ksefconfig jest pusta, filtry KSeF w GUI LMS nie działają poprawnie. Wstaw rekord dla każdej dywizji (sprawdź ID w Ustawienia → Oddziały).

MySQL:

INSERT INTO ksefconfig (divisionid, delay, allconsumers, boundarydate, showbalancesummary, xmladdallvalues)
VALUES (1, 0, 0, UNIX_TIMESTAMP('2026-04-01'), 0, 0);

PostgreSQL:

INSERT INTO ksefconfig (divisionid, delay, allconsumers, boundarydate, showbalancesummary, xmladdallvalues)
VALUES (1, 0, 0, EXTRACT(EPOCH FROM TIMESTAMP '2026-04-01')::bigint, 0, 0);

Zmień divisionid = 1 jeśli masz inny ID dywizji. Jeśli masz więcej dywizji — wstaw wiersz dla każdej.

17.04.2026

  • StopkaFaktury, format_ten, bugfixy KSeF.php
  • Plugin KSeFSubmit — przepisany na Batch API, poprawka zawieszania się przy wystawianiu faktury
  • Migracja ksefallconsumers → ksefconfig

Pobieranie plików

Są dwa zipy. Nie pobieraj obu „na wszelki wypadek” i nie odpalaj obu procedur pod spodem — to dwa różne światy.

Aktualna wersja: 30.08.2026 Master Chilka 30.08 (baza 2026081100). git pull nie kończy aktualizacji: composer n1ebieski 1.9.0 + jQuery UI do js/jqueryui/dist/, potem patcher setlocale. PDF z paczki nie wgrywać na nowego Chilka. Osobno: poprawka --retry-errors w bin/lms-ksef.php (wystarczy podmienić ten jeden plik).
Zanim cokolwiek wkleisz Backup katalogu LMS (bez templates_c i cache) oraz dump bazy. W komendach php to ten sam binarny, którym Apache odpala LMS — sprawdź php -m | grep mysqli. Jak pusto, u Ciebie pewnie php81 albo php82, nie systemowe php. Zamiast /sciezka/lms i /etc/lms/lms.ini — swoje ścieżki.

A. Łącznik już masz — tylko poprawka wysyłki

Chodzi o lms-ksef.php --retry-errors: stary skrypt mógł ponownie wysłać do MF fakturę, która numer KSeF już miała. Gita, composera, bazy i PDF nie ruszasz.

cd /tmp
wget -O lms-ksef-netlink-30082026.zip "https://ksef2lms.idzik.pl/?track=lms-ksef-netlink-30082026.zip"
unzip -o lms-ksef-netlink-30082026.zip -d ksef-pack
cp /tmp/ksef-pack/bin/lms-ksef.php /sciezka/lms/bin/lms-ksef.php

B. LMS z gita — aktualizacja Chilka (np. kwiecień/czerwiec → 30.08)

git pull samego kodu nie kończy sprawy. Git nie rusza pluginów ani bin/lms-ksef*.php (ich nie ma u Chilka). Nadpisze lib/KSeF/KSeF.php — znika setlocale, patcher to potem wrzuca z powrotem. Szablon PDF też nadpisze, jeśli wcześniej go ręcznie zmieniałeś; mojego starego PDF-a z paczki na sierpniowy plik Chilka nie wgrywaj.

Nie rób git reset --hard. Nie rób git stash pop ze starym zestawem plików z kwietnia — wrócą Ci stare XML/PDF na nowego Chilka.

1. Kod Chilka. Najpierw git status — jeśli wisi cokolwiek poza KSeF.php, nie wklejaj merge na ślepo. Brudny sam KSeF.php możesz oddać Chilkowi (setlocale wróci w punkcie 3):

cd /sciezka/lms
git status -sb
git fetch origin
git checkout -- lib/KSeF/KSeF.php
git merge --ff-only origin/master

git merge --ff-only ma przejść. Jak odmawia — stop. Są wtedy lokalne commity albo inne brudne pliki. Nie obchodź tego resetem na twardo.

2. Biblioteki. Git nie aktualizuje vendor/ ani JS. Po skoku z kwietnia LMS pokazuje menu, a środek strony kręci kółko — szablon woła jQuery UI z js/jqueryui/dist/, a u Ciebie plik leży obok, bez dist/. To nie jest awaria KSeF. Nie odpalaj gołego composer update bez nazwy paczki (pociągnie pół projektu):

cd /sciezka/lms
composer update n1ebieski/ksef-php-client components/jqueryui --with-dependencies --no-dev
mkdir -p js/jqueryui/dist
if [ -f vendor/components/jqueryui/dist/jquery-ui.min.js ]; then
  cp -a vendor/components/jqueryui/dist/jquery-ui.min.js js/jqueryui/dist/
else
  ln -sf ../jquery-ui.min.js js/jqueryui/dist/jquery-ui.min.js
fi
composer show n1ebieski/ksef-php-client | grep versions
ls -l js/jqueryui/dist/jquery-ui.min.js

3. Setlocale, baza, cache. Patcher musi już leżeć w bin/ (jest w paczce łącznika). php = ten od Apache, jak wyżej:

cd /sciezka/lms
python3 bin/patch-netlink.py /sciezka/lms
php devel/upgradedb.php -C /etc/lms/lms.ini
find templates_c -type f ! -name '.gitkeep' -delete
bash bin/check-netlink-patches.sh

Checker ma zgłosić setlocale i skrypty. Komunikat że brak info_box_text w PDF jest w porządku przy zwykłym szablonie Chilka — to moja ramka na fakturze, nie część łącznika.

4. Pluginu i skryptów jeszcze nie ma? Dopiero wtedy paczka łącznika i lms-npi.sh (Krok 3). To nie zastępuje punktów 1–3 i nie wgrywa PDF.

C. Nie masz gita — aktualizacja LMS z zipa Chilka

To jest dla instalacji, które i tak żyją na ręcznym wgrywaniu zipa. Nie rób tego na katalogu, w którym masz gita — rozjedziesz working tree. rsync nadpisze pliki LMS o tych samych nazwach (w tym KSeF.php i szablon PDF). Pluginów nie skasuje. Zip Chilka nie zawiera vendor/.

1. Pobierz zip Chilka (łącznik tylko jeśli jeszcze go nie masz):

cd /tmp
wget -O lms-master-30082026.zip "https://ksef2lms.idzik.pl/?track=lms-master-30082026.zip"
unzip -q lms-master-30082026.zip -d /tmp/lms-src

2. Wgraj kod Chilka. Zamień ścieżkę. Nie dodawaj --delete:

rsync -a --exclude vendor --exclude templates_c --exclude cache --exclude .git \
  /tmp/lms-src/lms-master/ /sciezka/lms/

3. Jeśli łącznika jeszcze nie było — najpierw Krok 3 (lms-npi.sh). Potem w katalogu LMS:

cd /sciezka/lms
composer update n1ebieski/ksef-php-client components/jqueryui --with-dependencies --no-dev
mkdir -p js/jqueryui/dist
if [ -f vendor/components/jqueryui/dist/jquery-ui.min.js ]; then
  cp -a vendor/components/jqueryui/dist/jquery-ui.min.js js/jqueryui/dist/
else
  ln -sf ../jquery-ui.min.js js/jqueryui/dist/jquery-ui.min.js
fi
python3 bin/patch-netlink.py /sciezka/lms
php devel/upgradedb.php -C /etc/lms/lms.ini
find templates_c -type f ! -name '.gitkeep' -delete
bash bin/check-netlink-patches.sh
▸ Archiwum wcześniejszych wersji (10)
Data LMS-MASTER NETLink pack Zmiany
05.06.2026 lms-master-05062026.zip lms-ksef-netlink-05062026.zip Sync z master Chilka 05.06: drugi adres odbiorcy (Podmiot3 #2, recipient_address_id2), IDNabywcy UUID dla korekt, Podmiot2K, bugfixy korekt taxest. n1ebieski/ksef-php-client 1.5.1.
24.04.2026 lms-master-24042026.zip lms-ksef-netlink-24042026.zip Merge z master 24.04: KSeF.php — P_18A bazuje na DOC_FLAG_SPLIT_PAYMENT (bugfix Chilka). Latarnia pomijana na demo/test. Fix XSD JST: Podmiot3 nie generuje BrakID gdy brak NIP odbiorcy.
22.04.2026 lms-master-22042026.zip lms-ksef-netlink-22042026.zip Merge z master 22.04: KSeF.php — GROUPING_USED=false (fix separatora tysięcy w XML), KodKraju z bazy countries, lms-sendinvoices.php part=auto, bugfixy LMSEventManager/numberplanedit.
21.04.2026 lms-master-21042026.zip lms-ksef-netlink-21042026.zip Merge z master 21.04: smartFormatNumber (KSeF.php) + zachowany setlocale patch P1. lms-master-update-21042026.zip zawiera tylko 12 zmienionych plików (delta, nie pełny master).
18.04.2026 lms-master-18042026.zip lms-ksef-netlink-18042026.zip Faktura korygująca (plugin + bulk). PostgreSQL. lms-ksef.php: --from-date, TOTAL_FAILURE, czystszy log. Instrukcja INSERT ksefconfig.
17.04.2026 lms-master-17042026.zip lms-ksef-netlink-17042026.zip REPO MASTER 17.04. Plugin KSeFSubmit przepisany na Batch API. Migracja ksefallconsumers→ksefconfig.
15.04.2026 lms-master-15042026.zip lms-ksef-netlink-15042026.zip Chilek: bugfix pustego adresu w XML (AdresL1/AdresL2), bugfix INSERT ksefconfig.
14.04.2026 lms-master-14042026.zip lms-ksef-netlink-14042026.zip Chilek: xmladdallvalues (P_11/P_11A), bugfix ksefshowbalancesummary, lms-notify.
13.04.2026 lms-master-13042026.zip lms-ksef-netlink-13042026.zip KSeF.php: nowe Rozliczenie, NazwaBanku. Poprawka fill-xml po 429. P2 wycofana.
11.04.2026 lms-master-11042026.zip lms-ksef-netlink-11042026.zip Pierwsza publiczna wersja. UPO, plugin GUI, saveUpoFile w cron.

Weryfikacja po instalacji i po każdej aktualizacji LMS Uruchom skrypt weryfikujący czy wszystkie poprawki są na miejscu:
cd /twoja/sciezka/lms && bash bin/check-netlink-patches.sh
⚠ Ważne: Kopia zapasowa Backup katalogu LMS i dump bazy — zanim cokolwiek wkleisz. Bez templates_c i cache.

Uwagi lub zapytania dotyczące działania integracji można zgłaszać na adres: cocoban78@gmail.com.

Komentarze

Pytania i uwagi do integracji

Napisz komentarz

Odpowiadasz jako cocoban78

E-mail widoczny tylko dla moderatora

Panel admina

Oczekujace komentarze