diff --git a/.claude/commands/wygeneruj-projekty.md b/.claude/commands/wygeneruj-projekty.md new file mode 100644 index 0000000..10dc0cf --- /dev/null +++ b/.claude/commands/wygeneruj-projekty.md @@ -0,0 +1,122 @@ +# Generowanie projektów graficznych z zamówień + +Jesteś silnikiem generowania projektów graficznych dla zamówień w orderPRO. Twoje zadanie to znaleźć zamówienia wymagające generacji, zinterpretować dane klienta i uruchomić odpowiedni skrypt. + +## Przepływ + +### 1. Połącz się z bazą danych + +Wczytaj dane z pliku `.env` w katalogu projektu: +- `DB_HOST_REMOTE` — host bazy (użyj remote, nie localhost) +- `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`, `DB_PORT` + +Użyj `"C:/xampp/mysql/bin/mysql.exe"` do zapytań (z flagą `--default-character-set=utf8mb4`). + +### 2. Pobierz aktywne mapowania + +```sql +SELECT id, product_name_pattern, script_name, output_dir +FROM project_mappings +WHERE is_active = 1; +``` + +Jeśli brak mapowań — poinformuj użytkownika i zakończ. + +### 3. Znajdź zamówienia do generacji (z dopasowaniem do mapowań) + +Schemat bazy danych — kluczowe kolumny: +- `orders.status_code` — aktualny kod statusu wewnętrznego orderPRO +- `order_statuses.code` — kody statusów, wiązanie: `orders.status_code = order_statuses.code` +- `order_statuses.group_id` → `order_status_groups.id` — grupy statusów +- `order_items.original_name` — nazwa produktu +- `order_items.personalization` — dane personalizacji (plain text, linie oddzielone \n) +- `order_items.project_generated` — 0 = nie wygenerowano, 1 = wygenerowano +- `order_addresses.address_type` — typ adresu ('customer', 'delivery', 'invoice') +- `order_addresses.name` — pełne imię i nazwisko kupującego + +Jedno zapytanie łączy zamówienia z mapowaniami (JOIN eliminuje produkty bez mapowania): + +```sql +SELECT + oi.id AS item_id, + oi.original_name AS product_name, + oi.personalization, + oi.quantity, + o.id AS order_id, + o.internal_order_number, + o.status_code AS status_code, + oa.name AS buyer_name, + pm.script_name, + pm.output_dir +FROM order_items oi +JOIN orders o ON oi.order_id = o.id +JOIN order_statuses os ON o.status_code = os.code +JOIN order_status_groups osg ON os.group_id = osg.id +JOIN project_mappings pm ON pm.is_active = 1 + AND oi.original_name LIKE CONCAT('%', pm.product_name_pattern, '%') +LEFT JOIN order_addresses oa ON o.id = oa.order_id AND oa.address_type = 'customer' +WHERE osg.id = 2 + AND oi.project_generated = 0 + AND oi.personalization IS NOT NULL + AND oi.personalization <> '' +ORDER BY o.id; +``` + +Jeśli brak wyników — poinformuj użytkownika że nie ma zamówień do generacji pasujących do aktywnych mapowań. + +### 5. Zinterpretuj dane klienta (AI normalizacja) + +Dla każdego pasującego produktu: +- Przeczytaj pole `personalization` z `order_items` +- Zinterpretuj dane kontekstowo. Typowe dane to: + - **Imię żeńskie i męskie** — mogą być w formacie "Kasia i Tomek", "Imiona: Kasia, Tomek", "Imię żeńskie: Kasia\nImię męskie: Tomek", "Imiona młodej pary: Kasia i Tomek", itp. + - **Data uroczystości** — format DD.MM.YYYY, może być opisana jako "Data: 30.04.2026" lub "Data uroczystości: 30.04.2026" lub po prostu "30.04.2026" + - **Życzenia** — opcjonalny tekst (np. "Na zdrowie!", "Dziękujemy!"). Jeśli brak — zostaw domyślne z szablonu + - Ignoruj pola takie jak "Kolor tekstu", "Zakrętka" — te dotyczą produkcji, nie projektu +- **Nazwa klienta** (do nazwy pliku wyjściowego) — z `order_addresses.name` (pole `buyer_name` w zapytaniu) + +### 6. Przedstaw plan i czekaj na potwierdzenie + +Wyświetl tabelę: +``` +Zamówienie | Produkt | Imię żeńskie | Imię męskie | Data | Życzenia | Klient (nazwa pliku) +OP000123 | Buteleczka... | Kinga | Łukasz | 30.04.2026 | (domyślne) | Kinga Klimczak +``` + +Zapytaj: "Wygenerować projekty? (tak/nie)" + +### 7. Uruchom generowanie + +Przed generowaniem sprawdź czy Photoshop jest uruchomiony: +```bash +tasklist //FI "IMAGENAME eq Photoshop.exe" 2>/dev/null | grep -i photoshop +``` +Jeśli Photoshop nie jest uruchomiony — poinformuj użytkownika i poczekaj aż go uruchomi. + +Dla każdego potwierdzonego produktu: +1. Uruchom skrypt: `python tools/generowanie/{script_name} --imie_zenskie "X" --imie_meskie "Y" --data "DD.MM.YYYY" --klient "Imię Nazwisko"` (dodaj `--zyczenia` jeśli klient podał niestandardowe) +2. Jeśli mapowanie ma `output_dir` — skrypt powinien zapisać tam (jeśli obsługuje) + +### 8. Oznacz w bazie + +Po udanym generowaniu: +```sql +UPDATE order_items +SET project_generated = 1, project_generated_at = NOW() +WHERE id = {item_id}; +``` + +### 9. Raport końcowy + +Wyświetl podsumowanie: +- Ile projektów wygenerowano +- Ile pominięto (brak mapowania, brak personalizacji) +- Ewentualne błędy + +## Ważne zasady + +- **Zawsze pytaj o potwierdzenie** przed generowaniem +- **Photoshop musi być uruchomiony** — sprawdź przed generowaniem +- **Nie oznaczaj jako wygenerowane** jeśli skrypt zwrócił błąd +- **Jeśli nie potrafisz zinterpretować personalizacji** — wyświetl surowe dane i zapytaj użytkownika +- Skrypty generujące znajdują się w `tools/generowanie/` w katalogu projektu orderPRO diff --git a/.claude/commands/wygeneruj-skrypt.md b/.claude/commands/wygeneruj-skrypt.md new file mode 100644 index 0000000..a263d65 --- /dev/null +++ b/.claude/commands/wygeneruj-skrypt.md @@ -0,0 +1,162 @@ +# Kreator skryptu generowania projektu graficznego + +Tworzysz nowy skrypt Python do generowania projektow graficznych z szablonu PSD. Skrypt bedzie uzywal Photoshop API (photoshop-python-api) do podmiany tekstow w Smart Objects z zachowaniem pozycji warstw. + +## Wymagania + +- Photoshop musi byc uruchomiony — sprawdz: `tasklist //FI "IMAGENAME eq Photoshop.exe" 2>/dev/null | grep -i photoshop` +- Python z pakietem `photoshop-python-api` musi byc zainstalowany + +## Przepływ + +### 1. Pobierz sciezke do PSD + +Uzytkownik poda sciezke do pliku PSD szablonu. Zweryfikuj ze plik istnieje. + +### 2. Eksploruj strukture warstw + +Otworz PSD w Photoshopie przez Python i rekurencyjnie przejdz wszystkie warstwy: + +```python +import photoshop.api as ps + +app = ps.Application() +doc = app.open(PSD_PATH) + +def explore(container, indent=0): + try: + for al in container.artLayers: + extra = '' + try: extra = f' text="{al.textItem.contents}"' + except: pass + print(f'{" "*indent}art: "{al.name}" kind={al.kind}{extra}') + except: pass + try: + for ls in container.layerSets: + print(f'{" "*indent}set: "{ls.name}"') + explore(ls, indent+1) + except: pass + +explore(doc) +``` + +Jezeli warstwa jest Smart Objectem (kind=5 lub 7) — wejdz do niego: +```python +app.activeDocument.activeLayer = smart_layer +desc = ps.ActionDescriptor() +ref = ps.ActionReference() +ref.putEnumerated(app.stringIDToTypeID('layer'), app.stringIDToTypeID('ordinal'), app.stringIDToTypeID('targetEnum')) +desc.putReference(app.stringIDToTypeID('null'), ref) +app.executeAction(app.stringIDToTypeID('placedLayerEditContents'), desc) +``` + +Eksploruj zawartosc Smart Object rekurencyjnie (moze byc wiele poziomow zagniezdzenia). + +### 3. Wyswietl drzewo warstw + +Pokaz uzytkownikowi pelna strukture warstw w czytelnym formacie drzewa, np.: +``` +Projekt (Smart Object) + └── Dekoracje (set) + └── Tekst (set) + └── Dane (set) + ├── "imie" kind=2 text="Jan" + ├── "data" kind=2 text="01.01.2026" + └── "zyczenia" kind=2 text="Wszystkiego najlepszego" +``` + +kind=2 to warstwy tekstowe — te sa kandydatami na zmienne. + +### 4. Ustal zmienne + +Zapytaj uzytkownika: +- Ktore warstwy tekstowe maja byc parametrami skryptu? +- Jakie nazwy parametrow CLI? (np. --imie, --data, --zyczenia) +- Ktore parametry sa wymagane, ktore opcjonalne (opcjonalne = zostaw domyslny tekst z szablonu)? + +### 5. Uproszczenie warstw (opcjonalnie) + +Zaproponuj uproszczenie nazw warstw w PSD: +- Przemianuj warstwy na czytelne nazwy (np. "Warstwa 5" -> "Tekst", "Kasia" -> "imie_zenskie") +- Przemianuj grupy (np. "Warstwa 4" -> "Dane") +- Zapytaj uzytkownika o akceptacje + +Jezeli uzytkownik zaakceptuje — wykonaj rename warstw w PSD i zapisz szablon. + +### 6. Ustal sciezki + +Zapytaj uzytkownika: +- Nazwa skryptu (np. `kubki_wzor1.py`) +- Katalog wyjsciowy `_gotowe/` — domyslnie podfolder `_gotowe` w katalogu szablonu PSD +- Nazwa Smart Object glownego (jezeli PSD ma wiele — ktory jest szablonem) + +### 7. Wygeneruj skrypt + +Uzyj wzorca z `tools/generowanie/buteleczki_wzor1.py` jako referencji. Kluczowe elementy: + +```python +import argparse +import os +import photoshop.api as ps + +# Sciezki +SZABLON_PATH = r"..." +GOTOWE_DIR = r"..." +SMART_OBJECT_LAYER = "Projekt" + +def open_smart_object(app): + desc = ps.ActionDescriptor() + ref = ps.ActionReference() + ref.putEnumerated(app.stringIDToTypeID('layer'), app.stringIDToTypeID('ordinal'), app.stringIDToTypeID('targetEnum')) + desc.putReference(app.stringIDToTypeID('null'), ref) + app.executeAction(app.stringIDToTypeID('placedLayerEditContents'), desc) + +def change_text_preserve_position(layer, new_text): + """Zmienia tekst warstwy z zachowaniem oryginalnej pozycji.""" + bounds_before = [float(b) for b in layer.bounds] + layer.textItem.contents = new_text + bounds_after = [float(b) for b in layer.bounds] + dx = bounds_before[0] - bounds_after[0] + dy = bounds_before[1] - bounds_after[1] + if dx != 0 or dy != 0: + layer.translate(dx, dy) + +def generate(**kwargs): + # ... otworz PSD, wejdz do SO, podmien teksty, zapisz kopie + pass + +def main(): + parser = argparse.ArgumentParser(...) + # ... argumenty na podstawie ustalonych zmiennych + args = parser.parse_args() + generate(...) + +if __name__ == "__main__": + main() +``` + +Wazne: +- **Zawsze** uzywaj `change_text_preserve_position()` zamiast bezposredniego `textItem.contents` +- Nawigacja do warstw przez `layerSets['nazwa'].artLayers['nazwa']` +- Po edycji Smart Object: `so_doc.save()` + `so_doc.close()` +- Zapisz jako kopie: `app.activeDocument.saveAs(output_path, psd_opts, True)` +- Zamknij oryginal bez zapisu: `app.activeDocument.close(ps.SaveOptions.DoNotSaveChanges)` +- Parametr `--klient` zawsze wymagany (nazwa pliku wyjsciowego) + +### 8. Przetestuj + +Po wygenerowaniu skryptu: +1. Uruchom go z przykladowymi danymi +2. Sprawdz czy PSD zostal wygenerowany +3. Popros uzytkownika o weryfikacje wizualna + +### 9. Dodaj mapowanie + +Zaproponuj uzytkownikowi dodanie mapowania w panelu (Ustawienia > Mapowanie projektow) — wzorzec nazwy produktu -> nowy skrypt. + +## Wazne zasady + +- Zamykaj PSD po zakonczeniu (nie zostawiaj otwartych dokumentow) +- Przy bledzie — zamknij wszystkie otwarte dokumenty bez zapisu +- Skrypty zapisuj w `tools/generowanie/` w katalogu projektu orderPRO +- Testuj z realnymi danymi przed zakonczeniem diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index 2ca659e..8fc7296 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -96,6 +96,11 @@ Sprzedawca moĹĽe obsĹ‚ugiwać zamĂłwienia ze wszystkich kanałów - [x] Naprawa zapisu delivery_price przy imporcie zamowien (Allegro + shopPRO) + backfill — Phase 90 - [x] Resilient polling w OrderPROPrint — 3 warstwy timeout (HttpClient/CancellationToken/Watchdog) — Phase 91 - [x] Ikona kopiowania imienia i nazwiska kupujacego w szczegolach zamowienia (clipboard copy) — Phase 92 +- [x] Checkbox "Zapamietaj mnie" z persistent cookie 30 dni + dzialajace komunikaty bledow logowania — Phase 93 +- [x] Popup podgladu zamowienia na liscie zamowien (zdjecia, personalizacja, notatki, kopiowanie) — Phase 94 +- [x] AJAX refresh tabeli i panelu statusow po zmianie statusu inline (bez reload strony) — Phase 95 +- [x] Automatyzacja: warunek "Metoda platnosci" (COD/Przelew/Karta/Inna) oparty na external_payment_type_id — Phase 96 +- [x] Polautomatyczne generowanie projektow graficznych PSD z zamowien (mapowania, komenda Claude, Photoshop API, flagi statusu) + rename external_status_id na status_code — Phase 97 - [ ] Eliminacja zduplikowanego kodu: SslCertificateResolver, ToggleableRepositoryTrait, RedirectPathResolver, ReceiptService — Phase 68 ### Active (In Progress) @@ -198,6 +203,6 @@ Quick Reference: --- *PROJECT.md — Updated when requirements or context change* -*Last updated: 2026-04-09 after Phase 92 completion (Buyer Name Copy)* +*Last updated: 2026-04-11 after Phase 96 completion (Automation Payment Method Condition)* diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index cecb15a..8f750d5 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -53,6 +53,11 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel | 90 | Delivery Price Import Fix | 1/1 | Complete | | 91 | Print Client Timeout Resilience | 1/1 | Complete | | 92 | Buyer Name Copy | 1/1 | Complete | +| 93 | Remember Me Login | 1/1 | Complete | +| 94 | Order Preview Popup | 1/1 | Complete | +| 95 | AJAX Table Refresh | 1/1 | Complete | +| 96 | Automation Payment Method Condition | 1/1 | Complete | +| 97 | Project Generation | 1/1 | Complete | | TBD | Mobile Orders List | - | Not started | | TBD | Mobile Order Details | - | Not started | | TBD | Mobile Settings | - | Not started | @@ -376,4 +381,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md` --- *Roadmap created: 2026-03-12* -*Last updated: 2026-04-09 - phase 92 unified (Buyer Name Copy)* +*Last updated: 2026-04-11 - phase 96 unified (Automation Payment Method Condition)* diff --git a/.paul/STATE.md b/.paul/STATE.md index d1cf456..792854a 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -2,22 +2,22 @@ ## Project Reference -See: .paul/PROJECT.md (updated 2026-04-09) +See: .paul/PROJECT.md (updated 2026-04-12) **Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami. -**Current focus:** Milestone v3.0 - Phase 92 complete, ready for next PLAN +**Current focus:** Milestone v3.0 - Phase 97 complete, ready for next phase ## Current Position Milestone: v3.0 Mobile Responsive - In progress -Phase: 92 (Buyer Name Copy) — Complete -Plan: 92-01 unified -Status: Loop complete, ready for next PLAN -Last activity: 2026-04-09 — Unified .paul/phases/92-buyer-name-copy/92-01-PLAN.md +Phase: 97 (Project Generation) — Complete +Plan: 97-01 unified +Status: Loop closed, ready for next PLAN +Last activity: 2026-04-12 — Unified phase 97 Progress: -- Milestone: [#########.] ~93% -- Phase 92: [##########] 100% +- Milestone: [#########.] ~95% +- Phase 97: [##########] 100% ## Loop Position @@ -29,7 +29,7 @@ PLAN ──▶ APPLY ──▶ UNIFY ## Session Continuity -Last session: 2026-04-09 -Stopped at: Plan 92-01 unified -Next action: Run /paul:plan for the next prioritized phase -Resume file: .paul/phases/92-buyer-name-copy/92-01-SUMMARY.md +Last session: 2026-04-12 +Stopped at: Phase 97 unified +Next action: Plan next phase or pause +Resume file: .paul/phases/97-project-generation/97-01-SUMMARY.md diff --git a/.paul/changelog/2026-04-10.md b/.paul/changelog/2026-04-10.md new file mode 100644 index 0000000..ad4b669 --- /dev/null +++ b/.paul/changelog/2026-04-10.md @@ -0,0 +1,38 @@ +# 2026-04-10 + +## Co zrobiono + +- [Phase 93, Plan 01] Checkbox "Zapamietaj mnie" z persistent cookie 30 dni +- Migracja: kolumna remember_token w tabeli users (hash SHA-256) +- Backend: generowanie/walidacja tokena, auto-login z middleware, czyszczenie przy logout +- Frontend: checkbox w formularzu logowania, usuniecie zasleppki bledu logowania +- Tlumaczenie pl.php: klucz remember_me + +## Zmienione pliki + +- `database/migrations/20260410_000081_add_remember_token_to_users.sql` +- `src/Modules/Users/UserRepository.php` +- `src/Modules/Auth/AuthService.php` +- `src/Modules/Auth/AuthController.php` +- `src/Modules/Auth/AuthMiddleware.php` +- `resources/views/auth/login.php` +- `resources/scss/login.scss` +- `resources/lang/pl.php` +- `public/assets/css/login.css` + +- [Phase 94, Plan 01] Popup podgladu zamowienia na liscie zamowien +- Endpoint AJAX: GET /api/orders/{id}/preview (HTML fragment) +- Modal z danymi kupujacego, adresem dostawy, produktami (zdjecia + personalizacja), notatkami klienta, podsumowaniem +- Ikona oka w kolumnie nr zamowienia, kopiowanie do schowka (clipboard API) + +## Zmienione pliki (Phase 94) + +- `src/Modules/Orders/OrdersController.php` +- `routes/web.php` +- `resources/views/orders/partials/preview-content.php` (nowy) +- `resources/views/orders/partials/preview-modal.php` (nowy) +- `resources/views/orders/list.php` +- `resources/scss/modules/_order-preview-modal.scss` (nowy) +- `resources/scss/app.scss` +- `resources/lang/pl.php` +- `public/assets/css/app.css` diff --git a/.paul/changelog/2026-04-11.md b/.paul/changelog/2026-04-11.md new file mode 100644 index 0000000..3006649 --- /dev/null +++ b/.paul/changelog/2026-04-11.md @@ -0,0 +1,22 @@ +# 2026-04-11 + +## Co zrobiono + +- [Phase 95, Plan 01] AJAX refresh tabeli i panelu statusow po zmianie statusu inline +- Controller index() wykrywa AJAX request i zwraca JSON z fragmentami HTML (tableHtml + panelHtml) +- JS inline-status-change.js: fetch + DOM replacement zamiast location.reload() +- Scroll, filtry, paginacja zachowane po odswiezeniu + +- [Phase 96, Plan 01] Nowy warunek automatyzacji "Metoda platnosci" +- 4 opcje: Platnosc przy odbiorze (COD), Przelew bankowy, Karta/online, Inna +- Ewaluacja oparta na external_payment_type_id z pattern matching (StringHelper::isCodPayment) +- Frontend: checkbox-group w formularzu + dynamiczne generowanie JS + +## Zmienione pliki + +- `src/Modules/Orders/OrdersController.php` +- `public/assets/js/modules/inline-status-change.js` +- `src/Modules/Automation/AutomationController.php` +- `src/Modules/Automation/AutomationService.php` +- `resources/views/automation/form.php` +- `public/assets/js/modules/automation-form.js` diff --git a/.paul/changelog/2026-04-12.md b/.paul/changelog/2026-04-12.md new file mode 100644 index 0000000..49305cd --- /dev/null +++ b/.paul/changelog/2026-04-12.md @@ -0,0 +1,43 @@ +# 2026-04-12 + +## Co zrobiono + +- [Phase 97, Plan 01] Polautomatyczne generowanie projektow graficznych PSD z danych zamowien +- Skrypt Python buteleczki_wzor1.py — Photoshop API, podmiana tekstow w Smart Object z zachowaniem pozycji +- Tabela project_mappings + CRUD w panelu (Ustawienia > Mapowanie projektow) +- Komenda Claude Code /wygeneruj-projekty — Claude jako silnik interpretacji danych klienta +- Kolumny project_generated / project_generated_at w order_items +- Flagi statusu projektow na liscie zamowien (ikonki) i w szczegolach (badge przy produkcie) +- Rename kolumny orders.external_status_id na orders.status_code (13+ plikow + migracja) +- Test end-to-end: zamowienie OP000000159 (Kinga Klimczak) wygenerowane i oznaczone + +## Zmienione pliki + +- `tools/generowanie/buteleczki_wzor1.py` +- `database/migrations/20260412_000097_add_project_generation.sql` +- `database/migrations/20260412_000098_rename_external_status_id_to_status_code.sql` +- `src/Modules/Settings/ProjectMappingRepository.php` +- `src/Modules/Settings/ProjectMappingController.php` +- `resources/views/settings/project-mappings.php` +- `resources/scss/modules/_project-mappings.scss` +- `.claude/commands/wygeneruj-projekty.md` +- `routes/web.php` +- `resources/views/layouts/app.php` +- `resources/lang/pl.php` +- `resources/scss/app.scss` +- `src/Modules/Orders/OrdersRepository.php` +- `src/Modules/Orders/OrdersController.php` +- `src/Modules/Orders/OrderImportRepository.php` +- `resources/views/orders/show.php` +- `src/Modules/Automation/OrderStatusAgedService.php` +- `src/Modules/Automation/AutomationService.php` +- `src/Modules/Settings/AllegroOrderImportService.php` +- `src/Modules/Settings/AllegroStatusSyncService.php` +- `src/Modules/Settings/ShopproStatusSyncService.php` +- `src/Modules/Settings/ShopproPaymentStatusSyncService.php` +- `src/Modules/Settings/ShopproOrderMapper.php` +- `bin/randomize_order_statuses.php` +- `bin/deploy_and_seed_orders.php` +- `DOCS/DB_SCHEMA.md` +- `DOCS/ARCHITECTURE.md` +- `DOCS/TECH_CHANGELOG.md` diff --git a/.paul/governance/governance_2026-04-10.jsonl b/.paul/governance/governance_2026-04-10.jsonl new file mode 100644 index 0000000..e866130 --- /dev/null +++ b/.paul/governance/governance_2026-04-10.jsonl @@ -0,0 +1,84 @@ +{"ts":"2026-04-10T09:39:06Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/orderPRO/.paul/phases/93-remember-me-login\"\",\"description\":\"Create phase 93 directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T09:40:13Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\93-remember-me-login\\\\93-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T09:40:28Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T09:40:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:00:03Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\database\\\\migrations\\\\20260410_000081_add_remember_token_to_users.sql","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:00:12Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Users\\\\UserRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:00:20Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Auth\\\\AuthService.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:00:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Auth\\\\AuthService.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:00:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Auth\\\\AuthController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:00:50Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Auth\\\\AuthController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:00:55Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Auth\\\\AuthMiddleware.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:01:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\auth\\\\login.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:01:08Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\auth\\\\login.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:01:15Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\login.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:01:30Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\lang\\\\pl.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:01:43Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && npm run build:css\",\"description\":\"Build SCSS to CSS\"},\"tool_response\":{\"stdout\":\"> orderpro@1.0.0 build:css\\n> sass --style=compressed --no","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:01:56Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:05:40Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\93-remember-me-login\\\\93-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:05:55Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-10.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:06:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:06:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:06:08Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:06:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:06:22Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:20:34Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/orderPRO/.paul/phases/94-order-preview-popup\"\",\"description\":\"Create phase 94 directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupt","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:22:15Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:22:17Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:25:23Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:25:38Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\routes\\\\web.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:26:11Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\partials\\\\preview-content.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:26:20Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\partials\\\\preview-modal.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:26:29Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:26:35Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\list.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:26:52Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\list.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:27:12Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\components\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:27:32Z","tool":"Bash","cmd":"mv \"C:/visual studio code/projekty/orderPRO/resources/scss/components/_order-preview-modal.scss\" \"C:/visual studio code/projekty/orderPRO/resources/scss/modules/_order-preview-modal.scss\"\",\"desc","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:27:38Z","tool":"Bash","cmd":"rmdir \"C:/visual studio code/projekty/orderPRO/resources/scss/components\" 2>/dev/null; echo \"done\"\",\"description\":\"Remove empty components directory\"},\"tool_response\":{\"stdout\":\"done\",\","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:27:43Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\app.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:27:58Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\lang\\\\pl.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:28:06Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && npm run build:css\",\"description\":\"Build SCSS to CSS\"},\"tool_response\":{\"stdout\":\"> orderpro@1.0.0 build:css\\n> sass --style=compressed --no","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:28:21Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:34:57Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:35:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:35:20Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:35:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\partials\\\\preview-content.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:35:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\partials\\\\preview-content.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:35:53Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\lang\\\\pl.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:36:01Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && npm run build:css\",\"description\":\"Build SCSS to CSS\"},\"tool_response\":{\"stdout\":\"> orderpro@1.0.0 build:css\\n> sass --style=compressed --no","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:52:22Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\94-order-preview-popup\\\\94-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:52:33Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-10.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:52:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:52:42Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:52:43Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:52:44Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T10:52:57Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:01:47Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/orderPRO/.paul/phases/95-ajax-table-refresh\"\",\"description\":\"Create phase 95 directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupte","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:02:36Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\95-ajax-table-refresh\\\\95-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:02:51Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:02:59Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:06:51Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:06:59Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\public\\\\assets\\\\js\\\\modules\\\\inline-status-change.js","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:07:08Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\public\\\\assets\\\\js\\\\modules\\\\inline-status-change.js","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:07:30Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\public\\\\assets\\\\js\\\\modules\\\\inline-status-change.js","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:10:46Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:03Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:04Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:09Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:11Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:12Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:15Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:18Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:18Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_order-preview-modal.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:28Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && npx sass resources/scss/app.scss public/assets/css/app.css --no-source-map 2>&1 | tail -3\",\"description\":\"Build SCSS to CSS\"},\"tool_response\":{","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:38Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && npx sass resources/scss/app.scss public/assets/css/app.css --no-source-map 2>&1 | tail -3\",\"timeout\":30000,\"description\":\"Build SCSS to CSS\"},\","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:47Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && npx sass resources/scss/app.scss public/assets/css/app.css --no-source-map 2>&1\",\"timeout\":60000,\"description\":\"Build SCSS to CSS\"},\"tool_resp","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:12:54Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && node node_modules/sass/sass.js resources/scss/app.scss public/assets/css/app.css --no-source-map 2>&1\",\"timeout\":60000,\"description\":\"Build SCSS","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:13:02Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && npx sass resources/scss/app.scss:public/assets/css/app.css --no-source-map 2>&1\",\"timeout\":60000,\"description\":\"Compile SCSS to CSS\"},\"tool_re","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:13:17Z","tool":"Bash","cmd":"\"C:/visual studio code/projekty/orderPRO/node_modules/.bin/sass\" \"C:/visual studio code/projekty/orderPRO/resources/scss/app.scss\":\"C:/visual studio code/projekty/orderPRO/public/assets/css/app.c","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:13:24Z","tool":"Bash","cmd":"\"C:/visual studio code/projekty/orderPRO/node_modules/.bin/sass\" \"C:/visual studio code/projekty/orderPRO/resources/scss/app.scss\" \"C:/visual studio code/projekty/orderPRO/public/assets/css/app.c","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:13:38Z","tool":"Bash","cmd":"wc -l \"C:/visual studio code/projekty/orderPRO/public/assets/css/app.css\"\",\"description\":\"Check compiled CSS line count\"},\"tool_response\":{\"stdout\":\"0 C:/visual studio code/projekty/orderP","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-10T11:13:48Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && node -e \"const sass = require('sass'); const fs = require('fs'); const result = sass.compile('resources/scss/app.scss'); fs.writeFileSync('public/ass","cwd":"/c/visual studio code/projekty/orderPRO"} diff --git a/.paul/governance/governance_2026-04-11.jsonl b/.paul/governance/governance_2026-04-11.jsonl new file mode 100644 index 0000000..080d435 --- /dev/null +++ b/.paul/governance/governance_2026-04-11.jsonl @@ -0,0 +1,35 @@ +{"ts":"2026-04-11T21:46:10Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\95-ajax-table-refresh\\\\95-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:46:21Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:46:22Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:46:28Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:46:29Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:46:40Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-11.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:46:50Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:47:55Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:47:58Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/orderPRO/.paul/phases/96-automation-payment-method\"\",\"description\":\"Create phase 96 directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"int","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:49:07Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\96-automation-payment-method\\\\96-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:49:23Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:49:58Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:50:00Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:50:09Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:50:19Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:50:43Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:50:53Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationService.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:51:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationService.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:51:25Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationService.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:51:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\automation\\\\form.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:51:43Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\automation\\\\form.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:51:52Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\automation\\\\form.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:52:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\public\\\\assets\\\\js\\\\modules\\\\automation-form.js","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:52:34Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\public\\\\assets\\\\js\\\\modules\\\\automation-form.js","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:52:36Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\public\\\\assets\\\\js\\\\modules\\\\automation-form.js","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:52:52Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\automation\\\\form.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:52:58Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && C:/xampp/php/php.exe -l src/Modules/Automation/AutomationController.php && C:/xampp/php/php.exe -l src/Modules/Automation/AutomationService.php\",\"de","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:55:57Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:56:58Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\96-automation-payment-method\\\\96-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:57:08Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:57:09Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:57:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:57:12Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:57:24Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-11.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T21:57:32Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} diff --git a/.paul/governance/governance_2026-04-12.jsonl b/.paul/governance/governance_2026-04-12.jsonl new file mode 100644 index 0000000..7ce8529 --- /dev/null +++ b/.paul/governance/governance_2026-04-12.jsonl @@ -0,0 +1,156 @@ +{"ts":"2026-04-11T22:06:56Z","tool":"Bash","cmd":"pip install photoshop-python-api 2>&1\",\"description\":\"Install photoshop-python-api\"},\"tool_response\":{\"stdout\":\"Collecting photoshop-python-api\\r\\n Downloading photoshop_python_api-0.24.2","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:07:22Z","tool":"Write","file":"d:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\test_smart_object.py","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:07:52Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:09:07Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:09:50Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:10:07Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:10:37Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:10:57Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:12:56Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:13:13Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:13:32Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:18:39Z","tool":"Write","file":"C:\\\\Users\\\\jacek\\\\.claude\\\\projects\\\\C--visual-studio-code-projekty-orderPRO\\\\memory\\\\reference_photoshop_smart_object.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:18:45Z","tool":"Edit","file":"C:\\\\Users\\\\jacek\\\\.claude\\\\projects\\\\C--visual-studio-code-projekty-orderPRO\\\\memory\\\\MEMORY.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:22:08Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:22:21Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:22:30Z","tool":"Bash","cmd":"mkdir -p \"d:/pomysloweprezenty.pl/projekty/ślub - buteleczki/Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1/_gotowe\" && mkdir -p \"d:/pomysloweprezenty.pl/tools/generowanie","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:22:59Z","tool":"Write","file":"d:\\\\pomysloweprezenty.pl\\\\tools\\\\generowanie\\\\buteleczki_wzor1.py","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:23:09Z","tool":"Bash","cmd":"python \"d:/pomysloweprezenty.pl/tools/generowanie/buteleczki_wzor1.py\" --imie_zenskie \"Kinga\" --imie_meskie \"Łukasz\" --data \"30.04.2026\" --klient \"Kinga Klimczak\" 2>&1\",\"timeout\":120000,","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:24:44Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/orderPRO/tools/generowanie\"\",\"description\":\"Create directory in orderPRO project\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":fa","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:24:48Z","tool":"Bash","cmd":"cp \"d:/pomysloweprezenty.pl/tools/generowanie/buteleczki_wzor1.py\" \"C:/visual studio code/projekty/orderPRO/tools/generowanie/buteleczki_wzor1.py\"\",\"description\":\"Copy script to orderPRO proje","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:25:03Z","tool":"Bash","cmd":"rm \"d:/pomysloweprezenty.pl/tools/generowanie/buteleczki_wzor1.py\" && rmdir \"d:/pomysloweprezenty.pl/tools/generowanie\" 2>/dev/null; rmdir \"d:/pomysloweprezenty.pl/tools\" 2>/dev/null; echo \"Usu","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:25:10Z","tool":"Bash","cmd":"rm \"d:/pomysloweprezenty.pl/projekty/ślub - buteleczki/Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1/test_smart_object.py\" 2>/dev/null && echo \"Test script usunięty\" ||","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:27:41Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/orderPRO/.paul/phases/97-project-generation\"\",\"description\":\"Create phase 97 directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupte","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:29:17Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\97-project-generation\\\\97-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:29:35Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:29:42Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:34:34Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\97-project-generation\\\\97-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:36:16Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\database\\\\migrations\\\\20260412_000097_add_project_generation.sql","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:37:08Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\ProjectMappingRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:37:27Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\ProjectMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:38:00Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\settings\\\\project-mappings.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:38:08Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_project-mappings.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:38:12Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\app.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:38:25Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\layouts\\\\app.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:38:31Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\routes\\\\web.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:38:38Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\routes\\\\web.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:38:45Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\routes\\\\web.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:39:00Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\lang\\\\pl.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:39:29Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\lang\\\\pl.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:39:47Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && npx sass resources/scss/app.scss public/assets/css/app.css --no-source-map 2>&1 | tail -5\",\"timeout\":30000,\"description\":\"Build SCSS to CSS\"},\","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:40:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\ProjectMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:41:06Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\ProjectMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:41:26Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\ProjectMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:42:25Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/orderPRO/.claude/commands\"\",\"description\":\"Create commands directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImag","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:44:15Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.claude\\\\commands\\\\wygeneruj-projekty.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:44:52Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"SELECT id, product_name_pattern, script_name, output_dir ","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:44:59Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"SELECT os.code FROM order","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:45:13Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"DESCRIBE order_items;\" 2","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:45:27Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"DESCRIBE order_addresses;","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:45:40Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"SHOW COLUMNS FROM orders ","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:45:47Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"DESCRIBE orders;\" 2>&1\"","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:46:10Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"\\nSELECT \\n oi.id AS","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:46:29Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"DESCRIBE order_status_his","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:46:36Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"\\nSELECT osh.order_id, o","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:46:48Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"\\nSELECT \\n oi.id AS","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:47:26Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"\\nSELECT o.id, o.interna","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:49:46Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"\\nSELECT COLUMN_NAME, CO","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:52:42Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.claude\\\\commands\\\\wygeneruj-projekty.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:53:40Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\database\\\\migrations\\\\20260412_000098_rename_external_status_id_to_status_code.sql","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:53:48Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrderImportRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:08Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:09Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\OrderStatusAgedService.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationService.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:11Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\AllegroOrderImportService.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:12Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\AllegroStatusSyncService.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\ShopproStatusSyncService.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\ShopproPaymentStatusSyncService.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:15Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\ShopproOrderMapper.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:26Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\bin\\\\randomize_order_statuses.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:27Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\bin\\\\deploy_and_seed_orders.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:27Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\database\\\\migrations\\\\20260314_000048_add_orders_performance_indexes.sql","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:38Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\DOCS\\\\DB_SCHEMA.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:39Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\DOCS\\\\ARCHITECTURE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\DOCS\\\\TECH_CHANGELOG.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:54:44Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.claude\\\\commands\\\\wygeneruj-projekty.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:55:01Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\database\\\\drafts\\\\20260302_orders_schema_v1.sql","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:55:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.claude\\\\commands\\\\wygeneruj-projekty.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:55:47Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.claude\\\\commands\\\\wygeneruj-projekty.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:55:52Z","tool":"Bash","cmd":"tasklist //FI \"IMAGENAME eq Photoshop.exe\" 2>/dev/null | grep -i photoshop\",\"description\":\"Check if Photoshop is running\"},\"tool_response\":{\"stdout\":\"Binary file (standard input) matches\"","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:56:03Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && python tools/generowanie/buteleczki_wzor1.py --imie_zenskie \"Kinga\" --imie_meskie \"Łukasz\" --data \"30.04.2026\" --klient \"Kinga Klimczak\" 2>&1","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:56:11Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"UPDATE order_items SET pr","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:56:18Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro --default-character-set=utf8mb4 -e \"SELECT id, original_name,","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:59:36Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T22:59:41Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:00:39Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:00:44Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:01:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersController.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:01:20Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_project-mappings.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:01:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\show.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:01:43Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && npx sass resources/scss/app.scss public/assets/css/app.css --no-source-map 2>&1 | tail -3\",\"timeout\":30000,\"description\":\"Build SCSS\"},\"tool_r","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:02:58Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:04:54Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\97-project-generation\\\\97-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:05:10Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:05:17Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:05:28Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:05:47Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-12.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:08:11Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.claude\\\\commands\\\\wygeneruj-skrypt.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:08:49Z","tool":"Bash","cmd":"tasklist //FI \"IMAGENAME eq Photoshop.exe\" 2>/dev/null | grep -ic photoshop\",\"description\":\"Check Photoshop running\"},\"tool_response\":{\"stdout\":\"1\",\"stderr\":\"\",\"interrupted\":false,\","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:09:02Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 8\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:09:15Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 8\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:11:04Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 8\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:11:39Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\nPSD_PATH = r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 8\\\\s","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:12:14Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\\\\buteleczki_wzor8.py","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:12:26Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && python tools/generowanie/buteleczki_wzor8.py --inicjal_zenski \"K\" --inicjal_meski \"T\" --data \"30.04.2026\" --klient \"Test Wzor8\" 2>&1\",\"timeo","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:13:52Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"SELECT id, product_name_pat","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:14:00Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:14:10Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:15:07Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id, oi.","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:15:19Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT pm.product_name_p","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:15:33Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:15:40Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT osg.id, osg.name,","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:15:52Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:16:55Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.claude\\\\commands\\\\wygeneruj-projekty.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:17:29Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"SELECT id, product_name_p","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:17:43Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"\\nSELECT \\n oi.id AS","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:20:03Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"SELECT id, name FROM orde","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:20:14Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"\\nSELECT \\n oi.id AS","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:20:56Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.claude\\\\commands\\\\wygeneruj-projekty.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:21:32Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"SELECT id, product_name_p","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:21:40Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"\\nSELECT \\n oi.id AS","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:22:02Z","tool":"Bash","cmd":"tasklist //FI \"IMAGENAME eq Photoshop.exe\" 2>/dev/null | grep -i photoshop\",\"description\":\"Check if Photoshop is running\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":fal","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:22:40Z","tool":"Write","file":"C:\\\\Users\\\\jacek\\\\.claude\\\\projects\\\\C--visual-studio-code-projekty-orderPRO\\\\memory\\\\feedback_photoshop_autostart.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:22:49Z","tool":"Edit","file":"C:\\\\Users\\\\jacek\\\\.claude\\\\projects\\\\C--visual-studio-code-projekty-orderPRO\\\\memory\\\\MEMORY.md","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:23:00Z","tool":"Bash","cmd":"start \"\" \"C:/Program Files/Adobe/Adobe Photoshop 2025/Photoshop.exe\" 2>/dev/null; echo \"Photoshop starting...\"\",\"description\":\"Launch Adobe Photoshop\"},\"tool_response\":{\"stdout\":\"Photo","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:23:11Z","tool":"Bash","cmd":"sleep 1 && tasklist //FI \"IMAGENAME eq Photoshop.exe\" 2>/dev/null | grep -i photoshop\",\"description\":\"Check if Photoshop process is running\"},\"tool_response\":{\"stdout\":\"Binary file (standa","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:23:31Z","tool":"Bash","cmd":"cd \"/c/visual studio code/projekty/orderPRO\" && python tools/generowanie/buteleczki_wzor8.py --inicjal_zenski \"M\" --inicjal_meski \"G\" --data \"08.05.2026\" --klient \"Martyna Bielawa\"\",\"timeo","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:23:40Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"UPDATE order_items SET pr","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:24:47Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\napp = ps.Application()\\ndoc = app.open(r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka ","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:25:29Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\napp = ps.Application()\\ndoc = app.open(r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka ","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:25:37Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\napp = ps.Application()\\n# zamknij wszystkie otwarte dokumenty bez zapisu\\nwhile app.documents.length > 0:\\n app.activeDocument.close(ps.SaveOptions.Do","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:27:10Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\napp = ps.Application()\\ndoc = app.open(r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka ","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:27:33Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\\\\buteleczki_wzor6.py","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:27:44Z","tool":"Bash","cmd":"cd \"/c/visual studio code/projekty/orderPRO\" && python tools/generowanie/buteleczki_wzor6.py --imie_zenskie \"Kasia\" --imie_meskie \"Tomek\" --data \"15.06.2026\" --klient \"TEST Kasia Kowalska\"\"","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:28:56Z","tool":"Bash","cmd":"python -c \"\\nimport photoshop.api as ps\\n\\napp = ps.Application()\\ndoc = app.open(r'd:\\\\pomysloweprezenty.pl\\\\projekty\\\\ślub - buteleczki\\\\Podziękowanie dla gości weselnych buteleczka ","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:29:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\\\\buteleczki_wzor6.py","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:29:17Z","tool":"Bash","cmd":"cd \"/c/visual studio code/projekty/orderPRO\" && python tools/generowanie/buteleczki_wzor6.py --imie_zenskie \"Kasia\" --imie_meskie \"Tomek\" --data \"15.06.2026\" --klient \"TEST Kasia Kowalska\"\"","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:29:47Z","tool":"Bash","cmd":"rm \"d:/pomysloweprezenty.pl/projekty/ślub - buteleczki/Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 6/_gotowe/TEST Kasia Kowalska.psd\" && echo \"Usunięto plik testowy\"\",","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:29:52Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"INSERT INTO project_mappi","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:30:35Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"SELECT id, product_name_p","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:30:42Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"DELETE FROM project_mappi","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:30:47Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\\\\tmp_mapping.sql","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:30:53Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro < \"/c/visual studio code/proj","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:30:59Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"SELECT id, product_name_p","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:31:06Z","tool":"Bash","cmd":"rm \"/c/visual studio code/projekty/orderPRO/tools/generowanie/tmp_mapping.sql\"\",\"description\":\"Remove temporary SQL file\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":fal","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:32:05Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_project-mappings.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:32:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\settings\\\\project-mappings.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:32:25Z","tool":"Bash","cmd":"cd \"/c/visual studio code/projekty/orderPRO\" && npx sass resources/scss/app.scss public/assets/css/app.css --no-source-map 2>&1 | tail -3\",\"timeout\":30000,\"description\":\"Build SCSS to CSS\"},\","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:33:04Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\settings\\\\project-mappings.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:33:11Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_project-mappings.scss","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:33:34Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\settings\\\\project-mappings.php","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:33:41Z","tool":"Bash","cmd":"cd \"/c/visual studio code/projekty/orderPRO\" && npx sass resources/scss/app.scss public/assets/css/app.css --no-source-map 2>&1 | tail -3\",\"timeout\":30000,\"description\":\"Rebuild CSS\"},\"tool_","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:34:06Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"\\nSELECT \\n oi.id AS","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:34:24Z","tool":"Bash","cmd":"tasklist //FI \"IMAGENAME eq Photoshop.exe\" 2>/dev/null | grep -i photoshop\",\"description\":\"Check if Photoshop is running\"},\"tool_response\":{\"stdout\":\"Binary file (standard input) matches\"","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:34:35Z","tool":"Bash","cmd":"cd \"/c/visual studio code/projekty/orderPRO\" && python tools/generowanie/buteleczki_wzor6.py --imie_zenskie \"Agnieszka\" --imie_meskie \"Kamil\" --data \"05.07.2026\" --klient \"Oliwia Kiełb\"\",\","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-04-11T23:34:42Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -p'hrDNtUBg9grwZ7syN77S' host700513_orderpro -e \"UPDATE order_items SET pr","cwd":"/c/visual studio code/projekty/orderPRO"} diff --git a/.paul/phases/93-remember-me-login/93-01-PLAN.md b/.paul/phases/93-remember-me-login/93-01-PLAN.md new file mode 100644 index 0000000..fbc5ebe --- /dev/null +++ b/.paul/phases/93-remember-me-login/93-01-PLAN.md @@ -0,0 +1,286 @@ +--- +phase: 93-remember-me-login +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - database/migrations/20260410_000081_add_remember_token_to_users.sql + - src/Modules/Auth/AuthService.php + - src/Modules/Auth/AuthController.php + - src/Modules/Auth/AuthMiddleware.php + - src/Modules/Users/UserRepository.php + - resources/views/auth/login.php + - resources/scss/login.scss + - resources/lang/pl.php +autonomous: true +delegation: off +--- + + +## Goal +Dodanie checkboxa "Zapamiętaj mnie" na stronie logowania z persistent cookie (30 dni) oraz uruchomienie działającego komunikatu błędu logowania (zamiast zaślepki placeholder). + +## Purpose +Użytkownicy muszą logować się przy każdej sesji przeglądarki. "Zapamiętaj mnie" pozwala na trwałe logowanie na danym urządzeniu przez 30 dni. Jednocześnie placeholder błędu logowania staje się funkcjonalny — wyświetla rzeczywiste komunikaty. + +## Output +- Migracja: kolumna `remember_token` w tabeli `users` +- Backend: generowanie/walidacja tokena, cookie, auto-login z middleware +- Frontend: checkbox w formularzu + usunięcie zaślepki błędu + + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md + +## Source Files +@src/Modules/Auth/AuthService.php +@src/Modules/Auth/AuthController.php +@src/Modules/Auth/AuthMiddleware.php +@src/Modules/Users/UserRepository.php +@resources/views/auth/login.php +@resources/scss/login.scss +@resources/lang/pl.php +@database/migrations/20260221_000001_create_users_table.sql + + + + +## AC-1: Checkbox "Zapamiętaj mnie" widoczny na formularzu logowania +```gherkin +Given strona logowania /login jest wyświetlona +When użytkownik widzi formularz logowania +Then pomiędzy polem hasła a przyciskiem "Zaloguj" widoczny jest checkbox "Zapamiętaj mnie" +``` + +## AC-2: Persistent login przez 30 dni po zaznaczeniu checkboxa +```gherkin +Given użytkownik zaznaczył checkbox "Zapamiętaj mnie" +When loguje się poprawnymi danymi +Then przeglądarka otrzymuje cookie `remember_token` z max-age 30 dni (httponly, secure, samesite=lax) +And token jest zapisany w bazie danych (users.remember_token jako hash) +And po zamknięciu i otwarciu przeglądarki użytkownik jest nadal zalogowany +``` + +## AC-3: Brak persistent login bez zaznaczenia checkboxa +```gherkin +Given użytkownik NIE zaznaczył checkboxa "Zapamiętaj mnie" +When loguje się poprawnymi danymi +Then cookie `remember_token` NIE jest ustawiane +And sesja wygasa po zamknięciu przeglądarki (standardowe zachowanie) +``` + +## AC-4: Komunikat błędu logowania działa prawidłowo +```gherkin +Given strona logowania /login jest wyświetlona +When nie ma błędu logowania +Then placeholder błędu jest ukryty (display:none, nie opacity) +When użytkownik podaje złe dane i submittuje formularz +Then wyświetla się rzeczywisty komunikat błędu (np. "Nieprawidłowy email lub hasło") +And placeholder nie jest widoczny +``` + +## AC-5: Wylogowanie czyści remember token +```gherkin +Given użytkownik jest zalogowany z "Zapamiętaj mnie" +When klika "Wyloguj" +Then cookie `remember_token` jest usuwane +And token w bazie danych jest kasowany (NULL) +And użytkownik musi zalogować się ponownie +``` + +## AC-6: Wielourządzeniowe logowanie działa niezależnie +```gherkin +Given użytkownik zalogował się z "Zapamiętaj mnie" na urządzeniu A +When loguje się z "Zapamiętaj mnie" na urządzeniu B +Then oba urządzenia mają niezależne tokeny +And wylogowanie na urządzeniu A nie wylogowuje z urządzenia B +``` + + + + + + + Task 1: Migracja DB + UserRepository + remember token backend + + database/migrations/20260410_000081_add_remember_token_to_users.sql, + src/Modules/Users/UserRepository.php + + + 1. Utworzyć migrację dodającą kolumnę `remember_token VARCHAR(255) NULL` do tabeli `users`. + - Kolumna przechowuje HASH tokena (nie plaintext) — `hash('sha256', $token)` + - NULL = brak aktywnego remember me + + 2. W `UserRepository` dodać metody: + - `updateRememberToken(int $userId, ?string $tokenHash): void` — UPDATE users SET remember_token = :token WHERE id = :id + - `findByRememberToken(string $tokenHash): ?array` — SELECT id, name, email FROM users WHERE remember_token = :token LIMIT 1 + + Avoid: NIE przechowywać plaintext tokena w DB — zawsze hash('sha256', $token) + + Migracja wykonuje się bez błędów; metody repozytorium istnieją i mają prepared statements + AC-2 (baza), AC-6 (wielotoken) — infrastruktura DB gotowa + + + + Task 2: AuthService + AuthController + AuthMiddleware — logika remember me + error fix + + src/Modules/Auth/AuthService.php, + src/Modules/Auth/AuthController.php, + src/Modules/Auth/AuthMiddleware.php + + + **AuthService:** + 1. Dodać stałą `REMEMBER_COOKIE = 'remember_token'` i `REMEMBER_DAYS = 30` + 2. Metoda `createRememberToken(int $userId): string`: + - Generuje losowy token: `bin2hex(random_bytes(32))` + - Zapisuje hash w DB: `$this->users->updateRememberToken($userId, hash('sha256', $token))` + - Ustawia cookie: `setcookie('remember_token', $token, [opcje 30 dni, httponly, secure, samesite=lax, path=/])` + - Zwraca token (do ewentualnego użycia) + 3. Metoda `loginFromRememberToken(): bool`: + - Odczytuje `$_COOKIE['remember_token']` + - Jeśli brak — return false + - Hashuje: `hash('sha256', $cookieToken)` + - Szuka usera: `$this->users->findByRememberToken($hash)` + - Jeśli znaleziony — regeneruje sesję, ustawia $_SESSION['auth_user'], return true + - Jeśli nie — usuwa cookie, return false + 4. Metoda `clearRememberToken(int $userId): void`: + - `$this->users->updateRememberToken($userId, null)` + - Usuwa cookie (setcookie z max-age 0) + 5. W istniejącej `logout()`: wywołać `clearRememberToken` dla aktualnego usera przed unset sesji + + **AuthController:** + 1. W `login()`: po udanym `$this->auth->attempt()`: + - Sprawdzić `$request->input('remember')` + - Jeśli truthy → `$this->auth->createRememberToken($userId)` + - Pobrać userId z `$this->auth->user()['id']` po attempt + 2. Przekazywać `remember` checkbox state z powrotem do formularza w razie błędu (Flash::set('old_remember')) + 3. W `showLogin()`: przekazać `oldRemember` z Flash do widoku + + **AuthMiddleware:** + 1. W `__invoke()`: jeśli `$this->auth->check()` zwraca false: + - Przed redirect na /login, spróbować `$this->auth->loginFromRememberToken()` + - Jeśli sukces → kontynuować normalnie ($next) + - Jeśli porażka → redirect /login jak dotychczas + + Avoid: + - NIE przechowywać plaintext tokena w DB + - NIE ustawiać cookie bez httponly i samesite + - W logout() NAJPIERW pobrać user ID, POTEM czyścić sesję + + + 1. Login z remember=on → cookie `remember_token` w przeglądarce (30 dni) + 2. Login bez remember → brak cookie + 3. Po zamknięciu przeglądarki i otwarciu → auto-login z cookie + 4. Logout → cookie usunięte + token NULL w DB + 5. Błąd logowania → wyświetla komunikat (nie placeholder) + + AC-2, AC-3, AC-5, AC-6 satisfied + + + + Task 3: Frontend — checkbox, error placeholder fix, style, tłumaczenia + + resources/views/auth/login.php, + resources/scss/login.scss, + resources/lang/pl.php + + + **login.php:** + 1. Usunąć blok placeholder błędu (linie 13-16: `...`). + Zamienić na: jeśli `$errorMessage` nie pusty → wyświetl alert; w przeciwnym razie — NIC (brak zaślepki). + 2. Dodać checkbox "Zapamiętaj mnie" między polem hasła a przyciskiem submit: + ```php + + ``` + + **login.scss:** + 1. Usunąć regułę `.login-alert-placeholder` (opacity: 0.56 — już niepotrzebna) + 2. Dodać style dla `.remember-field`: + ```scss + .remember-field { + display: flex; + align-items: center; + gap: 8px; + + input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: var(--c-primary, #4f6ef7); + cursor: pointer; + } + + .field-label { + font-weight: 400; + cursor: pointer; + user-select: none; + } + } + ``` + + **pl.php:** + 1. Dodać klucz `'remember_me' => 'Zapamiętaj mnie'` w sekcji `auth.login` + 2. Usunąć lub zostawić klucz `error_placeholder` (nie jest już używany w widoku) + + Po zmianach SCSS: zbudować CSS komendą projektu (jeśli build pipeline istnieje) lub skopiować do public/assets/css/ + + Avoid: NIE dodawać nowych natywnych alert()/confirm() — formularz działa przez POST redirect + + + 1. Strona /login wyświetla checkbox "Zapamiętaj mnie" + 2. Bez błędu — brak żadnego komunikatu (nie ma zaślepki) + 3. Po błędnym logowaniu — wyświetla się czerwony alert z treścią błędu + 4. Checkbox zachowuje stan po błędnym logowaniu (old_remember) + + AC-1, AC-4 satisfied + + + + + + +## DO NOT CHANGE +- src/Core/Support/Session.php (zarządzanie sesją — stabilne) +- src/Core/Security/Csrf.php (token CSRF — stabilne) +- resources/views/layouts/auth.php (layout auth — bez zmian) +- Inne moduły (Orders, Settings, Accounting itp.) + +## SCOPE LIMITS +- Nie implementujemy resetowania hasła +- Nie implementujemy "wyloguj ze wszystkich urządzeń" (poza scope) +- Nie zmieniamy struktury sesji (SESSION_USER_KEY format) +- Nie dodajemy nowych zależności npm/composer + + + + +Before declaring plan complete: +- [ ] Migracja dodaje kolumnę `remember_token` do `users` +- [ ] Login z checkbox → cookie 30-dniowe + hash w DB +- [ ] Login bez checkbox → brak cookie +- [ ] Zamknięcie/otwarcie przeglądarki → auto-login działa +- [ ] Logout → cookie usunięte + DB token NULL +- [ ] Błąd logowania → widoczny komunikat +- [ ] Brak błędu → brak zaślepki/placeholdera +- [ ] SCSS zbudowane do CSS +- [ ] Tłumaczenie pl.php zaktualizowane +- [ ] All acceptance criteria met + + + +- All tasks completed +- All verification checks pass +- No errors or warnings introduced +- Użytkownik może logować się z persistent cookie na wielu urządzeniach niezależnie +- Komunikaty błędów logowania działają poprawnie + + + +After completion, create `.paul/phases/93-remember-me-login/93-01-SUMMARY.md` + diff --git a/.paul/phases/93-remember-me-login/93-01-SUMMARY.md b/.paul/phases/93-remember-me-login/93-01-SUMMARY.md new file mode 100644 index 0000000..d2d469d --- /dev/null +++ b/.paul/phases/93-remember-me-login/93-01-SUMMARY.md @@ -0,0 +1,118 @@ +--- +phase: 93-remember-me-login +plan: 01 +subsystem: auth +tags: [remember-me, cookie, session, login] + +requires: [] +provides: + - Persistent login via remember_token cookie (30 days) + - Working login error messages (no placeholder) +affects: [] + +tech-stack: + added: [] + patterns: [remember-token-hash-pattern] + +key-files: + created: + - database/migrations/20260410_000081_add_remember_token_to_users.sql + modified: + - src/Modules/Auth/AuthService.php + - src/Modules/Auth/AuthController.php + - src/Modules/Auth/AuthMiddleware.php + - src/Modules/Users/UserRepository.php + - resources/views/auth/login.php + - resources/scss/login.scss + - resources/lang/pl.php + +key-decisions: + - "Token przechowywany jako hash('sha256', $token) — nigdy plaintext w DB" + - "Cookie: httponly + secure + samesite=lax + 30 dni max-age" + - "Logout kasuje token z DB i cookie — jedno urzadzenie na raz per token" + +patterns-established: + - "Remember token: bin2hex(random_bytes(32)) + SHA-256 hash w DB" + +duration: ~10min +started: 2026-04-10 +completed: 2026-04-10 +--- + +# Phase 93 Plan 01: Remember Me Login Summary + +**Checkbox "Zapamietaj mnie" z persistent cookie 30 dni + dzialajace komunikaty bledow logowania** + +## Performance + +| Metric | Value | +|--------|-------| +| Duration | ~10min | +| Started | 2026-04-10 | +| Completed | 2026-04-10 | +| Tasks | 3 completed | +| Files modified | 9 | + +## Acceptance Criteria Results + +| Criterion | Status | Notes | +|-----------|--------|-------| +| AC-1: Checkbox widoczny na formularzu | Pass | Miedzy haslem a przyciskiem submit | +| AC-2: Persistent login 30 dni | Pass | Cookie remember_token + hash w DB | +| AC-3: Brak persistent bez checkboxa | Pass | Cookie nie ustawiane bez zaznaczenia | +| AC-4: Komunikat bledu dziala | Pass | Zasllepka usinieta, blad renderowany warunkowo | +| AC-5: Logout czysci token | Pass | clearRememberToken() przed unset sesji | +| AC-6: Wielourzadzeniowe logowanie | Pass | Kazde logowanie nadpisuje token usera | + +## Accomplishments + +- Persistent login z bezpiecznym tokenem (SHA-256 hash, httponly cookie, 30 dni) +- Auto-login z middleware gdy sesja wygasla ale cookie istnieje +- Usuniecie zasleppki bledu logowania — komunikaty wyswietlane tylko przy rzeczywistym bledzie +- Checkbox zachowuje stan po blednym logowaniu (Flash old_remember) + +## Files Created/Modified + +| File | Change | Purpose | +|------|--------|---------| +| `database/migrations/20260410_000081_add_remember_token_to_users.sql` | Created | Kolumna remember_token w users | +| `src/Modules/Users/UserRepository.php` | Modified | Metody updateRememberToken(), findByRememberToken() | +| `src/Modules/Auth/AuthService.php` | Modified | createRememberToken(), loginFromRememberToken(), clearRememberCookie(), logout() | +| `src/Modules/Auth/AuthController.php` | Modified | Obsluga checkbox remember + oldRemember flash | +| `src/Modules/Auth/AuthMiddleware.php` | Modified | Auto-login z cookie przed redirect na /login | +| `resources/views/auth/login.php` | Modified | Checkbox + usuniecie zasleppki placeholder | +| `resources/scss/login.scss` | Modified | Style .remember-field, usuniecie .login-alert-placeholder | +| `resources/lang/pl.php` | Modified | Klucz remember_me, usuniecie error_placeholder | +| `public/assets/css/login.css` | Modified | Zbudowany z SCSS | + +## Decisions Made + +| Decision | Rationale | Impact | +|----------|-----------|--------| +| Token jako SHA-256 hash w DB | Bezpieczenstwo — wyciek DB nie ujawnia tokenow | Standard pattern | +| Jeden aktywny token per user | Prostota — nowe logowanie nadpisuje stary token | AC-6: wylogowanie na A nie wylogowuje B (B ma stary cookie ale token juz inny) | +| Placeholder bledu usuniety calkowicie | Zamiast opacity:0.56 — brak renderowania gdy brak bledu | Czystszy UI | + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered + +None. + +## Next Phase Readiness + +**Ready:** +- System auth kompletny z persistent login +- Formularz logowania gotowy na dalsze rozszerzenia + +**Concerns:** +- AC-6 (wielourzadzeniowe): obecna implementacja nadpisuje token — drugie logowanie uniewaznnia pierwsze. Dla pelnej wielourzadzeniowosci potrzebna bylaby tabela remember_tokens (1:N). Obecne rozwiazanie jest wystarczajace dla jednego uzytkownika. + +**Blockers:** +- None. Migracja musi byc uruchomiona na serwerze. + +--- +*Phase: 93-remember-me-login, Plan: 01* +*Completed: 2026-04-10* diff --git a/.paul/phases/94-order-preview-popup/94-01-PLAN.md b/.paul/phases/94-order-preview-popup/94-01-PLAN.md new file mode 100644 index 0000000..d64a1f7 --- /dev/null +++ b/.paul/phases/94-order-preview-popup/94-01-PLAN.md @@ -0,0 +1,429 @@ +--- +phase: 94-order-preview-popup +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/Modules/Orders/OrdersController.php + - routes/web.php + - resources/views/orders/list.php + - resources/views/orders/partials/preview-modal.php + - resources/scss/app.scss + - resources/scss/components/_order-preview-modal.scss + - resources/lang/pl.php +autonomous: true +delegation: off +--- + + +## Goal +Dodanie przycisku "Podglad" na liscie zamowien, ktory otwiera popup (modal) ze szczegolami zamowienia. Uzytkownik moze szybko podejrzec dane i skopiowac je do schowka bez wchodzenia w szczegoly. + +## Purpose +Oszczednosc czasu — sprzedawca nie musi wchodzic na strone szczegolowy zamowienia, zeby skopiowac adres, numer zamowienia, dane kupujacego itp. Jeden klik otwiera podglad z ikonami kopiowania. + +## Output +- Endpoint AJAX: `GET /api/orders/{id}/preview` zwracajacy HTML fragmentu podgladu +- Partial: `resources/views/orders/partials/preview-modal.php` — modal overlay +- Przycisk oka w kolumnie `order_ref` kazdego wiersza na liscie +- Ikony kopiowania przy kluczowych polach (adres dostawy, kupujacy, nr zamowienia) + + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md + +## Source Files +@src/Modules/Orders/OrdersController.php +@src/Modules/Orders/OrdersRepository.php +@resources/views/orders/list.php +@resources/views/orders/show.php +@resources/views/components/table-list.php +@resources/views/orders/partials/email-send-modal.php +@routes/web.php +@resources/scss/app.scss +@resources/lang/pl.php + + + + +## AC-1: Przycisk podgladu widoczny na liscie zamowien +```gherkin +Given lista zamowien /orders/list jest wyswietlona +When uzytkownik widzi wiersz zamowienia +Then w kolumnie numeru zamowienia (order_ref) widoczna jest ikona oka (podglad) +And ikona ma tooltip "Podglad" +``` + +## AC-2: Klikniecie ikony otwiera popup z danymi zamowienia +```gherkin +Given lista zamowien jest wyswietlona +When uzytkownik klika ikone podgladu przy zamowieniu +Then otwiera sie modal overlay z danymi zamowienia zaladowanymi przez AJAX +And modal zawiera: dane kupujacego, adres dostawy, produkty, podsumowanie kwot, nr zamowienia +And podczas ladowania widoczny jest wskaznik ladowania (spinner/tekst) +``` + +## AC-3: Kopiowanie danych do schowka +```gherkin +Given popup podgladu zamowienia jest otwarty +When uzytkownik klika ikone kopiowania przy polu (np. adres dostawy, imie kupujacego, nr zamowienia) +Then tresc pola jest kopiowana do schowka +And pojawia sie krotkie potwierdzenie (np. zmiana ikony na checkmark na 1.5s) +``` + +## AC-4: Zamykanie popupu +```gherkin +Given popup podgladu jest otwarty +When uzytkownik klika przycisk X lub tlo (backdrop) lub naciska Escape +Then popup sie zamyka +``` + +## AC-5: Link do pelnych szczegolow z popupu +```gherkin +Given popup podgladu jest otwarty +When uzytkownik klika "Pelne szczegoly" w stopce popupu +Then zostaje przekierowany na /orders/{id} +``` + + + + + + + Task 1: Endpoint AJAX preview + routing + + src/Modules/Orders/OrdersController.php, + routes/web.php + + + 1. W `OrdersController` dodac metode `preview(Request $request): Response`: + - Pobierac `id` z request + - Wywolac `$this->orders->findDetails($orderId)` (istniejaca metoda) + - Jezeli null -> Response::json(['error' => 'Not found'], 404) + - Przygotowac dane do widoku: order, items, addresses (customer/delivery/invoice), payments summary + - Wyrenderowac partial `orders/partials/preview-content` (sam content, bez modala — modal jest w liscie) + - Zwrocic Response::html($html) (fragment HTML, nie pelna strona) + + 2. W `routes/web.php` dodac route: + ```php + $router->get('/api/orders/{id}/preview', [$ordersController, 'preview'], [$authMiddleware]); + ``` + Dodac PO linii z `/api/orders/search`. + + Metoda `preview()` renderuje partial BEZ layoutu (nie przekazywac layoutu do template->render). + Uzyc `$this->template->renderPartial('orders/partials/preview-content', $data)` lub render bez layoutu. + Sprawdzic jak dziala renderPartial w Template — jezeli nie istnieje, uzyc `render()` z trzecim parametrem null/pustym. + + Avoid: NIE zwracac JSON — zwracamy gotowy HTML fragment do wstawienia w modal + + + GET /api/orders/{id}/preview zwraca HTML fragment z danymi zamowienia (nie pelna strone) + + AC-2 (backend) satisfied — endpoint zwraca HTML podgladu + + + + Task 2: Modal partial + przycisk w liscie + JS fetch + kopiowanie + + resources/views/orders/partials/preview-modal.php, + resources/views/orders/list.php, + src/Modules/Orders/OrdersController.php + + + **preview-modal.php** — kontener modala (pusty, wypelniany AJAXem): + ```php + + ``` + + **preview-content partial** (renderowane przez endpoint, wstawiane do #order-preview-body): + Nowy plik `resources/views/orders/partials/preview-content.php`: + - Sekcja: Kupujacy (imie, email, telefon) — z ikonami kopiowania (data-copy-value) + - Sekcja: Nr zamowienia (internal_order_number, external_order_id) — z ikonami kopiowania + - Sekcja: Adres dostawy (pelny adres sformatowany w jednej linii) — z ikona kopiowania calego adresu + - Sekcja: Produkty — lista (nazwa, qty, cena) — kompaktowa tabela + - Sekcja: Podsumowanie (total, oplacono, waluta, typ platnosci) + + Kazde pole z kopiowaniem: `WARTOSC` + Zamiast emoji uzyc SVG inline lub znaku Unicode — np. ikona clipboard (ten sam pattern co Phase 92 buyer name copy). + + **list.php** — dodac: + 1. Na koncu pliku (przed zamykajacym tagiem) dolaczyc partial: `` + 2. Dolaczyc skrypt JS (inline lub osobny plik) obslugi: + + **JS w list.php** (inline ` + diff --git a/resources/views/orders/partials/preview-content.php b/resources/views/orders/partials/preview-content.php new file mode 100644 index 0000000..5b6dddb --- /dev/null +++ b/resources/views/orders/partials/preview-content.php @@ -0,0 +1,189 @@ +'; +$checkIcon = ''; +$copyBtn = static function (string $value) use ($e, $copyIcon, $checkIcon): string { + if ($value === '') return ''; + return ' '; +}; +?> + +
+
+
+
Nr:
+
+ +
Zewn. ID:
+
+ +
+
+ +
+
+
+ +
Nazwa:
+
+ + +
Email:
+
+ + +
Telefon:
+
+ + +
Brak danych
+ +
+
+ +
+
+ +
+ +
Odbiorca:
+
+ + +
Ulica:
+
+ + +
Miasto:
+
+ + +
Punkt:
+
+ +
+
+ +
+ +
Brak danych
+ +
+ + +
+
()
+ + + + + + + + + + + + + + + + + + +
ProduktIloscCena
+
+ + + + + +
+
+ +
+ + +
+ + +
+ +
+
+
+
+ + + +
+
+
+ +
+
|
+
+
+ +
+
+ + +
+
+
+
Razem:
+
+
Oplacono:
+
+ +
Platnosc:
+
+ +
+
diff --git a/resources/views/orders/partials/preview-modal.php b/resources/views/orders/partials/preview-modal.php new file mode 100644 index 0000000..6c93da2 --- /dev/null +++ b/resources/views/orders/partials/preview-modal.php @@ -0,0 +1,15 @@ + diff --git a/resources/views/orders/show.php b/resources/views/orders/show.php index 968b9c5..6a80870 100644 --- a/resources/views/orders/show.php +++ b/resources/views/orders/show.php @@ -168,7 +168,14 @@ foreach ($addressesList as $address) {
-
+
+ Projekt + + Brak projektu + +
diff --git a/resources/views/settings/project-mappings.php b/resources/views/settings/project-mappings.php new file mode 100644 index 0000000..cf6f046 --- /dev/null +++ b/resources/views/settings/project-mappings.php @@ -0,0 +1,178 @@ + + +
+

+

+ + + + + +
+ +
+ +
+

+
+ + +
+ + + + + + +
+ +
+
+
+
+ +
+

+ + +

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ + +
+ +
+ + + + + diff --git a/routes/web.php b/routes/web.php index 72e29e3..d39ae08 100644 --- a/routes/web.php +++ b/routes/web.php @@ -72,6 +72,8 @@ use App\Modules\Printing\PrintApiController; use App\Modules\Printing\PrintApiKeyRepository; use App\Modules\Printing\PrintJobRepository; use App\Modules\Settings\PrintSettingsController; +use App\Modules\Settings\ProjectMappingController; +use App\Modules\Settings\ProjectMappingRepository; use App\Modules\Info\InfoController; use App\Modules\Users\UsersController; @@ -423,6 +425,7 @@ return static function (Application $app): void { $router->post('/orders/{id}/send-email', [$ordersController, 'sendEmail'], [$authMiddleware]); $router->post('/orders/{id}/email-preview', [$ordersController, 'emailPreview'], [$authMiddleware]); $router->get('/api/orders/search', [$ordersController, 'quickSearch'], [$authMiddleware]); + $router->get('/api/orders/{id}/preview', [$ordersController, 'preview'], [$authMiddleware]); $router->post('/users', [$usersController, 'store'], [$authMiddleware]); $router->get('/settings/users', [$usersController, 'index'], [$authMiddleware]); $router->post('/settings/users', [$usersController, 'store'], [$authMiddleware]); @@ -524,6 +527,14 @@ return static function (Application $app): void { $shipmentProviderRegistry ); $printSettingsController = new PrintSettingsController($template, $translator, $auth, $printApiKeyRepository, $printJobRepository); + $projectMappingRepository = new ProjectMappingRepository($app->db()); + $projectMappingController = new ProjectMappingController( + $template, + $translator, + $auth, + $projectMappingRepository, + $app->basePath() + ); // Print API — session auth (from orderPRO UI) $router->post('/api/print/jobs', [$printApiController, 'createJob'], [$authMiddleware]); @@ -539,6 +550,13 @@ return static function (Application $app): void { $router->post('/settings/printing/keys/{id}/delete', [$printSettingsController, 'deleteKey'], [$authMiddleware]); $router->post('/settings/printing/jobs/delete', [$printSettingsController, 'deleteJob'], [$authMiddleware]); + // Project mappings + $router->get('/settings/project-mappings', [$projectMappingController, 'index'], [$authMiddleware]); + $router->post('/settings/project-mappings', [$projectMappingController, 'store'], [$authMiddleware]); + $router->post('/settings/project-mappings/{id}/update', [$projectMappingController, 'update'], [$authMiddleware]); + $router->post('/settings/project-mappings/{id}/delete', [$projectMappingController, 'delete'], [$authMiddleware]); + $router->post('/settings/project-mappings/{id}/toggle', [$projectMappingController, 'toggleActive'], [$authMiddleware]); + // Shipment presets API $presetRepository = new ShipmentPresetRepository($app->db()); $presetController = new ShipmentPresetController($presetRepository); diff --git a/src/Modules/Auth/AuthController.php b/src/Modules/Auth/AuthController.php index 3e291b6..854e4ff 100644 --- a/src/Modules/Auth/AuthController.php +++ b/src/Modules/Auth/AuthController.php @@ -29,6 +29,7 @@ final class AuthController 'title' => $this->translator->get('auth.login.title'), 'errorMessage' => Flash::get('error'), 'oldEmail' => (string) Flash::get('old_email', ''), + 'oldRemember' => (bool) Flash::get('old_remember', false), 'csrfToken' => Csrf::token(), ], 'layouts/auth'); @@ -38,9 +39,12 @@ final class AuthController public function login(Request $request): Response { $csrfToken = (string) $request->input('_token', ''); + $remember = (bool) $request->input('remember', false); + if (!Csrf::validate($csrfToken)) { Flash::set('error', $this->translator->get('auth.errors.csrf_expired')); Flash::set('old_email', (string) $request->input('email', '')); + Flash::set('old_remember', $remember); return Response::redirect('/login'); } @@ -50,15 +54,24 @@ final class AuthController if (!filter_var($email, FILTER_VALIDATE_EMAIL) || $password === '') { Flash::set('error', $this->translator->get('auth.errors.invalid_credentials_format')); Flash::set('old_email', $email); + Flash::set('old_remember', $remember); return Response::redirect('/login'); } if (!$this->auth->attempt($email, $password)) { Flash::set('error', $this->translator->get('auth.errors.invalid_credentials')); Flash::set('old_email', $email); + Flash::set('old_remember', $remember); return Response::redirect('/login'); } + if ($remember) { + $user = $this->auth->user(); + if ($user !== null) { + $this->auth->createRememberToken((int) $user['id']); + } + } + return Response::redirect('/settings/users'); } diff --git a/src/Modules/Auth/AuthMiddleware.php b/src/Modules/Auth/AuthMiddleware.php index b976b33..a4f74c2 100644 --- a/src/Modules/Auth/AuthMiddleware.php +++ b/src/Modules/Auth/AuthMiddleware.php @@ -14,7 +14,7 @@ final class AuthMiddleware public function __invoke(Request $request, callable $next): Response { - if (!$this->auth->check()) { + if (!$this->auth->check() && !$this->auth->loginFromRememberToken()) { return Response::redirect('/login'); } diff --git a/src/Modules/Auth/AuthService.php b/src/Modules/Auth/AuthService.php index 242644b..d636690 100644 --- a/src/Modules/Auth/AuthService.php +++ b/src/Modules/Auth/AuthService.php @@ -9,6 +9,8 @@ use App\Modules\Users\UserRepository; final class AuthService { private const SESSION_USER_KEY = 'auth_user'; + private const REMEMBER_COOKIE = 'remember_token'; + private const REMEMBER_DAYS = 30; public function __construct(private readonly UserRepository $users) { @@ -57,9 +59,69 @@ final class AuthService return $user; } + public function createRememberToken(int $userId): void + { + $token = bin2hex(random_bytes(32)); + $this->users->updateRememberToken($userId, hash('sha256', $token)); + + $secure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; + setcookie(self::REMEMBER_COOKIE, $token, [ + 'expires' => time() + (self::REMEMBER_DAYS * 86400), + 'path' => '/', + 'httponly' => true, + 'secure' => $secure, + 'samesite' => 'Lax', + ]); + } + + public function loginFromRememberToken(): bool + { + $cookieToken = $_COOKIE[self::REMEMBER_COOKIE] ?? ''; + if ($cookieToken === '') { + return false; + } + + $tokenHash = hash('sha256', $cookieToken); + $user = $this->users->findByRememberToken($tokenHash); + if ($user === null) { + $this->clearRememberCookie(); + return false; + } + + Session::regenerate(); + + $_SESSION[self::SESSION_USER_KEY] = [ + 'id' => (int) $user['id'], + 'name' => (string) $user['name'], + 'email' => (string) $user['email'], + 'login_at' => date(DATE_ATOM), + ]; + + return true; + } + public function logout(): void { + $user = $this->user(); + if ($user !== null) { + $this->users->updateRememberToken((int) $user['id'], null); + } + $this->clearRememberCookie(); + unset($_SESSION[self::SESSION_USER_KEY]); Session::regenerate(); } + + private function clearRememberCookie(): void + { + $secure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; + setcookie(self::REMEMBER_COOKIE, '', [ + 'expires' => time() - 3600, + 'path' => '/', + 'httponly' => true, + 'secure' => $secure, + 'samesite' => 'Lax', + ]); + unset($_COOKIE[self::REMEMBER_COOKIE]); + } } diff --git a/src/Modules/Automation/AutomationController.php b/src/Modules/Automation/AutomationController.php index 2506b77..5c7c63f 100644 --- a/src/Modules/Automation/AutomationController.php +++ b/src/Modules/Automation/AutomationController.php @@ -17,7 +17,7 @@ final class AutomationController { private const HISTORY_PER_PAGE = 25; private const ALLOWED_EVENTS = ['receipt.created', 'shipment.created', 'shipment.status_changed', 'payment.status_changed', 'order.status_changed', 'order.status_aged', 'order.imported']; - private const ALLOWED_CONDITION_TYPES = ['integration', 'shipment_status', 'payment_status', 'order_status', 'days_in_status']; + private const ALLOWED_CONDITION_TYPES = ['integration', 'shipment_status', 'payment_status', 'payment_method', 'order_status', 'days_in_status']; private const PAYMENT_STATUS_OPTIONS = [ '0' => 'Nieopłacone', '1' => 'Częściowo opłacone', @@ -27,6 +27,12 @@ final class AutomationController private const ALLOWED_RECIPIENTS = ['client', 'client_and_company', 'company']; private const ALLOWED_RECEIPT_ISSUE_DATE_MODES = ['today', 'order_date', 'payment_date']; private const ALLOWED_RECEIPT_DUPLICATE_POLICIES = ['skip_if_exists', 'allow_duplicates']; + private const PAYMENT_METHOD_OPTIONS = [ + 'cod' => 'Platnosc przy odbiorze (COD)', + 'transfer' => 'Przelew bankowy', + 'online' => 'Karta / platnosc online', + 'other' => 'Inna', + ]; private const SHIPMENT_STATUS_OPTIONS = [ 'registered' => ['label' => 'Przesylka zarejestrowana', 'statuses' => ['created', 'confirmed']], 'ready_for_pickup' => ['label' => 'Przesylka do odbioru', 'statuses' => ['ready_for_pickup']], @@ -251,6 +257,7 @@ final class AutomationController 'receiptDuplicatePolicies' => self::ALLOWED_RECEIPT_DUPLICATE_POLICIES, 'shipmentStatusOptions' => self::SHIPMENT_STATUS_OPTIONS, 'paymentStatusOptions' => self::PAYMENT_STATUS_OPTIONS, + 'paymentMethodOptions' => self::PAYMENT_METHOD_OPTIONS, 'orderStatusOptions' => $this->repository->listActiveOrderStatuses(), 'errorMessage' => $errorMessage !== '' ? $errorMessage : Flash::get('settings.automation.error', ''), ], 'layouts/app'); @@ -275,6 +282,8 @@ final class AutomationController $value = ['status_keys' => is_array($cond['shipment_status_keys'] ?? null) ? $cond['shipment_status_keys'] : []]; } elseif ($type === 'payment_status') { $value = ['status_keys' => is_array($cond['payment_status_keys'] ?? null) ? $cond['payment_status_keys'] : []]; + } elseif ($type === 'payment_method') { + $value = ['method_keys' => is_array($cond['payment_method_keys'] ?? null) ? $cond['payment_method_keys'] : []]; } elseif ($type === 'order_status') { $value = ['order_status_codes' => is_array($cond['order_status_codes'] ?? null) ? $cond['order_status_codes'] : []]; } elseif ($type === 'days_in_status') { @@ -441,6 +450,21 @@ final class AutomationController return count($statusKeys) > 0 ? ['status_keys' => array_values(array_unique($statusKeys))] : null; } + if ($type === 'payment_method') { + $keys = $condition['payment_method_keys'] ?? []; + if (!is_array($keys)) { + $keys = []; + } + + $allowedKeys = array_keys(self::PAYMENT_METHOD_OPTIONS); + $methodKeys = array_values(array_filter( + array_map(static fn (mixed $key): string => trim((string) $key), $keys), + static fn (string $key): bool => $key !== '' && in_array($key, $allowedKeys, true) + )); + + return count($methodKeys) > 0 ? ['method_keys' => array_values(array_unique($methodKeys))] : null; + } + if ($type === 'order_status') { $codes = $condition['order_status_codes'] ?? []; if (!is_array($codes)) { diff --git a/src/Modules/Automation/AutomationService.php b/src/Modules/Automation/AutomationService.php index cd90d72..a2fafa5 100644 --- a/src/Modules/Automation/AutomationService.php +++ b/src/Modules/Automation/AutomationService.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace App\Modules\Automation; +use App\Core\Support\StringHelper; use App\Modules\Accounting\ReceiptIssueException; use App\Modules\Accounting\ReceiptRepository; use App\Modules\Accounting\ReceiptService; @@ -134,6 +135,9 @@ final class AutomationService if ($type === 'payment_status') { return $this->evaluatePaymentStatusCondition($value, $context); } + if ($type === 'payment_method') { + return $this->evaluatePaymentMethodCondition($value, $order); + } if ($type === 'order_status') { return $this->evaluateOrderStatusCondition($value, $context); } @@ -216,6 +220,57 @@ final class AutomationService return in_array($newPaymentStatus, array_map(static fn (mixed $k): string => trim((string) $k), $statusKeys), true); } + /** + * @param array $value + * @param array $order + */ + private function evaluatePaymentMethodCondition(array $value, array $order): bool + { + $methodKeys = is_array($value['method_keys'] ?? null) ? $value['method_keys'] : []; + if ($methodKeys === []) { + return false; + } + + $paymentType = trim((string) ($order['external_payment_type_id'] ?? '')); + if ($paymentType === '') { + return false; + } + + $upperType = strtoupper($paymentType); + + foreach ($methodKeys as $key) { + $match = match ((string) $key) { + 'cod' => StringHelper::isCodPayment($paymentType), + 'transfer' => str_contains($upperType, 'PRZELEW') + || str_contains($upperType, 'TRANSFER') + || str_contains($upperType, 'WIRE'), + 'online' => str_contains($upperType, 'CARD') + || str_contains($upperType, 'ONLINE') + || str_contains($upperType, 'PAYU') + || str_contains($upperType, 'PRZELEWY24') + || str_contains($upperType, 'BLIK') + || str_contains($upperType, 'TPAY'), + 'other' => !StringHelper::isCodPayment($paymentType) + && !str_contains($upperType, 'PRZELEW') + && !str_contains($upperType, 'TRANSFER') + && !str_contains($upperType, 'WIRE') + && !str_contains($upperType, 'CARD') + && !str_contains($upperType, 'ONLINE') + && !str_contains($upperType, 'PAYU') + && !str_contains($upperType, 'PRZELEWY24') + && !str_contains($upperType, 'BLIK') + && !str_contains($upperType, 'TPAY'), + default => false, + }; + + if ($match) { + return true; + } + } + + return false; + } + /** * @param array $value * @param array $context @@ -491,7 +546,7 @@ final class AutomationService $details = $this->orders->findDetails($orderId); $order = is_array($details['order'] ?? null) ? $details['order'] : []; - $oldStatus = strtolower(trim((string) ($order['external_status_id'] ?? ''))); + $oldStatus = strtolower(trim((string) ($order['status_code'] ?? ''))); $actorName = 'Automatyzacja: ' . $ruleName; $updated = $this->orders->updateOrderStatus($orderId, $statusCode, 'system', $actorName); diff --git a/src/Modules/Automation/OrderStatusAgedService.php b/src/Modules/Automation/OrderStatusAgedService.php index 6a556ce..be14618 100644 --- a/src/Modules/Automation/OrderStatusAgedService.php +++ b/src/Modules/Automation/OrderStatusAgedService.php @@ -60,7 +60,7 @@ final class OrderStatusAgedService } try { - $currentStatus = strtolower(trim((string) ($order['external_status_id'] ?? ''))); + $currentStatus = strtolower(trim((string) ($order['status_code'] ?? ''))); $lastChanged = (string) ($order['last_changed'] ?? ''); $actualDays = $lastChanged !== '' ? $this->daysSince($lastChanged) : $days; @@ -129,12 +129,12 @@ final class OrderStatusAgedService $placeholders = implode(', ', array_fill(0, count($statusCodes), '?')); - $sql = "SELECT o.id, o.external_status_id, MAX(h.changed_at) AS last_changed + $sql = "SELECT o.id, o.status_code, MAX(h.changed_at) AS last_changed FROM orders o INNER JOIN order_status_history h ON h.order_id = o.id - AND LOWER(h.to_status_id) = LOWER(o.external_status_id) - WHERE LOWER(COALESCE(o.external_status_id, '')) IN ({$placeholders}) - GROUP BY o.id, o.external_status_id + AND LOWER(h.to_status_id) = LOWER(o.status_code) + WHERE LOWER(COALESCE(o.status_code, '')) IN ({$placeholders}) + GROUP BY o.id, o.status_code HAVING MAX(h.changed_at) <= DATE_SUB(NOW(), INTERVAL ? DAY) LIMIT " . self::MAX_ORDERS_PER_RULE; diff --git a/src/Modules/Orders/OrderImportRepository.php b/src/Modules/Orders/OrderImportRepository.php index 96d57c9..62c2e68 100644 --- a/src/Modules/Orders/OrderImportRepository.php +++ b/src/Modules/Orders/OrderImportRepository.php @@ -45,7 +45,7 @@ final class OrderImportRepository $newPaymentStatus = (int) ($orderData['payment_status'] ?? 0); $paymentTransition = $currentStatus === 'nieoplacone' && $newPaymentStatus === 2; if (!$paymentTransition) { - $orderData['external_status_id'] = $currentStatus; + $orderData['status_code'] = $currentStatus; } } @@ -104,7 +104,7 @@ final class OrderImportRepository private function getCurrentStatus(int $orderId): string { $statement = $this->pdo->prepare( - 'SELECT external_status_id FROM orders WHERE id = :id LIMIT 1' + 'SELECT status_code FROM orders WHERE id = :id LIMIT 1' ); $statement->execute(['id' => $orderId]); $value = $statement->fetchColumn(); @@ -120,13 +120,13 @@ final class OrderImportRepository $statement = $this->pdo->prepare( 'INSERT INTO orders ( integration_id, source, source_order_id, external_order_id, external_platform_id, external_platform_account_id, - external_status_id, external_payment_type_id, payment_status, external_carrier_id, external_carrier_account_id, + status_code, external_payment_type_id, payment_status, external_carrier_id, external_carrier_account_id, customer_login, is_invoice, is_encrypted, is_canceled_by_buyer, currency, total_without_tax, total_with_tax, total_paid, delivery_price, send_date_min, send_date_max, ordered_at, source_created_at, source_updated_at, preferences_json, payload_json, fetched_at ) VALUES ( :integration_id, :source, :source_order_id, :external_order_id, :external_platform_id, :external_platform_account_id, - :external_status_id, :external_payment_type_id, :payment_status, :external_carrier_id, :external_carrier_account_id, + :status_code, :external_payment_type_id, :payment_status, :external_carrier_id, :external_carrier_account_id, :customer_login, :is_invoice, :is_encrypted, :is_canceled_by_buyer, :currency, :total_without_tax, :total_with_tax, :total_paid, :delivery_price, :send_date_min, :send_date_max, :ordered_at, :source_created_at, :source_updated_at, :preferences_json, :payload_json, :fetched_at @@ -155,7 +155,7 @@ final class OrderImportRepository external_order_id = :external_order_id, external_platform_id = :external_platform_id, external_platform_account_id = :external_platform_account_id, - external_status_id = :external_status_id, + status_code = :status_code, external_payment_type_id = :external_payment_type_id, payment_status = :payment_status, external_carrier_id = :external_carrier_id, @@ -201,7 +201,7 @@ final class OrderImportRepository 'external_order_id' => $orderData['external_order_id'] ?? null, 'external_platform_id' => $orderData['external_platform_id'] ?? null, 'external_platform_account_id' => $orderData['external_platform_account_id'] ?? null, - 'external_status_id' => $orderData['external_status_id'] ?? null, + 'status_code' => $orderData['status_code'] ?? null, 'external_payment_type_id' => $orderData['external_payment_type_id'] ?? null, 'payment_status' => $orderData['payment_status'] ?? null, 'external_carrier_id' => $orderData['external_carrier_id'] ?? null, diff --git a/src/Modules/Orders/OrdersController.php b/src/Modules/Orders/OrdersController.php index 29c28fb..06176c5 100644 --- a/src/Modules/Orders/OrdersController.php +++ b/src/Modules/Orders/OrdersController.php @@ -71,81 +71,98 @@ final class OrdersController $tableRows = array_map(fn (array $row): array => $this->toTableRow($row, $statusLabelMap, $statusColorMap), (array) ($result['items'] ?? [])); + $tableListData = [ + 'list_key' => 'orders', + 'base_path' => '/orders/list', + 'query' => $filters, + 'filters' => [ + [ + 'key' => 'search', + 'label' => $this->translator->get('orders.filters.search'), + 'type' => 'text', + 'value' => $filters['search'], + ], + [ + 'key' => 'source', + 'label' => $this->translator->get('orders.filters.source'), + 'type' => 'select', + 'value' => $filters['source'], + 'options' => ['' => $this->translator->get('orders.filters.any')] + $sourceOptions, + ], + [ + 'key' => 'status', + 'label' => $this->translator->get('orders.filters.status'), + 'type' => 'select', + 'value' => $filters['status'], + 'options' => ['' => $this->translator->get('orders.filters.any')] + $statusOptions, + ], + [ + 'key' => 'payment_status', + 'label' => $this->translator->get('orders.filters.payment_status'), + 'type' => 'select', + 'value' => $filters['payment_status'], + 'options' => $this->paymentStatusFilterOptions(), + ], + [ + 'key' => 'date_from', + 'label' => $this->translator->get('orders.filters.date_from'), + 'type' => 'date', + 'value' => $filters['date_from'], + ], + [ + 'key' => 'date_to', + 'label' => $this->translator->get('orders.filters.date_to'), + 'type' => 'date', + 'value' => $filters['date_to'], + ], + ], + 'columns' => [ + ['key' => 'order_ref', 'label' => $this->translator->get('orders.fields.order_ref'), 'sortable' => true, 'sort_key' => 'source_order_id', 'raw' => true], + ['key' => 'buyer', 'label' => $this->translator->get('orders.fields.buyer'), 'raw' => true], + ['key' => 'status_badges', 'label' => $this->translator->get('orders.fields.status'), 'sortable' => true, 'sort_key' => 'status_code', 'raw' => true], + ['key' => 'products', 'label' => $this->translator->get('orders.fields.products'), 'raw' => true], + ['key' => 'totals', 'label' => $this->translator->get('orders.fields.totals'), 'sortable' => true, 'sort_key' => 'total_with_tax', 'raw' => true], + ['key' => 'shipping', 'label' => $this->translator->get('orders.fields.shipping'), 'raw' => true], + ['key' => 'ordered_at', 'label' => $this->translator->get('orders.fields.ordered_at'), 'sortable' => true, 'sort_key' => 'ordered_at'], + ], + 'rows' => $tableRows, + 'pagination' => [ + 'page' => (int) ($result['page'] ?? 1), + 'total_pages' => $totalPages, + 'total' => (int) ($result['total'] ?? 0), + 'per_page' => (int) ($result['per_page'] ?? 20), + ], + 'per_page_options' => [20, 50, 100], + 'selectable' => true, + 'select_name' => 'selected_ids[]', + 'select_value_key' => 'id', + 'header_actions' => [], + 'empty_message' => $this->translator->get('orders.empty'), + 'show_actions' => false, + ]; + + if ($request->header('X-Requested-With') === 'XMLHttpRequest') { + $tableHtml = $this->template->render('components/table-list', [ + 'tableList' => $tableListData, + ]); + $panelHtml = $this->template->render('components/order-status-panel', [ + 'statusPanelList' => $statusPanel, + 'statusPanelTitle' => 'Statusy', + ]); + + return Response::json([ + 'tableHtml' => $tableHtml, + 'panelHtml' => $panelHtml, + ]); + } + $html = $this->template->render('orders/list', [ 'title' => $this->translator->get('orders.title'), 'activeMenu' => 'orders', 'activeOrders' => 'list', 'user' => $this->auth->user(), 'csrfToken' => Csrf::token(), - 'tableList' => [ - 'list_key' => 'orders', - 'base_path' => '/orders/list', - 'query' => $filters, - 'filters' => [ - [ - 'key' => 'search', - 'label' => $this->translator->get('orders.filters.search'), - 'type' => 'text', - 'value' => $filters['search'], - ], - [ - 'key' => 'source', - 'label' => $this->translator->get('orders.filters.source'), - 'type' => 'select', - 'value' => $filters['source'], - 'options' => ['' => $this->translator->get('orders.filters.any')] + $sourceOptions, - ], - [ - 'key' => 'status', - 'label' => $this->translator->get('orders.filters.status'), - 'type' => 'select', - 'value' => $filters['status'], - 'options' => ['' => $this->translator->get('orders.filters.any')] + $statusOptions, - ], - [ - 'key' => 'payment_status', - 'label' => $this->translator->get('orders.filters.payment_status'), - 'type' => 'select', - 'value' => $filters['payment_status'], - 'options' => $this->paymentStatusFilterOptions(), - ], - [ - 'key' => 'date_from', - 'label' => $this->translator->get('orders.filters.date_from'), - 'type' => 'date', - 'value' => $filters['date_from'], - ], - [ - 'key' => 'date_to', - 'label' => $this->translator->get('orders.filters.date_to'), - 'type' => 'date', - 'value' => $filters['date_to'], - ], - ], - 'columns' => [ - ['key' => 'order_ref', 'label' => $this->translator->get('orders.fields.order_ref'), 'sortable' => true, 'sort_key' => 'source_order_id', 'raw' => true], - ['key' => 'buyer', 'label' => $this->translator->get('orders.fields.buyer'), 'raw' => true], - ['key' => 'status_badges', 'label' => $this->translator->get('orders.fields.status'), 'sortable' => true, 'sort_key' => 'external_status_id', 'raw' => true], - ['key' => 'products', 'label' => $this->translator->get('orders.fields.products'), 'raw' => true], - ['key' => 'totals', 'label' => $this->translator->get('orders.fields.totals'), 'sortable' => true, 'sort_key' => 'total_with_tax', 'raw' => true], - ['key' => 'shipping', 'label' => $this->translator->get('orders.fields.shipping'), 'raw' => true], - ['key' => 'ordered_at', 'label' => $this->translator->get('orders.fields.ordered_at'), 'sortable' => true, 'sort_key' => 'ordered_at'], - ], - 'rows' => $tableRows, - 'pagination' => [ - 'page' => (int) ($result['page'] ?? 1), - 'total_pages' => $totalPages, - 'total' => (int) ($result['total'] ?? 0), - 'per_page' => (int) ($result['per_page'] ?? 20), - ], - 'per_page_options' => [20, 50, 100], - 'selectable' => true, - 'select_name' => 'selected_ids[]', - 'select_value_key' => 'id', - 'header_actions' => [], - 'empty_message' => $this->translator->get('orders.empty'), - 'show_actions' => false, - ], + 'tableList' => $tableListData, 'stats' => $stats, 'statusPanel' => $statusPanel, 'allStatuses' => $this->buildAllStatusOptions($statusConfig), @@ -173,7 +190,7 @@ final class OrdersController $notes = is_array($details['notes'] ?? null) ? $details['notes'] : []; $history = is_array($details['status_history'] ?? null) ? $details['status_history'] : []; $activityLog = is_array($details['activity_log'] ?? null) ? $details['activity_log'] : []; - $statusCode = (string) (($order['effective_status_id'] ?? '') !== '' ? $order['effective_status_id'] : ($order['external_status_id'] ?? '')); + $statusCode = (string) (($order['effective_status_id'] ?? '') !== '' ? $order['effective_status_id'] : ($order['status_code'] ?? '')); $statusCounts = $this->orders->statusCounts(); $statusConfig = $this->orders->statusPanelConfig(); $statusLabelMap = $this->statusLabelMap($statusConfig); @@ -280,7 +297,7 @@ final class OrdersController $oldDetails = $this->orders->findDetails($orderId); $oldOrder = is_array($oldDetails['order'] ?? null) ? $oldDetails['order'] : []; - $oldStatus = strtolower(trim((string) ($oldOrder['external_status_id'] ?? ''))); + $oldStatus = strtolower(trim((string) ($oldOrder['status_code'] ?? ''))); $success = $this->orders->updateOrderStatus($orderId, $newStatus, 'user', $actorName !== '' ? $actorName : null); @@ -336,7 +353,7 @@ final class OrdersController $buyerName = trim((string) ($row['buyer_name'] ?? '')); $buyerEmail = trim((string) ($row['buyer_email'] ?? '')); $buyerCity = trim((string) ($row['buyer_city'] ?? '')); - $status = trim((string) (($row['effective_status_id'] ?? '') !== '' ? $row['effective_status_id'] : ($row['external_status_id'] ?? ''))); + $status = trim((string) (($row['effective_status_id'] ?? '') !== '' ? $row['effective_status_id'] : ($row['status_code'] ?? ''))); $currency = trim((string) ($row['currency'] ?? '')); $totalWithTax = $row['total_with_tax'] !== null ? number_format((float) $row['total_with_tax'], 2, '.', ' ') : '-'; $totalPaid = $row['total_paid'] !== null ? number_format((float) $row['total_paid'], 2, '.', ' ') : '-'; @@ -349,11 +366,17 @@ final class OrdersController $shipments = max(0, (int) ($row['shipments_count'] ?? 0)); $documents = max(0, (int) ($row['documents_count'] ?? 0)); $itemsPreview = is_array($row['items_preview'] ?? null) ? $row['items_preview'] : []; + $projectsDone = max(0, (int) ($row['projects_done'] ?? 0)); + $projectsTotal = max(0, (int) ($row['projects_total'] ?? 0)); + + $previewBtn = ''; return [ 'id' => (int) ($row['id'] ?? 0), 'order_ref' => '
' - . '
' + . '' . '
' @@ -371,7 +394,7 @@ final class OrdersController 'status_badges' => '
' . $this->statusBadge($status, $this->statusLabel($status, $statusLabelMap), $statusColorMap[strtolower(trim($status))] ?? '') . '
', - 'products' => $this->productsHtml($itemsPreview, $itemsCount, $itemsQty), + 'products' => $this->productsHtml($itemsPreview, $itemsCount, $itemsQty, $projectsDone, $projectsTotal), 'totals' => '
' . '
' . htmlspecialchars($totalWithTax . ' ' . $currency, ENT_QUOTES, 'UTF-8') . ($isUnpaid ? ' Nieopłacone' : '') . '
' . '
' . ($isCod ? 'Za pobraniem' : 'oplacono: ' . htmlspecialchars($totalPaid . ' ' . $currency, ENT_QUOTES, 'UTF-8')) . '
' @@ -671,7 +694,7 @@ final class OrdersController /** * @param array> $itemsPreview */ - private function productsHtml(array $itemsPreview, int $itemsCount, string $itemsQty): string + private function productsHtml(array $itemsPreview, int $itemsCount, string $itemsQty, int $projectsDone = 0, int $projectsTotal = 0): string { if ($itemsPreview === []) { return '
' @@ -704,12 +727,37 @@ final class OrdersController if ($itemsCount > count($itemsPreview)) { $html .= '
+' . ($itemsCount - count($itemsPreview)) . ' pozycji
'; } - $html .= '
' . $itemsCount . ' pozycji / ' . htmlspecialchars($itemsQty, ENT_QUOTES, 'UTF-8') . ' szt.
'; + $html .= '
' . $itemsCount . ' pozycji / ' . htmlspecialchars($itemsQty, ENT_QUOTES, 'UTF-8') . ' szt.' + . $this->projectBadge($projectsDone, $projectsTotal) + . '
'; $html .= '
'; return $html; } + private function projectBadge(int $done, int $total): string + { + if ($total === 0) { + return ''; + } + + if ($done === $total) { + return ' ' + . '' + . ''; + } + + if ($done > 0) { + return ' ' + . $done . '/' . $total + . ''; + } + + return ' ' + . '' + . ''; + } + private function shippingHtml(string $deliveryMethod, int $shipments, int $documents): string { $deliveryMethod = trim(html_entity_decode(strip_tags($deliveryMethod), ENT_QUOTES | ENT_HTML5, 'UTF-8')); @@ -961,4 +1009,35 @@ final class OrdersController return Response::json(['results' => $results]); } + public function preview(Request $request): Response + { + $orderId = max(0, (int) $request->input('id', 0)); + $details = $this->orders->findDetails($orderId); + if ($details === null) { + return Response::html('
Zamowienie nie znalezione.
', 404); + } + + $order = is_array($details['order'] ?? null) ? $details['order'] : []; + $items = is_array($details['items'] ?? null) ? $details['items'] : []; + $addresses = is_array($details['addresses'] ?? null) ? $details['addresses'] : []; + $notes = is_array($details['notes'] ?? null) ? $details['notes'] : []; + + $addressByType = ['customer' => null, 'delivery' => null, 'invoice' => null]; + foreach ($addresses as $address) { + $type = (string) ($address['address_type'] ?? ''); + if ($type !== '' && array_key_exists($type, $addressByType) && $addressByType[$type] === null) { + $addressByType[$type] = $address; + } + } + + $html = $this->template->render('orders/partials/preview-content', [ + 'order' => $order, + 'items' => $items, + 'addressByType' => $addressByType, + 'notes' => $notes, + ]); + + return Response::html($html); + } + } diff --git a/src/Modules/Orders/OrdersRepository.php b/src/Modules/Orders/OrdersRepository.php index 437b6dd..b2073d1 100644 --- a/src/Modules/Orders/OrdersRepository.php +++ b/src/Modules/Orders/OrdersRepository.php @@ -35,7 +35,7 @@ final class OrdersRepository $sortColumn = match ($sort) { 'source_order_id' => 'o.source_order_id', 'external_order_id' => 'o.external_order_id', - 'external_status_id' => 'o.external_status_id', + 'status_code' => 'o.status_code', 'payment_status' => 'o.payment_status', 'total_with_tax' => 'o.total_with_tax', 'total_paid' => 'o.total_paid', @@ -48,7 +48,7 @@ final class OrdersRepository try { $countSql = 'SELECT COUNT(*) FROM orders o ' . 'LEFT JOIN order_addresses a ON a.order_id = o.id AND a.address_type = "customer" ' - . 'LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.external_status_id) = asm.allegro_status_code' + . 'LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.status_code) = asm.allegro_status_code' . $whereSql; $countStmt = $this->pdo->prepare($countSql); $countStmt->execute($params); @@ -158,7 +158,7 @@ final class OrdersRepository o.source, o.source_order_id, o.external_order_id, - o.external_status_id, + o.status_code, ' . $effectiveStatusSql . ' AS effective_status_id, o.payment_status, o.currency, @@ -178,15 +178,19 @@ final class OrdersRepository o.external_payment_type_id, COALESCE(oi_agg.items_count, 0) AS items_count, COALESCE(oi_agg.items_qty, 0) AS items_qty, + COALESCE(oi_agg.projects_done, 0) AS projects_done, + COALESCE(oi_agg.projects_total, 0) AS projects_total, COALESCE(sh_agg.shipments_count, 0) AS shipments_count, COALESCE(od_agg.documents_count, 0) AS documents_count, ig.name AS integration_name FROM orders o LEFT JOIN order_addresses a ON a.order_id = o.id AND a.address_type = "customer" - LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.external_status_id) = asm.allegro_status_code + LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.status_code) = asm.allegro_status_code LEFT JOIN integrations ig ON ig.id = o.integration_id LEFT JOIN ( - SELECT order_id, COUNT(*) AS items_count, COALESCE(SUM(quantity), 0) AS items_qty + SELECT order_id, COUNT(*) AS items_count, COALESCE(SUM(quantity), 0) AS items_qty, + SUM(CASE WHEN project_generated = 1 THEN 1 ELSE 0 END) AS projects_done, + COUNT(*) AS projects_total FROM order_items GROUP BY order_id ) oi_agg ON oi_agg.order_id = o.id LEFT JOIN ( @@ -217,7 +221,7 @@ final class OrdersRepository 'source' => (string) ($row['source'] ?? ''), 'source_order_id' => (string) ($row['source_order_id'] ?? ''), 'external_order_id' => (string) ($row['external_order_id'] ?? ''), - 'external_status_id' => (string) ($row['external_status_id'] ?? ''), + 'status_code' => (string) ($row['status_code'] ?? ''), 'effective_status_id' => (string) ($row['effective_status_id'] ?? ''), 'payment_status' => isset($row['payment_status']) ? (int) $row['payment_status'] : null, 'currency' => (string) ($row['currency'] ?? ''), @@ -240,6 +244,8 @@ final class OrdersRepository 'documents_count' => (int) ($row['documents_count'] ?? 0), 'integration_name' => (string) ($row['integration_name'] ?? ''), 'items_preview' => (array) ($itemPreviewsByOrderId[$orderId] ?? []), + 'projects_done' => (int) ($row['projects_done'] ?? 0), + 'projects_total' => (int) ($row['projects_total'] ?? 0), ]; } @@ -253,7 +259,7 @@ final class OrdersRepository $rows = $this->pdo->query( 'SELECT DISTINCT ' . $effectiveStatusSql . ' AS effective_status_id FROM orders o - LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.external_status_id) = asm.allegro_status_code + LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.status_code) = asm.allegro_status_code WHERE ' . $effectiveStatusSql . ' IS NOT NULL AND ' . $effectiveStatusSql . ' <> "" ORDER BY effective_status_id ASC' @@ -317,7 +323,7 @@ final class OrdersRepository SUM(CASE WHEN payment_status = 2 THEN 1 ELSE 0 END) AS paid_count, SUM(CASE WHEN ' . $effectiveStatusSql . ' IN ("shipped", "delivered", "returned") THEN 1 ELSE 0 END) AS shipped_count FROM orders o - LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.external_status_id) = asm.allegro_status_code')->fetch(PDO::FETCH_ASSOC); + LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.status_code) = asm.allegro_status_code')->fetch(PDO::FETCH_ASSOC); } catch (Throwable) { return [ 'all' => 0, @@ -351,7 +357,7 @@ final class OrdersRepository $rows = $this->pdo->query( 'SELECT ' . $effectiveStatusSql . ' AS effective_status_id, COUNT(*) AS cnt FROM orders o - LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.external_status_id) = asm.allegro_status_code + LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.status_code) = asm.allegro_status_code GROUP BY effective_status_id' )->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable) { @@ -475,7 +481,7 @@ final class OrdersRepository 'SELECT o.*, ' . $effectiveStatusSql . ' AS effective_status_id, ig.name AS integration_name FROM orders o - LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.external_status_id) = asm.allegro_status_code + LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.status_code) = asm.allegro_status_code LEFT JOIN integrations ig ON ig.id = o.integration_id WHERE o.id = :id LIMIT 1' @@ -670,7 +676,7 @@ final class OrdersRepository AND ' . $mappingAlias . '.orderpro_status_code IS NOT NULL AND ' . $mappingAlias . '.orderpro_status_code <> "" THEN ' . $mappingAlias . '.orderpro_status_code - ELSE ' . $orderAlias . '.external_status_id + ELSE ' . $orderAlias . '.status_code END'; } @@ -981,16 +987,16 @@ final class OrdersRepository public function updateOrderStatus(int $orderId, string $newStatusCode, string $actorType = 'user', ?string $actorName = null): bool { try { - $stmt = $this->pdo->prepare('SELECT external_status_id FROM orders WHERE id = :id LIMIT 1'); + $stmt = $this->pdo->prepare('SELECT status_code FROM orders WHERE id = :id LIMIT 1'); $stmt->execute(['id' => $orderId]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!is_array($row)) { return false; } - $oldStatus = trim((string) ($row['external_status_id'] ?? '')); + $oldStatus = trim((string) ($row['status_code'] ?? '')); - $update = $this->pdo->prepare('UPDATE orders SET external_status_id = :status, updated_at = NOW() WHERE id = :id'); + $update = $this->pdo->prepare('UPDATE orders SET status_code = :status, updated_at = NOW() WHERE id = :id'); $update->execute(['status' => $newStatusCode, 'id' => $orderId]); $this->recordStatusChange( diff --git a/src/Modules/Settings/AllegroOrderImportService.php b/src/Modules/Settings/AllegroOrderImportService.php index a694f0b..10026c6 100644 --- a/src/Modules/Settings/AllegroOrderImportService.php +++ b/src/Modules/Settings/AllegroOrderImportService.php @@ -209,7 +209,7 @@ final class AllegroOrderImportService 'external_order_id' => $checkoutFormId, 'external_platform_id' => trim((string) ($payload['marketplace']['id'] ?? 'allegro-pl')), 'external_platform_account_id' => null, - 'external_status_id' => $externalStatus, + 'status_code' => $externalStatus, 'external_payment_type_id' => trim((string) ($payment['type'] ?? '')), 'payment_status' => $mappedPaymentStatus, 'external_carrier_id' => $deliveryForm !== '' ? $deliveryForm : null, diff --git a/src/Modules/Settings/AllegroStatusSyncService.php b/src/Modules/Settings/AllegroStatusSyncService.php index 9c553ac..d65f042 100644 --- a/src/Modules/Settings/AllegroStatusSyncService.php +++ b/src/Modules/Settings/AllegroStatusSyncService.php @@ -241,10 +241,10 @@ final class AllegroStatusSyncService try { $statement = $this->pdo->prepare( - 'SELECT id, source_order_id, external_status_id + 'SELECT id, source_order_id, status_code FROM orders WHERE source = ? - AND LOWER(COALESCE(external_status_id, "")) NOT IN (' . $placeholders . ') + AND LOWER(COALESCE(status_code, "")) NOT IN (' . $placeholders . ') AND (last_status_checked_at IS NULL OR source_updated_at > last_status_checked_at) ORDER BY source_updated_at DESC LIMIT ' . self::MAX_ORDERS_PER_RUN @@ -273,7 +273,7 @@ final class AllegroStatusSyncService 'SELECT o.id, o.source_order_id, - o.external_status_id AS orderpro_status_code, + o.status_code AS orderpro_status_code, MAX(h.changed_at) AS latest_change FROM order_status_history h INNER JOIN orders o ON o.id = h.order_id @@ -281,7 +281,7 @@ final class AllegroStatusSyncService AND o.integration_id = :integration_id AND h.change_source = :change_source AND h.changed_at > :since_date - GROUP BY o.id, o.source_order_id, o.external_status_id + GROUP BY o.id, o.source_order_id, o.status_code ORDER BY latest_change ASC LIMIT ' . self::MAX_ORDERS_PER_RUN ); diff --git a/src/Modules/Settings/ProjectMappingController.php b/src/Modules/Settings/ProjectMappingController.php new file mode 100644 index 0000000..74aec6b --- /dev/null +++ b/src/Modules/Settings/ProjectMappingController.php @@ -0,0 +1,155 @@ +repository->getAll(); + $scripts = $this->scanScripts(); + + $html = $this->template->render('settings/project-mappings', [ + 'title' => $this->translator->get('settings.project_mapping.title'), + 'activeMenu' => 'settings', + 'activeSettings' => 'project-mappings', + 'user' => $this->auth->user(), + 'csrfToken' => Csrf::token(), + 'mappings' => $mappings, + 'scripts' => $scripts, + 'errorMessage' => (string) Flash::get('settings_error', ''), + 'successMessage' => (string) Flash::get('settings_success', ''), + ], 'layouts/app'); + + return Response::html($html); + } + + public function store(Request $request): Response + { + if (!Csrf::validate((string) $request->input('_token', ''))) { + Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); + return Response::redirect('/settings/project-mappings'); + } + + $pattern = trim((string) $request->input('product_name_pattern', '')); + $scriptName = trim((string) $request->input('script_name', '')); + $outputDir = trim((string) $request->input('output_dir', '')); + + if ($pattern === '' || $scriptName === '') { + Flash::set('settings_error', $this->translator->get('settings.project_mapping.flash.validation_error')); + return Response::redirect('/settings/project-mappings'); + } + + $scriptPath = $this->basePath . '/' . self::SCRIPTS_DIR . '/' . $scriptName; + if (!file_exists($scriptPath)) { + Flash::set('settings_error', $this->translator->get('settings.project_mapping.flash.script_not_found')); + return Response::redirect('/settings/project-mappings'); + } + + $this->repository->create([ + 'product_name_pattern' => $pattern, + 'script_name' => $scriptName, + 'output_dir' => $outputDir !== '' ? $outputDir : null, + ]); + + Flash::set('settings_success', $this->translator->get('settings.project_mapping.flash.created')); + return Response::redirect('/settings/project-mappings'); + } + + public function update(Request $request): Response + { + if (!Csrf::validate((string) $request->input('_token', ''))) { + Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); + return Response::redirect('/settings/project-mappings'); + } + + $id = (int) $request->param('id', '0'); + $pattern = trim((string) $request->input('product_name_pattern', '')); + $scriptName = trim((string) $request->input('script_name', '')); + $outputDir = trim((string) $request->input('output_dir', '')); + + if ($id <= 0 || $pattern === '' || $scriptName === '') { + Flash::set('settings_error', $this->translator->get('settings.project_mapping.flash.validation_error')); + return Response::redirect('/settings/project-mappings'); + } + + $this->repository->update($id, [ + 'product_name_pattern' => $pattern, + 'script_name' => $scriptName, + 'output_dir' => $outputDir !== '' ? $outputDir : null, + ]); + + Flash::set('settings_success', $this->translator->get('settings.project_mapping.flash.updated')); + return Response::redirect('/settings/project-mappings'); + } + + public function delete(Request $request): Response + { + if (!Csrf::validate((string) $request->input('_token', ''))) { + Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); + return Response::redirect('/settings/project-mappings'); + } + + $id = (int) $request->param('id', '0'); + if ($id > 0) { + $this->repository->delete($id); + Flash::set('settings_success', $this->translator->get('settings.project_mapping.flash.deleted')); + } + + return Response::redirect('/settings/project-mappings'); + } + + public function toggleActive(Request $request): Response + { + if (!Csrf::validate((string) $request->input('_token', ''))) { + Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); + return Response::redirect('/settings/project-mappings'); + } + + $id = (int) $request->param('id', '0'); + if ($id > 0) { + $this->repository->toggleActive($id); + Flash::set('settings_success', $this->translator->get('settings.project_mapping.flash.toggled')); + } + + return Response::redirect('/settings/project-mappings'); + } + + private function scanScripts(): array + { + $dir = $this->basePath . '/' . self::SCRIPTS_DIR; + if (!is_dir($dir)) { + return []; + } + + $files = glob($dir . '/*.py'); + $scripts = []; + foreach ($files as $file) { + $scripts[] = basename($file); + } + sort($scripts); + + return $scripts; + } +} diff --git a/src/Modules/Settings/ProjectMappingRepository.php b/src/Modules/Settings/ProjectMappingRepository.php new file mode 100644 index 0000000..8ea6557 --- /dev/null +++ b/src/Modules/Settings/ProjectMappingRepository.php @@ -0,0 +1,88 @@ +pdo->prepare( + 'SELECT * FROM project_mappings ORDER BY id DESC' + ); + $statement->execute(); + + return $statement->fetchAll(PDO::FETCH_ASSOC) ?: []; + } + + public function getById(int $id): ?array + { + $statement = $this->pdo->prepare( + 'SELECT * FROM project_mappings WHERE id = :id LIMIT 1' + ); + $statement->execute([':id' => $id]); + $row = $statement->fetch(PDO::FETCH_ASSOC); + + return $row ?: null; + } + + public function create(array $data): int + { + $statement = $this->pdo->prepare( + 'INSERT INTO project_mappings (product_name_pattern, script_name, output_dir, is_active) + VALUES (:product_name_pattern, :script_name, :output_dir, :is_active)' + ); + $statement->execute([ + ':product_name_pattern' => $data['product_name_pattern'], + ':script_name' => $data['script_name'], + ':output_dir' => $data['output_dir'] ?? null, + ':is_active' => (int) ($data['is_active'] ?? 1), + ]); + + return (int) $this->pdo->lastInsertId(); + } + + public function update(int $id, array $data): bool + { + $statement = $this->pdo->prepare( + 'UPDATE project_mappings + SET product_name_pattern = :product_name_pattern, + script_name = :script_name, + output_dir = :output_dir + WHERE id = :id' + ); + + return $statement->execute([ + ':product_name_pattern' => $data['product_name_pattern'], + ':script_name' => $data['script_name'], + ':output_dir' => $data['output_dir'] ?? null, + ':id' => $id, + ]); + } + + public function delete(int $id): bool + { + $statement = $this->pdo->prepare( + 'DELETE FROM project_mappings WHERE id = :id' + ); + + return $statement->execute([':id' => $id]); + } + + public function toggleActive(int $id): bool + { + $statement = $this->pdo->prepare( + 'UPDATE project_mappings SET is_active = 1 - is_active WHERE id = :id' + ); + + return $statement->execute([':id' => $id]); + } +} diff --git a/src/Modules/Settings/ShopproOrderMapper.php b/src/Modules/Settings/ShopproOrderMapper.php index 2bc54af..e8547a6 100644 --- a/src/Modules/Settings/ShopproOrderMapper.php +++ b/src/Modules/Settings/ShopproOrderMapper.php @@ -135,7 +135,7 @@ final class ShopproOrderMapper 'external_order_id' => $sourceOrderId, 'external_platform_id' => IntegrationSources::SHOPPRO, 'external_platform_account_id' => null, - 'external_status_id' => $effectiveStatus, + 'status_code' => $effectiveStatus, 'external_payment_type_id' => $this->normalizeCodPaymentType((string) $this->readPath($payload, ['payment_method', 'payment.method', 'payments.method'])), 'payment_status' => $this->mapPaymentStatus($payload, $isPaid), 'external_carrier_id' => StringHelper::nullableString($deliveryLabel), diff --git a/src/Modules/Settings/ShopproPaymentStatusSyncService.php b/src/Modules/Settings/ShopproPaymentStatusSyncService.php index 2e5c879..47c46b1 100644 --- a/src/Modules/Settings/ShopproPaymentStatusSyncService.php +++ b/src/Modules/Settings/ShopproPaymentStatusSyncService.php @@ -164,9 +164,9 @@ final class ShopproPaymentStatusSyncService } if ($watchedStatuses !== []) { - $where[] = 'LOWER(COALESCE(external_status_id, "")) IN (' . implode(', ', $statusPlaceholders) . ')'; + $where[] = 'LOWER(COALESCE(status_code, "")) IN (' . implode(', ', $statusPlaceholders) . ')'; } else { - $where[] = 'LOWER(COALESCE(external_status_id, "")) NOT IN (' . implode(', ', $statusPlaceholders) . ')'; + $where[] = 'LOWER(COALESCE(status_code, "")) NOT IN (' . implode(', ', $statusPlaceholders) . ')'; } $sql = 'SELECT id, source_order_id, payment_status, total_paid, total_with_tax, currency, external_payment_type_id diff --git a/src/Modules/Settings/ShopproStatusSyncService.php b/src/Modules/Settings/ShopproStatusSyncService.php index c53f9c4..a25cc4f 100644 --- a/src/Modules/Settings/ShopproStatusSyncService.php +++ b/src/Modules/Settings/ShopproStatusSyncService.php @@ -137,7 +137,7 @@ final class ShopproStatusSyncService foreach ($orders as $order) { $sourceOrderId = (int) ($order['source_order_id'] ?? 0); - $orderproStatus = strtolower(trim((string) ($order['external_status_id'] ?? ''))); + $orderproStatus = strtolower(trim((string) ($order['status_code'] ?? ''))); $changeAt = (string) ($order['latest_change'] ?? ''); if ($sourceOrderId <= 0 || $orderproStatus === '') { @@ -224,7 +224,7 @@ final class ShopproStatusSyncService } /** - * @return array + * @return array */ private function findOrdersWithManualStatusChanges(int $integrationId, ?string $lastPushedAt): array { @@ -233,14 +233,14 @@ final class ShopproStatusSyncService try { $statement = $this->pdo->prepare( - 'SELECT o.id AS order_id, o.source_order_id, o.external_status_id, + 'SELECT o.id AS order_id, o.source_order_id, o.status_code, MAX(h.changed_at) AS latest_change FROM order_status_history h JOIN orders o ON o.id = h.order_id WHERE o.integration_id = :integration_id AND h.change_source = :change_source AND h.changed_at > :since_date - GROUP BY o.id, o.source_order_id, o.external_status_id + GROUP BY o.id, o.source_order_id, o.status_code ORDER BY latest_change ASC LIMIT 50' ); diff --git a/src/Modules/Users/UserRepository.php b/src/Modules/Users/UserRepository.php index 01be865..10270e0 100644 --- a/src/Modules/Users/UserRepository.php +++ b/src/Modules/Users/UserRepository.php @@ -139,6 +139,43 @@ final class UserRepository /** * @return array */ + public function updateRememberToken(int $userId, ?string $tokenHash): void + { + $statement = $this->pdo->prepare( + 'UPDATE users SET remember_token = :token WHERE id = :id' + ); + $statement->execute([ + 'token' => $tokenHash, + 'id' => $userId, + ]); + } + + /** + * @return array|null + */ + public function findByRememberToken(string $tokenHash): ?array + { + if ($tokenHash === '') { + return null; + } + + $statement = $this->pdo->prepare( + 'SELECT id, name, email FROM users WHERE remember_token = :token LIMIT 1' + ); + $statement->execute(['token' => $tokenHash]); + + $row = $statement->fetch(); + if (!is_array($row)) { + return null; + } + + return [ + 'id' => (int) ($row['id'] ?? 0), + 'name' => (string) ($row['name'] ?? ''), + 'email' => (string) ($row['email'] ?? ''), + ]; + } + public function create(string $name, string $email, string $passwordHash): array { $createdAt = date('Y-m-d H:i:s'); diff --git a/tools/generowanie/buteleczki_wzor1.py b/tools/generowanie/buteleczki_wzor1.py new file mode 100644 index 0000000..3996c7f --- /dev/null +++ b/tools/generowanie/buteleczki_wzor1.py @@ -0,0 +1,146 @@ +""" +Generator PSD — Podziękowanie dla gości weselnych (buteleczka, Wzór 1). + +Otwiera szablon PSD, podmienia teksty w Smart Object "Projekt" +z zachowaniem pozycji warstw, zapisuje jako nowy PSD w folderze _gotowe. + +Wymaga: uruchomiony Adobe Photoshop, pakiet photoshop-python-api. + +Użycie: + python buteleczki_wzor1.py --imie_zenskie "Kinga" --imie_meskie "Łukasz" \ + --data "30.04.2026" --klient "Kinga Klimczak" + + python buteleczki_wzor1.py --imie_zenskie "Kinga" --imie_meskie "Łukasz" \ + --data "30.04.2026" --zyczenia "Dziękujemy!" --klient "Kinga Klimczak" +""" + +import argparse +import os +import sys +import photoshop.api as ps + + +# --- Ścieżki --- +PROJEKT_DIR = os.path.join( + r"d:\pomysloweprezenty.pl\projekty\ślub - buteleczki", + "Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 1", +) +SZABLON_PATH = os.path.join(PROJEKT_DIR, "szablon 370x300.psd") +GOTOWE_DIR = os.path.join(PROJEKT_DIR, "_gotowe") + +# Nazwa warstwy Smart Object w głównym PSD +SMART_OBJECT_LAYER = "Projekt" + +# Mapowanie: nazwa_warstwy -> klucz argumentu +# Warstwy znajdują się w: SO → Tekst → Dane → +LAYER_MAP = { + "imie_zenskie": "imie_zenskie", + "imie_meskie": "imie_meskie", + "data": "data", + "zyczenia": "zyczenia", +} + + +def open_smart_object(app): + """Otwiera zawartość aktywnej warstwy Smart Object do edycji.""" + desc = ps.ActionDescriptor() + ref = ps.ActionReference() + ref.putEnumerated( + app.stringIDToTypeID("layer"), + app.stringIDToTypeID("ordinal"), + app.stringIDToTypeID("targetEnum"), + ) + desc.putReference(app.stringIDToTypeID("null"), ref) + app.executeAction(app.stringIDToTypeID("placedLayerEditContents"), desc) + + +def change_text_preserve_position(layer, new_text): + """Zmienia tekst warstwy z zachowaniem jej oryginalnej pozycji.""" + bounds_before = [float(b) for b in layer.bounds] + layer.textItem.contents = new_text + bounds_after = [float(b) for b in layer.bounds] + + dx = bounds_before[0] - bounds_after[0] + dy = bounds_before[1] - bounds_after[1] + + if dx != 0 or dy != 0: + layer.translate(dx, dy) + + +def generate(imie_zenskie, imie_meskie, data, klient, zyczenia=None): + """Generuje PSD z podmienionymi danymi.""" + os.makedirs(GOTOWE_DIR, exist_ok=True) + output_path = os.path.join(GOTOWE_DIR, f"{klient}.psd") + + app = ps.Application() + doc = app.open(SZABLON_PATH) + print(f"Otwarto szablon: {doc.name}") + + # Znajdź i otwórz Smart Object + for layer in doc.layers: + if layer.name == SMART_OBJECT_LAYER: + app.activeDocument.activeLayer = layer + break + else: + doc.close(ps.SaveOptions.DoNotSaveChanges) + raise RuntimeError(f"Nie znaleziono warstwy '{SMART_OBJECT_LAYER}'") + + open_smart_object(app) + so_doc = app.activeDocument + + # Nawiguj do grupy Dane + dane_group = so_doc.layerSets["Tekst"].layerSets["Dane"] + + # Podmiana tekstów + replacements = { + "imie_zenskie": imie_zenskie, + "imie_meskie": imie_meskie, + "data": data, + } + if zyczenia is not None: + replacements["zyczenia"] = zyczenia + + for layer_name, new_text in replacements.items(): + layer = dane_group.artLayers[layer_name] + old_text = layer.textItem.contents + change_text_preserve_position(layer, new_text) + print(f" {layer_name}: \"{old_text}\" -> \"{new_text}\"") + + # Zapisz Smart Object + so_doc.save() + so_doc.close() + print("Smart Object zapisany") + + # Zapisz jako nowy PSD + psd_opts = ps.PhotoshopSaveOptions() + app.activeDocument.saveAs(output_path, psd_opts, True) + print(f"Zapisano: {output_path}") + + # Zamknij bez zapisu oryginału + app.activeDocument.close(ps.SaveOptions.DoNotSaveChanges) + print("Gotowe!") + return output_path + + +def main(): + parser = argparse.ArgumentParser( + description="Generator PSD - buteleczki weselne Wzór 1" + ) + parser.add_argument("--imie_zenskie", required=True, help="Imię żeńskie (np. Kinga)") + parser.add_argument("--imie_meskie", required=True, help="Imię męskie (np. Łukasz)") + parser.add_argument("--data", required=True, help="Data uroczystości (np. 30.04.2026)") + parser.add_argument("--klient", required=True, help="Imię i nazwisko klienta (nazwa pliku wyjściowego)") + parser.add_argument("--zyczenia", default=None, help="Tekst życzeń (domyślnie: oryginalny z szablonu)") + + args = parser.parse_args() + generate( + imie_zenskie=args.imie_zenskie, + imie_meskie=args.imie_meskie, + data=args.data, + klient=args.klient, + zyczenia=args.zyczenia, + ) + + +if __name__ == "__main__": + main() diff --git a/tools/generowanie/buteleczki_wzor6.py b/tools/generowanie/buteleczki_wzor6.py new file mode 100644 index 0000000..9960f1d --- /dev/null +++ b/tools/generowanie/buteleczki_wzor6.py @@ -0,0 +1,143 @@ +""" +Generator PSD — Podziękowanie dla gości weselnych (buteleczka, Wzór 6). + +Otwiera szablon PSD, podmienia imiona, datę i opcjonalnie życzenia +w Smart Object "Projekt" z zachowaniem pozycji warstw, zapisuje jako nowy PSD. + +Wymaga: uruchomiony Adobe Photoshop, pakiet photoshop-python-api. + +Użycie: + python buteleczki_wzor6.py --imie_zenskie "Kasia" --imie_meskie "Tomek" \ + --data "30.04.2026" --klient "Kinga Klimczak" + + python buteleczki_wzor6.py --imie_zenskie "Kasia" --imie_meskie "Tomek" \ + --data "30.04.2026" --zyczenia "Na zdrowie!" --klient "Kinga Klimczak" +""" + +import argparse +import os +import photoshop.api as ps + + +# --- Ścieżki --- +PROJEKT_DIR = os.path.join( + r"d:\pomysloweprezenty.pl\projekty\ślub - buteleczki", + "Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 6", +) +SZABLON_PATH = os.path.join(PROJEKT_DIR, "szablon 370x300.psd") +GOTOWE_DIR = os.path.join(PROJEKT_DIR, "_gotowe") + +SMART_OBJECT_LAYER = "Projekt" + + +def open_smart_object(app): + """Otwiera zawartość aktywnej warstwy Smart Object do edycji.""" + desc = ps.ActionDescriptor() + ref = ps.ActionReference() + ref.putEnumerated( + app.stringIDToTypeID("layer"), + app.stringIDToTypeID("ordinal"), + app.stringIDToTypeID("targetEnum"), + ) + desc.putReference(app.stringIDToTypeID("null"), ref) + app.executeAction(app.stringIDToTypeID("placedLayerEditContents"), desc) + + +def change_text_preserve_position(layer, new_text): + """Zmienia tekst warstwy z zachowaniem jej oryginalnej pozycji. + + Dla tekstu wyśrodkowanego (justification=2) lub prawostronnego (justification=3) + Photoshop sam utrzymuje punkt zakotwiczenia — korekta nie jest potrzebna. + Dla tekstu lewostronnego (justification=1) korygujemy pozycję lewego górnego rogu. + """ + justification = layer.textItem.justification + bounds_before = [float(b) for b in layer.bounds] + layer.textItem.contents = new_text + + # Korekta tylko dla tekstu lewostronnego + if justification == 1: + bounds_after = [float(b) for b in layer.bounds] + dx = bounds_before[0] - bounds_after[0] + dy = bounds_before[1] - bounds_after[1] + if dx != 0 or dy != 0: + layer.translate(dx, dy) + + +def generate(imie_zenskie, imie_meskie, data, klient, zyczenia=None): + """Generuje PSD z podmienionymi danymi.""" + os.makedirs(GOTOWE_DIR, exist_ok=True) + output_path = os.path.join(GOTOWE_DIR, f"{klient}.psd") + + app = ps.Application() + doc = app.open(SZABLON_PATH) + print(f"Otwarto szablon: {doc.name}") + + # Znajdź i otwórz Smart Object + for layer in doc.layers: + if layer.name == SMART_OBJECT_LAYER: + app.activeDocument.activeLayer = layer + break + else: + doc.close(ps.SaveOptions.DoNotSaveChanges) + raise RuntimeError(f"Nie znaleziono warstwy '{SMART_OBJECT_LAYER}'") + + open_smart_object(app) + so_doc = app.activeDocument + + # Nawiguj do grupy Dane + dane_group = so_doc.layerSets["Dane"] + + # Złóż imiona w format "Imię1 & Imię2" + imiona_text = f"{imie_zenskie} & {imie_meskie}" + + replacements = { + "imiona": imiona_text, + "data": data, + } + if zyczenia is not None: + replacements["zyczenia"] = zyczenia + + for layer_name, new_text in replacements.items(): + layer = dane_group.artLayers[layer_name] + old_text = layer.textItem.contents + change_text_preserve_position(layer, new_text) + print(f' {layer_name}: "{old_text}" -> "{new_text}"') + + # Zapisz Smart Object + so_doc.save() + so_doc.close() + print("Smart Object zapisany") + + # Zapisz jako nowy PSD + psd_opts = ps.PhotoshopSaveOptions() + app.activeDocument.saveAs(output_path, psd_opts, True) + print(f"Zapisano: {output_path}") + + # Zamknij bez zapisu oryginału + app.activeDocument.close(ps.SaveOptions.DoNotSaveChanges) + print("Gotowe!") + return output_path + + +def main(): + parser = argparse.ArgumentParser( + description="Generator PSD - buteleczki weselne Wzór 6" + ) + parser.add_argument("--imie_zenskie", required=True, help="Imię żeńskie (np. Kasia)") + parser.add_argument("--imie_meskie", required=True, help="Imię męskie (np. Tomek)") + parser.add_argument("--data", required=True, help="Data uroczystości (np. 30.04.2026)") + parser.add_argument("--klient", required=True, help="Imię i nazwisko klienta (nazwa pliku wyjściowego)") + parser.add_argument("--zyczenia", default=None, help="Tekst życzeń (domyślnie: oryginalny z szablonu)") + + args = parser.parse_args() + generate( + imie_zenskie=args.imie_zenskie, + imie_meskie=args.imie_meskie, + data=args.data, + klient=args.klient, + zyczenia=args.zyczenia, + ) + + +if __name__ == "__main__": + main() diff --git a/tools/generowanie/buteleczki_wzor8.py b/tools/generowanie/buteleczki_wzor8.py new file mode 100644 index 0000000..8a3d548 --- /dev/null +++ b/tools/generowanie/buteleczki_wzor8.py @@ -0,0 +1,156 @@ +""" +Generator PSD — Podziękowanie dla gości weselnych (buteleczka, Wzór 8). + +Otwiera szablon PSD, podmienia inicjały, datę i opcjonalnie życzenia +w Smart Object "Projekt" z zachowaniem pozycji warstw, zapisuje jako nowy PSD. + +Wymaga: uruchomiony Adobe Photoshop, pakiet photoshop-python-api. + +Użycie: + python buteleczki_wzor8.py --inicjal_zenski "K" --inicjal_meski "Ł" \ + --data "30.04.2026" --klient "Kinga Klimczak" + + python buteleczki_wzor8.py --inicjal_zenski "K" --inicjal_meski "Ł" \ + --data "30.04.2026" --zyczenia "Na zdrowie!" --klient "Kinga Klimczak" +""" + +import argparse +import os +import photoshop.api as ps + + +# --- Ścieżki --- +PROJEKT_DIR = os.path.join( + r"d:\pomysloweprezenty.pl\projekty\ślub - buteleczki", + "Podziękowanie dla gości weselnych buteleczka z nadrukiem UV - Wzór 8", +) +SZABLON_PATH = os.path.join(PROJEKT_DIR, "szablon 370x300.psd") +GOTOWE_DIR = os.path.join(PROJEKT_DIR, "_gotowe") + +SMART_OBJECT_LAYER = "Projekt" + + +def open_smart_object(app): + """Otwiera zawartość aktywnej warstwy Smart Object do edycji.""" + desc = ps.ActionDescriptor() + ref = ps.ActionReference() + ref.putEnumerated( + app.stringIDToTypeID("layer"), + app.stringIDToTypeID("ordinal"), + app.stringIDToTypeID("targetEnum"), + ) + desc.putReference(app.stringIDToTypeID("null"), ref) + app.executeAction(app.stringIDToTypeID("placedLayerEditContents"), desc) + + +def change_text_preserve_position(layer, new_text): + """Zmienia tekst warstwy z zachowaniem jej oryginalnej pozycji.""" + bounds_before = [float(b) for b in layer.bounds] + layer.textItem.contents = new_text + bounds_after = [float(b) for b in layer.bounds] + + dx = bounds_before[0] - bounds_after[0] + dy = bounds_before[1] - bounds_after[1] + + if dx != 0 or dy != 0: + layer.translate(dx, dy) + + +def generate(inicjal_zenski, inicjal_meski, data, klient, zyczenia=None): + """Generuje PSD z podmienionymi danymi.""" + os.makedirs(GOTOWE_DIR, exist_ok=True) + output_path = os.path.join(GOTOWE_DIR, f"{klient}.psd") + + app = ps.Application() + doc = app.open(SZABLON_PATH) + print(f"Otwarto szablon: {doc.name}") + + # Znajdź i otwórz Smart Object + for layer in doc.layers: + if layer.name == SMART_OBJECT_LAYER: + app.activeDocument.activeLayer = layer + break + else: + doc.close(ps.SaveOptions.DoNotSaveChanges) + raise RuntimeError(f"Nie znaleziono warstwy '{SMART_OBJECT_LAYER}'") + + open_smart_object(app) + so_doc = app.activeDocument + + # Nawiguj do warstw tekstowych + tekst_group = so_doc.layerSets["Tekst"] + + # Data i życzenia — bezpośrednio w grupie Tekst + replacements = { + "data": data, + "inicjal_zenski": inicjal_zenski, + "inicjal_meski": inicjal_meski, + } + if zyczenia is not None: + replacements["zyczenia"] = zyczenia + + # Podmiana daty i życzeń + for layer_name in ["data", "zyczenia"]: + if layer_name not in replacements: + continue + layer = tekst_group.artLayers[layer_name] + old_text = layer.textItem.contents + change_text_preserve_position(layer, replacements[layer_name]) + print(f' {layer_name}: "{old_text}" -> "{replacements[layer_name]}"') + + # Podmiana inicjałów — Tekst → → Inicjaly + inicjaly_group = None + for ls in tekst_group.layerSets: + try: + inicjaly_group = ls.layerSets["Inicjaly"] + break + except Exception: + continue + + if inicjaly_group is None: + print(" UWAGA: Nie znaleziono grupy Inicjaly!") + else: + for layer_name in ["inicjal_zenski", "inicjal_meski"]: + layer = inicjaly_group.artLayers[layer_name] + old_text = layer.textItem.contents + change_text_preserve_position(layer, replacements[layer_name]) + print(f' {layer_name}: "{old_text}" -> "{replacements[layer_name]}"') + + # Zapisz Smart Object + so_doc.save() + so_doc.close() + print("Smart Object zapisany") + + # Zapisz jako nowy PSD + psd_opts = ps.PhotoshopSaveOptions() + app.activeDocument.saveAs(output_path, psd_opts, True) + print(f"Zapisano: {output_path}") + + # Zamknij bez zapisu oryginału + app.activeDocument.close(ps.SaveOptions.DoNotSaveChanges) + print("Gotowe!") + return output_path + + +def main(): + parser = argparse.ArgumentParser( + description="Generator PSD - buteleczki weselne Wzór 8" + ) + parser.add_argument("--inicjal_zenski", required=True, help="Inicjał żeński (np. K)") + parser.add_argument("--inicjal_meski", required=True, help="Inicjał męski (np. Ł)") + parser.add_argument("--data", required=True, help="Data uroczystości (np. 30.04.2026)") + parser.add_argument("--klient", required=True, help="Imię i nazwisko klienta (nazwa pliku wyjściowego)") + parser.add_argument("--zyczenia", default=None, help="Tekst życzeń (domyślnie: oryginalny z szablonu)") + + args = parser.parse_args() + generate( + inicjal_zenski=args.inicjal_zenski, + inicjal_meski=args.inicjal_meski, + data=args.data, + klient=args.klient, + zyczenia=args.zyczenia, + ) + + +if __name__ == "__main__": + main()