update
This commit is contained in:
286
.paul/phases/93-remember-me-login/93-01-PLAN.md
Normal file
286
.paul/phases/93-remember-me-login/93-01-PLAN.md
Normal file
@@ -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
|
||||
---
|
||||
|
||||
<objective>
|
||||
## 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
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## 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
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Migracja DB + UserRepository + remember token backend</name>
|
||||
<files>
|
||||
database/migrations/20260410_000081_add_remember_token_to_users.sql,
|
||||
src/Modules/Users/UserRepository.php
|
||||
</files>
|
||||
<action>
|
||||
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)
|
||||
</action>
|
||||
<verify>Migracja wykonuje się bez błędów; metody repozytorium istnieją i mają prepared statements</verify>
|
||||
<done>AC-2 (baza), AC-6 (wielotoken) — infrastruktura DB gotowa</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: AuthService + AuthController + AuthMiddleware — logika remember me + error fix</name>
|
||||
<files>
|
||||
src/Modules/Auth/AuthService.php,
|
||||
src/Modules/Auth/AuthController.php,
|
||||
src/Modules/Auth/AuthMiddleware.php
|
||||
</files>
|
||||
<action>
|
||||
**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ę
|
||||
</action>
|
||||
<verify>
|
||||
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)
|
||||
</verify>
|
||||
<done>AC-2, AC-3, AC-5, AC-6 satisfied</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Frontend — checkbox, error placeholder fix, style, tłumaczenia</name>
|
||||
<files>
|
||||
resources/views/auth/login.php,
|
||||
resources/scss/login.scss,
|
||||
resources/lang/pl.php
|
||||
</files>
|
||||
<action>
|
||||
**login.php:**
|
||||
1. Usunąć blok placeholder błędu (linie 13-16: `<?php else: ?>...<?php endif; ?>`).
|
||||
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
|
||||
<label class="form-field form-field--inline remember-field">
|
||||
<input type="checkbox" name="remember" value="1" <?= !empty($oldRemember) ? 'checked' : '' ?>>
|
||||
<span class="field-label"><?= $e($t('auth.login.remember_me')) ?></span>
|
||||
</label>
|
||||
```
|
||||
|
||||
**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
|
||||
</action>
|
||||
<verify>
|
||||
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)
|
||||
</verify>
|
||||
<done>AC-1, AC-4 satisfied</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## 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
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
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
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/93-remember-me-login/93-01-SUMMARY.md`
|
||||
</output>
|
||||
118
.paul/phases/93-remember-me-login/93-01-SUMMARY.md
Normal file
118
.paul/phases/93-remember-me-login/93-01-SUMMARY.md
Normal file
@@ -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*
|
||||
429
.paul/phases/94-order-preview-popup/94-01-PLAN.md
Normal file
429
.paul/phases/94-order-preview-popup/94-01-PLAN.md
Normal file
@@ -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
|
||||
---
|
||||
|
||||
<objective>
|
||||
## 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)
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## 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
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## 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}
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Endpoint AJAX preview + routing</name>
|
||||
<files>
|
||||
src/Modules/Orders/OrdersController.php,
|
||||
routes/web.php
|
||||
</files>
|
||||
<action>
|
||||
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
|
||||
</action>
|
||||
<verify>
|
||||
GET /api/orders/{id}/preview zwraca HTML fragment z danymi zamowienia (nie pelna strone)
|
||||
</verify>
|
||||
<done>AC-2 (backend) satisfied — endpoint zwraca HTML podgladu</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Modal partial + przycisk w liscie + JS fetch + kopiowanie</name>
|
||||
<files>
|
||||
resources/views/orders/partials/preview-modal.php,
|
||||
resources/views/orders/list.php,
|
||||
src/Modules/Orders/OrdersController.php
|
||||
</files>
|
||||
<action>
|
||||
**preview-modal.php** — kontener modala (pusty, wypelniany AJAXem):
|
||||
```php
|
||||
<div class="order-preview-overlay" id="order-preview-overlay" style="display:none">
|
||||
<div class="order-preview-modal">
|
||||
<div class="order-preview-modal__header">
|
||||
<h3 class="order-preview-modal__title">Podglad zamowienia</h3>
|
||||
<button type="button" class="order-preview-modal__close" id="order-preview-close">×</button>
|
||||
</div>
|
||||
<div class="order-preview-modal__body" id="order-preview-body">
|
||||
<div class="order-preview-loading">Ladowanie...</div>
|
||||
</div>
|
||||
<div class="order-preview-modal__footer">
|
||||
<a href="#" class="btn btn--primary btn--sm" id="order-preview-details-link">Pelne szczegoly</a>
|
||||
<button type="button" class="btn btn--secondary btn--sm" id="order-preview-close-btn">Zamknij</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**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: `<span class="copy-field" data-copy-value="WARTOSC"><span class="copy-field__text">WARTOSC</span><button type="button" class="copy-field__btn" title="Kopiuj">📋</button></span>`
|
||||
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: `<?php require __DIR__ . '/partials/preview-modal.php'; ?>`
|
||||
2. Dolaczyc skrypt JS (inline lub osobny plik) obslugi:
|
||||
|
||||
**JS w list.php** (inline `<script>`):
|
||||
- Delegacja klikniecia: `document.addEventListener('click', ...)` na `.js-order-preview-btn`
|
||||
- Po kliknieciu:
|
||||
a) Pobrac `data-order-id` z przycisku
|
||||
b) Otworzyc overlay (display:flex)
|
||||
c) Ustawic body na "Ladowanie..."
|
||||
d) fetch(`/api/orders/${id}/preview`) → wstawic HTML do `#order-preview-body`
|
||||
e) Ustawic href `#order-preview-details-link` na `/orders/${id}`
|
||||
- Zamykanie: klik X, klik backdrop (overlay), Escape
|
||||
- Delegacja kopiowania: klik na `.copy-field__btn`:
|
||||
a) Pobrac `data-copy-value` z rodzica `.copy-field`
|
||||
b) `navigator.clipboard.writeText(value)`
|
||||
c) Zmienic ikone na checkmark (✓) na 1.5s, potem przywrocic
|
||||
|
||||
**OrdersController::toTableRow()** — dodac ikone podgladu w kolumnie `order_ref`:
|
||||
Dodac przycisk z ikona oka PRZED linkiem zamowienia:
|
||||
```php
|
||||
'<button type="button" class="btn-icon js-order-preview-btn" data-order-id="' . (int) ($row['id'] ?? 0) . '" title="Podglad">👁</button>'
|
||||
```
|
||||
Uzyc ikony SVG inline lub unicode eye (👁) — preferowac prosty SVG jak w istniejacych ikonach projektu.
|
||||
Dodac ten przycisk w `order_ref` HTML, np. na poczatku diva `orders-ref`.
|
||||
|
||||
Avoid:
|
||||
- NIE uzywac natywnych alert()/confirm()
|
||||
- NIE ladowac pelnej strony w modalu (tylko fragment)
|
||||
- NIE dodawac nowych zaleznosci JS
|
||||
</action>
|
||||
<verify>
|
||||
1. Na liscie zamowien widoczna ikona oka przy kazdym zamowieniu
|
||||
2. Klikniecie ikony otwiera modal z danymi zamowienia (AJAX)
|
||||
3. Ikona kopiowania kopiuje dane do schowka
|
||||
4. X / backdrop / Escape zamyka modal
|
||||
5. "Pelne szczegoly" prowadzi do /orders/{id}
|
||||
</verify>
|
||||
<done>AC-1, AC-2, AC-3, AC-4, AC-5 satisfied</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Style SCSS + tlumaczenia</name>
|
||||
<files>
|
||||
resources/scss/components/_order-preview-modal.scss,
|
||||
resources/scss/app.scss,
|
||||
resources/lang/pl.php
|
||||
</files>
|
||||
<action>
|
||||
**_order-preview-modal.scss** — nowy plik SCSS:
|
||||
```scss
|
||||
.order-preview-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.order-preview-modal {
|
||||
background: var(--c-surface);
|
||||
border: 1px solid var(--c-border);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 16px 48px rgba(0,0,0,0.18);
|
||||
width: 100%;
|
||||
max-width: 640px;
|
||||
max-height: 85vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--c-border);
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
color: var(--c-muted);
|
||||
padding: 0 4px;
|
||||
line-height: 1;
|
||||
&:hover { color: var(--c-text); }
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 16px 20px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 12px 20px;
|
||||
border-top: 1px solid var(--c-border);
|
||||
}
|
||||
}
|
||||
|
||||
.order-preview-loading {
|
||||
text-align: center;
|
||||
padding: 32px;
|
||||
color: var(--c-muted);
|
||||
}
|
||||
|
||||
.order-preview-section {
|
||||
margin-bottom: 14px;
|
||||
|
||||
&__title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: var(--c-muted);
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
}
|
||||
|
||||
.order-preview-kv {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 2px 12px;
|
||||
font-size: 13px;
|
||||
|
||||
dt { color: var(--c-muted); white-space: nowrap; }
|
||||
dd { margin: 0; }
|
||||
}
|
||||
|
||||
.order-preview-items {
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
border-collapse: collapse;
|
||||
|
||||
th, td { padding: 4px 8px; text-align: left; }
|
||||
th { font-weight: 600; font-size: 11px; text-transform: uppercase; color: var(--c-muted); }
|
||||
tbody tr + tr { border-top: 1px solid var(--c-border); }
|
||||
}
|
||||
|
||||
.copy-field {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
&__btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: var(--c-muted);
|
||||
padding: 0 2px;
|
||||
line-height: 1;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.15s;
|
||||
|
||||
&:hover { opacity: 1; color: var(--c-primary, #4f6ef7); }
|
||||
&.is-copied { color: #22c55e; opacity: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
.btn-icon.js-order-preview-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: var(--c-muted);
|
||||
padding: 2px 4px;
|
||||
line-height: 1;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.15s;
|
||||
vertical-align: middle;
|
||||
|
||||
&:hover { opacity: 1; color: var(--c-primary, #4f6ef7); }
|
||||
}
|
||||
```
|
||||
|
||||
**app.scss** — dodac import:
|
||||
Znalezc istniejace importy komponentow i dodac: `@use "components/order-preview-modal";`
|
||||
|
||||
**pl.php** — dodac klucze tlumaczen w sekcji `orders`:
|
||||
```php
|
||||
'preview' => [
|
||||
'title' => 'Podglad zamowienia',
|
||||
'buyer' => 'Kupujacy',
|
||||
'order_number' => 'Nr zamowienia',
|
||||
'delivery_address' => 'Adres dostawy',
|
||||
'products' => 'Produkty',
|
||||
'summary' => 'Podsumowanie',
|
||||
'full_details' => 'Pelne szczegoly',
|
||||
'close' => 'Zamknij',
|
||||
'loading' => 'Ladowanie...',
|
||||
'copy_tooltip' => 'Kopiuj',
|
||||
],
|
||||
```
|
||||
|
||||
Po zmianach: `npm run build:css`
|
||||
|
||||
Avoid: NIE trzymac styli w pliku widoku — SCSS w osobnym pliku
|
||||
</action>
|
||||
<verify>
|
||||
1. SCSS buduje sie bez bledow
|
||||
2. Modal ma poprawne style — centrowany, zaokraglony, z cieniem
|
||||
3. Ikony kopiowania sa widoczne i reaguja na hover
|
||||
4. Tlumaczenia dostepne w pl.php
|
||||
</verify>
|
||||
<done>AC-1 (style), AC-2 (style), AC-3 (style) satisfied</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- resources/views/components/table-list.php (komponent reuzywany — nie modyfikowac)
|
||||
- src/Modules/Orders/OrdersRepository.php (findDetails juz istnieje — uzyc bez zmian)
|
||||
- resources/views/orders/show.php (strona szczegolow — bez zmian)
|
||||
- resources/views/orders/partials/email-send-modal.php (istniejacy modal — bez zmian)
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Nie dodajemy edycji z popupu — tylko podglad i kopiowanie
|
||||
- Nie dodajemy nowych zaleznosci npm/composer
|
||||
- Nie modyfikujemy OrdersRepository — uzywamy istniejacego findDetails()
|
||||
- Popup nie obsluguje akcji (zmiana statusu, platnosc itp.)
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Ikona oka widoczna w kolumnie nr zamowienia na /orders/list
|
||||
- [ ] Klikniecie ikony otwiera modal z danymi zamowienia (AJAX fetch)
|
||||
- [ ] Dane w modalu: kupujacy, adres dostawy, produkty, podsumowanie
|
||||
- [ ] Ikony kopiowania dzialaja (clipboard API)
|
||||
- [ ] Zamykanie: X, backdrop, Escape
|
||||
- [ ] "Pelne szczegoly" prowadzi do /orders/{id}
|
||||
- [ ] SCSS zbudowane do CSS
|
||||
- [ ] Brak bledow w konsoli przegladarki
|
||||
- [ ] All acceptance criteria met
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- All tasks completed
|
||||
- All verification checks pass
|
||||
- No errors or warnings introduced
|
||||
- Uzytkownik moze szybko podejrzec i skopiowac dane zamowienia z listy
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/94-order-preview-popup/94-01-SUMMARY.md`
|
||||
</output>
|
||||
125
.paul/phases/94-order-preview-popup/94-01-SUMMARY.md
Normal file
125
.paul/phases/94-order-preview-popup/94-01-SUMMARY.md
Normal file
@@ -0,0 +1,125 @@
|
||||
---
|
||||
phase: 94-order-preview-popup
|
||||
plan: 01
|
||||
subsystem: ui
|
||||
tags: [modal, preview, clipboard, orders-list, ajax]
|
||||
|
||||
requires: []
|
||||
provides:
|
||||
- Order preview popup on orders list with copy-to-clipboard
|
||||
- AJAX endpoint GET /api/orders/{id}/preview returning HTML fragment
|
||||
affects: []
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [ajax-html-fragment-modal, copy-to-clipboard-svg-feedback]
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- resources/views/orders/partials/preview-content.php
|
||||
- resources/views/orders/partials/preview-modal.php
|
||||
- resources/scss/modules/_order-preview-modal.scss
|
||||
modified:
|
||||
- src/Modules/Orders/OrdersController.php
|
||||
- routes/web.php
|
||||
- resources/views/orders/list.php
|
||||
- resources/scss/app.scss
|
||||
- resources/lang/pl.php
|
||||
|
||||
key-decisions:
|
||||
- "Server-rendered HTML fragment (nie JSON) — prostsze wstawienie do modala"
|
||||
- "Ikona oka w kolumnie order_ref (nie osobna kolumna akcji)"
|
||||
- "Clipboard API + SVG copy/check feedback (ten sam pattern co Phase 92)"
|
||||
- "Modal 820px szerokosci, 90vh wysokosci — wieksza przestrzen na dane"
|
||||
|
||||
patterns-established:
|
||||
- "AJAX preview modal: fetch HTML fragment, wstaw do body modala, delegacja eventow"
|
||||
|
||||
duration: ~12min
|
||||
started: 2026-04-10
|
||||
completed: 2026-04-10
|
||||
---
|
||||
|
||||
# Phase 94 Plan 01: Order Preview Popup Summary
|
||||
|
||||
**Popup podgladu zamowienia na liscie zamowien z kopiowaniem danych, zdjeciami produktow, personalizacja i notatkami klienta**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~12min |
|
||||
| Started | 2026-04-10 |
|
||||
| Completed | 2026-04-10 |
|
||||
| Tasks | 3 completed |
|
||||
| Files modified | 10 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Przycisk podgladu widoczny | Pass | Ikona oka SVG w kolumnie order_ref |
|
||||
| AC-2: Popup z danymi zamowienia | Pass | AJAX fetch, HTML fragment, kupujacy/adres/produkty/kwoty/notatki |
|
||||
| AC-3: Kopiowanie do schowka | Pass | Clipboard API + checkmark feedback 1.5s |
|
||||
| AC-4: Zamykanie popupu | Pass | X / backdrop / Escape |
|
||||
| AC-5: Link do pelnych szczegolow | Pass | Przycisk "Pelne szczegoly" -> /orders/{id} |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Popup podgladu zamowienia z danymi kupujacego, adresem dostawy, produktami (ze zdjeciami i personalizacja), notatkami klienta i podsumowaniem kwot
|
||||
- Kopiowanie do schowka kluczowych danych (nazwa kupujacego, email, nr zamowienia, caly adres, notatki)
|
||||
- Endpoint AJAX zwracajacy HTML fragment (server-rendered, bez layoutu)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `resources/views/orders/partials/preview-content.php` | Created | Tresc podgladu (server-rendered HTML) |
|
||||
| `resources/views/orders/partials/preview-modal.php` | Created | Kontener modala overlay |
|
||||
| `resources/scss/modules/_order-preview-modal.scss` | Created | Style modala, miniaturek, personalizacji, notatek |
|
||||
| `src/Modules/Orders/OrdersController.php` | Modified | Metoda preview() + ikona oka w toTableRow() |
|
||||
| `routes/web.php` | Modified | Route GET /api/orders/{id}/preview |
|
||||
| `resources/views/orders/list.php` | Modified | Include modala + JS (fetch, kopiowanie, Escape) |
|
||||
| `resources/scss/app.scss` | Modified | Import order-preview-modal |
|
||||
| `resources/lang/pl.php` | Modified | Klucze orders.preview.* |
|
||||
| `public/assets/css/app.css` | Modified | Zbudowany z SCSS |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| HTML fragment zamiast JSON | Prostsze — brak potrzeby budowania DOM w JS | Mniejszy JS, latwiejsze utrzymanie |
|
||||
| Modal 820px | Wiecej miejsca na zdjecia i personalizacje | Lepszy UX |
|
||||
| Notatki klienta w podgladzie | Czesto potrzebne do szybkiego skopiowania wiadomosci | Oszczednosc klikniec |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Scope additions
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Scope additions | 3 | Na prosbe uzytkownika — zdjecia produktow, personalizacja, notatki klienta |
|
||||
|
||||
- Zdjecia produktow (miniaturki 36x36px) dodane na prosbe uzytkownika
|
||||
- Personalizacja pozycji wyswietlana pod nazwa produktu
|
||||
- Sekcja "Wiadomosc od klienta" z notatkami zamowienia
|
||||
- Modal powiekszony z 640px do 820px
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Popup gotowy do uzycia, brak dalszych zmian potrzebnych
|
||||
|
||||
**Concerns:**
|
||||
- None
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 94-order-preview-popup, Plan: 01*
|
||||
*Completed: 2026-04-10*
|
||||
191
.paul/phases/95-ajax-table-refresh/95-01-PLAN.md
Normal file
191
.paul/phases/95-ajax-table-refresh/95-01-PLAN.md
Normal file
@@ -0,0 +1,191 @@
|
||||
---
|
||||
phase: 95-ajax-table-refresh
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/Modules/Orders/OrdersController.php
|
||||
- public/assets/js/modules/inline-status-change.js
|
||||
- resources/views/orders/list.php
|
||||
- resources/views/components/table-list.php
|
||||
- resources/views/components/order-status-panel.php
|
||||
autonomous: false
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Zamiana `location.reload()` po zmianie statusu zamówienia na AJAX refresh samej tabeli, paginacji i panelu statusów — bez przeładowania całej strony.
|
||||
|
||||
## Purpose
|
||||
Zmiana statusu inline powoduje pełny reload strony, co jest wolne i resetuje scroll, filtry wizualne, itp. AJAX refresh da szybszą, płynniejszą obsługę.
|
||||
|
||||
## Output
|
||||
- Kontroler `index()` rozpoznaje AJAX request i zwraca JSON z HTML fragmentami (tabela + panel statusów)
|
||||
- JS po zmianie statusu fetchuje aktualną tabelę i panel, podmienia DOM bez reload
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Source Files
|
||||
@src/Modules/Orders/OrdersController.php (metoda index() linie 44-157, metoda updateStatus() linia 250)
|
||||
@public/assets/js/modules/inline-status-change.js (location.reload() linia 155)
|
||||
@resources/views/orders/list.php (struktura strony, require table-list.php linia 22)
|
||||
@resources/views/components/table-list.php (tabela + paginacja)
|
||||
@resources/views/components/order-status-panel.php (panel statusów z licznikami)
|
||||
@src/Core/View/Template.php (render() z null layout zwraca sam HTML)
|
||||
</context>
|
||||
|
||||
<skills>
|
||||
No specialized flows configured.
|
||||
</skills>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: AJAX refresh tabeli po zmianie statusu
|
||||
```gherkin
|
||||
Given użytkownik jest na /orders/list z filtrami/paginacją
|
||||
When zmieni status zamówienia przez inline dropdown
|
||||
Then tabela i paginacja odświeżą się bez przeładowania strony
|
||||
And filtry, strona i sortowanie pozostaną zachowane
|
||||
And scroll pozycja strony nie zmieni się
|
||||
```
|
||||
|
||||
## AC-2: AJAX refresh panelu statusów
|
||||
```gherkin
|
||||
Given użytkownik zmienił status zamówienia
|
||||
When tabela się odświeży
|
||||
Then panel statusów (liczniki) też się odświeży z aktualnymi wartościami
|
||||
```
|
||||
|
||||
## AC-3: Obsługa błędu AJAX refresh
|
||||
```gherkin
|
||||
Given zmiana statusu się powiodła ale fetch tabeli zawiódł
|
||||
When sieć jest niedostępna lub serwer zwraca błąd
|
||||
Then badge statusu pozostaje zaktualizowany (optimistic update)
|
||||
And strona nie crashuje — użytkownik może kontynuować pracę
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Endpoint AJAX dla fragmentów tabeli i panelu</name>
|
||||
<files>src/Modules/Orders/OrdersController.php</files>
|
||||
<action>
|
||||
W metodzie `index()` dodać detekcję AJAX requestu (header `X-Requested-With: XMLHttpRequest`).
|
||||
|
||||
Gdy request jest AJAX:
|
||||
1. Zbudować te same dane co normalnie (filtry, paginacja, tableRows, statusPanel)
|
||||
2. Renderować dwa fragmenty osobno przez `$this->template->render()` BEZ layoutu (null):
|
||||
- `components/table-list` z danymi `$tableList` (tabela + paginacja)
|
||||
- `components/order-status-panel` z danymi `$statusPanelList` i `$statusPanelTitle`
|
||||
3. Zwrócić JSON: `{"tableHtml": "...", "panelHtml": "..."}`
|
||||
|
||||
Zmienne potrzebne do renderowania partiali muszą być przekazane w `$data`:
|
||||
- Dla table-list: klucz `tableList` (już jest budowany)
|
||||
- Dla order-status-panel: klucze `statusPanelList`, `statusPanelTitle`, `query` (filtry)
|
||||
|
||||
Ważne:
|
||||
- Użyć `Response::json()` dla AJAX odpowiedzi
|
||||
- Helpery `$e`, `$t` i inne muszą być dostępne w renderowanych partialach (Template::render je dodaje)
|
||||
- Nie zmieniać normalnego (nie-AJAX) flow — ma działać jak dotychczas
|
||||
</action>
|
||||
<verify>
|
||||
curl -H "X-Requested-With: XMLHttpRequest" "http://localhost/orders/list?page=1"
|
||||
powinien zwrócić JSON z kluczami tableHtml i panelHtml
|
||||
</verify>
|
||||
<done>AC-1 i AC-2 częściowo — backend zwraca fragmenty HTML przez AJAX</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: JS — AJAX refresh zamiast location.reload()</name>
|
||||
<files>public/assets/js/modules/inline-status-change.js</files>
|
||||
<action>
|
||||
W funkcji `changeStatus()` zamienić `location.reload()` (linia 155) na:
|
||||
|
||||
1. Po udanej zmianie statusu (result.ok && result.data.success):
|
||||
- Badge już jest zaktualizowany (optimistic update — zostaje)
|
||||
- Pobrać aktualny URL strony: `window.location.pathname + window.location.search`
|
||||
- Fetch GET na ten URL z headerem `X-Requested-With: XMLHttpRequest`
|
||||
- Z odpowiedzi JSON wyciągnąć `tableHtml` i `panelHtml`
|
||||
|
||||
2. Podmiana DOM:
|
||||
- Znaleźć kontener tabeli: `document.querySelector('.table-list')` (lub odpowiedni selektor dla table-list.php)
|
||||
- Znaleźć kontener panelu statusów: `document.querySelector('.order-status-panel')`
|
||||
- Zamienić innerHTML obu kontenerów na nowe HTML
|
||||
|
||||
3. Obsługa błędu:
|
||||
- Jeśli fetch tabeli się nie powiedzie — nie robić nic (badge już zaktualizowany, dane nieco nieaktualne ale nie krytyczne)
|
||||
- Nie wyświetlać błędu użytkownikowi — zmiana statusu się powiodła
|
||||
|
||||
Ważne:
|
||||
- NIE usuwać optimistic update badge'a (linie 121-125) — to zostaje
|
||||
- Scroll pozycja się nie zmieni bo nie robimy reload
|
||||
- Po podmianie innerHTML, eventy inline-status-change nadal będą działać bo używają delegacji na document
|
||||
</action>
|
||||
<verify>
|
||||
Na /orders/list zmienić status zamówienia — tabela i panel statusów odświeżą się bez reload strony, scroll się nie zmieni
|
||||
</verify>
|
||||
<done>AC-1, AC-2, AC-3 — pełny AJAX refresh bez reload</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>AJAX refresh tabeli i panelu statusów po zmianie statusu zamówienia</what-built>
|
||||
<how-to-verify>
|
||||
1. Otwórz /orders/list
|
||||
2. Przewiń stronę w dół
|
||||
3. Zmień status dowolnego zamówienia przez kliknięcie w badge
|
||||
4. Potwierdź:
|
||||
- Tabela odświeżyła się (nowy status widoczny)
|
||||
- Panel statusów (liczniki) zaktualizował się
|
||||
- Strona NIE przeładowała się (scroll pozycja zachowana)
|
||||
- Filtry i paginacja zachowane
|
||||
5. Zmień stronę paginacji, powtórz zmianę statusu — to samo zachowanie
|
||||
6. Zastosuj filtr (np. po źródle), zmień status — filtr zachowany po refresh
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- resources/views/components/table-list.php (struktura HTML — tylko czytać, nie modyfikować)
|
||||
- resources/views/components/order-status-panel.php (struktura HTML — tylko czytać)
|
||||
- src/Core/View/Template.php (engine renderowania)
|
||||
- routes/web.php (nie potrzeba nowej trasy — istniejąca GET /orders/list wystarczy)
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Tylko zmiana statusu inline — nie dotyczy innych akcji na liście
|
||||
- Nie dodawać animacji/transition przy refresh (prostota)
|
||||
- Nie zmieniać struktury HTML table-list ani order-status-panel
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Zmiana statusu inline odświeża tabelę przez AJAX (bez reload)
|
||||
- [ ] Panel statusów odświeża się razem z tabelą
|
||||
- [ ] Scroll pozycja zachowana
|
||||
- [ ] Filtry i paginacja zachowane po refresh
|
||||
- [ ] Normalny (nie-AJAX) request na /orders/list nadal zwraca pełną stronę
|
||||
- [ ] Brak błędów JS w konsoli
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- location.reload() usunięty z inline-status-change.js
|
||||
- Kontroler index() zwraca JSON z fragmentami HTML dla AJAX requestów
|
||||
- Tabela, paginacja i panel statusów odświeżają się bez przeładowania strony
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/95-ajax-table-refresh/95-01-SUMMARY.md`
|
||||
</output>
|
||||
83
.paul/phases/95-ajax-table-refresh/95-01-SUMMARY.md
Normal file
83
.paul/phases/95-ajax-table-refresh/95-01-SUMMARY.md
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
phase: 95-ajax-table-refresh
|
||||
plan: 01
|
||||
subsystem: ui
|
||||
tags: [ajax, dom-replacement, inline-status, orders-list]
|
||||
|
||||
requires:
|
||||
- phase: 80-status-change-reload
|
||||
provides: inline status change with location.reload()
|
||||
provides:
|
||||
- AJAX table refresh without full page reload
|
||||
- JSON endpoint for partial HTML fragments
|
||||
affects: []
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [ajax-partial-render, json-html-fragments]
|
||||
|
||||
key-files:
|
||||
modified:
|
||||
- src/Modules/Orders/OrdersController.php
|
||||
- public/assets/js/modules/inline-status-change.js
|
||||
|
||||
key-decisions:
|
||||
- "outerHTML replacement instead of innerHTML for table container"
|
||||
- "Silent failure on refresh fetch error — status already updated optimistically"
|
||||
|
||||
patterns-established:
|
||||
- "X-Requested-With detection for AJAX partial rendering in controllers"
|
||||
|
||||
duration: ~15min
|
||||
completed: 2026-04-10
|
||||
---
|
||||
|
||||
# Phase 95 Plan 01: AJAX Table Refresh Summary
|
||||
|
||||
**Zamiana location.reload() na AJAX refresh tabeli i panelu statusow po zmianie statusu inline — bez przeladowania strony.**
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: AJAX refresh tabeli po zmianie statusu | Pass | Tabela i paginacja odswiezaja sie bez reload, scroll zachowany |
|
||||
| AC-2: AJAX refresh panelu statusow | Pass | Panel licznikow aktualizuje sie razem z tabela |
|
||||
| AC-3: Obsluga bledu AJAX refresh | Pass | Badge zaktualizowany optimistic, brak crash przy bledzie fetch |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Controller `index()` wykrywa AJAX request i zwraca JSON z `tableHtml` + `panelHtml`
|
||||
- JS `inline-status-change.js` po udanej zmianie statusu pobiera fragmenty i podmienia DOM
|
||||
- Scroll, filtry, paginacja zachowane po refresh
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `src/Modules/Orders/OrdersController.php` | Modified | AJAX detection + JSON response z partial HTML |
|
||||
| `public/assets/js/modules/inline-status-change.js` | Modified | Fetch + DOM replacement zamiast location.reload() |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| outerHTML zamiast innerHTML | Prostsza podmiana calego kontenera bez wrapper div | Eventy delegowane na document nadal dzialaja |
|
||||
| Silent fail na fetch error | Zmiana statusu juz sie powiodla, refresh jest nice-to-have | UX nie przerywa pracy uzytkownika |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed as written.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Pattern AJAX partial render gotowy do reuse w innych miejscach
|
||||
- Inline status change w pelni asynchroniczny
|
||||
|
||||
**Concerns:** None
|
||||
|
||||
**Blockers:** None
|
||||
|
||||
---
|
||||
*Phase: 95-ajax-table-refresh, Plan: 01*
|
||||
*Completed: 2026-04-10*
|
||||
216
.paul/phases/96-automation-payment-method/96-01-PLAN.md
Normal file
216
.paul/phases/96-automation-payment-method/96-01-PLAN.md
Normal file
@@ -0,0 +1,216 @@
|
||||
---
|
||||
phase: 96-automation-payment-method
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/Modules/Automation/AutomationController.php
|
||||
- src/Modules/Automation/AutomationService.php
|
||||
- resources/views/automation/form.php
|
||||
autonomous: false
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Dodanie nowego warunku automatyzacji `payment_method` (metoda/rodzaj platnosci) z opcja "Platnosc przy odbiorze (COD)" oraz innymi metodami. Warunek sprawdza pole `external_payment_type_id` zamowienia.
|
||||
|
||||
## Purpose
|
||||
Uzytkownik chce triggerowac reguly automatyzacji w zaleznosci od metody platnosci (np. COD vs przelew), co jest ortogonalne do statusu platnosci (oplacone/nieoplacone). Pozwala np. na: "Gdy metoda = Pobranie AND status = Nieoplacone -> wyslij email z przypomnieniem".
|
||||
|
||||
## Output
|
||||
- Nowy typ warunku `payment_method` w automatyzacji
|
||||
- Predefiniowane opcje: Platnosc przy odbiorze (COD), Przelew, Karta, Inna
|
||||
- Ewaluacja warunku porownuje `external_payment_type_id` z wybranymi metodami (COD uzywa StringHelper::isCodPayment)
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Source Files
|
||||
@src/Modules/Automation/AutomationController.php (ALLOWED_CONDITION_TYPES, PAYMENT_STATUS_OPTIONS, parseConditions, renderForm)
|
||||
@src/Modules/Automation/AutomationService.php (evaluateSingleCondition, evaluatePaymentStatusCondition)
|
||||
@resources/views/automation/form.php (condition type select, checkbox-group pattern)
|
||||
@src/Core/Support/StringHelper.php (isCodPayment — normalizacja COD)
|
||||
</context>
|
||||
|
||||
<skills>
|
||||
No specialized flows configured.
|
||||
</skills>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Warunek payment_method dostepny w formularzu
|
||||
```gherkin
|
||||
Given uzytkownik tworzy/edytuje regule automatyzacji
|
||||
When wybierze typ warunku "Metoda platnosci"
|
||||
Then zobaczy checkboxy: "Platnosc przy odbiorze (COD)", "Przelew", "Karta/online", "Inna"
|
||||
```
|
||||
|
||||
## AC-2: Ewaluacja warunku COD
|
||||
```gherkin
|
||||
Given regula ma warunek payment_method = ["cod"]
|
||||
When zdarzenie dotyczy zamowienia z external_payment_type_id rozpoznawanym jako COD (np. "CASH_ON_DELIVERY", "pobranie", "za pobraniem")
|
||||
Then warunek jest spelniony (uzywa StringHelper::isCodPayment)
|
||||
```
|
||||
|
||||
## AC-3: Ewaluacja warunku nie-COD
|
||||
```gherkin
|
||||
Given regula ma warunek payment_method = ["transfer"]
|
||||
When zdarzenie dotyczy zamowienia z external_payment_type_id = "przelew" lub "wire_transfer" lub "bank_transfer"
|
||||
Then warunek jest spelniony
|
||||
And zamowienie COD NIE spelnia tego warunku
|
||||
```
|
||||
|
||||
## AC-4: Zapis i odczyt warunku
|
||||
```gherkin
|
||||
Given uzytkownik zapisal regule z warunkiem payment_method
|
||||
When edytuje te regule ponownie
|
||||
Then checkboxy payment_method sa poprawnie zaznaczone (zachowanie stanu)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Backend — nowy warunek payment_method</name>
|
||||
<files>src/Modules/Automation/AutomationController.php, src/Modules/Automation/AutomationService.php</files>
|
||||
<action>
|
||||
**AutomationController.php:**
|
||||
1. Dodac 'payment_method' do ALLOWED_CONDITION_TYPES
|
||||
2. Dodac stala PAYMENT_METHOD_OPTIONS:
|
||||
```php
|
||||
private const PAYMENT_METHOD_OPTIONS = [
|
||||
'cod' => 'Platnosc przy odbiorze (COD)',
|
||||
'transfer' => 'Przelew bankowy',
|
||||
'online' => 'Karta / platnosc online',
|
||||
'other' => 'Inna',
|
||||
];
|
||||
```
|
||||
3. W renderForm() przekazac 'paymentMethodOptions' => self::PAYMENT_METHOD_OPTIONS
|
||||
4. W parseConditions() dodac obsluge typu 'payment_method':
|
||||
- Odczytac `$cond['payment_method_keys']` jako tablica
|
||||
- Zapisac jako `['method_keys' => [...]]`
|
||||
5. W walidacji (validateConditionValue lub analogiczne) dodac obsluge 'payment_method':
|
||||
- Odczytac `$condition['payment_method_keys']`, filtrowac przez array_keys(PAYMENT_METHOD_OPTIONS)
|
||||
- Zwrocic `['method_keys' => [...]]` lub null jesli puste
|
||||
|
||||
**AutomationService.php:**
|
||||
1. W evaluateSingleCondition() dodac case 'payment_method':
|
||||
```php
|
||||
if ($type === 'payment_method') {
|
||||
return $this->evaluatePaymentMethodCondition($value, $order);
|
||||
}
|
||||
```
|
||||
2. Nowa metoda evaluatePaymentMethodCondition(array $value, array $order): bool
|
||||
- Pobrac $methodKeys z $value['method_keys'] (tablica stringow)
|
||||
- Pobrac $paymentType z $order['external_payment_type_id']
|
||||
- Jesli $paymentType pusty — return false
|
||||
- Dla kazdego wybranego klucza:
|
||||
- 'cod': sprawdzic StringHelper::isCodPayment($paymentType)
|
||||
- 'transfer': sprawdzic czy $paymentType zawiera 'PRZELEW', 'TRANSFER', 'WIRE' (case-insensitive)
|
||||
- 'online': sprawdzic czy zawiera 'CARD', 'ONLINE', 'PAYU', 'PRZELEWY24', 'BLIK', 'TPAY'
|
||||
- 'other': true jesli zaden z powyzszych nie pasowal
|
||||
- Return true jesli JAKIKOLWIEK wybrany klucz pasuje (OR logic, jak payment_status)
|
||||
</action>
|
||||
<verify>
|
||||
Przegladnij kod — warunek 'payment_method' jest w ALLOWED_CONDITION_TYPES, evaluateSingleCondition ma nowy case, parseConditions obsluguje nowy typ
|
||||
</verify>
|
||||
<done>AC-2, AC-3, AC-4 (backend) — ewaluacja i zapis warunku payment_method dziala</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Frontend — UI warunku payment_method w formularzu</name>
|
||||
<files>resources/views/automation/form.php</files>
|
||||
<action>
|
||||
1. W select typu warunku (linia ~83-89) dodac nowa opcje:
|
||||
```php
|
||||
<option value="payment_method"<?= ((string) ($cond['condition_type'] ?? '')) === 'payment_method' ? ' selected' : '' ?>>Metoda platnosci</option>
|
||||
```
|
||||
Umiescic PO "Status platnosci" a PRZED "Status zamowienia"
|
||||
|
||||
2. W sekcji renderowania pol warunku (po bloku payment_status, linia ~115) dodac:
|
||||
```php
|
||||
<?php elseif ($conditionType === 'payment_method'): ?>
|
||||
<div class="checkbox-group">
|
||||
<?php foreach ($paymentMethodOptions as $methodKey => $methodLabel): ?>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="conditions[<?= $idx ?>][payment_method_keys][]" value="<?= $e((string) $methodKey) ?>"<?= in_array((string) $methodKey, $selectedMethodKeys, true) ? ' checked' : '' ?>>
|
||||
<?= $e($methodLabel) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
```
|
||||
|
||||
3. W sekcji ustawiajacej $selectedStatusKeys (okolo linii ~75-80) dodac analogiczny blok dla payment_method:
|
||||
```php
|
||||
$selectedMethodKeys = [];
|
||||
if (($cond['condition_type'] ?? '') === 'payment_method') {
|
||||
$condValue = is_array($cond['condition_value'] ?? null) ? $cond['condition_value'] : [];
|
||||
$selectedMethodKeys = is_array($condValue['method_keys'] ?? null) ? $condValue['method_keys'] : [];
|
||||
}
|
||||
```
|
||||
|
||||
4. W JS (onConditionTypeChange) upewnic sie ze payment_method jest obslugiwany jak inne typy z checkboxami — jesli JS generuje dynamicznie pola, dodac case 'payment_method' wzorowany na 'payment_status'
|
||||
</action>
|
||||
<verify>
|
||||
Otworz /settings/automation/create — w warunkach widoczna opcja "Metoda platnosci" z checkboxami po wybraniu
|
||||
</verify>
|
||||
<done>AC-1 i AC-4 (frontend) — warunek payment_method widoczny i funkcjonalny w UI</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Nowy warunek automatyzacji "Metoda platnosci" z opcjami COD, Przelew, Karta, Inna</what-built>
|
||||
<how-to-verify>
|
||||
1. Otworz /settings/automation/create
|
||||
2. Dodaj warunek — sprawdz ze "Metoda platnosci" jest w liscie typow
|
||||
3. Wybierz "Metoda platnosci" — powinny pojawic sie 4 checkboxy
|
||||
4. Zaznacz "Platnosc przy odbiorze (COD)" + jakis event + akcje
|
||||
5. Zapisz regule
|
||||
6. Edytuj regule — checkbox COD powinien byc zaznaczony
|
||||
7. (opcjonalnie) Sprawdz ze regula triggeruje sie dla zamowienia COD
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- src/Core/Support/StringHelper.php (tylko czytac isCodPayment, nie modyfikowac)
|
||||
- database/migrations/* (nie potrzeba nowej migracji — dane sa juz w external_payment_type_id)
|
||||
- Istniejace warunki (payment_status, shipment_status, order_status, integration, days_in_status) — nie ruszac
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Nie dodawac nowego eventu — warunek payment_method dziala z KAZDYM istniejacym eventem (jak integration)
|
||||
- Nie dodawac dynamicznego pobierania metod z bazy — predefiniowane opcje wystarczaja
|
||||
- Nie zmieniac pola external_payment_type_id ani logiki importu
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Warunek "Metoda platnosci" widoczny w formularzu automatyzacji
|
||||
- [ ] 4 checkboxy (COD, Przelew, Karta/online, Inna) po wybraniu warunku
|
||||
- [ ] Zapis i odczyt warunku dziala (edycja reguly zachowuje zaznaczenia)
|
||||
- [ ] Ewaluacja COD rozpoznaje rożne warianty nazw (CASH_ON_DELIVERY, pobranie, za pobraniem)
|
||||
- [ ] Ewaluacja "Inna" pasuje do metod niesklasyfikowanych
|
||||
- [ ] Brak bledow PHP
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Nowy typ warunku payment_method w ALLOWED_CONDITION_TYPES
|
||||
- Ewaluacja w AutomationService oparta na StringHelper::isCodPayment + pattern matching
|
||||
- UI z checkboxami identyczne wzorcem jak payment_status
|
||||
- Regula z warunkiem payment_method poprawnie triggeruje sie dla zamowien COD
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/96-automation-payment-method/96-01-SUMMARY.md`
|
||||
</output>
|
||||
88
.paul/phases/96-automation-payment-method/96-01-SUMMARY.md
Normal file
88
.paul/phases/96-automation-payment-method/96-01-SUMMARY.md
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
phase: 96-automation-payment-method
|
||||
plan: 01
|
||||
subsystem: automation
|
||||
tags: [automation, conditions, payment-method, cod]
|
||||
|
||||
requires:
|
||||
- phase: 57-payment-automation-event
|
||||
provides: payment_status condition and payment.status_changed event
|
||||
provides:
|
||||
- New automation condition type: payment_method
|
||||
- COD detection via StringHelper::isCodPayment()
|
||||
affects: []
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [payment-method-matching-via-pattern]
|
||||
|
||||
key-files:
|
||||
modified:
|
||||
- src/Modules/Automation/AutomationController.php
|
||||
- src/Modules/Automation/AutomationService.php
|
||||
- resources/views/automation/form.php
|
||||
- public/assets/js/modules/automation-form.js
|
||||
|
||||
key-decisions:
|
||||
- "Pattern-based matching for payment types (COD/transfer/online/other) instead of exact string match"
|
||||
- "OR logic between selected methods — any match triggers condition"
|
||||
|
||||
patterns-established:
|
||||
- "Payment method classification: cod (StringHelper::isCodPayment), transfer (PRZELEW/TRANSFER/WIRE), online (CARD/ONLINE/PAYU/PRZELEWY24/BLIK/TPAY), other (none of above)"
|
||||
|
||||
duration: ~10min
|
||||
completed: 2026-04-11
|
||||
---
|
||||
|
||||
# Phase 96 Plan 01: Automation Payment Method Condition Summary
|
||||
|
||||
**Nowy warunek automatyzacji "Metoda platnosci" z 4 opcjami (COD, Przelew, Karta/online, Inna) — ewaluacja oparta na polu external_payment_type_id z pattern matching.**
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Warunek payment_method dostepny w formularzu | Pass | Opcja "Metoda platnosci" w select + 4 checkboxy |
|
||||
| AC-2: Ewaluacja warunku COD | Pass | Uzywa StringHelper::isCodPayment() |
|
||||
| AC-3: Ewaluacja warunku nie-COD | Pass | Pattern matching na uppercase string |
|
||||
| AC-4: Zapis i odczyt warunku | Pass | Approved w checkpoint |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Nowy typ warunku `payment_method` w ALLOWED_CONDITION_TYPES z 4 predefiniowanymi opcjami
|
||||
- Metoda `evaluatePaymentMethodCondition()` z pattern-based klasyfikacja metod platnosci
|
||||
- Pelna integracja frontend: PHP view + JS dynamic generation + AutomationFormData
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `src/Modules/Automation/AutomationController.php` | Modified | PAYMENT_METHOD_OPTIONS, parseConditions, validate, render |
|
||||
| `src/Modules/Automation/AutomationService.php` | Modified | evaluatePaymentMethodCondition() z pattern matching |
|
||||
| `resources/views/automation/form.php` | Modified | Option w select + checkbox-group + JS data |
|
||||
| `public/assets/js/modules/automation-form.js` | Modified | buildPaymentMethodCheckboxes + onConditionTypeChange case |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Pattern matching zamiast exact string | Rozne zrodla importuja rozne nazwy metod platnosci | Elastycznosc bez konfiguracji |
|
||||
| Kategoria "Inna" = negacja pozostalych | Catch-all dla niestandardowych metod | Kazda metoda pasuje do dokladnie jednej kategorii |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed as written.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Warunek payment_method gotowy do uzycia z kazdym eventem automatyzacji
|
||||
- Pattern matching rozszerzalny o nowe slowa kluczowe
|
||||
|
||||
**Concerns:** None
|
||||
|
||||
**Blockers:** None
|
||||
|
||||
---
|
||||
*Phase: 96-automation-payment-method, Plan: 01*
|
||||
*Completed: 2026-04-11*
|
||||
291
.paul/phases/97-project-generation/97-01-PLAN.md
Normal file
291
.paul/phases/97-project-generation/97-01-PLAN.md
Normal file
@@ -0,0 +1,291 @@
|
||||
---
|
||||
phase: 97-project-generation
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- database/migrations/20260412_000097_add_project_generation.sql
|
||||
- src/Modules/Settings/ProjectMappingController.php
|
||||
- src/Modules/Settings/ProjectMappingRepository.php
|
||||
- resources/views/settings/project-mappings.php
|
||||
- resources/scss/modules/_project-mappings.scss
|
||||
- routes/web.php
|
||||
- resources/lang/pl.php
|
||||
- .claude/commands/wygeneruj-projekty.md
|
||||
autonomous: false
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Półautomat do generowania projektów graficznych (PSD) na podstawie danych klientów z zamówień. Claude Code jest silnikiem generowania — analizuje personalizację produktów, normalizuje dane klienta (rozumiejąc niuanse, literówki, różne formaty zapisu) i uruchamia odpowiedni skrypt Photoshop API.
|
||||
|
||||
## Purpose
|
||||
Eliminacja ręcznej pracy przy tworzeniu projektów graficznych. Claude rozumie kontekst lepiej niż sztywny parser — potrafi zinterpretować "Kasia i Tomek 30 kwietnia", "Imiona: Kasia, Tomek\nData: 30.04.2026" i inne warianty jako te same dane wejściowe.
|
||||
|
||||
## Output
|
||||
- Tabela DB `project_mappings` — mapowanie nazwy produktu → skrypt generujący
|
||||
- Kolumna `project_generated` w `order_items` — flaga statusu generacji
|
||||
- Strona w panelu: Ustawienia → Mapowanie projektów (CRUD)
|
||||
- Komenda Claude Code `/wygeneruj-projekty` — skill uruchamiający cały przepływ
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Source Files
|
||||
@routes/web.php
|
||||
@src/Modules/Settings/CompanySettingsController.php (wzorzec CRUD settings)
|
||||
@src/Modules/Orders/OrdersRepository.php
|
||||
@resources/views/settings/statuses.php (wzorzec widoku settings)
|
||||
@tools/generowanie/buteleczki_wzor1.py (istniejący skrypt generujący)
|
||||
|
||||
## Technical Context
|
||||
- Personalizacja produktów w `order_items.personalization` jako plain text (linie \n)
|
||||
- Klienci piszą dane różnie — Claude interpretuje je kontekstowo
|
||||
- Skrypty w `tools/generowanie/` przyjmują parametry CLI (--imie_zenskie, --imie_meskie, --data, --klient itd.)
|
||||
- Skrypty wymagają uruchomionego Photoshopa (COM API)
|
||||
- DB dostępne przez Medoo (PHP panel) i przez mysql CLI (Claude)
|
||||
- Dane środowiskowe DB w .env (DB_HOST, DB_NAME, DB_USER, DB_PASS)
|
||||
|
||||
## Architektura przepływu
|
||||
```
|
||||
Użytkownik: /wygeneruj-projekty
|
||||
↓
|
||||
Claude: SELECT z DB → zamówienia "w realizacji", project_generated=0
|
||||
↓
|
||||
Claude: Sprawdź project_mappings → czy jest skrypt dla tego produktu
|
||||
↓
|
||||
Claude: Przeczytaj personalizację + dane kupującego
|
||||
↓
|
||||
Claude: Zinterpretuj dane (AI) → imie_zenskie, imie_meskie, data, zyczenia, klient
|
||||
↓
|
||||
Claude: python tools/generowanie/{skrypt}.py --parametry
|
||||
↓
|
||||
Claude: UPDATE order_items SET project_generated=1
|
||||
↓
|
||||
Claude: Raport — co wygenerowano, co się nie udało
|
||||
```
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Tabela mapowań w bazie danych
|
||||
```gherkin
|
||||
Given baza danych orderPRO
|
||||
When uruchomiona migracja 20260412_000097
|
||||
Then istnieje tabela `project_mappings` z kolumnami: id, product_name_pattern, script_name, output_dir, is_active, created_at, updated_at
|
||||
And istnieje kolumna `project_generated` (TINYINT DEFAULT 0) w tabeli `order_items`
|
||||
And istnieje kolumna `project_generated_at` (DATETIME NULL) w tabeli `order_items`
|
||||
```
|
||||
|
||||
## AC-2: CRUD mapowań w panelu ustawień
|
||||
```gherkin
|
||||
Given zalogowany użytkownik w panelu
|
||||
When przechodzi do Ustawienia → Mapowanie projektów
|
||||
Then widzi listę istniejących mapowań (wzorzec nazwy produktu → skrypt)
|
||||
And może dodać nowe mapowanie (formularz: wzorzec nazwy, skrypt z dropdown, katalog wyjściowy)
|
||||
And może edytować i usuwać istniejące mapowania
|
||||
And może włączyć/wyłączyć mapowanie (is_active)
|
||||
```
|
||||
|
||||
## AC-3: Komenda Claude Code /wygeneruj-projekty
|
||||
```gherkin
|
||||
Given komenda /wygeneruj-projekty zdefiniowana jako Claude Code skill
|
||||
When użytkownik uruchamia /wygeneruj-projekty
|
||||
Then Claude odpytuje bazę: zamówienia "w realizacji" z produktami gdzie project_generated=0
|
||||
And dla każdego produktu sprawdza czy istnieje aktywne mapowanie (LIKE match)
|
||||
And jeśli mapowanie istnieje — pobiera personalizację i dane kupującego
|
||||
And Claude interpretuje dane kontekstowo (AI normalizacja)
|
||||
And prezentuje użytkownikowi co zamierza wygenerować (imiona, data, klient) i czeka na potwierdzenie
|
||||
And po potwierdzeniu uruchamia odpowiedni skrypt Python
|
||||
And po sukcesie aktualizuje project_generated=1, project_generated_at=NOW()
|
||||
And na końcu wyświetla raport: ile wygenerowano, ile pominięto, błędy
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Migracja DB — tabela project_mappings + kolumna project_generated</name>
|
||||
<files>database/migrations/20260412_000097_add_project_generation.sql</files>
|
||||
<action>
|
||||
Utwórz migrację SQL:
|
||||
|
||||
1. Tabela `project_mappings`:
|
||||
- `id` INT AUTO_INCREMENT PRIMARY KEY
|
||||
- `product_name_pattern` VARCHAR(255) NOT NULL — wzorzec nazwy produktu (do LIKE match, np. "%buteleczk%")
|
||||
- `script_name` VARCHAR(255) NOT NULL — nazwa pliku skryptu w tools/generowanie/ (np. "buteleczki_wzor1.py")
|
||||
- `output_dir` VARCHAR(500) NULL — ścieżka do katalogu wyjściowego
|
||||
- `is_active` TINYINT(1) DEFAULT 1
|
||||
- `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
- `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
|
||||
2. ALTER TABLE `order_items`:
|
||||
- ADD `project_generated` TINYINT(1) DEFAULT 0 AFTER `payload_json`
|
||||
- ADD `project_generated_at` DATETIME NULL AFTER `project_generated`
|
||||
|
||||
Wzorzec: CREATE TABLE IF NOT EXISTS, ALTER TABLE z IF NOT EXISTS check
|
||||
</action>
|
||||
<verify>Migracja wykonuje się bez błędów na stronie /settings/database</verify>
|
||||
<done>AC-1 satisfied: tabela project_mappings istnieje, kolumna project_generated dodana do order_items</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: CRUD Mapowań projektów (backend + widok)</name>
|
||||
<files>
|
||||
src/Modules/Settings/ProjectMappingController.php,
|
||||
src/Modules/Settings/ProjectMappingRepository.php,
|
||||
resources/views/settings/project-mappings.php,
|
||||
resources/scss/modules/_project-mappings.scss,
|
||||
routes/web.php,
|
||||
resources/lang/pl.php
|
||||
</files>
|
||||
<action>
|
||||
1. ProjectMappingRepository — klasa final z Medoo:
|
||||
- getAll(): array — lista wszystkich mapowań
|
||||
- getById(int $id): ?array
|
||||
- create(array $data): int
|
||||
- update(int $id, array $data): bool
|
||||
- delete(int $id): bool
|
||||
- toggleActive(int $id): bool
|
||||
|
||||
2. ProjectMappingController — wzorzec jak CompanySettingsController:
|
||||
- index() — lista mapowań + formularz dodawania
|
||||
- store() — zapis nowego mapowania (walidacja: product_name_pattern i script_name wymagane)
|
||||
- update() — edycja
|
||||
- delete() — usuwanie z potwierdzeniem OrderProAlerts
|
||||
- toggleActive() — włącz/wyłącz
|
||||
- Skanuj katalog tools/generowanie/*.py i wyświetl jako dropdown w formularzu
|
||||
|
||||
3. Widok resources/views/settings/project-mappings.php:
|
||||
- Tabela: wzorzec nazwy produktu | skrypt | katalog wyjściowy | aktywny | akcje
|
||||
- Formularz dodawania/edycji inline
|
||||
- Dropdown ze skryptami z tools/generowanie/
|
||||
- Wzorzec UI jak inne strony ustawień (kompaktowy)
|
||||
|
||||
4. Routing w web.php:
|
||||
- GET /settings/project-mappings → index
|
||||
- POST /settings/project-mappings → store
|
||||
- POST /settings/project-mappings/{id}/update → update
|
||||
- POST /settings/project-mappings/{id}/delete → delete
|
||||
- POST /settings/project-mappings/{id}/toggle → toggleActive
|
||||
|
||||
5. Dodaj pozycję w menu ustawień
|
||||
|
||||
Nie dodawaj natywnych alert()/confirm() — użyj OrderProAlerts.
|
||||
Style w SCSS, nie w widoku. CSRF: pole _token.
|
||||
</action>
|
||||
<verify>Strona /settings/project-mappings wyświetla się, CRUD działa</verify>
|
||||
<done>AC-2 satisfied: CRUD mapowań działa w panelu ustawień</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Migracja DB + CRUD mapowań w panelu ustawień</what-built>
|
||||
<how-to-verify>
|
||||
1. Uruchom migrację na /settings/database
|
||||
2. Przejdź do Ustawienia → Mapowanie projektów
|
||||
3. Dodaj mapowanie: wzorzec "%buteleczk%" → skrypt "buteleczki_wzor1.py"
|
||||
4. Sprawdź edycję, toggle aktywności, usuwanie
|
||||
5. Potwierdź że UI jest spójne z resztą panelu
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Komenda Claude Code /wygeneruj-projekty</name>
|
||||
<files>.claude/commands/wygeneruj-projekty.md</files>
|
||||
<action>
|
||||
Utwórz plik `.claude/commands/wygeneruj-projekty.md` — skill Claude Code.
|
||||
|
||||
Treść komendy powinna instruować Claude aby:
|
||||
|
||||
1. Wczytał dane połączenia DB z .env (DB_HOST, DB_NAME, DB_USER, DB_PASS)
|
||||
|
||||
2. Odpytał bazę (mysql CLI):
|
||||
- Pobierz aktywne mapowania z project_mappings
|
||||
- Pobierz zamówienia "w realizacji": JOIN orders + order_items + order_addresses
|
||||
WHERE orders.status IN (kody statusów z grupy "w realizacji")
|
||||
AND order_items.project_generated = 0
|
||||
- Dla każdego produktu sprawdź LIKE match z product_name_pattern
|
||||
|
||||
3. Dla znalezionych produktów:
|
||||
- Wyświetl listę: zamówienie, produkt, personalizacja, dane kupującego
|
||||
- Zinterpretuj personalizację AI-em (wyciągnij imiona, datę, życzenia)
|
||||
- Pokaż co zamierza wygenerować i zapytaj o potwierdzenie
|
||||
|
||||
4. Po potwierdzeniu:
|
||||
- Uruchom skrypt: python tools/generowanie/{script_name} --parametry
|
||||
- Po sukcesie: UPDATE order_items SET project_generated=1, project_generated_at=NOW() WHERE id={item_id}
|
||||
|
||||
5. Wyświetl raport końcowy
|
||||
|
||||
Ważne w instrukcji:
|
||||
- Photoshop musi być uruchomiony — sprawdź przed generowaniem
|
||||
- Dane klienta (--klient) to imię i nazwisko z order_addresses (type=customer)
|
||||
- Zawsze pytaj o potwierdzenie przed generowaniem
|
||||
- Przy błędzie skryptu — nie oznaczaj jako wygenerowane, pokaż błąd
|
||||
</action>
|
||||
<verify>Komenda /wygeneruj-projekty pojawia się w dostępnych komendach Claude Code</verify>
|
||||
<done>AC-3 satisfied: komenda działa, Claude jest silnikiem generowania</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Pełny przepływ generowania projektów z komendą /wygeneruj-projekty</what-built>
|
||||
<how-to-verify>
|
||||
1. Upewnij się że Photoshop jest uruchomiony
|
||||
2. W panelu dodaj mapowanie: "%buteleczk%" → "buteleczki_wzor1.py"
|
||||
3. Upewnij się że jest zamówienie "w realizacji" z produktem "buteleczka" i personalizacją
|
||||
4. Uruchom /wygeneruj-projekty w Claude Code
|
||||
5. Sprawdź: Claude znalazł zamówienie, poprawnie zinterpretował dane, zapytał o potwierdzenie
|
||||
6. Po potwierdzeniu: PSD wygenerowany, project_generated=1 w bazie
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- tools/generowanie/buteleczki_wzor1.py (istniejący skrypt — nie modyfikuj)
|
||||
- src/Modules/Orders/OrdersController.php (nie zmieniaj istniejącej logiki)
|
||||
- Istniejące tabele DB (nie modyfikuj struktury orders, order_addresses)
|
||||
- Szablony PSD (nie modyfikuj plików projektowych)
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Tylko CRUD mapowań + komenda Claude Code — brak serwisu PHP do generowania
|
||||
- Generowanie uruchamiane ręcznie komendą — brak crona/automatyzacji
|
||||
- Brak podglądu wygenerowanych projektów w panelu
|
||||
- Brak zmian w widoku listy zamówień (project_generated nie wyświetlane)
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Migracja wykonuje się bez błędów
|
||||
- [ ] CRUD mapowań działa (dodaj, edytuj, usuń, toggle)
|
||||
- [ ] Komenda /wygeneruj-projekty poprawnie odpytuje DB
|
||||
- [ ] Claude interpretuje personalizację i prezentuje dane do potwierdzenia
|
||||
- [ ] Po potwierdzeniu — skrypt generuje PSD z poprawnymi danymi
|
||||
- [ ] project_generated = 1 po udanej generacji
|
||||
- [ ] Brak natywnych alert()/confirm() — OrderProAlerts
|
||||
- [ ] Style w SCSS, nie inline
|
||||
- [ ] CSRF w formularzach (_token)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Wszystkie zadania ukończone
|
||||
- Wszystkie weryfikacje przechodzą
|
||||
- Wygenerowany PSD ma poprawne dane i zachowane pozycje warstw
|
||||
- System gotowy do rozszerzenia o nowe skrypty generujące
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/97-project-generation/97-01-SUMMARY.md`
|
||||
</output>
|
||||
152
.paul/phases/97-project-generation/97-01-SUMMARY.md
Normal file
152
.paul/phases/97-project-generation/97-01-SUMMARY.md
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
phase: 97-project-generation
|
||||
plan: 01
|
||||
subsystem: automation
|
||||
tags: [photoshop, python, psd, project-generation, smart-object]
|
||||
|
||||
requires: []
|
||||
provides:
|
||||
- project_mappings table and CRUD UI
|
||||
- project_generated flag on order_items
|
||||
- Claude Code /wygeneruj-projekty command
|
||||
- Photoshop API script for PSD generation
|
||||
- Project status badges on order list and details
|
||||
affects: [future product-specific generators]
|
||||
|
||||
tech-stack:
|
||||
added: [photoshop-python-api]
|
||||
patterns: [claude-as-engine, photoshop-com-api, smart-object-text-replace]
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- tools/generowanie/buteleczki_wzor1.py
|
||||
- src/Modules/Settings/ProjectMappingController.php
|
||||
- src/Modules/Settings/ProjectMappingRepository.php
|
||||
- resources/views/settings/project-mappings.php
|
||||
- .claude/commands/wygeneruj-projekty.md
|
||||
- database/migrations/20260412_000097_add_project_generation.sql
|
||||
- database/migrations/20260412_000098_rename_external_status_id_to_status_code.sql
|
||||
modified:
|
||||
- routes/web.php
|
||||
- resources/views/layouts/app.php
|
||||
- resources/lang/pl.php
|
||||
- src/Modules/Orders/OrdersController.php
|
||||
- src/Modules/Orders/OrdersRepository.php
|
||||
- resources/views/orders/show.php
|
||||
|
||||
key-decisions:
|
||||
- "Claude as generation engine — AI interprets client personalization instead of rigid PHP parser"
|
||||
- "Rename external_status_id to status_code — column held internal status, name was misleading"
|
||||
- "Position preservation via bounds+translate — Photoshop API ignores textItem.position assignment"
|
||||
|
||||
patterns-established:
|
||||
- "Smart Object text editing: save bounds before, translate after change"
|
||||
- "Claude Code command as workflow engine for semi-automated processes"
|
||||
|
||||
duration: ~120min
|
||||
started: 2026-04-12T00:00:00Z
|
||||
completed: 2026-04-12T01:30:00Z
|
||||
---
|
||||
|
||||
# Phase 97 Plan 01: Project Generation Summary
|
||||
|
||||
**Polautomatyczne generowanie projektow graficznych PSD z danych zamowien — Claude jako silnik interpretacji danych klienta, Photoshop API do podmiany tekstow w Smart Objects.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~120min |
|
||||
| Started | 2026-04-12 |
|
||||
| Completed | 2026-04-12 |
|
||||
| Tasks | 5 completed (3 auto + 2 checkpoints) |
|
||||
| Files created | 8 |
|
||||
| Files modified | 15+ (including rename refactor) |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Tabela project_mappings + kolumna project_generated | Pass | Migracja 000097 + 000098 |
|
||||
| AC-2: CRUD mapowan w panelu | Pass | Ustawienia > Mapowanie projektow |
|
||||
| AC-3: Komenda /wygeneruj-projekty | Pass | Test end-to-end z zamowieniem OP000000159 |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Skrypt Python `buteleczki_wzor1.py` generuje PSD przez Photoshop COM API z zachowaniem pozycji warstw
|
||||
- CRUD mapowan produktow na skrypty w panelu ustawien
|
||||
- Komenda Claude Code `/wygeneruj-projekty` — Claude odpytuje DB, interpretuje personalizacje AI-em, uruchamia skrypt, oznacza w bazie
|
||||
- Flagi statusu projektow na liscie zamowien (zielona/zolta/szara ikonka) i w szczegolach (badge przy produkcie)
|
||||
- Rename `external_status_id` → `status_code` w 13+ plikach PHP — poprawka mylacej nazwy kolumny
|
||||
- Detekcja Photoshopa przed generowaniem (tasklist)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `tools/generowanie/buteleczki_wzor1.py` | Created | Skrypt Photoshop API — buteleczki weselne Wzor 1 |
|
||||
| `database/migrations/20260412_000097_add_project_generation.sql` | Created | Tabela project_mappings + kolumny project_generated |
|
||||
| `database/migrations/20260412_000098_rename_external_status_id_to_status_code.sql` | Created | Rename kolumny statusu |
|
||||
| `src/Modules/Settings/ProjectMappingRepository.php` | Created | CRUD repository dla mapowan |
|
||||
| `src/Modules/Settings/ProjectMappingController.php` | Created | Kontroler CRUD mapowan |
|
||||
| `resources/views/settings/project-mappings.php` | Created | Widok ustawien mapowan |
|
||||
| `resources/scss/modules/_project-mappings.scss` | Created | Style mapowan + badge projektow |
|
||||
| `.claude/commands/wygeneruj-projekty.md` | Created | Komenda Claude Code |
|
||||
| `routes/web.php` | Modified | Routing + instancjacja kontrolera |
|
||||
| `resources/views/layouts/app.php` | Modified | Link w sidebar menu |
|
||||
| `resources/lang/pl.php` | Modified | Tlumaczenia |
|
||||
| `resources/scss/app.scss` | Modified | Import SCSS modulu |
|
||||
| `src/Modules/Orders/OrdersRepository.php` | Modified | projects_done/total w query + rename |
|
||||
| `src/Modules/Orders/OrdersController.php` | Modified | projectBadge() + productsHtml() |
|
||||
| `resources/views/orders/show.php` | Modified | Badge przy produkcie |
|
||||
| `src/Modules/Orders/OrderImportRepository.php` | Modified | Rename |
|
||||
| `src/Modules/Automation/OrderStatusAgedService.php` | Modified | Rename |
|
||||
| `src/Modules/Automation/AutomationService.php` | Modified | Rename |
|
||||
| `src/Modules/Settings/AllegroOrderImportService.php` | Modified | Rename |
|
||||
| `src/Modules/Settings/AllegroStatusSyncService.php` | Modified | Rename |
|
||||
| `src/Modules/Settings/ShopproStatusSyncService.php` | Modified | Rename |
|
||||
| `src/Modules/Settings/ShopproPaymentStatusSyncService.php` | Modified | Rename |
|
||||
| `src/Modules/Settings/ShopproOrderMapper.php` | Modified | Rename |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Scope additions | 2 | Valuable improvements |
|
||||
| Auto-fixed | 3 | Import namespace fixes |
|
||||
|
||||
### Scope Additions
|
||||
|
||||
1. **Rename `external_status_id` → `status_code`** — odkryto podczas testow komendy ze nazwa kolumny jest mylaca. Refactor 13+ plikow + migracja.
|
||||
|
||||
2. **Flagi statusu projektow na liscie/szczegolach** — uzytkownik poprosil o wizualna informacje o statusie generacji. Dodano ikonki i badge.
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
1. **Bledne importy w ProjectMappingController** — `App\Core\Template\Template` → `App\Core\View\Template`, `App\Core\Translation\Translator` → `App\Core\I18n\Translator`, `App\Core\Session\Flash` → `App\Core\Support\Flash`
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Claude jako silnik generowania | AI lepiej interpretuje rozne formaty personalizacji niz sztywny parser PHP | Komenda zamiast serwisu PHP |
|
||||
| Rename external_status_id | Kolumna trzyma wewnetrzny status, nazwa mylaca | 13+ plikow zmienionych, czytelniejszy kod |
|
||||
| Bounds+translate dla pozycji | textItem.position nie dziala w Photoshop API | Niezawodne zachowanie pozycji warstw |
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- System gotowy do rozszerzenia o nowe skrypty (kubki, pudełka, itp.)
|
||||
- Mapowania w panelu — latwe dodawanie nowych produktow
|
||||
- Struktura warstw PSD uporzadkowana (imie_zenskie, imie_meskie, data, zyczenia)
|
||||
|
||||
**Concerns:**
|
||||
- Kazdy nowy produkt wymaga osobnego skryptu Python + uporządkowania warstw PSD
|
||||
|
||||
**Blockers:** None
|
||||
|
||||
---
|
||||
*Phase: 97-project-generation, Plan: 01*
|
||||
*Completed: 2026-04-12*
|
||||
Reference in New Issue
Block a user