update
This commit is contained in:
@@ -102,6 +102,8 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
||||
- [x] Automatyzacja: warunek "Metoda platnosci" (COD/Przelew/Karta/Inna) oparty na external_payment_type_id — Phase 96
|
||||
- [x] Polautomatyczne generowanie projektow graficznych PSD z zamowien (mapowania, komenda Claude, Photoshop API, flagi statusu) + rename external_status_id na status_code — Phase 97
|
||||
- [x] Event automatyzacji `order.imported` ogranicza sie do pierwszego importu zamowienia + backfill statusow w_realizacji -> wyslane dla zamowien z istniejaca przesylka — Phase 98
|
||||
- [x] Preset przesylek nadpisuje wylacznie wymiary+wage paczki + polling kolejki wydruku + auto-click ostatniej etykiety po utworzeniu przesylki + usuniecie placeholderow "-" — Phase 100
|
||||
- [x] Wizualna flaga wiekowa na liscie zamowien: czerwona ramka o rosnacej intensywnosci dla zamowien 4-7+ dniowych — Phase 101
|
||||
- [ ] Eliminacja zduplikowanego kodu: SslCertificateResolver, ToggleableRepositoryTrait, RedirectPathResolver, ReceiptService — Phase 68
|
||||
|
||||
### Active (In Progress)
|
||||
|
||||
@@ -59,6 +59,9 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel
|
||||
| 96 | Automation Payment Method Condition | 1/1 | Complete |
|
||||
| 97 | Project Generation | 1/1 | Complete |
|
||||
| 98 | Order Imported First Only | 1/1 | Complete |
|
||||
| 99 | Order Delivery & Payment Edit | 0/1 | Cancelled |
|
||||
| 100 | Preset Scope & Print UX | 1/1 | Complete |
|
||||
| 101 | Aged Orders Row Highlight | 1/1 | Complete |
|
||||
| TBD | Mobile Orders List | - | Not started |
|
||||
| TBD | Mobile Order Details | - | Not started |
|
||||
| TBD | Mobile Settings | - | Not started |
|
||||
|
||||
@@ -10,26 +10,27 @@ See: .paul/PROJECT.md (updated 2026-04-12)
|
||||
## Current Position
|
||||
|
||||
Milestone: v3.0 Mobile Responsive - In progress
|
||||
Phase: 98 (Order Imported First Only) — Complete
|
||||
Plan: 98-01 unified
|
||||
Phase: 101 (Aged Orders Row Highlight) — Complete
|
||||
Plan: 101-01 Complete
|
||||
Status: Loop closed, ready for next PLAN
|
||||
Last activity: 2026-04-13 — Unified phase 98
|
||||
Last activity: 2026-04-13 — UNIFY Phase 101 complete
|
||||
|
||||
Progress:
|
||||
- Milestone: [#########.] ~95%
|
||||
- Phase 98: [##########] 100%
|
||||
- Milestone: [#########.] ~96%
|
||||
- Phase 101: [##########] 100%
|
||||
- Phase 100: [##########] 100%
|
||||
|
||||
## Loop Position
|
||||
|
||||
Current loop state:
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ✓ ✓ [Loop complete - ready for next PLAN]
|
||||
✓ ✓ ✓ [Loop complete — ready for next PLAN]
|
||||
```
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-04-13
|
||||
Stopped at: Phase 98 unified
|
||||
Next action: Plan next phase or pause
|
||||
Resume file: .paul/phases/98-order-imported-first-only/98-01-SUMMARY.md
|
||||
Stopped at: Phase 101 Complete
|
||||
Next action: /paul:plan dla kolejnej fazy
|
||||
Resume file: .paul/phases/101-aged-orders-row-highlight/101-01-SUMMARY.md
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
- [Phase 98, Plan 01] Event automatyzacji `order.imported` ogranicza sie do pierwszego importu zamowienia (gating po `$wasCreated` w AllegroOrderImportService i ShopproOrdersSyncService)
|
||||
- [Phase 98, Plan 01] Backfill `bin/backfill_shipped_status_98.php`: 4 zamowienia (#275, #340, #396, #422) w statusie `w_realizacji` z istniejaca przesylka przestawione na `wyslane` przez `OrdersRepository::updateOrderStatus` (z historia statusu i activity log)
|
||||
- [Phase 98, Plan 01] Skrypt idempotentny: drugie uruchomienie zwraca 0 kandydatow
|
||||
- [Phase 100, Plan 01] Preset Scope & Print UX — presety nadpisuja wylacznie wymiary+wage paczki (carrier/serwis/sender_point/label_format nietykane)
|
||||
- [Phase 100, Plan 01] GET /api/print/jobs/status + polling w orders/show.php i shipments/prepare.php — przycisk "W kolejce" wraca do "Drukuj" po wydruku (max 3s opoznienia)
|
||||
- [Phase 100, Plan 01] Po utworzeniu przesylki redirect z `?printLast=1` i auto-click na ostatniej etykiecie
|
||||
- [Phase 100, Plan 01] Usuniete placeholdery "-" przed przyciskiem Drukuj w obu widokach
|
||||
- [Phase 99] Order Delivery & Payment Edit — plan zamkniety bez implementacji (cancelled)
|
||||
- [Phase 101, Plan 01] Aged Orders Row Highlight — wiersze zamowien 4-7+ dniowych dostaja czerwona ramke o rosnacej intensywnosci (4 poziomy, `order-row-aged-{4..7}`)
|
||||
- [Phase 101, Plan 01] `components/table-list.php` obsluguje opcjonalny `_row_class` na rekordach, OrdersController liczy wiek z `ordered_at`
|
||||
|
||||
## Zmienione pliki
|
||||
|
||||
@@ -16,3 +23,18 @@
|
||||
- `.paul/STATE.md`
|
||||
- `.paul/ROADMAP.md`
|
||||
- `.paul/PROJECT.md`
|
||||
- `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`
|
||||
- `.paul/phases/100-preset-scope-print-ux/100-01-PLAN.md`
|
||||
- `.paul/phases/100-preset-scope-print-ux/100-01-SUMMARY.md`
|
||||
- `.paul/phases/99-order-delivery-payment-edit/99-01-SUMMARY.md`
|
||||
- `resources/views/components/table-list.php`
|
||||
- `src/Modules/Orders/OrdersController.php`
|
||||
- `resources/scss/app.scss`
|
||||
- `public/assets/css/app.css`
|
||||
- `.paul/phases/101-aged-orders-row-highlight/101-01-PLAN.md`
|
||||
- `.paul/phases/101-aged-orders-row-highlight/101-01-SUMMARY.md`
|
||||
|
||||
@@ -33,3 +33,74 @@
|
||||
{"ts":"2026-04-13T08:55:44Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T08:55:51Z","tool":"Bash","cmd":"cd \"/c/visual studio code/projekty/orderPRO\" && git add src/Modules/Settings/AllegroOrderImportService.php src/Modules/Settings/ShopproOrdersSyncService.php bin/backfill_shipped_status_98.php .paul/","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T08:56:03Z","tool":"Bash","cmd":"cd \"/c/visual studio code/projekty/orderPRO\" && git commit -m \"feat(98-order-imported-first-only): gate order.imported na pierwszy import + backfill statusow\\n\\n- AllegroOrderImportService / Shop","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:00:35Z","tool":"Bash","cmd":"mkdir -p \".paul/phases/99-order-delivery-payment-edit\"\",\"description\":\"Create phase 99 directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,\"n","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:02:00Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\99-order-delivery-payment-edit\\\\99-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:02:13Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:03:23Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:03:36Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:03:41Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\routes\\\\web.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:03:46Z","tool":"Bash","cmd":"\"/c/xampp/php/php.exe\" -l \"src/Modules/Orders/OrdersRepository.php\" && \"/c/xampp/php/php.exe\" -l \"src/Modules/Orders/OrdersController.php\" && \"/c/xampp/php/php.exe\" -l \"routes/web.php\"\",\","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:04:08Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\show.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:04:21Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\show.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:04:52Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\app.scss","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:04:57Z","tool":"Bash","cmd":"\"/c/xampp/php/php.exe\" -l \"src/Modules/Orders/OrdersController.php\" \"resources/views/orders/show.php\" 2>&1 | tail -5\",\"description\":\"Lint modified PHP files\"},\"tool_response\":{\"stdout\":","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:05:01Z","tool":"Bash","cmd":"\"/c/xampp/php/php.exe\" -l \"resources/views/orders/show.php\"\",\"description\":\"Lint show.php\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in resources/views/orders/show.php\",\"st","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:05:16Z","tool":"Bash","cmd":"npm run build:css 2>&1 | tail -5\",\"description\":\"Build CSS from SCSS\"},\"tool_response\":{\"stdout\":\"> orderpro@1.0.0 build:css\\n> sass --style=compressed --no-source-map resources/scss/app.sc","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:06:31Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\show.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:09:14Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\database\\\\migrations\\\\20260413_000100_ensure_orders_delivery_payment_columns.sql","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:09:41Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\database\\\\migrations\\\\20260413_000100_ensure_orders_delivery_payment_columns.sql","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:09:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\database\\\\migrations\\\\20260413_000100_ensure_orders_delivery_payment_columns.sql","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:13:39Z","tool":"Bash","cmd":"mkdir -p \".paul/phases/100-preset-scope-print-ux\"\",\"description\":\"Create phase 100 directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,\"noOut","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:15:23Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\100-preset-scope-print-ux\\\\100-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:15:31Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:15:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:17:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\shipments\\\\prepare.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:17:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\shipments\\\\prepare.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:17:13Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\show.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:17:18Z","tool":"Bash","cmd":"php -l \"resources/views/shipments/prepare.php\" && php -l \"resources/views/orders/show.php\"\",\"description\":\"Lint check prepare.php and show.php\"},\"tool_response\":{\"stdout\":\"No syntax erro","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:17:30Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Printing\\\\PrintApiController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:17:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Printing\\\\PrintJobRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:18:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\routes\\\\web.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:18:22Z","tool":"Bash","cmd":"php -l src/Modules/Printing/PrintApiController.php && php -l src/Modules/Printing/PrintJobRepository.php && php -l routes/web.php\",\"description\":\"Lint backend files\"},\"tool_response\":{\"stdout\","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:18:54Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Shipments\\\\ShipmentController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:19:01Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\show.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:19:06Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\shipments\\\\prepare.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:19:35Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\show.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:20:04Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\shipments\\\\prepare.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:20:09Z","tool":"Bash","cmd":"php -l \"resources/views/orders/show.php\" && php -l \"resources/views/shipments/prepare.php\" && php -l \"src/Modules/Shipments/ShipmentController.php\"\",\"description\":\"Final lint check\"},\"tool","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:20:39Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\100-preset-scope-print-ux\\\\100-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:20:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:20:51Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:20:56Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:22:12Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\100-preset-scope-print-ux\\\\100-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:22:35Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\99-order-delivery-payment-edit\\\\99-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:22:44Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:22:49Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:22:55Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:23:00Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:23:33Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-13.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:23:39Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:25:02Z","tool":"Bash","cmd":"wc -l \"C:/visual studio code/projekty/orderPRO/resources/views/orders/list.php\"\",\"description\":\"Count lines\"},\"tool_response\":{\"stdout\":\"140 C:/visual studio code/projekty/orderPRO/resourc","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:25:21Z","tool":"Bash","cmd":"mkdir -p \".paul/phases/101-aged-orders-row-highlight\"\",\"description\":\"Create phase dir\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,\"noOutputExp","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:26:23Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\101-aged-orders-row-highlight\\\\101-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:26:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:26:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:26:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:27:25Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\components\\\\table-list.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:27:45Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Orders\\\\OrdersController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:27:50Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && C:/xampp/php/php.exe -l src/Modules/Orders/OrdersController.php\",\"description\":\"PHP lint check\"},\"tool_response\":{\"stdout\":\"No syntax errors","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:27:56Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && tail -5 resources/scss/app.scss && echo \"---CSS---\" && tail -3 public/assets/css/app.css\",\"description\":\"Check tails\"},\"tool_response\":{\"std","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:28:03Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && ls package.json 2>/dev/null && cat package.json 2>/dev/null | head -30 || echo \"no package.json\"\",\"description\":\"Check build\"},\"tool_response\","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:28:12Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && printf '\\\\n// Aged orders row highlight (4-7+ days since ordered_at)\\\\n.table-list-table tbody tr.order-row-aged > td {\\\\n border-top: 2px soli","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:28:19Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && npm run build:css 2>&1 | tail -20\",\"description\":\"Build CSS\"},\"tool_response\":{\"stdout\":\"> orderpro@1.0.0 build:css\\n> sass --style=compres","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:28:24Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && grep -o \"order-row-aged[^{,]*\" public/assets/css/app.css | head\",\"description\":\"Verify CSS output\"},\"tool_response\":{\"stdout\":\"order-row-a","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:28:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:28:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:30:01Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\101-aged-orders-row-highlight\\\\101-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:30:09Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:30:18Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:30:22Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:30:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:30:36Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && ls .paul/changelog/2026-04-13.md 2>&1\",\"description\":\"Check changelog exists\"},\"tool_response\":{\"stdout\":\".paul/changelog/2026-04-13.md\",\"","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:30:47Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-13.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-13T20:30:53Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-13.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
|
||||
214
.paul/phases/100-preset-scope-print-ux/100-01-PLAN.md
Normal file
214
.paul/phases/100-preset-scope-print-ux/100-01-PLAN.md
Normal 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 —)
|
||||
|
||||
## 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>
|
||||
133
.paul/phases/100-preset-scope-print-ux/100-01-SUMMARY.md
Normal file
133
.paul/phases/100-preset-scope-print-ux/100-01-SUMMARY.md
Normal 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*
|
||||
174
.paul/phases/101-aged-orders-row-highlight/101-01-PLAN.md
Normal file
174
.paul/phases/101-aged-orders-row-highlight/101-01-PLAN.md
Normal 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>
|
||||
106
.paul/phases/101-aged-orders-row-highlight/101-01-SUMMARY.md
Normal file
106
.paul/phases/101-aged-orders-row-highlight/101-01-SUMMARY.md
Normal 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*
|
||||
202
.paul/phases/99-order-delivery-payment-edit/99-01-PLAN.md
Normal file
202
.paul/phases/99-order-delivery-payment-edit/99-01-PLAN.md
Normal 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>
|
||||
32
.paul/phases/99-order-delivery-payment-edit/99-01-SUMMARY.md
Normal file
32
.paul/phases/99-order-delivery-payment-edit/99-01-SUMMARY.md
Normal 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*
|
||||
14
.vscode/ftp-kr.sync.cache.json
vendored
14
.vscode/ftp-kr.sync.cache.json
vendored
@@ -103,6 +103,12 @@
|
||||
"size": 1109,
|
||||
"lmtime": 1772752935681,
|
||||
"modified": false
|
||||
},
|
||||
"backfill_shipped_status_98.php": {
|
||||
"type": "-",
|
||||
"size": 2843,
|
||||
"lmtime": 1776068930550,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"bootstrap": {
|
||||
@@ -3338,8 +3344,8 @@
|
||||
},
|
||||
"AllegroOrderImportService.php": {
|
||||
"type": "-",
|
||||
"size": 32739,
|
||||
"lmtime": 1775948051353,
|
||||
"size": 32754,
|
||||
"lmtime": 1776068853142,
|
||||
"modified": false
|
||||
},
|
||||
"AllegroOrdersSyncService.php": {
|
||||
@@ -3590,8 +3596,8 @@
|
||||
},
|
||||
"ShopproOrdersSyncService.php": {
|
||||
"type": "-",
|
||||
"size": 15154,
|
||||
"lmtime": 1775674624190,
|
||||
"size": 15169,
|
||||
"lmtime": 1776068857797,
|
||||
"modified": false
|
||||
},
|
||||
"ShopproOrderSyncStateRepository.php": {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
-- =============================================================
|
||||
-- COMPENSATING MIGRATION
|
||||
-- Kompensuje: 20260302_000018_create_orders_tables_and_schedule.sql
|
||||
-- Powod: Oryginalna migracja 000018 uzywa CREATE TABLE IF NOT EXISTS.
|
||||
-- Na srodowiskach gdzie tabela `orders` istniala wczesniej bez
|
||||
-- kolumn `delivery_method` / `payment_method`, migracja 000018
|
||||
-- nic nie zrobila i kolumny sa brakujace, mimo ze schemat
|
||||
-- deklaruje je jako standardowe pola importu.
|
||||
-- Status: Idempotentna - bezpieczna do ponownego uruchomienia.
|
||||
-- =============================================================
|
||||
SET @sql := (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `orders` ADD COLUMN `payment_method` VARCHAR(128) NULL',
|
||||
'SELECT 1')
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'orders'
|
||||
AND column_name = 'payment_method'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `orders` ADD COLUMN `delivery_method` VARCHAR(128) NULL',
|
||||
'SELECT 1')
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'orders'
|
||||
AND column_name = 'delivery_method'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
File diff suppressed because one or more lines are too long
@@ -1680,6 +1680,92 @@ details[open] > .order-statuses-side__title .order-statuses-side__arrow {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.order-payment-shipping {
|
||||
.section-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-edit-inline {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
color: #6b7280;
|
||||
padding: 3px 5px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s, background-color 0.15s, color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: #f3f4f6;
|
||||
color: #111827;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .btn-edit-inline {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.order-details-edit-form {
|
||||
margin-top: 12px;
|
||||
padding: 10px;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
|
||||
.form-row {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
color: #374151;
|
||||
font-weight: 500;
|
||||
|
||||
input[type="text"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 3px;
|
||||
padding: 5px 7px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
label.checkbox-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-weight: 400;
|
||||
|
||||
input {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #eef2ff;
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.payment-summary {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
@@ -2752,3 +2838,16 @@ body.no-scroll {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Aged orders row highlight (4-7+ days since ordered_at)
|
||||
.table-list-table tbody tr.order-row-aged > td {
|
||||
border-top: 2px solid transparent;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
.table-list-table tbody tr.order-row-aged > td:first-child { border-left: 2px solid transparent; }
|
||||
.table-list-table tbody tr.order-row-aged > td:last-child { border-right: 2px solid transparent; }
|
||||
|
||||
.table-list-table tbody tr.order-row-aged-4 > td { border-color: #f8b4b4; }
|
||||
.table-list-table tbody tr.order-row-aged-5 > td { border-color: #f28282; }
|
||||
.table-list-table tbody tr.order-row-aged-6 > td { border-color: #e74c3c; }
|
||||
.table-list-table tbody tr.order-row-aged-7 > td { border-color: #991b1b; }
|
||||
|
||||
@@ -217,7 +217,8 @@ $buildUrl = static function (array $params = []) use ($basePath, $query): string
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($rows as $row): ?>
|
||||
<tr>
|
||||
<?php $rowClass = trim((string) ($row['_row_class'] ?? '')); ?>
|
||||
<tr<?= $rowClass !== '' ? ' class="' . $e($rowClass) . '"' : '' ?>>
|
||||
<?php if ($selectable): ?>
|
||||
<?php $selectValue = $row[$selectValueKey] ?? ''; ?>
|
||||
<td class="table-select-col">
|
||||
|
||||
@@ -216,10 +216,73 @@ foreach ($addressesList as $address) {
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<article class="card order-payment-shipping">
|
||||
<div class="section-title-row">
|
||||
<h3 class="section-title"><?= $e($t('orders.details.payment_shipping')) ?></h3>
|
||||
<dl class="order-kv mt-12">
|
||||
<button type="button" class="btn-edit-inline js-toggle-details-edit" title="Edytuj formę dostawy i płatności" aria-label="Edytuj">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<?php
|
||||
$deliveryMethodValue = trim((string) ($orderRow['delivery_method'] ?? ''));
|
||||
$paymentMethodValue = trim((string) ($orderRow['payment_method'] ?? ''));
|
||||
$paymentTypeCurrent = strtoupper(trim((string) ($orderRow['external_payment_type_id'] ?? '')));
|
||||
$isCodCurrent = \App\Core\Support\StringHelper::isCodPayment($paymentTypeCurrent);
|
||||
?>
|
||||
<form method="POST" action="/orders/<?= (int) $orderId ?>/details/update" class="order-details-edit-form js-details-edit-form" style="display:none">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<div class="form-row">
|
||||
<label>Forma dostawy
|
||||
<input type="text" name="delivery_method" value="<?= $e($deliveryMethodValue) ?>" maxlength="128" list="js-delivery-methods-list">
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Forma płatności
|
||||
<input type="text" name="payment_method" value="<?= $e($paymentMethodValue) ?>" maxlength="128" list="js-payment-methods-list">
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" name="is_cod" value="1" <?= $isCodCurrent ? 'checked' : '' ?>>
|
||||
<span>Pobranie (COD) — ustawi typ płatności na <code>CASH_ON_DELIVERY</code></span>
|
||||
</label>
|
||||
</div>
|
||||
<datalist id="js-delivery-methods-list">
|
||||
<?php foreach (($deliveryMethodSuggestions ?? []) as $sug): ?>
|
||||
<option value="<?= $e((string) $sug) ?>"></option>
|
||||
<?php endforeach; ?>
|
||||
</datalist>
|
||||
<datalist id="js-payment-methods-list">
|
||||
<?php foreach (($paymentMethodSuggestions ?? []) as $sug): ?>
|
||||
<option value="<?= $e((string) $sug) ?>"></option>
|
||||
<?php endforeach; ?>
|
||||
</datalist>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Zapisz</button>
|
||||
<button type="button" class="btn btn-sm js-cancel-details-edit">Anuluj</button>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
var toggleBtn = document.querySelector('.js-toggle-details-edit');
|
||||
var cancelBtn = document.querySelector('.js-cancel-details-edit');
|
||||
var form = document.querySelector('.js-details-edit-form');
|
||||
var view = document.querySelector('.js-details-view');
|
||||
if (!toggleBtn || !form || !view) return;
|
||||
toggleBtn.addEventListener('click', function(){
|
||||
form.style.display = 'block';
|
||||
view.style.display = 'none';
|
||||
});
|
||||
if (cancelBtn) cancelBtn.addEventListener('click', function(){
|
||||
form.style.display = 'none';
|
||||
view.style.display = '';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<dl class="order-kv mt-12 js-details-view">
|
||||
<dt><?= $e($t('orders.details.fields.payment_status')) ?></dt><dd><?= $e((string) ($orderRow['payment_status'] ?? '-')) ?></dd>
|
||||
<dt>Forma dostawy</dt><dd><?= $e($deliveryMethodValue !== '' ? $deliveryMethodValue : '-') ?></dd>
|
||||
<dt>Forma płatności</dt><dd><?= $e($paymentMethodValue !== '' ? $paymentMethodValue : '-') ?></dd>
|
||||
<dt>Typ platnosci</dt>
|
||||
<dd>
|
||||
<?php
|
||||
@@ -514,12 +577,15 @@ foreach ($addressesList as $address) {
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<button type="submit" class="btn btn--sm btn--secondary">Pobierz</button>
|
||||
</form>
|
||||
<?php elseif ($pkgStatus !== 'error'): ?>
|
||||
-
|
||||
<?php endif; ?>
|
||||
<?php if (in_array($pkgStatus, ['label_ready', 'created'], true)): ?>
|
||||
<?php if (in_array((int) ($pkg['id'] ?? 0), $pendingPrintIds, true)): ?>
|
||||
<button type="button" class="btn btn--sm btn--danger" disabled style="white-space:nowrap">W kolejce</button>
|
||||
<button type="button"
|
||||
class="btn btn--sm btn--danger js-print-queue-pending"
|
||||
data-package-id="<?= $e((string) ($pkg['id'] ?? 0)) ?>"
|
||||
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
|
||||
disabled
|
||||
style="white-space:nowrap">W kolejce</button>
|
||||
<?php else: ?>
|
||||
<button type="button"
|
||||
class="btn btn--sm btn--secondary btn-print-label"
|
||||
@@ -831,6 +897,58 @@ foreach ($addressesList as $address) {
|
||||
});
|
||||
}
|
||||
|
||||
// --- Print queue polling: rewert przycisku "W kolejce" -> "Drukuj" po wydrukowaniu ---
|
||||
var printQueuePending = {}; // packageId -> btn element
|
||||
var printQueueTimer = null;
|
||||
var printQueueTicks = 0;
|
||||
var PRINT_QUEUE_MAX_TICKS = 120; // 120 * 3s = 6 min
|
||||
|
||||
function revertPrintButton(btn) {
|
||||
if (!btn) return;
|
||||
btn.innerHTML = 'Drukuj';
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('btn--danger');
|
||||
btn.classList.remove('js-print-queue-pending');
|
||||
btn.classList.add('btn--secondary');
|
||||
btn.classList.add('btn-print-label');
|
||||
btn.setAttribute('title', 'Wyslij do drukarki');
|
||||
}
|
||||
|
||||
function stopPrintQueuePoll() {
|
||||
if (printQueueTimer) { clearInterval(printQueueTimer); printQueueTimer = null; }
|
||||
printQueueTicks = 0;
|
||||
}
|
||||
|
||||
function pollPrintQueueTick() {
|
||||
printQueueTicks += 1;
|
||||
if (printQueueTicks > PRINT_QUEUE_MAX_TICKS) { stopPrintQueuePoll(); return; }
|
||||
var ids = Object.keys(printQueuePending);
|
||||
if (ids.length === 0) { stopPrintQueuePoll(); return; }
|
||||
fetch('/api/print/jobs/status?package_ids=' + encodeURIComponent(ids.join(',')), { credentials: 'same-origin' })
|
||||
.then(function (r) { return r.ok ? r.json() : { pending: ids.map(Number) }; })
|
||||
.then(function (data) {
|
||||
var pending = (data && Array.isArray(data.pending)) ? data.pending.map(String) : [];
|
||||
Object.keys(printQueuePending).forEach(function (pid) {
|
||||
if (pending.indexOf(String(pid)) === -1) {
|
||||
revertPrintButton(printQueuePending[pid]);
|
||||
delete printQueuePending[pid];
|
||||
}
|
||||
});
|
||||
if (Object.keys(printQueuePending).length === 0) stopPrintQueuePoll();
|
||||
})
|
||||
.catch(function () { /* ignore transient */ });
|
||||
}
|
||||
|
||||
function watchPrintQueueButton(btn) {
|
||||
var pid = btn.getAttribute('data-package-id');
|
||||
if (!pid) return;
|
||||
printQueuePending[pid] = btn;
|
||||
if (!printQueueTimer) {
|
||||
printQueueTicks = 0;
|
||||
printQueueTimer = setInterval(pollPrintQueueTick, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// Print label button handler (delegated for dynamically added buttons)
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('.btn-print-label');
|
||||
@@ -854,7 +972,10 @@ foreach ($addressesList as $address) {
|
||||
btn.innerHTML = 'W kolejce';
|
||||
btn.disabled = true;
|
||||
btn.classList.remove('btn--secondary');
|
||||
btn.classList.remove('btn-print-label');
|
||||
btn.classList.add('btn--danger');
|
||||
btn.classList.add('js-print-queue-pending');
|
||||
watchPrintQueueButton(btn);
|
||||
} else {
|
||||
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany blad';
|
||||
if (window.OrderProAlerts) { window.OrderProAlerts.show({ message: msg, type: 'error' }); }
|
||||
@@ -869,6 +990,41 @@ foreach ($addressesList as $address) {
|
||||
});
|
||||
});
|
||||
|
||||
// Przy zaladowaniu strony: uruchom polling dla przyciskow juz w kolejce
|
||||
document.querySelectorAll('.js-print-queue-pending').forEach(function (btn) {
|
||||
watchPrintQueueButton(btn);
|
||||
});
|
||||
|
||||
// Auto-click ostatniej etykiety po utworzeniu przesylki (?printLast=1)
|
||||
(function autoClickLastLabel() {
|
||||
if (!/[?&]printLast=1\b/.test(location.search)) return;
|
||||
var attempts = 0;
|
||||
var MAX_ATTEMPTS = 30; // 30 * 1s = 30s
|
||||
var timer = setInterval(function () {
|
||||
attempts += 1;
|
||||
if (attempts > MAX_ATTEMPTS) {
|
||||
clearInterval(timer);
|
||||
cleanupPrintLastParam();
|
||||
return;
|
||||
}
|
||||
var buttons = document.querySelectorAll('.btn-print-label');
|
||||
if (buttons.length > 0) {
|
||||
var last = buttons[buttons.length - 1];
|
||||
clearInterval(timer);
|
||||
cleanupPrintLastParam();
|
||||
last.click();
|
||||
}
|
||||
}, 1000);
|
||||
})();
|
||||
|
||||
function cleanupPrintLastParam() {
|
||||
try {
|
||||
var url = new URL(location.href);
|
||||
url.searchParams.delete('printLast');
|
||||
history.replaceState({}, '', url.toString());
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -412,12 +412,15 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
<?php endif; ?>
|
||||
<?php elseif ($pkgShipmentId !== '' && $pkgStatus === 'created'): ?>
|
||||
<span class="muted">Generowanie etykiety...</span>
|
||||
<?php else: ?>
|
||||
-
|
||||
<?php endif; ?>
|
||||
<?php if (in_array($pkgStatus, ['label_ready', 'created'], true)): ?>
|
||||
<?php if (in_array($pkgId, $pendingPrintIds, true)): ?>
|
||||
<button type="button" class="btn btn--sm btn--danger" disabled style="white-space:nowrap">W kolejce</button>
|
||||
<button type="button"
|
||||
class="btn btn--sm btn--danger js-print-queue-pending"
|
||||
data-package-id="<?= $e((string) $pkgId) ?>"
|
||||
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
|
||||
disabled
|
||||
style="white-space:nowrap">W kolejce</button>
|
||||
<?php else: ?>
|
||||
<button type="button"
|
||||
class="btn btn--sm btn--secondary btn-print-label"
|
||||
@@ -765,6 +768,58 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
});
|
||||
});
|
||||
|
||||
// --- Print queue polling (shared mechanika: rewert "W kolejce" -> "Drukuj") ---
|
||||
var printQueuePending = {};
|
||||
var printQueueTimer = null;
|
||||
var printQueueTicks = 0;
|
||||
var PRINT_QUEUE_MAX_TICKS = 120;
|
||||
|
||||
function revertPrintButton(btn) {
|
||||
if (!btn) return;
|
||||
btn.innerHTML = 'Drukuj';
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('btn--danger');
|
||||
btn.classList.remove('js-print-queue-pending');
|
||||
btn.classList.add('btn--secondary');
|
||||
btn.classList.add('btn-print-label');
|
||||
btn.setAttribute('title', 'Wyslij do drukarki');
|
||||
}
|
||||
|
||||
function stopPrintQueuePoll() {
|
||||
if (printQueueTimer) { clearInterval(printQueueTimer); printQueueTimer = null; }
|
||||
printQueueTicks = 0;
|
||||
}
|
||||
|
||||
function pollPrintQueueTick() {
|
||||
printQueueTicks += 1;
|
||||
if (printQueueTicks > PRINT_QUEUE_MAX_TICKS) { stopPrintQueuePoll(); return; }
|
||||
var ids = Object.keys(printQueuePending);
|
||||
if (ids.length === 0) { stopPrintQueuePoll(); return; }
|
||||
fetch('/api/print/jobs/status?package_ids=' + encodeURIComponent(ids.join(',')), { credentials: 'same-origin' })
|
||||
.then(function (r) { return r.ok ? r.json() : { pending: ids.map(Number) }; })
|
||||
.then(function (data) {
|
||||
var pending = (data && Array.isArray(data.pending)) ? data.pending.map(String) : [];
|
||||
Object.keys(printQueuePending).forEach(function (pid) {
|
||||
if (pending.indexOf(String(pid)) === -1) {
|
||||
revertPrintButton(printQueuePending[pid]);
|
||||
delete printQueuePending[pid];
|
||||
}
|
||||
});
|
||||
if (Object.keys(printQueuePending).length === 0) stopPrintQueuePoll();
|
||||
})
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
function watchPrintQueueButton(btn) {
|
||||
var pid = btn.getAttribute('data-package-id');
|
||||
if (!pid) return;
|
||||
printQueuePending[pid] = btn;
|
||||
if (!printQueueTimer) {
|
||||
printQueueTicks = 0;
|
||||
printQueueTimer = setInterval(pollPrintQueueTick, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// Print label button handler
|
||||
document.querySelectorAll('.btn-print-label').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
@@ -786,7 +841,10 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
btn.innerHTML = 'W kolejce';
|
||||
btn.disabled = true;
|
||||
btn.classList.remove('btn--secondary');
|
||||
btn.classList.remove('btn-print-label');
|
||||
btn.classList.add('btn--danger');
|
||||
btn.classList.add('js-print-queue-pending');
|
||||
watchPrintQueueButton(btn);
|
||||
} else {
|
||||
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany blad';
|
||||
if (window.OrderProAlerts) {
|
||||
@@ -806,6 +864,11 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
});
|
||||
});
|
||||
|
||||
// Przy zaladowaniu strony: uruchom polling dla przyciskow juz w kolejce
|
||||
document.querySelectorAll('.js-print-queue-pending').forEach(function (btn) {
|
||||
watchPrintQueueButton(btn);
|
||||
});
|
||||
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var autoCheckId = params.get('check');
|
||||
|
||||
@@ -955,47 +1018,20 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
}
|
||||
|
||||
// --- Apply preset (autofill form) ---
|
||||
// Preset nadpisuje wylacznie rozmiary paczki i wage. Forma dostawy (carrier,
|
||||
// serwis, punkt nadania, format etykiety) pozostaje bez zmian.
|
||||
function applyPreset(preset) {
|
||||
var carrierSelect = document.getElementById('shipment-carrier-select');
|
||||
var hiddenInput = document.getElementById('shipment-delivery-service');
|
||||
var credentialsInput = document.getElementById('shipment-credentials-id');
|
||||
var carrierInput = document.getElementById('shipment-carrier-id');
|
||||
var providerInput = document.getElementById('shipment-provider-code');
|
||||
|
||||
if (!carrierSelect) return;
|
||||
|
||||
// Set carrier — use native change so existing handler shows correct panel
|
||||
carrierSelect.value = preset.carrier || '';
|
||||
if (carrierSelect._syncTrigger) carrierSelect._syncTrigger();
|
||||
carrierSelect.dispatchEvent(new Event('change'));
|
||||
|
||||
// The existing change handler clears hidden fields and resets selects.
|
||||
// We must wait for that to finish, then override with preset values.
|
||||
setTimeout(function () {
|
||||
// Hidden fields — set AFTER the change handler cleared them
|
||||
if (hiddenInput) hiddenInput.value = preset.delivery_method_id || '';
|
||||
if (credentialsInput) credentialsInput.value = preset.credentials_id || '';
|
||||
if (carrierInput) carrierInput.value = preset.carrier_id || '';
|
||||
if (providerInput) providerInput.value = preset.provider_code || '';
|
||||
|
||||
// Package fields
|
||||
setFieldValue('package_type', preset.package_type || 'PACKAGE');
|
||||
setFieldValue('length_cm', preset.length_cm || '25');
|
||||
setFieldValue('width_cm', preset.width_cm || '20');
|
||||
setFieldValue('height_cm', preset.height_cm || '8');
|
||||
setFieldValue('weight_kg', preset.weight_kg || '1');
|
||||
setFieldValue('sender_point_id', preset.sender_point_id || '');
|
||||
setFieldValue('label_format', preset.label_format || 'PDF');
|
||||
|
||||
// Select delivery service in the correct panel
|
||||
selectDeliveryService(preset);
|
||||
|
||||
// Auto-submit after autofill completes
|
||||
setTimeout(function () {
|
||||
var form = document.getElementById('shipment-form');
|
||||
if (form) form.submit();
|
||||
}, 300);
|
||||
}, 200);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function setFieldValue(name, value) {
|
||||
|
||||
@@ -422,6 +422,7 @@ return static function (Application $app): void {
|
||||
$router->get('/orders/list', [$ordersController, 'index'], [$authMiddleware]);
|
||||
$router->get('/orders/{id}', [$ordersController, 'show'], [$authMiddleware]);
|
||||
$router->post('/orders/{id}/status', [$ordersController, 'updateStatus'], [$authMiddleware]);
|
||||
$router->post('/orders/{id}/details/update', [$ordersController, 'updateDetails'], [$authMiddleware]);
|
||||
$router->post('/orders/{id}/send-email', [$ordersController, 'sendEmail'], [$authMiddleware]);
|
||||
$router->post('/orders/{id}/email-preview', [$ordersController, 'emailPreview'], [$authMiddleware]);
|
||||
$router->get('/api/orders/search', [$ordersController, 'quickSearch'], [$authMiddleware]);
|
||||
@@ -538,6 +539,7 @@ return static function (Application $app): void {
|
||||
|
||||
// Print API — session auth (from orderPRO UI)
|
||||
$router->post('/api/print/jobs', [$printApiController, 'createJob'], [$authMiddleware]);
|
||||
$router->get('/api/print/jobs/status', [$printApiController, 'status'], [$authMiddleware]);
|
||||
|
||||
// Print API — API key auth (from Windows client)
|
||||
$router->get('/api/print/jobs/pending', [$printApiController, 'listPending'], [$apiKeyMiddleware]);
|
||||
|
||||
@@ -264,6 +264,67 @@ final class OrdersController
|
||||
return Response::html($html);
|
||||
}
|
||||
|
||||
public function updateDetails(Request $request): Response
|
||||
{
|
||||
$orderId = max(0, (int) $request->input('id', 0));
|
||||
if ($orderId <= 0) {
|
||||
return Response::html('Not found', 404);
|
||||
}
|
||||
|
||||
$csrfToken = (string) $request->input('_token', '');
|
||||
if (!Csrf::validate($csrfToken)) {
|
||||
Flash::set('order.error', $this->translator->get('auth.errors.csrf_expired'));
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
$details = $this->orders->findDetails($orderId);
|
||||
if (!is_array($details['order'] ?? null)) {
|
||||
return Response::html('Not found', 404);
|
||||
}
|
||||
|
||||
$deliveryMethodRaw = trim((string) $request->input('delivery_method', ''));
|
||||
$paymentMethodRaw = trim((string) $request->input('payment_method', ''));
|
||||
$isCod = (string) $request->input('is_cod', '') === '1';
|
||||
|
||||
if ($deliveryMethodRaw === '' && $paymentMethodRaw === '') {
|
||||
Flash::set('order.error', 'Podaj formę dostawy lub formę płatności.');
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
$deliveryMethod = $deliveryMethodRaw !== '' ? $deliveryMethodRaw : null;
|
||||
$paymentMethod = $paymentMethodRaw !== '' ? $paymentMethodRaw : null;
|
||||
|
||||
$externalPaymentTypeId = null;
|
||||
$currentOrder = (array) $details['order'];
|
||||
$currentExternal = strtoupper(trim((string) ($currentOrder['external_payment_type_id'] ?? '')));
|
||||
$currentlyCod = StringHelper::isCodPayment($currentExternal);
|
||||
if ($isCod && !$currentlyCod) {
|
||||
$externalPaymentTypeId = 'CASH_ON_DELIVERY';
|
||||
} elseif (!$isCod && $currentlyCod) {
|
||||
$externalPaymentTypeId = '';
|
||||
}
|
||||
|
||||
$user = $this->auth->user();
|
||||
$actorName = is_array($user) ? trim((string) ($user['name'] ?? $user['email'] ?? '')) : null;
|
||||
|
||||
$changed = $this->orders->updateDeliveryAndPayment(
|
||||
$orderId,
|
||||
$deliveryMethod,
|
||||
$paymentMethod,
|
||||
$externalPaymentTypeId,
|
||||
'user',
|
||||
$actorName !== '' ? $actorName : null
|
||||
);
|
||||
|
||||
if ($changed) {
|
||||
Flash::set('order.success', 'Dane zamówienia zostały zaktualizowane.');
|
||||
} else {
|
||||
Flash::set('order.error', 'Brak zmian do zapisania.');
|
||||
}
|
||||
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
public function updateStatus(Request $request): Response
|
||||
{
|
||||
$isAjax = strtolower($request->header('X-Requested-With')) === 'xmlhttprequest';
|
||||
@@ -405,9 +466,28 @@ final class OrdersController
|
||||
$documents
|
||||
),
|
||||
'ordered_at' => (string) ($row['ordered_at'] ?? ''),
|
||||
'_row_class' => $this->agedRowClass((string) ($row['ordered_at'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
private function agedRowClass(string $orderedAt): string
|
||||
{
|
||||
if ($orderedAt === '') {
|
||||
return '';
|
||||
}
|
||||
$ts = strtotime($orderedAt);
|
||||
if ($ts === false) {
|
||||
return '';
|
||||
}
|
||||
$ageDays = (int) floor((time() - $ts) / 86400);
|
||||
if ($ageDays < 4) {
|
||||
return '';
|
||||
}
|
||||
$level = $ageDays >= 7 ? 7 : $ageDays;
|
||||
|
||||
return 'order-row-aged order-row-aged-' . $level;
|
||||
}
|
||||
|
||||
private function statusBadge(string $statusCode, string $statusLabel, string $colorHex = ''): string
|
||||
{
|
||||
$label = $statusLabel !== '' ? $statusLabel : '-';
|
||||
|
||||
@@ -784,6 +784,93 @@ final class OrdersRepository
|
||||
/**
|
||||
* @param array<string, mixed>|null $details
|
||||
*/
|
||||
/**
|
||||
* Aktualizuje formę dostawy i/lub formę płatności zamówienia. Zapisuje wpis do activity log.
|
||||
*
|
||||
* @return bool true gdy faktycznie nastąpiła zmiana
|
||||
*/
|
||||
public function updateDeliveryAndPayment(
|
||||
int $orderId,
|
||||
?string $deliveryMethod,
|
||||
?string $paymentMethod,
|
||||
?string $externalPaymentTypeId,
|
||||
string $actorType = 'user',
|
||||
?string $actorName = null
|
||||
): bool {
|
||||
if ($orderId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT id, delivery_method, payment_method, external_payment_type_id FROM orders WHERE id = :id LIMIT 1'
|
||||
);
|
||||
$stmt->execute(['id' => $orderId]);
|
||||
$before = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!is_array($before)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
$params = ['id' => $orderId];
|
||||
$changed = [];
|
||||
|
||||
if ($deliveryMethod !== null) {
|
||||
$oldValue = (string) ($before['delivery_method'] ?? '');
|
||||
if ($oldValue !== $deliveryMethod) {
|
||||
$updates[] = 'delivery_method = :delivery_method';
|
||||
$params['delivery_method'] = $deliveryMethod;
|
||||
$changed['delivery_method'] = ['before' => $oldValue, 'after' => $deliveryMethod];
|
||||
}
|
||||
}
|
||||
|
||||
if ($paymentMethod !== null) {
|
||||
$oldValue = (string) ($before['payment_method'] ?? '');
|
||||
if ($oldValue !== $paymentMethod) {
|
||||
$updates[] = 'payment_method = :payment_method';
|
||||
$params['payment_method'] = $paymentMethod;
|
||||
$changed['payment_method'] = ['before' => $oldValue, 'after' => $paymentMethod];
|
||||
}
|
||||
}
|
||||
|
||||
if ($externalPaymentTypeId !== null) {
|
||||
$oldValue = (string) ($before['external_payment_type_id'] ?? '');
|
||||
if ($oldValue !== $externalPaymentTypeId) {
|
||||
$updates[] = 'external_payment_type_id = :external_payment_type_id';
|
||||
$params['external_payment_type_id'] = $externalPaymentTypeId;
|
||||
$changed['external_payment_type_id'] = ['before' => $oldValue, 'after' => $externalPaymentTypeId];
|
||||
}
|
||||
}
|
||||
|
||||
if ($updates === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$updates[] = 'updated_at = NOW()';
|
||||
$sql = 'UPDATE orders SET ' . implode(', ', $updates) . ' WHERE id = :id';
|
||||
$update = $this->pdo->prepare($sql);
|
||||
$update->execute($params);
|
||||
|
||||
$summaryParts = [];
|
||||
if (isset($changed['delivery_method'])) {
|
||||
$summaryParts[] = 'forma dostawy';
|
||||
}
|
||||
if (isset($changed['payment_method']) || isset($changed['external_payment_type_id'])) {
|
||||
$summaryParts[] = 'forma płatności';
|
||||
}
|
||||
$summary = 'Zmiana danych zamówienia: ' . implode(', ', $summaryParts);
|
||||
|
||||
$this->recordActivity(
|
||||
$orderId,
|
||||
'details_change',
|
||||
$summary,
|
||||
$changed,
|
||||
$actorType,
|
||||
$actorName
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function recordActivity(
|
||||
int $orderId,
|
||||
string $eventType,
|
||||
|
||||
@@ -145,6 +145,33 @@ final class PrintApiController
|
||||
);
|
||||
}
|
||||
|
||||
public function status(Request $request): Response
|
||||
{
|
||||
$raw = trim((string) $request->input('package_ids', ''));
|
||||
if ($raw === '') {
|
||||
return Response::json(['pending' => []]);
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
foreach (explode(',', $raw) as $part) {
|
||||
$id = (int) trim($part);
|
||||
if ($id > 0) {
|
||||
$ids[] = $id;
|
||||
}
|
||||
if (count($ids) >= 100) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ids === []) {
|
||||
return Response::json(['pending' => []]);
|
||||
}
|
||||
|
||||
$pending = $this->printJobs->filterPendingPackageIds($ids);
|
||||
|
||||
return Response::json(['pending' => $pending]);
|
||||
}
|
||||
|
||||
public function markComplete(Request $request): Response
|
||||
{
|
||||
$jobId = (int) $request->input('id', 0);
|
||||
|
||||
@@ -107,6 +107,34 @@ final class PrintJobRepository
|
||||
return is_array($rows) ? array_map('intval', $rows) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $packageIds
|
||||
* @return list<int>
|
||||
*/
|
||||
public function filterPendingPackageIds(array $packageIds): array
|
||||
{
|
||||
if ($packageIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$placeholders = [];
|
||||
$bindings = [];
|
||||
foreach (array_values($packageIds) as $index => $id) {
|
||||
$key = ':pkg_' . $index;
|
||||
$placeholders[] = $key;
|
||||
$bindings[$key] = (int) $id;
|
||||
}
|
||||
|
||||
$sql = 'SELECT DISTINCT package_id FROM print_jobs '
|
||||
. "WHERE status = 'pending' AND package_id IN (" . implode(', ', $placeholders) . ')';
|
||||
|
||||
$statement = $this->pdo->prepare($sql);
|
||||
$statement->execute($bindings);
|
||||
$rows = $statement->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
return is_array($rows) ? array_map('intval', $rows) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
|
||||
@@ -218,7 +218,7 @@ final class ShipmentController
|
||||
|
||||
$this->triggerShipmentCreatedAutomation($orderId, $packageId, $providerCode);
|
||||
Flash::set('order.success', 'Przesylka utworzona. Sprawdz status w zakladce Przesylki.');
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
return Response::redirect('/orders/' . $orderId . '?printLast=1');
|
||||
} catch (Throwable $exception) {
|
||||
$this->ordersRepository->recordActivity(
|
||||
$orderId,
|
||||
|
||||
Reference in New Issue
Block a user