feat(108): delivery status management
Phase 108 complete (v3.2 milestone):
Plan 108-01 — Delivery Status DB & CRUD:
- Tabela delivery_statuses z seedem 11 statusow systemowych
- DeliveryStatusRepository (CRUD + per-request static cache)
- DeliveryStatus::setRepository() — DB fallback dla static final class
- Panel /settings/delivery-statuses (zakladki Statusy + Mapowanie)
- Sidebar przebudowany: Statusy zamowien + Statusy przesylek
Plan 108-02 — Automation Dropdowns z DB + UI Refactor:
- Dropdowny automatyzacji ladowane z DB (warunek shipment_status + akcja update_shipment_status)
- Walidacja przez DeliveryStatus::getAllStatuses()
- Osobna podstrona formularza CRUD (delivery-status-form.php)
- Lista uproszczona: rename Terminal -> Koncowy, usunieta kolumna Typ
- BREAKING: drop backward compat dla starych grupowych kluczy automatyzacji
- Bug fix: path params w DeliveryStatusesController via \$request->input('id')
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,8 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|-------|
|
||||
| Version | 3.1.0 |
|
||||
| Status | v3.1 shipped — all phases complete |
|
||||
| Version | 3.2.0 |
|
||||
| Status | v3.2 shipped — Delivery Status Management complete |
|
||||
| Last Updated | 2026-04-27 |
|
||||
|
||||
## Requirements
|
||||
@@ -111,6 +111,7 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
||||
- [x] Wersja mobilna — modul po module (v3.0) — shipped across phases 52–105
|
||||
- [x] Alert o kliencie z historia zwrotow: badge w liscie zamowien (kolumna buyer) + czerwony banner u gory szczegolow zamowienia; matching OR po email/phone/name; `<details>` z lista zwroconych zamowien — Phase 106
|
||||
- [x] Idempotentna jednorazowa wysylka e-mail per zamowienie: tabela deduplikacji `automation_email_once_deliveries` (UNIQUE KEY rule_id+action_id+order_id), checkbox "Wyslij tylko raz" w konfiguracji akcji, markSent() tylko po sukcesie — Phase 107
|
||||
- [x] Delivery Status Management: tabela `delivery_statuses` z CRUD panelem `/settings/delivery-statuses`, `DeliveryStatus::setRepository()` z DB fallbackiem, integracja DB-driven w dropdownach automatyzacji (warunek shipment_status + akcja update_shipment_status), osobna podstrona formularza CRUD (BREAKING: drop backward compat dla starych grupowych kluczy automatyzacji) — Phase 108
|
||||
|
||||
### Deferred
|
||||
|
||||
@@ -193,6 +194,9 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API
|
||||
| Statistics netto fallback `ROUND(gross / 1.23, 2)` gdy `total_without_tax` puste | shopPRO nie wysyla netto ani w zamowieniu ani w `order_items`; tymczasowy fallback — docelowy fix w `.paul/TODO.md` (STAT-NET) | 2026-04-19 | Active |
|
||||
| ON DUPLICATE KEY UPDATE created_at = created_at dla idempotentnego markSent() | Unikniece silent failure i race condition przy rownolegych cronach; thread-safe bez wyjatkow | 2026-04-25 | Active |
|
||||
| send_once_per_order opt-in przez checkbox (domyslnie off) | Wsteczna zgodnosc — istniejace reguly nie zmieniaja zachowania; markSent() tylko po sukcesie wysylki | 2026-04-25 | Active |
|
||||
| DeliveryStatus::setRepository() pattern: DB fallback dla static final class | Operator dodaje status w UI bez zmian kodu; `getAllOptions()`/`label()`/`getColor()` ladują z DB gdy repo ustawione, fallback na hardcoded ALL_STATUSES/LABEL_PL | 2026-04-27 | Active |
|
||||
| Drop backward compat dla starych grupowych kluczy automatyzacji (Phase 108-02) | Kolizja semantyczna: stary `picked_up` mapował na `delivered`, nowy klucz DB `picked_up` to "Odebrana przez kuriera" — odwrotne końce cyklu. Hybrid evaluation by silently dawała wrong matches | 2026-04-27 | Active |
|
||||
| Path params w controllerach via `$request->input('id')` (nie jako argumenty metody) | Konwencja routera projektu: handler wywoływany z jednym argumentem `$request`, params siedzą jako attributes — `ReceiptController::show()` jako wzorzec | 2026-04-27 | Active |
|
||||
|
||||
## Success Metrics
|
||||
|
||||
@@ -224,6 +228,6 @@ Quick Reference:
|
||||
|
||||
---
|
||||
*PROJECT.md — Updated when requirements or context change*
|
||||
*Last updated: 2026-04-27 after v3.1 Operational Enhancements milestone completion (Phase 107 Automation Email Send Once)*
|
||||
*Last updated: 2026-04-27 after v3.2 Delivery Status Management milestone completion (Phase 108)*
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ orderPRO to narzedzie do wielokanalowego zarzadzania sprzedaza. Projekt przechod
|
||||
|
||||
## Current Milestone
|
||||
|
||||
Brak aktywnego milestone — v3.1 zamkniety. Nastepny milestone do zaplanowania.
|
||||
Brak aktywnego milestone — v3.2 zamkniety. Nastepny milestone do zaplanowania.
|
||||
|
||||
## Next Milestone
|
||||
|
||||
@@ -19,6 +19,19 @@ Kandydaci w kolejce:
|
||||
|
||||
## Completed Milestones
|
||||
|
||||
<details>
|
||||
<summary>v3.2 Delivery Status Management - 2026-04-27 (1 phase, 2 plans)</summary>
|
||||
|
||||
Wyniesienie znormalizowanych statusow przesylek do tabeli DB z CRUD panelem oraz pelna integracja DB-driven w dropdownach automatyzacji.
|
||||
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 108 | Delivery Status Management | 2/2 | Complete |
|
||||
|
||||
Archive: `.paul/phases/108-delivery-status-management/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v3.1 Operational Enhancements - 2026-04-27 (2 phases, 2 plans)</summary>
|
||||
|
||||
@@ -415,4 +428,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
|
||||
|
||||
---
|
||||
*Roadmap created: 2026-03-12*
|
||||
*Last updated: 2026-04-19 - v3.0 Mobile Responsive milestone closed (52 phases shipped, 55 plans; phase 68 deferred, phase 99 cancelled)*
|
||||
*Last updated: 2026-04-27 - v3.2 Delivery Status Management milestone closed (Phase 108, 2 plans)*
|
||||
|
||||
@@ -5,47 +5,55 @@
|
||||
See: .paul/PROJECT.md (updated 2026-04-27)
|
||||
|
||||
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
|
||||
**Current focus:** v3.1 Operational Enhancements — COMPLETE. Oczekiwanie na kolejny milestone.
|
||||
**Current focus:** Brak aktywnego milestone — v3.2 zamkniety
|
||||
|
||||
## Current Position
|
||||
|
||||
Milestone: v3.1 Operational Enhancements — COMPLETE
|
||||
Phase: 107 of 107 (Automation Email Send Once) — Complete
|
||||
Plan: 107-01 — Unified
|
||||
Version: 3.1.0
|
||||
Status: Milestone zamkniety, gotowy do planowania kolejnego milestone
|
||||
Milestone: v3.2 — COMPLETE (Delivery Status Management)
|
||||
Phase: 108 of 108 — COMPLETE
|
||||
Plan: 108-01 — COMPLETE / 108-02 — COMPLETE
|
||||
Version: 3.2.0
|
||||
Status: v3.2 shipped — gotowy do nastepnego milestone
|
||||
|
||||
Last activity: 2026-04-27 — UNIFY phase 107, milestone v3.1 closed
|
||||
Last activity: 2026-04-27 — TRANSITION Phase 108 / v3.2 milestone complete
|
||||
|
||||
Progress:
|
||||
- Milestone: [##########] 100%
|
||||
- Phase 107: [##########] 100%
|
||||
- Milestone v3.2: [##########] 100% (1/1 phases, 2/2 plans)
|
||||
|
||||
## Loop Position
|
||||
|
||||
Current loop state:
|
||||
```
|
||||
PLAN --> APPLY --> UNIFY
|
||||
[x] [x] [x] [Loop complete — milestone complete]
|
||||
v3.2 milestone:
|
||||
Phase 108 (Delivery Status Management):
|
||||
Plan 108-01: PLAN ✓ APPLY ✓ UNIFY ✓
|
||||
Plan 108-02: PLAN ✓ APPLY ✓ UNIFY ✓
|
||||
→ Phase 108 closed
|
||||
→ v3.2 milestone closed
|
||||
```
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-04-27
|
||||
Stopped at: Milestone v3.1 complete — wszystkie fazy zamkniete
|
||||
Next action: /paul:milestone — zaplanuj kolejny milestone (v3.2)
|
||||
Stopped at: v3.2 milestone closed
|
||||
Next action: /paul:milestone — wybor i zaplanowanie nastepnego milestone
|
||||
Resume file: .paul/ROADMAP.md
|
||||
|
||||
## Pending Actions
|
||||
|
||||
- Uruchom migracje gdy XAMPP online: `php bin/migrate.php` (delivery_statuses)
|
||||
- Recznie odtworzyc istniejace reguly automatyzacji z grupowymi kluczami (BREAKING z 108-02)
|
||||
|
||||
## Deferred to Next Milestones
|
||||
|
||||
- Phase 68 - Code Deduplication Refactor (0/2 Planning, nigdy nie rozpoczety)
|
||||
- STAT-NET - netto shopPRO z API lub z `order_items.tax_rate` (`.paul/TODO.md`)
|
||||
- Mobile Orders List / Mobile Order Details / Mobile Settings (TBD z poprzedniego roadmapu)
|
||||
- sonar-scanner - skan dla phase 105, 106, 107 nie zostal uruchomiony (skill gap odnotowany)
|
||||
- sonar-scanner - skan dla phase 105, 106, 107, 108 nie zostal uruchomiony (skill gap odnotowany)
|
||||
- INDEX-106-01 - indeksy DB dla query `customer_returned_count`: `order_addresses(order_id, address_type)`, `shipment_packages(order_id, delivery_status)` (gdy dataset >50k wierszy)
|
||||
|
||||
## Skill Audit (Phase 107)
|
||||
## Skill Audit (Phase 108)
|
||||
|
||||
| Expected | Invoked | Notes |
|
||||
|----------|---------|-------|
|
||||
| sonar-scanner (required) | o | Nie uruchomiony — odlozony analogicznie do Phase 105/106 |
|
||||
| sonar-scanner (required) | o | Wymagany po APPLY 108-01 i 108-02 — odlozony |
|
||||
|
||||
43
.paul/changelog/2026-04-27.md
Normal file
43
.paul/changelog/2026-04-27.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# 2026-04-27
|
||||
|
||||
## Co zrobiono
|
||||
|
||||
- [Phase 108, Plan 01] Wyniesiono znormalizowane statusy przesyłek do tabeli DB z CRUD panelem i dynamicznym ładowaniem
|
||||
- Migracja `20260427_000103_create_delivery_statuses_table.sql` — tabela delivery_statuses z seedem 11 statusów systemowych
|
||||
- Nowy `DeliveryStatusRepository` — CRUD + per-request static cache, blokady systemowych i używanych statusów
|
||||
- `DeliveryStatus::setRepository()` — transparent DB loading: label(), getAllOptions(), getColor() z DB, fallback na stałe
|
||||
- Nowy panel `/settings/delivery-statuses` z zakładkami: Statusy (CRUD niebędacych systemowych) + Mapowanie dostawy (embedded)
|
||||
- Sidebar przebudowany: "Statusy" → "Statusy zamówień", nowa pozycja "Statusy przesyłek" z badge niezmapowanych; usunięto osobną pozycję "Mapowanie statusów dostawy"
|
||||
- Badge przesyłek: system statuses → CSS class; custom statuses → `.delivery-badge--custom` + CSS custom property `--status-color`
|
||||
|
||||
- [Phase 108, Plan 02] Domknięcie integracji DB-driven statusów dla automatyzacji + refaktor UI listy
|
||||
- `AutomationController` — usunięto stałą `SHIPMENT_STATUS_OPTIONS`; dropdown z `DeliveryStatus::getAllOptions()`
|
||||
- `AutomationService` — usunięto `SHIPMENT_STATUS_OPTION_MAP`; ewaluacja porównuje klucze bezpośrednio (BREAKING dla starych reguł)
|
||||
- Walidacja shipment_status / update_shipment_status przez `DeliveryStatus::getAllStatuses()`
|
||||
- Nowa podstrona `/settings/delivery-statuses/new` i `/{id}/edit` — osobny formularz CRUD zamiast inline edit row
|
||||
- Lista statusów: rename "Terminal" → "Końcowy", usunięta kolumna "Typ" (badge systemowy)
|
||||
- Bug fix: path params w `DeliveryStatusesController::edit/update/destroy` przez `$request->input('id')` (pre-existing z Plan 01)
|
||||
|
||||
## Zmienione pliki
|
||||
|
||||
- `database/migrations/20260427_000103_create_delivery_statuses_table.sql`
|
||||
- `src/Modules/Shipments/DeliveryStatusRepository.php`
|
||||
- `src/Modules/Settings/DeliveryStatusesController.php`
|
||||
- `resources/views/settings/delivery-statuses.php`
|
||||
- `resources/views/settings/_delivery-status-mappings-content.php`
|
||||
- `src/Modules/Shipments/DeliveryStatus.php`
|
||||
- `src/Modules/Settings/DeliveryStatusMappingController.php`
|
||||
- `resources/views/settings/delivery-status-mappings.php`
|
||||
- `resources/views/layouts/app.php`
|
||||
- `resources/scss/modules/_delivery-status.scss`
|
||||
- `public/assets/css/app.css`
|
||||
- `resources/lang/pl.php`
|
||||
- `resources/views/orders/show.php`
|
||||
- `resources/views/shipments/prepare.php`
|
||||
- `.paul/docs/DB_SCHEMA.md`
|
||||
- `.paul/docs/ARCHITECTURE.md`
|
||||
- `.paul/docs/TECH_CHANGELOG.md`
|
||||
- `src/Modules/Automation/AutomationController.php`
|
||||
- `src/Modules/Automation/AutomationService.php`
|
||||
- `routes/web.php`
|
||||
- `resources/views/settings/delivery-status-form.php`
|
||||
@@ -1,3 +1,28 @@
|
||||
# ARCHITECTURE
|
||||
|
||||
> Struktura klas, modulow, przeplywow i zaleznosci w projekcie.
|
||||
|
||||
## Phase 108 — Delivery Status Management
|
||||
|
||||
### DeliveryStatusRepository (`src/Modules/Shipments/DeliveryStatusRepository.php`)
|
||||
- CRUD dla tabeli `delivery_statuses`
|
||||
- Per-request static cache (`private static ?array $cache`)
|
||||
- Blokuje edycję/usunięcie statusów systemowych (`is_system=1`)
|
||||
- Blokuje usunięcie statusów używanych w `delivery_status_mappings` lub `shipment_packages`
|
||||
|
||||
### DeliveryStatusesController (`src/Modules/Settings/DeliveryStatusesController.php`)
|
||||
- Panel `/settings/delivery-statuses`
|
||||
- Dwie zakładki via `?tab=` param: `statuses` (CRUD) i `mapping` (embed mapowania)
|
||||
- Wstrzykuje `DeliveryStatusRepository` i `DeliveryStatusMappingRepository`
|
||||
|
||||
### DeliveryStatus::setRepository() (dynamic loading)
|
||||
- Wywoływane raz w `routes/web.php` po bootstrap
|
||||
- `label()`, `getAllOptions()`, `getAllStatuses()`, `getColor()` ladują z DB gdy repo ustawione
|
||||
- Fallback na hardcoded stałe gdy repo niedostępne
|
||||
|
||||
### AutomationController + AutomationService (Phase 108 Plan 02)
|
||||
- `AutomationController::buildShipmentStatusOptions()` — buduje listę opcji `[key => ['label' => ...]]` z `DeliveryStatus::getAllOptions()` (DB-driven)
|
||||
- Walidacja `shipment_status` warunku i `update_shipment_status` akcji w `parseConditionValue()`/`parseActionConfig()` używa `DeliveryStatus::getAllStatuses()`
|
||||
- `AutomationService::evaluateShipmentStatusCondition()` — bezpośrednie porównanie kluczy DB (usunięto mapping grupowy `SHIPMENT_STATUS_OPTION_MAP`)
|
||||
- `AutomationService::resolveStatusFromActionKey()` — bezpośredni klucz statusu z DB jako target
|
||||
- BREAKING: stare reguły z grupowymi kluczami (`registered`, `courier_pickup`, `dropped_at_point`, `unclaimed`, `picked_up_return`) nie matchują się — operator musi je odtworzyć przy użyciu nowych kluczy DB
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
# DB_SCHEMA
|
||||
|
||||
> Schemat bazy danych — tabele, kolumny, FK, indeksy.
|
||||
|
||||
## delivery_statuses
|
||||
|
||||
Tabela znormalizowanych statusów przesyłek. Zastępuje hardcoded stałe z `DeliveryStatus.php` (od Phase 108).
|
||||
|
||||
| Kolumna | Typ | Opis |
|
||||
|---------|-----|------|
|
||||
| id | INT UNSIGNED PK | Auto-increment |
|
||||
| key | VARCHAR(50) UNIQUE | Klucz statusu (np. `in_transit`) |
|
||||
| label_pl | VARCHAR(100) | Etykieta po polsku |
|
||||
| color | VARCHAR(7) | Kolor tła w formacie hex (`#rrggbb`) |
|
||||
| sort_order | TINYINT UNSIGNED | Kolejność sortowania |
|
||||
| is_terminal | TINYINT(1) | 1 = status końcowy (nie można cofnąć) |
|
||||
| is_system | TINYINT(1) | 1 = status systemowy (nieedytowalny z UI) |
|
||||
| created_at | DATETIME | Data utworzenia |
|
||||
|
||||
**Statusy systemowe** (is_system=1, is_terminal=1): `delivered`, `returned`, `cancelled`.
|
||||
**Migracja**: `20260427_000103_create_delivery_statuses_table.sql`
|
||||
|
||||
@@ -1,3 +1,31 @@
|
||||
# TECH_CHANGELOG
|
||||
|
||||
> Chronologiczny log zmian technicznych — co i dlaczego.
|
||||
|
||||
## 2026-04-27 — Phase 108 Plan 02: Automation Dropdowns z DB
|
||||
|
||||
**Co zrobiono:**
|
||||
- `AutomationController` — usunięto stałą `SHIPMENT_STATUS_OPTIONS` (8 grupowych kluczy)
|
||||
- Dropdown statusów w warunku `shipment_status` i akcji `update_shipment_status` ładuje statusy z DB przez `DeliveryStatus::getAllOptions()`
|
||||
- Walidacja w `parseConditionValue()` i `parseActionConfig()` używa `DeliveryStatus::getAllStatuses()`
|
||||
- `AutomationService` — usunięto stałą `SHIPMENT_STATUS_OPTION_MAP`; ewaluacja `evaluateShipmentStatusCondition()` porównuje klucze bezpośrednio
|
||||
- `resolveStatusFromActionKey()` — bezpośredni klucz statusu z DB jako target (zamiast pierwszego z grupy)
|
||||
|
||||
**Dlaczego:**
|
||||
- Zamknięcie integracji z Plan 01 — operator dodaje status w `/settings/delivery-statuses` i jest on od razu dostępny w dropdownach automatyzacji bez deploymentu
|
||||
- Eliminacja kolizji semantycznej: stary klucz grupowy `picked_up` mapował na `delivered` (paczka odebrana przez klienta), nowy klucz DB `picked_up` to "Odebrana przez kuriera" (od nadawcy)
|
||||
- BREAKING: stare reguły z grupowymi kluczami (`registered`, `courier_pickup`, `dropped_at_point`, `unclaimed`, `picked_up_return`, oraz `picked_up`/`ready_for_pickup`/`cancelled` w starym znaczeniu) nie matchują — wymagają ręcznego odtworzenia z nowymi kluczami DB
|
||||
|
||||
## 2026-04-27 — Phase 108 Plan 01: Delivery Status Management
|
||||
|
||||
**Co zrobiono:**
|
||||
- Tabela `delivery_statuses` z seedem 11 statusów (migracja `20260427_000103`)
|
||||
- `DeliveryStatusRepository` — CRUD + per-request cache
|
||||
- `DeliveryStatus.php` — dynamiczne ładowanie statusów z DB (`setRepository()`)
|
||||
- Panel `/settings/delivery-statuses` z CRUD (zakładka "Statusy") i mapowaniem (zakładka "Mapowanie dostawy")
|
||||
- Sidebar: "Statusy" → "Statusy zamówień", nowe "Statusy przesyłek" z badge niezmapowanych
|
||||
- Badge przesyłek: inline CSS custom property `--status-color` dla niestandardowych statusów
|
||||
|
||||
**Dlaczego:**
|
||||
- Dodanie nowego statusu wymagało zmiany kodu + deploymentu; teraz z UI
|
||||
- Operator może definiować własne statusy znormalizowane bez ingerencji w kod
|
||||
|
||||
@@ -89,3 +89,101 @@
|
||||
{"ts":"2026-04-27T12:55:15Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && python tools/generowanie/email_message_fetcher.py --email \"kozix_x@onet.pl\" --days 60 --json 2>/dev/null | head -50\",\"description\":\"Sprawdź mai","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T12:55:46Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && PYTHONIOENCODING=utf-8 python tools/generowanie/pudelko_komunia_dziewczynka.py \\\\\\n --klient \"Natalia Barczyk\" \\\\\\n --imie \"Oliwii\" \\\\\\","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T12:55:57Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"UPDATE order_items SET proj","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:10:36Z","tool":"Bash","cmd":"git log --oneline -10\",\"description\":\"Recent commits\"},\"tool_response\":{\"stdout\":\"5aca417 update\\n32d7ae9 update\\n34dac15 update\\nf5c5201 chore: move TODO to .paul/codebase/todo.md\\nc3b6","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:10:45Z","tool":"Bash","cmd":"git show --stat 5aca417 && git show --stat 32d7ae9 && git show --stat 34dac15\",\"description\":\"Show changed files in last 3 commits\"},\"tool_response\":{\"stdout\":\"commit 5aca41750ca5b34c5b6d816","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:16:17Z","tool":"Bash","cmd":"git log --oneline --since=\"2026-04-25\" | head -20\",\"description\":\"Commits since plan creation\"},\"tool_response\":{\"stdout\":\"5aca417 update\\n32d7ae9 update\\n34dac15 update\\nf5c5201 chore:","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:16:22Z","tool":"Bash","cmd":"git log --all --oneline --since=\"2026-04-24\" --until=\"2026-04-26\" -- \"src/Modules/Automation/*\" \"database/migrations/*\" \"tests/Unit/*\" | head -20\",\"description\":\"Commits touching phase 1","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:16:28Z","tool":"Bash","cmd":"git show --stat 4b998ea\",\"description\":\"Show files in the apply commit\"},\"tool_response\":{\"stdout\":\"commit 4b998ea5be917df1aad90bba87da50f800b46fe4\\nAuthor: Jacek Pyziak <jacek.pyziak@proje","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:17:09Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\107-automation-email-send-once\\\\107-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:17:24Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-25.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:17:47Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:17:56Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:18:04Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:18:18Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:18:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:18:47Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:19:10Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:19:16Z","tool":"Bash","cmd":"git add .paul/phases/107-automation-email-send-once/ .paul/STATE.md .paul/PROJECT.md .paul/ROADMAP.md .paul/changelog/2026-04-25.md\",\"description\":\"Stage phase 107 unify files\"},\"tool_response\"","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:19:29Z","tool":"Bash","cmd":"git commit -m \"$(cat <<'EOF'\\nfeat(107-automation-email-send-once): idempotent send-once per order for email automation\\n\\nPhase 107 complete:\\n- New table automation_email_once_deliveries with U","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:24:15Z","tool":"Bash","cmd":"wc -l \"C:/visual studio code/projekty/orderPRO/src/Core/Application.php\" && grep -n \"delivery\\\\|statuses\\\\|route\\\\|Router\" \"C:/visual studio code/projekty/orderPRO/src/Core/Application.php\","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:28:23Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/orderPRO/.paul/phases/108-delivery-status-management\"\",\"description\":\"Create Phase 108 directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:29:53Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\108-delivery-status-management\\\\108-01-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:30:23Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\108-delivery-status-management\\\\108-02-PLAN.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:34:48Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\database\\\\migrations\\\\20260427_000103_create_delivery_statuses_table.sql","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:35:02Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Shipments\\\\DeliveryStatusRepository.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:35:06Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/orderPRO/src/Modules/Shipments/DeliveryStatusRepository.php\"\",\"description\":\"Syntax check DeliveryStatusRepository.php\"},\"tool_response\":{\"stdout\":\"N","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:35:19Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/orderPRO/src/Modules/Shipments/DeliveryStatusRepository.php\" && ls \"C:/visual studio code/projekty/orderPRO/database/migrations/20260427_000103_create_deliver","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:37:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Shipments\\\\DeliveryStatus.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:37:38Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Shipments\\\\DeliveryStatus.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:37:42Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:37:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:37:50Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:37:54Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:38:03Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:38:12Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:38:19Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:38:23Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:38:54Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\settings\\\\_delivery-status-mappings-content.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:38:58Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\settings\\\\delivery-status-mappings.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:39:24Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusesController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:39:51Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\settings\\\\delivery-statuses.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:39:57Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\scss\\\\modules\\\\_delivery-status.scss","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:40:01Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\routes\\\\web.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:40:05Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\routes\\\\web.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:40:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\routes\\\\web.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:40:15Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\routes\\\\web.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:40:21Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/orderPRO/src/Modules/Shipments/DeliveryStatus.php\" && php -l \"C:/visual studio code/projekty/orderPRO/src/Modules/Settings/DeliveryStatusMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:40:26Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/orderPRO/resources/views/settings/delivery-statuses.php\" && php -l \"C:/visual studio code/projekty/orderPRO/resources/views/settings/_delivery-status-mappings","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:40:47Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/orderPRO/src/Modules/Shipments/DeliveryStatus.php\" && php -l \"C:/visual studio code/projekty/orderPRO/src/Modules/Settings/DeliveryStatusMappingController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:42:17Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\lang\\\\pl.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:42:31Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\layouts\\\\app.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:42:39Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\show.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:42:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\orders\\\\show.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:42:55Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\shipments\\\\prepare.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:43:06Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\docs\\\\DB_SCHEMA.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:43:15Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\docs\\\\ARCHITECTURE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:43:23Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\docs\\\\TECH_CHANGELOG.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:43:28Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/orderPRO/resources/views/orders/show.php\" && php -l \"C:/visual studio code/projekty/orderPRO/resources/views/shipments/prepare.php\" && php -l \"C:/visual stu","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:43:47Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/orderPRO/resources/views/orders/show.php\" && php -l \"C:/visual studio code/projekty/orderPRO/resources/views/shipments/prepare.php\" && php -l \"C:/visual stu","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:44:09Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && npm run build:assets 2>&1\",\"description\":\"Build SCSS assets\"},\"tool_response\":{\"stdout\":\"> orderpro@1.0.0 build:assets\\n> npm run build:css","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:47:33Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\108-delivery-status-management\\\\108-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:47:49Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-27.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:55:47Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:55:53Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:55:57Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:56:04Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:56:09Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:56:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:56:23Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationService.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:56:29Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationService.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:56:35Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Automation\\\\AutomationService.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:56:39Z","tool":"Bash","cmd":"php -l \"src/Modules/Automation/AutomationController.php\" && php -l \"src/Modules/Automation/AutomationService.php\"\",\"description\":\"PHP syntax check on modified files\"},\"tool_response\":{\"std","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:57:03Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\docs\\\\ARCHITECTURE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:57:15Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\docs\\\\TECH_CHANGELOG.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:57:30Z","tool":"Bash","cmd":"php vendor/bin/phpunit tests/Unit/AutomationServiceTest.php 2>&1 | tail -30\",\"description\":\"Run automation service tests\"},\"tool_response\":{\"stdout\":\"PHPUnit 11.5.55 by Sebastian Bergmann an","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:57:38Z","tool":"Bash","cmd":"php vendor/bin/phpunit 2>&1 | tail -15\",\"description\":\"Run full PHPUnit suite\"},\"tool_response\":{\"stdout\":\"Failed asserting that Array &0 [\\n 'status' => 'created',\\n 'shipment_id' =","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:57:45Z","tool":"Bash","cmd":"git stash && php vendor/bin/phpunit tests/Unit/AllegroShipmentServiceTest.php 2>&1 | tail -5 && git stash pop\",\"description\":\"Check if AllegroShipmentService failures are pre-existing\"},\"tool_re","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T19:58:04Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:00:42Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\routes\\\\web.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:00:49Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusesController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:00:57Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusesController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:01:13Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\settings\\\\delivery-status-form.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:01:47Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\resources\\\\views\\\\settings\\\\delivery-statuses.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:01:53Z","tool":"Bash","cmd":"php -l \"src/Modules/Settings/DeliveryStatusesController.php\" && php -l \"resources/views/settings/delivery-statuses.php\" && php -l \"resources/views/settings/delivery-status-form.php\" && php -l \"","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:04:03Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusesController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:04:08Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusesController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:04:13Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Settings\\\\DeliveryStatusesController.php","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:04:17Z","tool":"Bash","cmd":"php -l \"src/Modules/Settings/DeliveryStatusesController.php\"\",\"description\":\"Syntax check controller\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in src/Modules/Settings/Delivery","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:05:48Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\108-delivery-status-management\\\\108-02-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:05:56Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-27.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:06:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-27.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:06:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:08:36Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:08:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:08:59Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:09:03Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:09:15Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:09:21Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:09:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:09:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
{"ts":"2026-04-27T20:09:50Z","tool":"Bash","cmd":"git branch --list \"feature/108*\" && git status --short | head -30\",\"description\":\"Check feature branches and git status\"},\"tool_response\":{\"stdout\":\" M .paul/PROJECT.md\\n M .paul/ROADMAP.","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||
|
||||
326
.paul/phases/108-delivery-status-management/108-01-PLAN.md
Normal file
326
.paul/phases/108-delivery-status-management/108-01-PLAN.md
Normal file
@@ -0,0 +1,326 @@
|
||||
---
|
||||
phase: 108-delivery-status-management
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- database/migrations/20260427_000103_create_delivery_statuses_table.sql
|
||||
- src/Modules/Shipments/DeliveryStatusRepository.php
|
||||
- src/Modules/Shipments/DeliveryStatus.php
|
||||
- src/Modules/Settings/DeliveryStatusesController.php
|
||||
- src/Modules/Settings/DeliveryStatusMappingController.php
|
||||
- resources/views/settings/delivery-statuses.php
|
||||
- resources/views/settings/_delivery-status-mappings-content.php
|
||||
- resources/views/settings/delivery-status-mappings.php
|
||||
- resources/views/layouts/app.php
|
||||
- resources/scss/settings/_delivery-statuses.scss
|
||||
- resources/scss/app.scss
|
||||
- resources/lang/pl.php
|
||||
- routes/web.php
|
||||
- .paul/docs/DB_SCHEMA.md
|
||||
- .paul/docs/ARCHITECTURE.md
|
||||
- .paul/docs/TECH_CHANGELOG.md
|
||||
autonomous: true
|
||||
delegation: on
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Wyniesc statusy znormalizowane przesylek do tabeli DB, udostepnic CRUD w nowym panelu ustawien "Statusy przesylek" i dynamicznie ladowac statusy z DB wszedzie tam, gdzie wczesniej uzywano stalych z `DeliveryStatus.php`.
|
||||
|
||||
## Purpose
|
||||
Dodanie nowego statusu znormalizowanego wymaga teraz zmiany kodu i deploymentu. Po tej fazie operator moze dodac wlasny status z UI bez ingerencji w kod. Statusy systemowe (delivered, returned, cancelled) pozostaja nieedytowalne.
|
||||
|
||||
## Output
|
||||
- Tabela `delivery_statuses` z seedem 11 istniejacych statusow
|
||||
- `DeliveryStatusRepository` — odczyt z DB z per-request static cache
|
||||
- `DeliveryStatus.php` — laduje `ALL_STATUSES` i `LABEL_PL` z DB (fallback na stale przy bledzie)
|
||||
- Nowy panel `/settings/delivery-statuses` z dwoma zakladkami: CRUD statusow + mapowanie dostawy
|
||||
- Sidebar: "Statusy zamowien" (istniejaca pozycja), nowa pozycja "Statusy przesylek"
|
||||
- Walidacja w `DeliveryStatusMappingController` uzywajaca danych z DB
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Source Files
|
||||
@src/Modules/Shipments/DeliveryStatus.php
|
||||
@src/Modules/Settings/DeliveryStatusMappingController.php
|
||||
@resources/views/settings/delivery-status-mappings.php
|
||||
@resources/views/layouts/app.php
|
||||
@resources/lang/pl.php
|
||||
@routes/web.php
|
||||
@.paul/docs/DB_SCHEMA.md
|
||||
@.paul/docs/ARCHITECTURE.md
|
||||
</context>
|
||||
|
||||
<skills>
|
||||
## Required Skills
|
||||
|
||||
| Skill | Priority | When to Invoke | Loaded? |
|
||||
|-------|----------|----------------|---------|
|
||||
| sonar-scanner (CLI) | required | Po APPLY, przed UNIFY | o |
|
||||
</skills>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Tabela delivery_statuses istnieje i zawiera seed 11 statusow
|
||||
```gherkin
|
||||
Given migracja zostala uruchomiona
|
||||
Then tabela delivery_statuses zawiera 11 wierszy odpowiadajacych stalym z DeliveryStatus.php
|
||||
And kolumny: id, key, label_pl, color, sort_order, is_terminal, is_system, created_at
|
||||
And statusy: delivered, returned, cancelled maja is_system=1, is_terminal=1
|
||||
And pozostale 8 statusow maja is_system=0
|
||||
```
|
||||
|
||||
## AC-2: CRUD niebedacych systemowymi statusami dziala z panelu
|
||||
```gherkin
|
||||
Given operator otwiera /settings/delivery-statuses (zakladka Statusy)
|
||||
When doda nowy status z kluczem, etykieta, kolorem
|
||||
Then status pojawia sie na liscie i w dropdownie mapowania statusow
|
||||
When probuje edytowac status systemowy (delivered/returned/cancelled)
|
||||
Then formularz jest zablokowany (readonly/disabled fields lub brak przycisku edycji)
|
||||
When probuje usunac status uzyty w delivery_status_mappings lub shipment_packages
|
||||
Then otrzymuje blad "Status jest uzywany, nie mozna usunac"
|
||||
```
|
||||
|
||||
## AC-3: Strona /settings/delivery-statuses ma dwie zakladki
|
||||
```gherkin
|
||||
Given operator otwiera /settings/delivery-statuses
|
||||
Then widzi zakladke "Statusy" (CRUD) i "Mapowanie dostawy"
|
||||
And klikajac "Mapowanie dostawy" widzi te sama tresc co wczesniej na /settings/delivery-status-mappings
|
||||
And /settings/delivery-status-mappings dalej dziala (backward compat dla zakładek)
|
||||
```
|
||||
|
||||
## AC-4: Sidebar odzwierciedla nowa strukture nawigacji
|
||||
```gherkin
|
||||
Given operator jest w ustawieniach
|
||||
Then widzi "Statusy zamowien" (link do /settings/statuses)
|
||||
And widzi "Statusy przesylek" (link do /settings/delivery-statuses)
|
||||
And stara pozycja "Mapowanie statusow dostawy" znika z sidebara
|
||||
And badge z liczba niezmapowanych statusow widnieje przy "Statusy przesylek"
|
||||
```
|
||||
|
||||
## AC-5: DeliveryStatus.php laduje statusy z DB
|
||||
```gherkin
|
||||
Given tabela delivery_statuses istnieje
|
||||
When kod wywola DeliveryStatus::label($key)
|
||||
Then zwraca etykiete z DB (nie hardcoded)
|
||||
And kolejne wywolania w tym samym request uzywaja per-request static cache
|
||||
```
|
||||
|
||||
## AC-6: Badge renderuje sie dla nowych statusow
|
||||
```gherkin
|
||||
Given status niebedacy jednym z 11 systemowych ma kolor #ff5500
|
||||
When badge jest renderowany w liscie zamowien lub szczegolow przesylki
|
||||
Then badge uzywa inline style="background-color: #ff5500" dla nieznanych kluczy CSS
|
||||
And istniejace 11 statusow dalej korzysta z hardcoded klas CSS (.delivery-badge--delivered itp.)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Migracja tabeli delivery_statuses i DeliveryStatusRepository</name>
|
||||
<files>database/migrations/20260427_000103_create_delivery_statuses_table.sql, src/Modules/Shipments/DeliveryStatusRepository.php</files>
|
||||
<action>
|
||||
1. Utworzyc migracje `20260427_000103_create_delivery_statuses_table.sql`:
|
||||
- CREATE TABLE `delivery_statuses` (
|
||||
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`key` VARCHAR(50) NOT NULL UNIQUE,
|
||||
`label_pl` VARCHAR(100) NOT NULL,
|
||||
`color` VARCHAR(7) NOT NULL DEFAULT '#6c757d',
|
||||
`sort_order` TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`is_terminal` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`is_system` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
- INSERT seed: wszystkie 11 statusow z DeliveryStatus.php (unknown, created, confirmed,
|
||||
picked_up, in_transit, out_for_delivery, ready_for_pickup, delivered, returned,
|
||||
cancelled, problem) z odpowiednimi wartosciami label_pl, color, sort_order,
|
||||
is_terminal (delivered/returned/cancelled = 1), is_system (delivered/returned/cancelled = 1).
|
||||
- Kolory bazujac na istniejacych klasach CSS (sprawdzic resources/scss/).
|
||||
2. Utworzyc `src/Modules/Shipments/DeliveryStatusRepository.php`:
|
||||
- Konstruktor przyjmuje `\Medoo\Medoo $db`
|
||||
- `getAll(): array` — SELECT * FROM delivery_statuses ORDER BY sort_order ASC
|
||||
z per-request static cache (prywatna static zmienna)
|
||||
- `getByKey(string $key): ?array` — szuka w getAll()
|
||||
- `getAllAsOptions(): array` — zwraca [key => label_pl] (do dropdownow)
|
||||
- `create(array $data): int` — INSERT, zwraca id; waliduje unikalnosc key przed insertem
|
||||
- `update(int $id, array $data): void` — UPDATE; blokuje is_system=1
|
||||
- `delete(int $id): void` — DELETE; sprawdza czy key nie wystepuje w
|
||||
delivery_status_mappings.normalized_status lub shipment_packages.delivery_status;
|
||||
blokuje jesli is_system=1 lub uzywany
|
||||
- `clearCache(): void` — resetuje static cache (do testow i po mutacjach)
|
||||
</action>
|
||||
<verify>
|
||||
Uruchom migracje na lokalnej bazie. Sprawdz `SELECT COUNT(*) FROM delivery_statuses` = 11.
|
||||
Sprawdz ze delivered/returned/cancelled maja is_system=1, is_terminal=1.
|
||||
</verify>
|
||||
<done>Dane bazodanowe gotowe; repozytorium dostepne do uzycia w pozostalych taskach.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Dynamiczne ladowanie w DeliveryStatus.php + controller + widok CRUD + routing</name>
|
||||
<files>src/Modules/Shipments/DeliveryStatus.php, src/Modules/Settings/DeliveryStatusesController.php, src/Modules/Settings/DeliveryStatusMappingController.php, resources/views/settings/delivery-statuses.php, resources/views/settings/_delivery-status-mappings-content.php, resources/views/settings/delivery-status-mappings.php, resources/scss/settings/_delivery-statuses.scss, resources/scss/app.scss, routes/web.php</files>
|
||||
<action>
|
||||
1. Zaktualizowac `DeliveryStatus.php`:
|
||||
- Dodac statyczna metode `setRepository(DeliveryStatusRepository $repo): void` (called once at bootstrap)
|
||||
- Zmodyfikowac `label(string $key): string` — gdy repozytorium jest wstrzykniete,
|
||||
pobiera z DB przez `getByKey($key)['label_pl']`; fallback na `LABEL_PL[$key] ?? $key`
|
||||
- Dodac `getAllStatuses(): array` — zwraca `DeliveryStatusRepository::getAll()` lub
|
||||
fallback `ALL_STATUSES` gdy repo niedostepne
|
||||
- Dodac `getAllOptions(): array` — zwraca `DeliveryStatusRepository::getAllAsOptions()` lub
|
||||
fallback `LABEL_PL`
|
||||
- STALE i TERMINAL_STATUSES/providerowe mapy (INPOST_MAP etc.) pozostaja hardcoded (nie
|
||||
dotyczace listy statusow uzytkownika)
|
||||
- Podpiac `DeliveryStatusRepository` w bootstrapie (app.php lub DI kontener projektu —
|
||||
sprawdzic jak inne repozytoria sa inicjalizowane i powtorzyc ten sam wzorzec)
|
||||
2. Zaktualizowac `DeliveryStatusMappingController`:
|
||||
- Wstrzyknac `DeliveryStatusRepository` przez konstruktor
|
||||
- W `save()` i `saveBulk()` — walidacja normalizedStatus przez
|
||||
`$this->deliveryStatusRepository->getByKey($normalizedStatus) !== null`
|
||||
zamiast `in_array($normalizedStatus, DeliveryStatus::ALL_STATUSES)`
|
||||
- Zaktualizowac `REDIRECT_PATH` z `/settings/delivery-status-mappings`
|
||||
na `/settings/delivery-statuses?tab=mapping`
|
||||
- Zachowac parametr `?provider=` przy redirectach gdzie byl
|
||||
3. Wydzielic zawartosc widoku mapowania do include'a:
|
||||
- Przeniesc zawartosc `resources/views/settings/delivery-status-mappings.php` do
|
||||
`resources/views/settings/_delivery-status-mappings-content.php`
|
||||
- W `delivery-status-mappings.php` uzywac `include '_delivery-status-mappings-content.php'`
|
||||
(zachowanie backward compat)
|
||||
4. Utworzyc `DeliveryStatusesController.php` w `src/Modules/Settings/`:
|
||||
- `index()` — laduje statusy z DeliveryStatusRepository, renderuje delivery-statuses.php;
|
||||
przekazuje `$tab = $_GET['tab'] ?? 'statuses'` i dane mapowania gdy tab=mapping
|
||||
- `store()` — POST create nowego statusu; waliduje key (lowercase, underscored), label_pl,
|
||||
color (#hex); zapisuje przez repozytorium; redirect z Flash
|
||||
- `update(int $id)` — POST edit; blokuje is_system=1; redirect z Flash
|
||||
- `destroy(int $id)` — POST delete; blokuje is_system=1 i uzywane; redirect z Flash
|
||||
- Każda akcja mutujaca: walidacja CSRF `_token`, potem `DeliveryStatusRepository::clearCache()`
|
||||
5. Utworzyc widok `resources/views/settings/delivery-statuses.php`:
|
||||
- Dwie zakladki: "Statusy" i "Mapowanie dostawy"
|
||||
- Persystencja aktywnej zakladki przez `?tab=` param (nie localStorage — zeby linki
|
||||
z sidebara "Statusy przesylek" i redirect po save trafialy w dobra zakladke)
|
||||
- Zakladka "Statusy":
|
||||
* Tabela statusow: kolor (swatchek), klucz, etykieta, sort_order, is_terminal,
|
||||
akcje (edycja/usun dla nie-systemowych; informacja "systemowy" dla systemowych)
|
||||
* Formularz dodawania nowego statusu (inline pod tabela lub modal)
|
||||
* Formularz edycji (inline row edit lub osobny formularz)
|
||||
* Potwierdzenie usuwania: `window.OrderProAlerts.confirm()`
|
||||
* Formularz dodawania: pola key (slug, lowercase, max 50), label_pl, color (input type=color),
|
||||
sort_order (number), is_terminal (checkbox)
|
||||
- Zakladka "Mapowanie dostawy":
|
||||
* include '_delivery-status-mappings-content.php'
|
||||
6. Dodac styl `resources/scss/settings/_delivery-statuses.scss`:
|
||||
- `.delivery-status-swatch` — maly kwadrat koloru (16x16px inline-block)
|
||||
- `.delivery-status-system-badge` — np. szary badge "systemowy"
|
||||
- Dodac `@use 'settings/delivery-statuses'` do `app.scss`
|
||||
7. Dodac trasy w `routes/web.php`:
|
||||
- GET `/settings/delivery-statuses` → `DeliveryStatusesController::index`
|
||||
- POST `/settings/delivery-statuses` → `DeliveryStatusesController::store`
|
||||
- POST `/settings/delivery-statuses/{id}/update` → `DeliveryStatusesController::update`
|
||||
- POST `/settings/delivery-statuses/{id}/delete` → `DeliveryStatusesController::destroy`
|
||||
</action>
|
||||
<verify>
|
||||
- GET /settings/delivery-statuses zwraca 200, widac zakladki
|
||||
- Dodanie nowego statusu przez CRUD pojawia sie na liscie
|
||||
- Proba edycji/usuniecia statusu systemowego jest blokowana (HTTP 400 lub redirect z bledem)
|
||||
- GET /settings/delivery-status-mappings dalej dziala (backward compat)
|
||||
- redirect po save mapowania idzie do /settings/delivery-statuses?tab=mapping
|
||||
</verify>
|
||||
<done>AC-2, AC-3, AC-5 spelnione.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Sidebar, jezyk, badge rendering i dokumentacja</name>
|
||||
<files>resources/views/layouts/app.php, resources/lang/pl.php, resources/views/orders/show.php, resources/views/shipments/prepare.php, .paul/docs/DB_SCHEMA.md, .paul/docs/ARCHITECTURE.md, .paul/docs/TECH_CHANGELOG.md</files>
|
||||
<action>
|
||||
1. Zaktualizowac `resources/lang/pl.php`:
|
||||
- `navigation.statuses` → 'Statusy zamowien'
|
||||
- Dodac `navigation.delivery_statuses` => 'Statusy przesylek'
|
||||
2. Zaktualizowac sidebar `resources/views/layouts/app.php`:
|
||||
- Zmieniac istniejaca pozycje "Statusy" (href=/settings/statuses) tylko etykiete na
|
||||
$t('navigation.statuses') (juz uzywana, ale tresc sie zmieni po kroku 1)
|
||||
- Podmiana calego bloku sidebar z "Mapowanie statusow dostawy" (linie ~130-147):
|
||||
* Nowy link "Statusy przesylek" href=/settings/delivery-statuses
|
||||
* active gdy $currentSettings === 'delivery-statuses'
|
||||
LUB $currentSettings === 'delivery-status-mappings' (backward compat)
|
||||
* Badge z liczba niezmapowanych (zachowac istniejaca logike z DeliveryStatusMappingRepository)
|
||||
3. Zaktualizowac badge rendering:
|
||||
- W `resources/views/orders/show.php` i `resources/views/shipments/prepare.php`:
|
||||
Aktualny wzorzec:
|
||||
`<span class="delivery-badge delivery-badge--<?= $e($pkgDeliveryStatus) ?>">`
|
||||
Zmienic na helper lub inline logike:
|
||||
- Jesli `$pkgDeliveryStatus` jest jednym z 11 stalych — uzyj klasy CSS jak dotychczas
|
||||
- W przeciwnym razie: pobierz kolor z DeliveryStatus::getByKey() i dodaj
|
||||
`style="background-color: <?= $e($color) ?>"`
|
||||
Wzorzec do uzycia (zdefiniowac helper w DeliveryStatus lub inline w widoku):
|
||||
```php
|
||||
$statusColor = DeliveryStatus::getColor($pkgDeliveryStatus);
|
||||
$isSystemStatus = in_array($pkgDeliveryStatus, DeliveryStatus::ALL_STATUSES);
|
||||
$badgeClass = 'delivery-badge' . ($isSystemStatus ? ' delivery-badge--' . $pkgDeliveryStatus : '');
|
||||
$badgeStyle = $isSystemStatus ? '' : 'background-color: ' . $statusColor . ';';
|
||||
```
|
||||
`<span class="<?= $e($badgeClass) ?>" <?= $badgeStyle ? 'style="' . $e($badgeStyle) . '"' : '' ?>>`
|
||||
- Dodac do `DeliveryStatus.php` metode `getColor(string $key): string` — pobiera kolor z
|
||||
repozytorium lub zwraca '#6c757d' jako fallback
|
||||
4. Zaktualizowac `.paul/docs/DB_SCHEMA.md` (nowa tabela delivery_statuses)
|
||||
5. Zaktualizowac `.paul/docs/ARCHITECTURE.md` (DeliveryStatusRepository, DeliveryStatusesController)
|
||||
6. Zaktualizowac `.paul/docs/TECH_CHANGELOG.md` (entry dla Phase 108 Plan 01)
|
||||
</action>
|
||||
<verify>
|
||||
- Sidebar pokazuje "Statusy zamowien" i "Statusy przesylek", brak "Mapowanie statusow dostawy"
|
||||
- Badge dla istniejacych 11 statusow renderuje sie klasa CSS jak wczesniej
|
||||
- Badge dla nowego custom statusu renderuje sie z inline style
|
||||
- Na stronie zamowien i przesylki brak bledow PHP
|
||||
</verify>
|
||||
<done>AC-4, AC-6 spelnione; dokumentacja aktualna.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- Logika eventow automatyzacji, reguł i akcji niezwiazana z lista statusow
|
||||
- Provider mapy (INPOST_MAP, APACZKA_MAP, ALLEGRO_MAP) — pozostaja hardcoded compile-time
|
||||
- TERMINAL_STATUSES — stala pozostaje w kodzie dla logiki biznesowej; DB `is_terminal` jest dodatkowa informacja dla UI
|
||||
- Runtime konfiguracji DB hostow (`DB_HOST` / `DB_HOST_REMOTE`)
|
||||
- Istniejace CSS klasy `.delivery-badge--{status}` dla 11 systemowych statusow
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Brak zmian w logice normalizacji statusow dostawcow (normalize(), normalizeWithOverrides())
|
||||
- Brak zmian w cronie ani harmonogramie
|
||||
- Automatyzacje — dropdown statusow aktualizowany w Plan 02 (nie w tym planie)
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] `SELECT COUNT(*) FROM delivery_statuses` = 11
|
||||
- [ ] GET /settings/delivery-statuses zwraca 200 z dwoma zakladkami
|
||||
- [ ] Dodanie nowego statusu przez CRUD jest widoczne na liscie
|
||||
- [ ] Statusy systemowe sa zablokowane przed edycja i usunieciem
|
||||
- [ ] GET /settings/delivery-status-mappings nadal dziala (200)
|
||||
- [ ] Redirect po save mapowania idzie do /settings/delivery-statuses?tab=mapping
|
||||
- [ ] Sidebar pokazuje "Statusy zamowien" i "Statusy przesylek"
|
||||
- [ ] Badge istniejacych statusow bez regresji
|
||||
- [ ] Badge nowego custom statusu renderuje sie z inline style
|
||||
- [ ] Dokumentacja .paul/docs/* zaktualizowana
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Operator moze dodac nowy status znormalizowany z panelu bez deploymentu
|
||||
- Status systemowy (delivered/returned/cancelled) jest nieedytowalny z UI
|
||||
- Strona /settings/delivery-statuses ma dwie dzialajace zakladki
|
||||
- Sidebar jest uporzadkowany zgodnie z nowa struktura
|
||||
- Brak regresji w istniejacym wyswietlaniu odznaczen statusow
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/108-delivery-status-management/108-01-SUMMARY.md`
|
||||
</output>
|
||||
155
.paul/phases/108-delivery-status-management/108-01-SUMMARY.md
Normal file
155
.paul/phases/108-delivery-status-management/108-01-SUMMARY.md
Normal file
@@ -0,0 +1,155 @@
|
||||
---
|
||||
phase: 108-delivery-status-management
|
||||
plan: 01
|
||||
subsystem: settings, shipments
|
||||
tags: [delivery-status, crud, repository, scss, sidebar, php, pdo]
|
||||
|
||||
requires:
|
||||
- phase: 107-automation-email-send-once
|
||||
provides: project baseline; delivery status constants in DeliveryStatus.php
|
||||
|
||||
provides:
|
||||
- Tabela delivery_statuses z seedem 11 statusów
|
||||
- DeliveryStatusRepository (CRUD + per-request static cache)
|
||||
- DeliveryStatus::setRepository() — dynamiczne ładowanie etykiet/kolorów z DB
|
||||
- Panel /settings/delivery-statuses z CRUD i zakładką mapowania
|
||||
- Sidebar: "Statusy zamówień" + "Statusy przesyłek" (z badge niezmapowanych)
|
||||
- Badge rendering z CSS custom property dla niestandardowych statusów
|
||||
|
||||
affects: [108-02-automation-dropdowns, delivery-status-consumers, shipment-views]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- DeliveryStatusRepository — static cache pattern (private static ?array $cache)
|
||||
- DeliveryStatus::setRepository() — one-time bootstrap DI dla final static class
|
||||
- CSS custom property --status-color dla dynamicznych kolorów badge
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- database/migrations/20260427_000103_create_delivery_statuses_table.sql
|
||||
- src/Modules/Shipments/DeliveryStatusRepository.php
|
||||
- src/Modules/Settings/DeliveryStatusesController.php
|
||||
- resources/views/settings/delivery-statuses.php
|
||||
- resources/views/settings/_delivery-status-mappings-content.php
|
||||
modified:
|
||||
- src/Modules/Shipments/DeliveryStatus.php
|
||||
- src/Modules/Settings/DeliveryStatusMappingController.php
|
||||
- resources/views/settings/delivery-status-mappings.php
|
||||
- resources/views/layouts/app.php
|
||||
- resources/scss/modules/_delivery-status.scss
|
||||
- resources/lang/pl.php
|
||||
- resources/views/orders/show.php
|
||||
- resources/views/shipments/prepare.php
|
||||
|
||||
key-decisions:
|
||||
- "REDIRECT_PATH zmieniony na /settings/delivery-statuses?tab=mapping — wszystkie redirecty z mappingController idą do nowej strony"
|
||||
- "Zakładki w delivery-statuses przez URL ?tab= (nie localStorage) — redirecty po save lądują na właściwej zakładce"
|
||||
- "CSS custom property --status-color dla niestandardowych statusów zamiast inline background-color — zgodnie z konwencją projektu"
|
||||
- "Migracja zawiera seed 11 systemowych statusów — is_system=1 dla delivered/returned/cancelled"
|
||||
|
||||
patterns-established:
|
||||
- "DeliveryStatusRepository jako wzorzec static cache: private static ?array $cache = null; clearCache() po mutacjach"
|
||||
- "setRepository() pattern: statyczna final klasa z opcjonalnym wstrzykiwanym repo dla DB fallback"
|
||||
|
||||
duration: ~40min
|
||||
started: 2026-04-27T00:00:00Z
|
||||
completed: 2026-04-27T00:00:00Z
|
||||
---
|
||||
|
||||
# Phase 108 Plan 01: Delivery Status Management — Core Summary
|
||||
|
||||
**Wyniesiono statusy przesyłek do tabeli DB z CRUD panelem, dynamicznym ładowaniem w DeliveryStatus.php i przebudową sidebaru — operator może dodać nowy status bez deploymentu.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~40 min |
|
||||
| Tasks | 3/3 completed |
|
||||
| Files created | 5 |
|
||||
| Files modified | 8 |
|
||||
| Delegated agents | 3 Claude |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Tabela delivery_statuses z 11 statusami | Pass* | Migracja gotowa; nieuruchomiona (MySQL offline podczas APPLY) |
|
||||
| AC-2: CRUD niebędacych systemowych statusów | Pass | Controller + widok z blokadami is_system=1 i in-use check |
|
||||
| AC-3: /settings/delivery-statuses ma 2 zakładki | Pass | Statusy (CRUD) + Mapowanie dostawy (include) |
|
||||
| AC-4: Sidebar z nową strukturą | Pass | Statusy zamówień + Statusy przesyłek z badge |
|
||||
| AC-5: DeliveryStatus ładuje z DB | Pass | setRepository() + label()/getAllOptions()/getColor() z DB fallback |
|
||||
| AC-6: Badge dla niestandardowych statusów | Pass | .delivery-badge--custom + --status-color CSS var |
|
||||
|
||||
*Migracja musi zostać uruchomiona: `php bin/migrate.php`
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- `DeliveryStatusRepository` z PDO, per-request static cache, pełnym CRUD i blokadami (systemowe + używane statusy)
|
||||
- `DeliveryStatus::setRepository()` — transparent DB loading bez łamania istniejącego API klasy (ALL_STATUSES, TERMINAL_STATUSES, provider mapy niezmienione)
|
||||
- Panel `/settings/delivery-statuses` z URL-based tabs (`?tab=statuses` / `?tab=mapping`) — redirecty po save zawsze trafiają na właściwą zakładkę
|
||||
- `_delivery-status-mappings-content.php` wydzielony jako reużywalny include z `$mappingBaseUrl` — stara strona `/settings/delivery-status-mappings` nadal działa (backward compat)
|
||||
- Badge rendering z CSS custom property `--status-color` — system statuses → klasy CSS; custom statuses → `delivery-badge--custom` + inline CSS var
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Zmiana | Cel |
|
||||
|------|--------|-----|
|
||||
| `database/migrations/20260427_000103_create_delivery_statuses_table.sql` | Utworzony | CREATE TABLE + seed 11 statusów |
|
||||
| `src/Modules/Shipments/DeliveryStatusRepository.php` | Utworzony | CRUD + static cache dla delivery_statuses |
|
||||
| `src/Modules/Settings/DeliveryStatusesController.php` | Utworzony | Panel /settings/delivery-statuses (index/store/update/destroy) |
|
||||
| `resources/views/settings/delivery-statuses.php` | Utworzony | Widok 2-zakładkowy (Statusy + Mapowanie) |
|
||||
| `resources/views/settings/_delivery-status-mappings-content.php` | Utworzony | Wydzielony include z $mappingBaseUrl |
|
||||
| `src/Modules/Shipments/DeliveryStatus.php` | Zmodyfikowany | +setRepository, +getAllStatuses, +getAllOptions, +getColor; label() z DB |
|
||||
| `src/Modules/Settings/DeliveryStatusMappingController.php` | Zmodyfikowany | +DeliveryStatusRepository DI; REDIRECT_PATH; walidacja przez repo |
|
||||
| `resources/views/settings/delivery-status-mappings.php` | Zmodyfikowany | Thin wrapper → include _delivery-status-mappings-content.php |
|
||||
| `resources/views/layouts/app.php` | Zmodyfikowany | Sidebar: nowy link "Statusy przesyłek", usunięto "Mapowanie statusów" |
|
||||
| `resources/scss/modules/_delivery-status.scss` | Zmodyfikowany | +delivery-status-swatch, +delivery-status-system-badge, +delivery-badge--custom |
|
||||
| `resources/lang/pl.php` | Zmodyfikowany | statuses→"Statusy zamówień"; +delivery_statuses→"Statusy przesyłek" |
|
||||
| `resources/views/orders/show.php` | Zmodyfikowany | Badge (2 miejsca): system→CSS class; custom→--status-color |
|
||||
| `resources/views/shipments/prepare.php` | Zmodyfikowany | Badge (1 miejsce): system→CSS class; custom→--status-color |
|
||||
| `.paul/docs/DB_SCHEMA.md` | Zaktualizowany | Nowa tabela delivery_statuses |
|
||||
| `.paul/docs/ARCHITECTURE.md` | Zaktualizowany | DeliveryStatusRepository, DeliveryStatusesController, setRepository() |
|
||||
| `.paul/docs/TECH_CHANGELOG.md` | Zaktualizowany | Entry Phase 108 Plan 01 |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decyzja | Uzasadnienie | Wpływ |
|
||||
|---------|-------------|-------|
|
||||
| REDIRECT_PATH = `/settings/delivery-statuses?tab=mapping` | Nowa strona jest primary; stara URL to backward compat | Redirecty z DeliveryStatusMappingController lądują na nowej stronie |
|
||||
| Zakładki przez URL `?tab=` nie localStorage | Redirecty po POST muszą trafić na właściwą zakładkę | Provider tabs w mapping content też używają `$mappingBaseUrl` |
|
||||
| CSS custom property `--status-color` dla nowych statusów | Konwencja projektu (conventions.md) | Zachowanie spójności; brak inline `background-color` w PHP |
|
||||
| `is_system=1` dla delivered/returned/cancelled (nie tylko is_terminal) | Są używane w logice biznesowej (AutomationService itp.) | UI blokuje edycję/usunięcie systemowych |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
| Typ | Opis |
|
||||
|-----|------|
|
||||
| Korekta planu | Plan wspominał "Medoo" — projekt używa PDO. Skorygowano przed delegacją. |
|
||||
| Korekta SCSS path | Plan mówił `settings/_delivery-statuses.scss` — faktyczny pattern to `modules/`. Dodano do istniejącego `_delivery-status.scss`. |
|
||||
| Migracja nieuruchomiona | MySQL offline podczas APPLY. Migracja musi być uruchomiona ręcznie. |
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
| Problem | Rozwiązanie |
|
||||
|---------|------------|
|
||||
| MySQL offline podczas APPLY | Migracja zapisana, czeka na uruchomienie XAMPP |
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- `DeliveryStatusRepository` dostępny dla Plan 02 (automatyzacja)
|
||||
- `DeliveryStatus::getAllOptions()` dostarcza DB-driven listę statusów
|
||||
- Routing i widoki działają (syntax OK, SCSS build OK)
|
||||
|
||||
**Concerns:**
|
||||
- Migracja musi być uruchomiona przed testem UI: `php bin/migrate.php`
|
||||
- Plan 02 (automatyzacja) wymaga sprawdzenia gdzie AutomationController/Repository używa hardcoded listy statusów
|
||||
|
||||
**Blockers:**
|
||||
- Brak (poza uruchomieniem migracji)
|
||||
|
||||
---
|
||||
*Phase: 108-delivery-status-management, Plan: 01*
|
||||
*Completed: 2026-04-27*
|
||||
136
.paul/phases/108-delivery-status-management/108-02-PLAN.md
Normal file
136
.paul/phases/108-delivery-status-management/108-02-PLAN.md
Normal file
@@ -0,0 +1,136 @@
|
||||
---
|
||||
phase: 108-delivery-status-management
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: [108-01]
|
||||
files_modified:
|
||||
- src/Modules/Automation/AutomationService.php
|
||||
- src/Modules/Automation/AutomationRepository.php
|
||||
- resources/views/automation/form.php
|
||||
- public/assets/js/modules/automation-form.js
|
||||
- .paul/docs/ARCHITECTURE.md
|
||||
- .paul/docs/TECH_CHANGELOG.md
|
||||
autonomous: true
|
||||
delegation: on
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Zaktualizowac wszystkie miejsca w module Automatyzacji, ktore uzywaaly hardcoded listy statusow znormalizowanych, zeby korzystaly z DB przez `DeliveryStatus::getAllOptions()`.
|
||||
|
||||
## Purpose
|
||||
Po Plan 01 operator moze dodac nowy status w DB. Jednak dopoki formularze automatyzacji i walidacje beda uzywaly hardcoded stalych, nowy status nie pojawi sie w dropdownach regul. Plan 02 domyka integracje dla warstwy automatyzacji.
|
||||
|
||||
## Output
|
||||
- Dropdown warunków/akcji automatyzacji laduje statusy z DB
|
||||
- Brak regresji dla istniejacych regul automatyzacji
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/STATE.md
|
||||
|
||||
## Prior Work
|
||||
@.paul/phases/108-delivery-status-management/108-01-SUMMARY.md
|
||||
|
||||
## Source Files
|
||||
@src/Modules/Automation/AutomationService.php
|
||||
@src/Modules/Automation/AutomationRepository.php
|
||||
@resources/views/automation/form.php
|
||||
@public/assets/js/modules/automation-form.js
|
||||
</context>
|
||||
|
||||
<skills>
|
||||
## Required Skills
|
||||
|
||||
| Skill | Priority | When to Invoke | Loaded? |
|
||||
|-------|----------|----------------|---------|
|
||||
| sonar-scanner (CLI) | required | Po APPLY, przed UNIFY | o |
|
||||
</skills>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Dropdown statusow w automatyzacji pochodzi z DB
|
||||
```gherkin
|
||||
Given operator tworzy lub edytuje regule automatyzacji
|
||||
When wybierze warunek "status przesylki" lub akcje zalezna od statusu
|
||||
Then lista dostepnych statusow zawiera wszystkie statusy z tabeli delivery_statuses
|
||||
And nowy status dodany przez /settings/delivery-statuses pojawia sie w dropdownie bez deploymentu
|
||||
```
|
||||
|
||||
## AC-2: Istniejace reguly automatyzacji dzialaja bez zmian
|
||||
```gherkin
|
||||
Given regula automatyzacji z warunkiem na status "in_transit"
|
||||
When cron wywola ewaluacje tej reguly
|
||||
Then warunek jest oceniany tak samo jak przed zmiana
|
||||
And brak wyjatkow zwiazanych z nieistniejacymi stalymi statusow
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Zaktualizowac dropdown statusow w automatyzacji</name>
|
||||
<files>src/Modules/Automation/AutomationService.php, src/Modules/Automation/AutomationRepository.php, resources/views/automation/form.php, public/assets/js/modules/automation-form.js</files>
|
||||
<action>
|
||||
1. Zidentyfikowac wszystkie miejsca w module Automation gdzie uzywa sie:
|
||||
- `DeliveryStatus::ALL_STATUSES` lub `DeliveryStatus::LABEL_PL` do budowania opcji selecta
|
||||
- Hardcoded tablicy statusow w walidacji warunkow/akcji
|
||||
2. Podpiac `DeliveryStatusRepository` do `AutomationRepository` lub serwisu ktory
|
||||
buduje opcje dla widoku formularza (sprawdzic jak repo jest dostarczone w tym module).
|
||||
3. W widoku `resources/views/automation/form.php` — zamiana hardcoded opcji statusow
|
||||
na przekazana z kontrolera tablice `$deliveryStatusOptions` (z getAllOptions()).
|
||||
4. W `automation-form.js` — opcje do dynamicznie dodawanych wierszy warunkow/akcji:
|
||||
- Jesli opcje sa generowane statycznie w JS (hardcoded), zastapic je danymi
|
||||
z `window.automationDeliveryStatusOptions` ktore beda inicjalizowane inline w widoku PHP
|
||||
- Jesli opcje sa generowane przez PHP przy renderowaniu — brak zmian w JS, tylko upewnic
|
||||
sie ze dynamicznie dodawane wiersze tez dostaja prawidlowa liste
|
||||
5. Walidacja: sprawdzic czy AutomationService lub AutomationRepository waliduje wartosc statusu
|
||||
przy zapisie — jesli tak, zaktualizowac by uzywala `DeliveryStatusRepository::getByKey()`.
|
||||
6. Zaktualizowac `.paul/docs/ARCHITECTURE.md` i `.paul/docs/TECH_CHANGELOG.md`.
|
||||
</action>
|
||||
<verify>
|
||||
- Formularz automatyzacji laduje sie poprawnie, dropdown statusow zawiera wszystkie DB-rekordy
|
||||
- Dodanie nowego statusu przez /settings/delivery-statuses sprawia ze pojawia sie w dropdownie
|
||||
automatyzacji bez odswiezania cache serwera
|
||||
- Istniejace reguly z hardcoded kluczami statusow (np. in_transit) dzialaja bez bledow
|
||||
- `php vendor/bin/phpunit` bez nowych failurow
|
||||
</verify>
|
||||
<done>AC-1 i AC-2 spelnione.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- Logika ewaluacji warunkow automatyzacji poza podpieciem dynamicznej listy statusow
|
||||
- Schemat danych regul i akcji automatyzacji
|
||||
- Mechanizm crona ani harmonogram
|
||||
- Provider mapy w DeliveryStatus.php
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Tylko dropdown statusow — nie zmieniamy innych typow warunkow/akcji automatyzacji
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Dropdown statusow w formularzu automatyzacji pokazuje statusy z DB
|
||||
- [ ] Nowy status z CRUD pojawia sie w dropdownie automatyzacji
|
||||
- [ ] Istniejace reguly automatyzacji dzialaja bez bledow
|
||||
- [ ] `php vendor/bin/phpunit` przechodzi bez regresji
|
||||
- [ ] Dokumentacja .paul/docs/* zaktualizowana
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Pelna integracja listy statusow z DB w calej aplikacji
|
||||
- Operator nie musi robic deploymentu zeby nowy status pojawil sie we wszystkich miejscach
|
||||
- Brak regresji w istniejacych automatyzacjach
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/108-delivery-status-management/108-02-SUMMARY.md`
|
||||
</output>
|
||||
151
.paul/phases/108-delivery-status-management/108-02-SUMMARY.md
Normal file
151
.paul/phases/108-delivery-status-management/108-02-SUMMARY.md
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
phase: 108-delivery-status-management
|
||||
plan: 02
|
||||
subsystem: automation, settings
|
||||
tags: [delivery-status, automation, dropdown, php, db-driven, ui-refactor]
|
||||
|
||||
requires:
|
||||
- phase: 108-01-delivery-status-management
|
||||
provides: DeliveryStatusRepository, DeliveryStatus::setRepository(), getAllOptions(), getAllStatuses()
|
||||
|
||||
provides:
|
||||
- Automation dropdowns dla shipment_status (warunek + akcja) załadowane z DB
|
||||
- Walidacja shipment_status / update_shipment_status przeciw kluczom z DB
|
||||
- Osobna podstrona formularza dla CRUD delivery statuses (`/settings/delivery-statuses/new`, `/{id}/edit`)
|
||||
- Lista statusów uproszczona: rename "Terminal" → "Końcowy", usunięta kolumna "Typ"
|
||||
|
||||
affects: [delivery-status-consumers, automation-rules-existing]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- Path params w controllerach przez `$request->input('id')`, nie jako argumenty metody
|
||||
- Form-as-separate-page pattern dla CRUD (zamiast inline edit row)
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- resources/views/settings/delivery-status-form.php
|
||||
modified:
|
||||
- src/Modules/Automation/AutomationController.php
|
||||
- src/Modules/Automation/AutomationService.php
|
||||
- src/Modules/Settings/DeliveryStatusesController.php
|
||||
- resources/views/settings/delivery-statuses.php
|
||||
- routes/web.php
|
||||
- .paul/docs/ARCHITECTURE.md
|
||||
- .paul/docs/TECH_CHANGELOG.md
|
||||
|
||||
key-decisions:
|
||||
- "BREAKING: Drop backward compat dla starych grupowych kluczy automatyzacji (registered, courier_pickup, etc.) — kolizja semantyczna picked_up by silently dawała wrong matches"
|
||||
- "Path params w DeliveryStatusesController via $request->input('id') — naprawia bug pre-existing z Plan 01 (update/destroy też miały błędną sygnaturę)"
|
||||
- "CRUD delivery statuses przeniesiony na osobną podstronę — UX lepszy niż inline edit row"
|
||||
|
||||
patterns-established:
|
||||
- "Walidacja submitted status keys przez DeliveryStatus::getAllStatuses() (zamiast hardcoded array_keys konstanty)"
|
||||
- "Form view jako osobna podstrona z disabled key field przy edycji (immutable po utworzeniu)"
|
||||
|
||||
duration: ~50min
|
||||
started: 2026-04-27T01:00:00Z
|
||||
completed: 2026-04-27T02:00:00Z
|
||||
---
|
||||
|
||||
# Phase 108 Plan 02: Automation Dropdowns z DB Summary
|
||||
|
||||
**Domknięcie integracji DB-driven statusów: dropdowny automatyzacji ładują się z `delivery_statuses`, plus refaktor UI listy statusów (osobna podstrona dla CRUD).**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~50 min |
|
||||
| Tasks | 1 planowane + 2 user-requested ad-hoc |
|
||||
| Files created | 1 |
|
||||
| Files modified | 7 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Dropdown statusów z DB | Pass | `AutomationController::buildShipmentStatusOptions()` z `DeliveryStatus::getAllOptions()` |
|
||||
| AC-2: Istniejące reguły działają bez błędów | Pass* | *BREAKING: stare grupowe klucze nie matchują (decyzja użytkownika "poprawię ręcznie"); brak wyjątków, ciche no-match |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- `AutomationController` — usunięto stałą `SHIPMENT_STATUS_OPTIONS` (8 grupowych kluczy); dropdown buduje się z DB przez `DeliveryStatus::getAllOptions()`
|
||||
- `AutomationService` — usunięto stałą `SHIPMENT_STATUS_OPTION_MAP`; ewaluacja `evaluateShipmentStatusCondition` porównuje klucze bezpośrednio; `resolveStatusFromActionKey` używa kluczy DB jako target
|
||||
- Walidacja w `parseConditionValue('shipment_status')` i `parseActionConfig('update_shipment_status')` przez `DeliveryStatus::getAllStatuses()`
|
||||
- **Ad-hoc UI refactor (user request):** osobna podstrona formularza CRUD (`delivery-status-form.php`) zamiast inline edit row na liście; przycisk "+ Dodaj status" zamiast formy na dole strony
|
||||
- **Ad-hoc UI changes:** rename column "Terminal" → "Końcowy", usunięta kolumna "Typ" (badge "systemowy" — informacja zbędna gdy brak akcji edycji)
|
||||
- **Bug fix (pre-existing):** sygnatury `update`/`destroy`/`edit` w `DeliveryStatusesController` używały path param jako 2. argument metody — router projektu przekazuje params przez `$request->input('id')`. Naprawione wszystkie trzy.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `src/Modules/Automation/AutomationController.php` | Modified | Usunięta stała SHIPMENT_STATUS_OPTIONS; nowa metoda `buildShipmentStatusOptions()`; walidacja przez DeliveryStatus |
|
||||
| `src/Modules/Automation/AutomationService.php` | Modified | Usunięta stała SHIPMENT_STATUS_OPTION_MAP; bezpośrednie porównanie kluczy w ewaluacji |
|
||||
| `src/Modules/Settings/DeliveryStatusesController.php` | Modified | +`create()`, `edit()`, `renderForm()`; fix path params w update/destroy |
|
||||
| `resources/views/settings/delivery-status-form.php` | Created | Osobna podstrona formularza CRUD statusów |
|
||||
| `resources/views/settings/delivery-statuses.php` | Modified | Lista uproszczona: rename Terminal→Końcowy, usunięte Typ + inline edit + bottom add form, przycisk "+ Dodaj status" |
|
||||
| `routes/web.php` | Modified | +2 GET routes: `/new`, `/{id}/edit` |
|
||||
| `.paul/docs/ARCHITECTURE.md` | Modified | Sekcja Phase 108 Plan 02 |
|
||||
| `.paul/docs/TECH_CHANGELOG.md` | Modified | Wpis Phase 108 Plan 02 (BREAKING) |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decyzja | Uzasadnienie | Wpływ |
|
||||
|---------|-------------|-------|
|
||||
| Drop backward compat (option A) | Kolizja semantyczna `picked_up`: stary grupowy klucz mapował na `delivered`, nowy klucz DB to "Odebrana przez kuriera" — odwrotne końce cyklu | BREAKING dla istniejących reguł; user: "poprawię ręcznie jak coś" |
|
||||
| Form jako osobna podstrona zamiast inline | Inline edit row na liście "źle wygląda" (user feedback) | Lepszy UX, czytelniejszy formularz z grid 2-col |
|
||||
| Usunięcie kolumny "Typ" | Badge "systemowy" zbędny — brak przycisku Edytuj/Usuń przy systemowych już to komunikuje | Lista 6 kolumn zamiast 7 |
|
||||
| Path params via `$request->input('id')` | Konwencja projektu (router nie injectuje argumentów do handlerów) | Naprawiono pre-existing bug w update/destroy z Plan 01 |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Files w plan, ale bez zmian | 3 | `AutomationRepository.php`, `form.php`, `automation-form.js` — analiza wykazała brak potrzeby modyfikacji |
|
||||
| Scope additions (user request) | 3 | Rename column, hide column, separate form page |
|
||||
| Bug fix poza scope | 1 | Path params w update/destroy (pre-existing z Plan 01) |
|
||||
| Deferred | 1 | sonar-scanner dla Phase 105/106/107/108 — punkt deferred z Plan 01, niezmieniony |
|
||||
|
||||
**Total impact:** Plan zakończony plus dodatkowe ulepszenia UX zgłoszone przez użytkownika podczas weryfikacji Plan 01. Bug fix path params eliminuje crash przy edycji.
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Routing] Path params jako argumenty metody zamiast `$request->input()`**
|
||||
- **Found during:** Test ścieżki `/settings/delivery-statuses/{id}/edit` (user runtime error)
|
||||
- **Issue:** "Too few arguments to function ... edit(), 1 passed ... and exactly 2 expected"
|
||||
- **Fix:** Zmieniono sygnatury `edit()`, `update()`, `destroy()` na single `Request $request`; `$id` czytany przez `$request->input('id', 0)`
|
||||
- **Files:** `src/Modules/Settings/DeliveryStatusesController.php`
|
||||
- **Verification:** `php -l` OK; konwencja zgodna z `ReceiptController`
|
||||
|
||||
### Deferred Items
|
||||
|
||||
- sonar-scanner skill (required) — Phase 105, 106, 107, 108 (już deferred z poprzednich planów)
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
| Issue | Resolution |
|
||||
|-------|------------|
|
||||
| Kolizja semantyczna klucza `picked_up` (stary group → `delivered` vs nowy DB → "Odebrana przez kuriera") | Advisor consult przed kodowaniem; user wybrał opcję A (drop backward compat) |
|
||||
| Pre-existing bug z path params w update/destroy | Zauważony dopiero przy nowym `edit()` — naprawiony razem |
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Pełna integracja DB-driven statusów: dropdowny + walidacja + ewaluacja
|
||||
- CRUD UI ergonomiczny: lista + osobna podstrona formularza
|
||||
- Phase 108 — wszystkie 2 plans zamknięte
|
||||
|
||||
**Concerns:**
|
||||
- Migracja `20260427_000103_create_delivery_statuses_table.sql` — wymaga `php bin/migrate.php` na środowisku produkcyjnym (XAMPP offline podczas APPLY 108-01)
|
||||
- Stare reguły automatyzacji z grupowymi kluczami (jeśli istnieją) — wymagają ręcznego odtworzenia z nowymi kluczami DB
|
||||
- sonar-scanner gap dla Phase 105/106/107/108 — odłożony
|
||||
|
||||
**Blockers:** None
|
||||
|
||||
---
|
||||
*Phase: 108-delivery-status-management, Plan: 02*
|
||||
*Completed: 2026-04-27*
|
||||
@@ -0,0 +1,25 @@
|
||||
CREATE TABLE IF NOT EXISTS `delivery_statuses` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`key` VARCHAR(50) NOT NULL,
|
||||
`label_pl` VARCHAR(100) NOT NULL,
|
||||
`color` VARCHAR(7) NOT NULL DEFAULT '#6c757d',
|
||||
`sort_order` TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`is_terminal` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`is_system` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `delivery_statuses_key_unique` (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
INSERT INTO `delivery_statuses` (`key`, `label_pl`, `color`, `sort_order`, `is_terminal`, `is_system`) VALUES
|
||||
('unknown', 'Nieznany', '#f5f5f5', 0, 0, 0),
|
||||
('created', 'Utworzona', '#e3f2fd', 1, 0, 0),
|
||||
('confirmed', 'Potwierdzona', '#bbdefb', 2, 0, 0),
|
||||
('picked_up', 'Odebrana przez kuriera', '#e1f5fe', 3, 0, 0),
|
||||
('in_transit', 'W tranzycie', '#fff3e0', 4, 0, 0),
|
||||
('out_for_delivery', 'W doręczeniu', '#ffe0b2', 5, 0, 0),
|
||||
('ready_for_pickup', 'Gotowa do odbioru', '#f3e5f5', 6, 0, 0),
|
||||
('delivered', 'Doręczona', '#e8f5e9', 7, 1, 1),
|
||||
('returned', 'Zwrócona', '#ffebee', 8, 1, 1),
|
||||
('cancelled', 'Anulowana', '#e0e0e0', 9, 1, 1),
|
||||
('problem', 'Problem', '#fff8e1', 10, 0, 0);
|
||||
File diff suppressed because one or more lines are too long
@@ -29,7 +29,8 @@ return [
|
||||
'cron' => 'Harmonogram',
|
||||
'dashboard' => 'Dashboard',
|
||||
'settings' => 'Ustawienia',
|
||||
'statuses' => 'Statusy',
|
||||
'statuses' => 'Statusy zamówień',
|
||||
'delivery_statuses' => 'Statusy przesyłek',
|
||||
'integrations' => 'Integracje',
|
||||
'allegro' => 'Integracje Allegro',
|
||||
'apaczka' => 'Integracja Apaczka',
|
||||
|
||||
@@ -24,3 +24,26 @@
|
||||
text-decoration: none;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.delivery-status-swatch {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 2px;
|
||||
background: var(--status-color, #6c757d);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.delivery-status-system-badge {
|
||||
display: inline-block;
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.75em;
|
||||
background: #e9ecef;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.delivery-badge--custom {
|
||||
background: var(--status-color, #6c757d);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -139,8 +139,8 @@
|
||||
$dsmUnmappedCount = 0;
|
||||
}
|
||||
?>
|
||||
<a class="sidebar__sublink<?= $currentMenu === 'settings' && $currentSettings === 'delivery-status-mappings' ? ' is-active' : '' ?>" href="/settings/delivery-status-mappings">
|
||||
Mapowanie statusów dostawy
|
||||
<a class="sidebar__sublink<?= $currentMenu === 'settings' && in_array($currentSettings, ['delivery-statuses', 'delivery-status-mappings'], true) ? ' is-active' : '' ?>" href="/settings/delivery-statuses">
|
||||
<?= $e($t('navigation.delivery_statuses')) ?>
|
||||
<?php if ($dsmUnmappedCount > 0): ?>
|
||||
<span class="sidebar__badge" title="Niezmapowane statusy"><?= (int) $dsmUnmappedCount ?></span>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -370,7 +370,11 @@ foreach ($addressesList as $address) {
|
||||
?>
|
||||
<dt>Status dostawy</dt>
|
||||
<dd>
|
||||
<span class="delivery-badge delivery-badge--<?= $e($ldStatus) ?>"><?= $e($ldLabel) ?></span>
|
||||
<?php
|
||||
$ldIsSystem = in_array($ldStatus, \App\Modules\Shipments\DeliveryStatus::ALL_STATUSES, true);
|
||||
$ldColor = $ldIsSystem ? '' : \App\Modules\Shipments\DeliveryStatus::getColor($ldStatus);
|
||||
?>
|
||||
<span class="delivery-badge<?= $ldIsSystem ? ' delivery-badge--' . $e($ldStatus) : ' delivery-badge--custom' ?>"<?= $ldColor !== '' ? ' style="--status-color: ' . $e($ldColor) . '"' : '' ?>><?= $e($ldLabel) ?></span>
|
||||
<?php if ($ldDate !== ''): ?><small class="muted" style="margin-left:4px"><?= $e($ldDate) ?></small><?php endif; ?>
|
||||
</dd>
|
||||
<?php endif; ?>
|
||||
@@ -603,7 +607,11 @@ foreach ($addressesList as $address) {
|
||||
$pkgDeliveryDesc = $pkgDeliveryRaw !== '' ? \App\Modules\Shipments\DeliveryStatus::description($pkgProvider, $pkgDeliveryRaw) : '';
|
||||
$pkgDeliveryTitle = $pkgDeliveryRaw !== '' ? ($pkgDeliveryRaw . ' — ' . $pkgDeliveryDesc) : '';
|
||||
?>
|
||||
<span class="delivery-badge delivery-badge--<?= $e($pkgDeliveryStatus) ?>" title="<?= $e($pkgDeliveryTitle) ?>"><?= $e($pkgDeliveryLabel) ?></span>
|
||||
<?php
|
||||
$pkgIsSystem = in_array($pkgDeliveryStatus, \App\Modules\Shipments\DeliveryStatus::ALL_STATUSES, true);
|
||||
$pkgColor = $pkgIsSystem ? '' : \App\Modules\Shipments\DeliveryStatus::getColor($pkgDeliveryStatus);
|
||||
?>
|
||||
<span class="delivery-badge<?= $pkgIsSystem ? ' delivery-badge--' . $e($pkgDeliveryStatus) : ' delivery-badge--custom' ?>" title="<?= $e($pkgDeliveryTitle) ?>"<?= $pkgColor !== '' ? ' style="--status-color: ' . $e($pkgColor) . '"' : '' ?>><?= $e($pkgDeliveryLabel) ?></span>
|
||||
</td>
|
||||
<td style="white-space:nowrap" data-pkg-tracking-cell="<?= $e((string) ($pkg['id'] ?? 0)) ?>">
|
||||
<?= $e($pkgTracking !== '' ? $pkgTracking : '-') ?><?php
|
||||
|
||||
211
resources/views/settings/_delivery-status-mappings-content.php
Normal file
211
resources/views/settings/_delivery-status-mappings-content.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
$providersList = is_array($providers ?? null) ? $providers : [];
|
||||
$mappingsList = is_array($mappings ?? null) ? $mappings : [];
|
||||
$normalizedOptionsList = is_array($normalizedOptions ?? null) ? $normalizedOptions : [];
|
||||
$unmappedList = is_array($unmappedRawStatuses ?? null) ? $unmappedRawStatuses : [];
|
||||
$currentProvider = (string) ($provider ?? 'inpost');
|
||||
$mappingBaseUrl = isset($mappingBaseUrl) ? (string) $mappingBaseUrl : '/settings/delivery-status-mappings';
|
||||
?>
|
||||
|
||||
<?php if ($unmappedList !== []): ?>
|
||||
<section class="card mt-16 dsm-unmapped">
|
||||
<h2 class="section-title">Niezmapowane statusy wykryte w systemie (<?= count($unmappedList) ?>)</h2>
|
||||
<p class="muted mt-8">Statusy odebrane z API przewoźnika <strong><?= $e((string) ($providersList[$currentProvider] ?? $currentProvider)) ?></strong>, dla których nie ma jeszcze mapowania. Przypisz znormalizowany status, aby paczki przestały być oznaczane jako „Nieznany".</p>
|
||||
|
||||
<form action="/settings/delivery-status-mappings/save-bulk" method="post" class="mt-12">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="provider" value="<?= $e($currentProvider) ?>">
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table class="table table--compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status surowy</th>
|
||||
<th>Liczba paczek</th>
|
||||
<th>Ostatnio widziany</th>
|
||||
<th>Opis (własny)</th>
|
||||
<th>Status znormalizowany</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($unmappedList as $unmapped): ?>
|
||||
<?php
|
||||
$rawStatus = (string) ($unmapped['raw_status'] ?? '');
|
||||
$count = (int) ($unmapped['count'] ?? 0);
|
||||
$lastSeen = (string) ($unmapped['last_seen'] ?? '');
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<code class="dsm-raw-status"><?= $e($rawStatus) ?></code>
|
||||
<input type="hidden" name="raw_status[]" value="<?= $e($rawStatus) ?>">
|
||||
</td>
|
||||
<td><?= $count ?></td>
|
||||
<td><?= $e($lastSeen) ?></td>
|
||||
<td>
|
||||
<input type="text" name="description[]" class="form-control form-control--sm"
|
||||
value="<?= $e($rawStatus) ?>" maxlength="255">
|
||||
</td>
|
||||
<td>
|
||||
<select name="normalized_status[]" class="form-control form-control--sm" required>
|
||||
<?php foreach ($normalizedOptionsList as $optValue => $optLabel): ?>
|
||||
<option value="<?= $e($optValue) ?>"<?= $optValue === 'unknown' ? ' selected' : '' ?>>
|
||||
<?= $e($optLabel) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-12">
|
||||
<button type="submit" class="btn btn--primary">Dodaj do mapowania</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="card mt-16">
|
||||
<nav class="content-tabs-nav" aria-label="Wybierz przewoźnika">
|
||||
<?php foreach ($providersList as $provKey => $provLabel): ?>
|
||||
<?php
|
||||
$mappingProviderUrl = $mappingBaseUrl . (str_contains($mappingBaseUrl, '?') ? '&' : '?') . 'provider=' . rawurlencode($provKey);
|
||||
?>
|
||||
<a class="content-tab-btn<?= $currentProvider === $provKey ? ' is-active' : '' ?>"
|
||||
href="<?= $e($mappingProviderUrl) ?>">
|
||||
<?= $e($provLabel) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
|
||||
<?php if ($mappingsList === []): ?>
|
||||
<p class="muted mt-16">Brak mapowań dla tego przewoźnika.</p>
|
||||
<?php else: ?>
|
||||
<form id="dsm-bulk-form" action="/settings/delivery-status-mappings/save-bulk" method="post" class="mt-16">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="provider" value="<?= $e($currentProvider) ?>">
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table class="table table--compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status surowy</th>
|
||||
<th>Opis</th>
|
||||
<th>Status znormalizowany</th>
|
||||
<th>Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($mappingsList as $mapping): ?>
|
||||
<?php
|
||||
$rawStatus = (string) ($mapping['raw_status'] ?? '');
|
||||
$desc = (string) ($mapping['description'] ?? '');
|
||||
$normalized = (string) ($mapping['normalized_status'] ?? '');
|
||||
$isCustom = !empty($mapping['is_custom']);
|
||||
?>
|
||||
<tr class="<?= $isCustom ? 'dsm-row--custom' : '' ?>">
|
||||
<td>
|
||||
<code class="dsm-raw-status"><?= $e($rawStatus) ?></code>
|
||||
<input type="hidden" name="raw_status[]" value="<?= $e($rawStatus) ?>">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="description[]" class="form-control form-control--sm"
|
||||
value="<?= $e($desc) ?>" maxlength="255">
|
||||
</td>
|
||||
<td>
|
||||
<select name="normalized_status[]" class="form-control form-control--sm">
|
||||
<?php foreach ($normalizedOptionsList as $optValue => $optLabel): ?>
|
||||
<option value="<?= $e($optValue) ?>"<?= $optValue === $normalized ? ' selected' : '' ?>>
|
||||
<?= $e($optLabel) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($isCustom): ?>
|
||||
<button type="button" class="btn btn--danger btn--sm js-dsm-reset"
|
||||
data-raw-status="<?= $e($rawStatus) ?>"
|
||||
data-confirm-msg="Przywrócić domyślne mapowanie dla "<?= $e($rawStatus) ?>"?">Resetuj</button>
|
||||
<?php else: ?>
|
||||
<span class="muted">domyślne</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary">Zapisz wszystkie</button>
|
||||
<button type="button" class="btn btn--danger" id="js-dsm-reset-all">Resetuj wszystkie</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form id="dsm-reset-all-form" action="/settings/delivery-status-mappings/reset-all" method="post" style="display:none;">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="provider" value="<?= $e($currentProvider) ?>">
|
||||
</form>
|
||||
|
||||
<form id="dsm-reset-form" action="/settings/delivery-status-mappings/reset" method="post" style="display:none;">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="provider" value="<?= $e($currentProvider) ?>">
|
||||
<input type="hidden" name="raw_status" id="dsm-reset-raw-status" value="">
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var resetForm = document.getElementById('dsm-reset-form');
|
||||
var resetInput = document.getElementById('dsm-reset-raw-status');
|
||||
if (!resetForm || !resetInput) return;
|
||||
|
||||
var resetAllBtn = document.getElementById('js-dsm-reset-all');
|
||||
var resetAllForm = document.getElementById('dsm-reset-all-form');
|
||||
if (resetAllBtn && resetAllForm) {
|
||||
resetAllBtn.addEventListener('click', function() {
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.confirm === 'function') {
|
||||
window.OrderProAlerts.confirm({
|
||||
title: 'Resetuj wszystkie mapowania',
|
||||
message: 'Przywrócić wszystkie mapowania do domyślnych dla tego przewoźnika?',
|
||||
confirmLabel: 'Resetuj wszystkie',
|
||||
cancelLabel: 'Anuluj',
|
||||
danger: true
|
||||
}).then(function(accepted) {
|
||||
if (!accepted) return;
|
||||
resetAllForm.submit();
|
||||
});
|
||||
} else {
|
||||
resetAllForm.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.js-dsm-reset').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var rawStatus = btn.getAttribute('data-raw-status') || '';
|
||||
var message = btn.getAttribute('data-confirm-msg') || 'Przywrócić domyślne?';
|
||||
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.confirm === 'function') {
|
||||
window.OrderProAlerts.confirm({
|
||||
title: 'Resetuj mapowanie',
|
||||
message: message,
|
||||
confirmLabel: 'Resetuj',
|
||||
cancelLabel: 'Anuluj',
|
||||
danger: true
|
||||
}).then(function(accepted) {
|
||||
if (!accepted) return;
|
||||
resetInput.value = rawStatus;
|
||||
resetForm.submit();
|
||||
});
|
||||
} else {
|
||||
resetInput.value = rawStatus;
|
||||
resetForm.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
76
resources/views/settings/delivery-status-form.php
Normal file
76
resources/views/settings/delivery-status-form.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
$row = is_array($row ?? null) ? $row : null;
|
||||
$isEdit = (bool) ($isEdit ?? false);
|
||||
$errorMessage = (string) ($errorMessage ?? '');
|
||||
$csrfToken = (string) ($csrfToken ?? '');
|
||||
|
||||
$rowId = (int) ($row['id'] ?? 0);
|
||||
$rowKey = (string) ($row['key'] ?? '');
|
||||
$rowLabel = (string) ($row['label_pl'] ?? '');
|
||||
$rowColor = (string) ($row['color'] ?? '#6c757d');
|
||||
$rowSortOrder = (int) ($row['sort_order'] ?? 100);
|
||||
$rowIsTerminal = !empty($row['is_terminal']);
|
||||
|
||||
$action = $isEdit
|
||||
? '/settings/delivery-statuses/' . $rowId . '/update'
|
||||
: '/settings/delivery-statuses';
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="section-title"><?= $isEdit ? 'Edytuj status przesyłki' : 'Nowy status przesyłki' ?></h2>
|
||||
|
||||
<?php if ($errorMessage !== ''): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e($errorMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="<?= $e($action) ?>" method="post" novalidate class="mt-12">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken) ?>">
|
||||
|
||||
<div class="form-grid-2">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Klucz *</span>
|
||||
<?php if ($isEdit): ?>
|
||||
<input class="form-control" type="text" value="<?= $e($rowKey) ?>" disabled>
|
||||
<small class="muted">Klucz nie może być zmieniany po utworzeniu.</small>
|
||||
<?php else: ?>
|
||||
<input class="form-control" type="text" name="key" maxlength="50" required
|
||||
pattern="[a-z][a-z0-9_]{0,49}"
|
||||
placeholder="np. custom_status"
|
||||
value="<?= $e($rowKey) ?>">
|
||||
<small class="muted">Małe litery, cyfry, _, max 50 znaków, zaczyna się literą.</small>
|
||||
<?php endif; ?>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">Etykieta *</span>
|
||||
<input class="form-control" type="text" name="label_pl" maxlength="100" required
|
||||
placeholder="np. Mój status" value="<?= $e($rowLabel) ?>">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-grid-2 mt-0">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Kolor</span>
|
||||
<input class="form-control" type="color" name="color" value="<?= $e($rowColor) ?>">
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">Kolejność</span>
|
||||
<input class="form-control" type="number" name="sort_order"
|
||||
min="0" max="9999" value="<?= $rowSortOrder ?>">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-grid-2 mt-0">
|
||||
<label class="form-field" style="display:flex;align-items:center;gap:6px;flex-direction:row">
|
||||
<input type="checkbox" name="is_terminal" value="1"<?= $rowIsTerminal ? ' checked' : '' ?>>
|
||||
<span class="field-label" style="margin:0">Końcowy</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $isEdit ? 'Zapisz zmiany' : 'Utwórz status' ?></button>
|
||||
<a href="/settings/delivery-statuses?tab=statuses" class="btn btn--secondary">Anuluj</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -1,220 +1,3 @@
|
||||
<?php
|
||||
$providersList = is_array($providers ?? null) ? $providers : [];
|
||||
$mappingsList = is_array($mappings ?? null) ? $mappings : [];
|
||||
$normalizedOptionsList = is_array($normalizedOptions ?? null) ? $normalizedOptions : [];
|
||||
$unmappedList = is_array($unmappedRawStatuses ?? null) ? $unmappedRawStatuses : [];
|
||||
$currentProvider = (string) ($provider ?? 'inpost');
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="section-title">Mapowanie statusów dostawy</h2>
|
||||
<p class="muted mt-12">Konfiguracja przypisania surowych statusów z API przewoźników do znormalizowanych statusów w aplikacji.</p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<?php if ($unmappedList !== []): ?>
|
||||
<section class="card mt-16 dsm-unmapped">
|
||||
<h2 class="section-title">Niezmapowane statusy wykryte w systemie (<?= count($unmappedList) ?>)</h2>
|
||||
<p class="muted mt-8">Statusy odebrane z API przewoźnika <strong><?= $e((string) ($providersList[$currentProvider] ?? $currentProvider)) ?></strong>, dla których nie ma jeszcze mapowania. Przypisz znormalizowany status, aby paczki przestały być oznaczane jako „Nieznany".</p>
|
||||
|
||||
<form action="/settings/delivery-status-mappings/save-bulk" method="post" class="mt-12">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="provider" value="<?= $e($currentProvider) ?>">
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table class="table table--compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status surowy</th>
|
||||
<th>Liczba paczek</th>
|
||||
<th>Ostatnio widziany</th>
|
||||
<th>Opis (własny)</th>
|
||||
<th>Status znormalizowany</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($unmappedList as $unmapped): ?>
|
||||
<?php
|
||||
$rawStatus = (string) ($unmapped['raw_status'] ?? '');
|
||||
$count = (int) ($unmapped['count'] ?? 0);
|
||||
$lastSeen = (string) ($unmapped['last_seen'] ?? '');
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<code class="dsm-raw-status"><?= $e($rawStatus) ?></code>
|
||||
<input type="hidden" name="raw_status[]" value="<?= $e($rawStatus) ?>">
|
||||
</td>
|
||||
<td><?= $count ?></td>
|
||||
<td><?= $e($lastSeen) ?></td>
|
||||
<td>
|
||||
<input type="text" name="description[]" class="form-control form-control--sm"
|
||||
value="<?= $e($rawStatus) ?>" maxlength="255">
|
||||
</td>
|
||||
<td>
|
||||
<select name="normalized_status[]" class="form-control form-control--sm" required>
|
||||
<?php foreach ($normalizedOptionsList as $optValue => $optLabel): ?>
|
||||
<option value="<?= $e($optValue) ?>"<?= $optValue === 'unknown' ? ' selected' : '' ?>>
|
||||
<?= $e($optLabel) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-12">
|
||||
<button type="submit" class="btn btn--primary">Dodaj do mapowania</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="card mt-16">
|
||||
<nav class="content-tabs-nav" aria-label="Wybierz przewoźnika">
|
||||
<?php foreach ($providersList as $provKey => $provLabel): ?>
|
||||
<a class="content-tab-btn<?= $currentProvider === $provKey ? ' is-active' : '' ?>"
|
||||
href="/settings/delivery-status-mappings?provider=<?= $e(rawurlencode($provKey)) ?>">
|
||||
<?= $e($provLabel) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
|
||||
<?php if ($mappingsList === []): ?>
|
||||
<p class="muted mt-16">Brak mapowań dla tego przewoźnika.</p>
|
||||
<?php else: ?>
|
||||
<form id="dsm-bulk-form" action="/settings/delivery-status-mappings/save-bulk" method="post" class="mt-16">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="provider" value="<?= $e($currentProvider) ?>">
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table class="table table--compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status surowy</th>
|
||||
<th>Opis</th>
|
||||
<th>Status znormalizowany</th>
|
||||
<th>Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($mappingsList as $mapping): ?>
|
||||
<?php
|
||||
$rawStatus = (string) ($mapping['raw_status'] ?? '');
|
||||
$desc = (string) ($mapping['description'] ?? '');
|
||||
$normalized = (string) ($mapping['normalized_status'] ?? '');
|
||||
$isCustom = !empty($mapping['is_custom']);
|
||||
?>
|
||||
<tr class="<?= $isCustom ? 'dsm-row--custom' : '' ?>">
|
||||
<td>
|
||||
<code class="dsm-raw-status"><?= $e($rawStatus) ?></code>
|
||||
<input type="hidden" name="raw_status[]" value="<?= $e($rawStatus) ?>">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="description[]" class="form-control form-control--sm"
|
||||
value="<?= $e($desc) ?>" maxlength="255">
|
||||
</td>
|
||||
<td>
|
||||
<select name="normalized_status[]" class="form-control form-control--sm">
|
||||
<?php foreach ($normalizedOptionsList as $optValue => $optLabel): ?>
|
||||
<option value="<?= $e($optValue) ?>"<?= $optValue === $normalized ? ' selected' : '' ?>>
|
||||
<?= $e($optLabel) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($isCustom): ?>
|
||||
<button type="button" class="btn btn--danger btn--sm js-dsm-reset"
|
||||
data-raw-status="<?= $e($rawStatus) ?>"
|
||||
data-confirm-msg="Przywrócić domyślne mapowanie dla "<?= $e($rawStatus) ?>"?">Resetuj</button>
|
||||
<?php else: ?>
|
||||
<span class="muted">domyślne</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button type="submit" class="btn btn--primary">Zapisz wszystkie</button>
|
||||
<button type="button" class="btn btn--danger" id="js-dsm-reset-all">Resetuj wszystkie</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form id="dsm-reset-all-form" action="/settings/delivery-status-mappings/reset-all" method="post" style="display:none;">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="provider" value="<?= $e($currentProvider) ?>">
|
||||
</form>
|
||||
|
||||
<form id="dsm-reset-form" action="/settings/delivery-status-mappings/reset" method="post" style="display:none;">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="provider" value="<?= $e($currentProvider) ?>">
|
||||
<input type="hidden" name="raw_status" id="dsm-reset-raw-status" value="">
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var resetForm = document.getElementById('dsm-reset-form');
|
||||
var resetInput = document.getElementById('dsm-reset-raw-status');
|
||||
if (!resetForm || !resetInput) return;
|
||||
|
||||
var resetAllBtn = document.getElementById('js-dsm-reset-all');
|
||||
var resetAllForm = document.getElementById('dsm-reset-all-form');
|
||||
if (resetAllBtn && resetAllForm) {
|
||||
resetAllBtn.addEventListener('click', function() {
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.confirm === 'function') {
|
||||
window.OrderProAlerts.confirm({
|
||||
title: 'Resetuj wszystkie mapowania',
|
||||
message: 'Przywrócić wszystkie mapowania do domyślnych dla tego przewoźnika?',
|
||||
confirmLabel: 'Resetuj wszystkie',
|
||||
cancelLabel: 'Anuluj',
|
||||
danger: true
|
||||
}).then(function(accepted) {
|
||||
if (!accepted) return;
|
||||
resetAllForm.submit();
|
||||
});
|
||||
} else {
|
||||
resetAllForm.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.js-dsm-reset').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var rawStatus = btn.getAttribute('data-raw-status') || '';
|
||||
var message = btn.getAttribute('data-confirm-msg') || 'Przywrócić domyślne?';
|
||||
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.confirm === 'function') {
|
||||
window.OrderProAlerts.confirm({
|
||||
title: 'Resetuj mapowanie',
|
||||
message: message,
|
||||
confirmLabel: 'Resetuj',
|
||||
cancelLabel: 'Anuluj',
|
||||
danger: true
|
||||
}).then(function(accepted) {
|
||||
if (!accepted) return;
|
||||
resetInput.value = rawStatus;
|
||||
resetForm.submit();
|
||||
});
|
||||
} else {
|
||||
resetInput.value = rawStatus;
|
||||
resetForm.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
$mappingBaseUrl = '/settings/delivery-status-mappings';
|
||||
include __DIR__ . '/_delivery-status-mappings-content.php';
|
||||
|
||||
123
resources/views/settings/delivery-statuses.php
Normal file
123
resources/views/settings/delivery-statuses.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
$currentTab = (string) ($tab ?? 'statuses');
|
||||
$statusesList = is_array($statuses ?? null) ? $statuses : [];
|
||||
$errorMessage = (string) ($errorMessage ?? '');
|
||||
$successMessage = (string) ($successMessage ?? '');
|
||||
$csrfToken = (string) ($csrfToken ?? '');
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="section-title">Statusy przesyłek</h2>
|
||||
<?php if ($errorMessage !== ''): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e($errorMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($successMessage !== ''): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e($successMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<nav class="content-tabs-nav" aria-label="Zakładki">
|
||||
<a class="content-tab-btn<?= $currentTab === 'statuses' ? ' is-active' : '' ?>"
|
||||
href="/settings/delivery-statuses?tab=statuses">Statusy</a>
|
||||
<a class="content-tab-btn<?= $currentTab === 'mapping' ? ' is-active' : '' ?>"
|
||||
href="/settings/delivery-statuses?tab=mapping">Mapowanie dostawy</a>
|
||||
</nav>
|
||||
|
||||
<?php if ($currentTab === 'statuses'): ?>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<a href="/settings/delivery-statuses/new" class="btn btn--primary btn--sm">+ Dodaj status</a>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper mt-12">
|
||||
<table class="table table--compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Kolor</th>
|
||||
<th>Klucz</th>
|
||||
<th>Etykieta</th>
|
||||
<th>Kolejność</th>
|
||||
<th>Końcowy</th>
|
||||
<th>Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($statusesList as $row): ?>
|
||||
<?php
|
||||
$rowId = (int) ($row['id'] ?? 0);
|
||||
$rowKey = (string) ($row['key'] ?? '');
|
||||
$rowLabel = (string) ($row['label_pl'] ?? '');
|
||||
$rowColor = (string) ($row['color'] ?? '#6c757d');
|
||||
$rowSortOrder = (int) ($row['sort_order'] ?? 0);
|
||||
$rowIsTerminal = !empty($row['is_terminal']);
|
||||
$rowIsSystem = !empty($row['is_system']);
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="delivery-status-swatch" style="--status-color: <?= $e($rowColor) ?>"></span>
|
||||
</td>
|
||||
<td><code><?= $e($rowKey) ?></code></td>
|
||||
<td><?= $e($rowLabel) ?></td>
|
||||
<td><?= $rowSortOrder ?></td>
|
||||
<td><?= $rowIsTerminal ? '✓' : '' ?></td>
|
||||
<td>
|
||||
<?php if (!$rowIsSystem): ?>
|
||||
<a href="/settings/delivery-statuses/<?= $rowId ?>/edit"
|
||||
class="btn btn--sm btn--secondary">Edytuj</a>
|
||||
<button type="button"
|
||||
class="btn btn--sm btn--danger js-ds-delete"
|
||||
data-id="<?= $rowId ?>"
|
||||
data-label="<?= $e($rowLabel) ?>">Usuń</button>
|
||||
<form id="ds-delete-<?= $rowId ?>"
|
||||
action="/settings/delivery-statuses/<?= $rowId ?>/delete"
|
||||
method="post" style="display:none;">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken) ?>">
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<span class="muted">—</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<?php
|
||||
$mappingBaseUrl = '/settings/delivery-statuses?tab=mapping';
|
||||
include __DIR__ . '/_delivery-status-mappings-content.php';
|
||||
?>
|
||||
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// Delete with confirm
|
||||
document.querySelectorAll('.js-ds-delete').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var id = btn.getAttribute('data-id');
|
||||
var label = btn.getAttribute('data-label') || '';
|
||||
var form = document.getElementById('ds-delete-' + id);
|
||||
if (!form) return;
|
||||
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.confirm === 'function') {
|
||||
window.OrderProAlerts.confirm({
|
||||
title: 'Usuń status',
|
||||
message: 'Usunąć status "' + label + '"? Operacja jest nieodwracalna.',
|
||||
confirmLabel: 'Usuń',
|
||||
cancelLabel: 'Anuluj',
|
||||
danger: true
|
||||
}).then(function(accepted) {
|
||||
if (accepted) form.submit();
|
||||
});
|
||||
} else {
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -405,7 +405,11 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
$pkgDeliveryDesc = $pkgDeliveryRaw !== '' ? \App\Modules\Shipments\DeliveryStatus::description($pkgProvider, $pkgDeliveryRaw) : '';
|
||||
$pkgDeliveryTitle = $pkgDeliveryRaw !== '' ? ($pkgDeliveryRaw . ' — ' . $pkgDeliveryDesc) : '';
|
||||
?>
|
||||
<span class="delivery-badge delivery-badge--<?= $e($pkgDeliveryStatus) ?>" title="<?= $e($pkgDeliveryTitle) ?>"><?= $e($pkgDeliveryLabel) ?></span>
|
||||
<?php
|
||||
$pkgIsSystem = in_array($pkgDeliveryStatus, \App\Modules\Shipments\DeliveryStatus::ALL_STATUSES, true);
|
||||
$pkgColor = $pkgIsSystem ? '' : \App\Modules\Shipments\DeliveryStatus::getColor($pkgDeliveryStatus);
|
||||
?>
|
||||
<span class="delivery-badge<?= $pkgIsSystem ? ' delivery-badge--' . $e($pkgDeliveryStatus) : ' delivery-badge--custom' ?>" title="<?= $e($pkgDeliveryTitle) ?>"<?= $pkgColor !== '' ? ' style="--status-color: ' . $e($pkgColor) . '"' : '' ?>><?= $e($pkgDeliveryLabel) ?></span>
|
||||
</td>
|
||||
<td style="white-space:nowrap">
|
||||
<?= $e($pkgTracking !== '' ? $pkgTracking : '-') ?><?php
|
||||
|
||||
@@ -60,6 +60,7 @@ use App\Modules\Automation\AutomationExecutionLogRepository;
|
||||
use App\Modules\Automation\AutomationEmailOnceRepository;
|
||||
use App\Modules\Settings\CronSettingsController;
|
||||
use App\Modules\Settings\DeliveryStatusMappingController;
|
||||
use App\Modules\Settings\DeliveryStatusesController;
|
||||
use App\Modules\Settings\SettingsController;
|
||||
use App\Modules\Shipments\ApaczkaShipmentService;
|
||||
use App\Modules\Shipments\AllegroShipmentService;
|
||||
@@ -68,6 +69,7 @@ use App\Modules\Shipments\ShipmentController;
|
||||
use App\Modules\Shipments\ShipmentPackageRepository;
|
||||
use App\Modules\Shipments\ShipmentPresetController;
|
||||
use App\Modules\Shipments\DeliveryStatusMappingRepository;
|
||||
use App\Modules\Shipments\DeliveryStatusRepository;
|
||||
use App\Modules\Shipments\ShipmentPresetRepository;
|
||||
use App\Modules\Shipments\ShipmentProviderRegistry;
|
||||
use App\Modules\Printing\ApiKeyMiddleware;
|
||||
@@ -185,11 +187,21 @@ return static function (Application $app): void {
|
||||
(int) $app->config('app.cron.web_limit_default', 5)
|
||||
);
|
||||
$deliveryStatusMappingRepository = new DeliveryStatusMappingRepository($app->db());
|
||||
$deliveryStatusRepository = new DeliveryStatusRepository($app->db());
|
||||
\App\Modules\Shipments\DeliveryStatus::setRepository($deliveryStatusRepository);
|
||||
$deliveryStatusesController = new DeliveryStatusesController(
|
||||
$template,
|
||||
$translator,
|
||||
$auth,
|
||||
$deliveryStatusRepository,
|
||||
$deliveryStatusMappingRepository
|
||||
);
|
||||
$deliveryStatusMappingController = new DeliveryStatusMappingController(
|
||||
$template,
|
||||
$translator,
|
||||
$auth,
|
||||
$deliveryStatusMappingRepository
|
||||
$deliveryStatusMappingRepository,
|
||||
$deliveryStatusRepository
|
||||
);
|
||||
$companySettingsRepository = new CompanySettingsRepository($app->db());
|
||||
$companySettingsController = new CompanySettingsController(
|
||||
@@ -513,6 +525,12 @@ return static function (Application $app): void {
|
||||
$router->post('/settings/delivery-status-mappings/save-bulk', [$deliveryStatusMappingController, 'saveBulk'], [$authMiddleware]);
|
||||
$router->post('/settings/delivery-status-mappings/reset', [$deliveryStatusMappingController, 'reset'], [$authMiddleware]);
|
||||
$router->post('/settings/delivery-status-mappings/reset-all', [$deliveryStatusMappingController, 'resetAll'], [$authMiddleware]);
|
||||
$router->get('/settings/delivery-statuses', [$deliveryStatusesController, 'index'], [$authMiddleware]);
|
||||
$router->get('/settings/delivery-statuses/new', [$deliveryStatusesController, 'create'], [$authMiddleware]);
|
||||
$router->get('/settings/delivery-statuses/{id}/edit', [$deliveryStatusesController, 'edit'], [$authMiddleware]);
|
||||
$router->post('/settings/delivery-statuses', [$deliveryStatusesController, 'store'], [$authMiddleware]);
|
||||
$router->post('/settings/delivery-statuses/{id}/update', [$deliveryStatusesController, 'update'], [$authMiddleware]);
|
||||
$router->post('/settings/delivery-statuses/{id}/delete', [$deliveryStatusesController, 'destroy'], [$authMiddleware]);
|
||||
$router->get('/accounting', [$accountingController, 'index'], [$authMiddleware]);
|
||||
$router->post('/accounting/export', [$accountingController, 'export'], [$authMiddleware]);
|
||||
$router->get('/orders/{id}/receipt/create', [$receiptController, 'create'], [$authMiddleware]);
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Core\Support\Flash;
|
||||
use App\Core\View\Template;
|
||||
use App\Modules\Auth\AuthService;
|
||||
use App\Modules\Settings\ReceiptConfigRepository;
|
||||
use App\Modules\Shipments\DeliveryStatus;
|
||||
use Throwable;
|
||||
|
||||
final class AutomationController
|
||||
@@ -33,17 +34,6 @@ final class AutomationController
|
||||
'online' => 'Karta / platnosc online',
|
||||
'other' => 'Inna',
|
||||
];
|
||||
private const SHIPMENT_STATUS_OPTIONS = [
|
||||
'registered' => ['label' => 'Przesylka zarejestrowana', 'statuses' => ['created', 'confirmed']],
|
||||
'courier_pickup' => ['label' => 'Odebrana przez kuriera / nadana w paczkomacie', 'statuses' => ['picked_up']],
|
||||
'ready_for_pickup' => ['label' => 'Przesylka do odbioru', 'statuses' => ['ready_for_pickup']],
|
||||
'dropped_at_point' => ['label' => 'Przesylka nadana w punkcie', 'statuses' => ['confirmed', 'in_transit']],
|
||||
'picked_up' => ['label' => 'Przesylka odebrana', 'statuses' => ['delivered']],
|
||||
'cancelled' => ['label' => 'Przesylka anulowana', 'statuses' => ['cancelled']],
|
||||
'unclaimed' => ['label' => 'Przesylka nieodebrana', 'statuses' => ['problem']],
|
||||
'picked_up_return' => ['label' => 'Przesylka odebrana (zwrot)', 'statuses' => ['returned']],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly Template $template,
|
||||
private readonly Translator $translator,
|
||||
@@ -256,7 +246,7 @@ final class AutomationController
|
||||
'receiptConfigs' => $this->listActiveReceiptConfigs(),
|
||||
'receiptIssueDateModes' => self::ALLOWED_RECEIPT_ISSUE_DATE_MODES,
|
||||
'receiptDuplicatePolicies' => self::ALLOWED_RECEIPT_DUPLICATE_POLICIES,
|
||||
'shipmentStatusOptions' => self::SHIPMENT_STATUS_OPTIONS,
|
||||
'shipmentStatusOptions' => $this->buildShipmentStatusOptions(),
|
||||
'paymentStatusOptions' => self::PAYMENT_STATUS_OPTIONS,
|
||||
'paymentMethodOptions' => self::PAYMENT_METHOD_OPTIONS,
|
||||
'orderStatusOptions' => $this->repository->listActiveOrderStatuses(),
|
||||
@@ -427,7 +417,7 @@ final class AutomationController
|
||||
$keys = [];
|
||||
}
|
||||
|
||||
$allowedKeys = array_keys(self::SHIPMENT_STATUS_OPTIONS);
|
||||
$allowedKeys = DeliveryStatus::getAllStatuses();
|
||||
$statusKeys = array_values(array_filter(
|
||||
array_map(static fn (mixed $key): string => trim((string) $key), $keys),
|
||||
static fn (string $key): bool => $key !== '' && in_array($key, $allowedKeys, true)
|
||||
@@ -570,7 +560,7 @@ final class AutomationController
|
||||
|
||||
if ($type === 'update_shipment_status') {
|
||||
$statusKey = trim((string) ($action['shipment_status_key'] ?? ''));
|
||||
if (!array_key_exists($statusKey, self::SHIPMENT_STATUS_OPTIONS)) {
|
||||
if ($statusKey === '' || !in_array($statusKey, DeliveryStatus::getAllStatuses(), true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -598,6 +588,19 @@ final class AutomationController
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{label:string}>
|
||||
*/
|
||||
private function buildShipmentStatusOptions(): array
|
||||
{
|
||||
$options = [];
|
||||
foreach (DeliveryStatus::getAllOptions() as $key => $label) {
|
||||
$options[(string) $key] = ['label' => (string) $label];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{id:int,name:string,number_format:string}>
|
||||
*/
|
||||
|
||||
@@ -21,17 +21,6 @@ final class AutomationService
|
||||
private const MAX_CHAIN_DEPTH = 8;
|
||||
private const MAX_CHAIN_EXECUTIONS = 200;
|
||||
|
||||
private const SHIPMENT_STATUS_OPTION_MAP = [
|
||||
'registered' => ['created', 'confirmed'],
|
||||
'courier_pickup' => ['picked_up'],
|
||||
'ready_for_pickup' => ['ready_for_pickup'],
|
||||
'dropped_at_point' => ['confirmed', 'in_transit'],
|
||||
'picked_up' => ['delivered'],
|
||||
'cancelled' => ['cancelled'],
|
||||
'unclaimed' => ['problem'],
|
||||
'picked_up_return' => ['returned'],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly AutomationRepository $repository,
|
||||
private readonly AutomationExecutionLogRepository $executionLogs,
|
||||
@@ -189,12 +178,10 @@ final class AutomationService
|
||||
$allowedStatuses = [];
|
||||
foreach ($statusKeys as $statusKeyRaw) {
|
||||
$statusKey = trim((string) $statusKeyRaw);
|
||||
if ($statusKey === '' || !isset(self::SHIPMENT_STATUS_OPTION_MAP[$statusKey])) {
|
||||
if ($statusKey === '') {
|
||||
continue;
|
||||
}
|
||||
foreach (self::SHIPMENT_STATUS_OPTION_MAP[$statusKey] as $mappedStatus) {
|
||||
$allowedStatuses[$mappedStatus] = true;
|
||||
}
|
||||
$allowedStatuses[$statusKey] = true;
|
||||
}
|
||||
|
||||
if ($allowedStatuses === []) {
|
||||
@@ -609,21 +596,15 @@ final class AutomationService
|
||||
|
||||
private function resolveStatusFromActionKey(string $statusKey): ?string
|
||||
{
|
||||
if ($statusKey === '' || !isset(self::SHIPMENT_STATUS_OPTION_MAP[$statusKey])) {
|
||||
if ($statusKey === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mappedStatuses = self::SHIPMENT_STATUS_OPTION_MAP[$statusKey];
|
||||
if ($mappedStatuses === []) {
|
||||
if (!in_array($statusKey, DeliveryStatus::getAllStatuses(), true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$candidate = trim((string) $mappedStatuses[0]);
|
||||
if ($candidate === '' || !in_array($candidate, DeliveryStatus::ALL_STATUSES, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $candidate;
|
||||
return $statusKey;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,11 +12,12 @@ use App\Core\View\Template;
|
||||
use App\Modules\Auth\AuthService;
|
||||
use App\Modules\Shipments\DeliveryStatus;
|
||||
use App\Modules\Shipments\DeliveryStatusMappingRepository;
|
||||
use App\Modules\Shipments\DeliveryStatusRepository;
|
||||
use Throwable;
|
||||
|
||||
final class DeliveryStatusMappingController
|
||||
{
|
||||
private const REDIRECT_PATH = '/settings/delivery-status-mappings';
|
||||
private const REDIRECT_PATH = '/settings/delivery-statuses?tab=mapping';
|
||||
|
||||
private const PROVIDERS = [
|
||||
'inpost' => 'InPost',
|
||||
@@ -28,7 +29,8 @@ final class DeliveryStatusMappingController
|
||||
private readonly Template $template,
|
||||
private readonly Translator $translator,
|
||||
private readonly AuthService $auth,
|
||||
private readonly DeliveryStatusMappingRepository $repository
|
||||
private readonly DeliveryStatusMappingRepository $repository,
|
||||
private readonly DeliveryStatusRepository $deliveryStatusRepository
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -86,7 +88,7 @@ final class DeliveryStatusMappingController
|
||||
'providers' => self::PROVIDERS,
|
||||
'mappings' => $mappings,
|
||||
'unmappedRawStatuses' => $unmappedRawStatuses,
|
||||
'normalizedOptions' => DeliveryStatus::LABEL_PL,
|
||||
'normalizedOptions' => $this->deliveryStatusRepository->getAllAsOptions(),
|
||||
'errorMessage' => (string) Flash::get('dsm_error', ''),
|
||||
'successMessage' => (string) Flash::get('dsm_success', ''),
|
||||
], 'layouts/app');
|
||||
@@ -108,12 +110,12 @@ final class DeliveryStatusMappingController
|
||||
|
||||
if ($provider === '' || $rawStatus === '') {
|
||||
Flash::set('dsm_error', 'Brakuje wymaganych pól.');
|
||||
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
|
||||
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
|
||||
}
|
||||
|
||||
if (!in_array($normalizedStatus, DeliveryStatus::ALL_STATUSES, true)) {
|
||||
if ($this->deliveryStatusRepository->getByKey($normalizedStatus) === null) {
|
||||
Flash::set('dsm_error', 'Nieprawidłowy status znormalizowany.');
|
||||
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
|
||||
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -123,7 +125,7 @@ final class DeliveryStatusMappingController
|
||||
Flash::set('dsm_error', 'Błąd zapisu: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
|
||||
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
|
||||
}
|
||||
|
||||
public function saveBulk(Request $request): Response
|
||||
@@ -140,7 +142,7 @@ final class DeliveryStatusMappingController
|
||||
|
||||
if (!is_array($rawStatuses) || !is_array($normalizedStatuses) || !is_array($descriptions)) {
|
||||
Flash::set('dsm_error', 'Nieprawidłowe dane formularza.');
|
||||
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
|
||||
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -156,7 +158,7 @@ final class DeliveryStatusMappingController
|
||||
$normalizedStatus = trim((string) ($normalizedStatuses[$index] ?? ''));
|
||||
$description = trim((string) ($descriptions[$index] ?? ''));
|
||||
|
||||
if (!in_array($normalizedStatus, DeliveryStatus::ALL_STATUSES, true)) {
|
||||
if ($this->deliveryStatusRepository->getByKey($normalizedStatus) === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -178,7 +180,7 @@ final class DeliveryStatusMappingController
|
||||
Flash::set('dsm_error', 'Błąd zapisu: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
|
||||
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
|
||||
}
|
||||
|
||||
public function reset(Request $request): Response
|
||||
@@ -193,7 +195,7 @@ final class DeliveryStatusMappingController
|
||||
|
||||
if ($provider === '' || $rawStatus === '') {
|
||||
Flash::set('dsm_error', 'Brakuje wymaganych pól.');
|
||||
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
|
||||
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -203,7 +205,7 @@ final class DeliveryStatusMappingController
|
||||
Flash::set('dsm_error', 'Błąd resetu: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
|
||||
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
|
||||
}
|
||||
|
||||
public function resetAll(Request $request): Response
|
||||
@@ -226,7 +228,7 @@ final class DeliveryStatusMappingController
|
||||
Flash::set('dsm_error', 'Błąd resetu: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
|
||||
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
|
||||
}
|
||||
|
||||
private function validateCsrf(string $token): ?Response
|
||||
|
||||
286
src/Modules/Settings/DeliveryStatusesController.php
Normal file
286
src/Modules/Settings/DeliveryStatusesController.php
Normal file
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Settings;
|
||||
|
||||
use App\Core\Http\Request;
|
||||
use App\Core\Http\Response;
|
||||
use App\Core\I18n\Translator;
|
||||
use App\Core\Security\Csrf;
|
||||
use App\Core\Support\Flash;
|
||||
use App\Core\View\Template;
|
||||
use App\Modules\Auth\AuthService;
|
||||
use App\Modules\Shipments\DeliveryStatus;
|
||||
use App\Modules\Shipments\DeliveryStatusMappingRepository;
|
||||
use App\Modules\Shipments\DeliveryStatusRepository;
|
||||
use Throwable;
|
||||
|
||||
final class DeliveryStatusesController
|
||||
{
|
||||
private const REDIRECT_STATUSES = '/settings/delivery-statuses?tab=statuses';
|
||||
|
||||
private const PROVIDERS = [
|
||||
'inpost' => 'InPost',
|
||||
'apaczka' => 'Apaczka',
|
||||
'allegro_wza' => 'Allegro',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly Template $template,
|
||||
private readonly Translator $translator,
|
||||
private readonly AuthService $auth,
|
||||
private readonly DeliveryStatusRepository $deliveryStatusRepository,
|
||||
private readonly DeliveryStatusMappingRepository $deliveryStatusMappingRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$tab = (string) ($request->input('tab', 'statuses') ?? 'statuses');
|
||||
if (!in_array($tab, ['statuses', 'mapping'], true)) {
|
||||
$tab = 'statuses';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'title' => 'Statusy przesyłek',
|
||||
'activeMenu' => 'settings',
|
||||
'activeSettings' => 'delivery-statuses',
|
||||
'user' => $this->auth->user(),
|
||||
'csrfToken' => Csrf::token(),
|
||||
'tab' => $tab,
|
||||
'statuses' => $this->deliveryStatusRepository->getAll(),
|
||||
'errorMessage' => (string) Flash::get('ds_error', ''),
|
||||
'successMessage' => (string) Flash::get('ds_success', ''),
|
||||
];
|
||||
|
||||
if ($tab === 'mapping') {
|
||||
$data = array_merge($data, $this->buildMappingData($request));
|
||||
}
|
||||
|
||||
$html = $this->template->render('settings/delivery-statuses', $data, 'layouts/app');
|
||||
|
||||
return Response::html($html);
|
||||
}
|
||||
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return $this->renderForm(null);
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$id = max(0, (int) $request->input('id', 0));
|
||||
$row = null;
|
||||
foreach ($this->deliveryStatusRepository->getAll() as $candidate) {
|
||||
if ((int) ($candidate['id'] ?? 0) === $id) {
|
||||
$row = $candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($row === null) {
|
||||
Flash::set('ds_error', 'Status nie istnieje.');
|
||||
return Response::redirect(self::REDIRECT_STATUSES);
|
||||
}
|
||||
|
||||
if ((int) ($row['is_system'] ?? 0) === 1) {
|
||||
Flash::set('ds_error', 'Statusów systemowych nie można edytować.');
|
||||
return Response::redirect(self::REDIRECT_STATUSES);
|
||||
}
|
||||
|
||||
return $this->renderForm($row);
|
||||
}
|
||||
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||
if ($csrfError !== null) {
|
||||
return $csrfError;
|
||||
}
|
||||
|
||||
$key = trim((string) $request->input('key', ''));
|
||||
$labelPl = trim((string) $request->input('label_pl', ''));
|
||||
$color = trim((string) $request->input('color', '#6c757d'));
|
||||
$sortOrder = (int) $request->input('sort_order', 0);
|
||||
$isTerminal = $request->input('is_terminal', '') !== '' ? 1 : 0;
|
||||
|
||||
$validationError = $this->validateFields($key, $labelPl, $color, true);
|
||||
if ($validationError !== null) {
|
||||
Flash::set('ds_error', $validationError);
|
||||
return Response::redirect(self::REDIRECT_STATUSES);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->deliveryStatusRepository->create([
|
||||
'key' => $key,
|
||||
'label_pl' => $labelPl,
|
||||
'color' => $color,
|
||||
'sort_order' => $sortOrder,
|
||||
'is_terminal' => $isTerminal,
|
||||
]);
|
||||
Flash::set('ds_success', 'Status dodany.');
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('ds_error', 'Błąd: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect(self::REDIRECT_STATUSES);
|
||||
}
|
||||
|
||||
public function update(Request $request): Response
|
||||
{
|
||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||
if ($csrfError !== null) {
|
||||
return $csrfError;
|
||||
}
|
||||
|
||||
$id = max(0, (int) $request->input('id', 0));
|
||||
$labelPl = trim((string) $request->input('label_pl', ''));
|
||||
$color = trim((string) $request->input('color', '#6c757d'));
|
||||
$sortOrder = (int) $request->input('sort_order', 0);
|
||||
$isTerminal = $request->input('is_terminal', '') !== '' ? 1 : 0;
|
||||
|
||||
$validationError = $this->validateFields('valid_key', $labelPl, $color, false);
|
||||
if ($validationError !== null) {
|
||||
Flash::set('ds_error', $validationError);
|
||||
return Response::redirect(self::REDIRECT_STATUSES);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->deliveryStatusRepository->update($id, [
|
||||
'label_pl' => $labelPl,
|
||||
'color' => $color,
|
||||
'sort_order' => $sortOrder,
|
||||
'is_terminal' => $isTerminal,
|
||||
]);
|
||||
Flash::set('ds_success', 'Status zaktualizowany.');
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('ds_error', 'Błąd: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect(self::REDIRECT_STATUSES);
|
||||
}
|
||||
|
||||
public function destroy(Request $request): Response
|
||||
{
|
||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||
if ($csrfError !== null) {
|
||||
return $csrfError;
|
||||
}
|
||||
|
||||
$id = max(0, (int) $request->input('id', 0));
|
||||
|
||||
try {
|
||||
$this->deliveryStatusRepository->delete($id);
|
||||
Flash::set('ds_success', 'Status usunięty.');
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('ds_error', 'Błąd: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect(self::REDIRECT_STATUSES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $row
|
||||
*/
|
||||
private function renderForm(?array $row): Response
|
||||
{
|
||||
$isEdit = $row !== null && isset($row['id']);
|
||||
|
||||
$html = $this->template->render('settings/delivery-status-form', [
|
||||
'title' => $isEdit ? 'Edytuj status przesyłki' : 'Nowy status przesyłki',
|
||||
'activeMenu' => 'settings',
|
||||
'activeSettings' => 'delivery-statuses',
|
||||
'user' => $this->auth->user(),
|
||||
'csrfToken' => Csrf::token(),
|
||||
'row' => $row,
|
||||
'isEdit' => $isEdit,
|
||||
'errorMessage' => (string) Flash::get('ds_error', ''),
|
||||
], 'layouts/app');
|
||||
|
||||
return Response::html($html);
|
||||
}
|
||||
|
||||
private function validateCsrf(string $token): ?Response
|
||||
{
|
||||
if (Csrf::validate($token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Flash::set('ds_error', $this->translator->get('auth.errors.csrf_expired'));
|
||||
return Response::redirect(self::REDIRECT_STATUSES);
|
||||
}
|
||||
|
||||
private function validateFields(string $key, string $labelPl, string $color, bool $validateKey): ?string
|
||||
{
|
||||
if ($validateKey) {
|
||||
if ($key === '' || !preg_match('/^[a-z][a-z0-9_]{0,49}$/', $key)) {
|
||||
return 'Klucz statusu jest nieprawidłowy (tylko małe litery, cyfry, _, max 50 znaków, zaczyna się literą).';
|
||||
}
|
||||
}
|
||||
|
||||
if ($labelPl === '' || mb_strlen($labelPl) > 100) {
|
||||
return 'Etykieta jest wymagana i nie może przekraczać 100 znaków.';
|
||||
}
|
||||
|
||||
if (!preg_match('/^#[0-9a-fA-F]{6}$/', $color)) {
|
||||
return 'Kolor musi być w formacie #RRGGBB.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildMappingData(Request $request): array
|
||||
{
|
||||
$provider = strtolower(trim((string) $request->input('provider', 'inpost')));
|
||||
if (!isset(self::PROVIDERS[$provider])) {
|
||||
$provider = 'inpost';
|
||||
}
|
||||
|
||||
$defaults = DeliveryStatus::getDefaultMappings($provider);
|
||||
$overrides = $this->deliveryStatusMappingRepository->listByProvider($provider);
|
||||
|
||||
$overrideMap = [];
|
||||
foreach ($overrides as $row) {
|
||||
$overrideMap[$row['raw_status']] = $row;
|
||||
}
|
||||
|
||||
$mappings = [];
|
||||
$knownRawStatuses = [];
|
||||
foreach ($defaults as $rawStatus => $default) {
|
||||
$isCustom = isset($overrideMap[$rawStatus]);
|
||||
$mappings[] = [
|
||||
'raw_status' => $rawStatus,
|
||||
'description' => $isCustom ? $overrideMap[$rawStatus]['description'] : $default['description'],
|
||||
'normalized_status' => $isCustom ? $overrideMap[$rawStatus]['normalized_status'] : $default['normalized'],
|
||||
'is_custom' => $isCustom,
|
||||
];
|
||||
$knownRawStatuses[$rawStatus] = true;
|
||||
}
|
||||
|
||||
foreach ($overrideMap as $rawStatus => $row) {
|
||||
if (isset($knownRawStatuses[$rawStatus])) {
|
||||
continue;
|
||||
}
|
||||
$mappings[] = [
|
||||
'raw_status' => $rawStatus,
|
||||
'description' => $row['description'],
|
||||
'normalized_status' => $row['normalized_status'],
|
||||
'is_custom' => true,
|
||||
];
|
||||
$knownRawStatuses[$rawStatus] = true;
|
||||
}
|
||||
|
||||
$unmappedRawStatuses = $this->deliveryStatusMappingRepository->listUnmappedRawStatuses($provider, $knownRawStatuses);
|
||||
|
||||
return [
|
||||
'provider' => $provider,
|
||||
'providers' => self::PROVIDERS,
|
||||
'mappings' => $mappings,
|
||||
'unmappedRawStatuses' => $unmappedRawStatuses,
|
||||
'normalizedOptions' => $this->deliveryStatusRepository->getAllAsOptions(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ namespace App\Modules\Shipments;
|
||||
|
||||
final class DeliveryStatus
|
||||
{
|
||||
private static ?DeliveryStatusRepository $repository = null;
|
||||
|
||||
public const UNKNOWN = 'unknown';
|
||||
public const CREATED = 'created';
|
||||
public const CONFIRMED = 'confirmed';
|
||||
@@ -379,8 +381,46 @@ final class DeliveryStatus
|
||||
return $map[$rawStatus] ?? $rawStatus;
|
||||
}
|
||||
|
||||
public static function setRepository(DeliveryStatusRepository $repo): void
|
||||
{
|
||||
self::$repository = $repo;
|
||||
}
|
||||
|
||||
public static function getAllStatuses(): array
|
||||
{
|
||||
if (self::$repository !== null) {
|
||||
return array_column(self::$repository->getAll(), 'key');
|
||||
}
|
||||
return self::ALL_STATUSES;
|
||||
}
|
||||
|
||||
public static function getAllOptions(): array
|
||||
{
|
||||
if (self::$repository !== null) {
|
||||
return self::$repository->getAllAsOptions();
|
||||
}
|
||||
return self::LABEL_PL;
|
||||
}
|
||||
|
||||
public static function getColor(string $key): string
|
||||
{
|
||||
if (self::$repository !== null) {
|
||||
$row = self::$repository->getByKey($key);
|
||||
if ($row !== null) {
|
||||
return (string) $row['color'];
|
||||
}
|
||||
}
|
||||
return '#6c757d';
|
||||
}
|
||||
|
||||
public static function label(string $status): string
|
||||
{
|
||||
if (self::$repository !== null) {
|
||||
$row = self::$repository->getByKey($status);
|
||||
if ($row !== null) {
|
||||
return (string) $row['label_pl'];
|
||||
}
|
||||
}
|
||||
return self::LABEL_PL[$status] ?? 'Nieznany';
|
||||
}
|
||||
|
||||
|
||||
157
src/Modules/Shipments/DeliveryStatusRepository.php
Normal file
157
src/Modules/Shipments/DeliveryStatusRepository.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Shipments;
|
||||
|
||||
use PDO;
|
||||
|
||||
final class DeliveryStatusRepository
|
||||
{
|
||||
private static ?array $cache = null;
|
||||
|
||||
public function __construct(private readonly PDO $db)
|
||||
{
|
||||
}
|
||||
|
||||
public function getAll(): array
|
||||
{
|
||||
if (self::$cache !== null) {
|
||||
return self::$cache;
|
||||
}
|
||||
|
||||
$statement = $this->db->prepare(
|
||||
'SELECT * FROM delivery_statuses ORDER BY sort_order ASC'
|
||||
);
|
||||
$statement->execute();
|
||||
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
self::$cache = is_array($rows) ? $rows : [];
|
||||
|
||||
return self::$cache;
|
||||
}
|
||||
|
||||
public function getByKey(string $key): ?array
|
||||
{
|
||||
foreach ($this->getAll() as $row) {
|
||||
if ($row['key'] === $key) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getAllAsOptions(): array
|
||||
{
|
||||
$options = [];
|
||||
foreach ($this->getAll() as $row) {
|
||||
$options[$row['key']] = $row['label_pl'];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function create(array $data): int
|
||||
{
|
||||
if ($this->getByKey($data['key']) !== null) {
|
||||
throw new \RuntimeException('Status o tym kluczu już istnieje.');
|
||||
}
|
||||
|
||||
$statement = $this->db->prepare(
|
||||
'INSERT INTO delivery_statuses (`key`, label_pl, color, sort_order, is_terminal)
|
||||
VALUES (:key, :label_pl, :color, :sort_order, :is_terminal)'
|
||||
);
|
||||
$statement->bindValue(':key', $data['key']);
|
||||
$statement->bindValue(':label_pl', $data['label_pl']);
|
||||
$statement->bindValue(':color', $data['color']);
|
||||
$statement->bindValue(':sort_order', $data['sort_order'], PDO::PARAM_INT);
|
||||
$statement->bindValue(':is_terminal', $data['is_terminal'], PDO::PARAM_INT);
|
||||
$statement->execute();
|
||||
|
||||
$this->clearCache();
|
||||
|
||||
return (int) $this->db->lastInsertId();
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): void
|
||||
{
|
||||
$row = $this->findById($id);
|
||||
if ($row === null) {
|
||||
throw new \RuntimeException('Status nie istnieje.');
|
||||
}
|
||||
|
||||
if ((int) $row['is_system'] === 1) {
|
||||
throw new \RuntimeException('Statusów systemowych nie można edytować.');
|
||||
}
|
||||
|
||||
$statement = $this->db->prepare(
|
||||
'UPDATE delivery_statuses
|
||||
SET label_pl = :label_pl, color = :color, sort_order = :sort_order, is_terminal = :is_terminal
|
||||
WHERE id = :id'
|
||||
);
|
||||
$statement->bindValue(':label_pl', $data['label_pl']);
|
||||
$statement->bindValue(':color', $data['color']);
|
||||
$statement->bindValue(':sort_order', $data['sort_order'], PDO::PARAM_INT);
|
||||
$statement->bindValue(':is_terminal', $data['is_terminal'], PDO::PARAM_INT);
|
||||
$statement->bindValue(':id', $id, PDO::PARAM_INT);
|
||||
$statement->execute();
|
||||
|
||||
$this->clearCache();
|
||||
}
|
||||
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$row = $this->findById($id);
|
||||
if ($row === null) {
|
||||
throw new \RuntimeException('Status nie istnieje.');
|
||||
}
|
||||
|
||||
if ((int) $row['is_system'] === 1) {
|
||||
throw new \RuntimeException('Statusów systemowych nie można usunąć.');
|
||||
}
|
||||
|
||||
$key = (string) $row['key'];
|
||||
|
||||
$stmtMappings = $this->db->prepare(
|
||||
'SELECT COUNT(*) FROM delivery_status_mappings WHERE normalized_status = :key'
|
||||
);
|
||||
$stmtMappings->bindValue(':key', $key);
|
||||
$stmtMappings->execute();
|
||||
$countMappings = (int) $stmtMappings->fetchColumn();
|
||||
|
||||
$stmtPackages = $this->db->prepare(
|
||||
'SELECT COUNT(*) FROM shipment_packages WHERE delivery_status = :key'
|
||||
);
|
||||
$stmtPackages->bindValue(':key', $key);
|
||||
$stmtPackages->execute();
|
||||
$countPackages = (int) $stmtPackages->fetchColumn();
|
||||
|
||||
if ($countMappings > 0 || $countPackages > 0) {
|
||||
throw new \RuntimeException('Status jest używany i nie może być usunięty.');
|
||||
}
|
||||
|
||||
$statement = $this->db->prepare(
|
||||
'DELETE FROM delivery_statuses WHERE id = :id'
|
||||
);
|
||||
$statement->bindValue(':id', $id, PDO::PARAM_INT);
|
||||
$statement->execute();
|
||||
|
||||
$this->clearCache();
|
||||
}
|
||||
|
||||
public function clearCache(): void
|
||||
{
|
||||
self::$cache = null;
|
||||
}
|
||||
|
||||
private function findById(int $id): ?array
|
||||
{
|
||||
foreach ($this->getAll() as $row) {
|
||||
if ((int) $row['id'] === $id) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user