first commit

This commit is contained in:
2026-07-06 20:16:00 +02:00
commit 352e6d9e22
147 changed files with 12242 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
# PROJECT — finansePRO
**Utworzono:** 2026-07-06
**Status:** Inicjalizacja (pusty projekt)
## Nazwa
finansePRO
## Krótki opis
Projekt na wczesnym etapie — katalog nie zawiera jeszcze kodu (poza konfiguracją `.vscode`). Cel i zakres do doprecyzowania.
## Wartość podstawowa
_Do uzupełnienia — czego finansePRO ma dostarczać użytkownikowi._
## Aktualny status
- Repozytorium bez kodu źródłowego.
- PAUL zainicjalizowany w trybie plan-first.
- Brak zdefiniowanych wymagań i stosu technologicznego.
## Trwałe wymagania / ograniczenia
_Brak znanych na tę chwilę. Uzupełnij przy pierwszym `$paul-plan` lub `$paul-discuss`._
+35
View File
@@ -0,0 +1,35 @@
# STATE — finansePRO
**Aktualizacja:** 2026-07-06T14:55
## Project Reference
CodeIgniter 4.7.3 + MySQL (baza `host700513_finansepro` na hostido, czytana z `.env`). PHP 8.2 (XAMPP), Bootstrap 5 + ApexCharts (CDN). Logowanie: jeden użytkownik z `.env`. Tryb PAUL: plan-first.
## Current Position
- Plan `20260706-1248-fundament-wydatki-przychody`: **UKOŃCZONY**.
- Plan `20260706-1430-modul-inwestycji`: **UKOŃCZONY** (PLAN → APPLY → UNIFY). Wszystkie AC (AC-1..AC-5) Pass; SUMMARY utworzone.
- Gotowe do kolejnego planu.
## Loop Position
```
PLAN ──▶ APPLY ──▶ UNIFY
✓ ✓ ✓ [Pętla zamknięta — gotowe do następnego PLAN]
```
## Session Continuity
- Ostatnia aktywność: UNIFY planu 2 — SUMMARY + changelog + radar + STATE. Moduł inwestycji + pulpit globalny `/dashboard` + pulpit finansów `/finances`. Dane testowe wyczyszczone, `.env` na `production`.
- Next action: `$paul-plan` dla kolejnego planu, lub wdrożenie produkcyjne (patrz Aktywne sprawy).
- Resume file: `.paul/plans/20260706-1430-modul-inwestycji/SUMMARY.md`
## Roadmapa (nieformalna)
- ✓ Plan 1: fundament + wydatki/przychody bieżące.
- ✓ Plan 2: moduł inwestycji (instrumenty, wpłaty/wypłaty, wyceny) + pulpit inwestycji + pulpit globalny.
- ▷ Kolejne: do doprecyzowania (np. wykres per instrument, eksport, historia wartości).
## Aktywne sprawy / do weryfikacji
- Wdrożenie produkcyjne `https://finanse.projectpro.pl/`: wgrać pliki + `vendor/`, uruchomić `php spark migrate` na serwerze (3 nowe tabele `inv_*`), root `.htaccess`/docroot→`public/`, prawa `writable/`, `.env` z `CI_ENVIRONMENT=production` + `app.baseURL`.
- Lokalny dev: `.env` bywa przełączany na `CI_ENVIRONMENT=development` (używa `db_host_remote`, bo lokalny MySQL zgaszony); po pracy wraca na `production`. Aktualnie: `production`.
## Uwagi
- Radar: degraded (weryfikacja runtime zamiast pełnego indeksu). Pełny `codebase-memory-mcp` przez `$paul-map-codebase`.
- Repo nie jest git — brak commitów fazowych.
+39
View File
@@ -0,0 +1,39 @@
# 2026-07-06
## Co zrobiono
- [Plan 20260706-1248] Fundament finansePRO na CodeIgniter 4.7.3 + moduł wydatków/przychodów bieżących.
- Inicjalizacja PAUL (plan-first, Quality Radar), junction memory.
- Instalacja CI4 (Composer), konfiguracja DB czytana z `.env`, włączenie `intl` w XAMPP.
- Logowanie jednego użytkownika z `.env` (filtr `auth`, `Auth::login/logout`).
- Migracje `categories` (hierarchia+typ) i `operations` (+FK), seeder 9 kategorii.
- CRUD kategorii i operacji: filtry (data/kategoria/typ), sumy, blokada usunięcia używanej kategorii, typ operacji wymuszany przez kategorię.
- Dashboard z 3 wykresami ApexCharts (bilans miesięczny, wydatki wg kategorii, saldo skumulowane).
- Weryfikacja end-to-end przez `php spark serve` + curl; dane testowe wyczyszczone.
- Naprawa wdrożenia hostido: root `.htaccess` (forward do `public/`, 403), sekcja produkcyjna w `.env` (`CI_ENVIRONMENT=production`, `app.baseURL`).
- [Plan 20260706-1430] Moduł inwestycji: instrumenty (Obligacje/ETF), wpłaty/wypłaty, ręczne wyceny.
- Migracje `inv_instruments`, `inv_operations`, `inv_valuations` (FK RESTRICT), seeder instrumentów.
- CRUD instrumentów/operacji/wycen + filtry + blokada usunięcia instrumentu z powiązaniami.
- Pulpit inwestycji `/investments`: kafelki, wykres 3-seryjny w czasie, tabela per instrument (zarobek netto = ostatnia wycena (Σwpłat Σwypłat)).
- Pulpit globalny `/dashboard` (majątek razem = saldo bieżące + wartość inwestycji); dotychczasowy pulpit finansów przepięty pod `/finances`.
- Nawigacja zgrupowana w dropdowny: „Finanse bieżące" i „Inwestycje" + top-level „Pulpit".
- Weryfikacja end-to-end curl (ETF: netto 800, wartość 950, zarobek 150 = 18,75%); dane testowe wyczyszczone.
## Zmienione pliki
- `composer.json`, `composer.lock`, `vendor/`, `spark`, `public/`, `writable/`
- `app/Config/Database.php`, `app/Config/Routes.php`, `app/Config/Filters.php`, `app/Config/App.php`
- `app/Controllers/BaseController.php`, `app/Controllers/Auth.php`, `app/Controllers/Categories.php`, `app/Controllers/Operations.php`, `app/Controllers/Dashboard.php`
- `app/Filters/AuthFilter.php`
- `app/Models/CategoryModel.php`, `app/Models/OperationModel.php`
- `app/Database/Migrations/2026-07-06-100001_CreateCategories.php`, `app/Database/Migrations/2026-07-06-100002_CreateOperations.php`
- `app/Database/Seeds/DefaultCategoriesSeeder.php`
- `app/Views/layout/main.php`, `app/Views/auth/login.php`, `app/Views/categories/index.php`, `app/Views/categories/form.php`, `app/Views/operations/index.php`, `app/Views/operations/form.php`, `app/Views/dashboard/index.php`
- `.env`, `.htaccess`
- `app/Database/Migrations/2026-07-06-140001_CreateInvInstruments.php`, `...140002_CreateInvOperations.php`, `...140003_CreateInvValuations.php`
- `app/Database/Seeds/DefaultInstrumentsSeeder.php`
- `app/Models/InstrumentModel.php`, `app/Models/InvOperationModel.php`, `app/Models/ValuationModel.php`
- `app/Controllers/Instruments.php`, `app/Controllers/InvOperations.php`, `app/Controllers/Valuations.php`, `app/Controllers/Investments.php`, `app/Controllers/Dashboard.php`
- `app/Views/instruments/{index,form}.php`, `app/Views/inv_operations/{index,form}.php`, `app/Views/valuations/{index,form}.php`, `app/Views/investments/index.php`
- `app/Views/dashboard/index.php` (globalny), `app/Views/dashboard/finances.php` (finansowy)
- `app/Config/Routes.php`, `app/Views/layout/main.php`
+36
View File
@@ -0,0 +1,36 @@
# Impact Map
**Aktualizacja:** 2026-07-06 (post-apply Plan 2 — degraded)
## Kontekst
CodeIgniter 4.7.3 + MySQL (`.env`). Radar degraded — brak indeksu codebase-memory-mcp; weryfikacja runtime.
## Moduły
### Finanse bieżące (Plan 1)
- Kategorie/operacje: `app/Models/{Category,Operation}Model.php`, `app/Controllers/{Categories,Operations}.php`, tabele `categories`/`operations`.
- Pulpit finansów: `Dashboard::finances``app/Views/dashboard/finances.php`, trasa `/finances`.
### Inwestycje (Plan 2)
- Instrumenty: `InstrumentModel` (`withStats`, `portfolioTimeline`, `hasChildren`), `Instruments.php`, widoki `instruments/`, tabela `inv_instruments`.
- Operacje: `InvOperationModel`, `InvOperations.php`, widoki `inv_operations/`, tabela `inv_operations` (FK RESTRICT → inv_instruments).
- Wyceny: `ValuationModel`, `Valuations.php`, widoki `valuations/`, tabela `inv_valuations` (FK RESTRICT → inv_instruments).
- Pulpit inwestycji: `Investments::index``investments/index.php`, trasa `/investments`.
### Pulpit globalny
- `Dashboard::index``app/Views/dashboard/index.php`, trasy `/` i `/dashboard`. Majątek = saldo bieżące (`OperationModel::totals`) + wartość inwestycji (`InstrumentModel::withStats`).
## Routing / nawigacja
- `app/Config/Routes.php` — grupa `auth`: `/`, `dashboard`, `finances`, `operations/*`, `categories/*`, `investments`, `instruments/*`, `investments/operations/*`, `investments/valuations/*`.
- `app/Views/layout/main.php` — top-level „Pulpit" + dropdowny „Finanse bieżące" / „Inwestycje".
## Dane / config
- MySQL z `.env` (dev → `db_host_remote`, prod → `db_host`) w `Database.php`.
- Tabele inwestycji: `inv_instruments` (name unique), `inv_operations` (instrument_id, date, amount, type enum deposit/withdraw), `inv_valuations` (instrument_id, date, value).
## Ukryte sprzężenia
- Zarobek netto liczony wyłącznie w `InstrumentModel::withStats` — kontrolery/widoki konsumują (jedno źródło prawdy).
- FK RESTRICT: instrument z operacjami/wycenami nieusuwalny (spójne z blokadą w `Instruments::delete`).
- Pulpit globalny zależny od obu domen (finanse + inwestycje).
## Weryfikacja manualna
- Migracje `inv_*`; CRUD 3 encji; filtry; blokada usunięcia; wzór zarobku; pulpity `/dashboard`, `/finances`, `/investments`.
+19
View File
@@ -0,0 +1,19 @@
# Quality Risks
**Aktualizacja:** 2026-07-06 (post-apply Plan 2 — degraded)
## Zaakceptowane świadomie
- **Kopiowanie wzorców zapytań** z `OperationModel` do `InvOperationModel`/`ValuationModel` (filtered/join). Osobna domena (brak type income/expense, dochodzą wyceny) — nie konsolidować w bazowy model, kod krótki.
- **Format kwoty** `number_format(...,2,',',' ').' zł'` powtórzony w widokach jako lokalna funkcja `$fmt`. Trywialny, świadomie nie wydzielony do helpera (YAGNI).
## Rozwiązane
- Zarobek netto: jedno źródło prawdy `InstrumentModel::withStats` (`net_invested`, `profit`, `percent`); nie liczony w widokach.
- Typ operacji inwestycyjnej: ENUM `deposit|withdraw` w DB + walidacja `in_list` w modelu + sanity w kontrolerze.
- Integralność: FK RESTRICT na `inv_operations`/`inv_valuations`.
## Odłożone
- Pełna indeksacja `codebase-memory-mcp``$paul-map-codebase`.
- jscpd/ast-grep — wyłączone polityką w `config.md` (skan on-demand).
## Do obserwacji
- `portfolioTimeline` liczony w PHP O(daty × operacje) — trywialne przy ręcznych wycenach; przy dużej liczbie wpisów rozważyć agregację SQL.
+16
View File
@@ -0,0 +1,16 @@
# Tooling Status
**Timestamp:** 2026-07-06T13:10
**Scan mode:** post-apply (plan 20260706-1248-fundament-wydatki-przychody)
## Narzędzia
- `codebase-memory-mcp`: enabled, indeks nieodświeżony (ponytail: pełna indeksacja odłożona do `$paul-map-codebase`; kod świeżo dodany, weryfikacja przeszła runtime'owo).
- `jscpd`: disabled by policy
- `ast-grep`: disabled by policy
## Weryfikacja runtime (zamiast skanu statycznego)
- `php spark migrate` / `db:seed` — OK (zdalna baza hostido).
- `php spark serve` + curl: login/logout, CRUD kategorii i operacji, filtry, sumy, 3 wykresy dashboardu, blokada usunięcia używanej kategorii, wymuszenie typu operacji przez kategorię — wszystko potwierdzone.
## Następna akcja
`$paul-map-codebase` (pełny indeks codebase-memory-mcp) po ustabilizowaniu modułu, lub przed Planem 2 (inwestycje).
+46
View File
@@ -0,0 +1,46 @@
# Project Config
**Project:** finansePRO
**Created:** 2026-07-06
## Project Settings
```yaml
project:
name: finansePRO
version: 0.0.0
```
## Integrations
### Quality Radar
```yaml
quality_radar:
enabled: true
auto_install: true
tools:
codebase_memory_mcp: true
jscpd: false # manual/on-demand duplicate scan
ast_grep: false # manual/on-demand structural pattern scan
reports:
update_on_plan: true
update_on_apply: true
update_on_verify: true
```
## Preferences
```yaml
preferences:
auto_commit: false
verbose_output: false
parallel_agents: false
plan_storage: plans
state_autocompress: true
state_autocompress_max_lines: 500
```
---
*Config created: 2026-07-06*
@@ -0,0 +1,235 @@
---
plan_id: 20260706-1248-fundament-wydatki-przychody
title: Fundament finansePRO + moduł wydatków/przychodów bieżących
storage: plan-first
legacy_phase: null
created: 2026-07-06T12:48:27+02:00
status: planned
type: execute
autonomous: false
delegation: auto
files_modified:
- composer.json
- .env
- app/Config/Database.php
- app/Config/Routes.php
- app/Config/Filters.php
- app/Filters/AuthFilter.php
- app/Controllers/BaseController.php
- app/Controllers/Auth.php
- app/Controllers/Categories.php
- app/Controllers/Operations.php
- app/Controllers/Dashboard.php
- app/Models/CategoryModel.php
- app/Models/OperationModel.php
- app/Database/Migrations/*_create_categories.php
- app/Database/Migrations/*_create_operations.php
- app/Database/Seeds/DefaultCategoriesSeeder.php
- app/Views/layout/main.php
- app/Views/auth/login.php
- app/Views/categories/*
- app/Views/operations/*
- app/Views/dashboard/index.php
quality_radar: degraded
---
<objective>
## Cel
Postawić fundament aplikacji finansePRO na CodeIgniter 4 (MySQL z `.env`), logowanie jednego użytkownika oraz kompletny moduł finansów bieżących: kategorie (hierarchiczne, typ przychód/wydatek), operacje (CRUD + filtry) i dashboard z wykresami ApexCharts.
## Po co
To pierwsza, uruchamialna wersja narzędzia do analizy finansów osobistych. Daje działający szkielet (auth + DB + layout), na którym Plan 2 dołoży moduł inwestycji. Wzorzec domenowy (kategorie/operacje) świadomie czerpie z modułu finansów crmPRO, ale kod pisany jest od zera w CI4 — bez przenoszenia frameworka crmPRO.
## Wynik
Działająca aplikacja CI4: logowanie → dashboard z wykresami → zarządzanie kategoriami i operacjami. Migracje tworzą schemat, seeder wrzuca startowe kategorie.
</objective>
<context>
## Dokumenty projektu
@.paul/PROJECT.md
@.paul/STATE.md
@.paul/config.md
@.paul/codebase/impact_map.md
@.paul/codebase/quality_risks.md
## Referencja (NIE modyfikować — tylko wzorzec domenowy)
@C:/visual-studio-code/crmPRO/autoload/Domain/Finances/FinanceRepository.php
@C:/visual-studio-code/crmPRO/templates/finances/main-view.php
@C:/visual-studio-code/crmPRO/templates/finances/operations-list.php
## Konfiguracja środowiska
@.env
</context>
<clarifications>
- Stack: **CodeIgniter 4** (użytkownik zna go z innej aplikacji, wdrożenie na hostido OK).
- Logowanie: **jeden użytkownik wprost z `.env`** (`user_email` + `user_password`), bez tabeli users, bez rejestracji, bez remember-tokenów.
- Zakres tego planu: **fundament + moduł bieżących wydatków/przychodów**. Moduł inwestycji to osobny Plan 2 (ręczne snapshoty wartości).
- Kategorie: hierarchiczne (`parent_id`), z typem przychód/wydatek — wzorzec jak `finance_categories` w crmPRO.
- Wykresy: **ApexCharts** (znane z crmPRO).
- Hasło w `.env` jest jawne — logowanie porównuje wprost (narzędzie jednoosobowe). Podniesienie do hasha zostawione jako możliwa późniejsza zmiana.
</clarifications>
<impact_scan>
## Quality Radar
**Status:** degraded (finansePRO to greenfield — brak kodu do skanowania; codebase-memory-mcp nie ma czego indeksować)
**Tools:** codebase-memory-mcp (brak indeksu — projekt pusty); jscpd/ast-grep wyłączone polityką
## Obszary objęte planem
- fundament CI4 — `app/Config/*`, `composer.json`, bootstrap
- auth (jeden user z .env) — `app/Filters/AuthFilter.php`, `app/Controllers/Auth.php`
- domena finansów bieżących — `app/Models/{Category,Operation}Model.php`, `app/Controllers/{Categories,Operations,Dashboard}.php`, migracje, widoki
- config DB — `app/Config/Database.php` czyta klucze z istniejącego `.env`
## Ryzyka duplikacji / hardcode
- **Drugie źródło prawdy dla configu DB** — istniejący `.env` używa własnych kluczy (`db_host`, `db_user`, `db_name`, `db_password`), NIE standardowych CI4 (`database.default.*`). Obsłużone w Task 1: `Database.php` czyta te klucze przez `env('db_host')` itd., bez dublowania wartości i bez zmiany `.env`.
- **Typ operacji (przychód/wydatek)** — nie utrwalać go w dwóch miejscach niespójnie. Obsłużone w Task 2: `type` jest kolumną enum na kategorii i na operacji; walidacja spójności w `OperationModel`.
- **Wzorzec z crmPRO** — czerpiemy schemat domenowy, ale nie kopiujemy kodu 1:1 (inny framework). Brak ryzyka copy-paste między repo.
## Świadome odroczenia
- Hashowanie hasła użytkownika — odroczone (jawne `.env`, narzędzie jednoosobowe).
- Pełny skan radaru (codebase-memory-mcp index) — po zbudowaniu kodu, w `$paul-apply`/`$paul-map-codebase`.
- Moduł inwestycji — Plan 2.
</impact_scan>
<skills>
Brak SPECIAL-FLOWS.md — sekcja skills pominięta.
</skills>
<acceptance_criteria>
## AC-1: Aplikacja startuje i łączy się z bazą z .env
```gherkin
Given świeżo zainstalowany CodeIgniter 4 z konfiguracją z .env
When uruchomię `php spark serve` i otworzę stronę główną
Then aplikacja ładuje się bez błędu połączenia z MySQL, używając db_host/db_user/db_name/db_password z .env
```
## AC-2: Logowanie jednego użytkownika z .env
```gherkin
Given niezalogowany użytkownik wchodzi na dowolną chronioną trasę
When zostaje przekierowany na /login i poda email oraz hasło zgodne z user_email/user_password z .env
Then zostaje zalogowany (sesja) i przekierowany na dashboard; błędne dane pokazują komunikat, a /logout kończy sesję
```
## AC-3: Zarządzanie kategoriami (hierarchia + typ)
```gherkin
Given zalogowany użytkownik w widoku kategorii
When doda/edytuje/usunie kategorię z nazwą, typem (przychód/wydatek) i opcjonalnym rodzicem
Then kategoria zapisuje się w tabeli categories, respektuje parent_id, a usunięcie kategorii z operacjami jest zablokowane lub przenosi operacje zgodnie z regułą (patrz Task 3)
```
## AC-4: CRUD operacji z filtrami
```gherkin
Given zalogowany użytkownik w widoku operacji
When doda operację (data, kwota, typ, kategoria, opis) i użyje filtra po zakresie dat oraz kategorii
Then operacja zapisuje się w tabeli operations, lista pokazuje przefiltrowane wyniki i sumy (przychody, wydatki, saldo)
```
## AC-5: Dashboard z wykresami
```gherkin
Given istnieją operacje w bazie
When otworzę dashboard
Then widzę wykresy ApexCharts: (a) bilans miesięczny przychody vs wydatki, (b) wydatki wg kategorii (donut), (c) saldo skumulowane w czasie z danymi zgodnymi z operacjami
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Bootstrap CI4 + konfiguracja DB z .env + logowanie jednego użytkownika</name>
<files>composer.json, app/Config/Database.php, app/Config/Routes.php, app/Config/Filters.php, app/Filters/AuthFilter.php, app/Controllers/Auth.php, app/Views/auth/login.php, app/Views/layout/main.php</files>
<action>
- Zainstalować szkielet CodeIgniter 4 (`composer create-project codeigniter4/appstarter` lub równoważnie) w katalogu projektu, zachowując istniejący `.env`, `.paul/`, `.claude/`, `.vscode/`.
- W `app/Config/Database.php` ustawić `default` z istniejących kluczy .env: `hostname = env('db_host')`, `username = env('db_user')`, `database = env('db_name')`, `password = env('db_password')`, `DBDriver = 'MySQLi'`, `charset = 'utf8mb4'`. NIE dodawać nowych kluczy do `.env` (żeby nie tworzyć drugiego źródła prawdy). Zdalny host `db_host_remote` zostawić jako komentarz/opcję na wdrożenie.
- Utworzyć `AuthFilter` sprawdzający `session()->get('logged_in')`; niezalogowanych przekierowuje na `/login`. Zarejestrować w `Filters.php` jako alias `auth` i nałożyć na grupę tras chronionych w `Routes.php`.
- `Auth` controller: `login` (GET formularz, POST weryfikacja wobec `env('user_email')`/`env('user_password')` — porównanie wprost; // ponytail: jawne haslo z .env, podniesc do password_hash jesli pojawi sie wiecej userow), `logout` (czyści sesję).
- `app/Views/layout/main.php`: wspólny layout Bootstrap + miejsce na treść + nawigacja (Dashboard, Operacje, Kategorie, Wyloguj). ApexCharts i Bootstrap wpiąć (CDN lub `public/`).
- Trasa domyślna `/` → redirect na dashboard (gdy zalogowany) lub `/login`.
</action>
<verify>`php spark serve`, wejście na chronioną trasę przekierowuje na /login; poprawne dane z .env logują i przekierowują na dashboard; złe dane = komunikat; /logout wylogowuje. Brak błędu połączenia z DB.</verify>
<done>AC-1, AC-2</done>
</task>
<task type="auto">
<name>Task 2: Schemat DB (migracje) + modele + seeder kategorii</name>
<files>app/Database/Migrations/*_create_categories.php, app/Database/Migrations/*_create_operations.php, app/Models/CategoryModel.php, app/Models/OperationModel.php, app/Database/Seeds/DefaultCategoriesSeeder.php</files>
<action>
- Migracja `categories`: `id` PK, `name` VARCHAR, `type` ENUM('income','expense'), `parent_id` INT NULL (FK self, ON DELETE SET NULL), `created_at`/`updated_at`. Indeks na `parent_id`, `type`.
- Migracja `operations`: `id` PK, `date` DATE, `amount` DECIMAL(12,2) (dodatnia), `type` ENUM('income','expense'), `category_id` INT NULL (FK categories, ON DELETE SET NULL), `description` VARCHAR NULL, `created_at`/`updated_at`. Indeksy na `date`, `category_id`, `type`.
- `CategoryModel`: allowedFields, walidacja (name wymagane, type in income/expense, parent_id istnieje lub null), metoda `flatList()`/`treeList()` i `usedByOperations($id)`.
- `OperationModel`: allowedFields, walidacja (date, amount > 0, type in income/expense, category_id istnieje lub null); reguła spójności: `type` operacji musi zgadzać się z `type` wybranej kategorii (jedno źródło prawdy — kategoria narzuca typ). Metody agregujące dla dashboardu: `monthlyBalance($from,$to)`, `byCategory($from,$to,$type)`, `runningBalance($from,$to)`.
- `DefaultCategoriesSeeder`: kilka startowych kategorii przychodów (np. Wynagrodzenie, Inne przychody) i wydatków (np. Jedzenie, Mieszkanie, Transport, Rozrywka).
</action>
<verify>`php spark migrate` tworzy tabele bez błędów; `php spark db:seed DefaultCategoriesSeeder` wstawia kategorie; szybki tinker/SQL potwierdza strukturę i FK.</verify>
<done>AC-3 (schemat), AC-4 (schemat), AC-5 (agregacje)</done>
</task>
<task type="auto">
<name>Task 3: Moduł kategorii i operacji (CRUD + filtry + sumy)</name>
<files>app/Controllers/Categories.php, app/Controllers/Operations.php, app/Controllers/BaseController.php, app/Views/categories/*, app/Views/operations/*, app/Config/Routes.php</files>
<action>
- `Categories` controller + widoki: lista (drzewko/płaska z wcięciem wg parent_id), formularz dodaj/edytuj (name, type, parent_id), usuwanie. Usunięcie kategorii używanej przez operacje: zablokować z komunikatem LUB pozwolić (operacje przechodzą na category_id=NULL dzięki FK) — wybrać blokadę z jasnym komunikatem dla bezpieczeństwa danych.
- `Operations` controller + widoki: lista z filtrami (zakres dat — datepicker/`<input type=date>`, kategoria, typ), formularz dodaj/edytuj (date, amount, type auto z kategorii, category_id, description), usuwanie. Wybór kategorii ustawia typ operacji (spójność jak w Task 2). Lista pokazuje sumy: przychody, wydatki, saldo dla aktualnego filtra.
- Trasy CRUD w `Routes.php` w grupie chronionej filtrem `auth`.
- Walidacja po stronie serwera przez modele; komunikaty błędów w widokach.
</action>
<verify>Ręcznie: dodać/edytować/usunąć kategorię i operację; filtr po dacie i kategorii zawęża listę; sumy się zgadzają; próba usunięcia używanej kategorii jest zablokowana z komunikatem.</verify>
<done>AC-3, AC-4</done>
</task>
<task type="auto">
<name>Task 4: Dashboard z wykresami ApexCharts</name>
<files>app/Controllers/Dashboard.php, app/Views/dashboard/index.php, app/Config/Routes.php</files>
<action>
- `Dashboard` controller: pobiera z `OperationModel` dane dla domyślnego zakresu (np. bieżący rok) i oddaje do widoku jako JSON dla ApexCharts. Opcjonalny filtr zakresu dat u góry dashboardu.
- Wykresy ApexCharts w `dashboard/index.php`:
(a) bilans miesięczny — słupki przychody vs wydatki per miesiąc,
(b) wydatki wg kategorii — donut (tylko type=expense),
(c) saldo skumulowane w czasie — linia (cumsum operacji: +przychód, wydatek).
- Kafelki podsumowania: suma przychodów, suma wydatków, saldo dla wybranego zakresu.
- Trasa `/dashboard` (i `/`) w grupie chronionej.
</action>
<verify>Ręcznie: po dodaniu operacji dashboard pokazuje trzy wykresy z poprawnymi danymi; zmiana zakresu dat aktualizuje wykresy i kafelki.</verify>
<done>AC-5</done>
</task>
</tasks>
<boundaries>
## Nie zmieniać
- `.env` — czytamy istniejące klucze, nie dodajemy nowych ani nie zmieniamy wartości.
- `.paul/`, `.claude/`, `.vscode/` — infrastruktura projektu.
- Repozytorium crmPRO (`C:/visual-studio-code/crmPRO/`) — wyłącznie referencja wzorca, zero modyfikacji.
## Poza zakresem
- Moduł inwestycji (operacje inwestycyjne, wyceny, wykres wpłaty vs zarobek) — Plan 2.
- Tabela users, rejestracja, reset hasła, wielu użytkowników.
- Import z Fakturownia / integracje zewnętrzne (były w crmPRO, tu niepotrzebne).
- Hashowanie hasła, 2FA, remember-me.
- Wdrożenie produkcyjne na hostido (osobny krok po akceptacji lokalnej).
</boundaries>
<verification>
- [ ] `php spark serve` startuje, `/` przekierowuje wg stanu logowania.
- [ ] Logowanie wobec `.env` działa (poprawne/błędne dane, logout).
- [ ] `php spark migrate` i seeder wykonują się bez błędów.
- [ ] CRUD kategorii i operacji działa, filtry i sumy poprawne.
- [ ] Dashboard renderuje 3 wykresy ApexCharts z danymi zgodnymi z operacjami.
- [ ] Config DB czyta wyłącznie z `.env` (brak drugiego źródła prawdy).
- [ ] Typ operacji spójny z typem kategorii (jedno źródło prawdy).
- [ ] Quality Radar: ryzyka z `<impact_scan>` obsłużone lub odroczone świadomie.
</verification>
<success_criteria>
- [ ] Wszystkie AC (AC-1..AC-5) przechodzą.
- [ ] Weryfikacja kompletna.
- [ ] Radar/impact_map zaktualizowane po zbudowaniu kodu (w `$paul-apply`).
- [ ] Fundament gotowy pod Plan 2 (inwestycje).
</success_criteria>
<output>
SUMMARY.md path: `.paul/plans/20260706-1248-fundament-wydatki-przychody/SUMMARY.md`
</output>
@@ -0,0 +1,92 @@
---
plan_id: 20260706-1248-fundament-wydatki-przychody
title: Fundament finansePRO + moduł wydatków/przychodów bieżących
completed: 2026-07-06T13:20:00+02:00
storage: plan-first
quality_radar: degraded
---
# Summary: Fundament finansePRO + moduł wydatków/przychodów bieżących
## Objective
Postawić fundament aplikacji finansePRO na CodeIgniter 4 (MySQL z `.env`), logowanie jednego użytkownika oraz moduł finansów bieżących: kategorie hierarchiczne z typem, operacje z filtrami i dashboard z wykresami ApexCharts.
## What Was Built
| Area | Result |
|------|--------|
| Fundament CI4 | CodeIgniter 4.7.3 zainstalowany (Composer), `Database.php` czyta klucze z istniejącego `.env`, czyste URL-e (`indexPage=''`) |
| Logowanie | Filtr `auth` + `Auth::login/logout` weryfikuje wobec `user_email`/`user_password` z `.env`, sesja `logged_in` |
| Schemat DB | Migracje `categories` (hierarchia `parent_id`, `type`, FK self `SET NULL`) i `operations` (FK `category_id` `SET NULL`), seeder 9 kategorii |
| CRUD kategorii/operacji | Pełny CRUD, filtry (data/kategoria/typ), sumy przychody/wydatki/saldo, blokada usunięcia używanej kategorii |
| Dashboard | 3 wykresy ApexCharts (bilans miesięczny, wydatki wg kategorii donut, saldo skumulowane) + kafelki podsumowań |
| Deployment | Root `.htaccess` (forward do `public/`, HTTPS jak crmPRO), sekcja produkcyjna w `.env` |
## Files Modified
- `composer.json`, `composer.lock`, `vendor/`, `spark`, `public/`, `writable/` — szkielet CI4
- `app/Config/Database.php` — hostname/username/password/database z `.env` (jedno źródło prawdy)
- `app/Config/Routes.php` — trasy publiczne + grupa chroniona filtrem `auth`
- `app/Config/Filters.php` — alias `auth`
- `app/Config/App.php``indexPage=''`
- `app/Controllers/BaseController.php` — helpery `form`, `url`
- `app/Filters/AuthFilter.php` — ochrona tras
- `app/Controllers/{Auth,Categories,Operations,Dashboard}.php`
- `app/Models/{CategoryModel,OperationModel}.php`
- `app/Database/Migrations/2026-07-06-100001_CreateCategories.php`, `...100002_CreateOperations.php`
- `app/Database/Seeds/DefaultCategoriesSeeder.php`
- `app/Views/layout/main.php`, `auth/login.php`, `categories/{index,form}.php`, `operations/{index,form}.php`, `dashboard/index.php`
- `.env``CI_ENVIRONMENT`, `app.baseURL` (produkcja)
- `.htaccess` (root) — forward do `public/` dla hostido
- `C:/xampp/php/php.ini` — włączone `extension=intl` (poza repo, wymóg CI4)
## Acceptance Criteria Results
| Criterion | Status | Evidence |
|-----------|--------|----------|
| AC-1: start + połączenie z DB | Pass | `php spark serve` startuje, `migrate` łączy się z bazą hostido |
| AC-2: logowanie z `.env` | Pass | curl: poprawne dane → 303 `/dashboard`; złe → powrót; `/logout``/login`; trasa chroniona → `/login` |
| AC-3: kategorie (hierarchia+typ) | Pass | CRUD OK; `parent_id` respektowany; usunięcie używanej kategorii zablokowane komunikatem |
| AC-4: CRUD operacji + filtry | Pass | filtry income/expense/zakres dat zwracają dokładne kwoty; sumy 5000/450/4550 |
| AC-5: dashboard z wykresami | Pass | JSON `monthly`/`byCat`/`running` poprawne; 3 wykresy ApexCharts renderowane |
## Verification Results
| Check | Result | Notes |
|-------|--------|-------|
| `php spark migrate` | Pass | 2 migracje na bazie hostido |
| `php spark db:seed DefaultCategoriesSeeder` | Pass | 9 kategorii |
| `php spark serve` + curl (login/logout/CRUD/filtry/dashboard) | Pass | pełny przepływ end-to-end |
| Wymuszenie typu operacji przez kategorię | Pass | wysłane złe typy zostały poprawione wg kategorii |
| Blokada usunięcia używanej kategorii | Pass | redirect + komunikat |
| Dane testowe | Cleaned | operacje: 0, kategorie seed: 9 |
## Quality Radar Results
**Status:** degraded
- New risks: brak nowych ryzyk duplikacji (jscpd/ast-grep wyłączone polityką)
- Resolved risks: config DB — jedno źródło prawdy (`.env`); typ operacji — wymuszany przez kategorię
- Deferred risks: pełna indeksacja `codebase-memory-mcp` odłożona do `$paul-map-codebase`; weryfikacja przeprowadzona runtime'owo
- Raw outputs: `.paul/codebase/tooling_status.md`
## Deviations
- Dodano `CI_ENVIRONMENT` i `app.baseURL` do `.env` (potrzebne dla dev/prod; nie dubluje configu DB).
- `app/Config/App.php`: `indexPage=''` (czyste URL-e) — poza pierwotną listą plików.
- Włączono `intl` w XAMPP `php.ini`; zainstalowano Composer (środowisko).
- Dev łączy się do zdalnej bazy hostido (lokalny MySQL zgaszony); produkcyjnie `localhost`.
- Dodano root `.htaccess` (naprawa 403 na hostido) — poza pierwotnym zakresem, wynikło z wdrożenia.
## Key Decisions / Patterns
- **Jedno źródło prawdy dla DB**: `Database.php` czyta wyłącznie klucze `.env`, wybór host wg `ENVIRONMENT`.
- **Typ operacji narzuca kategoria**: spójność wymuszana po stronie serwera (`Operations::persist`) + UX w JS.
- **CI4 na współdzielonym hostingu**: root `.htaccess` forwarduje do `public/` (wzorzec z crmPRO dla HTTPS).
## Follow-up
- Plan 2: moduł inwestycji — `inv_operations` (wpłaty/wypłaty) + `inv_valuations` (ręczne snapshoty wartości), wykres suma wpłat vs bieżąca wartość vs zarobek.
- Opcjonalnie: `$paul-map-codebase` (pełny indeks) przed Planem 2.
- Weryfikacja wdrożenia produkcyjnego na `https://finanse.projectpro.pl/` (docroot/`.htaccess`, `vendor/`, prawa `writable/`).
@@ -0,0 +1,288 @@
---
plan_id: 20260706-1430-modul-inwestycji
title: Moduł inwestycji — instrumenty, wpłaty/wypłaty, ręczne wyceny, pulpit
storage: plan-first
legacy_phase: null
created: 2026-07-06T14:30:00+02:00
status: planned
type: execute
autonomous: true
delegation: auto
files_modified:
- app/Database/Migrations/2026-07-06-140001_CreateInvInstruments.php
- app/Database/Migrations/2026-07-06-140002_CreateInvOperations.php
- app/Database/Migrations/2026-07-06-140003_CreateInvValuations.php
- app/Database/Seeds/DefaultInstrumentsSeeder.php
- app/Models/InstrumentModel.php
- app/Models/InvOperationModel.php
- app/Models/ValuationModel.php
- app/Controllers/Instruments.php
- app/Controllers/InvOperations.php
- app/Controllers/Valuations.php
- app/Controllers/Investments.php
- app/Views/instruments/index.php
- app/Views/instruments/form.php
- app/Views/inv_operations/index.php
- app/Views/inv_operations/form.php
- app/Views/valuations/index.php
- app/Views/valuations/form.php
- app/Views/investments/index.php
- app/Config/Routes.php
- app/Views/layout/main.php
quality_radar: degraded
---
<objective>
## Goal
Dodać do finansePRO moduł inwestycji: płaska lista instrumentów (kategorie główne, np. obligacje, ETF), operacje wpłat/wypłat per instrument, ręczne snapshoty wyceny per instrument oraz pulpit inwestycji z wykresem zbiorczym w czasie i tabelą rozbicia per instrument.
## Purpose
Domknięcie Planu 2 z roadmapy. Użytkownik chce śledzić, ile realnie włożył w inwestycje (netto), ile są teraz warte i ile zarobił — bez integracji z API kursów (wyceny ręczne).
## Output
3 migracje (`inv_instruments`, `inv_operations`, `inv_valuations`), seeder instrumentów, 3 modele, 4 kontrolery (CRUD + pulpit), widoki, trasy i pozycja w nawigacji.
</objective>
<context>
## Project Docs
@.paul/PROJECT.md
@.paul/STATE.md
@.paul/codebase/impact_map.md
@.paul/codebase/quality_risks.md
@.paul/plans/20260706-1248-fundament-wydatki-przychody/SUMMARY.md
## Source Files (wzorce do naśladowania)
@app/Models/OperationModel.php
@app/Controllers/Operations.php
@app/Controllers/Dashboard.php
@app/Config/Routes.php
@app/Views/layout/main.php
@app/Views/dashboard/index.php
@app/Database/Migrations/2026-07-06-100002_CreateOperations.php
</context>
<clarifications>
- Instrumenty: płaska lista kategorii głównych (obligacje, ETF, …), bez podkategorii i bez rozróżniania konkretnych funduszy.
- Zarobek per instrument: `zarobek = ostatnia_wycena (Σwpłat Σwypłat)`. Wypłaty zmniejszają zainwestowany kapitał (zainwestowane netto).
- Wyceny: wyłącznie ręczne snapshoty (`inv_valuations`), brak integracji z API kursów.
- Pulpit: jeden wykres zbiorczy w czasie (Σwpłat_netto | wartość | zarobek) + tabela rozbicia per instrument (wpłacono | wartość | zarobek | %).
</clarifications>
<impact_scan>
## Quality Radar
**Status:** degraded (weryfikacja runtime + odczyt plików źródłowych; pełny `codebase-memory-mcp` odłożony — jscpd/ast-grep wyłączone polityką w `config.md`)
**Tools:** codebase-memory-mcp (nieindeksowany), fallback: bezpośredni odczyt plików
## Affected Areas
- Domena inwestycji (nowa): `app/Models/{Instrument,InvOperation,Valuation}Model.php`, `app/Controllers/{Instruments,InvOperations,Valuations,Investments}.php`, migracje `inv_*`, widoki `instruments/`, `inv_operations/`, `valuations/`, `investments/`.
- Routing: `app/Config/Routes.php` — nowa grupa tras w bloku `auth`.
- Nawigacja: `app/Views/layout/main.php` — jedna pozycja „Inwestycje”.
- Baza: nowe tabele `inv_instruments`, `inv_operations`, `inv_valuations` (FK do `inv_instruments`).
## Duplicate / Hardcoded Risks
- Ryzyko duplikacji logiki filtrów/sum wobec `OperationModel` — świadomie osobne modele (inna domena: brak `type income/expense`, dochodzą wyceny). Wzorce zapytań kopiowane celowo, kod trzymany krótki. Obsłużone: brak współdzielonego „drugiego źródła prawdy” dla wartości domenowych.
- Typ operacji inwestycyjnej `deposit|withdraw` jako ENUM w migracji + reguła walidacji `in_list` w modelu — jedno źródło (ENUM DB), walidacja to strażnik wejścia. Obsłużone w Task 1.
- Definicja zarobku (netto) liczona w jednym miejscu — `InstrumentModel`/`Investments` (metoda agregująca), nie powielana w widokach. Obsłużone w Task 3.
## Explicit Deferrals
- Pełna indeksacja `codebase-memory-mcp` — odłożona do `$paul-map-codebase` (spójne z Planem 1).
- Integracja kursów z API, waluty obce, podatek Belki — poza zakresem (wyceny ręczne w PLN).
</impact_scan>
<skills>
Brak `.paul/SPECIAL-FLOWS.md` — sekcja skills pominięta.
</skills>
<acceptance_criteria>
## AC-1: Instrumenty (CRUD, płaska lista)
```gherkin
Given zalogowany użytkownik
When otwiera /instruments i dodaje instrument "Obligacje"
Then instrument zapisuje się i pojawia na liście
And próba usunięcia instrumentu z operacjami lub wycenami jest zablokowana komunikatem
```
## AC-2: Operacje inwestycyjne (wpłaty/wypłaty)
```gherkin
Given istnieje instrument "ETF"
When użytkownik dodaje wpłatę 1000 zł i wypłatę 200 zł na "ETF"
Then obie operacje zapisują się z poprawnym typem (deposit/withdraw)
And lista pokazuje je z nazwą instrumentu, filtrowanie po instrumencie zwraca właściwe wiersze
```
## AC-3: Ręczne wyceny (snapshoty)
```gherkin
Given istnieje instrument "ETF" z wpłatami
When użytkownik dodaje wycenę 950 zł na dziś
Then wycena zapisuje się per instrument z datą
And ostatnia wycena instrumentu jest używana do wyliczenia zarobku
```
## AC-4: Zarobek per instrument (netto)
```gherkin
Given "ETF": Σwpłat = 1000, Σwypłat = 200, ostatnia wycena = 950
When system liczy zarobek
Then wpłacone_netto = 800 (1000 200)
And zarobek = 150 (950 800)
```
## AC-5: Pulpit inwestycji (wykres zbiorczy + tabela)
```gherkin
Given istnieją instrumenty z operacjami i wycenami
When użytkownik otwiera /investments
Then widzi kafelki (wpłacono netto, wartość, zarobek) dla całego portfela
And wykres w czasie z 3 seriami: Σwpłat_netto, wartość, zarobek
And tabelę per instrument: wpłacono | wartość | zarobek | % zwrotu
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Schemat DB + modele + seeder instrumentów</name>
<files>
app/Database/Migrations/2026-07-06-140001_CreateInvInstruments.php,
app/Database/Migrations/2026-07-06-140002_CreateInvOperations.php,
app/Database/Migrations/2026-07-06-140003_CreateInvValuations.php,
app/Database/Seeds/DefaultInstrumentsSeeder.php,
app/Models/InstrumentModel.php,
app/Models/InvOperationModel.php,
app/Models/ValuationModel.php
</files>
<action>
Migracje (wzorzec: CreateOperations — InnoDB, timestamps DATETIME null, indeksy):
- `inv_instruments`: id, name VARCHAR(120), created_at/updated_at. Unikalny indeks na `name`.
- `inv_operations`: id, instrument_id (unsigned, NOT NULL), date DATE, amount DECIMAL(12,2),
type ENUM('deposit','withdraw'), description VARCHAR(255) null, timestamps.
Indeksy: date, instrument_id, type. FK instrument_id → inv_instruments(id) ON DELETE RESTRICT
(nie kasujemy instrumentu z operacjami — spójne z blokadą w kontrolerze).
- `inv_valuations`: id, instrument_id (unsigned, NOT NULL), date DATE, value DECIMAL(12,2), timestamps.
Indeksy: date, instrument_id. FK instrument_id → inv_instruments(id) ON DELETE RESTRICT.
Modele (returnType array, useTimestamps true, validationRules):
- InstrumentModel (tabela inv_instruments): allowedFields=['name']; reguła name required|max_length[120].
Metoda `withStats()`: zwraca instrumenty z Σwpłat, Σwypłat, wpłacone_netto, ostatnia wycena (value
z max(date) per instrument — podzapytanie/JOIN), zarobek = ostatnia_wycena wpłacone_netto,
procent = wpłacone_netto>0 ? zarobek/wpłacone_netto*100 : null. Metoda `hasChildren($id)`:
true jeśli instrument ma operacje lub wyceny (do blokady usunięcia).
- InvOperationModel (tabela inv_operations): allowedFields=['instrument_id','date','amount','type','description'];
reguły: instrument_id required|is_natural_no_zero, date required|valid_date[Y-m-d],
amount required|decimal|greater_than[0], type required|in_list[deposit,withdraw],
description permit_empty|max_length[255]. Metoda `filtered($f)`: JOIN nazwa instrumentu,
opcjonalny filtr instrument_id/type/from/to, orderBy date DESC.
- ValuationModel (tabela inv_valuations): allowedFields=['instrument_id','date','value'];
reguły: instrument_id required|is_natural_no_zero, date required|valid_date[Y-m-d],
value required|decimal|greater_than_equal_to[0]. Metoda `filtered($f)`: JOIN nazwa instrumentu,
orderBy date DESC.
Seeder DefaultInstrumentsSeeder: wstaw "Obligacje" i "ETF" (idempotentnie — pomiń jeśli name istnieje).
</action>
<verify>php spark migrate && php spark db:seed DefaultInstrumentsSeeder — 3 tabele utworzone, 2 instrumenty</verify>
<done>AC-1 (schemat instrumentów), AC-2 (schemat operacji), AC-3 (schemat wycen), AC-4 (logika zarobku w withStats)</done>
</task>
<task type="auto">
<name>Task 2: Kontrolery CRUD + widoki + routing + nawigacja</name>
<files>
app/Controllers/Instruments.php,
app/Controllers/InvOperations.php,
app/Controllers/Valuations.php,
app/Views/instruments/index.php,
app/Views/instruments/form.php,
app/Views/inv_operations/index.php,
app/Views/inv_operations/form.php,
app/Views/valuations/index.php,
app/Views/valuations/form.php,
app/Config/Routes.php,
app/Views/layout/main.php
</files>
<action>
Kontrolery (wzorzec: Operations/Categories — metoda model(), index/new/create/edit/update/delete,
persist() z redirectem i flashdata):
- Instruments: CRUD instrumentów. delete() blokuje gdy hasChildren($id) → redirect z error.
- InvOperations: CRUD wpłat/wypłat. index z filtrami (instrument_id/type/from/to), przekazuje listę
instrumentów do selecta. type walidowany do deposit|withdraw.
- Valuations: CRUD wycen. index z filtrem instrument_id, select instrumentów w formularzu.
Widoki (wzorzec: operations/index + form, Bootstrap 5, flashdata z layoutu):
- instruments/index: tabela instrumentów + link do withStats? (statystyki zostają na pulpicie — tu prosty CRUD).
- inv_operations/index: filtry (instrument, typ, zakres dat) + tabela (data, instrument, typ badge,
kwota, opis, akcje). form: select instrument, date, amount, radio/select typ deposit/withdraw, opis.
- valuations/index: filtr instrument + tabela (data, instrument, wartość, akcje). form: select instrument, date, value.
Routes.php: w grupie 'auth' dodać trasy REST-lite (jak operations) dla:
instruments, investments/operations (kontroler InvOperations), investments/valuations (kontroler Valuations),
oraz investments (Investments::index z Task 3). Prefiksy URL czytelne, bez kolizji z istniejącym /operations.
layout/main.php: dodać jedną pozycję nav "Inwestycje" → site_url('investments') między "Operacje" a "Kategorie".
</action>
<verify>php spark serve + curl: dodanie instrumentu, wpłaty, wypłaty, wyceny; filtr po instrumencie; blokada usunięcia instrumentu z operacjami</verify>
<done>AC-1, AC-2, AC-3 (pełny CRUD i filtry przez UI/HTTP)</done>
</task>
<task type="auto">
<name>Task 3: Pulpit inwestycji — agregacja, wykres zbiorczy, tabela per instrument</name>
<files>
app/Controllers/Investments.php,
app/Views/investments/index.php
</files>
<action>
Investments::index (wzorzec: Dashboard::index):
- Kafelki portfela: Σwpłacone_netto, Σwartość (suma ostatnich wycen per instrument), Σzarobek.
- Tabela per instrument z InstrumentModel::withStats(): nazwa | wpłacono netto | wartość | zarobek | % zwrotu
(zarobek kolorem: dodatni income / ujemny expense — klasy z layoutu).
- Szereg czasowy zbiorczy do wykresu: zbierz wszystkie daty wycen (distinct). Dla każdej daty:
wartość = suma po instrumentach ostatniej wyceny o dacie ≤ ta data; wpłacone_netto = Σ(depositwithdraw)
o dacie ≤ ta data; zarobek = wartość wpłacone_netto. Policz w PHP (metoda w Investments lub
InstrumentModel::portfolioTimeline()) — nie duplikuj logiki zarobku w widoku.
// ponytail: timeline liczony w PHP z kilku zapytań, bez okien SQL; przy setkach wycen i tak trywialne.
Widok investments/index (wzorzec dashboard/index + ApexCharts z layoutu):
- 3 kafelki, wykres liniowy (area/line) z 3 seriami: "Wpłacono netto", "Wartość", "Zarobek",
xaxis = daty wycen; noData gdy pusto.
- tabela per instrument pod wykresem.
</action>
<verify>curl /investments: kafelki i tabela zgodne z ręcznym wyliczeniem (ETF: netto 800, wartość 950, zarobek 150); JSON serii wykresu poprawny</verify>
<done>AC-4 (zarobek netto), AC-5 (pulpit: kafelki + wykres + tabela)</done>
</task>
</tasks>
<boundaries>
## Do Not Change
- Moduł finansów bieżących (categories, operations, dashboard) — działa, nie ruszać poza dodaniem 1 pozycji nav.
- `app/Config/Database.php`, `.env`, auth/filtry — bez zmian.
- Istniejące migracje `categories`/`operations` i seeder kategorii.
## Scope Limits
- Bez integracji API kursów, walut obcych, podatków, prowizji.
- Bez podkategorii instrumentów (płaska lista).
- Bez edycji wielu instrumentów jednym formularzem / importu CSV.
</boundaries>
<verification>
- [ ] `php spark migrate` — 3 nowe tabele `inv_*` na bazie hostido.
- [ ] `php spark db:seed DefaultInstrumentsSeeder` — Obligacje, ETF.
- [ ] CRUD instrumentów/operacji/wycen przez HTTP (create/edit/delete, filtry).
- [ ] Blokada usunięcia instrumentu z operacjami/wycenami (FK RESTRICT + kontroler).
- [ ] Zarobek per instrument = ostatnia wycena (Σwpłat Σwypłat); przykład ETF = 150.
- [ ] Pulpit /investments: kafelki + wykres 3 serie + tabela per instrument.
- [ ] Nawigacja pokazuje "Inwestycje"; brak regresji w istniejących modułach.
- [ ] Quality Radar relevant risks handled or deferred.
</verification>
<success_criteria>
- [ ] Wszystkie AC (AC-1..AC-5) Pass.
- [ ] Weryfikacja runtime kompletna (migrate/seed/serve/curl).
- [ ] STATE.md wskazuje ten plan; radar delta zapisana w UNIFY.
</success_criteria>
<output>
SUMMARY.md path: `.paul/plans/20260706-1430-modul-inwestycji/SUMMARY.md`
</output>
@@ -0,0 +1,88 @@
---
plan_id: 20260706-1430-modul-inwestycji
title: Moduł inwestycji — instrumenty, wpłaty/wypłaty, ręczne wyceny, pulpit
completed: 2026-07-06T14:55:00+02:00
storage: plan-first
quality_radar: degraded
---
# Summary: Moduł inwestycji — instrumenty, wpłaty/wypłaty, ręczne wyceny, pulpit
## Objective
Dodać moduł inwestycji: płaska lista instrumentów (kategorie główne, np. Obligacje, ETF), operacje wpłat/wypłat per instrument, ręczne snapshoty wyceny per instrument oraz pulpit inwestycji z wykresem zbiorczym w czasie i tabelą per instrument. W trakcie sesji dodano też pulpit globalny (`/dashboard`) i przepięto dotychczasowy pulpit finansów pod `/finances`.
## What Was Built
| Area | Result |
|------|--------|
| Schemat DB | 3 migracje `inv_instruments`, `inv_operations` (FK RESTRICT), `inv_valuations` (FK RESTRICT); seeder Obligacje/ETF |
| Modele | `InstrumentModel` (`withStats`, `portfolioTimeline`, `hasChildren` — logika zarobku netto w jednym miejscu), `InvOperationModel`, `ValuationModel` |
| CRUD | Instrumenty, operacje inwestycyjne (wpłaty/wypłaty), wyceny — pełny CRUD, filtry, blokada usunięcia instrumentu z powiązaniami |
| Pulpit inwestycji | `/investments`: kafelki (netto/wartość/zarobek), wykres liniowy 3-seryjny w czasie, tabela per instrument z % zwrotu |
| Pulpit globalny | `/dashboard`: kafel „Majątek razem" (saldo bieżące + wartość inwestycji) + sekcje Finanse/Inwestycje z linkami |
| Pulpit finansów | dotychczasowy dashboard przepięty pod `/finances` (`Dashboard::finances`) |
| Nawigacja | top-level „Pulpit" + dropdown „Finanse bieżące" (Pulpit finansów/Operacje/Kategorie) + dropdown „Inwestycje" (Pulpit/Wpłaty-wypłaty/Wyceny/Instrumenty) |
## Files Modified
- `app/Database/Migrations/2026-07-06-140001_CreateInvInstruments.php`, `...140002_CreateInvOperations.php`, `...140003_CreateInvValuations.php` — schemat inwestycji
- `app/Database/Seeds/DefaultInstrumentsSeeder.php` — idempotentny seed Obligacje/ETF
- `app/Models/InstrumentModel.php`, `InvOperationModel.php`, `ValuationModel.php` — domena inwestycji
- `app/Controllers/Instruments.php`, `InvOperations.php`, `Valuations.php`, `Investments.php` — CRUD + pulpit inwestycji
- `app/Controllers/Dashboard.php``index()` globalny + `finances()`
- `app/Views/instruments/{index,form}.php`, `inv_operations/{index,form}.php`, `valuations/{index,form}.php`, `investments/index.php` — widoki inwestycji
- `app/Views/dashboard/index.php` (globalny, przepisany), `app/Views/dashboard/finances.php` (przeniesiony finansowy)
- `app/Config/Routes.php` — trasy `investments/*`, `instruments/*`, `finances`
- `app/Views/layout/main.php` — nawigacja zgrupowana w dropdowny
## Acceptance Criteria Results
| Criterion | Status | Evidence |
|-----------|--------|----------|
| AC-1: instrumenty CRUD + blokada usunięcia | Pass | curl: dodanie OK; usunięcie ETF z operacjami → 302 + „Nie można usunąć instrumentu z operacjami lub wycenami" |
| AC-2: operacje wpłata/wypłata + filtry | Pass | curl: deposit/withdraw zapisane; filtr `type=withdraw` zwraca wiersz 200,00; `instrument_id=1` → brak |
| AC-3: ręczne wyceny per instrument | Pass | curl: wycena 950 na 2026-07-01 zapisana, użyta do zarobku |
| AC-4: zarobek netto | Pass | ETF: netto 800,00 (1000200), zarobek 150,00 (950800), 18,75% |
| AC-5: pulpit inwestycji | Pass | kafelki 800/950/150; `timeline=[{date:2026-07-01,net:800,value:950,profit:150}]`; tabela per instrument |
## Verification Results
| Check | Result | Notes |
|-------|--------|-------|
| `php spark migrate` | Pass | 3 tabele `inv_*` na bazie hostido |
| `php spark db:seed DefaultInstrumentsSeeder` | Pass | Obligacje, ETF (idempotentnie) |
| `php -l` (7 nowych plików PHP + Dashboard) | Pass | brak błędów składni |
| `php spark serve` + curl (login/CRUD/filtry/blokada/pulpity) | Pass | pełny przepływ end-to-end |
| Zarobek netto ETF = 150 (18,75%) | Pass | zgodne z ręcznym wyliczeniem |
| `/dashboard` globalny + `/finances` | Pass | globalny: majątek + sekcje; finances: 200 + 3 wykresy |
| Dane testowe | Cleaned | `inv_operations=0`, `inv_valuations=0`, seed instrumentów zachowany |
## Quality Radar Results
**Status:** degraded
- New risks: brak nowych ryzyk duplikacji (jscpd/ast-grep wyłączone polityką). Kopiowanie wzorców zapytań z `OperationModel` świadome — osobna domena.
- Resolved risks: logika zarobku netto w jednym miejscu (`InstrumentModel::withStats`), nie powielana w widokach; typ operacji `deposit|withdraw` jako ENUM + walidacja `in_list`.
- Deferred risks: pełna indeksacja `codebase-memory-mcp` odłożona do `$paul-map-codebase`; weryfikacja runtime.
- Raw outputs: `.paul/codebase/tooling_status.md`
## Deviations
- **Wykonanie inline zamiast `delegation: auto`** — 3 zadania ściśle sekwencyjne, wspólne pliki (`Routes.php`, modele); delegacja tylko powielałaby odczyty wzorców.
- **`.env` toggle** — tymczasowo `production→development` na czas migracji/testów (lokalny MySQL zgaszony, dev łączy do zdalnej hostido), po weryfikacji przywrócony na `production`. Netto zero zmian, boundary `.env` zachowany.
- **Nawigacja rozszerzona** — zamiast jednego linku „Inwestycje" powstały dwa dropdowny (na prośbę użytkownika o zgrupowanie wcześniejszych pozycji).
- **Pulpit globalny + `/finances`** — poza pierwotnym zakresem planu; dodane na prośbę użytkownika (globalny `/dashboard`, finansowy przepięty pod `/finances`).
## Key Decisions / Patterns
- **Jedno źródło prawdy dla zarobku**: `InstrumentModel::withStats` liczy `net_invested = Σwpłat Σwypłat`, `profit = ostatnia_wycena net_invested`; kontrolery/widoki tylko konsumują.
- **FK RESTRICT** na `inv_operations`/`inv_valuations` — instrument z powiązaniami nie da się skasować (spójne z blokadą w kontrolerze).
- **Szereg czasowy portfela** liczony w PHP (`portfolioTimeline`) z kilku prostych zapytań — bez okien SQL; trywialne przy ręcznych wycenach.
- **Pulpit globalny** agreguje majątek = saldo bieżące + wartość inwestycji; szczegóły delegowane do `/finances` i `/investments`.
## Follow-up
- Wdrożenie produkcyjne `https://finanse.projectpro.pl/` — wgrać pliki + `vendor/`, uruchomić `php spark migrate` na serwerze (3 nowe tabele `inv_*`); `CI_ENVIRONMENT=production`. Schemat finansów bez zmian.
- Opcjonalnie: `$paul-map-codebase` (pełny indeks codebase-memory-mcp) przed kolejnym planem.
- Możliwe rozszerzenia: wykres per instrument, historia wartości vs wpłat na osi czasu z interpolacją, eksport.