Files
orderPRO/.paul/phases/115-invoice-from-order/115-01-SUMMARY.md
Jacek Pyziak 33ee1a1cf5 feat(115): wystawianie faktury z zamowienia (lokalne + delegowane Fakturownia)
Phase 115 complete (vertical slice "zamowienie z NIP -> faktura PDF"):
- Task 1: InvoiceRepository + InvoiceService (dual-flow orchestrator) +
  InvoiceIssueException + FakturowniaApiClient::createInvoice + buildPdfUrl
- Task 2: InvoiceController + OrdersController::toggleInvoiceRequested +
  OrdersRepository::setInvoiceRequested + auto-import invoice_requested z
  Allegro (invoice.required) i shopPRO (5-key flexible parser) + show.php
  (toggle w zakladce Platnosci + warunkowy przycisk Wystaw fakture)
- Task 3: Lista wystawionych /settings/accounting/invoices/issued z filtrami
  + invoice_preview + invoice_pdf Dompdf template + hub link
- Task 3b (dodany): NIP lookup przez MF Biala Lista (publiczne API, bez
  rejestracji) — MfWhitelistApiClient w src/Core/Http/ + /api/nip/lookup +
  przycisk "Pobierz z GUS" w formularzu

Auto-fixes podczas smoke testu (5):
- GUS endpoint Fakturowni nie istnial (HTML 404 -> "json is not valid");
  switch na MF Biala Liste
- PHP 8.5 curl_close() deprecation wycieka HTML przed JSON; usuniete z
  MfWhitelistApiClient i FakturowniaApiClient (3 miejsca)
- Fakturownia 422 payment_to_kind_days (nieistniejace pole) -> usuniete
- Generic "error" w 422 -> parser plaskuje errors: {pole: [...]} +
  error_log z 1000 znakow raw body
- Fakturownia security odrzuca seller_*/department_id jako "create new
  department"; usuniete z payloadu (Fakturownia uzywa danych konta)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:34:50 +02:00

16 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, duration, started, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established duration started completed
115-invoice-from-order 01 accounting
fakturownia
invoice
dompdf
nip-lookup
mf-whitelist
vat
phase provides
113-fakturownia-integration
tabele invoices/invoice_configs/invoice_number_counters/fakturownia_integration_settings
FakturowniaApiClient stub
IntegrationsRepository::updateTestResult
phase provides
114-accounting-configs-refactor
InvoiceConfigRepository CRUD
hub `/settings/accounting`
seed Domyslny VAT config
Wystawianie faktury z zamowienia (lokalne + delegowane przez Fakturownie)
Toggle orders.invoice_requested w UI + auto-import flagi z Allegro/shopPRO
Auto-fill nabywcy z NIP przez MF Biala Lista (publiczny endpoint)
Lista wystawionych faktur z filtrami + podglad/PDF dual-path (Dompdf lokalny / redirect 302 Fakturownia)
v3.7 next phases
statistics_orders (jezeli kiedys liczy faktury)
automation (jezeli kiedys invoice.created event)
added patterns
MfWhitelistApiClient (Core/Http)
Dual-mode invoice issuance: lokalna numeracja (atomowy counter) lub delegacja do API z importem rezultatu
Snapshot pattern: seller/buyer/items zamrozone jako JSON niezaleznie od trybu wystawienia
Public NIP lookup przez MF Biala Lista — bez rejestracji/klucza
created modified
src/Modules/Accounting/InvoiceRepository.php
src/Modules/Accounting/InvoiceService.php
src/Modules/Accounting/InvoiceController.php
src/Modules/Accounting/InvoiceIssueException.php
src/Core/Http/MfWhitelistApiClient.php
resources/views/accounting/invoice_form.php
resources/views/accounting/invoice_preview.php
resources/views/accounting/invoice_pdf.php
resources/views/accounting/invoices_issued_list.php
public/assets/js/modules/invoice-requested-toggle.js
src/Modules/Settings/FakturowniaApiClient.php
src/Modules/Orders/OrdersController.php
src/Modules/Orders/OrdersRepository.php
src/Modules/Settings/AllegroOrderImportService.php
src/Modules/Settings/ShopproOrdersSyncService.php
resources/views/orders/show.php
resources/views/layouts/app.php
resources/views/settings/accounting.php
routes/web.php
GUS lookup: switch z (nieistniejacego) Fakturownia endpoint na MF Biala Lista — public API, bez rejestracji
Fakturownia payload: NIE wysylamy seller_* — security konta interpretuje jako 'utworz nowy dzial' i odrzuca
Delegated invoice: POST do Fakturowni PRZED INSERT lokalnym; on success zapis external_invoice_id+pdf_url+numer Fakturowni
PHP 8.5: usuniete wszystkie curl_close() (deprecated, wycieka HTML do JSON response)
invoice_requested auto-set tylko przy created=true; manualny toggle nie nadpisywany przez delta-only re-import
PDF dual-path: lokalna -> Dompdf inline; delegowana -> redirect 302 do external_pdf_url (brak cache)
Confirm dialogs: OrderProAlerts.confirm options-object API ({title, message, onConfirm, danger}) — kontynuacja Phase 114
AJAX endpoint zwracajacy JSON: zawsze try/catch Throwable na koncu controller method, mapowanie do {success, error}
Vanilla JS modul z idempotent data-bound guard (jak invoice-requested-toggle.js)
~4h 2026-05-10T~14:00:00Z 2026-05-10T~18:00:00Z

