--- phase: 05-finances-fakturownia-import plan: 05 type: execute wave: 2 depends_on: ["05-04"] files_modified: - autoload/Domain/Finances/FakturowniaImportRepository.php - autoload/Domain/Finances/FakturowniaInvoiceImporter.php - autoload/controls/Finances.php - templates/finances/main_view.php - tests/Domain/Finances/FakturowniaApiClientTest.php autonomous: false delegation: off --- ## Goal Umozliwic uzytkownikowi oznaczenie wybranej pozycji faktury z Fakturowni jako "pomijanej" — taka pozycja NIE jest zapisywana do `finance_operations` i nie wplywa na statystyki finansowe, nawet jesli reszta faktury jest prawidlowo zaimportowana. ## Purpose Niektore pozycje faktury (np. koszty transportu przerzucane do klienta, zwrotne opakowania, pozycje techniczne) nie powinny byc ksiegowane jako oddzielne operacje finansowe. Dzisiaj kazda pozycja z mapowaniem trafia do `finance_operations` — nie ma sposobu, zeby pojedyncza pozycja byla widoczna w Fakturowni, ale nie liczona w statystykach CRM. ## Output - Nowa kolumna `skip` w `fakturownia_item_mappings` (lub pole `finance_category_id IS NULL` jako sygnal skip-u — decyzja w Task 1). - Rozszerzony importer: pozycje z mapowaniem typu "skip" sa pomijane na etapie `processSingleDocument`, ale liczone w `skipped_positions` metadanych dokumentu (dla audytu). - Rozszerzony panel `/finances/main_view/` o przycisk "Pomijaj" przy pozycji w kolejce unmapped + widok listy pozycji oznaczonych jako skip z mozliwoscia cofniecia. - Test regresyjny: pozycja oznaczona jako skip nie tworzy `finance_operations`, ale dokument nadal jest oznaczany jako zaimportowany (jesli pozostale pozycje OK) lub jako fully-skipped (jesli wszystkie pozycje skip). ## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md ## Prior Work @.paul/phases/05-finances-fakturownia-import/05-04-PLAN.md ## Source Files @autoload/Domain/Finances/FakturowniaImportRepository.php @autoload/Domain/Finances/FakturowniaInvoiceImporter.php @autoload/controls/Finances.php @templates/finances/main_view.php ## AC-1: Oznaczenie pozycji jako skip ```gherkin Given faktura kosztowa z 3 pozycjami jest w kolejce unmapped (queue_type=item) When uzytkownik klika przycisk "Pomijaj" obok pozycji w panelu /finances/main_view/ Then rekord `fakturownia_item_mappings` zostaje utworzony ze znacznikiem skip And pozycja znika z kolejki unmapped (jest rozwiazana) ``` ## AC-2: Import faktury ze skip-position ```gherkin Given istnieje `fakturownia_item_mappings` dla pozycji "Transport" ze znacznikiem skip And faktura 500000001 ma 3 pozycje: "Produkt A" (kategoria 1), "Produkt B" (kategoria 2), "Transport" (skip) When uruchomiony jest import z Fakturowni Then `finance_operations` dostaje 2 rekordy (Produkt A, Produkt B), NIE 3 And `fakturownia_imported_documents` dla id=500000001 zawiera w `meta_json` liste pominietych pozycji (item_key + name) And summary['imported'] === 1 (dokument zaimportowany jako calosc) ``` ## AC-3: Wszystkie pozycje skip ```gherkin Given faktura 500000002 ma 1 pozycje "Transport" oznaczona jako skip When uruchomiony jest import Then faktura zostaje oznaczona jako zaimportowana (document_key wpisany do fakturownia_imported_documents) And NIE powstaje zaden rekord w finance_operations And summary['imported'] === 1 (zeby nie powtarzala sie co import) ``` ## AC-4: Cofniecie skip ```gherkin Given pozycja "Transport" jest oznaczona jako skip w fakturownia_item_mappings When uzytkownik klika "Cofnij pomijanie" w panelu /finances/main_view/ Then znacznik skip jest usuniety And przy nastepnym imporcie pozycja ponownie trafia do kolejki unmapped (bo nie ma finance_category_id) ``` ## AC-5: Idempotencja ```gherkin Given faktura ze skip-position zostala juz raz zaimportowana When uruchomiony jest import po raz drugi (bez zmian w danych) Then summary['skipped'] === 1 dla tej faktury (isDocumentImported=true) And nie powstaja duplikaty finance_operations ``` Model danych dla skip-flag Jak reprezentowac skip w bazie — nowa kolumna czy konwencja pola juz istniejacego? Select: new-column or null-category Task 1: Rozszerzyc FakturowniaImportRepository o obsluge skip autoload/Domain/Finances/FakturowniaImportRepository.php W zaleznosci od wyboru z checkpointu: - option new-column: dodac migracja w `ensureTables()` — ALTER TABLE fakturownia_item_mappings ADD COLUMN IF NOT EXISTS skip TINYINT(1) NOT NULL DEFAULT 0. Dodac metode `markItemAsSkipped($itemKey, $itemName)` wstawiajaca rekord ze skip=1 i finance_category_id=NULL. Dodac `isItemSkipped($itemKey)` zwracajaca bool. - option null-category: dodac metode `markItemAsSkipped($itemKey, $itemName)` wstawiajaca rekord z finance_category_id=NULL. `getItemMapping` juz zwraca rekord; trzeba dodac helper `isSkipMapping($row)` sprawdzajacy czy finance_category_id jest null. Zmodyfikowac istniejaca metode `getItemMapping` tak, zeby kod konsumujacy mogl odroznic: a) brak mapowania (unmapped), b) skip, c) normalne mapowanie z kategoria. Unikaj: breaking changes w publicznym kontrakcie, zmian w logice mapowan klientow. php -l autoload/Domain/Finances/FakturowniaImportRepository.php; php tests/run.php AC-1 spelnione w warstwie repozytorium Task 2: Dodac obsluge skip w FakturowniaInvoiceImporter autoload/Domain/Finances/FakturowniaInvoiceImporter.php W `processSingleDocument`, pomiedzy resolveItemMapping a insertem do finance_operations: - Dla kazdej pozycji sprawdzic czy `itemMap` jest skip (nowa kolumna LUB null category, zaleznie od decyzji). - Jesli skip: NIE dodawac pozycji do `resolvedPositions`, ale dodac do lokalnej listy `skippedPositions` (dla meta_json). - Jesli po filtrze pozostaje 0 pozycji `resolvedPositions` ALE byly `skippedPositions` → traktowac dokument jako imported (zapisac w fakturownia_imported_documents z pusta lista operation_ids i meta_json zawierajacym skipped_positions). AC-3. - Jesli pozostala co najmniej 1 pozycja → zapis jak dotychczas, ale meta_json dodatkowo zawiera `skipped_positions` dla audytu. Unikaj: zmiany semantyki summary['skipped'] (oznacza "pominiete dokumenty", nie "pominiete pozycje" — dodaj osobny kontener meta). php -l autoload/Domain/Finances/FakturowniaInvoiceImporter.php; php tests/run.php AC-2, AC-3, AC-5 spelnione Task 3: Przycisk "Pomijaj" i widok pominietych w panelu finances/main_view autoload/controls/Finances.php, templates/finances/main_view.php W `controls\Finances`: - Dodac akcje `fakturownia_skip_item($item_key, $item_name)` wywolujaca repo->markItemAsSkipped + oznaczajaca rekord w kolejce unmapped jako resolved. - Dodac akcje `fakturownia_unskip_item($item_key)` usuwajaca skip-mapping (lub ustawiajaca skip=0 w wariancie new-column). - Dodac widok `fakturownia_skipped_items_list()` zwracajacy tablice aktywnych skip-mapowan. W `templates/finances/main_view.php`: - W sekcji unmapped queue, przy kazdej pozycji (queue_type=item) dodac przycisk "Pomijaj w imporcie" obok istniejacego selecta kategorii. - Dodac osobna sekcje "Pozycje pomijane" pokazujaca liste z przyciskiem "Cofnij pomijanie". - UI w jezyku polskim, spojne z obecnym stylem Bootstrap. Unikaj: duplikowania logiki mapowania, zmian w mapowaniach klientow (client queue pozostaje bez zmian). php -l dla obu plikow. Recznie: otworzyc /finances/main_view/, kliknac "Pomijaj" na pozycji — rekord pojawia sie w sekcji pomijanych. AC-1, AC-4 spelnione w warstwie UI Task 4: Test regresyjny skip-position tests/Domain/Finances/FakturowniaApiClientTest.php (lub nowy test osobny dla importera, jesli wymaga DB-less mocka) Dodac test case `skip_position_is_excluded_from_finance_operations`: - Stworzyc in-memory mock FakturowniaImportRepository (stub) implementujacy getItemMapping zwracajacy skip-mapping dla "Transport". - Stworzyc mock FakturowniaApiClient z faktura zawierajaca 3 pozycje: Produkt A, Produkt B, Transport. - Uruchomic FakturowniaInvoiceImporter z tymi mockami (dependency injection przez setter lub konstruktor — moze wymagac drobnego refactoru importera, zeby mozna bylo wstrzyknac repo). - Oczekiwac: 2 pozycje w resolvedPositions (A, B), 1 w skippedPositions (Transport), summary['imported'] === 1. Dodatkowy test: wszystkie pozycje skip → imported=1, 0 finance_operations. Unikaj: zaleznosci od prawdziwej bazy; mockowanie tylko tego, co niezbedne. php tests/run.php — nowy test zielony. AC-2, AC-3 pokryte testami Mechanizm skip-list dla pozycji faktur Fakturownia. 1. Otworz /finances/main_view/ — zobacz kolejke unmapped. 2. Przy dowolnej pozycji (np. "Transport") kliknij "Pomijaj w imporcie". 3. Pozycja znika z kolejki i pojawia sie w sekcji "Pozycje pomijane". 4. Uruchom ponownie import. Faktura ktora miala te pozycje importuje sie BEZ niej (sprawdz `finance_operations` po `description LIKE '%Transport%'` — brak). 5. Kliknij "Cofnij pomijanie" — pozycja wraca do kolejki unmapped przy nastepnym imporcie. Napisz "approved" lub opisz problem. ## DO NOT CHANGE - Logika filtra proforma (05-03). - Logika mapowania klientow po NIP (05-02). - Fix KSeF `taxNoEqualsOwn` (05-04). - `FakturowniaApiClient` (sciezki API) — skip jest czysta logika po stronie importera. - Publiczny kontrakt `FakturowniaInvoiceImporter::import()` (zwracany shape summary). ## SCOPE LIMITS - Nie dodawac skip-list dla klientow (tylko pozycje). - Nie dodawac masowego oznaczania (pojedyncze pozycje tylko). - Nie ruszac eksportu/raportow finansowych — skip dziala na etapie importu, nie przy wyswietlaniu. - Nie dodawac historii zmian skip-list (simple on/off wystarczy). - [ ] php -l dla wszystkich zmienionych plikow - [ ] php tests/run.php — wszystkie testy zielone - [ ] Manualny flow: oznacz pozycje jako skip, uruchom import, sprawdz brak w finance_operations - [ ] Cofniecie skip dziala i pozycja wraca do kolejki - [ ] Wszystkie AC zaspokojone - Uzytkownik moze pojedynczym klikiem pominac pozycje w imporcie. - Pominiete pozycje nie wplywaja na statystyki finansowe. - Skip jest odwracalny. - Audit trail: meta_json zaimportowanej faktury zawiera liste pominietych pozycji. - Zaden istniejacy test nie regresuje. Po zakonczeniu utworz `.paul/phases/05-finances-fakturownia-import/05-05-SUMMARY.md` z: - wybranym wariantem modelu danych (new-column vs null-category) i uzasadnieniem, - opisem zmian w UI, - screenshotami lub krotkim opisem flow "oznacz → import → odtworz", - wynikami UAT.