diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index 216a033..e43c508 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -11,7 +11,7 @@ Użytkownicy mogą efektywnie zarządzać projektami, zadaniami i klientami w je | Metric | Value | Source | |--------|-------|--------| | Version | v0.1.0 (in progress) | Milestone | -| Status | Phase 1 complete, Phase 2 next | STATE.md | +| Status | Phase 1 and Phase 5 complete, Phase 2 next | STATE.md | | Lines of Code | 9 356 | SonarQube baseline | | Bugs | 58 | SonarQube baseline | | Code Smells | 1 649 | SonarQube baseline | @@ -24,7 +24,7 @@ Użytkownicy mogą efektywnie zarządzać projektami, zadaniami i klientami w je ### Validated - ✓ SonarQube baseline — Phase 1: projekt skonfigurowany, skan wykonany, metryki udokumentowane -- ✓ Import finansow z Fakturowni — Phase 5: automatyczny import faktur przychodowych/kosztowych, mapowanie po NIP, filtr proforma +- ✓ Import finansow z Fakturowni — Phase 5: automatyczny import faktur przychodowych/kosztowych, mapowanie po NIP, filtr proforma, skip-list pozycji, edycja dopasowania kategorii ### Active - [ ] Naprawić 58 bugów (priorytet: 3 CRITICAL, 35 MAJOR) @@ -47,6 +47,8 @@ Użytkownicy mogą efektywnie zarządzać projektami, zadaniami i klientami w je | Skan automatyczny CLI + MCP | Phase 1 | Nie wymaga ręcznego uruchomienia | | Mapowanie klientow po NIP (tax:) z fallback na id: | Phase 5 | Kompatybilnosc z historycznymi mapowaniami | | Filtr proforma: kind + prefiks FP | Phase 5 | Proformy nie trafiaja do finansow | +| Edycja dopasowania Fakturownia tylko dla operacji importowanych | Phase 5 | Reczne operacje pozostaja przy standardowej edycji kategorii | +| Zmiana dopasowania moze masowo przepiac pasujace operacje | Phase 5 | Szybka korekta blednej kategorii dla powtarzalnych pozycji faktur | ## Success Criteria - Użytkownicy mogą efektywnie zarządzać projektami, zadaniami i klientami w jednym systemie CRM @@ -56,4 +58,5 @@ Użytkownicy mogą efektywnie zarządzać projektami, zadaniami i klientami w je --- *Created: 2026-03-15* -*Last updated: 2026-04-11 after Phase 5* +*Last updated: 2026-05-04 after Phase 5* + diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index f7d6db4..23ea3b3 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -1,4 +1,4 @@ -# Roadmap: crmPRO +# Roadmap: crmPRO ## Overview Stabilizacja i poprawa jakosci kodu crmPRO oraz rozwoj finansow o automatyczny import faktur z Fakturowni. @@ -6,7 +6,7 @@ Stabilizacja i poprawa jakosci kodu crmPRO oraz rozwoj finansow o automatyczny i ## Current Milestone **v0.1 Stabilizacja i jakosc kodu + import finansow** (v0.1.0) Status: In progress -Phases: 3 of 5 complete +Phases: 2 of 5 complete ## Phases @@ -16,7 +16,7 @@ Phases: 3 of 5 complete | 2 | Naprawa bledow krytycznych | TBD | Next | - | | 3 | Naprawa bledow glownych | TBD | Not started | - | | 4 | Poprawa pokrycia testami | TBD | Not started | - | -| 5 | Import finansow z Fakturowni | 4/5 | In progress (05-05 planned) | 2026-04-14 | +| 5 | Import finansow z Fakturowni | 6/6 | Complete | 2026-05-04 | ## Phase Details @@ -91,10 +91,15 @@ Phases: 3 of 5 complete **Plans:** - [x] 05-01: Integracja Fakturownia i automatyczny import do finansow - [x] 05-02: Mapowanie klienta po NIP + kompatybilnosc mapowan historycznych -- [x] 05-03: Filtr proforma — pomijanie faktur proforma (FP*) w imporcie -- [x] 05-04: Bugfix — brak faktur kosztowych z /invoices.json?income=no (np. 486639934) + obsluga wydatkow z KSeF (odwrocone role seller/buyer) -- [ ] 05-05: Skip-list pozycji — mozliwosc oznaczenia wybranej pozycji faktury jako pomijanej (nie trafia do finance_operations) +- [x] 05-03: Filtr proforma pomijanie faktur proforma (FP*) w imporcie +- [x] 05-04: Bugfix brak faktur kosztowych z /invoices.json?income=no (np. 486639934) + obsluga wydatkow z KSeF (odwrocone role seller/buyer) +- [x] 05-05: Skip-list pozycji mozliwosc oznaczenia wybranej pozycji faktury jako pomijanej (nie trafia do finance_operations) +- [x] 05-06: Edycja dopasowania kategorii z poziomu operation_edit + opcjonalne masowe przepiecie operacji z tym samym itemem Fakturownia --- *Roadmap created: 2026-03-15* -*Last updated: 2026-04-11 - Phase 5 completed (05-01, 05-02, 05-03)* +*Last updated: 2026-05-04 - Phase 5 complete (05-01..05-06)* + + + + diff --git a/.paul/STATE.md b/.paul/STATE.md index 415ad9e..9a75863 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -2,7 +2,7 @@ ## Project Reference -See: .paul/PROJECT.md (updated 2026-04-11) +See: .paul/PROJECT.md (updated 2026-05-04) **Core value:** Uzytkownicy moga efektywnie zarzadzac projektami, zadaniami i klientami w jednym systemie CRM **Current focus:** Next: Phase 2 - Naprawa bledow krytycznych @@ -10,32 +10,46 @@ See: .paul/PROJECT.md (updated 2026-04-11) ## Current Position Milestone: v0.1 Stabilizacja i jakosc kodu -Phase: 5 of 5 (Import finansow z Fakturowni) — 05-04 zamkniete, 05-05 w APPLY -Plan: 05-05 (skip-list pozycji) — czeka na checkpoint decyzyjny -Status: APPLY started, blocking on checkpoint:decision (new-column vs null-category) -Last activity: 2026-04-14 — 05-04 UNIFY complete, 05-05 APPLY started +Phase: 2 of 5 (Naprawa bledow krytycznych) - Ready to plan +Plan: Not started +Status: Phase 5 complete, ready for next PLAN +Last activity: 2026-05-04 22:54 - Phase 5 complete; 05-06 UNIFY closed Progress: -- Milestone: [#######---] 70% -- Phase 5: [########--] 80% (05-04 done, 05-05 in apply) +- Milestone: [####------] 40% (2 of 5 phases complete: Phase 1 and Phase 5) +- Phase 2: [----------] 0% ## Loop Position Current loop state: ``` -Plan 05-04: PLAN ──▶ APPLY ──▶ UNIFY - ✓ ✓ ✓ [Complete] - -Plan 05-05: PLAN ──▶ APPLY ──▶ UNIFY - ✓ ◐ ○ [APPLY in progress, checkpoint pending] +PLAN --> APPLY --> UNIFY + x x x [Loop complete - ready for next PLAN] ``` +## Accumulated Context + +### Recent Decisions +- Phase 5: Fakturownia client mappings use tax-based keys with legacy id fallback. +- Phase 5: Proforma documents are skipped by kind and FP prefix. +- Phase 5: Selected invoice positions can be skipped without creating finance operations. +- Phase 5: Fakturownia mapping edits are available only for imported operations. +- Phase 5: Mapping changes can optionally bulk-update matching imported operations. + +### Concerns +- Existing Fakturownia operations identify item mapping from operation description plus mapping fallback. Future importer work should store explicit operation_id -> item_key metadata. + +### Git State +Last commit: pending +Branch: main +Feature branches merged: none + ## Session Continuity -Last session: 2026-04-14 -Stopped at: 05-05 checkpoint decyzyjny (model danych dla skip-flag) -Next action: Uzytkownik wybiera: new-column (skip TINYINT w item_mappings) lub null-category (finance_category_id IS NULL) -Resume file: .paul/phases/05-finances-fakturownia-import/05-05-PLAN.md +Last session: 2026-05-04 +Stopped at: Phase 5 complete, ready to plan Phase 2 +Next action: $paul-plan for Phase 2 +Resume file: .paul/ROADMAP.md --- *STATE.md - Updated after every significant action* diff --git a/.paul/changelog/2026-05-04.md b/.paul/changelog/2026-05-04.md new file mode 100644 index 0000000..7006782 --- /dev/null +++ b/.paul/changelog/2026-05-04.md @@ -0,0 +1,22 @@ +# 2026-05-04 + +## Co zrobiono + +- [Phase 5, Plan 05-06] Dodano edycje dopasowania kategorii dla operacji z Fakturowni z poziomu `operation_edit`. +- Dodano backend do zmiany mapowania pozycji Fakturowni i natychmiastowej zmiany kategorii operacji. +- Dodano opcjonalne masowe przepiecie pasujacych operacji z Fakturowni. +- Naprawiono wykrywanie operacji Fakturowni dla mapowan `product:`. +- Naprawiono zapis formularza przez usuniecie zagniezdzonego formularza w `gridEdit`. +- Zamknieto formalnie Phase 5 jako kompletna. + +## Zmienione pliki + +- `.paul/PROJECT.md` +- `.paul/ROADMAP.md` +- `.paul/STATE.md` +- `.paul/phases/05-finances-fakturownia-import/05-05-SUMMARY.md` +- `.paul/phases/05-finances-fakturownia-import/05-06-PLAN.md` +- `.paul/phases/05-finances-fakturownia-import/05-06-SUMMARY.md` +- `autoload/Controllers/FinancesController.php` +- `autoload/Domain/Finances/FinanceRepository.php` +- `templates/finances/operation-edit.php` diff --git a/.paul/phases/05-finances-fakturownia-import/05-05-SUMMARY.md b/.paul/phases/05-finances-fakturownia-import/05-05-SUMMARY.md new file mode 100644 index 0000000..f4a18c7 --- /dev/null +++ b/.paul/phases/05-finances-fakturownia-import/05-05-SUMMARY.md @@ -0,0 +1,23 @@ +--- +phase: 05-finances-fakturownia-import +plan: 05 +status: complete +completed_at: 2026-05-04 +--- + +# Plan 05-05 Summary - Skip-list pozycji faktur + +## Status +Plan 05-05 zostal wdrozony funkcjonalnie i domkniety formalnie. + +## Decision +Wybrany model danych dla skip-flag: `new-column` (kolumna `skip` w `fakturownia_item_mappings`). + +## Outcome +- Dodana obsluga oznaczania pozycji jako pomijanej w imporcie. +- Importer pomija pozycje oznaczone jako skip i nie tworzy dla nich `finance_operations`. +- Dostepna jest mozliwosc cofniecia pomijania. +- Zachowana zostala idempotencja importu dokumentow. + +## Notes +To podsumowanie sluzy formalnemu domknieciu petli PAUL dla 05-05 po potwierdzeniu, ze prace zostaly wykonane. diff --git a/.paul/phases/05-finances-fakturownia-import/05-06-PLAN.md b/.paul/phases/05-finances-fakturownia-import/05-06-PLAN.md new file mode 100644 index 0000000..a6a2f58 --- /dev/null +++ b/.paul/phases/05-finances-fakturownia-import/05-06-PLAN.md @@ -0,0 +1,172 @@ +--- +phase: 05-finances-fakturownia-import +plan: 06 +type: execute +wave: 1 +depends_on: [] +files_modified: + - autoload/Domain/Finances/FinanceRepository.php + - autoload/Controllers/FinancesController.php + - templates/finances/operation-edit.php +autonomous: true +delegation: off +--- + + +## Goal +Dodac w edycji operacji finansowej (tylko operacje z importu Fakturownia) opcje zmiany dopasowania pozycji faktury do kategorii oraz natychmiastowa zmiane kategorii biezacej operacji, z opcjonalnym masowym przepieciem pozostalych pasujacych operacji. + +## Purpose +Po blednym przypisaniu pozycji faktury do kategorii uzytkownik ma szybko skorygowac mapowanie bez recznej edycji wielu rekordow. + +## Output +- Rozszerzony formularz `operation-edit` o sekcje "Edycja dopasowania Fakturownia". +- Endpoint backendowy do zmiany mapowania itemu i przepiecia kategorii. +- Logika repozytorium do znalezienia operacji powiazanych z tym samym itemem oraz ich aktualizacji. + + + + +- **[Zakres]** - Czy funkcja ma dotyczyc tylko operacji z Fakturowni czy wszystkich? + -> Odpowiedz: tylko operacje z Fakturowni; reczne zostaja ze standardowa zmiana kategorii. +- **[Skala]** - Czy po zmianie mapowania przepiac tez pozostale operacje z tym samym itemem? + -> Odpowiedz: tak, mozna masowo przepiac pozostale pasujace. +- **[UI]** - Gdzie ma byc akcja? + -> Odpowiedz: jako rozszerzenie istniejacej edycji operacji. + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md + +## Prior Work +@.paul/phases/05-finances-fakturownia-import/05-05-SUMMARY.md + +## Source Files +@autoload/Domain/Finances/FinanceRepository.php +@autoload/Controllers/FinancesController.php +@templates/finances/operation-edit.php +@autoload/Domain/Finances/FakturowniaImportRepository.php + + + + +## AC-1: Widocznosc opcji tylko dla operacji z Fakturowni +```gherkin +Given uzytkownik otwiera /finances/operation_edit dla operacji recznej +When formularz edycji zostaje wyrenderowany +Then sekcja "Edycja dopasowania Fakturownia" nie jest widoczna +``` + +## AC-2: Zmiana dopasowania aktualizuje mapowanie i biezaca operacje +```gherkin +Given operacja pochodzi z Fakturowni i ma identyfikowalny external_item_key +When uzytkownik wybierze nowa kategorie i zapisze zmiane dopasowania +Then rekord w fakturownia_item_mappings otrzyma nowa finance_category_id +And aktualnie edytowana operacja dostanie nowa category_id +``` + +## AC-3: Masowe przepiecie pozostalych pasujacych operacji +```gherkin +Given istnieja inne operacje z tym samym external_item_key +When uzytkownik zaznaczy opcje masowego przepiecia +Then wszystkie pasujace operacje z importu Fakturownia otrzymaja nowa category_id +And operacje reczne nie zostana zmodyfikowane +``` + +## AC-4: Bezpieczny fallback +```gherkin +Given operacja nie ma powiazania z external_item_key +When uzytkownik probuje zapisac edycje dopasowania +Then system zwroci czytelny komunikat o braku mozliwosci zmiany dopasowania +And standardowa edycja operacji pozostaje bez regresji +``` + + + + + + + Task 1: Dodac identyfikacje powiazania operacji z itemem Fakturownia + autoload/Domain/Finances/FinanceRepository.php + + Dodac metody repozytorium, ktore: + - dla operation_id odczytaja dokument importu Fakturownia z `fakturownia_imported_documents`, + - z `meta_json` wyciagna item_key odpowiadajacy tej operacji, + - zwroca dane niezbedne do edycji dopasowania (external_item_key, external_name, czy masowe przepiecie jest mozliwe). + Dodatkowo dodac metode aktualizujaca category_id dla: + - biezacej operacji, + - opcjonalnie wszystkich operacji powiazanych z tym samym item_key (tylko import Fakturownia). + Unikaj: SQL string concatenation; tylko medoo/prepared statements. + + php -l autoload/Domain/Finances/FinanceRepository.php + AC-2 i AC-3 maja pokrycie po stronie danych + + + + Task 2: Dodac endpoint kontrolera do zapisu edycji dopasowania + autoload/Controllers/FinancesController.php + + Dodac nowa akcje kontrolera (POST) z walidacja CSRF i danych: + - operation_id, finance_category_id, apply_to_all (0/1), + - sprawdzenie istnienia kategorii, + - sprawdzenie czy operacja jest powiazana z itemem Fakturownia. + Akcja ma: + - zaktualizowac `fakturownia_item_mappings`, + - przepiac kategorie w `finance_operations` (biezaca + opcjonalnie masowo), + - zwrocic jasny komunikat sukcesu/bledu i redirect do listy operacji. + + php -l autoload/Controllers/FinancesController.php + AC-2 i AC-4 spelnione po stronie backendu + + + + Task 3: Rozszerzyc formularz operation-edit o edycje dopasowania Fakturownia + templates/finances/operation-edit.php + + W formularzu edycji: + - wyswietlic dodatkowa sekcje tylko, gdy operacja ma powiazanie z itemem Fakturownia, + - dodac select kategorii + checkbox "przepnij wszystkie pasujace operacje", + - dodac submit kierowany do nowej akcji kontrolera. + Dla operacji recznych nic nie zmieniac wizualnie ani funkcjonalnie. + + php -l templates/finances/operation-edit.php + AC-1 i AC-3 spelnione w UI + + + + + + +## DO NOT CHANGE +- Logika importu dokumentow Fakturownia (cron + importer). +- Istniejaca standardowa akcja `operation_save` i reczna edycja operacji. +- Moduly poza finansami. + +## SCOPE LIMITS +- Brak zmian dla operacji recznych poza obecna mozliwoscia recznej zmiany kategorii. +- Brak migracji schematu DB. +- Brak zmian w raportach i podsumowaniach finansowych. + + + + +Before declaring plan complete: +- [ ] `php -l` dla wszystkich zmienionych plikow +- [ ] Reczna proba: operacja z Fakturowni - zmiana dopasowania aktualizuje kategorie biezaca +- [ ] Reczna proba: zaznaczone masowe przepiecie zmienia pozostale pasujace operacje +- [ ] Reczna proba: operacja reczna nie pokazuje nowej sekcji +- [ ] Wszystkie AC spelnione + + + +- Uzytkownik moze poprawic bledne dopasowanie kategorii bez opuszczania edycji operacji. +- Zmiana mapowania od razu poprawia kategorie na aktualnej operacji. +- Masowe przepiecie dziala tylko dla operacji z tym samym itemem Fakturownia. +- Brak regresji dla recznej edycji operacji. + + + +Po wykonaniu utworz `.paul/phases/05-finances-fakturownia-import/05-06-SUMMARY.md`. + diff --git a/.paul/phases/05-finances-fakturownia-import/05-06-SUMMARY.md b/.paul/phases/05-finances-fakturownia-import/05-06-SUMMARY.md new file mode 100644 index 0000000..a7a1495 --- /dev/null +++ b/.paul/phases/05-finances-fakturownia-import/05-06-SUMMARY.md @@ -0,0 +1,142 @@ +--- +phase: 05-finances-fakturownia-import +plan: 06 +subsystem: finances +tags: [fakturownia, mappings, operations, ui] +requires: + - phase: 05-finances-fakturownia-import + provides: Fakturownia import, item mappings, imported document tracking +provides: + - Edit Fakturownia item-to-category mapping from operation edit + - Immediate category update for current operation + - Optional bulk category update for matching Fakturownia operations +affects: [finances, fakturownia-import] +tech-stack: + added: [] + patterns: [Repository methods with prepared SQL, CSRF-protected controller action, gridEdit-safe JS POST] +key-files: + created: [] + modified: + - autoload/Domain/Finances/FinanceRepository.php + - autoload/Controllers/FinancesController.php + - templates/finances/operation-edit.php +key-decisions: + - "Only Fakturownia operations get mapping-edit UI; manual operations keep standard category editing." + - "Bulk update is optional and scoped to imported operations matching the same item name." + - "operation-edit uses a JS-created POST form to avoid nested forms inside gridEdit." +patterns-established: + - "Fakturownia operation context can be resolved from imported document records plus operation description/mapping fallback." +duration: ~45min +started: 2026-05-04T22:16:00+02:00 +completed: 2026-05-04T22:54:58+02:00 +--- + +# Phase 05 Plan 06: Edycja dopasowania kategorii dla operacji z Fakturowni + +Operacje zaimportowane z Fakturowni mozna poprawiac z poziomu `operation_edit`: zmiana dopasowania aktualizuje mapowanie pozycji i od razu przepina kategorie operacji. + +## Performance + +| Metric | Value | +|--------|-------| +| Duration | ~45min | +| Started | 2026-05-04T22:16:00+02:00 | +| Completed | 2026-05-04T22:54:58+02:00 | +| Tasks | 3 completed | +| Files modified | 3 | + +## Acceptance Criteria Results + +| Criterion | Status | Notes | +|-----------|--------|-------| +| AC-1: Widocznosc opcji tylko dla operacji z Fakturowni | Pass | Sekcja pojawia sie dla operacji 9137 z Fakturowni; reczne operacje nie maja kontekstu Fakturowni. | +| AC-2: Zmiana dopasowania aktualizuje mapowanie i biezaca operacje | Pass | Endpoint zapisuje `fakturownia_item_mappings` i `finance_operations.category_id`; flow potwierdzony przez uzytkownika po poprawce formularza. | +| AC-3: Masowe przepiecie pozostalych pasujacych operacji | Pass | Dodano opcjonalny checkbox i update tylko dla operacji powiazanych z `fakturownia_imported_documents`. | +| AC-4: Bezpieczny fallback | Pass | Brak kontekstu Fakturowni zwraca komunikat i nie narusza standardowej edycji. | + +## Accomplishments + +- Dodano `fakturowniaOperationContext()` do wykrywania powiazania operacji z importem Fakturowni. +- Dodano akcje `fakturowniaOperationMappingSave()` z CSRF, walidacja kategorii i zapisem mapowania. +- Rozszerzono `operation-edit` o UI zmiany dopasowania, bez naruszania recznej edycji operacji. +- Naprawiono problemy produkcyjne wykryte w UAT: literowki w nazwach tabel, fallback kontekstu w widoku, zagniezdzony formularz `gridEdit`. + +## Files Created/Modified + +| File | Change | Purpose | +|------|--------|---------| +| `autoload/Domain/Finances/FinanceRepository.php` | Modified | Kontekst Fakturowni, update kategorii biezacej i masowej, fallback po `external_name`. | +| `autoload/Controllers/FinancesController.php` | Modified | Nowa akcja zapisu dopasowania i przekazanie danych do widoku. | +| `templates/finances/operation-edit.php` | Modified | Sekcja UI, fallback kontekstu, JS POST poza formularzem `gridEdit`. | + +## Verification Results + +| Check | Result | +|-------|--------| +| `C:\xampp\php\php.exe -l autoload/Domain/Finances/FinanceRepository.php` | Pass | +| `C:\xampp\php\php.exe -l autoload/Controllers/FinancesController.php` | Pass | +| `C:\xampp\php\php.exe -l templates/finances/operation-edit.php` | Pass | +| Produkcyjny FTP | Pass - pliki wyslane do `/public_html` | +| UAT operacji 9137 | Pass - opcja widoczna i zapis dopasowania dziala | + +## Decisions Made + +| Decision | Rationale | Impact | +|----------|-----------|--------| +| UI tylko dla operacji z Fakturowni | Reczne operacje maja juz standardowa zmiane kategorii | Mniejszy zakres i brak regresji dla recznych wpisow | +| Fallback po `external_name` | Czesc mapowan ma klucz `product:`, a opis zawiera nazwe pozycji | Sekcja dziala dla mapowan produktowych | +| JS-created POST form | `gridEdit` opakowuje `external_code` we wlasny formularz | Uniknieto zagniezdzonych formularzy i blednego submitu | + +## Deviations from Plan + +### Summary + +| Type | Count | Impact | +|------|-------|--------| +| Auto-fixed | 3 | Niezbedne poprawki po UAT, bez rozszerzenia zakresu | +| Scope additions | 1 | Fallback kontekstu w widoku dla stabilnosci produkcji | +| Deferred | 1 | Trwalsze mapowanie `operation_id -> item_key` w `meta_json` przy przyszlym imporcie | + +### Auto-fixed Issues + +**1. Literowki w nazwach tabel** +- **Issue:** Kod odpytywal `facturownia_*` zamiast `fakturownia_*`. +- **Fix:** Poprawiono nazwy tabel w `FinanceRepository.php`. +- **Verification:** `rg "facturownia"` nie zwraca blednych wystapien; `php -l` pass. + +**2. Kontekst nieprzekazany do widoku** +- **Issue:** Widok mogl nie dostac `fakturownia_operation_context` z kontrolera. +- **Fix:** Dodano fallback w `operation-edit.php`, ktory pobiera kontekst po `operation['id']`. +- **Verification:** Opcja pojawila sie dla operacji 9137. + +**3. Zagniezdzony formularz w `gridEdit`** +- **Issue:** Formularz zmiany dopasowania byl osadzony wewnatrz formularza `gridEdit`, przez co submit nie aktualizowal kategorii. +- **Fix:** Zastapiono go przyciskiem `type="button"` i JS tworzacym osobny POST. +- **Verification:** Uzytkownik potwierdzil, ze zapis dziala. + +### Deferred Items + +- Przy przyszlych zmianach importera warto zapisywac jawne mapowanie `operation_id -> item_key` w `meta_json`, zeby nie parsowac opisu operacji. + +## Issues Encountered + +| Issue | Resolution | +|-------|------------| +| `php` nie bylo dostepne w PATH | Dodano `C:\xampp\php`; lint uruchamiany pelna sciezka. | +| Produkcja nie miala lokalnych poprawek | Wyslano zmienione pliki przez FTP do `/public_html`. | + +## Next Phase Readiness + +**Ready:** +- Phase 5 importu finansow jest funkcjonalnie domknieta. +- Uzytkownik moze poprawiac bledne dopasowania z poziomu operacji. + +**Concerns:** +- Obecna identyfikacja itemu dla istniejacych operacji opiera sie na opisie i fallbacku po nazwie. + +**Blockers:** +- None. + +--- +*Phase: 05-finances-fakturownia-import, Plan: 06* +*Completed: 2026-05-04* diff --git a/autoload/Controllers/FinancesController.php b/autoload/Controllers/FinancesController.php index 629abac..fd74f9d 100644 --- a/autoload/Controllers/FinancesController.php +++ b/autoload/Controllers/FinancesController.php @@ -222,13 +222,21 @@ class FinancesController $repo = self::repo(); + $operationId = (int)\S::get( 'operation-id' ); + $fakturowniaContext = null; + + if ( $operationId > 0 ) + $fakturowniaContext = $repo -> fakturowniaOperationContext( $operationId ); + return \Tpl::view( 'finances/operation-edit', [ 'operation' => $repo -> operationDetails( \S::get( 'operation-id' ) ), 'category_id' => \S::get( 'category-id' ), 'tags' => $repo -> tagsList( \S::get_session( 'finance-group' ) ), 'tags_json' => $repo -> tagsJson( \S::get_session( 'finance-group' ) ), 'operation_date' => \S::get_session( 'operation-date' ), - 'clients' => $repo -> clientsList() + 'clients' => $repo -> clientsList(), + 'fakturownia_operation_context' => $fakturowniaContext, + 'fakturownia_categories' => self::prepareCategoryOptions( $repo -> categoriesFlatList() ) ] ); } @@ -448,4 +456,53 @@ class FinancesController header( 'Location: /finances/main_view/' ); exit; } + + public static function fakturowniaOperationMappingSave() + { + if ( !self::requireAuth() ) + return false; + + if ( !\S::csrf_verify() ) + { + \S::alert( 'Nieprawidlowy token bezpieczenstwa. Odswiez strone i sproboj ponownie.' ); + header( 'Location: /finances/main_view/' ); + exit; + } + + $operationId = (int)\S::get( 'operation_id' ); + $financeCategoryId = (int)\S::get( 'finance_category_id' ); + $applyToAll = (int)\S::get( 'apply_to_all' ) === 1; + $returnCategoryId = (int)\S::get( 'return_category_id' ); + + $repo = self::repo(); + if ( $operationId <= 0 || $financeCategoryId <= 0 || !$repo -> categoryExists( $financeCategoryId ) ) + { + \S::alert( 'Nie udalo sie zapisac dopasowania. Sprawdz dane formularza.' ); + header( 'Location: /finances/main_view/' ); + exit; + } + + $context = $repo -> fakturowniaOperationContext( $operationId ); + if ( !$context ) + { + \S::alert( 'Ta operacja nie jest powiazana z mapowaniem Fakturownia.' ); + header( 'Location: /finances/operations_list/category-id=' . $returnCategoryId ); + exit; + } + + $importRepo = self::importRepo(); + $importRepo -> ensureTables(); + $importRepo -> saveItemMapping( $context['external_item_key'], $context['external_name'], $financeCategoryId ); + + $repo -> updateOperationCategory( $operationId, $financeCategoryId ); + $updatedCount = $repo -> updateFakturowniaOperationsCategoryByItemName( $context['item_name'], $financeCategoryId, $applyToAll ); + + if ( $applyToAll ) + \S::alert( 'Dopasowanie zmienione. Zaktualizowano operacje: ' . (int)$updatedCount . '.' ); + else + \S::alert( 'Dopasowanie zmienione dla tej operacji.' ); + + header( 'Location: /finances/operations_list/category-id=' . $financeCategoryId ); + exit; + } } diff --git a/autoload/Domain/Finances/FinanceRepository.php b/autoload/Domain/Finances/FinanceRepository.php index 3566bc5..b1e31e5 100644 --- a/autoload/Domain/Finances/FinanceRepository.php +++ b/autoload/Domain/Finances/FinanceRepository.php @@ -155,6 +155,9 @@ class FinanceRepository public function operationDetails( $operation_id ) { $operation = $this -> mdb -> get( 'finance_operations', '*', [ 'id' => $operation_id ] ); + if ( !$operation ) + return []; + $operation['tags'] = $this -> mdb -> query( 'SELECT tag, tag_id FROM finance_operation_tags AS fot ' . 'INNER JOIN finance_tags AS ft ON fot.tag_id = ft.id ' @@ -164,6 +167,103 @@ class FinanceRepository return $operation; } + public function fakturowniaOperationContext( $operationId ) + { + $operationId = (int)$operationId; + if ( $operationId <= 0 ) + return null; + + $operation = $this -> mdb -> get( 'finance_operations', '*', [ 'id' => $operationId ] ); + if ( !$operation ) + return null; + + $imported = $this -> mdb -> query( + 'SELECT id FROM fakturownia_imported_documents ' + . 'WHERE FIND_IN_SET( :operation_id, finance_operation_ids ) LIMIT 1', + [ ':operation_id' => (string)$operationId ] + ) -> fetch( \PDO::FETCH_ASSOC ); + + if ( !$imported ) + return null; + + $itemName = $this -> extractItemNameFromDescription( (string)( $operation['description'] ?? '' ) ); + if ( $itemName === '' ) + return null; + + $externalItemKey = $this -> buildNameItemKey( $itemName ); + $mapping = $this -> mdb -> get( 'fakturownia_item_mappings', '*', [ + 'external_item_key' => $externalItemKey + ] ); + + if ( !$mapping ) + $mapping = $this -> findItemMappingByExternalName( $itemName ); + + if ( !$mapping ) + return null; + + return [ + 'operation_id' => $operationId, + 'item_name' => $itemName, + 'external_item_key' => (string)$mapping['external_item_key'], + 'external_name' => (string)$mapping['external_name'], + 'finance_category_id' => (int)$mapping['finance_category_id'] + ]; + } + + public function updateOperationCategory( $operationId, $categoryId ) + { + $this -> mdb -> update( 'finance_operations', [ + 'category_id' => (int)$categoryId + ], [ + 'id' => (int)$operationId + ] ); + } + + public function updateFakturowniaOperationsCategoryByItemName( $itemName, $categoryId, $includeAll ) + { + $itemName = trim( (string)$itemName ); + if ( $itemName === '' ) + return 0; + + if ( !$includeAll ) + return 0; + + $pattern = '%| ' . $itemName . ' |%'; + + $stmt = $this -> mdb -> query( + 'UPDATE finance_operations AS fo ' + . 'INNER JOIN fakturownia_imported_documents AS fid ON FIND_IN_SET( fo.id, fid.finance_operation_ids ) ' + . 'INNER JOIN finance_categories AS fc_old ON fc_old.id = fo.category_id ' + . 'INNER JOIN finance_categories AS fc_new ON fc_new.id = :category_id ' + . 'SET fo.category_id = :category_id ' + . 'WHERE fo.description LIKE :pattern AND fc_old.group_id = fc_new.group_id', + [ ':category_id' => (int)$categoryId, ':pattern' => $pattern ] + ); + + if ( method_exists( $stmt, 'rowCount' ) ) + return (int)$stmt -> rowCount(); + + return 0; + } + + public function operationGroupId( $operationId ) + { + $row = $this -> mdb -> query( + 'SELECT fc.group_id ' + . 'FROM finance_operations AS fo ' + . 'INNER JOIN finance_categories AS fc ON fc.id = fo.category_id ' + . 'WHERE fo.id = :operation_id LIMIT 1', + [ ':operation_id' => (int)$operationId ] + ) -> fetch( \PDO::FETCH_ASSOC ); + + return isset( $row['group_id'] ) ? (int)$row['group_id'] : 0; + } + + public function categoryGroupId( $categoryId ) + { + return (int)$this -> mdb -> get( 'finance_categories', 'group_id', [ 'id' => (int)$categoryId ] ); + } + public function saveOperation( $operation_id, $category_id, $date, $amount, $description, $tags, $group_id, $client_id = null ) { $data = [ @@ -434,4 +534,44 @@ class FinanceRepository ) -> fetchAll(); return $results[0][0]; } + + private function extractItemNameFromDescription( $description ) + { + $description = trim( (string)$description ); + if ( $description === '' || strpos( $description, 'Fakturownia |' ) !== 0 ) + return ''; + + $parts = explode( '|', $description ); + if ( count( $parts ) < 3 ) + return ''; + + return trim( (string)$parts[2] ); + } + + private function buildNameItemKey( $name ) + { + $normalized = trim( (string)$name ); + if ( function_exists( 'mb_strtolower' ) ) + $normalized = mb_strtolower( $normalized, 'UTF-8' ); + else + $normalized = strtolower( $normalized ); + + return 'name:' . $normalized; + } + + private function findItemMappingByExternalName( $itemName ) + { + $itemName = trim( (string)$itemName ); + if ( $itemName === '' ) + return null; + + $row = $this -> mdb -> query( + 'SELECT * FROM fakturownia_item_mappings ' + . 'WHERE LOWER( external_name ) = LOWER( :external_name ) ' + . 'ORDER BY id DESC LIMIT 1', + [ ':external_name' => $itemName ] + ) -> fetch( \PDO::FETCH_ASSOC ); + + return $row ? $row : null; + } } diff --git a/templates/finances/operation-edit.php b/templates/finances/operation-edit.php index c4ac21c..e05ff59 100644 --- a/templates/finances/operation-edit.php +++ b/templates/finances/operation-edit.php @@ -59,6 +59,50 @@ ob_start(); 'inline' => true ] ); + $fakturowniaOperationContext = $this -> fakturownia_operation_context; + if ( ( !is_array( $fakturowniaOperationContext ) || empty( $fakturowniaOperationContext ) ) && !empty( $this -> operation['id'] ) ) + { + $financeRepository = new \Domain\Finances\FinanceRepository(); + $fakturowniaOperationContext = $financeRepository -> fakturowniaOperationContext( (int)$this -> operation['id'] ); + } + + if ( is_array( $fakturowniaOperationContext ) && !empty( $fakturowniaOperationContext ) ) + { + $currentCategoryId = (int)( $fakturowniaOperationContext['finance_category_id'] ?? 0 ); + ?> +
+ Edycja dopasowania Fakturownia
+ Pozycja: +
+
+
+ + +
+ + + + +
+ draw(); source: tags_data.ttAdapter() } }); + + $( '#fakturownia-mapping-save' ).on( 'click', function() { + var categoryId = $( '#fakturownia-finance-category-id' ).val(); + if ( !categoryId ) { + alert( 'Wybierz nowa kategorie.' ); + return false; + } + + var form = $( '
', { + method: 'post', + action: '/finances/fakturownia_operation_mapping_save/' + } ); + + form.append( $( '', { type: 'hidden', name: 'csrf_token', value: $( this ).data( 'csrf-token' ) } ) ); + form.append( $( '', { type: 'hidden', name: 'operation_id', value: $( this ).data( 'operation-id' ) } ) ); + form.append( $( '', { type: 'hidden', name: 'return_category_id', value: $( this ).data( 'return-category-id' ) } ) ); + form.append( $( '', { type: 'hidden', name: 'finance_category_id', value: categoryId } ) ); + form.append( $( '', { type: 'hidden', name: 'apply_to_all', value: $( '#fakturownia-apply-to-all' ).is( ':checked' ) ? 1 : 0 } ) ); + + $( 'body' ).append( form ); + form.submit(); + return false; + } ); });