Phase 115 Plan 01: Wystawianie faktury z zamowienia — Summary

Vertical slice "zamowienie z NIP → faktura PDF" dziala end-to-end w trybach lokalnym (Dompdf + lokalna numeracja z invoice_number_counters) i delegowanym (POST do Fakturowni + redirect 302 do natywnego PDF). NIP rozwijany przez MF Biala Liste. Phase 115 zamyka v3.7 milestone w wymiarze "wystawianie".

Performance

Metric Value
Duration ~4h (planowanie+implementacja+smoke+5 iteracji bugfix)
Tasks 4 zaplanowane + 1 dodany w trakcie (Task 3b: NIP lookup)
Files created 10
Files modified 9
New DB migrations 0 (Phase 113 wystarczyl)

Acceptance Criteria Results

Criterion Status Notes
AC-1: Flaga invoice_requested ustawia widocznosc przycisku Pass Toggle dziala AJAX-em, log w order_activity_log; auto-set z Allegro invoice.required; shopPRO heuristic dla 5 znanych kluczy payloadu
AC-2: Wystawienie faktury — tryb lokalny Pass Numer z invoice_number_counters (atomowy), snapshoty zamrozone, Dompdf renderuje fakture VAT
AC-3: Wystawienie faktury — tryb delegowany (Fakturownia) Pass Smoke test approved przez usera: POST przed INSERT, sukces -> external_invoice_id zapisane, blad -> brak wiersza w invoices + czytelny komunikat
AC-4: Lista, podglad i PDF Pass /settings/accounting/invoices/issued z filtrami; PDF lokalny przez Dompdf, delegowany przez redirect 302 do external_pdf_url

Accomplishments

  • Operator widzi przycisk "Wystaw fakture" tylko gdy invoice_requested=1 (auto z importu lub manualnie z zakladki Platnosci)
  • Formularz prefilled NIP-em z payload_json zamowienia + "Pobierz z GUS" wypelnia 4 pola firmy z MF Biala Lista jednym klikiem
  • Dual flow działa: lokalne faktury z Dompdf PDF, delegowane z natywnym PDF Fakturowni — operator wybiera config, reszta automatyczna
  • Lista /settings/accounting/invoices/issued z filtrami (search/config/mode/date) i paginacja
  • Snapshot pattern: lokalna kopia seller/buyer/items_json w invoices niezalezna od trybu i przyszlych zmian zrodla
  • 5 bug-fixow iteracyjnych w trakcie smoke testu — caly pipeline error reporting/parser uodporniony na PHP 8.5 i edge cases Fakturowni

