Files
orderPRO/.paul/phases/07-pre-expansion-fixes/07-01-PLAN.md
Jacek Pyziak 03c18f6782 plan(07-pre-expansion-fixes): create 5 plans for pre-expansion fixes
07-01: Performance — N+1 subqueries, information_schema static, DB indexes
07-02: Stability — SSL verification (4 clients), cron throttle→DB, migration 000014b
07-03: UX — orderpro_to_allegro disable, orders list items 14-17
07-04: Tests — AllegroTokenManager + AllegroOrderImportService unit tests
07-05: InPost ShipmentProviderInterface (replaces allegro_wza workaround)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 17:44:42 +01:00

9.0 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous
phase plan type wave depends_on files_modified autonomous
07-pre-expansion-fixes 01 execute 1
src/Modules/Orders/OrdersRepository.php
database/migrations/20260313_000048_add_orders_performance_indexes.sql
true
## Goal Wyeliminować dwa bottlenecki wydajnościowe w module Orders: 4 correlated subqueries na każdy wiersz listy zamówień oraz zapytanie do information_schema przy każdym żądaniu HTTP.

Purpose

Lista zamówień jest głównym widokiem aplikacji. Przy 50 wierszach na stronę, correlated subqueries oznaczają 200+ dodatkowych zapytań per page load. canResolveMappedMedia() uderza w information_schema przy każdym żądaniu do OrdersRepository — notoriously slow na MySQL. Oba problemy narastają z liczbą zamówień.

Output

  • OrdersRepository::buildListSql() — 4 subqueries zastąpione aggregating LEFT JOIN
  • OrdersRepository::canResolveMappedMedia() — wynik cachowany w static property
  • Nowa migracja z brakującymi indeksami dla orders table
## Project Context @.paul/PROJECT.md

Source Files

@src/Modules/Orders/OrdersRepository.php

<acceptance_criteria>

AC-1: Brak correlated subqueries w liście zamówień

Given OrdersRepository::buildListSql() zawiera 4 correlated subqueries (items_count, items_qty, shipments_count, documents_count)
When metoda buduje SQL dla listy zamówień
Then SQL nie zawiera "(SELECT COUNT(*) FROM order_items" ani podobnych correlated subqueries
  AND zwracane kolumny items_count, items_qty, shipments_count, documents_count nadal istnieją
  AND logika transformOrderRow() w pełni działa (te pola nadal trafiają do wyniku)

AC-2: information_schema nie jest odpytywany per-request

Given canResolveMappedMedia() używa instance property $this->supportsMappedMedia jako cache
When OrdersRepository jest instanciowany dwukrotnie w tej samej sesji PHP (np. dwa wywołania repository)
Then information_schema.COLUMNS jest zapytany co najwyżej raz na cykl PHP (static property)

AC-3: Brakujące indeksy dodane migracją

Given tabela orders nie ma indeksów na kolumnach source, external_status_id, ordered_at
When tworzona jest nowa migracja i wykonana przez migrator
Then tabela orders ma indeksy na: source, external_status_id, ordered_at
  AND migracja jest idempotentna (IF NOT EXISTS lub ADD INDEX IF NOT EXISTS lub ALTER IGNORE)

</acceptance_criteria>

Task 1: Zamień 4 correlated subqueries na aggregating LEFT JOINs w buildListSql() src/Modules/Orders/OrdersRepository.php W metodzie `buildListSql()` (linie ~135-171) zamień: ```sql (SELECT COUNT(*) FROM order_items oi WHERE oi.order_id = o.id) AS items_count, (SELECT COALESCE(SUM(oi.quantity), 0) FROM order_items oi WHERE oi.order_id = o.id) AS items_qty, (SELECT COUNT(*) FROM order_shipments sh WHERE sh.order_id = o.id) AS shipments_count, (SELECT COUNT(*) FROM order_documents od WHERE od.order_id = o.id) AS documents_count ``` Na: ```sql COALESCE(oi_agg.items_count, 0) AS items_count, COALESCE(oi_agg.items_qty, 0) AS items_qty, COALESCE(sh_agg.shipments_count, 0) AS shipments_count, COALESCE(od_agg.documents_count, 0) AS documents_count ``` I dodaj do klauzuli FROM (po istniejących LEFT JOINach, przed WHERE): ```sql LEFT JOIN ( SELECT order_id, COUNT(*) AS items_count, COALESCE(SUM(quantity), 0) AS items_qty FROM order_items GROUP BY order_id ) oi_agg ON oi_agg.order_id = o.id LEFT JOIN ( SELECT order_id, COUNT(*) AS shipments_count FROM order_shipments GROUP BY order_id ) sh_agg ON sh_agg.order_id = o.id LEFT JOIN ( SELECT order_id, COUNT(*) AS documents_count FROM order_documents GROUP BY order_id ) od_agg ON od_agg.order_id = o.id ``` UWAGA: buildListSql() generuje SQL jako string — upewnij się że nowe JOINy są wstawione przed `$whereSql` (który jest konkatenowany na końcu z klauzulą WHERE).
Sprawdź jak wygląda $whereSql — czy zawiera "WHERE" czy tylko "AND ..."?
Jeśli WHERE jest w $whereSql, nowe JOINy idą bezpośrednio przed nim.

