---
phase: 05-finances-fakturownia-import
plan: 04
type: execute
wave: 1
depends_on: []
files_modified:
  - autoload/Domain/Finances/FakturowniaApiClient.php
  - autoload/Domain/Finances/FakturowniaInvoiceImporter.php
  - tests/Domain/Finances/FakturowniaInvoiceImporterTest.php
autonomous: false
delegation: off
---

<objective>
## Goal
Naprawic import faktur kosztowych z Fakturowni tak, by dokumenty typu "invoice" z `income=false` (np. faktura 486639934 pod URL /invoices/486639934) byly niezawodnie pobierane, normalizowane i importowane do finansow.

## Purpose
Zgloszony bug: faktura kosztowa 486639934 nie pojawila sie w finansach po imporcie. Phase 5 dostarczyla mechanizm importu, ale obecna logika `FakturowniaApiClient::fetchCostDocuments()` i `FakturowniaInvoiceImporter::processDocumentType()` ma trzy potencjalne dziury, przez ktore dokumenty kosztowe typu `invoices.json?income=no` moga zostac calkowicie pominiete.

## Output
- Poprawiony `FakturowniaApiClient` zawsze odpytujacy `/invoices.json?income=no` dla kosztow (z `/costs.json` i `/expenses.json` jako uzupelnienie, a nie jako early-exit).
- Poprawiony `FakturowniaInvoiceImporter`: usuniete pressie "wczesnego przerwania" na podstawie daty z listy oraz warunkowe `period=this_month` dla kosztow.
- Test jednostkowy odtwarzajacy brak faktury 486639934 i potwierdzajacy fix.
- UAT: ponowny import zwraca faktura 486639934 do panelu mapowan lub do finansow.
</objective>

<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md

## Prior Work
@.paul/phases/05-finances-fakturownia-import/05-01-SUMMARY.md
@.paul/phases/05-finances-fakturownia-import/05-03-SUMMARY.md

## Source Files
@autoload/Domain/Finances/FakturowniaApiClient.php
@autoload/Domain/Finances/FakturowniaInvoiceImporter.php
@autoload/Domain/Finances/FakturowniaImportRepository.php
@tests/Domain/Finances/FakturowniaInvoiceImporterTest.php
</context>

<acceptance_criteria>

## AC-1: Koszty pobierane zawsze z /invoices.json?income=no
```gherkin
Given token Fakturowni jest skonfigurowany
And faktura kosztowa istnieje pod /invoices/486639934 (income=false)
When importer wywoluje fetchCostDocuments
Then zapytanie do /invoices.json?income=no jest wykonane niezaleznie od odpowiedzi /costs.json i /expenses.json
And lista zwrocona importerowi zawiera rekord 486639934
```

## AC-2: Brak filtra period=this_month dla kosztow
```gherkin
Given FAKTUROWNIA_START_DATE jest pierwszym dniem biezacego miesiaca
When fetchCostDocuments buduje query
Then parametr period nie jest wysylany
And API zwraca takze faktury z wczesniejszych dni miesiaca wg issue_date
```

## AC-3: Importer nie przerywa paginacji na podstawie dat listy
```gherkin
Given pierwsza strona z API zawiera dokumenty posortowane malejaco po updated_at
And wszystkie dokumenty na stronie maja issue_date starsze niz startDate
When processDocumentType przetwarza strone
Then petla kontynuuje do kolejnej strony dopoki API zwraca pelna strone (per_page)
And faktura 486639934 nie jest pomijana wylacznie z powodu sortowania
```

## AC-4: Test regresyjny
```gherkin
Given test z fake'owym api clientem zwracajacym liste dokumentow z jednym kosztem income=false
When FakturowniaInvoiceImporter::import zostaje wywolany
Then dokument trafia do kolejki unmapped lub do finance_operations (nie jest skipped)
```

</acceptance_criteria>

<tasks>

<task type="auto">
  <name>Task 1: Napraw fetchCostDocuments w FakturowniaApiClient</name>
  <files>autoload/Domain/Finances/FakturowniaApiClient.php</files>
  <action>
    Przebuduj fetchCostDocuments tak, by:
    - Zawsze probowal /invoices.json?income=no (to jest primary source — URL faktury to /invoices/ID).
    - Uzupelniajaco probowal /costs.json i /expenses.json i laczyl wyniki (dedup po id), aby nie gubic dokumentow obecnych pod jednym endpointem a nieobecnych pod innym.
    - NIE stosowal period=this_month dla kosztow (usun canUseCurrentMonthPeriod z tej sciezki; sales moze zostac bez zmian albo tez ma byc usuniete — zdecyduj w ramach Task 1 spojnie i udokumentuj w komentarzu).
    - Zachowal softFail dla endpointow pobocznych (/costs.json, /expenses.json zwracajacych 404), ale traktowal 2xx z pusta lista jako "ta sciezka pusta" i szedl dalej zamiast zwracac wynik.
    - W razie gdy wszystkie trzy sciezki zwroca puste listy BEZ bledu HTTP, zwraca [] (nie rzuca wyjatku). Wyjatek tylko gdy wszystkie trzy rzucily bledy.
    Unikaj: dodawania nowych zaleznosci, zmiany sygnatur publicznych metod, ukrywania bledow HTTP.
  </action>
  <verify>
    php -l autoload/Domain/Finances/FakturowniaApiClient.php
    Manualne wywolanie z tokenem testowym: var_dump($client->fetchCostDocuments('2026-04-01', 1)) zawiera faktura 486639934.
  </verify>
  <done>AC-1, AC-2 spelnione</done>
</task>

