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>
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 |
|
|
|
|
|
|
|
|
~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/issuedz filtrami (search/config/mode/date) i paginacja - Snapshot pattern: lokalna kopia
seller/buyer/items_jsonwinvoicesniezalezna 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.jsonzwracal 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)zhttpGet()w MfWhitelistApiClient i zhttpGet()+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_kindipayment_to_kind_days(sama datapayment_towystarcza) - 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: [...]}napole: 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_idz payloadu. Fakturownia uzywa danych konta jako sprzedawca. Lokalny snapshot winvoices.seller_data_jsonzachowany 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)
MfWhitelistApiClientwsrc/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_requestedflaga worderstable — moze byc wykorzystana w przyszlych filtrach/raportachinvoice_pdf.phptemplate 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.createdautomation event (per Phase 113 decision) — wysylka faktury mailem wymaga osobnego mechanizmu seller_data_jsonsnapshot 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