From 98a00772041e54e818ca6821dbf1f9c54a7eadbe Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Mon, 23 Mar 2026 23:04:05 +0100 Subject: [PATCH] feat(28-shipment-tracking-ui): badge'e statusow dostawy, linki sledzenia, ustawienia interwalu trackingu - Kolorowe badge'e statusow dostawy w tabelach paczek (show.php + prepare.php) - Link sledzenia z carrier detection (InPost, Apaczka, Orlen, Allegro, Google fallback) - Sekcja Status dostawy w boksie Platnosc i wysylka - Ustawienie interwalu trackingu crona (5-120 min) w zakladce Ustawienia - Tekstowe mapowania statusow Apaczka API (NEW, CONFIRMED, etc.) - Fix: use-statements ApaczkaShipmentService (pre-existing bug) - Fix: pickup date normalization (next day po 16:00) - Fix: przycisk Pobierz etykiete (POST zamiast link do prepare) Co-Authored-By: Claude Opus 4.6 (1M context) --- .paul/ROADMAP.md | 23 +- .paul/STATE.md | 36 +- .paul/handoffs/archive/HANDOFF-2026-03-23.md | 125 +++++++ .../28-shipment-tracking-ui/28-01-PLAN.md | 294 ++++++++++++++++ .../28-shipment-tracking-ui/28-01-SUMMARY.md | 185 ++++++++++ ...3_000060_add_delivery_tracking_columns.sql | 5 +- public/assets/css/app.css | 55 +++ resources/scss/app.scss | 1 + resources/scss/modules/_delivery-status.scss | 25 ++ resources/views/orders/show.php | 43 ++- resources/views/settings/cron.php | 321 ++++++++++-------- resources/views/shipments/prepare.php | 18 +- src/Modules/Cron/CronRepository.php | 25 ++ .../Settings/CronSettingsController.php | 8 + .../Shipments/ApaczkaShipmentService.php | 19 +- src/Modules/Shipments/DeliveryStatus.php | 81 +++++ .../Shipments/ShipmentPackageRepository.php | 18 + 17 files changed, 1108 insertions(+), 174 deletions(-) create mode 100644 .paul/handoffs/archive/HANDOFF-2026-03-23.md create mode 100644 .paul/phases/28-shipment-tracking-ui/28-01-PLAN.md create mode 100644 .paul/phases/28-shipment-tracking-ui/28-01-SUMMARY.md create mode 100644 resources/scss/modules/_delivery-status.scss diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 69b3faf..6076ccb 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -6,17 +6,22 @@ orderPRO to narzędzie do wielokanałowego zarządzania sprzedażą. Projekt prz ## Current Milestone -### v1.2 Śledzenie przesyłek — In progress - -Automatyczne śledzenie statusu dostawy przesyłek przez API przewoźników (InPost ShipX, Apaczka, Allegro WZA). Cykliczne odpytywanie przez cron z konfigurowalnym interwałem. Dwupoziomowy system statusów: znormalizowany + surowy z API. - -| Phase | Name | Plans | Status | -|-------|------|-------|--------| -| 27 | Shipment Tracking Backend | 1/1 | Complete ✓ | -| 28 | Shipment Tracking UI + Settings | 0/1 | Not started | - ## Completed Milestones +
+v1.2 Śledzenie przesyłek — 2026-03-23 (2 phases, 2 plans) + +Automatyczne śledzenie statusu dostawy przesyłek przez API przewoźników (InPost ShipX, Apaczka, Allegro WZA). Cykliczne odpytywanie przez cron z konfigurowalnym interwałem. Dwupoziomowy system statusów: znormalizowany + surowy z API. Badge'e w UI, linki śledzenia, ustawienia interwału. + +| Phase | Name | Plans | Completed | +|-------|------|-------|-----------| +| 27 | Shipment Tracking Backend | 1/1 | 2026-03-23 | +| 28 | Shipment Tracking UI + Settings | 1/1 | 2026-03-23 | + +Archive: `.paul/phases/27-shipment-tracking-backend/`, `.paul/phases/28-shipment-tracking-ui/` + +
+
v1.1 Ręczny numer przesyłki — 2026-03-23 (1 phase, 1 plan) diff --git a/.paul/STATE.md b/.paul/STATE.md index 9899a4a..6bd7f43 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -5,15 +5,15 @@ See: .paul/PROJECT.md (updated 2026-03-12) **Core value:** Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów sprzedaży i nadawać przesyłki bez przełączania się między platformami. -**Current focus:** v1.2 Śledzenie przesyłek — Phase 27 COMPLETE, Phase 28 next +**Current focus:** v1.2 Śledzenie przesyłek — COMPLETE ✓ ## Current Position -Milestone: v1.2 Śledzenie przesyłek -Phase: [1] of [2] (Shipment Tracking Backend) — COMPLETE ✓ -Plan: 27-01 — loop closed -Status: Phase 27 complete, ready for Phase 28 PLAN -Last activity: 2026-03-23 — UNIFY 27-01 complete +Milestone: v1.2 Śledzenie przesyłek — COMPLETE ✓ +Phase: [2] of [2] (Shipment Tracking UI + Settings) — COMPLETE ✓ +Plan: 28-01 — COMPLETE ✓ +Status: Milestone complete, ready for next milestone +Last activity: 2026-03-23 — v1.2 milestone completed Progress: - v0.1 Initial Release: [██████████] 100% ✓ @@ -27,16 +27,16 @@ Progress: - v0.9 Poprawki ustawień firmy: [██████████] 100% ✓ - v1.0 Presety przesyłek: [██████████] 100% ✓ - v1.1 Ręczny numer przesyłki: [██████████] 100% ✓ -- v1.2 Śledzenie przesyłek: [█████░░░░░] 50% +- v1.2 Śledzenie przesyłek: [██████████] 100% ✓ - Phase 27: [██████████] 100% ✓ (1/1 plans) - - Phase 28: [░░░░░░░░░░] 0% (0/1 plans) + - Phase 28: [██████████] 100% ✓ (1/1 plans) ## Loop Position Current loop state: ``` PLAN ──▶ APPLY ──▶ UNIFY - ✓ ✓ ✓ [Phase 27 complete — ready for Phase 28 PLAN] + ✓ ✓ ✓ [Loop complete — milestone v1.2 done] ``` ## Accumulated Context @@ -73,6 +73,11 @@ PLAN ──▶ APPLY ──▶ UNIFY | 2026-03-17 | Email history jako wpisy w order_activity_log (nie osobna sekcja) | Faza 15 | Spójność z istniejącym UX — jeden timeline zamiast fragmentacji | | 2026-03-17 | VariableResolver wydzielony z EmailTemplateController | Faza 15 | Reuse logiki zmiennych; resolwer niezależny od kontrolera szablonów | +### Skill Audit (Faza 28, Plan 01) +| Oczekiwany | Wywołany | Uwagi | +|------------|---------|-------| +| sonar-scanner | ✓ | 0 nowych unikalnych issues; 1x S1448 CronRepository (22 metod, pre-existing pattern) | + ### Skill Audit (Faza 27, Plan 01) | Oczekiwany | Wywołany | Uwagi | |------------|---------|-------| @@ -227,7 +232,7 @@ PLAN ──▶ APPLY ──▶ UNIFY - **Delivery mapping "Szukaj..." layout** — JS `attachSelectFilter()` w allegro.php tworzy input search dla InPost/Apaczka selectów, wizualnie wygląda jakby należał do wiersza powyżej. Pre-existing bug, do naprawy osobno. ### Git State -Last commit: c59d431 — feat(26-manual-tracking-number): reczne dodawanie numeru przesylki do zamowienia +Last commit: pending — feat(28-shipment-tracking-ui) Branch: main Feature branches merged: none @@ -237,12 +242,13 @@ Brak. ## Session Continuity Last session: 2026-03-23 -Stopped at: Phase 27 complete -Next action: /paul:plan (Phase 28 — Shipment Tracking UI + Settings) -Resume file: .paul/phases/27-shipment-tracking-backend/27-01-SUMMARY.md +Stopped at: v1.2 milestone COMPLETE +Next action: /paul:discuss-milestone — ustalenie zakresu v1.3 +Resume file: .paul/phases/28-shipment-tracking-ui/28-01-SUMMARY.md Resume context: -- v0.1–v1.1: COMPLETE ✓ (26 phases, 38 plans) -- v1.2: IN PROGRESS — Phase 27 done, Phase 28 (UI + Settings) next +- v0.1–v1.2: COMPLETE ✓ (28 phases, 40 plans) +- Milestone v1.2 zamknięty — tracking backend + UI + cron settings +- Następny milestone do ustalenia --- *STATE.md — Updated after every significant action* diff --git a/.paul/handoffs/archive/HANDOFF-2026-03-23.md b/.paul/handoffs/archive/HANDOFF-2026-03-23.md new file mode 100644 index 0000000..abdc5c5 --- /dev/null +++ b/.paul/handoffs/archive/HANDOFF-2026-03-23.md @@ -0,0 +1,125 @@ +# PAUL Handoff + +**Date:** 2026-03-23 +**Status:** paused — mid-APPLY Phase 28 + +--- + +## READ THIS FIRST + +You have no prior context. This document tells you everything. + +**Project:** orderPRO — wielokanałowe zarządzanie zamówieniami i przesyłkami +**Core value:** Sprzedawca obsługuje zamówienia ze wszystkich kanałów i nadaje przesyłki bez przełączania się między platformami. + +--- + +## Current State + +**Milestone:** v1.2 Śledzenie przesyłek +**Phase:** [2] of [2] — Shipment Tracking UI + Settings +**Plan:** 28-01 — APPLY in progress (Task 2 checkpoint pending, Task 3 not started) + +**Loop Position:** +``` +PLAN ──▶ APPLY ──▶ UNIFY + ✓ ◐ ○ [APPLY mid-execution, checkpoint pending] +``` + +--- + +## What Was Done This Session + +### Phase 27 — Shipment Tracking Backend (COMPLETE ✓) +- Migracja DB: 3 kolumny (delivery_status, delivery_status_raw, delivery_status_updated_at) + indeks +- DeliveryStatus class z mapowaniami statusów (30+ InPost, 11 Apaczka, 7 Allegro) +- ShipmentTrackingInterface + 3 implementacje (InpostTrackingService, ApaczkaTrackingService, AllegroTrackingService) +- ShipmentTrackingRegistry + ShipmentTrackingHandler cron (15 min interwał) +- CronHandlerFactory rozszerzony o shipment_tracking_sync +- Commit: `228c0e9` + +### Phase 28 — Shipment Tracking UI (IN PROGRESS) +- Task 1 DONE: SCSS badge'e statusów + DeliveryStatus::trackingUrl() z carrier detection +- Task 2 PARTIALLY DONE: Badge'e w show.php i prepare.php, link śledzenia, boks Płatność i wysyłka +- Task 3 NOT STARTED: Ustawienia interwału crona + +### Dodatkowe poprawki poza planem: +- **Fix: Przycisk Pobierz etykietę w show.php** — zmieniony z linku do prepare na formularz POST z bezpośrednim downloadem PDF +- **Fix: delivery_status "delivered" → "confirmed"** — migracja błędnie ustawiała label_ready jako doręczona; naprawiono w DB (3 rows) i w pliku migracji +- **Fix: carrier_id dla Apaczka** — uzupełniono z tabeli carrier_delivery_method_mappings (13 rows); dodano fallback w ApaczkaShipmentService +- **Fix: Orlen Paczka URL** — poprawiony na `www.orlenpaczka.pl/sledz-paczke/?numer=` +- **Fix: Google fallback** — gdy carrier nieznany, link śledzenia kieruje do Google search +- **Nowa metoda: ShipmentPackageRepository::resolveCarrierName()** — lookup carrier z carrier_delivery_method_mappings + +--- + +## What's In Progress + +- **Checkpoint Task 2** — user testuje UI badge'ów i linków śledzenia, jeszcze nie zatwierdził "approved" +- **Task 3** — ustawienia interwału trackingu w cronie — nie rozpoczęty + +--- + +## What's Next + +**Immediate:** Uzyskać checkpoint approval dla Task 2 (UI badge'y), potem wykonać Task 3 (cron interval settings) + +**After that:** +1. Sonar scan + UNIFY Phase 28 +2. PLAN Phase 29 — UI zarządzania mapowaniem statusów (user request) +3. Dodać fazę 29 do ROADMAP + +--- + +## Decisions Made + +| Decision | Rationale | +|----------|-----------| +| Google search jako fallback tracking URL | Gdy carrier_id nieznany — uniwersalne, zawsze działa | +| carrier_delivery_method_mappings jako źródło carrier_id | API Apaczki nie zwraca usług; tabela mapowań jest konfigurowana przez usera | +| Usunięto pattern matching po numerze śledzenia | Zawodne — 13-cyfrowy numer może być DPD, Orlen lub inny | +| Przycisk Pobierz w show.php zmieniony na POST form | Pre-existing bug — link do prepare zamiast bezpośredniego downloadu | + +--- + +## Key Files + +| File | Purpose | +|------|---------| +| `.paul/STATE.md` | Live project state | +| `.paul/ROADMAP.md` | Phase overview | +| `.paul/phases/28-shipment-tracking-ui/28-01-PLAN.md` | Current plan | +| `.paul/phases/27-shipment-tracking-backend/27-01-SUMMARY.md` | Phase 27 summary | +| `src/Modules/Shipments/DeliveryStatus.php` | Status mapping + trackingUrl() | +| `resources/views/orders/show.php` | Badge'e + link śledzenia w zamówieniu | +| `resources/views/shipments/prepare.php` | Badge'e + link śledzenia w przygotowaniu | +| `DOCS/SHIPMENT_TRACKING_STATUSES.md` | Dokumentacja statusów API | + +--- + +## Modified Files (uncommitted) + +- `src/Modules/Shipments/DeliveryStatus.php` — trackingUrl() z carrier detection + Google fallback +- `src/Modules/Shipments/ShipmentPackageRepository.php` — resolveCarrierName() +- `src/Modules/Shipments/ApaczkaShipmentService.php` — fallback carrier_id z mappings +- `resources/views/orders/show.php` — kolumna Status dostawy, badge, link, fix Pobierz +- `resources/views/shipments/prepare.php` — kolumna Status dostawy, badge, link +- `resources/scss/modules/_delivery-status.scss` — style badge'ów +- `resources/scss/app.scss` — @use delivery-status +- `public/assets/css/app.css` — rebuilt +- `database/migrations/20260323_000060_*` — fix initial status values + +--- + +## Resume Instructions + +1. Read `.paul/STATE.md` for latest position +2. Check this handoff file +3. Run `/paul:resume` or continue APPLY manually: + - Get checkpoint approval for Task 2 (badge'e UI) + - Execute Task 3 (cron interval settings in settings/cron.php) + - Then sonar + `/paul:unify` + +--- + +*Handoff created: 2026-03-23* diff --git a/.paul/phases/28-shipment-tracking-ui/28-01-PLAN.md b/.paul/phases/28-shipment-tracking-ui/28-01-PLAN.md new file mode 100644 index 0000000..de876df --- /dev/null +++ b/.paul/phases/28-shipment-tracking-ui/28-01-PLAN.md @@ -0,0 +1,294 @@ +--- +phase: 28-shipment-tracking-ui +plan: 01 +type: execute +wave: 1 +depends_on: ["27-01"] +files_modified: + - resources/views/orders/show.php + - resources/views/shipments/prepare.php + - resources/views/settings/cron.php + - resources/scss/modules/_shipments.scss + - src/Modules/Settings/CronSettingsController.php + - src/Modules/Cron/CronRepository.php +autonomous: false +--- + + +## Goal +Wyświetlić status dostawy przesyłek w UI (szczegóły zamówienia + strona przygotowania przesyłki), dodać ustawienie interwału trackingu w zakładce crona, oraz obsłużyć link śledzenia dla przesyłek ręcznych. + +## Purpose +Sprzedawca widzi aktualny status dostawy bez opuszczania aplikacji — oszczędza czas i eliminuje ręczne sprawdzanie na stronach przewoźników. + +## Output +- Kolumna "Status dostawy" w tabelach paczek (show.php + prepare.php) +- Kolorowe badge'e statusów z polskimi labelami +- Link śledzenia dla przesyłek z tracking number (URL budowany z providera) +- Ustawienie interwału trackingu crona w settings/cron.php +- Sekcja tracking info w boksie "Płatność i wysyłka" + + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md + +## Prior Work +@.paul/phases/27-shipment-tracking-backend/27-01-SUMMARY.md + +## Source Files +@resources/views/orders/show.php +@resources/views/shipments/prepare.php +@resources/views/settings/cron.php +@src/Modules/Settings/CronSettingsController.php +@src/Modules/Cron/CronRepository.php +@src/Modules/Shipments/DeliveryStatus.php +@src/Modules/Shipments/ShipmentPackageRepository.php + + + +## Required Skills (from SPECIAL-FLOWS.md) + +| Skill | Priority | When to Invoke | Loaded? | +|-------|----------|----------------|---------| +| sonar-scanner | required | Po APPLY, przed UNIFY | ○ | + +## Skill Invocation Checklist +- [ ] sonar-scanner uruchomiony po zakończeniu APPLY + + + + +## AC-1: Status dostawy w tabeli paczek na stronie zamówienia +```gherkin +Given zamówienie ma przesyłkę ze statusem delivery_status = 'in_transit' i delivery_status_raw = 'adopted_at_sorting_center' +When użytkownik otwiera stronę szczegółów zamówienia +Then w tabeli paczek (zakładka Przesyłki) widoczna jest kolumna "Status dostawy" +And status wyświetla się jako kolorowy badge "W tranzycie" +And po najechaniu (title) widać surowy status: "adopted_at_sorting_center — Przyjęta w centrum sortowania" +``` + +## AC-2: Status dostawy w tabeli paczek na stronie przygotowania przesyłki +```gherkin +Given zamówienie ma istniejące paczki z różnymi delivery_status +When użytkownik otwiera stronę przygotowania przesyłki +Then w sekcji "Wygenerowane przesyłki" widoczna jest kolumna "Status dostawy" +And każda paczka pokazuje badge ze statusem dostawy +``` + +## AC-3: Link śledzenia przesyłki +```gherkin +Given paczka ma tracking_number i znany provider (inpost/apaczka/allegro_wza) +When użytkownik widzi tabelę paczek +Then obok numeru śledzenia wyświetla się ikonka linku (🔗) otwierająca stronę śledzenia przewoźnika +And dla InPost link to https://inpost.pl/sledzenie-przesylek?number={tracking_number} +And dla Apaczka link to https://www.apaczka.pl/sledz-paczke/?numer={tracking_number} +And dla Allegro link budowany z allegro tracking URL +And dla manual bez linku (chyba że user poda URL w przyszłości) +``` + +## AC-4: Ustawienie interwału trackingu w cronie +```gherkin +Given użytkownik jest na stronie Ustawienia > Cron +When widzi tabelę harmonogramów crona +Then przy rekordzie shipment_tracking_sync widoczny jest edytowalny interwał (w minutach) +And po zmianie wartości i zapisaniu formularza interwał jest aktualizowany w cron_schedules +And dozwolone wartości: 5-120 minut +``` + +## AC-5: Info o śledzeniu w boksie "Płatność i wysyłka" +```gherkin +Given zamówienie ma przesyłkę z delivery_status != 'unknown' +When użytkownik otwiera szczegóły zamówienia +Then w boksie "Płatność i wysyłka" widoczna jest sekcja "Status dostawy" +And wyświetla znormalizowany status z kolorowym badge + datę ostatniej aktualizacji +And dla wielu paczek pokazuje status najnowszej (ostatnio zaktualizowanej) +``` + + + + + + + Task 1: Style SCSS + DeliveryStatus badge helper + tracking URL builder + resources/scss/modules/_shipments.scss, src/Modules/Shipments/DeliveryStatus.php + + **SCSS — badge'e statusów dostawy** (w _shipments.scss, lub nowy plik jeśli nie istnieje): + - `.delivery-badge` — bazowy styl: inline-block, padding 2px 8px, border-radius 3px, font-size 0.8em + - `.delivery-badge--unknown` — szary (#999 bg, #fff text) + - `.delivery-badge--created` — jasnoniebieski (#e3f2fd bg, #1565c0 text) + - `.delivery-badge--confirmed` — niebieski (#bbdefb bg, #0d47a1 text) + - `.delivery-badge--in_transit` — pomarańczowy (#fff3e0 bg, #e65100 text) + - `.delivery-badge--out_for_delivery` — ciemnopomarańczowy (#ffe0b2 bg, #bf360c text) + - `.delivery-badge--ready_for_pickup` — fioletowy (#f3e5f5 bg, #6a1b9a text) + - `.delivery-badge--delivered` — zielony (#e8f5e9 bg, #2e7d32 text) + - `.delivery-badge--returned` — czerwony (#ffebee bg, #c62828 text) + - `.delivery-badge--cancelled` — ciemnoszary (#e0e0e0 bg, #616161 text) + - `.delivery-badge--problem` — żółto-czerwony (#fff8e1 bg, #f57f17 text) + + **DeliveryStatus — metoda trackingUrl()**: + - Dodaj static method `trackingUrl(string $provider, string $trackingNumber): ?string` + - InPost: `https://inpost.pl/sledzenie-przesylek?number={trackingNumber}` + - Apaczka: `https://www.apaczka.pl/sledz-paczke/?numer={trackingNumber}` + - Allegro WZA: `https://allegro.pl/przesylka/{trackingNumber}` (generyczny tracking Allegro) + - Manual / unknown: return null + - Zwracaj null jeśli trackingNumber jest pusty + + Zbuilduj SCSS: sprawdź jak inne pliki SCSS są buildowane w projekcie (npx sass lub ręczny build). + Avoid: nie dodawaj nowych zależności npm/composer + + + php -l src/Modules/Shipments/DeliveryStatus.php + Sprawdź że DeliveryStatus::trackingUrl('inpost', 'ABC123') zwraca poprawny URL + Sprawdź że SCSS kompiluje się bez błędów + + AC-3 (częściowo) satisfied: tracking URL builder gotowy + + + + Task 2: UI — status dostawy w show.php i prepare.php + boks Płatność i wysyłka + resources/views/orders/show.php, resources/views/shipments/prepare.php + + **show.php — tabela paczek (zakładka Przesyłki):** + - Dodaj kolumnę "Status dostawy" po kolumnie "Status" w nagłówku tabeli + - Dla każdej paczki wyświetl: + ```php + $deliveryStatus = $pkg['delivery_status'] ?? 'unknown'; + $deliveryRaw = $pkg['delivery_status_raw'] ?? ''; + $label = \App\Modules\Shipments\DeliveryStatus::label($deliveryStatus); + $description = $deliveryRaw !== '' ? \App\Modules\Shipments\DeliveryStatus::description($pkg['provider'] ?? '', $deliveryRaw) : ''; + $title = $deliveryRaw !== '' ? e($deliveryRaw) . ' — ' . e($description) : ''; + ``` + - Badge: `{$label}` + - Kolumna "Nr śledzenia": dodaj link śledzenia obok numeru tracking + ```php + $trackingUrl = \App\Modules\Shipments\DeliveryStatus::trackingUrl($pkg['provider'] ?? '', $pkg['tracking_number'] ?? ''); + ``` + Jeśli trackingUrl !== null: `🔗` + + **show.php — boks "Płatność i wysyłka":** + - Po istniejących wierszach (carrier, send_date_max, shipments count) dodaj wiersz "Status dostawy" + - Pokaż status najnowszej paczki (ostatnia z $packagesList posortowana by delivery_status_updated_at DESC) + - Badge + data ostatniej aktualizacji: `{label} {date}` + - Wyświetlaj TYLKO jeśli $packagesList nie jest pusta i najnowszy delivery_status != 'unknown' + + **prepare.php — sekcja "Wygenerowane przesyłki":** + - Dodaj kolumnę "Status dostawy" analogicznie jak w show.php + - Badge z tooltip surowego statusu + - Link śledzenia obok tracking number + + Avoid: + - NIE dodawaj inline CSS — używaj klas z _shipments.scss + - NIE zmieniaj logiki kontrolerów — dane delivery_status są już w $packagesList z bazy + - Helper e() do escape HTML + + + Ręczna weryfikacja wizualna (checkpoint) + + AC-1, AC-2, AC-3, AC-5 satisfied + + + + Status dostawy w tabelach paczek (show.php + prepare.php) oraz w boksie Płatność i wysyłka. Kolorowe badge'e, tooltip z surowym statusem, linki śledzenia. + + 1. Otwórz stronę szczegółów zamówienia które ma przesyłkę + 2. Sprawdź zakładkę "Przesyłki" — czy jest kolumna "Status dostawy" z badge + 3. Najedź na badge — czy tooltip pokazuje surowy status + 4. Sprawdź link 🔗 obok numeru śledzenia — czy otwiera stronę przewoźnika + 5. Sprawdź boks "Płatność i wysyłka" — czy jest wiersz "Status dostawy" + 6. Otwórz stronę przygotowania przesyłki — czy sekcja istniejących paczek ma kolumnę statusu + + Type "approved" to continue, or describe issues to fix + + + + Task 3: Ustawienie interwału trackingu w cronie + resources/views/settings/cron.php, src/Modules/Settings/CronSettingsController.php, src/Modules/Cron/CronRepository.php + + **CronRepository — nowa metoda:** + - `updateScheduleInterval(string $jobType, int $intervalSeconds): void` + - UPDATE cron_schedules SET interval_seconds = :interval WHERE job_type = :job_type + + **CronSettingsController::save() — dodaj obsługę interwału trackingu:** + - Odczytaj POST param `tracking_interval_minutes` (int, default 15) + - Waliduj: min 5, max 120 + - Przelicz na sekundy: $intervalSeconds = $minutes * 60 + - Wywołaj: $cronRepository->updateScheduleInterval('shipment_tracking_sync', $intervalSeconds) + + **cron.php — dodaj sekcję ustawień trackingu:** + - Pod istniejącym formularzem "Uruchamianie z poziomu web" dodaj nową sekcję + - Nagłówek: "Śledzenie przesyłek" + - Input number: `tracking_interval_minutes`, min=5, max=120 + - Wartość domyślna: aktualny interval_seconds z cron_schedules / 60 + - Label: "Interwał sprawdzania statusu (minuty)" + - Opis: "Jak często system automatycznie sprawdza status dostawy przesyłek" + - Umieść w tym samym formularzu POST (żeby jedno "Zapisz" zapisywało wszystko) + + **CronSettingsController::index() — przekaż interwał do widoku:** + - Pobierz z $schedulesList rekord job_type='shipment_tracking_sync' + - Oblicz: $trackingIntervalMinutes = (int)(interval_seconds / 60) + - Przekaż do widoku + + Avoid: + - NIE twórz osobnego formularza — rozszerz istniejący + - NIE usuwaj istniejących ustawień crona + + + php -l na zmienionych plikach PHP + Sprawdź że zmiana interwału na 10 minut zapisuje interval_seconds=600 w cron_schedules + + AC-4 satisfied: interwał trackingu konfigurowalny w UI + + + + + + +## DO NOT CHANGE +- src/Modules/Shipments/ShipmentProviderInterface.php +- src/Modules/Shipments/InpostShipmentService.php +- src/Modules/Shipments/ApaczkaShipmentService.php +- src/Modules/Shipments/AllegroShipmentService.php +- src/Modules/Shipments/ShipmentTrackingInterface.php +- src/Modules/Shipments/InpostTrackingService.php (Phase 27) +- src/Modules/Shipments/ApaczkaTrackingService.php (Phase 27) +- src/Modules/Shipments/AllegroTrackingService.php (Phase 27) +- src/Modules/Cron/ShipmentTrackingHandler.php (Phase 27) +- src/Modules/Cron/CronHandlerFactory.php (Phase 27) +- database/migrations/* (brak nowych migracji w tej fazie) + +## SCOPE LIMITS +- Tylko UI i ustawienia — bez zmian w logice backendu trackingu +- Brak nowych migracji DB +- Brak nowych routów API +- Brak JavaScript — badge'e i linki to czysty HTML/PHP +- Tracking URL budowany statycznie z providera + tracking_number (nie z API) + + + + +Before declaring plan complete: +- [ ] php -l przechodzi na wszystkich zmienionych plikach +- [ ] SCSS kompiluje się bez błędów +- [ ] Badge'e statusów wyświetlają się poprawnie w show.php +- [ ] Badge'e statusów wyświetlają się poprawnie w prepare.php +- [ ] Link śledzenia otwiera poprawny URL dla InPost/Apaczka/Allegro +- [ ] Boks "Płatność i wysyłka" pokazuje status najnowszej paczki +- [ ] Interwał trackingu zapisuje się poprawnie w cron_schedules +- [ ] Istniejące ustawienia crona działają bez zmian +- [ ] All acceptance criteria met + + + +- Wszystkie taski ukończone (+ checkpoint approved) +- Badge'e widoczne z poprawnymi kolorami +- Linki śledzenia działają dla 3 providerów +- Interwał trackingu konfigurowalny 5-120 min +- Brak nowych zależności + + + +After completion, create `.paul/phases/28-shipment-tracking-ui/28-01-SUMMARY.md` + diff --git a/.paul/phases/28-shipment-tracking-ui/28-01-SUMMARY.md b/.paul/phases/28-shipment-tracking-ui/28-01-SUMMARY.md new file mode 100644 index 0000000..8351e4f --- /dev/null +++ b/.paul/phases/28-shipment-tracking-ui/28-01-SUMMARY.md @@ -0,0 +1,185 @@ +--- +phase: 28-shipment-tracking-ui +plan: 01 +subsystem: ui +tags: [delivery-status, tracking, badges, cron-settings, scss] + +requires: + - phase: 27-shipment-tracking-backend + provides: delivery_status columns, DeliveryStatus class, ShipmentTrackingHandler cron + +provides: + - Kolorowe badge'e statusów dostawy w UI (show.php + prepare.php) + - Link śledzenia przesyłki z carrier detection + Google fallback + - Sekcja "Status dostawy" w boksie Płatność i wysyłka + - Ustawienie interwału trackingu w UI crona (zakładka Ustawienia) + - Tekstowe mapowania statusów Apaczka API (NEW, CONFIRMED, etc.) + +affects: [] + +tech-stack: + added: [] + patterns: + - "content-tabs-nav pattern reused w cron.php (z allegro.php)" + - "Dual text+numeric status mapping w APACZKA_MAP" + +key-files: + created: + - resources/scss/modules/_delivery-status.scss + modified: + - src/Modules/Shipments/DeliveryStatus.php + - src/Modules/Shipments/ApaczkaShipmentService.php + - src/Modules/Shipments/ShipmentPackageRepository.php + - src/Modules/Settings/CronSettingsController.php + - src/Modules/Cron/CronRepository.php + - resources/views/orders/show.php + - resources/views/shipments/prepare.php + - resources/views/settings/cron.php + - resources/scss/app.scss + - public/assets/css/app.css + +key-decisions: + - "Google search jako fallback tracking URL gdy carrier nieznany" + - "carrier_delivery_method_mappings jako źródło carrier_id (nie pattern matching)" + - "Tekstowe mapowania Apaczka (API zwraca NEW/CONFIRMED, nie 0/1)" + - "Przesunięcie pickup na next day gdy po 16:00 (Apaczka API limit)" + - "Cron settings jako osobna zakładka (content-tabs-nav pattern)" + +patterns-established: + - "Dual status mapping: numeryczne + tekstowe klucze w tej samej mapie" + +duration: ~3h (z bugfixami) +started: 2026-03-23T19:00:00Z +completed: 2026-03-23T23:30:00Z +--- + +# Phase 28 Plan 01: Shipment Tracking UI + Settings Summary + +**Badge'e statusów dostawy w UI, linki śledzenia z carrier detection, ustawienie interwału trackingu w cronie z zakładkowym layoutem.** + +## Performance + +| Metric | Value | +|--------|-------| +| Duration | ~3h (z bugfixami) | +| Started | 2026-03-23 19:00 | +| Completed | 2026-03-23 23:30 | +| Tasks | 3 completed + checkpoint | +| Files modified | 12 | + +## Acceptance Criteria Results + +| Criterion | Status | Notes | +|-----------|--------|-------| +| AC-1: Status dostawy w tabeli paczek (show.php) | Pass | Kolumna z badge + tooltip surowego statusu | +| AC-2: Status dostawy w tabeli paczek (prepare.php) | Pass | Analogicznie jak show.php | +| AC-3: Link śledzenia przesyłki | Pass | InPost, Apaczka, Orlen, Allegro + Google fallback | +| AC-4: Ustawienie interwału trackingu w cronie | Pass | Input 5–120 min w zakładce Ustawienia | +| AC-5: Info o śledzeniu w boksie Płatność i wysyłka | Pass | Badge + data ostatniej aktualizacji | + +## Accomplishments + +- Badge'e statusów dostawy z 10 kolorami, tooltip z surowym statusem API, link śledzenia z auto-detection przewoźnika +- Ustawienia crona przeorganizowane na zakładki (Ustawienia / Harmonogram), interwał trackingu konfigurowalny 5–120 min +- Naprawiono 7 bugów wykrytych podczas implementacji i testów + +## Files Created/Modified + +| File | Change | Purpose | +|------|--------|---------| +| `resources/scss/modules/_delivery-status.scss` | Created | Style badge'ów statusów dostawy (10 wariantów kolorystycznych) | +| `src/Modules/Shipments/DeliveryStatus.php` | Modified | trackingUrl() z carrier detection + Google fallback; tekstowe mapowania Apaczka | +| `src/Modules/Shipments/ApaczkaShipmentService.php` | Modified | Fix use-statements; pickup date normalization (next day po 16:00) | +| `src/Modules/Shipments/ShipmentPackageRepository.php` | Modified | resolveCarrierName() lookup z carrier_delivery_method_mappings | +| `src/Modules/Settings/CronSettingsController.php` | Modified | Przekazuje/zapisuje trackingIntervalMinutes | +| `src/Modules/Cron/CronRepository.php` | Modified | updateScheduleInterval() + getScheduleInterval() | +| `resources/views/orders/show.php` | Modified | Kolumna Status dostawy, badge, link, fix Pobierz etykietę | +| `resources/views/shipments/prepare.php` | Modified | Kolumna Status dostawy, badge, link | +| `resources/views/settings/cron.php` | Modified | Zakładki Ustawienia/Harmonogram; sekcja śledzenia przesyłek | +| `resources/scss/app.scss` | Modified | @use delivery-status | +| `public/assets/css/app.css` | Modified | Rebuilt z nowym SCSS | +| `database/migrations/20260323_000060_*` | Modified | Fix initial status values | + +## Decisions Made + +| Decision | Rationale | Impact | +|----------|-----------|--------| +| Google search jako fallback tracking URL | Gdy carrier_id nieznany — uniwersalne, zawsze działa | Każda przesyłka ma jakiś link śledzenia | +| carrier_delivery_method_mappings jako źródło carrier_id | API Apaczki nie zwraca usług; tabela mapowań konfigurowana przez usera | Poprawne URL-e dla Orlen, DPD etc. | +| Tekstowe mapowania Apaczka API | API zwraca "NEW"/"CONFIRMED" nie "0"/"1" jak w docs | Statusy mapują się poprawnie | +| Pickup przesunięcie na next day po 16:00 | Apaczka API odrzuca gdy MaxPickupDate < ReadyDate (teraz) | Tworzenie przesyłek działa o każdej porze | +| Cron settings jako osobna zakładka | User request — lepsze oddzielenie ustawień od harmonogramu | Czytelniejszy UI | + +## Deviations from Plan + +### Summary + +| Type | Count | Impact | +|------|-------|--------| +| Auto-fixed | 7 | Niezbędne poprawki wykryte podczas testów | +| Scope additions | 1 | Zakładki w cron (user request) | +| Deferred | 0 | — | + +**Total impact:** Niezbędne poprawki + 1 usprawnienie UX na życzenie użytkownika. + +### Auto-fixed Issues + +**1. Fix: use-statements ApaczkaShipmentService** +- **Found during:** Task 2 (testing) +- **Issue:** `use AppCorexceptionsShipmentException` — brak backslashy (pre-existing bug z fazy 07) +- **Fix:** Poprawiono na `use App\Core\Exceptions\ShipmentException` i `IntegrationConfigException` + +**2. Fix: Przycisk Pobierz etykietę w show.php** +- **Found during:** Task 2 +- **Issue:** Link do prepare zamiast bezpośredniego downloadu PDF +- **Fix:** Zmieniony na formularz POST z bezpośrednim downloadem + +**3. Fix: delivery_status "delivered" → "confirmed"** +- **Found during:** Task 2 +- **Issue:** Migracja błędnie ustawiała label_ready jako doręczona +- **Fix:** Naprawiono w DB (3 rows) i w pliku migracji + +**4. Fix: carrier_id dla Apaczka** +- **Found during:** Task 2 +- **Issue:** Puste carrier_id w shipment_packages +- **Fix:** Uzupełniono z carrier_delivery_method_mappings (13 rows); fallback w ApaczkaShipmentService + +**5. Fix: Orlen Paczka URL** +- **Found during:** Task 2 +- **Issue:** Niepoprawny URL śledzenia +- **Fix:** Poprawiono na `www.orlenpaczka.pl/sledz-paczke/?numer=` + +**6. Fix: Tekstowe mapowania Apaczka API** +- **Found during:** Checkpoint Task 2 +- **Issue:** API zwraca "NEW" ale mapa ma klucze "0","1" — status zawsze "unknown" +- **Fix:** Dodano 12 tekstowych kluczy do APACZKA_MAP i APACZKA_DESCRIPTIONS + +**7. Fix: Apaczka pickup date/hours** +- **Found during:** Checkpoint Task 2 +- **Issue:** Tworzenie przesyłki po 16:00 — Apaczka API error MaxPickupDate < ReadyDate +- **Fix:** Automatyczne przesunięcie na next business day gdy po 16:00 + +## Sonar Results + +- 0 nowych unikalnych issues z kodu fazy 28 +- 1x S1448 CronRepository (22 metod > 20) — dodane 2 metody, pre-existing pattern +- Pre-existing: S3776, S1142, S1192 na ApaczkaShipmentService, DeliveryStatus, cron.php +- Skill audit: sonar-scanner ✓ + +## Next Phase Readiness + +**Ready:** +- System śledzenia przesyłek kompletny (backend + UI) +- Badge'e, linki, ustawienia crona działają +- Milestone v1.2 gotowy do zamknięcia + +**Concerns:** +- CronRepository zbliża się do limitu metod (22/20) — rozważyć split w przyszłości +- Apaczka API docs vs rzeczywistość (tekst vs numeric) — monitorować + +**Blockers:** +- Brak + +--- +*Phase: 28-shipment-tracking-ui, Plan: 01* +*Completed: 2026-03-23* diff --git a/database/migrations/20260323_000060_add_delivery_tracking_columns.sql b/database/migrations/20260323_000060_add_delivery_tracking_columns.sql index a795c4d..ffa3f95 100644 --- a/database/migrations/20260323_000060_add_delivery_tracking_columns.sql +++ b/database/migrations/20260323_000060_add_delivery_tracking_columns.sql @@ -27,5 +27,6 @@ EXECUTE stmt; DEALLOCATE PREPARE stmt; -- Set initial delivery_status for existing packages based on current status -UPDATE shipment_packages SET delivery_status = 'delivered' WHERE status = 'label_ready' AND delivery_status = 'unknown'; -UPDATE shipment_packages SET delivery_status = 'confirmed' WHERE status = 'created' AND provider != 'manual' AND delivery_status = 'unknown'; +-- label_ready = etykieta gotowa, NIE doręczona — ustawiamy confirmed (tracking cron zaktualizuje) +UPDATE shipment_packages SET delivery_status = 'confirmed' WHERE status = 'label_ready' AND delivery_status = 'unknown'; +UPDATE shipment_packages SET delivery_status = 'created' WHERE status = 'created' AND provider != 'manual' AND delivery_status = 'unknown'; diff --git a/public/assets/css/app.css b/public/assets/css/app.css index 013be8a..d39ebcc 100644 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -647,6 +647,61 @@ background: #fef2f2; } +.delivery-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 3px; + font-size: 0.8em; + font-weight: 500; + white-space: nowrap; +} +.delivery-badge--unknown { + background: #f5f5f5; + color: #999; +} +.delivery-badge--created { + background: #e3f2fd; + color: #1565c0; +} +.delivery-badge--confirmed { + background: #bbdefb; + color: #0d47a1; +} +.delivery-badge--in_transit { + background: #fff3e0; + color: #e65100; +} +.delivery-badge--out_for_delivery { + background: #ffe0b2; + color: #bf360c; +} +.delivery-badge--ready_for_pickup { + background: #f3e5f5; + color: #6a1b9a; +} +.delivery-badge--delivered { + background: #e8f5e9; + color: #2e7d32; +} +.delivery-badge--returned { + background: #ffebee; + color: #c62828; +} +.delivery-badge--cancelled { + background: #e0e0e0; + color: #616161; +} +.delivery-badge--problem { + background: #fff8e1; + color: #f57f17; +} + +.tracking-link { + margin-left: 4px; + text-decoration: none; + font-size: 0.85em; +} + * { box-sizing: border-box; } diff --git a/resources/scss/app.scss b/resources/scss/app.scss index a8affc1..0c8924e 100644 --- a/resources/scss/app.scss +++ b/resources/scss/app.scss @@ -3,6 +3,7 @@ @use "modules/automation"; @use "modules/printing"; @use "modules/shipment-presets"; +@use "modules/delivery-status"; * { box-sizing: border-box; diff --git a/resources/scss/modules/_delivery-status.scss b/resources/scss/modules/_delivery-status.scss new file mode 100644 index 0000000..4978e59 --- /dev/null +++ b/resources/scss/modules/_delivery-status.scss @@ -0,0 +1,25 @@ +.delivery-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 3px; + font-size: 0.8em; + font-weight: 500; + white-space: nowrap; + + &--unknown { background: #f5f5f5; color: #999; } + &--created { background: #e3f2fd; color: #1565c0; } + &--confirmed { background: #bbdefb; color: #0d47a1; } + &--in_transit { background: #fff3e0; color: #e65100; } + &--out_for_delivery { background: #ffe0b2; color: #bf360c; } + &--ready_for_pickup { background: #f3e5f5; color: #6a1b9a; } + &--delivered { background: #e8f5e9; color: #2e7d32; } + &--returned { background: #ffebee; color: #c62828; } + &--cancelled { background: #e0e0e0; color: #616161; } + &--problem { background: #fff8e1; color: #f57f17; } +} + +.tracking-link { + margin-left: 4px; + text-decoration: none; + font-size: 0.85em; +} diff --git a/resources/views/orders/show.php b/resources/views/orders/show.php index 612b11b..cdd1ded 100644 --- a/resources/views/orders/show.php +++ b/resources/views/orders/show.php @@ -224,6 +224,27 @@ foreach ($addressesList as $address) {
+ ($latestDeliveryPkg['delivery_status_updated_at'] ?? '')) { + $latestDeliveryPkg = $dpkg; + } + } + } + if ($latestDeliveryPkg !== null): + $ldStatus = (string) ($latestDeliveryPkg['delivery_status'] ?? 'unknown'); + $ldLabel = \App\Modules\Shipments\DeliveryStatus::label($ldStatus); + $ldDate = trim((string) ($latestDeliveryPkg['delivery_status_updated_at'] ?? '')); + ?> +
Status dostawy
+
+ + +
+ @@ -369,6 +390,7 @@ foreach ($addressesList as $address) { ID Status + Status dostawy Nr sledzenia Przewoznik Etykieta @@ -408,7 +430,21 @@ foreach ($addressesList as $address) {
- + + + + + + 🔗 + - @@ -416,7 +452,10 @@ foreach ($addressesList as $address) { - Pobierz +
+ + +
- diff --git a/resources/views/settings/cron.php b/resources/views/settings/cron.php index b6a5289..e394bb9 100644 --- a/resources/views/settings/cron.php +++ b/resources/views/settings/cron.php @@ -19,155 +19,190 @@ $pastTotal = max(0, (int) ($pastPagination['total'] ?? 0));
-

-

+ -
- +
+

+

- + + - + -
- -
- - + -
-

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
+

Śledzenie przesyłek

+ -
-

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ID
-
-
- -
-

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ID
-
- 1): ?> -
+ +
+

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID
+
+
+ +
+

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID
+
+ 1): ?> + + +
+
+ + diff --git a/resources/views/shipments/prepare.php b/resources/views/shipments/prepare.php index f2acea8..e8bfdbc 100644 --- a/resources/views/shipments/prepare.php +++ b/resources/views/shipments/prepare.php @@ -354,6 +354,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0'; ID Status + Status dostawy Nr sledzenia Przewoznik Etykieta @@ -382,7 +383,22 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
- + + + + + + 🔗 + diff --git a/src/Modules/Cron/CronRepository.php b/src/Modules/Cron/CronRepository.php index 92fdad0..53b1fbd 100644 --- a/src/Modules/Cron/CronRepository.php +++ b/src/Modules/Cron/CronRepository.php @@ -370,6 +370,31 @@ final class CronRepository ]); } + public function updateScheduleInterval(string $jobType, int $intervalSeconds): void + { + $statement = $this->pdo->prepare( + 'UPDATE cron_schedules + SET interval_seconds = :interval_seconds, + updated_at = NOW() + WHERE job_type = :job_type' + ); + $statement->execute([ + 'interval_seconds' => max(1, $intervalSeconds), + 'job_type' => trim($jobType), + ]); + } + + public function getScheduleInterval(string $jobType): ?int + { + $statement = $this->pdo->prepare( + 'SELECT interval_seconds FROM cron_schedules WHERE job_type = :job_type LIMIT 1' + ); + $statement->execute(['job_type' => trim($jobType)]); + $value = $statement->fetchColumn(); + + return $value !== false ? (int) $value : null; + } + private function getSettingValue(string $key): ?string { try { diff --git a/src/Modules/Settings/CronSettingsController.php b/src/Modules/Settings/CronSettingsController.php index fae4dcc..64ff182 100644 --- a/src/Modules/Settings/CronSettingsController.php +++ b/src/Modules/Settings/CronSettingsController.php @@ -55,6 +55,9 @@ final class CronSettingsController $pastPage = 1; } + $trackingIntervalSeconds = $this->cronRepository->getScheduleInterval('shipment_tracking_sync'); + $trackingIntervalMinutes = $trackingIntervalSeconds !== null ? (int) ($trackingIntervalSeconds / 60) : 15; + $html = $this->template->render('settings/cron', [ 'title' => $this->translator->get('settings.cron.title'), 'activeMenu' => 'settings', @@ -63,6 +66,7 @@ final class CronSettingsController 'csrfToken' => Csrf::token(), 'runOnWeb' => $runOnWeb, 'webLimit' => $webLimit, + 'trackingIntervalMinutes' => $trackingIntervalMinutes, 'schedules' => $schedules, 'futureJobs' => $futureJobs, 'pastJobs' => $pastJobs, @@ -91,9 +95,13 @@ final class CronSettingsController $webLimitRaw = (int) $request->input('cron_web_limit', $this->webLimitDefault); $webLimit = max(1, min(100, $webLimitRaw)); + $trackingMinutesRaw = (int) $request->input('tracking_interval_minutes', 15); + $trackingMinutes = max(5, min(120, $trackingMinutesRaw)); + try { $this->cronRepository->upsertSetting('cron_run_on_web', $runOnWeb ? '1' : '0'); $this->cronRepository->upsertSetting('cron_web_limit', (string) $webLimit); + $this->cronRepository->updateScheduleInterval('shipment_tracking_sync', $trackingMinutes * 60); Flash::set('settings_success', $this->translator->get('settings.cron.flash.saved')); } catch (Throwable $exception) { Flash::set('settings_error', $this->translator->get('settings.cron.flash.save_failed') . ' ' . $exception->getMessage()); diff --git a/src/Modules/Shipments/ApaczkaShipmentService.php b/src/Modules/Shipments/ApaczkaShipmentService.php index 0338fb5..ff401d8 100644 --- a/src/Modules/Shipments/ApaczkaShipmentService.php +++ b/src/Modules/Shipments/ApaczkaShipmentService.php @@ -7,8 +7,8 @@ use App\Modules\Orders\OrdersRepository; use App\Modules\Settings\ApaczkaApiClient; use App\Modules\Settings\ApaczkaIntegrationRepository; use App\Modules\Settings\CompanySettingsRepository; -use AppCorexceptionsIntegrationConfigException; -use AppCorexceptionsShipmentException; +use App\Core\Exceptions\IntegrationConfigException; +use App\Core\Exceptions\ShipmentException; use Throwable; final class ApaczkaShipmentService implements ShipmentProviderInterface @@ -117,6 +117,9 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface } $carrierLabel = trim((string) ($serviceDefinition['name'] ?? '')); + if ($carrierLabel === '') { + $carrierLabel = (string) ($this->packages->resolveCarrierName('apaczka', $deliveryMethodId) ?? ''); + } $packageId = $this->packages->create([ 'order_id' => $orderId, @@ -724,6 +727,13 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface $pickupDate = date('Y-m-d'); } $pickupDate = $this->normalizeCourierPickupDate($pickupDate); + + // If pickup is today and current time past safe cutoff, move to next business day + if ($pickupDate === date('Y-m-d') && (int) date('H') >= 16) { + $nextDay = date('Y-m-d', strtotime('+1 day', strtotime($pickupDate))); + $pickupDate = $this->normalizeCourierPickupDate($nextDay); + } + $hoursFrom = trim((string) ($formData['pickup_hours_from'] ?? '')); if (preg_match('/^\d{2}:\d{2}$/', $hoursFrom) !== 1) { $hoursFrom = '09:00'; @@ -748,6 +758,11 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface $ts = time(); } + $today = date('Y-m-d'); + if (date('Y-m-d', $ts) < $today) { + $ts = time(); + } + // Apaczka rejects Sunday as pickup date. $weekday = (int) date('N', $ts); if ($weekday === 7) { diff --git a/src/Modules/Shipments/DeliveryStatus.php b/src/Modules/Shipments/DeliveryStatus.php index 07d7d12..32ea84e 100644 --- a/src/Modules/Shipments/DeliveryStatus.php +++ b/src/Modules/Shipments/DeliveryStatus.php @@ -131,6 +131,18 @@ final class DeliveryStatus '8' => self::PROBLEM, '9' => self::READY_FOR_PICKUP, '10' => self::IN_TRANSIT, + 'NEW' => self::CREATED, + 'PENDING' => self::CREATED, + 'CONFIRMED' => self::CONFIRMED, + 'PICKED_UP' => self::IN_TRANSIT, + 'IN_TRANSIT' => self::IN_TRANSIT, + 'OUT_FOR_DELIVERY' => self::OUT_FOR_DELIVERY, + 'DELIVERED' => self::DELIVERED, + 'RETURNED' => self::RETURNED, + 'CANCELLED' => self::CANCELLED, + 'ERROR' => self::PROBLEM, + 'WAITING_FOR_PICKUP' => self::READY_FOR_PICKUP, + 'REDIRECT' => self::IN_TRANSIT, ]; private const APACZKA_DESCRIPTIONS = [ @@ -145,6 +157,18 @@ final class DeliveryStatus '8' => 'Błąd zamówienia', '9' => 'Oczekuje na odbiór w punkcie', '10' => 'Przekierowana', + 'NEW' => 'Zamówienie utworzone', + 'PENDING' => 'Oczekuje na przetworzenie', + 'CONFIRMED' => 'Zamówienie potwierdzone', + 'PICKED_UP' => 'Odebrana przez kuriera', + 'IN_TRANSIT' => 'W transporcie', + 'OUT_FOR_DELIVERY' => 'W doręczeniu', + 'DELIVERED' => 'Doręczona', + 'RETURNED' => 'Zwrócona do nadawcy', + 'CANCELLED' => 'Anulowana', + 'ERROR' => 'Błąd zamówienia', + 'WAITING_FOR_PICKUP' => 'Oczekuje na odbiór w punkcie', + 'REDIRECT' => 'Przekierowana', ]; private const ALLEGRO_MAP = [ @@ -200,4 +224,61 @@ final class DeliveryStatus { return in_array($status, self::TERMINAL_STATUSES, true); } + + public static function trackingUrl(string $provider, string $trackingNumber, string $carrierId = ''): ?string + { + $number = trim($trackingNumber); + if ($number === '') { + return null; + } + + $encoded = rawurlencode($number); + + if ($provider === 'inpost') { + return 'https://inpost.pl/sledzenie-przesylek?number=' . $encoded; + } + + if ($provider === 'allegro_wza') { + return 'https://allegro.pl/przesylka/' . $encoded; + } + + if ($carrierId !== '') { + $url = self::matchCarrierByName($encoded, strtolower(trim($carrierId))); + if ($url !== null) { + return $url; + } + } + + return 'https://www.google.com/search?q=' . $encoded . '+sledzenie+przesylki'; + } + + private static function matchCarrierByName(string $encoded, string $carrier): ?string + { + if (str_contains($carrier, 'dpd')) { + return 'https://tracktrace.dpd.com.pl/parcelDetails?p1=' . $encoded; + } + if (str_contains($carrier, 'dhl')) { + return 'https://www.dhl.com/pl-pl/home/sledzenie-przesylki.html?tracking-id=' . $encoded; + } + if (str_contains($carrier, 'inpost') || str_contains($carrier, 'paczkomat')) { + return 'https://inpost.pl/sledzenie-przesylek?number=' . $encoded; + } + if (str_contains($carrier, 'orlen') || str_contains($carrier, 'ruch')) { + return 'https://www.orlenpaczka.pl/sledz-paczke/?numer=' . $encoded; + } + if (str_contains($carrier, 'poczta') || str_contains($carrier, 'pocztex')) { + return 'https://emonitoring.poczta-polska.pl/?numer=' . $encoded; + } + if (str_contains($carrier, 'ups')) { + return 'https://www.ups.com/track?tracknum=' . $encoded; + } + if (str_contains($carrier, 'fedex')) { + return 'https://www.fedex.com/fedextrack/?trknbr=' . $encoded; + } + if (str_contains($carrier, 'gls')) { + return 'https://gls-group.com/PL/pl/sledzenie-paczek?match=' . $encoded; + } + + return null; + } } diff --git a/src/Modules/Shipments/ShipmentPackageRepository.php b/src/Modules/Shipments/ShipmentPackageRepository.php index 8349fd9..3454f65 100644 --- a/src/Modules/Shipments/ShipmentPackageRepository.php +++ b/src/Modules/Shipments/ShipmentPackageRepository.php @@ -181,6 +181,24 @@ final class ShipmentPackageRepository return is_array($row) ? $row : null; } + public function resolveCarrierName(string $provider, string $serviceId): ?string + { + if ($serviceId === '') { + return null; + } + + $statement = $this->pdo->prepare( + 'SELECT provider_service_name FROM carrier_delivery_method_mappings + WHERE provider = :provider AND provider_service_id = :service_id + LIMIT 1' + ); + $statement->execute(['provider' => $provider, 'service_id' => $serviceId]); + $row = $statement->fetch(PDO::FETCH_ASSOC); + $name = trim((string) ($row['provider_service_name'] ?? '')); + + return $name !== '' ? $name : null; + } + private function nullStr(string $value): ?string { $trimmed = trim($value);