update
This commit is contained in:
233
.paul/phases/05-finances-fakturownia-import/05-05-PLAN.md
Normal file
233
.paul/phases/05-finances-fakturownia-import/05-05-PLAN.md
Normal file
@@ -0,0 +1,233 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
|
||||
<objective>
|
||||
## 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).
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## 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
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:decision" gate="blocking">
|
||||
<decision>Model danych dla skip-flag</decision>
|
||||
<context>Jak reprezentowac skip w bazie — nowa kolumna czy konwencja pola juz istniejacego?</context>
|
||||
<options>
|
||||
<option id="new-column">
|
||||
<name>Dodac kolumne `skip TINYINT(1) NOT NULL DEFAULT 0` do fakturownia_item_mappings</name>
|
||||
<pros>Jawne, latwe do query, latwe do indeksowania, mozna miec `finance_category_id` + `skip=1` rownoczesnie (np. "kategoria domyslna ale aktualnie pomijana")</pros>
|
||||
<cons>Wymaga migracji schematu, dodaje kolumne ktora w 99% przypadkow bedzie 0</cons>
|
||||
</option>
|
||||
<option id="null-category">
|
||||
<name>Uzyc `finance_category_id IS NULL` jako sygnalu skip (bez migracji schematu)</name>
|
||||
<pros>Zero migracji, semantyka "mapowanie istnieje ale bez kategorii = skip" jest naturalna, mniejsze ryzyko</pros>
|
||||
<cons>Mniej jawne, trudniej odroznic "skip" od "mapowanie niedokonczone"; kod musi sprawdzac NULL specjalnie</cons>
|
||||
</option>
|
||||
</options>
|
||||
<resume-signal>Select: new-column or null-category</resume-signal>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Rozszerzyc FakturowniaImportRepository o obsluge skip</name>
|
||||
<files>autoload/Domain/Finances/FakturowniaImportRepository.php</files>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>php -l autoload/Domain/Finances/FakturowniaImportRepository.php; php tests/run.php</verify>
|
||||
<done>AC-1 spelnione w warstwie repozytorium</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Dodac obsluge skip w FakturowniaInvoiceImporter</name>
|
||||
<files>autoload/Domain/Finances/FakturowniaInvoiceImporter.php</files>
|
||||
<action>
|
||||
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).
|
||||
</action>
|
||||
<verify>php -l autoload/Domain/Finances/FakturowniaInvoiceImporter.php; php tests/run.php</verify>
|
||||
<done>AC-2, AC-3, AC-5 spelnione</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Przycisk "Pomijaj" i widok pominietych w panelu finances/main_view</name>
|
||||
<files>autoload/controls/Finances.php, templates/finances/main_view.php</files>
|
||||
<action>
|
||||
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).
|
||||
</action>
|
||||
<verify>
|
||||
php -l dla obu plikow.
|
||||
Recznie: otworzyc /finances/main_view/, kliknac "Pomijaj" na pozycji — rekord pojawia sie w sekcji pomijanych.
|
||||
</verify>
|
||||
<done>AC-1, AC-4 spelnione w warstwie UI</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 4: Test regresyjny skip-position</name>
|
||||
<files>tests/Domain/Finances/FakturowniaApiClientTest.php (lub nowy test osobny dla importera, jesli wymaga DB-less mocka)</files>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>php tests/run.php — nowy test zielony.</verify>
|
||||
<done>AC-2, AC-3 pokryte testami</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Mechanizm skip-list dla pozycji faktur Fakturownia.</what-built>
|
||||
<how-to-verify>
|
||||
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.
|
||||
</how-to-verify>
|
||||
<resume-signal>Napisz "approved" lub opisz problem.</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## 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).
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
- [ ] 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
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
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.
|
||||
</output>
|
||||
Reference in New Issue
Block a user