Files
2026-07-06 20:16:00 +02:00

14 KiB
Raw Permalink Blame History

plan_id, title, storage, legacy_phase, created, status, type, autonomous, delegation, files_modified, quality_radar
plan_id title storage legacy_phase created status type autonomous delegation files_modified quality_radar
20260706-1430-modul-inwestycji Moduł inwestycji — instrumenty, wpłaty/wypłaty, ręczne wyceny, pulpit plan-first null 2026-07-06T14:30:00+02:00 planned execute true auto
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
degraded
## 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.

## 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

- 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 | %).

<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>
Brak `.paul/SPECIAL-FLOWS.md` — sekcja skills pominięta.

<acceptance_criteria>

AC-1: Instrumenty (CRUD, płaska lista)

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)

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)

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)

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)

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>

Task 1: Schemat DB + modele + seeder instrumentów 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 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).
php spark migrate && php spark db:seed DefaultInstrumentsSeeder — 3 tabele utworzone, 2 instrumenty AC-1 (schemat instrumentów), AC-2 (schemat operacji), AC-3 (schemat wycen), AC-4 (logika zarobku w withStats) Task 2: Kontrolery CRUD + widoki + routing + nawigacja 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 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".
php spark serve + curl: dodanie instrumentu, wpłaty, wypłaty, wyceny; filtr po instrumencie; blokada usunięcia instrumentu z operacjami AC-1, AC-2, AC-3 (pełny CRUD i filtry przez UI/HTTP) Task 3: Pulpit inwestycji — agregacja, wykres zbiorczy, tabela per instrument app/Controllers/Investments.php, app/Views/investments/index.php 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.
curl /investments: kafelki i tabela zgodne z ręcznym wyliczeniem (ETF: netto 800, wartość 950, zarobek 150); JSON serii wykresu poprawny AC-4 (zarobek netto), AC-5 (pulpit: kafelki + wykres + tabela) ## 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.
- [ ] `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.

<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>
SUMMARY.md path: `.paul/plans/20260706-1430-modul-inwestycji/SUMMARY.md`