Files Created/Modified

File Change Purpose
src/Modules/Accounting/InvoiceRepository.php Created CRUD + atomowy nextLocalNumber + paginate z filtrami
src/Modules/Accounting/InvoiceService.php Created Orchestrator dual-flow (lokalny/delegowany) + buildFakturowniaPayload + extractBuyerTaxNumber
src/Modules/Accounting/InvoiceController.php Created Endpointy create/store/show/pdf/issuedList/nipLookup
src/Modules/Accounting/InvoiceIssueException.php Created Typed exception dla bledow biznesowych
src/Core/Http/MfWhitelistApiClient.php Created Klient MF Biala Lista — public API, parser adresu
src/Modules/Settings/FakturowniaApiClient.php Modified createInvoice() (POST /invoices.json), buildPdfUrl(); usuniete curl_close (PHP 8.5); flatten errors parser
src/Modules/Orders/OrdersController.php Modified toggleInvoiceRequested AJAX + invoices/invoiceConfigs do view + 2 optional ctor params
src/Modules/Orders/OrdersRepository.php Modified setInvoiceRequested()
src/Modules/Settings/AllegroOrderImportService.php Modified Auto-set invoice_requested z payload.invoice.required przy created=true
src/Modules/Settings/ShopproOrdersSyncService.php Modified shouldRequestInvoice() flexible parser dla 5 znanych kluczy payloadu
resources/views/accounting/invoice_form.php Created Form wystawiania + "Pobierz z GUS" + confirm dialog dla duplikatu
resources/views/accounting/invoice_preview.php Created HTML preview faktury z badgem Tryb
resources/views/accounting/invoice_pdf.php Created Dompdf template "FAKTURA VAT"
resources/views/accounting/invoices_issued_list.php Created Lista wystawionych z filtrami + paginacja
resources/views/orders/show.php Modified Toggle w zakladce Platnosci + warunkowy przycisk + sekcja Faktury w tabie Documents
resources/views/layouts/app.php Modified Rejestracja invoice-requested-toggle.js
resources/views/settings/accounting.php Modified Link "Faktury wystawione" w karcie Faktury
public/assets/js/modules/invoice-requested-toggle.js Created Vanilla JS AJAX toggle z idempotent bound guard
routes/web.php Modified 6 nowych routes + DI wiring InvoiceService/InvoiceController/InvoiceRepository

Decisions Made

Decision Rationale Impact
Switch GUS lookup na MF Biala Liste Fakturownia API NIE MA endpointu GUS — bylo wrong assumption. MF Biala Lista publiczna, bez rejestracji, zwraca te same dane (nazwa+adres) /api/nip/lookup zamiast /api/fakturownia/gus-lookup; usuniete deps na Fakturownia client w lookupie
Nie wysylamy seller_* do Fakturowni Konta Fakturowni z podwyzszonym security interpretuja roznice w seller_bank_account jako proba "utworz nowy dzial" -> HTTP 422 Fakturownia uzywa danych konta jako sprzedawca; lokalny snapshot w invoices.seller_data_json zachowany dla audytu
Delegated: POST przed INSERT lokalnym Gwarancja braku orphan rows w invoices gdy API padnie Trade-off: brak idempotencji przy double-POST -> INVOICE-IDEMP-115 w todo.md
PHP 8.5: usuniete wszystkie curl_close() Deprecated od 8.5 (no-op od 8.0), wycieka HTML
Deprecated...
przed JSON -> "json is not valid" w fetch().json()
Dwa pliki: MfWhitelistApiClient + FakturowniaApiClient (2 miejsca). ShopproIntegrationsRepository ma to samo (poza zakresem 115)
Pominiete department_id w payloadzie Konta Fakturowni z security level blokuja samo wystepowanie pola jako "create new department" attempt Fakturownia uzywa domyslnego dzialu konta
Auto-set invoice_requested tylko przy created=true Delta-only re-import (Phase 112) zachowuje stabilnosc manualnej flagi operatora Manualny toggle nie zostaje nadpisany przez re-import

