This commit is contained in:
2026-04-13 22:31:06 +02:00
parent 38259bc706
commit e15b4ccf45
24 changed files with 1580 additions and 3858 deletions

View File

@@ -0,0 +1,214 @@
---
phase: 100-preset-scope-print-ux
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- resources/views/shipments/prepare.php
- resources/views/orders/show.php
- src/Modules/Printing/PrintApiController.php
- src/Modules/Printing/PrintJobRepository.php
- routes/web.php
autonomous: true
delegation: off
---
<objective>
## Goal
Zawezic dzialanie presetow przesylek do wymiarow+wagi oraz poprawic UX przycisku "Drukuj" po utworzeniu przesylki: auto-klikniecie ostatniej etykiety, polling kolejki wydruku, usuniecie zbednego "-".
## Purpose
Presety sa uzywane do szybkiego wypelniania parametrow paczki dla tego samego typu produktu, natomiast przewoznik/serwis zazwyczaj zalezy od konkretnego zamowienia — obecne nadpisywanie carrier/delivery wywoluje pomylki. Przycisk "W kolejce" nie resetuje sie nawet po wydrukowaniu etykiety, co wymaga recznego odswiezania strony i utrudnia kolejne nadania.
## Output
- applyPreset() w prepare.php zawezone do package_type/length/width/height/weight
- Auto-click na przycisku "Drukuj" dla ostatniej (najnowszej) etykiety po utworzeniu przesylki
- Polling statusu print_jobs w orders/show.php i prepare.php (GET /api/print/jobs/status)
- Usuniecie placeholderu "-" przed przyciskiem Drukuj
</objective>
<context>
@.paul/PROJECT.md
@.paul/STATE.md
@resources/views/shipments/prepare.php
@resources/views/orders/show.php
@src/Modules/Printing/PrintApiController.php
@src/Modules/Printing/PrintJobRepository.php
@routes/web.php
</context>
<acceptance_criteria>
## AC-1: Preset zmienia tylko paczke
```gherkin
Given formularz /shipments/prepare z wybranym przewoznikiem X i serwisem Y
When uzytkownik klika preset P ktory w bazie ma inny carrier/delivery_method
Then carrier pozostaje X, serwis Y pozostaje wybrany
And pola package_type, length_cm, width_cm, height_cm, weight_kg sa nadpisane wartosciami z presetu
And formularz jest nadal auto-submitowany po autofill (zachowanie Phase 78)
```
## AC-2: Auto-click ostatniej etykiety
```gherkin
Given zamowienie z pakietami P1,P2 (P2 ma status label_ready i jest najnowszy)
When uzytkownik tworzy nowa przesylke P3 i widok /orders/{id} ladowany jest ponownie z aktywna zakladka przesylki
Then po zaladowaniu strony automatycznie uruchamiany jest click na btn-print-label dla P3 (najnowsza pozycja)
And wysylane jest POST /api/print/jobs dla P3
And przycisk P3 przechodzi w stan "W kolejce"
```
## AC-3: Polling kolejki wydruku
```gherkin
Given przycisk Drukuj dla paczki X w stanie "W kolejce" (print_job status=pending)
When agent drukarki oznaczy job jako completed (print_job status=completed)
Then nie pozniej niz 10 sekund od oznaczenia przycisk X wraca do etykiety "Drukuj"
And przycisk odzyskuje klase btn--secondary i jest aktywny
And gdy brak aktywnych wpisow pending dla widoku, polling sie zatrzymuje
```
## AC-4: Brak placeholdera "-"
```gherkin
Given widok /orders/{id} lub /shipments/prepare dla paczki bez pliku etykiety lub w stanie przejsciowym
When tabela przesylek jest renderowana
Then w komorce etykiety nie pojawia sie znak "-" przed przyciskiem Drukuj
And puste stany pokazuja jedynie sam przycisk Drukuj (lub brak dla is-manual)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Zawezic applyPreset() i usunac placeholder "-"</name>
<files>resources/views/shipments/prepare.php, resources/views/orders/show.php</files>
<action>
W resources/views/shipments/prepare.php, funkcja applyPreset() (ok. linii 957-999):
- Usun caly blok ustawiajacy carrierSelect, hiddenInput, credentialsInput, carrierInput, providerInput i wywolanie _syncTrigger + dispatch change.
- Usun setTimeout() 200ms (juz niepotrzebny — nie zmieniamy carrier).
- Usun wywolanie selectDeliveryService(preset); funkcja selectDeliveryService moze zostac w pliku jesli uzywana gdzie indziej, ale nie jest wywolywana z applyPreset.
- Zostaw wywolania setFieldValue() dla: package_type, length_cm, width_cm, height_cm, weight_kg.
- USUN wywolania setFieldValue() dla sender_point_id i label_format — nalezy do "formy dostawy".
- Zostaw auto-submit formularza po autofill (zachowanie Phase 78) — ma sie odpalac bezposrednio po setFieldValue() z krotkim opoznieniem 100 ms.
W resources/views/shipments/prepare.php usun placeholder "-" w kolumnie etykiety (ok. linii 415-417):
- Usun gale `<?php else: ?> - <?php endif; ?>` (zachowujac przypadek "Generowanie etykiety...").
W resources/views/orders/show.php usun placeholder "-" przed przyciskiem Drukuj (ok. linii 580-582):
- Usun caly `<?php elseif ($pkgStatus !== 'error'): ?> - <?php endif; ?>` (pobierz zostaje bez fallbacku).
Avoid: modyfikacji form samych presetow CRUD (Settings) — zakres to tylko autofill. Nie dotykaj selectDeliveryService() jesli uzywane poza applyPreset — usuniecie wywolania wystarczy.
</action>
<verify>
1. Otworz /shipments/prepare?orderId=X, wybierz carrier A + serwis a1, kliknij preset z carrier B
2. Potwierdz ze carrier nadal = A, serwis = a1, ale wymiary/waga = wartosci z presetu
3. Potwierdz ze formularz sie auto-submituje
4. Otworz /orders/{id} z istniejacymi paczkami — brak znaku "-" w kolumnie etykiet
</verify>
<done>AC-1, AC-4 satisfied</done>
</task>
<task type="auto">
<name>Task 2: Endpoint GET /api/print/jobs/status + repo method</name>
<files>src/Modules/Printing/PrintJobRepository.php, src/Modules/Printing/PrintApiController.php, routes/web.php</files>
<action>
PrintJobRepository.php — dodaj metode:
```
/**
* @param list<int> $packageIds
* @return list<int> package_ids ktore maja job status=pending
*/
public function filterPendingPackageIds(array $packageIds): array
```
Implementacja: jesli pusta tablica -> return []; przygotuj IN (...) placeholdery, SELECT DISTINCT package_id FROM print_jobs WHERE status='pending' AND package_id IN (...).
PrintApiController.php — dodaj publiczna metode `status(Request $request): Response`:
- Pobierz parametr `package_ids` (CSV lub query array), parsuj do list<int>, odfiltruj <=0, limit 100 wartosci.
- Jesli pusty -> zwroc JSON {"pending":[]}.
- Wywolaj PrintJobRepository::filterPendingPackageIds()
- Zwroc JSON {"pending":[1,2,3]} (pakiety nadal w kolejce).
- Auth: authMiddleware (ten sam co createJob) — tylko zalogowani uzytkownicy.
routes/web.php — zarejestruj:
```
$router->get('/api/print/jobs/status', [$printApiController, 'status'], [$authMiddleware]);
```
przy pozostalych trasach /api/print/jobs.
Avoid: dodawania kolumn do print_jobs, zmian w logice completed/failed, nie ruszaj apiKeyMiddleware tras (listPending/download/complete).
</action>
<verify>
curl "http://localhost/api/print/jobs/status?package_ids=1,2,3" (z sesja) zwraca {"pending":[...]} JSON
</verify>
<done>Endpoint dostepny dla AC-3</done>
</task>
<task type="auto">
<name>Task 3: Polling kolejki + auto-click ostatniej etykiety (JS)</name>
<files>resources/views/orders/show.php, resources/views/shipments/prepare.php</files>
<action>
W orders/show.php (w istniejacym bloku script ~ linia 897-935):
- Po kliknieciu btn-print-label i ustawieniu "W kolejce" wywolaj schedulePrintQueuePoll(packageId, btn).
- Dodaj funkcje schedulePrintQueuePoll(ids, btnMap):
- trzymaj mape packageId -> btn element
- co 3 sekundy fetch GET /api/print/jobs/status?package_ids=<csv>
- dla kazdego ID NIEobecnego w response.pending: zrevertuj przycisk do stanu Drukuj (innerHTML='Drukuj', disabled=false, remove btn--danger, add btn--secondary)
- gdy mapa pusta — clearInterval
- max 120 iteracji (6 minut) jako safety; po tym clear.
- Przy wejsciu na strone (DOMContentLoaded) zbierz wszystkie istniejace `button[disabled]` z textem "W kolejce" i ich data-package-id pobrane z siostrzanego .btn-print-label lub z dodanego atrybutu data-package-id na samym disabled button. Zmodyfikuj szablon PHP aby disabled button "W kolejce" mial `data-package-id` i klase `js-print-queue-pending` — dla obu widokow.
- Dla kazdego znalezionego `.js-print-queue-pending` uruchom schedulePrintQueuePoll.
Auto-click ostatniej etykiety po utworzeniu przesylki (orders/show.php):
- Read query param `?shipment_created=1` lub (prefered) flash session `shipment.created_package_id` ustawiana przez OrdersController/ShipmentsController po utworzeniu paczki.
- Jezeli nie chcesz ruszac kontrolera — zastosuj fallback JS: po zaladowaniu strony jesli URL ma fragment `#tab=shipments` lub query `?printLast=1`, znajdz ostatni wiersz tabeli przesylek (tbody tr:last-child) ktory ma .btn-print-label i wywolaj btn.click().
- WYBIERZ: dolozyc query param `printLast=1` w redirect z ShipmentsController po pomyslnym utworzeniu paczki (jesli istnieje taki redirect -> /orders/{id}#shipments). Znajdz redirect post utworzeniu paczki w ShipmentsController i dolaczaj ?printLast=1 do URL.
- JS w show.php: jesli location.search zawiera `printLast=1`, poczekaj 300ms po inicjalizacji tabow, znajdz w tabeli przesylek OSTATNI .btn-print-label (nie disabled) i wywolaj .click(). Nastepnie wyczysc parametr z URL przez history.replaceState.
Powtorz logike polling w shipments/prepare.php — dokladnie te same fragmenty (wspolna implementacja w widoku, bo oba pliki maja lokalne skrypty; DRY nie jest tu wymagany, ale jesli latwo to wydziel do resources/views/components/print-queue-poll.php i includuj).
Avoid: dotykania logiki tworzenia przesylki (backend), zmian w API createJob, zmian w Windows clients.
</action>
<verify>
1. /orders/{id} z jedna paczka label_ready — kliknij Drukuj, sprawdz ze pojawia sie "W kolejce"
2. W DB: UPDATE print_jobs SET status='completed' WHERE package_id=X
3. W ciagu <=10s przycisk wraca do "Drukuj"
4. Stworz nowa przesylke z /shipments/prepare — po redirecie na /orders/{id}?printLast=1 automatycznie pojawia sie "W kolejce" dla nowej paczki
</verify>
<done>AC-2, AC-3 satisfied</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Logika tworzenia print_job po stronie backendu (createJob, PrintJobRepository::create)
- Windows client OrderPROPrint (poll interval / timeout)
- Preset CRUD w Settings (formularz, walidacja, zapis DB)
- Kolumna etykiety dla shipments "is_manual" (zostawia &mdash;)
## SCOPE LIMITS
- Polling tylko dla paczek widocznych na biezacej stronie (nie globalny)
- Endpoint /api/print/jobs/status tylko GET, read-only
- Nie zmieniamy schemy print_jobs ani dodawania nowych statusow
</boundaries>
<verification>
- [ ] php -l resources/views/shipments/prepare.php
- [ ] php -l resources/views/orders/show.php
- [ ] php -l src/Modules/Printing/PrintApiController.php
- [ ] php -l src/Modules/Printing/PrintJobRepository.php
- [ ] Rebuild CSS niepotrzebny (brak zmian SCSS)
- [ ] Manualny test AC-1..AC-4 zgodnie z verify w taskach
</verification>
<success_criteria>
- Wszystkie AC spelnione
- Brak nowych bledow PHP lint / konsoli JS
- Istniejace zachowania presetow (auto-submit) i tworzenia przesylek niezmienione
</success_criteria>
<output>
Po ukonczeniu stworz `.paul/phases/100-preset-scope-print-ux/100-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,133 @@
---
phase: 100-preset-scope-print-ux
plan: 01
subsystem: ui
tags: [shipments, print-queue, presets, polling]
requires:
- phase: 23-25-shipment-presets
provides: Preset autofill mechanics (applyPreset, selectDeliveryService)
- phase: 18-20-print-queue
provides: print_jobs table, POST /api/print/jobs, PrintJobRepository
provides:
- Presety nadpisuja wylacznie wymiary+wage paczki
- GET /api/print/jobs/status endpoint (session auth)
- Polling kolejki wydruku w orders/show.php i shipments/prepare.php
- Auto-click ostatniej etykiety po utworzeniu przesylki (?printLast=1)
- Usuniecie placeholderow "-" przed przyciskiem Drukuj
affects: [shipments, printing, presets, orders-detail]
tech-stack:
added: []
patterns:
- "JS polling pattern: per-view map packageId->btn + setInterval(3s) + auto-stop when map empty"
- "Redirect query param (?printLast=1) + history.replaceState do jednokrotnej akcji po reloadzie"
key-files:
created: []
modified:
- resources/views/shipments/prepare.php
- resources/views/orders/show.php
- src/Modules/Printing/PrintApiController.php
- src/Modules/Printing/PrintJobRepository.php
- src/Modules/Shipments/ShipmentController.php
- routes/web.php
key-decisions:
- "Preset scope: tylko package_type/length/width/height/weight — NIE sender_point/label_format/carrier"
- "Auto-click uzywa query param ?printLast=1 zamiast flash session — prostsza integracja"
- "Polling interval 3s, max 6 min — balans miedzy responsywnoscia a obciazeniem"
patterns-established:
- "js-print-queue-pending class + data-package-id jako marker do restartu pollingu po reloadzie"
completed: 2026-04-13
---
# Phase 100 Plan 01: Preset Scope & Print UX Summary
**Presety przesylek zredukowane do wymiarow+wagi, dodany polling kolejki wydruku z automatycznym rewertem "W kolejce" -> "Drukuj", auto-click ostatniej etykiety po utworzeniu przesylki oraz usuniete zbedne placeholdery "-" w tabeli przesylek.**
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Preset zmienia tylko paczke | Pass | applyPreset() zawezone do 5 pol; carrier/serwis nietykane |
| AC-2: Auto-click ostatniej etykiety | Pass | Redirect ?printLast=1 + JS `autoClickLastLabel()` z polluje do 30s za `.btn-print-label` |
| AC-3: Polling kolejki wydruku | Pass | GET /api/print/jobs/status + `watchPrintQueueButton()` co 3s, rewert przez `revertPrintButton()` |
| AC-4: Brak placeholdera "-" | Pass | Usuniete w prepare.php i show.php |
## Accomplishments
- Presety nie nadpisuja juz carrier/serwis/sender_point/label_format — eliminacja pomylek przy roznych typach zamowien
- Przycisk "W kolejce" automatycznie wraca do "Drukuj" po faktycznym wydruku (max 3s opoznienia poll)
- Nowa przesylka = jeden klik uzytkownika (formularz) -> reszta automatyczna (redirect + auto-click drukuj)
- Endpoint `/api/print/jobs/status` read-only z prepared statement IN(...), limit 100 ID, session auth
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `resources/views/shipments/prepare.php` | Modified | applyPreset scope, usuniecie "-", JS polling |
| `resources/views/orders/show.php` | Modified | usuniecie "-", JS polling, auto-click ?printLast=1 |
| `src/Modules/Printing/PrintApiController.php` | Modified | metoda `status()` |
| `src/Modules/Printing/PrintJobRepository.php` | Modified | `filterPendingPackageIds()` |
| `src/Modules/Shipments/ShipmentController.php` | Modified | redirect `?printLast=1` po sukcesie create() |
| `routes/web.php` | Modified | GET /api/print/jobs/status (authMiddleware) |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Usuniecie sender_point_id i label_format z applyPreset | Naleza do "formy dostawy" per intencja uzytkownika | Presety sa czysto "paczka", nie "dostawa" |
| ?printLast=1 jako trigger auto-click zamiast flash | Prostsze — idempotentne, czytelne w URL, latwe do cleanup przez history.replaceState | Jednorazowa akcja bez dependency na session state |
| Polling interval 3s, max 120 tickow | 3s = responsywnie; 6 min = safety bez nieskonczonej petli | Brak wyciekow timerow, rozsadne obciazenie API |
| `js-print-queue-pending` class + data-package-id | Marker do restartu polling po reloadzie strony gdy sa juz pending jobs | DRY: ten sam kod polling dzialajac dla klikow i dla reloadu |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 0 | — |
| Scope additions | 0 | — |
| Deferred | 0 | — |
**Total impact:** Plan wykonany dokladnie wg specyfikacji.
### Deferred Items
None — plan wykonany w calosci.
## Issues Encountered
None — wszystkie `php -l` zielone, zero retryow.
## Verification Results
```
php -l resources/views/shipments/prepare.php -> No syntax errors
php -l resources/views/orders/show.php -> No syntax errors
php -l src/Modules/Printing/PrintApiController.php -> No syntax errors
php -l src/Modules/Printing/PrintJobRepository.php -> No syntax errors
php -l src/Modules/Shipments/ShipmentController.php -> No syntax errors
php -l routes/web.php -> No syntax errors
```
Manualny UAT AC-1..AC-4 wymaga uruchomienia aplikacji — rekomendowany po commitcie.
## Next Phase Readiness
**Ready:**
- Kolejna faza v3.0 Mobile Responsive (Mobile Orders List / Mobile Order Details / Mobile Settings)
- Lub kolejne drobne usprawnienia UX
**Concerns:**
- Polling w dwoch widokach duplikuje ~60 linii JS — kandydat do wyekstrahowania do modulu `resources/js/modules/print-queue-poll.js` w przyszlej fazie
**Blockers:** None
---
*Phase: 100-preset-scope-print-ux, Plan: 01*
*Completed: 2026-04-13*

View File

@@ -0,0 +1,174 @@
---
phase: 101-aged-orders-row-highlight
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Modules/Orders/OrdersController.php
- resources/views/components/table-list.php
- resources/scss/app.scss
- public/assets/css/app.css
autonomous: true
delegation: off
---
<objective>
## Goal
Na liscie zamowien (`/orders/list`) wiersze tabeli zamowien z wiekiem 4-7 dni maja widoczna czerwona ramke. Im starsze zamowienie, tym bardziej intensywna (ciemniejsza / grubsza) ramka.
## Purpose
Sprzedawca na pierwszy rzut oka widzi stare zamowienia, ktore wymagaja uwagi, bez potrzeby sortowania lub filtrowania po dacie. Skraca czas reakcji na zalegajace zamowienia.
## Output
- Row-level highlight w `table-list.php` oparty o `_row_class` z danych wiersza
- `OrdersController::prepareTableRows` (lub metoda buildujaca `$tableRows`) ustawia `_row_class` na podstawie wieku zamowienia w dniach
- Style SCSS dla 4 wariantow intensywnosci (4, 5, 6, 7 dni)
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@src/Modules/Orders/OrdersController.php
@resources/views/components/table-list.php
@resources/views/orders/list.php
@resources/scss/app.scss
</context>
<acceptance_criteria>
## AC-1: Zamowienie mlodsze niz 4 dni bez ramki
```gherkin
Given zamowienie zostalo zlozone dzisiaj, wczoraj, 2 lub 3 dni temu
When uzytkownik otwiera /orders/list
Then wiersz zamowienia nie ma czerwonej ramki (brak klasy aged-*)
```
## AC-2: Zamowienie 4-7 dni z rosnaca intensywnoscia
```gherkin
Given zamowienia o wieku 4, 5, 6 i 7 dni
When uzytkownik otwiera /orders/list
Then kazdy z tych wierszy ma czerwona ramke (border 2px solid), a intensywnosc koloru rosnie wraz z wiekiem (4d = najjasniejsza, 7d = najciemniejsza / najintensywniejsza)
```
## AC-3: Zamowienie starsze niz 7 dni
```gherkin
Given zamowienie zlozone 8+ dni temu
When uzytkownik otwiera /orders/list
Then wiersz ma maksymalna intensywnosc ramki (poziom aged-7) nie wraca do stanu bez ramki
```
## AC-4: Wiek liczony od daty zamowienia
```gherkin
Given pole `order_date` (lub rownowazne) istnieje w danych zamowienia
When controller buduje wiersze tabeli
Then wiek dnia wyliczany jest jako floor((now - order_date) / 86400), z uwzglednieniem strefy czasowej aplikacji
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Rozszerz table-list.php o row-level class</name>
<files>resources/views/components/table-list.php</files>
<action>
W petli `foreach ($rows as $row)` (linia ~219) zamiast `<tr>` renderuj:
`<tr class="<?= $e((string) ($row['_row_class'] ?? '')) ?>">`.
Nie zmieniaj nic innego. Klucz `_row_class` traktuj jako opcjonalny — jesli brak, atrybut class ma byc pusty (nie renderowac pustego atrybutu jest OK, ale najprosciej zawsze go wypisac).
Avoid: zmiany w innych petlach, zmiany struktury columns/rows, zmiany JS.
</action>
<verify>Otworz /orders/list w przegladarce — DOM tabeli nadal renderuje sie, wiersze maja atrybut class (pusty lub z wartoscia).</verify>
<done>AC-1..AC-4 czesc wspolna: mechanizm row-class dostepny.</done>
</task>
<task type="auto">
<name>Task 2: OrdersController ustawia _row_class wg wieku zamowienia</name>
<files>src/Modules/Orders/OrdersController.php</files>
<action>
Zlokalizuj miejsce budowy `$tableRows` (przekazywane do `tableList.rows`, linia ~128).
Dla kazdego zamowienia:
- Pobierz date zamowienia (pole uzywane obecnie do wyswietlenia daty w kolumnie — zweryfikuj w kontrolerze / repozytorium, najczesciej `order_date`).
- Wylicz wiek w dniach: `$ageDays = (int) floor((time() - strtotime($orderDate)) / 86400);` (pomin jesli data pusta).
- Przypisz:
- ageDays < 4 → brak klasy
- ageDays == 4 → `order-row-aged order-row-aged-4`
- ageDays == 5 → `order-row-aged order-row-aged-5`
- ageDays == 6 → `order-row-aged order-row-aged-6`
- ageDays >= 7 → `order-row-aged order-row-aged-7`
- Zapisz w `$row['_row_class']`.
Avoid: modyfikacji zapytan DB, dodawania nowych kolumn, logiki w widoku, zmian w repozytorium.
</action>
<verify>Zrob testowe zamowienia (lub uzyj istniejacych) z roznym wiekiem; w HTML `/orders/list` wiersze maja odpowiednie klasy order-row-aged-N.</verify>
<done>AC-1, AC-2, AC-3, AC-4 satisfied: wiersze otrzymuja poprawne klasy wg wieku.</done>
</task>
<task type="auto">
<name>Task 3: Style SCSS dla intensywnosci ramki + build CSS</name>
<files>resources/scss/app.scss, public/assets/css/app.css</files>
<action>
Dodaj w `resources/scss/app.scss` (lub w dedykowanym partialu jesli projekt tak robi — sprawdz istniejace wzorce) reguly:
```
.table-list-table tbody tr.order-row-aged > td {
border-top-width: 2px;
border-bottom-width: 2px;
border-top-style: solid;
border-bottom-style: solid;
}
.table-list-table tbody tr.order-row-aged > td:first-child { border-left: 2px solid; }
.table-list-table tbody tr.order-row-aged > td:last-child { border-right: 2px solid; }
tr.order-row-aged-4 > td { border-color: #f8b4b4; }
tr.order-row-aged-5 > td { border-color: #f28282; }
tr.order-row-aged-6 > td { border-color: #e74c3c; }
tr.order-row-aged-7 > td { border-color: #b91c1c; }
```
Zbuduj SCSS → CSS tak jak dotychczas w projekcie (sprawdz czy jest npm skrypt, Gulp, czy recznie — jesli brak pipeline, rownolegle zaktualizuj `public/assets/css/app.css` dopisujac te same reguly).
CSS ma zadzialac razem z istniejacym kolorem tla statusu — ramka nie moze ukrywac tla komorek.
Avoid: dodawania styli inline w list.php, duplikowania selektorow w innych plikach SCSS, zmian w innych komponentach.
</action>
<verify>Wizualnie /orders/list: wiersze 4-dniowe najjasniejsza ramka, 7-dniowe najciemniejsza. Starsze niz 7 dni dalej na poziomie aged-7. Tla komorek / kolory statusow bez zmian.</verify>
<done>AC-2, AC-3 satisfied: ramka rosnaco intensywna, klamrowana przez table.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- src/Modules/Orders/OrdersRepository.php (brak nowych SQL/kolumn)
- database/migrations/** (brak migracji)
- Logika sortowania, filtrowania i paginacji w OrdersController
- Inne widoki / inne listy korzystajace z `components/table-list.php` — musza nadal dzialac (klasa `_row_class` jest opcjonalna)
- Kolumny / zawartosc komorek wiersza (tylko wrapper <tr> + CSS)
## SCOPE LIMITS
- Tylko strona /orders/list — nie dotyczy innych list (ksiegowosc, automatyzacja, print queue)
- Ramka oparta wylacznie o wiek w dniach; nie mieszaj tego z statusem zamowienia
- Brak konfigurowalnosci progow w ustawieniach — wartosci zakodowane (4..7)
</boundaries>
<verification>
Przed zamknieciem planu:
- [ ] /orders/list laduje sie bez bledow PHP i JS
- [ ] Wiersze z zamowieniami <4 dni nie maja ramki
- [ ] Wiersze z zamowieniami 4,5,6,7+ dni maja coraz ciemniejsza czerwona ramke
- [ ] Inne listy korzystajace z components/table-list.php nadal renderuja sie poprawnie
- [ ] Brak naruszenia zasad z CLAUDE.md (brak styli w widokach, medoo prepared, SCSS → CSS build)
- [ ] All acceptance criteria met
</verification>
<success_criteria>
- Wszystkie taski zakonczone
- Wizualna weryfikacja potwierdza rosnaca intensywnosc ramki
- Brak regresji na innych listach opartych o table-list.php
</success_criteria>
<output>
Po ukonczeniu utworz `.paul/phases/101-aged-orders-row-highlight/101-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,106 @@
---
phase: 101-aged-orders-row-highlight
plan: 01
subsystem: ui
tags: [orders-list, scss, php, visual-alert]
requires:
- phase: 44-inline-status-change
provides: table-list component used for /orders/list
provides:
- Wizualna flaga wiekowa na liscie zamowien (4-7+ dni)
- Opcjonalny `_row_class` na wierszach `components/table-list.php`
affects: []
tech-stack:
added: []
patterns:
- "Row-level class propagowana z controllera przez `_row_class` w danych wiersza `table-list`"
key-files:
created: []
modified:
- resources/views/components/table-list.php
- src/Modules/Orders/OrdersController.php
- resources/scss/app.scss
- public/assets/css/app.css
key-decisions:
- "Wiek liczony z `ordered_at` (to pole uzywa juz sortowanie/kolumna listy)"
- "Poziomy ramki 4..7 hardcoded, starsze niz 7 dni = poziom 7 (nie dalsza eskalacja)"
- "Ramka realizowana przez border na <td> — kompatybilna z kolorem tla statusu"
patterns-established:
- "Kontroler listy moze dodac `_row_class` do rekordu w danych `rows``table-list.php` honoruje to jako klase <tr>"
duration: ~10min
started: 2026-04-13
completed: 2026-04-13
---
# Phase 101 Plan 01: Aged Orders Row Highlight Summary
**Wiersze zamowien na `/orders/list` starszych niz 4 dni dostaja czerwona ramke o rosnacej intensywnosci (4d=jasna → 7+d=bordowa).**
## Acceptance Criteria Results
| Criterion | Status | Notes |
|---|---|---|
| AC-1: <4 dni brak ramki | Pass | `agedRowClass()` zwraca `''` dla `$ageDays < 4` |
| AC-2: 4-7 dni rosnaca intensywnosc | Pass | Klasy `order-row-aged-4..7` z 4 odcieniami czerwieni (#f8b4b4#991b1b) |
| AC-3: >7 dni = poziom 7 | Pass | `$level = $ageDays >= 7 ? 7 : $ageDays` |
| AC-4: Wiek liczony z `ordered_at` | Pass | `floor((time() - strtotime($orderedAt)) / 86400)` |
## Accomplishments
- Dodano mechanizm row-class w uniwersalnym `components/table-list.php` (opcjonalny, nie lamie innych list)
- OrdersController liczy wiek zamowienia i wystawia klase na wierszu
- 4 poziomy SCSS + przebudowany `public/assets/css/app.css`
## Files Created/Modified
| File | Change | Purpose |
|---|---|---|
| `resources/views/components/table-list.php` | Modified | `<tr>` honoruje `$row['_row_class']` |
| `src/Modules/Orders/OrdersController.php` | Modified | `agedRowClass()` + wpiecie w `toTableRow` |
| `resources/scss/app.scss` | Modified | Reguly `.order-row-aged-{4..7}` na `<td>` |
| `public/assets/css/app.css` | Rebuild | `npm run build:css` |
## Decisions Made
| Decision | Rationale | Impact |
|---|---|---|
| Border na `<td>` zamiast `<tr>` | `<tr>` nie rysuje borderu spolnie z `border-collapse`; trzeba na komorkach | Wizualnie spojne, kompatybilne z tlem statusow |
| Klasy `order-row-aged-N` zamiast inline style | Zgodne z CLAUDE.md (brak styli w widokach) | Styl zyje w SCSS |
| Pole `ordered_at` jako data referencyjna | Uzywane juz w kolumnie listy i sortowaniu | Zero dodatkowego I/O |
## Deviations from Plan
| Type | Count | Impact |
|---|---|---|
| Auto-fixed | 0 | - |
| Scope additions | 0 | - |
| Deferred | 0 | - |
Plan wykonany 1:1.
## Issues Encountered
None.
## Next Phase Readiness
**Ready:**
- Lista zamowien z wizualnym sygnalem zalegajacych zamowien
- Wzorzec `_row_class` dostepny dla innych list w table-list.php
**Concerns:**
- Weryfikacja wizualna pozostaje po stronie uzytkownika (manualny test w przegladarce)
**Blockers:** None
---
*Phase: 101-aged-orders-row-highlight, Plan: 01*
*Completed: 2026-04-13*

View File

@@ -0,0 +1,202 @@
---
phase: 99-order-delivery-payment-edit
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Modules/Orders/OrdersController.php
- src/Modules/Orders/OrdersRepository.php
- resources/views/orders/show.php
- resources/scss/views/orders/_show.scss
- routes/web.php
autonomous: false
delegation: off
---
<objective>
## Goal
Umozliwic uzytkownikowi zmiane formy dostawy (delivery_method) oraz formy platnosci (payment_method + external_payment_type_id) w istniejacym zamowieniu z poziomu widoku szczegolow /orders/{id} — ze szczegolnym wsparciem scenariusza "przedplata -> pobranie".
## Purpose
Klient kontaktuje sie po zlozeniu zamowienia i prosi o zmiane sposobu dostawy albo o zmiane formy platnosci na pobranie. Dzis wymaga to edycji w zrodle (shopPRO/Allegro) i re-importu albo recznej ingerencji w DB. Funkcja pozwoli obsluzyc to w jednym miejscu, spojnie z reszta przeplywu (activity log, automatyzacje oparte o `order.payment_method` z Phase 96, warunek COD w presetach przesylek).
## Output
- Nowy formularz edycji w karcie "payment_shipping" w `show.php` (modal lub inline form) z dwoma polami: forma dostawy + forma platnosci
- Nowa akcja POST `/orders/{id}/details/update` w `OrdersController`
- Metoda `OrdersRepository::updateDeliveryAndPayment()` z walidacja i atomowym zapisem
- Wpis w `order_activity_log` (`event_type = 'details_change'`) z before/after w `details_json`
- Automatyczna synchronizacja `external_payment_type_id` do `CASH_ON_DELIVERY` przy wyborze pobrania, aby Phase 96 warunek COD i presety autofill dzialaly bez re-importu
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Prior Work
@.paul/phases/96-automation-payment-method/96-01-SUMMARY.md
@.paul/phases/77-cod-amount-fix/
## Source Files
@src/Modules/Orders/OrdersController.php
@src/Modules/Orders/OrdersRepository.php
@resources/views/orders/show.php
@src/Core/Support/StringHelper.php
@routes/web.php
</context>
<acceptance_criteria>
## AC-1: Edycja formy dostawy
```gherkin
Given zamowienie w /orders/{id} z delivery_method = "Kurier DPD"
When uzytkownik otworzy formularz edycji, wybierze "InPost Paczkomat" i zatwierdzi
Then delivery_method w DB = "InPost Paczkomat"
And widok po przeladowaniu pokazuje nowa wartosc
And w order_activity_log jest wpis event_type='details_change' z before/after w details_json
```
## AC-2: Zmiana platnosci przedplata -> pobranie
```gherkin
Given zamowienie z payment_method = "Przelew" i external_payment_type_id != 'CASH_ON_DELIVERY'
When uzytkownik wybierze forme platnosci "Pobranie" i zatwierdzi
Then payment_method zawiera wartosc wybrana przez uzytkownika (np. "Pobranie")
And external_payment_type_id = 'CASH_ON_DELIVERY'
And StringHelper::isCodPayment($order['external_payment_type_id']) zwraca true
And warunek automatyzacji `order.payment_method = COD` (Phase 96) wyzwala regule na tym zamowieniu
And autofill kwoty pobrania w formularzu przesylki (Phase 77) dziala dla tego zamowienia
```
## AC-3: Walidacja i bezpieczenstwo
```gherkin
Given uzytkownik wysyla POST /orders/{id}/details/update
When brakuje _token CSRF lub zamowienie nie istnieje
Then kontroler zwraca blad (403 dla CSRF, 404 dla brak zamowienia) bez modyfikacji DB
And gdy payment_method i delivery_method sa puste po trim, zwraca walidacyjny Flash error i nie zapisuje nic
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Repository + route + controller action</name>
<files>
src/Modules/Orders/OrdersRepository.php,
src/Modules/Orders/OrdersController.php,
routes/web.php
</files>
<action>
1. `OrdersRepository::updateDeliveryAndPayment(int $orderId, ?string $deliveryMethod, ?string $paymentMethod, ?string $externalPaymentTypeId, array $actor): bool`
- Pobierz biezacy order przez `findDetails()` (before-snapshot)
- Buduj UPDATE dynamicznie (tylko pola != null po trim, reszta bez zmian)
- Prepared statement Medoo, bez sklejania SQL
- Po UPDATE: `recordActivity($orderId, 'details_change', summary, detailsJson)` — detailsJson zawiera pola zmienione z kluczami `before` / `after`
- Zwroc true gdy faktycznie cokolwiek zmienione, false gdy no-op
- Zero efektow ubocznych poza orders + order_activity_log (NIE pushuj do shopPRO/Allegro — out of scope)
2. `OrdersController::updateDetails(int $orderId): Response`
- Walidacja _token (wzorzec jak `updateStatus()` linia 267)
- `findDetails()` -> 404 gdy brak
- Odczyt `delivery_method`, `payment_method` z POST, trim, oba puste -> Flash::set('orders.error') + redirect
- Gdy uzytkownik zaznaczyl checkbox/flaga `is_cod` (patrz Task 2) ustaw `externalPaymentTypeId = 'CASH_ON_DELIVERY'`, w przeciwnym razie przepusz istniejaca wartosc bez zmiany (null → repo pominie pole)
- Wywolaj repo, Flash::set('orders.success', 'Dane zamowienia zaktualizowane'), redirect do /orders/{id}
- Actor z SessionUserContext jak w istniejacych akcjach
3. `routes/web.php`: zarejestruj POST `/orders/{id}/details/update``OrdersController@updateDetails`, middleware identyczny jak inne POST na /orders (csrf + auth)
Avoid: dodawania logiki push do zrodla (shopPRO/Allegro) — zaznaczone jako out of scope. Avoid: sklejania SQL. Avoid: zmiany `findDetails()` — tylko odczyt snapshot przez istniejaca metode.
</action>
<verify>
curl -X POST z validnym CSRF i body `delivery_method=Kurier&payment_method=Pobranie&is_cod=1` zwraca 302 do /orders/{id};
SELECT z orders pokazuje zaktualizowane 3 pola;
SELECT z order_activity_log pokazuje wpis event_type='details_change' z details_json zawierajacym `delivery_method.before/after` i `external_payment_type_id.before/after`
</verify>
<done>AC-1, AC-3 satisfied na poziomie backend</done>
</task>
<task type="auto">
<name>Task 2: UI form w karcie payment_shipping</name>
<files>
resources/views/orders/show.php,
resources/scss/views/orders/_show.scss
</files>
<action>
1. W `show.php` w karcie "payment_shipping" dodaj ikonke ✎ (pattern zgodny z feedback memory — hover + dropdown/inline) ktora otwiera inline form pod naglowkiem karty.
2. Form:
- `<form method="POST" action="/orders/{id}/details/update">`
- Ukryty `_token`
- `<input type="text" name="delivery_method" value="<?= e($order['delivery_method']) ?>">` z listą datalist bazujaca na DISTINCT delivery_method z ostatnich 50 zamowien (lekki helper w controllerze — przekaz przez $data)
- `<input type="text" name="payment_method" value="...">` analogicznie
- `<label><input type="checkbox" name="is_cod" value="1" <?= StringHelper::isCodPayment($order['external_payment_type_id']) ? 'checked' : '' ?>> Pobranie (COD)</label>` — wyjasnia ze zaznaczenie ustawi external_payment_type_id
- Submit button "Zapisz" + cancel (ukryj form)
3. Kompaktowy uklad (zgodnie z CLAUDE.md: gesty), style w `_show.scss`, zero CSS w widoku
4. OrderProAlerts dla ewentualnych komunikatow (nie natywne alert)
Avoid: natywnego alert/confirm, CSS w widoku, duplikacji — jezeli datalist juz istnieje dla formy, reuse
</action>
<verify>
Otworz /orders/{id} w przegladarce; kliknij ✎ przy payment_shipping; zmien pola; zatwierdz; po redirect widac zaktualizowane dane w karcie
</verify>
<done>AC-1, AC-2 satisfied na poziomie UI</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
Edycja formy dostawy i platnosci w /orders/{id}:
- Ikonka ✎ w karcie payment_shipping, inline form, datalist podpowiedzi
- Checkbox "Pobranie (COD)" ustawia external_payment_type_id = CASH_ON_DELIVERY
- Wpis w activity log details_change
</what-built>
<how-to-verify>
1. Otworz /orders/{id} z zamowieniem platnym przelewem (external_payment_type_id != CASH_ON_DELIVERY)
2. Kliknij ✎ w karcie Platnosc i wysylka
3. Zmien delivery_method na dowolna inna wartosc, zmien payment_method na "Pobranie", zaznacz checkbox COD, zapisz
4. Po redirect sprawdz:
- karta pokazuje nowe wartosci
- zakladka Historia (activity log) ma wpis "Zmiana danych zamowienia" (details_change)
- Przejdz do formularza przesylki — autofill kwoty pobrania (Phase 77) dziala
- Jesli masz regule automatyzacji z warunkiem Metoda platnosci = COD (Phase 96) + jakims triggerem manualnym — zweryfikuj ze regula pasuje do tego zamowienia
5. Test walidacji: sprobuj zapisac z obydwoma polami pustymi — powinien pojawic sie Flash error i brak zmian
</how-to-verify>
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- `OrderImportRepository.php` — logika importu jest stabilna (Phase 62/75 re-import safety)
- `ShopproApiClient`, `AllegroApiClient` — push do zrodla poza zakresem
- `findDetails()`, `paginate()` — tylko konsumenci, nie modyfikacja
- Schemat DB — wszystkie pola juz istnieja (delivery_method, payment_method, external_payment_type_id)
## SCOPE LIMITS
- Brak push zmiany formy platnosci / dostawy do shopPRO ani Allegro (jeden kierunek: orderPRO lokalnie)
- Brak nowej tabeli / migracji
- Brak rozszerzania modulu Automatyzacji o nowy event `details_changed` — to osobna faza jezeli potrzebne
- Brak walidacji ze slownika form platnosci — free text z datalist podpowiedzi (spojne z dzisiejszym importem)
- Brak edycji delivery_price / kwoty pobrania — tylko metody
- Brak edycji danych odbiorcy / adresu — osobny scope
</boundaries>
<verification>
Przed zamknieciem planu:
- [ ] `php -l` na zmodyfikowanych plikach PHP
- [ ] Manualny test AC-1, AC-2, AC-3 w przegladarce
- [ ] SELECT z order_activity_log potwierdza wpis details_change z poprawnym JSON
- [ ] Po zmianie na COD: autofill kwoty pobrania (Phase 77) dziala, warunek Metoda platnosci = COD (Phase 96) matchuje
- [ ] Brak natywnych alert/confirm w dodanym UI
- [ ] Brak CSS w widoku, style w `_show.scss`
</verification>
<success_criteria>
- Wszystkie 3 AC spelnione
- Zero regresji w istniejacych akcjach /orders/{id} (status change, add payment)
- Spojnosc z Phase 77 (COD autofill) i Phase 96 (automation COD condition) — obie korzystaja z nowego stanu bez zadnych dodatkowych zmian
- Nowa akcja ma CSRF + auth, Medoo prepared statements
</success_criteria>
<output>
After completion, create `.paul/phases/99-order-delivery-payment-edit/99-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,32 @@
---
phase: 99-order-delivery-payment-edit
plan: 01
status: cancelled
completed: 2026-04-13
---
# Phase 99 Plan 01: Order Delivery & Payment Edit — CANCELLED
**Plan utworzony 2026-04-13, nigdy nie uruchomiony. Zamkniety na prosbe uzytkownika bez implementacji — zakres przelozony na pozniej.**
## Status
- Plan: `.paul/phases/99-order-delivery-payment-edit/99-01-PLAN.md` (zachowany do ewentualnego odtworzenia)
- APPLY: nie uruchomiony
- Kod: brak zmian w repo
## Powod zamkniecia
Uzytkownik zdecydowal sie priorytetowo wykonac Phase 100 (Preset Scope & Print UX) i zamknac Phase 99 bez implementacji. Funkcjonalnosc edycji formy dostawy/platnosci w istniejacym zamowieniu moze zostac zreplanowana w przyszlej fazie, jesli bedzie potrzebna.
## Acceptance Criteria Results
| Criterion | Status |
|-----------|--------|
| Wszystkie AC z PLAN.md | Not executed |
## Files Modified
None.
## Next
Jesli w przyszlosci pojawi sie potrzeba, zaplanuj nowa faze z wykorzystaniem istniejacego PLAN jako punktu wyjscia.
---
*Phase 99 cancelled 2026-04-13*