197 lines
9.4 KiB
Markdown
197 lines
9.4 KiB
Markdown
---
|
|
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>
|