NIE zmieniaj metody transformOrderRow() — klucze tablicy pozostają te same.
php -l src/Modules/Orders/OrdersRepository.php grep -c "SELECT COUNT\|SELECT COALESCE\|subquery" src/Modules/Orders/OrdersRepository.php # Powinno zwrócić 0 dla wzorców correlated subquery w buildListSql AC-1 satisfied: buildListSql() używa LEFT JOIN zamiast correlated subqueries Task 2: Zamień instance property na static w canResolveMappedMedia() src/Modules/Orders/OrdersRepository.php Znajdź deklarację instance property (okolice klasy): ```php private ?bool $supportsMappedMedia = null; ``` Zmień na: ```php private static ?bool $supportsMappedMedia = null; ``` W metodzie `canResolveMappedMedia()` zmień referencje z `$this->supportsMappedMedia` na `self::$supportsMappedMedia` (we wszystkich miejscach metody — przypisanie i odczyt).
To jedyna zmiana. Nie modyfikuj logiki zapytania ani żadnej innej metody.
php -l src/Modules/Orders/OrdersRepository.php grep -n "supportsMappedMedia" src/Modules/Orders/OrdersRepository.php # Powinno pokazać: private static ?bool i self::$supportsMappedMedia AC-2 satisfied: canResolveMappedMedia() używa static property Task 3: Migracja z brakującymi indeksami dla tabeli orders database/migrations/20260313_000048_add_orders_performance_indexes.sql Stwórz plik `database/migrations/20260313_000048_add_orders_performance_indexes.sql`.
Przed pisaniem migracji, sprawdź jakie indeksy już istnieją:
```bash
grep -n "INDEX\|KEY " database/migrations/20260302_000018_create_orders_tables_and_schedule.sql
grep -rn "ADD INDEX\|ADD KEY" database/migrations/ | grep -i "order"
```

Migracja powinna dodać brakujące indeksy:
```sql
-- Indeksy dla typowych filtrów i sortowań na liście zamówień
ALTER TABLE orders
    ADD INDEX IF NOT EXISTS orders_source_idx (source),
    ADD INDEX IF NOT EXISTS orders_external_status_idx (external_status_id),
    ADD INDEX IF NOT EXISTS orders_ordered_at_idx (ordered_at),
    ADD INDEX IF NOT EXISTS orders_source_status_idx (source, external_status_id);

-- Indeks dla allegro status mapping lookup (JOIN w buildListSql)
ALTER TABLE allegro_order_status_mappings
    ADD INDEX IF NOT EXISTS allegro_status_code_idx (allegro_status_code)
-- tylko jeśli ten indeks nie istnieje (sprawdź migration 000038)
```

WAŻNE: Sprawdź aktualny schemat przed dodaniem indeksu — nie twórz duplikatów.
Użyj `IF NOT EXISTS` gdzie MySQL to obsługuje (MySQL 8.0+), albo sformułuj jako
osobne polecenia z IGNORE: `ALTER TABLE orders ADD IGNORE INDEX ...`

Sprawdź też `database/migrations/20260308_000038_ensure_order_status_mappings_table.sql`
aby wiedzieć jakie indeksy allegro_order_status_mappings już ma.
php -l database/migrations/20260313_000048_add_orders_performance_indexes.sql 2>/dev/null || echo "SQL file - no php lint needed" # Sprawdź czy plik istnieje i nie jest pusty cat database/migrations/20260313_000048_add_orders_performance_indexes.sql AC-3 satisfied: migracja z indeksami na source, external_status_id, ordered_at istnieje

DO NOT CHANGE

  • Logika filtrowania ($whereSql) — tylko zmiana mechanizmu agregacji, nie filtrów
  • Metoda transformOrderRow() — zwracane klucze muszą pozostać identyczne
  • Inne metody OrdersRepository poza buildListSql() i canResolveMappedMedia()
  • Istniejące pliki migracji — tylko NOWY plik 000048

SCOPE LIMITS

  • Tylko optymalizacja zapytania list; nie zmieniamy zapytań detail/find
  • Nie dodajemy nowych kolumn ani nie zmieniamy schematu tabel — tylko indeksy
  • Nie implementujemy cache'owania na poziomie Redis/Memcached — tylko static property
Przed zamknięciem planu: - [ ] php -l src/Modules/Orders/OrdersRepository.php — brak błędów - [ ] grep buildListSql src/Modules/Orders/OrdersRepository.php — brak "SELECT COUNT(*) FROM order_items WHERE oi.order_id" - [ ] grep "supportsMappedMedia" src/Modules/Orders/OrdersRepository.php — pokazuje "static" - [ ] Plik migracji 000048 istnieje i jest nieopusty - [ ] Lista zamówień ładuje się bez błędów PHP

<success_criteria>

  • 4 correlated subqueries usunięte z buildListSql()
  • canResolveMappedMedia() z static property
  • Migracja 000048 z indeksami na source, external_status_id, ordered_at
  • Zero błędów składniowych PHP </success_criteria>
Po zakończeniu utwórz `.paul/phases/07-pre-expansion-fixes/07-01-SUMMARY.md`