<task type="auto">
  <name>Task 2: Popraw paginacje i filtr dat w FakturowniaInvoiceImporter</name>
  <files>autoload/Domain/Finances/FakturowniaInvoiceImporter.php</files>
  <action>
    W processDocumentType:
    - Usun wczesne przerywanie petli na podstawie !hasRelevantDateInPage. API moze sortowac po updated_at lub created_at, nie po issue_date — wczesne przerwanie gubi faktury z odroznionym issue_date.
    - Zachowaj twardy limit paginacji ($page > 100) jako safety-valve.
    - Zostaw filtr "za stare" na poziomie processSingleDocument (tam gdzie porownuje $document['date'] z startDate). Ten filtr dziala per-dokument i jest bezpieczny.
    Dodatkowo w normalizeDocument: dla dokumentow bez positions w liscie (gdy API zwraca skrocona forme), upewnij sie ze fallback do apiClient->fetchInvoiceDetails dziala i nie konsumuje zbyt wielu requestow (logika juz jest w resolvePositions — zweryfikuj ze nie regresuje przy naprawie).
    Unikaj: rekurencyjnego pobierania wszystkich stron w nieskonczonosc, cichego polykania wyjatkow API.
  </action>
  <verify>
    php -l autoload/Domain/Finances/FakturowniaInvoiceImporter.php
    php tests/run.php (istniejace testy przechodza).
  </verify>
  <done>AC-3 spelnione</done>
</task>

<task type="auto">
  <name>Task 3: Test regresyjny dla faktury kosztowej typu invoice</name>
  <files>tests/Domain/Finances/FakturowniaInvoiceImporterTest.php</files>
  <action>
    Dodaj test case "cost_invoice_from_invoices_endpoint_is_imported":
    - Zamockuj FakturowniaApiClient (interface lub testowa podklasa) tak, by fetchCostDocuments zwracalo [[
        'id' => 486639934,
        'number' => 'FV 100/04/2026',
        'kind' => 'vat',
        'income' => false,
        'issue_date' => '2026-04-05',
        'seller_name' => 'Dostawca Sp. z o.o.',
        'seller_tax_no' => '5252344567',
        'positions' => [[ 'name' => 'Uslugi IT', 'total_price_net' => 1230.00, 'quantity' => 1 ]]
      ]].
    - Uruchom importer z czystymi tabelami mapowan.
    - Oczekuj: summary['unmapped'] === 1 (bo brak mapowania klienta) oraz wpis w kolejce unmapped dla client_key='tax:5252344567'.
    - Dodatkowy test: z pre-zalozonym mapowaniem klienta i pozycji — oczekuj summary['imported'] === 1 i rekordu w finance_operations z amount = -1230.00.
    Unikaj: zaleznosci od prawdziwego API, pomijania czyszczenia bazy testowej miedzy testami.
  </action>
  <verify>php tests/run.php — oba nowe testy zielone.</verify>
  <done>AC-4 spelnione</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <what-built>Poprawiony import kosztow z Fakturowni + test regresyjny.</what-built>
  <how-to-verify>
    1. Uruchom import recznie: otworz /finances/main_view/ i kliknij "Importuj z Fakturowni" (lub wywolaj cron.php).
    2. Sprawdz komunikat podsumowania — powinno byc co najmniej 1 nowy rekord w kategoriach imported/unmapped.
    3. W bazie: SELECT * FROM fakturownia_import_documents WHERE external_id = '486639934' — rekord istnieje.
    4. Jesli klient niezmapowany: panel mapowan w /finances/main_view/ pokazuje faktura 486639934 w kolejce unmapped.
    5. Po zmapowaniu klienta i pozycji: ponowny import tworzy rekord w finance_operations z ujemna kwota (koszt).
  </how-to-verify>
  <resume-signal>Napisz "approved" lub opisz problem do naprawy.</resume-signal>
</task>

</tasks>

<boundaries>

## DO NOT CHANGE
- FakturowniaImportRepository.php (schemat tabel zablokowany — zmiany tylko jesli niezbedne do poprawki bugu).
- Logika mapowania klientow po NIP (05-02) — nie modyfikuj buildClientKey / normalizeTaxNo.
- Logika filtra proforma (05-03) — isProformaDocument zostaje bez zmian.
- Publiczny kontrakt FakturowniaInvoiceImporter::import() (zwracany shape summary).

## SCOPE LIMITS
- Nie dodajemy nowych endpointow API Fakturowni (tylko naprawiamy uzycie istniejacych).
- Nie zmieniamy UI panelu mapowan w /finances/main_view/.
- Nie wprowadzamy nowego cachingu ani retry-logic — tylko poprawa logiki pobierania/paginacji.
- Nie refaktoryzujemy importera pod testy E2E z prawdziwym API.

</boundaries>

<verification>
- [ ] php -l dla wszystkich zmienionych plikow
- [ ] php tests/run.php — wszystkie testy zielone
- [ ] Manualny import pobieral faktura 486639934 (checkpoint human-verify)
- [ ] Wszystkie AC zaspokojone
</verification>

<success_criteria>
- Faktura 486639934 pojawia sie w wynikach importu (imported lub unmapped, zaleznie od mapowan).
- Nowy test regresyjny przechodzi i zostaje w suite.
- Zadne istniejace testy nie regresuja.
- Brak nowych warningow php -l.
</success_criteria>

<output>
Po zakonczeniu utworz `.paul/phases/05-finances-fakturownia-import/05-04-SUMMARY.md` z:
- opisem zmian w API client i importerze,
- root cause bugu,
- przyklad payloadu z API Fakturowni dla faktury 486639934,
- wynikami UAT.
</output>