Deviations from Plan

Summary

Type Count Impact
Auto-fixed 5 Wszystkie podczas smoke testu - essential, no scope creep
Scope additions 1 Task 3b (GUS lookup) dodany po user request, potem refactor na MF
Deferred 2 INVOICE-IDEMP-115 (idempotencja Fakturowni), curl_close w ShopproIntegrationsRepository

Total impact: Essential bugfixes podczas smoke testu (rzeczywiste zachowanie Fakturowni rozne od dokumentacji), plus 1 zaakceptowane scope-extension (NIP lookup z prefillem zamiast manualnego). Bez scope creep.

Auto-fixed Issues

1. [GUS] FakturowniaApiClient::lookupClientByNip wolal nieistniejacy endpoint

  • Found during: Task 3b smoke (klik "Pobierz z GUS")
  • Issue: URL /clients/gus.json zwracal HTML 404, json_decode null
  • Fix: Switch na publiczne MF Biala Lista API (https://wl-api.mf.gov.pl/api/search/nip/{nip})
  • Files: Created src/Core/Http/MfWhitelistApiClient.php, removed lookupClientByNip from FakturowniaApiClient
  • Verification: Live test z NIP CD Projekt (7342867148) zwraca poprawne dane firmy

2. [PHP 8.5] curl_close() wycieka HTML deprecation przed JSON response

  • Found during: Pierwszy smoke test "Pobierz z GUS"
  • Issue: <br/><b>Deprecated</b>: curl_close() in...</b> pojawialo sie przed JSON -> fetch().json() throws
  • Fix: Usuniete curl_close($ch) z httpGet() w MfWhitelistApiClient i z httpGet() + httpPostJson() w FakturowniaApiClient
  • Files: src/Core/Http/MfWhitelistApiClient.php, src/Modules/Settings/FakturowniaApiClient.php
  • Verification: Po fixie response zaczyna sie czystym JSON, fetch parsuje OK

3. [Fakturownia] payload payment_to_kind_days nie istnieje w API

  • Found during: Smoke wystawienia delegowanej faktury
  • Issue: HTTP 422 "Nieprawidlowy atrybut: 'payment_to_kind_days'"
  • Fix: Usuniete payment_to_kind i payment_to_kind_days (sama data payment_to wystarcza)
  • Files: src/Modules/Accounting/InvoiceService.php (buildFakturowniaPayload)
  • Verification: Drugi POST nie wraca z tym bledem

4. [Fakturownia] generyczny error bez kontekstu w parserze bledow

  • Found during: Smoke iteracja 2 wystawienia delegowanej
  • Issue: HTTP 422: error — bez konkretow co odrzucone
  • Fix: Parser plaskuje errors: {pole: [...]} na pole: msg1, msg2; pole2: msg3, fallback do raw body, error_log() z 1000 znakow body
  • Files: src/Modules/Settings/FakturowniaApiClient.php (resolveErrorMessage + flattenFieldErrors)
  • Verification: Kolejny blad pokazal: {"department":["Poziom zabezpieczenia ... nie pozwala na utworzenie dzialu"]}

5. [Fakturownia] seller_ fields triggeruja "create new department" przy security level*

  • Found during: Smoke iteracja 3 wystawienia delegowanej
  • Issue: HTTP 422 "Poziom zabezpieczenia przed zmiana konta bankowego nie pozwala na utworzenie dzialu" — Fakturownia probowala auto-utworzyc dzial gdy seller_* roznia sie od konta
  • Fix: Usuniete VSZYSTKIE pola seller_* + department_id z payloadu. Fakturownia uzywa danych konta jako sprzedawca. Lokalny snapshot w invoices.seller_data_json zachowany dla audytu
  • Files: src/Modules/Accounting/InvoiceService.php (buildFakturowniaPayload)
  • Verification: Iteracja 4 — wystawienie sie powiodlo, user potwierdzil "Działa."

Scope Additions

1. Task 3b: GUS/NIP lookup (post-Task 3 implementacja)

  • Reason: User po smoke teście Task 1-3 zadal pytanie czy Fakturownia API wspiera pobieranie danych z NIP. Odpowiedz: nie bezposrednio. User zatwierdzil dodanie feature poprzez Fakturownia GUS endpoint (okazalo sie nie istnieje), nastepnie zaakceptowal switch na MF Biala Liste
  • Scope: 1 nowy client API (MfWhitelistApiClient), 1 endpoint controller method (nipLookup), 1 route, 1 przycisk + inline JS w invoice_form.php
  • Impact: Operator nie musi recznie wpisywac danych firmy po wpisaniu NIP

Deferred Items

  • INVOICE-IDEMP-115: Idempotencja podwojnego POST do Fakturowni (gdy response sie nie zwroci, ale faktura w Fakturowni juz powstala) — .paul/codebase/todo.md
  • curl_close() w ShopproIntegrationsRepository.php: Ten sam bug klasy co fix #2, ale poza zakresem Phase 115. Sugerowane jako mini-fix lub Phase 116

Issues Encountered

Issue Resolution
Bootstrap loads but DI wiring fails offline (DB connection refused) Akceptowalne - XAMPP offline; weryfikacja przez php -l + autoload + reflection, smoke test przez usera na zywej bazie
Receipt-create.php uzywa positional OrderProAlerts.confirm(string, callback) (legacy bug Phase 114) Invoice form uzywa options-object API zgodnie z memory; receipt-create do osobnej fazy
5 iteracji bug-fix podczas smoke testu Wszystkie potraktowane jako auto-fix, dokumentowane w deviations, pipeline zostal odporniejszy

Skill Audit

Expected Invoked Notes
(brak required skills w plan frontmatter) n/a Plan nie zglaszal SPECIAL-FLOWS requirements

Next Phase Readiness

Ready:

  • Phase 115 zamknieta — wystawianie faktur dziala end-to-end (lokalne + delegowane)
  • MfWhitelistApiClient w src/Core/Http/ dostepny dla innych modulow (np. weryfikacja NIP w order_addresses, statusVat check przed wystawieniem)
  • Pattern dual-flow (lokalny/delegowany) jako wzorzec dla przyszlych integracji ksiegowych
  • invoice_requested flaga w orders table — moze byc wykorzystana w przyszlych filtrach/raportach
  • invoice_pdf.php template gotowy do dalszej stylizacji (logo, podpisy itp.) bez zmian backendu

Concerns:

  • INVOICE-IDEMP-115 — przy braku idempotencji, operator musi recznie weryfikowac w panelu Fakturowni przy bledach sieci
  • curl_close() w ShopproIntegrationsRepository pozostal — ten sam bug klasy uderzy w shopPRO API calls na PHP 8.5 produkcji
  • Brak invoice.created automation event (per Phase 113 decision) — wysylka faktury mailem wymaga osobnego mechanizmu
  • seller_data_json snapshot w lokalnej DB moze odbiegac od danych faktury w Fakturowni (Fakturownia uzywa wlasnego konta) — niespojnosc tylko dla audytu, klient widzi PDF z Fakturowni

Blockers:

  • None — Phase 115 closed, ready for Phase 116 lub kandydatow z roadmapu (lista faktur juz dolaczona, automation event invoice.created jako kandydat, INVOICE-IDEMP-115 jako tech-debt phase)

Phase: 115-invoice-from-order, Plan: 01 Completed: 2026-05-10