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