"
- }
- },
- "categories": [1, 5],
- "products_related": [10, 20]
-}
-```
-
-Wymagane: `languages` (min. 1 jezyk z `name`) oraz `price_brutto`.
-
-Odpowiedz (HTTP 201):
-```json
-{
- "status": "ok",
- "data": {
- "id": 42
- }
-}
-```
-
-#### Aktualizacja produktu
-```
-PUT api.php?endpoint=products&action=update&id={product_id}
-Content-Type: application/json
-
-{
- "price_brutto": 129.99,
- "status": 1,
- "languages": {
- "pl": {
- "name": "Zaktualizowana nazwa"
- }
- }
-}
-```
-
-Partial update — wystarczy przeslac tylko zmienione pola. Pola nieprzeslane zachowuja aktualna wartosc.
-
-Odpowiedz: pelne dane produktu (jak w `get`).
-
-### Warianty produktow
-
-#### Lista wariantow produktu
-```
-GET api.php?endpoint=products&action=variants&id={product_id}
-```
-
-Zwraca warianty produktu nadrzednego wraz z dostepnymi atrybutami.
-
-Odpowiedz:
-```json
-{
- "status": "ok",
- "data": {
- "product_id": 1,
- "available_attributes": [
- {
- "id": 1,
- "type": 1,
- "status": 1,
- "names": {"pl": "Kolor", "en": "Color"},
- "values": [
- {"id": 3, "names": {"pl": "Czerwony", "en": "Red"}, "is_default": 0, "impact_on_the_price": null},
- {"id": 4, "names": {"pl": "Niebieski", "en": "Blue"}, "is_default": 0, "impact_on_the_price": 10.0}
- ]
- }
- ],
- "variants": [
- {
- "id": 101,
- "permutation_hash": "1-3",
- "sku": "PROD-001-RED",
- "ean": null,
- "price_brutto": 109.99,
- "price_brutto_promo": null,
- "price_netto": 89.42,
- "price_netto_promo": null,
- "quantity": 5,
- "stock_0_buy": 0,
- "weight": 0.5,
- "status": 1,
- "attributes": [
- {"attribute_id": 1, "attribute_names": {"pl": "Kolor"}, "value_id": 3, "value_names": {"pl": "Czerwony"}}
- ]
- }
- ]
- }
-}
-```
-
-#### Tworzenie wariantu
-```
-POST api.php?endpoint=products&action=create_variant&id={product_id}
-Content-Type: application/json
-
-{
- "attributes": {"1": 3, "2": 5},
- "sku": "PROD-001-RED-L",
- "ean": "5901234123458",
- "price_brutto": 109.99,
- "quantity": 5,
- "weight": 0.5
-}
-```
-
-Wymagane: `attributes` (mapa attribute_id -> value_id, min. 1). Kombinacja atrybutow musi byc unikalna.
-
-Odpowiedz (HTTP 201): pelne dane wariantu.
-
-#### Aktualizacja wariantu
-```
-PUT api.php?endpoint=products&action=update_variant&id={variant_id}
-Content-Type: application/json
-
-{
- "sku": "PROD-001-RED-XL",
- "price_brutto": 119.99,
- "quantity": 3
-}
-```
-
-Partial update — mozna zmienic: sku, ean, price_brutto, price_netto, price_brutto_promo, price_netto_promo, quantity, stock_0_buy, weight, status.
-
-Odpowiedz: pelne dane wariantu.
-
-#### Usuwanie wariantu
-```
-DELETE api.php?endpoint=products&action=delete_variant&id={variant_id}
-```
-
-Odpowiedz:
-```json
-{
- "status": "ok",
- "data": {"id": 101, "deleted": true}
-}
-```
-
-### Slowniki
-
-#### Lista statusow zamowien
-```
-GET api.php?endpoint=dictionaries&action=statuses
-```
-
-Odpowiedz:
-```json
-{
- "status": "ok",
- "data": [
- {"id": 0, "name": "Nowe"},
- {"id": 1, "name": "Oplacone"},
- {"id": 4, "name": "W realizacji"},
- {"id": 6, "name": "Wyslane"}
- ]
-}
-```
-
-#### Lista metod transportu
-```
-GET api.php?endpoint=dictionaries&action=transports
-```
-
-#### Lista metod platnosci
-```
-GET api.php?endpoint=dictionaries&action=payment_methods
-```
-
-#### Lista atrybutow
-```
-GET api.php?endpoint=dictionaries&action=attributes
-```
-
-Zwraca aktywne atrybuty z wartosciami i wielojezycznymi nazwami.
-
-Odpowiedz:
-```json
-{
- "status": "ok",
- "data": [
- {
- "id": 1,
- "type": 1,
- "status": 1,
- "names": {"pl": "Kolor", "en": "Color"},
- "values": [
- {"id": 3, "names": {"pl": "Czerwony"}, "is_default": 0, "impact_on_the_price": null},
- {"id": 4, "names": {"pl": "Niebieski"}, "is_default": 1, "impact_on_the_price": 10.0}
- ]
- }
- ]
-}
-```
-
-## Polling
-
-Aby pobierac tylko nowe/zmienione zamowienia, uzyj parametru `updated_since`:
-
-```
-GET api.php?endpoint=orders&action=list&updated_since=2026-02-19 12:00:00
-```
-
-Kolumna `updated_at` w `pp_shop_orders` jest aktualizowana automatycznie przy kazdej modyfikacji zamowienia (zmiana statusu, platnosci, edycja danych, tworzenie zamowienia).
-
-## Konfiguracja
-
-Klucz API ustawia sie w panelu admina w ustawieniach sklepu lub bezposrednio w bazie:
-
-```sql
-INSERT INTO pp_settings (param, value) VALUES ('api_key', 'twoj-klucz-api');
--- lub
-UPDATE pp_settings SET value = 'twoj-klucz-api' WHERE param = 'api_key';
-```
-
-## Architektura
-
-- Entry point: `api.php`
-- Router: `\api\ApiRouter` (`autoload/api/ApiRouter.php`)
-- Kontrolery: `autoload/api/Controllers/`
- - `OrdersApiController` — zamowienia (5 akcji)
- - `ProductsApiController` — produkty (8 akcji: list, get, create, update, variants, create_variant, update_variant, delete_variant)
- - `DictionariesApiController` — slowniki (4 akcje: statuses, transports, payment_methods, attributes)
diff --git a/DOCS/ARCHITECTURE.md b/DOCS/ARCHITECTURE.md
new file mode 100644
index 0000000..bb3cc60
--- /dev/null
+++ b/DOCS/ARCHITECTURE.md
@@ -0,0 +1,152 @@
+# Architecture
+
+## Status
+- Projekt po resecie do trybu `users-only`.
+
+## Moduly aktywne
+- `App\Modules\Auth`
+- `App\Modules\Orders`
+- `App\Modules\Users`
+- `App\Modules\Settings`
+
+## Routing
+- `GET /login`, `POST /login`, `POST /logout`
+- `GET /settings/users`, `POST /settings/users`
+- `GET /orders` (redirect do `/orders/list`)
+- `GET /orders/list`
+- `GET /orders/{id}`
+- `GET /users` (redirect do `/settings/users`)
+- `POST /users` (compat route)
+- `GET /settings` (redirect do `/settings/users`)
+- `GET /settings/database`
+- `POST /settings/database/migrate`
+- `GET /settings/statuses`
+- `POST /settings/status-groups`
+- `POST /settings/status-groups/update`
+- `POST /settings/status-groups/delete`
+- `POST /settings/status-groups/reorder`
+- `POST /settings/statuses/create`
+- `POST /settings/statuses/update`
+- `POST /settings/statuses/delete`
+- `POST /settings/statuses/reorder`
+- `GET /health`, `GET /` (redirect)
+
+## Korekta logowania
+- `AuthController::showLogin(Request): Response`:
+ - dla zalogowanego usera redirect na `/settings/users` (zamiast nieistniejacego `/dashboard`).
+- `AuthController::login(Request): Response`:
+ - po poprawnym logowaniu redirect na `/settings/users`.
+
+## Kluczowe klasy
+- `App\Core\Application`
+- `App\Modules\Auth\AuthController`
+- `App\Modules\Auth\AuthService`
+- `App\Modules\Orders\OrdersController`
+- `App\Modules\Orders\OrdersRepository`
+- `App\Modules\Settings\SettingsController`
+- `App\Modules\Settings\OrderStatusRepository`
+- `App\Modules\Users\UsersController`
+- `App\Modules\Users\UserRepository`
+
+## Przeplyw Zamowienia > Lista zamowien
+- `GET /orders/list`:
+ - `OrdersController::index(Request): Response`
+ - pobiera dane listy przez `OrdersRepository::paginate(...)`,
+ - pobiera slowniki filtrow (`sourceOptions()`, `statusOptions()`), statystyki (`quickStats()`), agregaty statusow (`statusCounts()`) i konfiguracje grup/statusow (`statusPanelConfig()`),
+ - buduje panel statusow z grupami i licznikami (`buildStatusPanel(...)`) z linkami filtrujacymi po statusie,
+ - panel statusow i etykiety statusow sa zgodne z konfiguracja z `Ustawienia > Statusy` (z fallbackiem `Pozostale`),
+ - renderuje podglad pozycji zamowienia (nazwa, miniatura, ilosc) na bazie `order_items`,
+ - obsluguje modal podgladu zdjecia pozycji po kliknieciu miniatury,
+ - normalizuje status techniczny na etykiete biznesowa (bez kodu statusu),
+ - renderuje widok `resources/views/orders/list.php` i komponent tabeli `resources/views/components/table-list.php`.
+- `GET /orders/{id}`:
+ - `OrdersController::show(Request): Response`
+ - pobiera szczegoly przez `OrdersRepository::findDetails(int $orderId)`, statystyke statusow przez `statusCounts()` oraz konfiguracje przez `statusPanelConfig()`,
+ - buduje panel statusow z grupami i licznikami (`buildStatusPanel(...)`),
+ - renderuje klikalne taby sekcji i przelaczanie paneli po stronie klienta (JS w `orders/show.php`),
+ - renderuje widok `resources/views/orders/show.php` z sekcjami:
+ - pozycje zamowienia,
+ - szczegoly zamowienia,
+ - platnosc i wysylka,
+ - adresy (`customer`, `invoice`, `delivery`),
+ - notatki i historia statusow.
+- Sidebar ma oddzielna grupe nawigacyjna:
+ - `Zamowienia` -> `Lista zamowien`.
+
+## Skrypty techniczne (CLI)
+- `bin/fix_status_codes.php`
+ - naprawa kodow grup/statusow (transliteracja PL -> ASCII, tryb `--dry-run`, opcja `--use-remote`).
+- `bin/deploy_and_seed_orders.php`
+ - aplikuje generyczny schema zamowien z `database/drafts/20260302_orders_schema_v1.sql`,
+ - seeduje dane testowe (`--count`, `--append`, `--use-remote`, `--profile=default|realistic`),
+ - profil `realistic` utrzymuje spojne zaleznosci miedzy:
+ - statusem zamowienia,
+ - statusem i kwota platnosci,
+ - obecnoscia wysylek i dokumentow,
+ - historia przejsc statusow (deterministyczne sciezki zamiast losowych przeskokow).
+
+## Przeplyw Ustawienia > Statusy
+- `GET /settings/statuses`:
+ - `SettingsController::statuses(Request): Response`
+ - pobiera dane przez `OrderStatusRepository::listGroups()` i `OrderStatusRepository::listStatuses()`,
+ - renderuje widok `resources/views/settings/statuses.php`.
+- `POST /settings/status-groups`:
+ - `SettingsController::createStatusGroup(Request): Response`
+ - waliduje CSRF i dane (`name`, `color_hex`, `is_active`),
+ - `code` jest generowany automatycznie z `name` i nie jest edytowany z UI,
+ - zapisuje przez `OrderStatusRepository::createGroup(...)`.
+- `POST /settings/status-groups/update`:
+ - `SettingsController::updateStatusGroup(Request): Response`
+ - waliduje istnienie grupy i aktualizuje rekord przez `updateGroup(...)`,
+ - `code` pozostaje bez zmian (read-only po utworzeniu).
+- `POST /settings/status-groups/delete`:
+ - `SettingsController::deleteStatusGroup(Request): Response`
+ - usuwa grupe przez `deleteGroup(...)`; statusy z tej grupy usuwane sa kaskadowo (FK).
+- `POST /settings/status-groups/reorder`:
+ - `SettingsController::reorderStatusGroups(Request): Response`
+ - zapisuje kolejnosc drag-and-drop grup przez `OrderStatusRepository::reorderGroups(...)`,
+ - endpoint jest wywolywany automatycznie po upuszczeniu elementu listy (auto-save).
+- `POST /settings/statuses/create`:
+ - `SettingsController::createStatus(Request): Response`
+ - waliduje grupe, pola statusu i zapisuje przez `createStatus(...)`.
+- `POST /settings/statuses/update`:
+ - `SettingsController::updateStatus(Request): Response`
+ - waliduje dane i aktualizuje status przez `updateStatus(...)`,
+ - `code` pozostaje bez zmian (read-only po utworzeniu).
+- `POST /settings/statuses/delete`:
+ - `SettingsController::deleteStatus(Request): Response`
+ - usuwa status przez `deleteStatus(...)`.
+- `POST /settings/statuses/reorder`:
+ - `SettingsController::reorderStatuses(Request): Response`
+ - zapisuje kolejnosc drag-and-drop statusow w ramach grupy przez `OrderStatusRepository::reorderStatusesByGroup(...)`,
+ - endpoint jest wywolywany automatycznie po upuszczeniu elementu listy (auto-save).
+
+## Nawigacja ustawien
+- Sidebar (`resources/views/layouts/app.php`) ma nowy podlink:
+ - `Statusy` (`/settings/statuses`).
+
+## Przeplyw Ustawienia > Baza danych
+- `GET /settings/database`:
+ - `SettingsController::database(Request): Response`
+ - pobiera `Migrator::status()`, przekazuje statystyki i liste pending migracji do widoku `resources/views/settings/database.php`.
+- `POST /settings/database/migrate`:
+ - `SettingsController::migrate(Request): Response`
+ - waliduje CSRF,
+ - uruchamia `Migrator::runPending()`,
+ - zapisuje wynik do flash (`settings_success` / `settings_error`, `settings_migrate_logs`),
+ - wykonuje redirect do `GET /settings/database`.
+
+## Zmiany nawigacji
+- Sidebar ma teraz grupe `Ustawienia` z podlinkami:
+ - `Uzytkownicy` (`/settings/users`)
+ - `Baza danych` (`/settings/database`)
+- `UsersController::index(Request): Response` ustawia:
+ - `activeMenu = settings`
+ - `activeSettings = users`
+- Usunieto wewnetrzny pasek `settings-nav` z widokow podstron ustawien.
+
+## Zasady aktualizacji
+- Przy kazdej zmianie dopisz:
+ - nowe klasy i metody (sygnatury + odpowiedzialnosc),
+ - zmiany przeplywu request -> controller -> repository,
+ - kontrakty wejscia/wyjscia istotnych metod.
diff --git a/DOCS/BACKLOG_MIKROZADANIA.md b/DOCS/BACKLOG_MIKROZADANIA.md
deleted file mode 100644
index 69dfe7a..0000000
--- a/DOCS/BACKLOG_MIKROZADANIA.md
+++ /dev/null
@@ -1,113 +0,0 @@
-# orderPRO - Backlog mikro-zadania (MVP)
-
-Data: 2026-02-19
-
-Legenda statusow:
-- `TODO` - do zrobienia
-- `DOING` - w trakcie
-- `DONE` - zakonczone
-- `BLOCKED` - zablokowane
-
-## Sprint 0 - Fundament
-
-1. `DONE` Utworzyc strukture katalogow projektu (`public`, `src`, `config`, `database`, `storage`, `bin`).
-2. `DONE` Dodac `composer.json` z podstawowymi bibliotekami.
-3. `DONE` Przygotowac `public/index.php` jako front controller.
-4. `DONE` Dodac prosty router i testowa trase `/health`.
-5. `DONE` Dodac loader konfiguracji `.env`.
-6. `TODO` Podlaczyc MySQL (polaczenie + test zapytania).
-7. `TODO` Utworzyc migracje tabeli `users`.
-8. `TODO` Dodac seed pierwszego uzytkownika admin.
-9. `TODO` Przygotowac bazowy layout HTML panelu (`header`, `sidebar`, `content`).
-10. `DONE` Przygotowac wyglad strony logowania (sam widok).
-11. `DONE` Dodac formularz logowania (email + haslo + submit).
-12. `DONE` Dodac endpoint POST logowania i walidacje danych.
-13. `DONE` Dodac sesje uzytkownika po poprawnym logowaniu.
-14. `DONE` Dodac middleware sprawdzajace zalogowanie.
-15. `DONE` Dodac wylogowanie (`POST /logout`).
-16. `DONE` Dodac komunikaty bledow logowania w widoku.
-17. `DONE` Dodac CSRF token do formularzy.
-18. `DONE` Dodac podstawowe logowanie bledow do pliku.
-19. `DONE` Przeniesc teksty UI do systemu tlumaczen (`resources/lang/pl.php` + helper `$t()`).
-20. `DONE` Dodac konfiguracje locale i podlaczyc translator do widokow i kontrolerow.
-21. `DONE` Przeniesc style do SCSS i uruchomic kompilacje/minifikacje do `public/assets/css`.
-22. `DONE` Dodac skrypty frontendowe do budowy styli (`npm run build:css`, `npm run watch:css`).
-23. `DONE` Ujednolicic design panelu i logowania pod styl adsPRO.
-
-## Sprint 1 - Zamowienia z wlasnego sklepu
-
-24. `TODO` Utworzyc migracje `orders`, `order_items`, `order_addresses`.
-25. `TODO` Dodac encje/repozytoria dla zamowien.
-26. `TODO` Dodac ekran listy zamowien (`/orders`) z paginacja.
-27. `TODO` Dodac ekran szczegolow zamowienia (`/orders/{id}`).
-28. `TODO` Dodac zmiane statusu zamowienia przyciskiem.
-29. `TODO` Dodac tabele `order_status_history`.
-30. `TODO` Zapisywac historie kazdej zmiany statusu.
-31. `TODO` Dodac konfiguracje kanalu "wlasny sklep" w tabeli `channels`.
-32. `TODO` Dodac klienta API wlasnego sklepu.
-33. `TODO` Dodac importer zamowien (reczne uruchomienie z panelu).
-34. `TODO` Dodac deduplikacje zamowien po `external_order_id`.
-35. `TODO` Dodac cron do cyklicznego importu.
-
-## Sprint 2 - Allegro
-
-36. `TODO` Utworzyc konfiguracje Allegro w `channels`.
-37. `TODO` Dodac ekran podpiecia konta Allegro (OAuth start/callback).
-38. `TODO` Zapisac i odswiezac tokeny Allegro.
-39. `TODO` Dodac importer zamowien Allegro.
-40. `TODO` Dodac mapowanie statusow Allegro -> statusy lokalne.
-41. `TODO` Dodac logowanie bledow API Allegro do `sync_errors`.
-
-## Sprint 3 - Apaczka
-
-42. `TODO` Utworzyc migracje `shipments`, `shipment_labels`.
-43. `TODO` Dodac konfiguracje API Apaczka.
-44. `TODO` Dodac przycisk "Utworz przesylke" na szczegolach zamowienia.
-45. `TODO` Dodac wysylke danych paczki do Apaczka.
-46. `TODO` Zapisac numer tracking i status utworzenia.
-47. `TODO` Dodac pobieranie etykiety PDF.
-48. `TODO` Dodac cron odswiezajacy statusy przesylek.
-
-## Sprint 4 - Dokumenty i magazyn uproszczony
-
-49. `TODO` Utworzyc migracje `documents`, `inventory_items`, `inventory_reservations`.
-50. `TODO` Dodac integracje z systemem dokumentow (API zewnetrzne).
-51. `TODO` Dodac przycisk "Wystaw dokument" na szczegolach zamowienia.
-52. `TODO` Zapisac identyfikator/link dokumentu z systemu zewnetrznego.
-53. `TODO` Dodac rezerwacje stanu po imporcie zamowienia.
-54. `TODO` Dodac zwolnienie rezerwacji po anulowaniu zamowienia.
-55. `TODO` Dodac prosty widok stanow magazynowych.
-
-## Sprint 5 - Stabilizacja
-
-56. `TODO` Dodac tabele `jobs` i `failed_jobs`.
-57. `TODO` Przeniesc ciezsze operacje integracyjne do jobow.
-58. `TODO` Dodac retry z rosnacym opoznieniem.
-59. `TODO` Dodac panel "Bledy synchronizacji".
-60. `TODO` Dodac testy najwazniejszych flow (logowanie, import, przesylka).
-61. `TODO` Dodac skrypt backupu bazy (cron nocny).
-
-## Nastepne zadanie (start)
-1. `TODO` Podlaczyc MySQL (polaczenie + test zapytania):
-- klasa polaczenia PDO,
-- konfiguracja z `.env`,
-- prosty endpoint kontrolny DB.
-
-## Sprint 1.5 - Modul produktow (bez importu/eksportu)
-
-62. `DONE` Utworzyc migracje tabel: `products`, `product_translations`, `product_images`, `product_categories`.
-63. `DONE` Utworzyc migracje tabel: `product_variants`, `product_variant_attributes`.
-64. `DONE` Utworzyc migracje tabel slownikowych: `attributes`, `attribute_translations`, `attribute_values`, `attribute_value_translations`.
-65. `DONE` Utworzyc migracje tabel pomocniczych: `product_change_log`, `sales_channels`, `product_channel_map`.
-66. `DONE` Dodac repozytoria i serwis domenowy dla produktow.
-67. `DONE` Dodac walidator produktow i wariantow (nazwa, cena, SKU produktu i wariantu, EAN, kombinacje atrybutow).
-68. `DONE` Dodac ekran listy produktow (`/products`) z filtrami i paginacja.
-69. `DONE` Dodac ekran tworzenia produktu (`/products/create`) i zapis (`POST /products`).
-70. `DONE` Dodac ekran szczegolow i edycji produktu (`/products/edit?id={id}`).
-71. `TODO` Dodac obsluge wariantow (dodaj/edytuj) na szczegolach produktu + konwersja simple <-> variant_parent.
-72. `TODO` Dodac sekcje atrybutow i wartosci (lista + dodawanie).
-73. `DONE` Dodac wpisy do `product_change_log` przy zmianach krytycznych.
-74. `DONE` Dodac pozycje "Produkty" do nawigacji + tlumaczenia `resources/lang/pl.php`.
-75. `DONE` Dodac dwukierunkowe przeliczanie cen brutto/netto (UI + backend) na podstawie VAT.
-76. `TODO` Dodac upload i zapis zdjec produktu na serwerze orderPRO.
-77. `TODO` Przeprowadzic test manualny flow: create/edit/list/filter/variant.
diff --git a/DOCS/CRON_QUEUE.md b/DOCS/CRON_QUEUE.md
deleted file mode 100644
index 0d5d794..0000000
--- a/DOCS/CRON_QUEUE.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Kolejka Cron (DB)
-
-## Cel
-- Zadania cron sa zapisywane w bazie (`cron_jobs`) i planowane przez harmonogram (`cron_schedules`).
-- Aktualnie domyslnie dziala zadanie: `product_links_health_check` (co 7 dni).
-
-## Tabele
-- `cron_jobs` - kolejka zadan z priorytetem, retry i backoff.
-- `cron_schedules` - definicje cyklicznych zadan.
-- `product_link_alerts` - alerty dla nieistniejacych powiazan produktu.
-
-## Uruchamianie
-- Jednorazowo: `php bin/cron.php`
-- Z limitem batcha: `php bin/cron.php --limit=50`
-- Z panelu (`Ustawienia -> Cron`) mozna wlaczyc uruchamianie workera podczas requestow HTTP.
-
-## Zalecenie dla systemowego crona
-- Uruchamiaj `php /sciezka/do/orderPRO/bin/cron.php` co 1-5 minut.
-- Harmonogram 7-dniowy jest liczony przez `cron_schedules.next_run_at`, wiec sam worker powinien byc uruchamiany regularnie.
-
-## Jak dziala `product_links_health_check`
-1. Pobiera aktywne integracje `shoppro` z API key.
-2. Odswieza cache ofert (`channel_offers`) przez import API.
-3. Czyści nieaktualne rekordy ofert z cache.
-4. Weryfikuje aktywne powiazania `product_channel_map`.
-5. Dla brakujacych powiazan ustawia alert `missing_remote_link`.
-6. Dla przywroconych powiazan zamyka alert.
diff --git a/DOCS/DB_SCHEMA.md b/DOCS/DB_SCHEMA.md
index d76bf3d..4d3aa11 100644
--- a/DOCS/DB_SCHEMA.md
+++ b/DOCS/DB_SCHEMA.md
@@ -1,119 +1,79 @@
-# Struktura bazy danych (orderPRO)
+# DB Schema
-## Cel pliku
-- Ten dokument opisuje aktualny schemat bazy danych na podstawie migracji w `database/migrations`.
-- Aktualizuj ten plik przy każdej zmianie schematu (nowa tabela, kolumna, indeks, klucz obcy).
+## Status
+- Projekt po resecie do trybu `users-only`.
+- Aktualizuj ten plik przy kazdej zmianie migracji/schematu.
+- 2026-03-02: Przywrocenie UI `Ustawienia > Baza danych` nie wprowadza zmian w schemacie.
+- 2026-03-02: Dodano tabele statusow (grupy + statusy) dla nowej zakladki `Ustawienia > Statusy`.
+- 2026-03-02: Przygotowano draft generycznego schematu zamowien (bez aktywnej migracji) w `database/drafts/20260302_orders_schema_v1.sql`.
+- 2026-03-03: Wdrozono generyczne tabele zamowien na bazie docelowej skryptem `bin/deploy_and_seed_orders.php` (bez migratora SQL).
+- 2026-03-03: Dodano UI `Zamowienia > Lista zamowien` - bez zmian schematu (wykorzystuje istniejace tabele domeny zamowien).
+- 2026-03-03: Dodano UI `Zamowienia > Szczegoly zamowienia` (`GET /orders/{id}`) - bez zmian schematu.
-## Tabele i przeznaczenie
+## Tabele
### `users`
- Uzytkownicy panelu.
- Klucz unikalny: `email`.
-### `products`
-- Glowna tabela produktow lokalnych.
-- Najwazniejsze pola: `type`, `sku`, `ean`, `status`, `promoted`, `vat`, `weight`, `price_*`, `quantity`.
-- Pola shopPRO: `new_to_date`, `additional_message`, `additional_message_required`, `additional_message_text`.
-- Dodatkowe: `producer_id`, `producer_name`, `product_unit_id`, `custom_fields_json`.
-- Soft delete: `deleted_at`.
+### `order_status_groups`
+- Grupy statusow zamowien zarzadzane z UI.
+- Kolumny:
+ - `id` (PK, int unsigned, AI),
+ - `name` (varchar 120),
+ - `code` (varchar 64, UNIQUE),
+ - `color_hex` (char 7, domyslnie `#64748b`),
+ - `sort_order` (int, domyslnie `0`),
+ - `is_active` (tinyint(1), domyslnie `1`),
+ - `created_at`, `updated_at`.
+- Indeksy:
+ - `order_status_groups_code_unique` (UNIQUE: `code`),
+ - `order_status_groups_sort_order_idx` (`sort_order`).
-### `product_translations`
-- Globalne tresci produktu per jezyk (`lang`).
-- Najwazniejsze pola: `name`, `short_description`, `description`, SEO, `security_information`.
-- Unikalnosc: `(product_id, lang)`.
+### `order_statuses`
+- Statusy przypisane do grup statusow.
+- Kolumny:
+ - `id` (PK, int unsigned, AI),
+ - `group_id` (FK -> `order_status_groups.id`),
+ - `name` (varchar 120),
+ - `code` (varchar 64, UNIQUE),
+ - `sort_order` (int, domyslnie `0`),
+ - `is_active` (tinyint(1), domyslnie `1`),
+ - `created_at`, `updated_at`.
+- Indeksy:
+ - `order_statuses_code_unique` (UNIQUE: `code`),
+ - `order_statuses_group_sort_idx` (`group_id`, `sort_order`, `id`).
+- Klucze obce:
+ - `order_statuses_group_fk`: `group_id` -> `order_status_groups.id` (`ON DELETE CASCADE`, `ON UPDATE CASCADE`).
-### `product_integration_translations`
-- Nadpisania tresci produktu per integracja shopPRO.
-- Pola: `name`, `short_description`, `description` (NULL = fallback do `product_translations`).
-- Unikalnosc: `(product_id, integration_id)`.
+### Domena zamowien (generyczna)
+- Wdrozone tabele:
+ - `orders`
+ - `order_addresses`
+ - `order_items`
+ - `order_payments`
+ - `order_shipments`
+ - `order_documents`
+ - `order_notes`
+ - `order_status_history`
+ - `order_tags_dict`
+ - `order_tag_links`
+ - `integration_order_sync_state`
+- Charakterystyka:
+ - schema neutralna wzgledem dostawcy API (pola `source_*`, `external_*`),
+ - kolekcje zamowienia rozdzielone na osobne tabele 1:N,
+ - `payload_json` dostepne dla diagnostyki/replay,
+ - historia zmian statusow utrzymywana w `order_status_history`.
-### `product_images`
-- Obrazy produktu (`storage_path`, `is_main`, `sort_order`).
+## Zasady aktualizacji
+- Po kazdej migracji dopisz:
+ - nowe/zmienione tabele i kolumny,
+ - indeksy i klucze obce,
+ - wplyw na dane i kompatybilnosc wsteczna.
-### `product_categories`
-- Relacja M:N produkt-kategoria (lokalna).
-
-### `attributes`
-- Definicje atrybutow wariantow.
-
-### `attribute_translations`
-- Tlumaczenia nazw atrybutow per jezyk.
-
-### `attribute_values`
-- Wartosci atrybutow (np. kolor, rozmiar), z opcjonalnym `impact_on_price`.
-
-### `attribute_value_translations`
-- Tlumaczenia wartosci atrybutow per jezyk.
-
-### `product_variants`
-- Warianty produktu.
-- Najwazniejsze pola: `permutation_hash`, `sku`, `ean`, `status`, `price_*`, `weight`, `stock_0_buy`.
-- Unikalnosc: `sku`, `(product_id, permutation_hash)`.
-
-### `product_variant_attributes`
-- Relacja wariant -> (atrybut, wartosc).
-- Klucz glowny: `(variant_id, attribute_id)`.
-
-### `product_change_log`
-- Log zmian produktow (audyt JSON: `before_json`, `after_json`).
-
-### `sales_channels`
-- Slownik kanalow sprzedazy (`shoppro`, `allegro`, `erli`, ...).
-
-### `product_channel_map`
-- Mapowanie lokalnego produktu do kanalu/integracji i ID zewnetrznych.
-- Najwazniejsze pola: `integration_id`, `external_product_id`, `external_variant_id`, `sync_state`, `link_type`, `link_status`, `confidence`.
-- Pola audytowe powiazania: `linked_at`, `linked_by_user_id`, `unlinked_at`, `unlinked_by_user_id`, `sync_meta_json`.
-
-### `channel_offers`
-- Cache ofert zewnetrznych dla integracji.
-- Najwazniejsze pola: `external_product_id`, `external_variant_id`, `external_offer_id`, `name` (tytul oferty), `sku`, `ean`, `offer_status`, `last_seen_at`, `payload_json`.
-- Unikalnosc: `(integration_id, external_product_id, external_variant_id)`.
-
-### `product_link_events`
-- Historia zdarzen na powiazaniach (`product_channel_map`).
-
-### `integrations`
-- Konfiguracja instancji integracji (obecnie shopPRO).
-- Najwazniejsze pola: `type`, `name`, `base_url`, `api_key_encrypted`, `timeout_seconds`, `is_active`.
-
-### `integration_test_logs`
-- Historia testow polaczen integracji.
-
-### `cron_jobs`
-- Kolejka jobow crona.
-
-### `cron_schedules`
-- Harmonogramy okresowych jobow.
-- Aktualnie zawiera m.in.:
- - `product_links_health_check`
- - `shoppro_offer_titles_refresh` (odswiezanie tytulow ofert; domyslnie co 30 dni)
-
-### `product_link_alerts`
-- Alerty zdrowia powiazan produktu.
-
-### `app_settings`
-- Ustawienia aplikacyjne key-value.
-- Przykładowe klucze: `cron_run_on_web`, `cron_web_limit`, `gs1_*`, `products_sku_format`.
-
-## Relacje (skrot)
-- `product_translations.product_id -> products.id`
-- `product_integration_translations.product_id -> products.id`
-- `product_integration_translations.integration_id -> integrations.id`
-- `product_images.product_id -> products.id`
-- `product_variants.product_id -> products.id`
-- `product_variant_attributes.variant_id -> product_variants.id`
-- `product_variant_attributes.attribute_id -> attributes.id`
-- `product_variant_attributes.value_id -> attribute_values.id`
-- `product_channel_map.product_id -> products.id`
-- `product_channel_map.channel_id -> sales_channels.id`
-- `product_channel_map.integration_id -> integrations.id`
-- `channel_offers.integration_id -> integrations.id`
-- `channel_offers.channel_id -> sales_channels.id`
-- `product_link_events.product_channel_map_id -> product_channel_map.id`
-- `product_link_alerts.product_channel_map_id -> product_channel_map.id`
-
-## Jak utrzymywac dokument
-1. Po dodaniu migracji zaktualizuj sekcje tabel/kolumn/relacji.
-2. Gdy dodajesz nowe klucze do `app_settings`, dopisz je tu.
-3. Przy zmianach harmonogramu crona zaktualizuj liste jobow w `cron_schedules`.
+## Drafty (nieaktywne)
+- `database/drafts/20260302_orders_schema_v1.sql`:
+ - propozycja normalizacji domeny zamowien pod integracje zewnetrzne (`orders`, `order_items`, `order_status_history`, platnosci, wysylki, dokumenty, notatki, tagi, sync-state),
+ - plik nie jest odpalany przez obecny migrator (`database/migrations/*.sql`).
+- `bin/deploy_and_seed_orders.php`:
+ - techniczny skrypt wdrozeniowy, ktory aplikuje schema z draftu i opcjonalnie seeduje dane testowe.
diff --git a/DOCS/FRONTEND_STANDARDS.md b/DOCS/FRONTEND_STANDARDS.md
deleted file mode 100644
index 297de47..0000000
--- a/DOCS/FRONTEND_STANDARDS.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# Frontend Standards
-
-## 1) Wspolne style UI
-- Powtarzalne elementy (np. przyciski, tabele, paginacja, alerty, pola formularzy) trzymamy w:
- - `resources/scss/shared/_ui-components.scss`
-- Widoki maja korzystac z klas wspolnych (`btn`, `table`, `pagination`, `form-control`, `alert`) zamiast duplikowania stylu lokalnie.
-
-## 2) Moduly JS wielokrotnego uzycia
-- Kazdy modul przenoszalny trzymamy w oddzielnym folderze:
- - `resources/modules//`
-- Minimalny zestaw plikow:
- - `resources/modules//.js`
- - `resources/modules//.scss`
-- Modul ma byc niezalezny od logiki projektu (brak hardcoded sciezek i zaleznosci biznesowych).
-
-## 3) Przyklad
-- Referencyjny modul: `resources/modules/jquery-alerts/`
diff --git a/DOCS/MIGRATIONS.md b/DOCS/MIGRATIONS.md
deleted file mode 100644
index 8c32225..0000000
--- a/DOCS/MIGRATIONS.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Migracje bazy danych
-
-## Zasada
-- Kazda zmiana schematu bazy to nowy plik `.sql` w `database/migrations`.
-- Pliki sa wykonywane rosnaco po nazwie.
-- Wykonane migracje sa zapisywane w tabeli `migrations`.
-
-## Nazewnictwo plikow
-- Format: `YYYYMMDD_HHMMSS_opis.sql`
-- Przyklad: `20260221_000001_create_users_table.sql`
-
-## Uruchamianie
-- CLI: `php bin/migrate.php` lub `composer migrate`
-- Panel: `Ustawienia > Aktualizacja bazy danych > Wykonaj aktualizacje`
-
-## Kolejne migracje
-1. Dodaj nowy plik SQL w `database/migrations`.
-2. Wrzuc plik na serwer.
-3. Uruchom aktualizacje z panelu albo z CLI.
diff --git a/DOCS/ORDERS_SCHEMA_DRAFT.md b/DOCS/ORDERS_SCHEMA_DRAFT.md
new file mode 100644
index 0000000..88c31f7
--- /dev/null
+++ b/DOCS/ORDERS_SCHEMA_DRAFT.md
@@ -0,0 +1,44 @@
+# Orders Schema Draft (Generic)
+
+## Context
+- This is a generic schema proposal for external orders import/sync.
+- API docs from Apilo were used only as an example of a rich order payload shape.
+- Target is integration-agnostic storage (no vendor lock in table/column names).
+
+## Proposed tables
+- `orders`
+- `order_addresses`
+- `order_items`
+- `order_payments`
+- `order_shipments`
+- `order_documents`
+- `order_notes`
+- `order_status_history`
+- `order_tags_dict`
+- `order_tag_links`
+- `integration_order_sync_state`
+
+SQL draft:
+- [20260302_orders_schema_v1.sql](/C:/visual%20studio%20code/projekty/orderPRO/database/drafts/20260302_orders_schema_v1.sql)
+
+## Design principles
+- Keep one row per imported source order in `orders`.
+- Store child collections in dedicated tables (1:N).
+- Keep source IDs and selected business scalars as first-class columns.
+- Keep raw payload snapshots (`payload_json`) for diagnostics and replay safety.
+- Keep import idempotent:
+ - unique `(integration_id, source_order_id)` in `orders`,
+ - unique child keys per order and source child ID.
+- Keep event timeline in separate `order_status_history`.
+
+## Why these extra tables
+- `order_status_history`: audit + timeline reconstruction + sync debugging.
+- `integration_order_sync_state`: robust incremental fetch cursor.
+- `order_tags_*`: proper many-to-many, easy filtering and deduplication.
+
+## Notes before implementation
+- Draft is intentionally in `database/drafts` (not auto-run by Migrator).
+- Before production migration:
+ - confirm source-specific mapping in service layer,
+ - confirm retention policy for `payload_json`,
+ - decide merge strategy for child rows (upsert by source IDs vs hard refresh per sync).
diff --git a/DOCS/PLAN_MODULU_POWIAZAN_PRODUKTOW.md b/DOCS/PLAN_MODULU_POWIAZAN_PRODUKTOW.md
deleted file mode 100644
index 5bde038..0000000
--- a/DOCS/PLAN_MODULU_POWIAZAN_PRODUKTOW.md
+++ /dev/null
@@ -1,235 +0,0 @@
-# orderPRO - Plan wdrozenia modulu powiazan produktow (shopPRO + marketplace)
-
-Data: 2026-02-23
-Status: draft do implementacji
-
-## Status realizacji (2026-02-23)
-- Etap A: wykonany
-- Etap B: wykonany
-- Etap C: wykonany
-- Etap D: wykonany (LinkMatcherService, priorytety EAN/SKU, endpoint sugestii)
-- Etap E: w toku (UI historii zdarzen + soft-unlink i audyt gotowe; do domkniecia testy manualne E2E)
-
-## Ustalenie implementacyjne (2026-02-23)
-- Import produktu z integracji tworzy od razu powiazanie w `product_channel_map` z ustawionym `integration_id` dla tej konkretnej instancji konta.
-
-## 1. Cel etapu
-Zbudowac osobny modul "Powiazania ofert" pozwalajacy mapowac produkty orderPRO do ofert zewnetrznych z instancji:
-- shopPRO,
-- marketplace.
-
-Modul ma dzialac jako osobna karta (analogicznie do podejscia Apilo/BaseLinker), ale osadzona w obecnej architekturze orderPRO.
-
-## 2. Stan obecny i luka
-### 2.1 Co juz jest
-- tabele `sales_channels` oraz `product_channel_map`,
-- seeding kanalow (`shoppro`, `allegro`, `erli`) w `IntegrationRepository::ensureSalesChannelsSeeded()`,
-- podstawowe mapowanie `product_id + channel_code + external ids` przez `upsertProductChannelMap(...)`.
-
-### 2.2 Czego brakuje
-- brak rozroznienia instancji kont (np. kilka kont marketplace),
-- brak lokalnego cache ofert zewnetrznych do wyszukiwania i laczenia,
-- brak statusow powiazania i konfliktow,
-- brak dedykowanego UI "Powiazania",
-- brak audytu operacji recznych (powiaz/przepnij/odlacz).
-
-## 3. Zakres MVP modulu powiazan
-W etapie MVP wdrazamy:
-1. osobna karta "Powiazania" na szczegolach produktu,
-2. reczne powiazanie produktu orderPRO z oferta zewnetrzna,
-3. auto-podpowiedzi po `EAN` i `SKU` (bez automatycznego zapisu),
-4. odlaczanie i przepinanie powiazan,
-5. podstawowy audit trail w osobnej tabeli logow,
-6. import/listowanie ofert z aktywnych integracji do lokalnej tabeli cache.
-
-W MVP NIE wdrazamy:
-- pelnej, automatycznej synchronizacji cen/stanow,
-- fuzzy matching po nazwie jako auto-link,
-- masowych operacji na setkach rekordow naraz.
-
-## 4. Model danych (docelowy dla MVP + rozszerzalny)
-### 4.1 Rozszerzenie istniejacej tabeli mapowania
-Tabela: `product_channel_map` (istniejaca)
-
-Dodac kolumny:
-- `integration_id` INT UNSIGNED NULL (FK -> `integrations.id`) - wskazuje konkretna instancje konta,
-- `link_type` VARCHAR(32) NOT NULL DEFAULT 'manual' (`manual`, `auto_sku`, `auto_ean`),
-- `link_status` VARCHAR(32) NOT NULL DEFAULT 'active' (`active`, `conflict`, `inactive`, `unverified`),
-- `confidence` TINYINT UNSIGNED NULL,
-- `linked_at` DATETIME NULL,
-- `linked_by_user_id` INT UNSIGNED NULL (FK -> `users.id`),
-- `unlinked_at` DATETIME NULL,
-- `unlinked_by_user_id` INT UNSIGNED NULL (FK -> `users.id`),
-- `sync_meta_json` JSON NULL (pole techniczne pod dane z API).
-
-Indeksy:
-- `(integration_id, external_product_id, external_variant_id)`,
-- `(product_id, link_status)`,
-- `(channel_id, link_status)`.
-
-Uwagi:
-- utrzymac kompatybilnosc ze starym kodem (`upsertProductChannelMap`),
-- `integration_id` moze byc NULL tylko dla rekordow historycznych.
-
-### 4.2 Nowa tabela cache ofert zewnetrznych
-Tabela: `channel_offers`
-- `id` INT UNSIGNED PK
-- `integration_id` INT UNSIGNED NOT NULL FK -> `integrations.id`
-- `channel_id` INT UNSIGNED NOT NULL FK -> `sales_channels.id`
-- `external_product_id` VARCHAR(128) NOT NULL
-- `external_variant_id` VARCHAR(128) NULL
-- `external_offer_id` VARCHAR(128) NULL
-- `name` VARCHAR(255) NOT NULL
-- `sku` VARCHAR(128) NULL
-- `ean` VARCHAR(32) NULL
-- `price_brutto` DECIMAL(12,2) NULL
-- `quantity` DECIMAL(12,3) NULL
-- `currency` VARCHAR(8) NULL
-- `offer_status` VARCHAR(32) NOT NULL DEFAULT 'active'
-- `source_updated_at` DATETIME NULL
-- `last_seen_at` DATETIME NOT NULL
-- `payload_json` JSON NULL
-- `created_at`, `updated_at`
-
-Unikalnosc:
-- `UNIQUE (integration_id, external_product_id, external_variant_id)`.
-
-### 4.3 Nowa tabela logow powiazan
-Tabela: `product_link_events`
-- `id` INT UNSIGNED PK
-- `product_channel_map_id` INT UNSIGNED NOT NULL FK -> `product_channel_map.id`
-- `event_type` VARCHAR(32) NOT NULL (`linked`, `relinked`, `unlinked`, `status_changed`, `conflict_detected`)
-- `before_json` JSON NULL
-- `after_json` JSON NULL
-- `created_by_user_id` INT UNSIGNED NULL FK -> `users.id`
-- `created_at` DATETIME NOT NULL
-
-## 5. Architektura aplikacyjna (orderPRO)
-Nowy modul: `src/Modules/ProductLinks`
-
-Klasy:
-- `ProductLinksController` - karta "Powiazania", akcje reczne,
-- `ProductLinksService` - logika powiaz/przepnij/odlacz + reguly konfliktow,
-- `ProductLinksRepository` - odczyt/zapis `product_channel_map` i `product_link_events`,
-- `ChannelOffersRepository` - odczyt cache ofert,
-- `LinkMatcherService` - podpowiedzi po EAN/SKU,
-- `OfferImportService` - import ofert z API integracji do `channel_offers`.
-
-Wspolpraca z istniejacymi modulami:
-- `Settings/IntegrationRepository` - lista aktywnych instancji,
-- `Products/ProductRepository` - dane produktu lokalnego,
-- `Core/Security/Csrf` + `Auth` - autoryzacja i CSRF dla akcji POST.
-
-## 6. UI/UX (osobna karta)
-### 6.1 Widok produktu
-W `resources/views/products/show.php` dodac zakladke:
-- `Powiazania`.
-
-Nowy widok czesciowy:
-- `resources/views/products/partials/links.php`.
-
-### 6.2 Sekcje karty
-1. Aktualne powiazania:
-- instancja (`integrations.name`),
-- kanal (`sales_channels.name`),
-- oferta (`name`, `external_product_id`, `external_variant_id`),
-- dopasowanie (`link_type`, `link_status`, `confidence`),
-- akcje: `Przepnij`, `Odlacz`.
-
-2. Wyszukiwarka ofert:
-- filtr po instancji,
-- filtr po SKU/EAN/nazwie,
-- lista wynikow z `channel_offers`,
-- akcja `Powiaz`.
-
-3. Podpowiedzi automatyczne:
-- sekcja "Proponowane dopasowania" (EAN/SKU),
-- tylko sugestia, finalny zapis reczny przez operatora.
-
-### 6.3 Potwierdzenia i alerty
-Krytyczne akcje (`Przepnij`, `Odlacz`) realizowac przez:
-- `window.OrderProAlerts.confirm(...)`.
-
-Nie dodawac natywnych `alert()` i `confirm()`.
-
-## 7. API / routing wewnetrzny (SSR + POST)
-Proponowane endpointy:
-- `GET /products/{id}/links` - zwrot zawartosci karty (lub render w `show`),
-- `POST /products/{id}/links` - utworzenie recznego powiazania,
-- `POST /products/{id}/links/{mapId}/relink` - przepiecie na inna oferte,
-- `POST /products/{id}/links/{mapId}/unlink` - odlaczenie,
-- `GET /products/{id}/links/suggestions` - sugestie EAN/SKU.
-
-Backoffice import ofert:
-- `POST /settings/integrations/{id}/offers/import` - reczny import cache,
-- docelowo cron/job: `php bin/cron_import_offers.php` (po MVP).
-
-## 8. Reguly biznesowe
-1. Jeden rekord oferty zewnetrznej (`integration_id + external_product_id + external_variant_id`) moze byc aktywnie powiazany tylko z jednym produktem orderPRO.
-2. Jeden produkt orderPRO moze miec wiele powiazan (multi-channel, multi-instance).
-3. Powiazanie reczne ma priorytet nad sugestiami auto-match.
-4. `EAN exact` ma wyzszy priorytet sugestii niz `SKU exact`.
-5. `SKU normalized` (bez spacji, myslnikow, podkreslen) jest nizszy niz `SKU exact`.
-6. Konflikty nie modyfikuja automatycznie aktywnego linku - nadaja status `conflict` i wymagaja decyzji operatora.
-7. Odlaczenie nie kasuje historii: rekord mapy moze przejsc w `link_status = inactive`.
-
-## 9. Etapy wdrozenia
-## Etap A - migracje i repozytoria
-- migracja rozszerzajaca `product_channel_map`,
-- nowe tabele `channel_offers`, `product_link_events`,
-- repozytoria i podstawowe testy manualne SQL.
-
-Kryterium akceptacji:
-- migracje przechodza lokalnie,
-- mozna zapisac i odczytac link wraz z `integration_id` i `link_status`.
-
-## Etap B - import i cache ofert
-- adapter importu ofert z aktywnych integracji,
-- upsert do `channel_offers`,
-- reczny trigger importu z panelu Integracje.
-
-Kryterium akceptacji:
-- po imporcie widoczne sa oferty w cache dla wskazanej integracji,
-- kolejne importy aktualizuja rekordy bez duplikatow.
-
-## Etap C - UI karty Powiazania
-- dodanie zakladki w szczegolach produktu,
-- lista aktywnych powiazan,
-- formularz recznego powiazania,
-- akcje odlacz/przepnij z `OrderProAlerts.confirm`.
-
-Kryterium akceptacji:
-- operator bez SQL moze powiazac, przepiac i odlaczyc oferte.
-
-## Etap D - sugestie i konflikty
-- `LinkMatcherService` (EAN/SKU),
-- widok sugestii,
-- logika konfliktu i statusy.
-
-Kryterium akceptacji:
-- sugestie sa widoczne i oznaczone confidence,
-- konflikt nie psuje istniejacego aktywnego mapowania.
-
-## Etap E - hardening i audyt
-- logowanie zdarzen do `product_link_events`,
-- dopracowanie komunikatow i walidacji,
-- testy manualne end-to-end.
-
-Kryterium akceptacji:
-- kazda operacja reczna ma wpis w historii,
-- UI poprawnie pokazuje status i ostatnia zmiane.
-
-## 10. Definicja "Done" dla modulu
-- istnieje osobna karta "Powiazania" na produkcie,
-- mozliwe jest reczne mapowanie produkt <-> oferta (shopPRO/marketplace),
-- dziala odlaczanie i przepinanie z potwierdzeniem UI,
-- cache ofert z integracji dziala i jest przeszukiwalny,
-- konflikty sa oznaczane i nie niszcza danych,
-- operacje sa audytowane.
-
-## 11. Kolejny krok po MVP
-Po MVP uruchomic:
-1. job/cron cyklicznego importu ofert,
-2. automatyczne auto-linkowanie tylko dla przypadkow `confidence >= prog`,
-3. masowe operacje mapowania (bulk),
-4. wykorzystanie powiazan w sync cen/stanow i publikacji ofert.
diff --git a/DOCS/PLAN_MODULU_PRODUKTOW.md b/DOCS/PLAN_MODULU_PRODUKTOW.md
deleted file mode 100644
index ea85357..0000000
--- a/DOCS/PLAN_MODULU_PRODUKTOW.md
+++ /dev/null
@@ -1,289 +0,0 @@
-# orderPRO - Plan wdrozenia modulu produktow (bez importu/eksportu)
-
-Data: 2026-02-23
-Status: draft do implementacji
-
-## 1. Cel etapu
-Zbudowac lokalny modul produktow w orderPRO, gotowy pod obsluge wielu kanalow sprzedazy (co najmniej 2 sklepy shopPRO), ale bez uruchamiania synchronizacji import/export.
-
-W tym etapie tworzymy:
-- model danych produktu w orderPRO,
-- panel do listowania, filtrowania, podgladu i edycji produktow,
-- obsluge wariantow i atrybutow,
-- podstawy pod przyszle mapowanie kanalowe (shopPRO, Allegro, Erli).
-
-W tym etapie NIE tworzymy:
-- importu z shopPRO,
-- eksportu do shopPRO,
-- eksportu do marketplace,
-- schedulerow/cronow synchronizacji produktow.
-
-## 2. Zalozenia domenowe
-1. orderPRO jest source of truth dla danych produktowych po wdrozeniu tego etapu.
-2. Produkt ma stabilny identyfikator lokalny oraz techniczny UUID do laczenia z integracjami.
-3. Jeden produkt moze miec wiele wariantow.
-4. Ceny sa przechowywane lokalnie i przeliczane dwukierunkowo (brutto <-> netto) na podstawie VAT.
-5. Stan magazynowy jest przechowywany wylacznie na produkcie glownym.
-6. Modul ma byc przygotowany pod wiele kanalow, ale bez aktywnych procesow synchronizacji.
-7. W MVP obslugiwany jest jezyk `pl`, z zachowaniem struktury pod kolejne jezyki.
-
-## 3. Zakres funkcjonalny MVP modulu produktow
-### 3.1 Lista produktow
-- paginacja,
-- wyszukiwanie po nazwie, SKU, EAN,
-- filtry: status, typ (prosty/wariantowy), producent, data modyfikacji,
-- sortowanie: id, nazwa, SKU, cena, stan, status, data modyfikacji,
-- widoczne flagi: aktywny/nieaktywny, promowany.
-
-### 3.2 Szczegoly produktu
-- dane glowne: nazwa, SKU, EAN, status, promoted,
-- ceny: brutto/netto + promo,
-- stan magazynowy i waga,
-- VAT i jednostka,
-- kategorie lokalne,
-- opis i meta (PL jako minimum),
-- galeria zdjec (metadane i kolejnosc).
-
-### 3.3 Tworzenie i edycja produktu
-- formularz z walidacja,
-- partial update,
-- historia zmian (minimum: kto, kiedy, co zmienil).
-
-### 3.4 Warianty
-- dodawanie wariantu po kombinacji atrybutow,
-- walidacja unikalnosci kombinacji,
-- osobne pola SKU/EAN/cena/waga/status dla wariantu (bez osobnego stanu),
-- aktywacja/dezaktywacja wariantu.
-
-### 3.5 Atrybuty i wartosci
-- slownik atrybutow i wartosci lokalnych,
-- oznaczenie typu atrybutu,
-- mozliwosc przypisania wielu atrybutow do produktu.
-
-## 4. Model danych (propozycja)
-### 4.1 Tabele glówne
-1. `products`
-- `id` BIGINT PK
-- `uuid` CHAR(36) UNIQUE
-- `type` ENUM('simple','variant_parent')
-- `sku` VARCHAR(128) NULL UNIQUE
-- `ean` VARCHAR(32) NULL
-- `status` TINYINT(1)
-- `promoted` TINYINT(1)
-- `vat` DECIMAL(5,2) NULL
-- `weight` DECIMAL(10,3) NULL
-- `price_brutto` DECIMAL(12,2)
-- `price_brutto_promo` DECIMAL(12,2) NULL
-- `price_netto` DECIMAL(12,2) NULL
-- `price_netto_promo` DECIMAL(12,2) NULL
-- `quantity` DECIMAL(12,3) DEFAULT 0
-- `producer_id` BIGINT NULL
-- `product_unit_id` BIGINT NULL
-- `created_at`, `updated_at`, `deleted_at`
-
-2. `product_translations`
-- `id` BIGINT PK
-- `product_id` BIGINT FK -> products.id
-- `lang` VARCHAR(8)
-- `name` VARCHAR(255)
-- `short_description` TEXT NULL
-- `description` LONGTEXT NULL
-- `meta_title` VARCHAR(255) NULL
-- `meta_description` VARCHAR(255) NULL
-- `meta_keywords` VARCHAR(255) NULL
-- `seo_link` VARCHAR(255) NULL
-- UNIQUE (`product_id`, `lang`)
-
-3. `product_images`
-- `id` BIGINT PK
-- `product_id` BIGINT FK
-- `storage_path` VARCHAR(255)
-- `alt` VARCHAR(255) NULL
-- `sort_order` INT DEFAULT 0
-- `is_main` TINYINT(1) DEFAULT 0
-
-4. `product_categories`
-- `product_id` BIGINT FK
-- `category_id` BIGINT FK
-- PK (`product_id`, `category_id`)
-
-5. `product_variants`
-- `id` BIGINT PK
-- `product_id` BIGINT FK -> products.id
-- `permutation_hash` VARCHAR(191)
-- `sku` VARCHAR(128) NULL UNIQUE
-- `ean` VARCHAR(32) NULL
-- `status` TINYINT(1)
-- `price_brutto` DECIMAL(12,2) NULL
-- `price_brutto_promo` DECIMAL(12,2) NULL
-- `price_netto` DECIMAL(12,2) NULL
-- `price_netto_promo` DECIMAL(12,2) NULL
-- `weight` DECIMAL(10,3) NULL
-- `created_at`, `updated_at`
-- UNIQUE (`product_id`, `permutation_hash`)
-
-6. `product_variant_attributes`
-- `variant_id` BIGINT FK -> product_variants.id
-- `attribute_id` BIGINT FK
-- `value_id` BIGINT FK
-- PK (`variant_id`, `attribute_id`)
-
-7. `attributes`
-- `id` BIGINT PK
-- `type` TINYINT
-- `status` TINYINT(1)
-
-8. `attribute_translations`
-- `attribute_id` BIGINT FK
-- `lang` VARCHAR(8)
-- `name` VARCHAR(255)
-- PK (`attribute_id`, `lang`)
-
-9. `attribute_values`
-- `id` BIGINT PK
-- `attribute_id` BIGINT FK
-- `status` TINYINT(1)
-- `is_default` TINYINT(1)
-- `impact_on_price` DECIMAL(12,2) NULL
-
-10. `attribute_value_translations`
-- `value_id` BIGINT FK
-- `lang` VARCHAR(8)
-- `name` VARCHAR(255)
-- PK (`value_id`, `lang`)
-
-11. `product_change_log`
-- `id` BIGINT PK
-- `product_id` BIGINT FK
-- `user_id` BIGINT FK -> users.id
-- `change_type` VARCHAR(64)
-- `before_json` JSON NULL
-- `after_json` JSON NULL
-- `created_at`
-
-### 4.2 Tabele "future-ready" pod integracje (bez aktywnej synchronizacji)
-12. `sales_channels`
-- `id` BIGINT PK
-- `code` VARCHAR(64) UNIQUE (np. shoppro_1, shoppro_2, allegro, erli)
-- `name` VARCHAR(128)
-- `type` VARCHAR(64) (shoppro/marketplace)
-- `status` TINYINT(1)
-
-13. `product_channel_map`
-- `id` BIGINT PK
-- `product_id` BIGINT FK
-- `channel_id` BIGINT FK
-- `external_product_id` VARCHAR(128) NULL
-- `external_variant_id` VARCHAR(128) NULL
-- `sync_state` VARCHAR(32) DEFAULT 'not_linked'
-- `last_sync_at` DATETIME NULL
-- UNIQUE (`product_id`, `channel_id`, `external_product_id`, `external_variant_id`)
-
-Uwagi:
-- na tym etapie `product_channel_map` jest tylko przygotowaniem danych,
-- nie tworzymy procesow, ktore aktualizuja `sync_state` automatycznie.
-
-## 5. Architektura aplikacyjna (orderPRO)
-Nowy modul: `src/Modules/Products`
-
-Sugerowane klasy:
-- `ProductsController` (SSR: lista, szczegoly, create, update),
-- `ProductVariantsController`,
-- `AttributesController`,
-- `ProductRepository`,
-- `VariantRepository`,
-- `AttributeRepository`,
-- `ProductValidator`,
-- `ProductService` (transakcje i reguly domenowe),
-- DTO/normalizery do mapowania formularz <-> domena.
-
-Widoki:
-- `resources/views/products/index.php`
-- `resources/views/products/form.php`
-- `resources/views/products/show.php`
-- `resources/views/products/partials/variants.php`
-- `resources/views/products/partials/attributes.php`
-
-Routing (propozycja):
-- `GET /products`
-- `GET /products/create`
-- `POST /products`
-- `GET /products/{id}`
-- `POST /products/{id}` (na teraz bez PUT, zgodnie z obecnym stylem formularzy)
-- `POST /products/{id}/variants`
-- `POST /variants/{id}`
-- `GET /attributes`
-- `POST /attributes`
-
-## 6. Walidacja i reguly biznesowe
-1. Wymagane: minimum jedna nazwa tlumaczenia (`pl.name`) i `price_brutto` dla produktu prostego.
-2. `sku` jest unikalne na poziomie produktu glownego (`products.sku`).
-3. `sku` wariantu jest unikalne na poziomie wariantu (`product_variants.sku`) gdy jest uzupelnione.
-4. Ceny brutto/netto sa liczone dwukierunkowo (na podstawie wpisanego pola i stawki VAT).
-5. Wariant musi miec niepusta i unikalna kombinacje atrybutow (`permutation_hash`).
-6. Stan magazynowy jest przechowywany tylko na produkcie glownym.
-7. `ean` opcjonalny, ale jesli podany to walidowany formatowo.
-8. Zmiany krytyczne (cena, status, stan, sku, ean) wpisywane do `product_change_log`.
-
-## 7. Etapy wdrozenia
-## Etap A - fundament danych
-- migracje tabel produktowych i atrybutowych,
-- indeksy pod filtry listy,
-- seed bazowych danych slownikowych (opcjonalnie).
-
-Kryterium akceptacji:
-- migracje przechodza lokalnie i tabela status w `Settings > Aktualizacja bazy` pokazuje brak pending.
-
-## Etap B - backend CRUD produktu
-- repozytoria + serwis + walidacja,
-- endpointy/form actions dla create/edit/show/list,
-- log zmian.
-
-Kryterium akceptacji:
-- mozna utworzyc i edytowac produkt prosty,
-- walidacje dzialaja,
-- zmiany zapisuja sie w `product_change_log`.
-
-## Etap C - warianty i atrybuty
-- CRUD atrybutow i wartosci,
-- dodawanie/edycja wariantow,
-- unikalnosc kombinacji atrybutow,
-- obsluga konwersji simple <-> variant_parent.
-
-Kryterium akceptacji:
-- mozna dodac wiele wariantow jednego produktu,
-- system blokuje duplikat kombinacji,
-- lista i szczegoly poprawnie prezentuja warianty.
-
-## Etap D - UI/UX panelu i stabilizacja
-- finalne widoki i filtry,
-- komunikaty flash i bledy,
-- porzadkowanie tlumaczen `resources/lang/pl.php`,
-- testy manualne flow end-to-end.
-
-Kryterium akceptacji:
-- caly flow produktowy dziala bez SQL manualnego,
-- menu zawiera sekcje "Produkty",
-- formularze sa zgodne z istniejacym stylem aplikacji.
-
-## 8. Ustalenia biznesowe (2026-02-23)
-1. `sku` jest unikalne na poziomie produktu glownego.
-2. `sku` jest unikalne rowniez na poziomie wariantu (jesli wariant ma wypelnione SKU).
-3. Ceny netto/brutto sa wyliczane automatycznie w obie strony.
-4. Stan magazynowy jest tylko na produkcie glownym.
-5. MVP dziala na jezyku `pl`, ale struktura danych zostaje wielojezyczna.
-6. Kategorie w tym etapie tylko jako relacja (`product_categories`), bez CRUD kategorii.
-7. Zdjecia sa przechowywane na serwerze orderPRO.
-8. Dozwolona jest konwersja produktu prostego na wariantowy i odwrotnie.
-9. Uprawnienia szczegolowe do modulu produktow sa odlozone (dostep dla zalogowanych).
-
-## 9. Definicja "Done" dla tego etapu
-- modul produktow dostepny z panelu,
-- kompletne CRUD dla produktu prostego,
-- obsluga wariantow i atrybutow,
-- dzialajace filtrowanie i paginacja,
-- log zmian,
-- przygotowane mapowanie kanalowe (tabele), ale bez aktywnej synchronizacji.
-
-## 10. Co dalej po tym etapie
-Kolejny etap to osobny plan: import produktow z 2x shopPRO + export z orderPRO do shopPRO/Allegro/Erli z kolejka, retry i monitorowaniem sync.
diff --git a/DOCS/PLAN_PROJEKTU.md b/DOCS/PLAN_PROJEKTU.md
deleted file mode 100644
index 448234a..0000000
--- a/DOCS/PLAN_PROJEKTU.md
+++ /dev/null
@@ -1,213 +0,0 @@
-# orderPRO - Plan projektu (MVP)
-
-Data: 2026-02-19
-
-## 0. Stan aktualny (wdrozone)
-- dziala logowanie/wylogowanie z sesja, middleware i CSRF,
-- dziala routing HTTP i renderowanie widokow SSR (wlasny lekki rdzen),
-- jest bazowy panel (`topbar + content`) oraz ekran logowania,
-- teksty interfejsu zostaly przeniesione do tlumaczen (`resources/lang/pl.php`),
-- style sa utrzymywane w SCSS (`resources/scss`) i kompilowane/minifikowane do `public/assets/css`,
-- wyglad loginu i panelu zostal ujednolicony pod styl adsPRO (kolory, typografia, buttony, spacing).
-
-## 1. Cel
-Zbudowac uproszczony panel do obslugi zamowien (inspiracja: Baselinker/Apilo/Sellasist), dzialajacy na hostingu wspoldzielonym (DirectAdmin), bez pelnego frameworka PHP.
-
-Zakres MVP:
-- import zamowien z wlasnego sklepu i Allegro,
-- obsluga zamowien w panelu (statusy, notatki),
-- tworzenie przesylek przez Apaczka (recznie przez przyciski),
-- integracja z zewnetrznym systemem faktur/paragonow,
-- uproszczona synchronizacja stanow magazynowych.
-
-## 2. Zalozenia techniczne
-- PHP: 8.4
-- Baza: MySQL 8.x
-- Hosting: wspoldzielony (DirectAdmin), cron dostepny
-- Skala startowa: do 50 zamowien dziennie
-- Model: jedna firma (single-tenant)
-- Styl wdrozenia: szybkie MVP
-
-## 3. Architektura (bez pelnego frameworka)
-Podejscie: modularny monolit + lekki wlasny rdzen.
-
-Elementy:
-- Router HTTP (wlasny)
-- Kontrolery + serwisy domenowe
-- Warstwa dostepu do danych (repozytoria)
-- Widoki SSR (natywne PHP templates)
-- Integracje przez adaptery API (Guzzle)
-- Kolejka oparta o MySQL (`jobs`) uruchamiana z crona
-
-Zalecane biblioteki Composer:
-- obecnie brak dodatkowych bibliotek runtime (rdzen jest autorski),
-- biblioteki integracyjne/testowe beda dodawane etapowo, gdy pojawia sie realne moduly integracji.
-
-## 4. Struktura katalogow
-```txt
-orderPRO/
- public/
- index.php
- assets/
- css/
- js/
- img/
- bootstrap/
- app.php
- container.php
- config/
- app.php
- database.php
- queue.php
- integrations.php
- src/
- Core/
- Http/
- Routing/
- Security/
- Database/
- Queue/
- View/
- Support/
- Modules/
- Auth/
- Dashboard/
- Orders/
- Inventory/
- Shipping/
- Documents/
- Integrations/
- Store/
- Allegro/
- Apaczka/
- Billing/
- database/
- migrations/
- seeders/
- storage/
- logs/
- cache/
- sessions/
- tmp/
- bin/
- cron_sync.php
- cron_queue.php
- cron_tracking.php
- tests/
- Unit/
- Feature/
- DOCS/
- PLAN_PROJEKTU.md
- BACKLOG_MIKROZADANIA.md
-```
-
-## 5. Glowne moduly MVP
-1. Auth
-- logowanie i wylogowanie
-- reset hasla
-- ochrona sesji i CSRF
-
-2. Orders
-- lista zamowien (filtry, statusy)
-- szczegoly zamowienia
-- notatki i historia zmian statusu
-
-3. Integrations
-- wlasny sklep: import zamowien
-- Allegro: OAuth + import zamowien
-- Apaczka: utworzenie przesylki i pobranie etykiety
-- Billing: tworzenie dokumentow w zewnetrznym systemie
-
-4. Inventory (uproszczone)
-- magazyn logiczny (SKU, ilosc)
-- rezerwacja przy zamowieniu
-- zwolnienie/anulowanie rezerwacji
-
-5. Jobs + Cron
-- przetwarzanie zadan asynchronicznych
-- retry i log bledow
-
-## 6. Model danych (MVP)
-Tabele:
-- `users`
-- `channels` (konfiguracje API: store/allegro/apaczka/billing)
-- `orders`
-- `order_items`
-- `order_addresses`
-- `order_status_history`
-- `shipments`
-- `shipment_labels`
-- `documents` (ID/link w systemie zewnetrznym)
-- `inventory_items`
-- `inventory_reservations`
-- `jobs`
-- `failed_jobs`
-- `sync_runs`
-- `sync_errors`
-- `webhook_events` (na przyszlosc)
-
-## 7. Synchronizacja i cron
-Konfiguracja na shared hostingu:
-- co 5 min: `php bin/cron_sync.php` (import zamowien)
-- co 1-2 min: `php bin/cron_queue.php` (obsluga jobow)
-- co 15 min: `php bin/cron_tracking.php` (statusy przesylek)
-
-Zasady:
-- krotkie zadania (batch np. po 20-50 rekordow),
-- timeouty i retry z backoff,
-- logowanie kazdej proby integracji.
-
-## 8. Integracje (kolejnosc)
-1. Wlasny sklep (pierwszy connector)
-2. Allegro (drugi connector)
-3. Apaczka (tworzenie przesylki z przycisku)
-4. System billingowy (faktura/paragon przez API)
-
-## 9. Plan etapow
-## Etap 0 - Fundament
-- skeleton aplikacji
-- routing, DI/container, konfiguracja `.env`
-- baza i migracje
-- logowanie uzytkownika
-- layout panelu admin
-
-## Etap 1 - Orders + wlasny sklep
-- import zamowien
-- lista i szczegoly
-- reczna zmiana statusow
-
-## Etap 2 - Allegro
-- autoryzacja OAuth
-- pobieranie i mapowanie zamowien
-
-## Etap 3 - Apaczka
-- tworzenie przesylki
-- etykieta PDF
-- zapis numeru tracking
-
-## Etap 4 - Billing + stock
-- wystawienie dokumentu przez API
-- uproszczony stock sync
-
-## Etap 5 - Stabilizacja
-- retry, logi, obsluga bledow
-- testy kluczowych flow
-- backup i checklista produkcyjna
-
-## 10. Wymagania niefunkcjonalne
-- bezpieczenstwo:
- - hashowanie hasel (`password_hash`)
- - CSRF tokeny
- - walidacja wejscia
- - escaping danych w widokach
-- audyt:
- - logi dzialan uzytkownika i integracji
-- wydajnosc:
- - paginacja list
- - indeksy DB pod filtry zamowien i statusow
-
-## 11. Decyzje odlozone na pozniej
-- multi-tenant (wiele firm)
-- zaawansowana dokumentacja magazynowa (WZ/PZ)
-- automatyka regul biznesowych
-- migracja na VPS / Docker
diff --git a/DOCS/TECH_CHANGELOG.md b/DOCS/TECH_CHANGELOG.md
new file mode 100644
index 0000000..1299d53
--- /dev/null
+++ b/DOCS/TECH_CHANGELOG.md
@@ -0,0 +1,131 @@
+# Tech Changelog
+
+## 2026-03-02
+- Dodano zakladke `Ustawienia > Statusy` do zarzadzania:
+ - grupami statusow (z kolorem na poziomie grupy),
+ - statusami przypisanymi do grup.
+- Dodano migracje `20260302_000022_create_order_status_groups_and_statuses_tables.sql`:
+ - tabela `order_status_groups`,
+ - tabela `order_statuses` z FK `order_statuses_group_fk` i kasowaniem kaskadowym.
+- Dodano `App\Modules\Settings\OrderStatusRepository` (CRUD grup/statusow i walidacja unikalnosci kodow).
+- Rozszerzono `App\Modules\Settings\SettingsController` o endpointy:
+ - `statuses`,
+ - `createStatusGroup`, `updateStatusGroup`, `deleteStatusGroup`,
+ - `createStatus`, `updateStatus`, `deleteStatus`.
+- Rozszerzono routing o trasy `/settings/statuses*` i `/settings/status-groups*`.
+- Sidebar ustawien ma nowy link `Statusy`.
+- Dodano widok `resources/views/settings/statuses.php` oraz style SCSS dla formularzy/akcji tego widoku.
+- Potwierdzenia usuwania w nowym widoku realizowane sa przez `window.OrderProAlerts.confirm(...)`.
+- Przebudowano UI `Ustawienia > Statusy`:
+ - 2 taby (`Statusy`, `Grupy statusow`),
+ - sortowanie realizowane przez drag-and-drop z automatycznym zapisem kolejnosci po upuszczeniu.
+- Skondensowano UI zakladki `Ustawienia > Statusy`:
+ - elementy listy statusow i grup maja bardziej kompaktowy, jednoliniowy uklad,
+ - zmniejszono paddingi/gapy i wysokosci kontrolek, aby zwiekszyc ilosc danych widocznych bez scrolla.
+- Wprowadzono globalna preferencje kompaktowego UI w `AGENTS.md`.
+- Poprawiono generowanie `code` dla statusow/grup: polskie znaki sa transliterowane do ASCII
+ (np. `Nieopłacone` -> `nieoplacone`), zamiast zamiany na `_`.
+- Dodano skrypt serwisowy `bin/fix_status_codes.php`:
+ - przelicza kody grup/statusow na podstawie aktualnych nazw z transliteracja PL->ASCII,
+ - zapewnia unikalnosc kodow (`_2`, `_3` przy konfliktach),
+ - wspiera `--dry-run` i `--use-remote`.
+- Wykonano naprawe kodow na bazie zdalnej (`--use-remote`): zaktualizowano 2 grupy i 1 status.
+- Przygotowano draft generycznego schematu tabel zamowien (Apilo tylko jako przyklad pol API):
+ - dokumentacja: `DOCS/ORDERS_SCHEMA_DRAFT.md`,
+ - draft SQL (nieuruchamiany automatycznie): `database/drafts/20260302_orders_schema_v1.sql`.
+- Wdrozono generyczny schema zamowien na bazie docelowej przez `bin/deploy_and_seed_orders.php`.
+- Zasiano dane testowe:
+ - `orders`: 30,
+ - `order_items`: 90,
+ - `order_status_history`: 123,
+ - pozostale kolekcje (adresy/platnosci/wysylki/dokumenty/notatki/tagi) proporcjonalnie.
+- Dodano endpointy zapisu kolejnosci:
+ - `POST /settings/status-groups/reorder`,
+ - `POST /settings/statuses/reorder`.
+- Zmieniono obsluge pola `code`:
+ - `code` jest automatycznie generowany przy tworzeniu z nazwy,
+ - po utworzeniu jest tylko do odczytu i nie podlega edycji z formularza.
+- Reset projektu do trybu `users-only`.
+- Zarchiwizowano moduly poza `Auth` i `Users` do `archive/2026-03-02_users-only-reset/`.
+- Uproszczono routing i layout do obslugi logowania i zarzadzania uzytkownikami.
+- Ustalono nowy standard dokumentacji technicznej w plikach root:
+ - `DB_SCHEMA.md`
+ - `ARCHITECTURE.md`
+ - `TECH_CHANGELOG.md`
+- Przywrocono sekcje `Ustawienia` w nawigacji jako grupe z podkategoriami:
+ - `Uzytkownicy` (`/users`)
+ - `Baza danych` (`/settings/database`)
+- Dodano modul `App\Modules\Settings` z kontrolerem `SettingsController` (metody `database`, `migrate`).
+- Przywrocono reczne uruchamianie migracji z UI:
+ - `GET /settings/database` (status migracji + lista pending plikow),
+ - `POST /settings/database/migrate` (wykonanie pending migracji + log ostatniego uruchomienia).
+- Zmieniono tlumaczenie `settings.database.title` na `Baza danych` oraz dodano `navigation.database`.
+- Poprawiono redirect po logowaniu (`AuthController`): `/dashboard` -> `/settings/users`.
+- Usunieto wewnetrzny pasek zakladek (`settings-nav`) z podstron ustawien.
+- Podstrona uzytkownikow jest adresowana jako `GET/POST /settings/users` (z zachowaniem tras kompatybilnosci `/users`).
+- Usunieto z podstron ustawien blok naglowkowy `Ustawienia` + opis, aby zwiekszyc obszar roboczy.
+- Rozszerzono `bin/deploy_and_seed_orders.php` o parametr `--profile=default|realistic`.
+- Dodano realistyczny profil seedowania:
+ - wazone losowanie statusow i metod platnosci,
+ - spojne mapowanie `external_status_id` -> `payment_status` i `total_paid`,
+ - bardziej realne reguly tworzenia wpisow `order_payments`, `order_shipments`, `order_documents`,
+ - historia statusow oparta na logicznych sciezkach przejsc (zamiast losowych skokow).
+- Wykonano ponowne wdrozenie draftu i seed z profilem realistycznym:
+ - komenda: `C:\xampp\php\php.exe bin/deploy_and_seed_orders.php --use-remote --count=30 --profile=realistic`,
+ - wynik: `orders=30`, `order_items=94`, `order_status_history=81`.
+- Dodano glowna sekcje panelu `Zamowienia` z podzakladka `Lista zamowien`.
+- Wdrozone endpointy:
+ - `GET /orders` (redirect do `/orders/list`),
+ - `GET /orders/list` (widok listy).
+- Dodano modul aplikacyjny:
+ - `App\Modules\Orders\OrdersController`,
+ - `App\Modules\Orders\OrdersRepository`.
+- Widok listy zamowien opiera sie o aktualna baze (`orders`, `order_addresses`, `order_items`, `order_shipments`, `order_documents`) i udostepnia:
+ - filtry (fraza, zrodlo, status, status platnosci, zakres dat),
+ - sortowanie i paginacje,
+ - kompaktowe komorki (referencje, klient, status+platnosc, pozycje, kwoty, wysylka, daty),
+ - skrócone statystyki (`wszystkie`, `oplacone`, `wyslane`).
+- Rozszerzono liste zamowien o podglad produktow w zamowieniu:
+ - nazwa produktu,
+ - miniatura (z `order_items.media_url`, fallback bez obrazu),
+ - ilosc sztuk per pozycja,
+ - licznik dodatkowych pozycji poza limitem podgladu.
+- Miniatury produktow na liscie zamowien zostaly powiekszone o 100% (uklad bardziej czytelny).
+- Dodano modal podgladu zdjecia po kliknieciu miniatury produktu na liscie zamowien.
+- Status w kolumnie statusow jest prezentowany jako nazwa biznesowa (np. `Nowe`, `W realizacji`) bez technicznego kodu.
+- Dodano skrypt serwisowy `bin/fill_order_item_images.php` do uzupelniania pustych `order_items.media_url`
+ losowymi URL (`picsum.photos`) i wykonano go na bazie zdalnej (`--use-remote`, zaktualizowano 94 rekordy).
+- Rozszerzono sidebar o grupe `Zamowienia` z podlinkiem `Lista zamowien`.
+- Dodano widok szczegolow zamowienia:
+ - endpoint `GET /orders/{id}`,
+ - link do szczegolow po kliknieciu numeru zamowienia na liscie,
+ - uklad sekcji inspirowany widokiem Apilo: pozycje, dane zamowienia, platnosc/wysylka, adresy, notatki, historia.
+- Dopracowano widok `GET /orders/{id}` do ukladu bardziej zblizonego do Apilo:
+ - lewy panel statusow z licznikami,
+ - prawa kolumna szczegolow z paskiem akcji i tabami sekcji,
+ - aktywne wyroznienie biezacego statusu zamowienia.
+- Dodano taki sam lewy panel statusow na `GET /orders/list`:
+ - grupy statusow z licznikami,
+ - klikniecie statusu filtruje liste zamowien po `status`,
+ - kolorowe liczniki per status (info/warn/success/danger).
+- Poprawiono zrodlo panelu statusow (lista + szczegoly):
+ - podzial na grupy i nazwy statusow sa pobierane dynamicznie z `order_status_groups` + `order_statuses`,
+ - kolory pochodza z `order_status_groups.color_hex`,
+ - dla statusow nieprzypisanych do konfiguracji dodawana jest sekcja `Pozostale`.
+- Ujednolicono render panelu statusow jako jeden widget widoku:
+ - nowy komponent `resources/views/components/order-status-panel.php`,
+ - komponent jest wspolnie uzywany przez `orders/list.php` i `orders/show.php`,
+ - statusy w szczegolach zamowienia sa klikalne (przejscie do listy z odpowiednim filtrem).
+- Dodano klikalne taby w `orders/show.php`:
+ - przelaczanie sekcji bez przeladowania strony (JS),
+ - aktywny panel `Szczegoly zamowienia`,
+ - pozostale panele (`Historia zmian`, `Przesylki`, `Platnosci`, `Dokumenty`) zawieraja tymczasowe puste boksy.
+- Zmieniono seed zamowien (`bin/deploy_and_seed_orders.php`):
+ - `external_status_id` jest losowany z aktywnych statusow z tabeli `order_statuses` (zgodnie z konfiguracja w `Ustawienia > Statusy`),
+ - dodano fallback do listy domyslnej, jesli tabela jest pusta/niedostepna,
+ - profil `realistic` ma fallback reguly finansowej dla niestandardowych statusow.
+- Dodano skrypt serwisowy `bin/randomize_order_statuses.php`:
+ - losowo podmienia `orders.external_status_id` dla juz istniejacych zamowien na aktywne statusy z `order_statuses`,
+ - aktualizuje tez `is_canceled_by_buyer` dla statusu `cancelled`,
+ - wspiera `--use-remote` i `--dry-run`.
+- Wykonano podmiane statusow na bazie zdalnej (`--use-remote`): zaktualizowano 30 zamowien.
diff --git a/DOCS/TODO.md b/DOCS/TODO.md
deleted file mode 100644
index 91ea8a6..0000000
--- a/DOCS/TODO.md
+++ /dev/null
@@ -1,6 +0,0 @@
-5. Rozbudować dane o producencie o pola z shopPRO
-11. Nowa zakładka ze stanami magazynowyi z inputami do szybkiego wpisania aktualnego stanu magazynowego
-13. Możliwość edycji pojedynczych wartości dla integracji shopPRO
-14. Możliwość wysyłania wybranych zdjęć przy eksporcie pojedynczego produktu.
-16. Obsługa pola Pozwól zamawiać gdy stan 0:
-17. Integracja z https://kie.ai/
\ No newline at end of file
diff --git a/DOCS/plans/2026-02-27-marketplace-category-assignment-design.md b/DOCS/plans/2026-02-27-marketplace-category-assignment-design.md
deleted file mode 100644
index c27b52f..0000000
--- a/DOCS/plans/2026-02-27-marketplace-category-assignment-design.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# Design: Przypisywanie kategorii shopPRO z poziomu Marketplace orderPRO
-
-**Data:** 2026-02-27
-**Status:** Zatwierdzony
-
-## Cel
-
-Umożliwienie przypisywania produktów do kategorii instancji shopPRO bezpośrednio z widoku "Powiązane oferty" w orderPRO, bez konieczności logowania się do panelu shopPRO.
-
-## Architektura
-
-```
-Przeglądarka → orderPRO (proxy AJAX) → shopPRO API
-```
-
-orderPRO działa jako bezpieczny proxy — klucz API shopPRO nigdy nie trafia do przeglądarki.
-
-## Zakres zmian
-
-### shopPRO (2 pliki)
-
-**1. `autoload/api/Controllers/CategoriesApiController.php`** (nowy)
-- Akcja `list` (GET) — zwraca płaską listę **aktywnych** kategorii:
- ```json
- {"status": "ok", "data": {"categories": [{"id": 1, "parent_id": null, "title": "Nazwa"}]}}
- ```
-- Tytuł z `pp_shop_categories_langs` w domyślnym języku sklepu (`pp_langs` WHERE `start=1`)
-- Tylko `status=1`
-
-**2. `autoload/api/ApiRouter.php`**
-- Rejestracja endpointu `'categories'` → `CategoriesApiController`
-
-### orderPRO (4 pliki)
-
-**1. `src/Modules/Settings/ShopProClient.php`**
-- Nowa metoda `fetchCategories(baseUrl, apiKey, timeoutSeconds)`:
- ```
- GET api.php?endpoint=categories&action=list
- Zwraca: array{ok:bool, categories:array, message:string}
- ```
-
-**2. `src/Modules/Marketplace/MarketplaceController.php`**
-- `categoriesJson(Request)` → `GET /marketplace/{id}/categories`
- - Sprawdza czy integracja jest aktywna i typu `shoppro`
- - Wywołuje `ShopProClient::fetchCategories()`
- - Zwraca JSON z listą kategorii
-- `saveProductCategoriesJson(Request)` → `POST /marketplace/{id}/product/{pid}/categories`
- - Waliduje CSRF
- - Pobiera `category_ids[]` z body
- - Wywołuje `ShopProClient::updateProduct()` z `{"categories": [...]}`
- - Zwraca JSON sukces/błąd
-
-**3. `routes/web.php`**
-```
-GET /marketplace/{integration_id}/categories → categoriesJson
-POST /marketplace/{integration_id}/product/{pid}/categories → saveProductCategoriesJson
-```
-
-**4. `resources/views/marketplace/offers.php`**
-- Nowa kolumna "Kategorie" (tylko gdy `integration.type === 'shoppro'`)
-- Przycisk "Przypisz kategorie" z `data-product-id="{external_product_id}"`
-- Modal z drzewkiem kategorii (checkbox tree, vanilla JS)
-- Aktualne kategorie produktu pobierane z istniejącego `fetchProductById()` przez nowy endpoint
-
-## Przepływ danych (kliknięcie przycisku)
-
-1. Klik "Przypisz kategorie" → spinner w przycisku
-2. Równoległe AJAX GET:
- - `/marketplace/{id}/categories` → lista kategorii instancji
- - `/marketplace/{id}/product/{pid}/categories` → aktualne kategorie produktu
- (endpoint wewnętrznie woła `products/get` na shopPRO i zwraca `categories` array)
-3. JS buduje drzewo kategorii z płaskiej listy (rekurencyjnie po `parent_id`)
-4. Pre-zaznacza checkboxy dla już przypisanych kategorii
-5. Modal otwarty
-6. Użytkownik zaznacza/odznacza, klika "Zapisz"
-7. POST `/marketplace/{id}/product/{pid}/categories` z `{category_ids: [1,5], csrf_token: "..."}`
-8. Toast sukcesu lub błędu
-
-## Bezpieczeństwo
-
-- Klucz API shopPRO nigdy nie opuszcza serwera orderPRO
-- CSRF token wymagany przy POST
-- Walidacja `integration_id` i `external_product_id` (muszą być int > 0)
-- Integracja musi być aktywna i należeć do zalogowanego użytkownika (istniejąca AuthService)
-
-## Decyzje projektowe
-
-- Płaska lista kategorii (nie zagnieżdżona) — drzewo buduje JS po stronie klienta
-- Vanilla JS — brak dodatkowych zależności
-- Tylko aktywne kategorie (status=1)
-- Tytuł w domyślnym języku sklepu
-- Kategorie cacheowane w pamięci JS na czas sesji strony (jeden fetch per integration)
diff --git a/DOCS/plans/2026-02-27-marketplace-category-assignment.md b/DOCS/plans/2026-02-27-marketplace-category-assignment.md
deleted file mode 100644
index 389a90a..0000000
--- a/DOCS/plans/2026-02-27-marketplace-category-assignment.md
+++ /dev/null
@@ -1,907 +0,0 @@
-# Marketplace Category Assignment Implementation Plan
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Dodaj kolumnę "Przypisz kategorie" w widoku powiązanych ofert marketplace orderPRO, która otwiera modal z drzewkiem kategorii shopPRO i umożliwia zapis wybranych kategorii do instancji shopPRO.
-
-**Architecture:** orderPRO działa jako proxy AJAX — przeglądarka nigdy nie widzi klucza API shopPRO. shopPRO dostaje nowy endpoint `categories/list` zwracający płaską listę aktywnych kategorii. Drzewo kategorii buduje vanilla JS po stronie klienta. Zapis kategorii używa istniejącego `products/update` w shopPRO API.
-
-**Tech Stack:** PHP 8.x, vanilla JS (bez dodatkowych bibliotek), `window.OrderProAlerts` (globalny, już załadowany w layoucie), `Response::json()` dla AJAX.
-
----
-
-## Kontekst — kluczowe pliki
-
-| Plik | Rola |
-|------|------|
-| `C:\visual studio code\projekty\shopPRO\autoload\api\ApiRouter.php` | Rejestracja endpointów shopPRO API |
-| `C:\visual studio code\projekty\shopPRO\autoload\api\Controllers\ProductsApiController.php` | Wzorzec dla nowego kontrolera |
-| `C:\visual studio code\projekty\shopPRO\autoload\Domain\Category\CategoryRepository.php` | Metody DB kategorii |
-| `C:\visual studio code\projekty\orderPRO\src\Modules\Settings\ShopProClient.php` | Klient HTTP do shopPRO |
-| `C:\visual studio code\projekty\orderPRO\src\Modules\Marketplace\MarketplaceController.php` | Kontroler do rozszerzenia |
-| `C:\visual studio code\projekty\orderPRO\routes\web.php` | Trasy — dodać 2 nowe |
-| `C:\visual studio code\projekty\orderPRO\resources\views\marketplace\offers.php` | Widok tabeli ofert |
-| `C:\visual studio code\projekty\orderPRO\resources\lang\pl.php` | Tłumaczenia PL |
-
----
-
-## Task 1: Nowy endpoint `categories/list` w shopPRO
-
-**Files:**
-- Create: `C:\visual studio code\projekty\shopPRO\autoload\api\Controllers\CategoriesApiController.php`
-- Modify: `C:\visual studio code\projekty\shopPRO\autoload\api\ApiRouter.php`
-
-### Step 1: Utwórz `CategoriesApiController.php`
-
-```php
-categoryRepo = $categoryRepo;
- }
-
- public function list(): void
- {
- if (!ApiRouter::requireMethod('GET')) {
- return;
- }
-
- $db = $GLOBALS['mdb'] ?? null;
- if (!$db) {
- ApiRouter::sendError('INTERNAL_ERROR', 'Database not available', 500);
- return;
- }
-
- // Pobierz domyślny język sklepu
- $defaultLang = $db->get('pp_langs', 'id', ['start' => 1]);
- if (!$defaultLang) {
- $defaultLang = 'pl';
- }
- $defaultLang = (string)$defaultLang;
-
- // Pobierz wszystkie aktywne kategorie (płaska lista)
- $rows = $db->select(
- 'pp_shop_categories',
- ['id', 'parent_id'],
- [
- 'status' => 1,
- 'ORDER' => ['o' => 'ASC'],
- ]
- );
-
- if (!is_array($rows)) {
- ApiRouter::sendSuccess(['categories' => []]);
- return;
- }
-
- $categories = [];
- foreach ($rows as $row) {
- $categoryId = (int)($row['id'] ?? 0);
- if ($categoryId <= 0) {
- continue;
- }
-
- $title = $db->get('pp_shop_categories_langs', 'title', [
- 'AND' => [
- 'category_id' => $categoryId,
- 'lang_id' => $defaultLang,
- ],
- ]);
-
- // Fallback: jeśli brak tłumaczenia w domyślnym języku, weź pierwsze dostępne
- if (!$title) {
- $title = $db->get('pp_shop_categories_langs', 'title', [
- 'category_id' => $categoryId,
- 'title[!]' => '',
- 'LIMIT' => 1,
- ]);
- }
-
- $parentId = $row['parent_id'] !== null ? (int)$row['parent_id'] : null;
-
- $categories[] = [
- 'id' => $categoryId,
- 'parent_id' => $parentId,
- 'title' => (string)($title ?? 'Kategoria #' . $categoryId),
- ];
- }
-
- ApiRouter::sendSuccess(['categories' => $categories]);
- }
-}
-```
-
-### Step 2: Zarejestruj endpoint w `ApiRouter.php`
-
-W metodzie `getControllerFactories()` dodaj wpis `'categories'` **po** wpisie `'dictionaries'`:
-
-```php
-'categories' => function () use ($db) {
- $categoryRepo = new \Domain\Category\CategoryRepository($db);
- return new Controllers\CategoriesApiController($categoryRepo);
-},
-```
-
-### Step 3: Przetestuj endpoint ręcznie
-
-Wywołaj z terminala (zastąp URL, klucz i ID instancji shopPRO):
-```bash
-curl -s -H "X-Api-Key: TWOJ_KLUCZ" \
- "https://INSTANCJA_SHOPPRO/api.php?endpoint=categories&action=list"
-```
-
-Oczekiwany wynik:
-```json
-{
- "status": "ok",
- "data": {
- "categories": [
- {"id": 1, "parent_id": null, "title": "Główna kategoria"},
- {"id": 3, "parent_id": 1, "title": "Podkategoria"}
- ]
- }
-}
-```
-
-### Step 4: Commit w shopPRO
-
-```bash
-cd "C:\visual studio code\projekty\shopPRO"
-git add autoload/api/Controllers/CategoriesApiController.php autoload/api/ApiRouter.php
-git commit -m "feat: add categories/list API endpoint"
-```
-
----
-
-## Task 2: Metoda `fetchCategories()` w `ShopProClient`
-
-**Files:**
-- Modify: `C:\visual studio code\projekty\orderPRO\src\Modules\Settings\ShopProClient.php`
-
-### Step 1: Dodaj metodę po `ensureProducer()` (przed `testConnection()`)
-
-```php
-/**
- * @return array{ok:bool,http_code:int|null,message:string,categories:array>}
- */
-public function fetchCategories(
- string $baseUrl,
- string $apiKey,
- int $timeoutSeconds
-): array {
- $normalizedBaseUrl = rtrim(trim($baseUrl), '/');
- $endpointUrl = $normalizedBaseUrl . '/api.php?endpoint=categories&action=list';
-
- $response = $this->requestJson($endpointUrl, $apiKey, $timeoutSeconds);
- if (($response['ok'] ?? false) !== true) {
- return [
- 'ok' => false,
- 'http_code' => $response['http_code'] ?? null,
- 'message' => (string) ($response['message'] ?? 'Nie mozna pobrac kategorii z shopPRO.'),
- 'categories' => [],
- ];
- }
-
- $data = is_array($response['data'] ?? null) ? $response['data'] : [];
- $categories = isset($data['categories']) && is_array($data['categories'])
- ? $data['categories']
- : [];
-
- return [
- 'ok' => true,
- 'http_code' => $response['http_code'] ?? null,
- 'message' => '',
- 'categories' => $categories,
- ];
-}
-```
-
-### Step 2: Commit
-
-```bash
-cd "C:\visual studio code\projekty\orderPRO"
-git add src/Modules/Settings/ShopProClient.php
-git commit -m "feat: add ShopProClient::fetchCategories() method"
-```
-
----
-
-## Task 3: Dwa nowe endpointy AJAX w `MarketplaceController`
-
-**Files:**
-- Modify: `C:\visual studio code\projekty\orderPRO\src\Modules\Marketplace\MarketplaceController.php`
-
-### Kontekst — co robi kontroler
-
-Kontroler dostaje z konstruktora: `$template`, `$translator`, `$auth`, `$marketplace` (MarketplaceRepository).
-Nie ma `$shopProClient` ani `$integrationRepository` — musimy je dodać przez `IntegrationRepository`.
-
-Spójrz jak `routes/web.php` tworzy kontroler (linia 89-94) — musimy tam dodać `ShopProClient` i `IntegrationRepository`.
-
-### Step 1: Rozszerz konstruktor kontrolera
-
-Zmień sygnaturę konstruktora na:
-
-```php
-public function __construct(
- private readonly Template $template,
- private readonly Translator $translator,
- private readonly AuthService $auth,
- private readonly MarketplaceRepository $marketplace,
- private readonly \App\Modules\Settings\IntegrationRepository $integrationRepository,
- private readonly \App\Modules\Settings\ShopProClient $shopProClient
-) {
-}
-```
-
-### Step 2: Dodaj metodę `categoriesJson()`
-
-Uwaga: używamy `findApiCredentials()` (nie `findById()`) — tylko ta metoda zwraca odszyfrowany `api_key`.
-
-```php
-public function categoriesJson(Request $request): Response
-{
- $integrationId = max(0, (int) $request->input('integration_id', 0));
- if ($integrationId <= 0) {
- return Response::json(['ok' => false, 'message' => 'Brak integration_id.'], 400);
- }
-
- $integration = $this->marketplace->findActiveIntegrationById($integrationId);
- if ($integration === null) {
- return Response::json(['ok' => false, 'message' => 'Integracja nie istnieje lub jest nieaktywna.'], 404);
- }
-
- $creds = $this->integrationRepository->findApiCredentials($integrationId);
- if ($creds === null) {
- return Response::json(['ok' => false, 'message' => 'Brak danych uwierzytelniających.'], 404);
- }
-
- $result = $this->shopProClient->fetchCategories(
- (string) ($creds['base_url'] ?? ''),
- (string) ($creds['api_key'] ?? ''),
- (int) ($creds['timeout_seconds'] ?? 10)
- );
-
- if (!($result['ok'] ?? false)) {
- return Response::json(['ok' => false, 'message' => $result['message']], 502);
- }
-
- return Response::json(['ok' => true, 'categories' => $result['categories']]);
-}
-```
-
-### Step 3: Dodaj metodę `saveProductCategoriesJson()`
-
-Uwaga: Request::capture() buduje z `$_POST` — dla JSON body potrzebujemy `file_get_contents('php://input')`. Parsujemy ręcznie.
-
-```php
-public function saveProductCategoriesJson(Request $request): Response
-{
- $integrationId = max(0, (int) $request->input('integration_id', 0));
- $externalProductId = max(0, (int) $request->input('external_product_id', 0));
-
- if ($integrationId <= 0 || $externalProductId <= 0) {
- return Response::json(['ok' => false, 'message' => 'Brak wymaganych parametrów.'], 400);
- }
-
- // CSRF z JSON body
- $rawBody = (string) file_get_contents('php://input');
- $body = json_decode($rawBody, true);
- if (!is_array($body)) {
- return Response::json(['ok' => false, 'message' => 'Nieprawidłowe ciało żądania JSON.'], 400);
- }
-
- $csrfToken = (string) ($body['_token'] ?? '');
- if (!\App\Core\Security\Csrf::validate($csrfToken)) {
- return Response::json(['ok' => false, 'message' => 'Nieprawidłowy token CSRF.'], 403);
- }
-
- $integration = $this->marketplace->findActiveIntegrationById($integrationId);
- if ($integration === null) {
- return Response::json(['ok' => false, 'message' => 'Integracja nie istnieje lub jest nieaktywna.'], 404);
- }
-
- $creds = $this->integrationRepository->findApiCredentials($integrationId);
- if ($creds === null) {
- return Response::json(['ok' => false, 'message' => 'Brak danych uwierzytelniających.'], 404);
- }
-
- $categoryIds = isset($body['category_ids']) && is_array($body['category_ids'])
- ? array_values(array_filter(array_map('intval', $body['category_ids']), static fn(int $id): bool => $id > 0))
- : [];
-
- $result = $this->shopProClient->updateProduct(
- (string) ($creds['base_url'] ?? ''),
- (string) ($creds['api_key'] ?? ''),
- (int) ($creds['timeout_seconds'] ?? 10),
- $externalProductId,
- ['categories' => $categoryIds]
- );
-
- if (!($result['ok'] ?? false)) {
- return Response::json(['ok' => false, 'message' => $result['message']], 502);
- }
-
- return Response::json(['ok' => true]);
-}
-```
-
-### Step 4: Metoda `IntegrationRepository::findApiCredentials()` — potwierdzone
-
-Użyj `findApiCredentials(int $id): ?array` — zwraca `['id', 'name', 'base_url', 'timeout_seconds', 'api_key']` z odszyfrowanym kluczem. **Nie używaj `findById()`** — ta metoda nie zwraca klucza API.
-
----
-
-## Task 4: Zaktualizuj `routes/web.php` — konstruktor + 2 nowe trasy
-
-**Files:**
-- Modify: `C:\visual studio code\projekty\orderPRO\routes\web.php`
-
-### Step 1: Dodaj `IntegrationRepository` i `ShopProClient` do konstruktora kontrolera
-
-Znajdź blok tworzenia `$marketplaceController` (linia ~89):
-```php
-$marketplaceController = new MarketplaceController(
- $template,
- $translator,
- $auth,
- $marketplaceRepository
-);
-```
-
-Zastąp:
-```php
-$marketplaceController = new MarketplaceController(
- $template,
- $translator,
- $auth,
- $marketplaceRepository,
- $integrationRepository,
- $shopProClient
-);
-```
-
-### Step 2: Dodaj 2 nowe trasy po linii z `/marketplace/{integration_id}`
-
-```php
-$router->get('/marketplace/{integration_id}/categories', [$marketplaceController, 'categoriesJson'], [$authMiddleware]);
-$router->post('/marketplace/{integration_id}/product/{external_product_id}/categories', [$marketplaceController, 'saveProductCategoriesJson'], [$authMiddleware]);
-```
-
-### Step 3: Sprawdź czy router obsługuje parametry w środku ścieżki
-
-Jeśli router nie obsługuje `/marketplace/{id}/product/{pid}/categories` (dwa parametry), użyj query stringa dla `external_product_id`:
-
-```
-POST /marketplace/{integration_id}/product-categories?external_product_id={pid}
-```
-
-I odpowiednio zaktualizuj metodę kontrolera i JS. Sprawdź jak router obsługuje routing w `src/Core/Router.php`.
-
-### Step 4: Commit
-
-```bash
-git add routes/web.php src/Modules/Marketplace/MarketplaceController.php
-git commit -m "feat: add AJAX category endpoints to MarketplaceController"
-```
-
----
-
-## Task 5: Sprawdź `IntegrationRepository::findById()`
-
-**Files:**
-- Read: `C:\visual studio code\projekty\orderPRO\src\Modules\Settings\IntegrationRepository.php`
-
-Otwórz plik i potwierdź:
-1. Nazwa metody zwracającej pełne dane integracji po ID (z odszyfrowanym `api_key`, `base_url`, `timeout_seconds`)
-2. Zaktualizuj wywołania w `MarketplaceController` jeśli nazwa jest inna niż `findById()`
-
-Typowe warianty nazwy: `findById()`, `findByIdDecrypted()`, `getById()`, `findWithCredentials()`.
-
----
-
-## Task 6: Zaktualizuj widok `offers.php` — kolumna + modal + JS
-
-**Files:**
-- Modify: `C:\visual studio code\projekty\orderPRO\resources\views\marketplace\offers.php`
-
-### Step 1: Dodaj nagłówek kolumny w ``
-
-Po ostatnim `
` (Ostatnia zmiana), dodaj:
-```php
-
Kategorie
-```
-
-### Step 2: Dodaj komórkę z przyciskiem w każdym wierszu ``
-
-Po ostatniej komórce `
` (`updated_at`), dodaj:
-```php
-
-
-
-```
-
-Gdzie `$integrationId` to zmienna dostępna z danych integracji. Pobierz ją z `$integrationData['id']` na początku widoku:
-```php
-
-```
-
-### Step 3: Dodaj modal HTML na końcu widoku (przed zamknięciem ``)
-
-```php
-
-
-
-
-
Przypisz kategorie
-
-
-
Ładowanie kategorii...
-
-
-
-
-
-
-```
-
-### Step 4: Dodaj `
-```
-
-**Uwaga do `productCategoriesPromise`:** Aktualny endpoint `GET /marketplace/{id}/categories` zwraca listę wszystkich kategorii. Potrzebujemy osobnego endpointu dla aktualnych kategorii produktu ALBO możemy użyć query stringa by wskazać produkt. Patrz Task 7 poniżej.
-
-### Step 5: Commit
-
-```bash
-git add resources/views/marketplace/offers.php
-git commit -m "feat: add category assignment column and modal to marketplace offers view"
-```
-
----
-
-## Task 7: Trzeci endpoint AJAX — aktualne kategorie produktu
-
-W kroku Task 3 mamy 2 endpointy. Potrzebujemy trzeciego do pobierania aktualnych kategorii produktu z shopPRO.
-
-**Files:**
-- Modify: `C:\visual studio code\projekty\orderPRO\src\Modules\Marketplace\MarketplaceController.php`
-- Modify: `C:\visual studio code\projekty\orderPRO\routes\web.php`
-
-### Step 1: Dodaj metodę `productCategoriesJson()`
-
-```php
-public function productCategoriesJson(Request $request): Response
-{
- $integrationId = max(0, (int) $request->input('integration_id', 0));
- $externalProductId = max(0, (int) $request->input('external_product_id', 0));
-
- if ($integrationId <= 0 || $externalProductId <= 0) {
- return Response::json(['ok' => false, 'message' => 'Brak wymaganych parametrów.'], 400);
- }
-
- $integration = $this->marketplace->findActiveIntegrationById($integrationId);
- if ($integration === null) {
- return Response::json(['ok' => false, 'message' => 'Integracja nie istnieje.'], 404);
- }
-
- $creds = $this->integrationRepository->findById($integrationId);
- if ($creds === null) {
- return Response::json(['ok' => false, 'message' => 'Brak danych uwierzytelniających.'], 404);
- }
-
- $result = $this->shopProClient->fetchProductById(
- (string) ($creds['base_url'] ?? ''),
- (string) ($creds['api_key'] ?? ''),
- (int) ($creds['timeout_seconds'] ?? 10),
- $externalProductId
- );
-
- if (!($result['ok'] ?? false)) {
- return Response::json(['ok' => false, 'message' => $result['message']], 502);
- }
-
- $product = is_array($result['product'] ?? null) ? $result['product'] : [];
- $categoryIds = isset($product['categories']) && is_array($product['categories'])
- ? array_values(array_filter(array_map('intval', $product['categories']), static fn(int $id): bool => $id > 0))
- : [];
-
- return Response::json(['ok' => true, 'current_category_ids' => $categoryIds]);
-}
-```
-
-### Step 2: Dodaj trasę w `routes/web.php`
-
-```php
-$router->get('/marketplace/{integration_id}/product/{external_product_id}/categories', [$marketplaceController, 'productCategoriesJson'], [$authMiddleware]);
-```
-
-Jeśli router nie obsługuje dwóch parametrów w środku ścieżki, użyj:
-```php
-$router->get('/marketplace/{integration_id}/product-categories', [$marketplaceController, 'productCategoriesJson'], [$authMiddleware]);
-```
-
-I zaktualizuj URL w JS (`productCategoriesPromise`) odpowiednio.
-
-### Step 3: Zaktualizuj JS w `offers.php` — URL dla `productCategoriesPromise`
-
-Zmień URL w fetch:
-```js
-var productCategoriesPromise = fetch(
- '/marketplace/' + integrationId + '/product/' + productId + '/categories',
- { headers: { 'Accept': 'application/json' } }
-)
-```
-
-(lub `/product-categories?external_product_id=` jeśli router nie obsługuje dwóch parametrów)
-
-### Step 4: Commit
-
-```bash
-git add src/Modules/Marketplace/MarketplaceController.php routes/web.php resources/views/marketplace/offers.php
-git commit -m "feat: add productCategoriesJson endpoint and fix JS fetch URL"
-```
-
----
-
-## Task 8: Sprawdź router — obsługa parametrów URL
-
-**Files:**
-- Read: `C:\visual studio code\projekty\orderPRO\src\Core\Router.php` (lub podobna ścieżka)
-
-Otwórz plik routera i sprawdź:
-1. Jak są przetwarzane segmenty `{param}` — czy obsługuje wiele parametrów w jednej trasie
-2. Jak parametry trafiają do `Request` — przez `$request->input('param_name')` czy `$request->attributes`
-
-Jeśli router **nie obsługuje** tras w stylu `/marketplace/{id}/product/{pid}/categories` (dwa parametry dynamic), wybierz alternatywę:
-```
-GET /marketplace/{integration_id}/product-categories?external_product_id={pid}
-POST /marketplace/{integration_id}/product-categories (body: {external_product_id, category_ids, _token})
-```
-
-Zaktualizuj odpowiednio routing, metody kontrolera i JS.
-
----
-
-## Task 9: Tłumaczenia w `pl.php`
-
-**Files:**
-- Modify: `C:\visual studio code\projekty\orderPRO\resources\lang\pl.php`
-
-Dodaj klucze do tablicy `'marketplace'`:
-
-```php
-'fields' => [
- // ... istniejące ...
- 'categories' => 'Kategorie',
-],
-'actions' => [
- // ... istniejące ...
- 'assign_categories' => 'Przypisz kategorie',
-],
-'category_modal' => [
- 'title' => 'Przypisz kategorie',
- 'loading' => 'Ładowanie kategorii...',
- 'no_categories' => 'Brak dostępnych kategorii.',
- 'save' => 'Zapisz',
- 'cancel' => 'Anuluj',
- 'saving' => 'Zapisuję...',
- 'saved' => 'Kategorie zapisane.',
- 'error_save' => 'Błąd zapisu.',
- 'error_network' => 'Błąd sieci.',
-],
-```
-
-### Step 2: Commit
-
-```bash
-git add resources/lang/pl.php
-git commit -m "feat: add category assignment translation keys"
-```
-
----
-
-## Task 10: Weryfikacja end-to-end
-
-### Checklist testów manualnych
-
-1. Otwórz `https://orderpro.projectpro.pl/marketplace/1`
-2. Sprawdź czy tabela ma nową kolumnę "Kategorie"
-3. Kliknij "Przypisz kategorie" przy dowolnym produkcie
-4. Sprawdź: modal otwiera się, spinner "Ładowanie kategorii..." widoczny
-5. Sprawdź: drzewo kategorii pojawia się z rozwijanymi gałęziami
-6. Sprawdź: kategorie już przypisane do produktu są wstępnie zaznaczone
-7. Zaznacz/odznacz kilka kategorii, kliknij "Zapisz"
-8. Sprawdź: toast "Kategorie zapisane." pojawia się, modal zamknięty
-9. Otwórz modal ponownie — sprawdź czy zaznaczone kategorie są aktualne
-10. Sprawdź DevTools Network — żadna odpowiedź nie może zawierać klucza API
-
-### Obsługa błędów
-
-- Jeśli shopPRO niedostępny → modal pokazuje alert z komunikatem błędu
-- Jeśli CSRF wygasł → odpowiedź 403 → toast "Nieprawidłowy token CSRF."
-- Jeśli produkt nie istnieje w shopPRO → toast z błędem API
-
-### Commit końcowy
-
-```bash
-cd "C:\visual studio code\projekty\orderPRO"
-git add -A
-git commit -m "feat: marketplace category assignment complete"
-```
-
----
-
-## Ważne: Kluczowe ustalenia
-
-- **Router** obsługuje wiele parametrów `{param}` w jednej ścieżce — trasy jak `/marketplace/{id}/product/{pid}/categories` działają
-- **IntegrationRepository**: używaj `findApiCredentials(int $id)` (nie `findById()`) — tylko ta metoda zwraca odszyfrowany `api_key`
-- **`productCategoriesJson()`** w Task 7 też musi używać `findApiCredentials()`
diff --git a/DOCS/plans/2026-02-27-per-integration-product-content-design.md b/DOCS/plans/2026-02-27-per-integration-product-content-design.md
deleted file mode 100644
index d2a7930..0000000
--- a/DOCS/plans/2026-02-27-per-integration-product-content-design.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# Design: Per-Integration Product Content
-
-**Date:** 2026-02-27
-**Status:** Approved
-
-## Summary
-
-Products need separate `name`, `short_description`, and `description` for each integration. Global values in `product_translations` remain the fallback. Integration-specific overrides are stored in a new table.
-
-## Model
-
-**Global + override per integration:**
-- `product_translations` stays as the global/base content (unchanged)
-- New table `product_integration_translations` stores per-integration overrides
-- NULL field = use global value
-- When exporting to a specific integration, prefer integration-specific content, fall back to global
-
-## Database
-
-New migration file: `20260227_000014_create_product_integration_translations.sql`
-
-```sql
-CREATE TABLE product_integration_translations (
- id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
- product_id INT UNSIGNED NOT NULL,
- integration_id INT UNSIGNED NOT NULL,
- name VARCHAR(255) NULL,
- short_description TEXT NULL,
- description LONGTEXT NULL,
- created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- UNIQUE KEY pit_product_integration_unique (product_id, integration_id),
- CONSTRAINT pit_product_fk FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
- CONSTRAINT pit_integration_fk FOREIGN KEY (integration_id) REFERENCES integrations(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-```
-
-Data migration: For all products currently linked to the "marianek.pl" integration via `product_channel_map`, copy `name`, `short_description`, `description` from `product_translations` to `product_integration_translations`.
-
-## Import Flow
-
-In `SettingsController::importExternalProductById`:
-1. Save to `product_translations` as now (global, unchanged)
-2. Additionally upsert `name`, `short_description`, `description` to `product_integration_translations` for the current `integration_id`
-
-## Repository
-
-New methods in `ProductRepository`:
-- `findIntegrationTranslations(int $productId): array` — returns all per-integration translation rows for a product
-- `upsertIntegrationTranslation(int $productId, int $integrationId, string|null $name, string|null $shortDescription, string|null $description): void`
-
-## Edit UI
-
-In `products/edit.php`, the Name/Short description/Description section gets tabs at the top:
-
-```
-[ Globalna ] [ marianek.pl ] [ inny sklep... ]
-```
-
-- Each tab shows: Nazwa, Krótki opis, Opis (WYSIWYG with Quill)
-- "Globalna" tab = existing global fields (`name`, `short_description`, `description`)
-- Integration tabs = per-integration overrides (`integration_content[{id}][name]`, etc.)
-- Rest of the form (prices, SKU, images, meta) is global — no tabs
-
-## Controller Changes
-
-`ProductsController`:
-- `edit` action: load active integrations + `findIntegrationTranslations($id)`, pass to view
-- `update` action: process `integration_content[{id}]` array, call `upsertIntegrationTranslation` for each
-
-## Existing Products Migration
-
-One-off SQL script assigns existing product content to "marianek.pl" integration. All products in `product_channel_map` linked to the marianek.pl integration get their current `product_translations` content copied to `product_integration_translations`.
diff --git a/DOCS/plans/2026-02-27-per-integration-product-content.md b/DOCS/plans/2026-02-27-per-integration-product-content.md
deleted file mode 100644
index bc276a8..0000000
--- a/DOCS/plans/2026-02-27-per-integration-product-content.md
+++ /dev/null
@@ -1,600 +0,0 @@
-# Per-Integration Product Content Implementation Plan
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Store separate `name`, `short_description`, and `description` per integration (shopPRO instance), with global `product_translations` as fallback.
-
-**Architecture:** New table `product_integration_translations (product_id, integration_id, name, short_description, description)` stores overrides. Import saves content to both global and per-integration tables. Edit form shows tabs: Globalna | per-integration.
-
-**Tech Stack:** PHP 8.4, MariaDB, vanilla JS (Quill WYSIWYG already loaded on edit page)
-
----
-
-### Task 1: Database migration — create table
-
-**Files:**
-- Create: `database/migrations/20260227_000014_create_product_integration_translations.sql`
-
-**Step 1: Create the migration file**
-
-```sql
-CREATE TABLE IF NOT EXISTS product_integration_translations (
- id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
- product_id INT UNSIGNED NOT NULL,
- integration_id INT UNSIGNED NOT NULL,
- name VARCHAR(255) NULL,
- short_description TEXT NULL,
- description LONGTEXT NULL,
- created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- UNIQUE KEY pit_product_integration_unique (product_id, integration_id),
- KEY pit_product_idx (product_id),
- KEY pit_integration_idx (integration_id),
- CONSTRAINT pit_product_fk
- FOREIGN KEY (product_id) REFERENCES products(id)
- ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT pit_integration_fk
- FOREIGN KEY (integration_id) REFERENCES integrations(id)
- ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-
--- Migrate existing products to marianek.pl integration.
--- Finds the integration by name 'marianek.pl' and copies current
--- product_translations content for all linked products.
-INSERT INTO product_integration_translations
- (product_id, integration_id, name, short_description, description, created_at, updated_at)
-SELECT
- pt.product_id,
- i.id AS integration_id,
- pt.name,
- pt.short_description,
- pt.description,
- NOW(),
- NOW()
-FROM product_translations pt
-INNER JOIN product_channel_map pcm ON pcm.product_id = pt.product_id
-INNER JOIN integrations i ON i.id = pcm.integration_id
-WHERE i.name = 'marianek.pl'
- AND pt.lang = 'pl'
-ON DUPLICATE KEY UPDATE
- name = VALUES(name),
- short_description = VALUES(short_description),
- description = VALUES(description),
- updated_at = VALUES(updated_at);
-```
-
-**Step 2: Run the migration via settings panel**
-
-Navigate to `/settings/database` and run pending migrations, or trigger via the app's migration runner. Verify table exists:
-```sql
-SHOW TABLES LIKE 'product_integration_translations';
-SELECT COUNT(*) FROM product_integration_translations;
-```
-
-**Step 3: Commit**
-
-```bash
-git add database/migrations/20260227_000014_create_product_integration_translations.sql
-git commit -m "feat: add product_integration_translations table and migrate marianek.pl data"
-```
-
----
-
-### Task 2: ProductRepository — two new methods
-
-**Files:**
-- Modify: `src/Modules/Products/ProductRepository.php`
-
-**Step 1: Add `findIntegrationTranslations` method**
-
-Add after the `findImagesByProductId` method (around line 250):
-
-```php
-/**
- * @return array>
- */
-public function findIntegrationTranslations(int $productId): array
-{
- $stmt = $this->pdo->prepare(
- 'SELECT pit.id, pit.product_id, pit.integration_id,
- pit.name, pit.short_description, pit.description,
- i.name AS integration_name
- FROM product_integration_translations pit
- INNER JOIN integrations i ON i.id = pit.integration_id
- WHERE pit.product_id = :product_id
- ORDER BY i.name ASC'
- );
- $stmt->execute(['product_id' => $productId]);
- $rows = $stmt->fetchAll();
-
- if (!is_array($rows)) {
- return [];
- }
-
- return array_map(static fn (array $row): array => [
- 'id' => (int) ($row['id'] ?? 0),
- 'product_id' => (int) ($row['product_id'] ?? 0),
- 'integration_id' => (int) ($row['integration_id'] ?? 0),
- 'integration_name' => (string) ($row['integration_name'] ?? ''),
- 'name' => isset($row['name']) ? (string) $row['name'] : null,
- 'short_description' => isset($row['short_description']) ? (string) $row['short_description'] : null,
- 'description' => isset($row['description']) ? (string) $row['description'] : null,
- ], $rows);
-}
-```
-
-**Step 2: Add `upsertIntegrationTranslation` method**
-
-Add immediately after the method above:
-
-```php
-public function upsertIntegrationTranslation(
- int $productId,
- int $integrationId,
- ?string $name,
- ?string $shortDescription,
- ?string $description
-): void {
- $now = date('Y-m-d H:i:s');
- $stmt = $this->pdo->prepare(
- 'INSERT INTO product_integration_translations
- (product_id, integration_id, name, short_description, description, created_at, updated_at)
- VALUES
- (:product_id, :integration_id, :name, :short_description, :description, :created_at, :updated_at)
- ON DUPLICATE KEY UPDATE
- name = VALUES(name),
- short_description = VALUES(short_description),
- description = VALUES(description),
- updated_at = VALUES(updated_at)'
- );
- $stmt->execute([
- 'product_id' => $productId,
- 'integration_id' => $integrationId,
- 'name' => $name !== '' ? $name : null,
- 'short_description' => $shortDescription !== '' ? $shortDescription : null,
- 'description' => $description !== '' ? $description : null,
- 'created_at' => $now,
- 'updated_at' => $now,
- ]);
-}
-```
-
-**Step 3: Commit**
-
-```bash
-git add src/Modules/Products/ProductRepository.php
-git commit -m "feat: add findIntegrationTranslations and upsertIntegrationTranslation to ProductRepository"
-```
-
----
-
-### Task 3: SettingsController — save per-integration content on import
-
-**Files:**
-- Modify: `src/Modules/Settings/SettingsController.php`
-
-The import flow is in `importExternalProductById` (line ~677). After the transaction commits (line ~783), `$savedProductId` and `$integrationId` are both set.
-
-**Step 1: Inject ProductRepository into SettingsController**
-
-Check the constructor of `SettingsController`. Add `ProductRepository` as a dependency if it is not already present. Look for the constructor and add:
-
-```php
-use App\Modules\Products\ProductRepository;
-```
-
-And in the constructor parameter list:
-```php
-private readonly ProductRepository $products,
-```
-
-If `$this->products` already exists (check the constructor), skip adding it — just use the existing reference.
-
-**Step 2: Add upsert call after transaction commit in `importExternalProductById`**
-
-Locate the block after `$this->pdo->commit();` (around line 783). Add the upsert call inside the try block, before the commit:
-
-```php
-// Save per-integration content override
-if ($integrationId > 0) {
- $this->products->upsertIntegrationTranslation(
- $savedProductId,
- $integrationId,
- $normalized['translation']['name'] ?? null,
- $normalized['translation']['short_description'] ?? null,
- $normalized['translation']['description'] ?? null
- );
-}
-```
-
-Place this BEFORE `$this->pdo->commit()` so it's inside the transaction.
-
-**Step 3: Commit**
-
-```bash
-git add src/Modules/Settings/SettingsController.php
-git commit -m "feat: save per-integration name/short_description/description on product import"
-```
-
----
-
-### Task 4: ProductsController — load per-integration data for edit
-
-**Files:**
-- Modify: `src/Modules/Products/ProductsController.php`
-
-**Step 1: Update the `edit` action (line ~186)**
-
-Find the block that builds data for the edit view. Currently it passes `form`, `productImages`, etc. Add two new variables:
-
-```php
-$activeIntegrations = $this->integrations->listByType('shoppro');
-$integrationTranslations = $this->products->findIntegrationTranslations($id);
-
-// Index integration translations by integration_id for easy lookup in view
-$integrationTranslationsMap = [];
-foreach ($integrationTranslations as $it) {
- $integrationTranslationsMap[(int) $it['integration_id']] = $it;
-}
-```
-
-Add them to the `render()` call:
-```php
-'activeIntegrations' => $activeIntegrations,
-'integrationTranslationsMap' => $integrationTranslationsMap,
-```
-
-**Step 2: Commit**
-
-```bash
-git add src/Modules/Products/ProductsController.php
-git commit -m "feat: pass active integrations and per-integration translations to product edit view"
-```
-
----
-
-### Task 5: ProductsController — save per-integration content on update
-
-**Files:**
-- Modify: `src/Modules/Products/ProductsController.php`
-
-**Step 1: Update the `update` action (line ~416)**
-
-After the successful `$this->service->update(...)` call (and before the redirect), add:
-
-```php
-// Save per-integration content overrides
-$integrationContent = $request->input('integration_content', []);
-if (is_array($integrationContent)) {
- foreach ($integrationContent as $rawIntegrationId => $content) {
- $integrationId = (int) $rawIntegrationId;
- if ($integrationId <= 0 || !is_array($content)) {
- continue;
- }
- $this->products->upsertIntegrationTranslation(
- $id,
- $integrationId,
- isset($content['name']) ? trim((string) $content['name']) : null,
- isset($content['short_description']) ? trim((string) $content['short_description']) : null,
- isset($content['description']) ? trim((string) $content['description']) : null
- );
- }
-}
-```
-
-Place this block AFTER the image changes block and BEFORE the success Flash/redirect.
-
-**Step 2: Commit**
-
-```bash
-git add src/Modules/Products/ProductsController.php
-git commit -m "feat: save per-integration content overrides on product update"
-```
-
----
-
-### Task 6: Edit view — content tabs UI
-
-**Files:**
-- Modify: `resources/views/products/edit.php`
-
-**Step 1: Replace the static name/short_description/description fields with a tabbed section**
-
-Current structure (around line 20-25 for name, and lines 111-125 for descriptions):
-
-```php
-
-```
-
-And:
-```php
-
-
-```
-
-**New structure:** wrap name + short_description + description in a tabbed card. Add this BEFORE the `
` (the existing grid with SKU, EAN etc.), replacing the name field in the grid:
-
-Remove the `name` label from `form-grid` and create a new card section above it:
-
-```php
-
-
-