--- phase: 105-orders-statistics plan: 01 subsystem: statistics tags: [statistics, orders, reporting, filters, ui, mysql-collation, vat-fallback] requires: - phase: none provides: existing orders + status dictionaries provides: - new menu section Statistics -> Orders - daily orders report by channel with totals - filters: date range, channels multiselect, status groups multiselect - net amount fallback (23% VAT) when source channel does not expose net affects: [orders analytics workflow] tech-stack: added: [] patterns: - daily SQL aggregation with dynamic channel columns - explicit COLLATE utf8mb4_unicode_ci on CAST expressions to avoid collation mix in mixed-charset schemas - brutto->netto deterministic fallback (/ 1.23) as last resort when source net is missing key-files: created: - src/Modules/Statistics/OrdersStatisticsController.php - src/Modules/Statistics/OrdersStatisticsRepository.php - resources/views/statistics/orders.php - .paul/TODO.md modified: - routes/web.php - resources/views/layouts/app.php - resources/lang/pl.php - resources/scss/app.scss - public/assets/css/app.css - .paul/docs/ARCHITECTURE.md - .paul/docs/DB_SCHEMA.md - .paul/docs/TECH_CHANGELOG.md key-decisions: - "Channel split: allegro as one channel, shopPRO by integration_id (shoppro:{id})" - "Default status groups: all active except group normalized to 'anulowane'" - "No DB migration - feature based on existing schema" - "Net fallback: gross / 1.23 when orders.total_without_tax is NULL or 0 (shopPRO does not send net)" - "Explicit COLLATE utf8mb4_unicode_ci on channel CASE (avoids 1271 Illegal mix of collations with CAST integer->CHAR)" patterns-established: - "Statistics module pattern (Controller + Repository + dedicated view)" - "Filter presence hidden flags to distinguish defaults from explicit empty multiselect" - "Post-APPLY hotfix pattern: collation bug caught by catching Throwable in repo → silently empty response; require explicit COLLATE on any CAST(int AS CHAR) used in IN clauses" completed: 2026-04-19 --- # Phase 105 Plan 01: Orders Statistics Summary Dodano nowa sekcje `Statystyki -> Zamowienia` z dziennym raportem ilosci/netto/brutto, filtrami (daty, kanaly multiselect, grupy statusow multiselect) oraz stopka `Podsumowanie`. Po poczatkowym wdrozeniu poprawiono bug kolizji collation w MySQL (widok zwracal pusta tabele) oraz dodano fallback wyliczania netto z brutto dla zrodel (shopPRO) ktore nie wysylaja kwoty netto. ## Acceptance Criteria Results | Criterion | Status | Notes | |-----------|--------|-------| | AC-1: Nowa pozycja menu i routing | Pass | `/statistics/orders` + nowy blok menu | | AC-2: Filtry dat/kanalow/grup statusow | Pass | Date range + multiselect + domyslne wykluczenie `anulowane` | | AC-3: Tabela dzienna + wiersz podsumowania | Pass | Dynamiczne kolumny per kanal, stopka `Podsumowanie` | | AC-4: Rozbicie na Allegro i shopPRO per integracja | Pass | `allegro` i `shoppro:{integration_id}` | ## Files Created/Modified | File | Change | Purpose | |------|--------|---------| | `src/Modules/Statistics/OrdersStatisticsController.php` | Created | Parsowanie filtrow, budowa view-modelu, render strony | | `src/Modules/Statistics/OrdersStatisticsRepository.php` | Created | Agregacja dzienna SQL + diagnostics + fallback netto | | `resources/views/statistics/orders.php` | Created | Widok formularza filtrow i tabeli dziennej | | `routes/web.php` | Modified | Route `GET /statistics/orders` za `AuthMiddleware` | | `resources/views/layouts/app.php` | Modified | Sidebar: grupa `Statystyki -> Zamowienia` | | `resources/lang/pl.php` | Modified | Klucze `navigation.statistics*`, `statistics.orders.*` | | `resources/scss/app.scss` | Modified | Kompaktowy layout filtrow i tabeli statystyk | | `public/assets/css/app.css` | Modified | Build SCSS | | `.paul/docs/ARCHITECTURE.md` | Modified | Opis modulu Statistics + endpoint | | `.paul/docs/DB_SCHEMA.md` | Modified | Adnotacja: feature bez migracji | | `.paul/docs/TECH_CHANGELOG.md` | Modified | Wpis + hotfix collation + hotfix fallback netto | | `.paul/TODO.md` | Created | Tag `STAT-NET`: docelowy netto z shopPRO / order_items.tax_rate | ## Decisions Made | Decision | Rationale | Impact | |----------|-----------|--------| | Explicit `COLLATE utf8mb4_unicode_ci` na CASE zwracajacym `channel_key` | `CAST(integration_id AS CHAR)` zwracal `utf8mb4_bin`, przez co `IN (...)` z parametrami (`utf8mb4_general_ci`) rzucal `1271 Illegal mix of collations`. Repo lapalo `Throwable` i zwracalo `[]`, widok byl pusty. | Statystyki dzialaja; pattern udokumentowany dla przyszlych agregacji po `integration_id` | | Fallback netto `ROUND(gross / 1.23, 2)` gdy `total_without_tax` puste | shopPRO nie przesyla netto ani na poziomie zamowienia, ani pozycji (`order_items.original_price_without_tax` = NULL); bez fallbacku kolumna `Netto` pokazywala 0 | Kolumna `Netto` pokazuje sensowne wartosci; docelowe rozwiazanie (STAT-NET) zapisane w TODO | | Status codes pozostaja `order_statuses.code` (LOWER-normalized) | Mapping Allegro zachowuje zgodnosc z `OrdersRepository` dzieki `allegro_order_status_mappings` | Spojna semantyka statusow w module Orders i Statistics | ## Deviations from Plan ### Summary | Type | Count | Impact | |------|-------|--------| | Auto-fixed | 1 | Essential — zerowe wyniki zanim hotfix | | Scope additions | 1 | Uzasadnione — brak fallback netto byl pokazywany jako bug | | Deferred | 1 | `STAT-NET` w `.paul/TODO.md` | ### Auto-fixed Issues **1. [SQL] Kolizja collation w channelSql() — statystyki zwracaly pusta tabele** - **Found during:** Post-APPLY walidacja — user zglosil "strona statystyk nie pokazuje zamowien" - **Issue:** `CAST(o.integration_id AS CHAR)` daje `utf8mb4_bin`, `CONCAT("shoppro:", ...)` + `IN (:ch0,:ch1)` rzuca `SQLSTATE[HY000] 1271`. `try/catch(Throwable)` polykal blad → puste dane. - **Fix:** `COLLATE utf8mb4_unicode_ci` na `CAST(...)` oraz na calym wyrazeniu CASE zwracajacym `channel_key`. - **Files:** `src/Modules/Statistics/OrdersStatisticsRepository.php` (`channelSql`) - **Verification:** End-to-end test na produkcyjnym DB (host700513) — 41 wierszy zagregowanych dla 2026-04-01..30, kanaly: `allegro, shoppro:5, shoppro:6, shoppro:7`. ### Scope Additions **1. [Feature] Fallback netto 23% VAT** - **Why:** shopPRO nie wysyla `total_without_tax`; `order_items.original_price_without_tax` rowniez NULL. Bez fallbacku kolumna Netto byla bezuzyteczna (same zera). - **Scope:** `netAmountSql()` rozszerzony o `CASE ... WHEN total_without_tax > 0 THEN total_without_tax ... WHEN total_with_tax > 0 THEN ROUND(total_with_tax/1.23, 2) ELSE 0 END`. - **Files:** `src/Modules/Statistics/OrdersStatisticsRepository.php`, `.paul/docs/TECH_CHANGELOG.md`, `.paul/TODO.md` ### Deferred Items - **STAT-NET:** Pobieranie netto z shopPRO (API) lub dokladne wyliczanie z `order_items.tax_rate` (unikniecie sztywnego 23%). Backfill historycznych rekordow. Zapisane w `.paul/TODO.md`. ## Verification Executed - `php -l src/Modules/Statistics/OrdersStatisticsRepository.php` - `php -l src/Modules/Statistics/OrdersStatisticsController.php` - `php -l routes/web.php` - `php -l resources/views/layouts/app.php` - `php -l resources/views/statistics/orders.php` - `php -l resources/lang/pl.php` - `npm run build:css` - End-to-end on production DB (post-hotfix): 41 agregowanych wierszy dla 2026-04, netto/brutto zgodne (np. shoppro:7 2026-04-19: orders=17, net=989.45, gross=1217.05). ## Skill Audit | Expected | Invoked | Notes | |----------|---------|-------| | `sonar-scanner` (required) | o | Gap — nie uruchomiony podczas tej sesji UNIFY. `.scannerwork/report-task.txt` pokazuje modyfikacje wczesniej; skan dedykowany do tej zmiany nie wykonany. | | /feature-dev (optional) | o | Nie uzywany | | /frontend-design (optional) | o | Nie uzywany | | /code-review (optional) | o | Nie uzywany | | /simplify (optional) | o | Nie uzywany | ## Next Phase Readiness **Ready:** - Modul Statistics dziala; schemat patternu dla kolejnych raportow (per-kanalowych). - TODO `STAT-NET` przechowane z konkretnym planem realizacji. **Concerns:** - Kolumna `Netto` na kanalach shopPRO jest obecnie estymatem (sztywne 23% VAT). Jesli pojawia sie produkty o innej stawce (5%, 8%, 0%), agregat bedzie zawyzal netto dla tych pozycji. **Blockers:** - None. --- *Phase: 105-orders-statistics, Plan: 01* *Completed: 2026-04-19*