This commit is contained in:
2026-04-07 20:32:43 +02:00
parent 1933c74395
commit 8fa9ca6439
45 changed files with 2974 additions and 3382 deletions

View File

@@ -78,6 +78,14 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
- [x] Zapamiętywanie wybranej liczby wierszy na stronie (per_page) w localStorage — Phase 72
- [x] Wyszukiwanie zamowien po nazwie produktu (EXISTS subquery) — Phase 73
- [x] Odwrocenie mapowania statusow: orderPRO po lewej, zewnetrzne po prawej (shopPRO + Allegro) — Phase 74
- [x] Rozdzielenie mapowania push/pull statusow shopPRO + ochrona statusu przy re-imporcie — Phase 75
- [x] Fallback danych odbiorcy z customer gdy delivery nie ma adresu (shopPRO) — Phase 76
- [x] Naprawa auto-fill kwoty pobrania (COD) dla zamowien shopPRO — Phase 77
- [x] Presety przesylek: auto-submit formularza po autofill — Phase 78
- [x] Import pola message z shopPRO do personalizacji pozycji i notatek zamowienia — Phase 79
- [x] Przeladowanie listy zamowien po zmianie statusu inline (location.reload) — Phase 80
- [x] Globalna wyszukiwarka zamowien w topbarze (AJAX search, dropdown, nawigacja klawiaturowa) — Phase 81
- [x] Tooltip z pelna nazwa produktu na liscie zamowien (natywny title attribute) — Phase 82
- [ ] Eliminacja zduplikowanego kodu: SslCertificateResolver, ToggleableRepositoryTrait, RedirectPathResolver, ReceiptService — Phase 68
### Active (In Progress)

View File

@@ -35,6 +35,14 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel
| 72 | Per Page Persistence | 1/1 | Complete |
| 73 | Search by Product | 1/1 | Complete |
| 74 | Reverse Status Mapping | 1/1 | Complete |
| 75 | Pull Status Mapping | 1/1 | Complete |
| 76 | Shipment Receiver Fallback | 1/1 | Complete |
| 77 | COD Amount Fix | 1/1 | Complete |
| 78 | Preset Auto Submit | 1/1 | Complete |
| 79 | Personalization Message Field | 1/1 | Complete |
| 80 | Status Change Reload | 1/1 | Complete |
| 81 | Global Search Bar | 1/1 | Complete |
| 82 | Product Title Tooltip | 1/1 | Complete |
| TBD | Mobile Orders List | - | Not started |
| TBD | Mobile Order Details | - | Not started |
| TBD | Mobile Settings | - | Not started |

View File

@@ -5,36 +5,36 @@
See: .paul/PROJECT.md (updated 2026-04-07)
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
**Current focus:** Milestone v3.0 - Phase 74 complete, ready for next PLAN
**Current focus:** Milestone v3.0 - Phase 82 complete, ready for next PLAN
## Current Position
Milestone: v3.0 Mobile Responsive - In progress
Phase: 74 (Reverse Status Mapping) — Complete
Plan: 74-01 unified
Phase: 82 (Product Title Tooltip) — Complete
Plan: 82-01 unified
Status: Loop complete, ready for next PLAN
Last activity: 2026-04-07 — Unified .paul/phases/74-reverse-status-mapping/74-01-PLAN.md
Last activity: 2026-04-07 — Unified .paul/phases/82-product-title-tooltip/82-01-PLAN.md
Progress:
- Milestone: [########..] ~78%
- Phase 74: [##########] 100%
- Milestone: [########..] ~89%
- Phase 82: [##########] 100%
## Loop Position
Current loop state:
```
PLAN --> APPLY --> UNIFY
PLAN ──▶ APPLY ──▶ UNIFY
✓ ✓ ✓ [Loop complete - ready for next PLAN]
```
## Session Continuity
Last session: 2026-04-07
Stopped at: Plan 74-01 unified
Stopped at: Plan 82-01 unified
Next action: Run /paul:plan for the next prioritized phase
Resume file: .paul/phases/74-reverse-status-mapping/74-01-SUMMARY.md
Resume file: .paul/phases/82-product-title-tooltip/82-01-SUMMARY.md
## Git State
Last commit: aadf98b
Last commit: 1933c74
Branch: main

View File

@@ -0,0 +1,254 @@
---
phase: 75-pull-status-mapping
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- database/migrations/20260407_000079_pull_status_mappings.sql
- src/Modules/Settings/ShopproPullStatusMappingRepository.php
- src/Modules/Settings/ShopproIntegrationsController.php
- src/Modules/Settings/ShopproOrdersSyncService.php
- resources/views/settings/shoppro.php
- resources/lang/pl.php
- routes/web.php
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
---
<objective>
## Goal
Rozdzielenie mapowania statusów shopPRO na dwa niezależne kierunki: PUSH (orderPRO→shopPRO, istniejąca tabela) i PULL (shopPRO→orderPRO, nowa tabela). Naprawa bugu Phase 74 gdzie wiele statusów orderPRO mapuje na ten sam kod shopPRO, a pull direction bierze alfabetycznie pierwszy (zawsze "do_odbioru" zamiast "w_realizacji").
## Purpose
Zamówienia importowane z shopPRO dostają złe statusy w orderPRO. Wszystkie zamówienia ze statusem shopPRO "4" (przyjęte do realizacji) lądują jako "do_odbioru" zamiast "w_realizacji" — potwierdzone na 4 zamówieniach (211-214).
## Output
- Nowa tabela `order_status_pull_mappings` (shopPRO code → orderPRO code, UNIQUE na shoppro side)
- Sekcja "Mapowanie przy imporcie" w UI pod istniejącym mapowaniem push
- `buildStatusMap()` korzysta z nowej tabeli pull zamiast odwracania push mappings
- Naprawione zamówienia 211-214
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@src/Modules/Settings/ShopproIntegrationsController.php (saveStatusMappings, buildMappingIndex, show — lines 55-120, 222-274, 500-518)
@src/Modules/Settings/ShopproStatusMappingRepository.php (listByIntegration, replaceForIntegration)
@src/Modules/Settings/ShopproOrdersSyncService.php (buildStatusMap — lines 302-318)
@resources/views/settings/shoppro.php (status mapping form — lines 195-265)
@resources/lang/pl.php (order_statuses section — lines 1008-1040)
@routes/web.php (shoppro routes — lines 449-450)
</context>
<acceptance_criteria>
## AC-1: Nowa tabela pull mappings
```gherkin
Given tabela order_status_pull_mappings istnieje
When zapiszę mapowanie shopPRO kod "4" orderPRO "w_realizacji" dla integracji 7
Then wiersz jest zapisany z UNIQUE constraint na (integration_id, shoppro_status_code)
And nie mogę zapisać drugiego wiersza z tym samym (integration_id, shoppro_status_code)
```
## AC-2: Sekcja pull mapping w UI
```gherkin
Given jestem na stronie Ustawienia > shopPRO > Statusy
When widzę formularz mapowania
Then pod sekcją "Wysyłka statusów" widzę sekcję "Import statusów"
And sekcja importu pokazuje statusy shopPRO po lewej z dropdownem orderPRO po prawej
And mogę zapisać mapowanie pull niezależnie od push
```
## AC-3: Import używa pull mappings
```gherkin
Given mam pull mapping: shopPRO "4" orderPRO "w_realizacji"
When cron importuje zamówienie ze statusem shopPRO "4"
Then zamówienie dostaje external_status_id = "w_realizacji"
And stara tabela order_status_mappings nie jest używana do pull
```
## AC-4: Naprawa istniejących zamówień
```gherkin
Given zamówienia 211-214 mają external_status_id = "do_odbioru"
When pull mapping shopPRO "4" "w_realizacji" jest skonfigurowane
And cron się uruchomi
Then zamówienia 211-214 zostaną zaktualizowane na "w_realizacji"
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Migracja DB + Repository</name>
<files>database/migrations/20260407_000079_pull_status_mappings.sql, src/Modules/Settings/ShopproPullStatusMappingRepository.php</files>
<action>
1. Utworzyć migrację SQL tworzącą tabelę `order_status_pull_mappings`:
- id INT AUTO_INCREMENT PRIMARY KEY
- integration_id INT NOT NULL
- shoppro_status_code VARCHAR(100) NOT NULL
- shoppro_status_name VARCHAR(255) DEFAULT NULL
- orderpro_status_code VARCHAR(100) NOT NULL
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- UNIQUE INDEX (integration_id, shoppro_status_code)
- INDEX (integration_id)
2. W migracji: pre-populate z istniejących danych. Dla każdej integracji i każdego unikalnego shoppro_status_code wziąć wiersz z `order_status_mappings` gdzie orderpro_status_code = 'w_realizacji' (jeśli istnieje) lub najnowszy wiersz (MAX(id)):
```sql
INSERT INTO order_status_pull_mappings (integration_id, shoppro_status_code, shoppro_status_name, orderpro_status_code)
SELECT osm.integration_id, osm.shoppro_status_code, osm.shoppro_status_name, osm.orderpro_status_code
FROM order_status_mappings osm
INNER JOIN (
SELECT integration_id, shoppro_status_code, MAX(id) as max_id
FROM order_status_mappings
WHERE shoppro_status_code <> ''
GROUP BY integration_id, shoppro_status_code
) latest ON osm.id = latest.max_id
WHERE osm.shoppro_status_code <> '';
```
Uwaga: to da domyślne mapowanie bazujące na najnowszym wpisie. Użytkownik może potem skorygować w UI.
3. Utworzyć `ShopproPullStatusMappingRepository` z metodami:
- `listByIntegration(int $integrationId): array` — zwraca wiersze z pull tabeli
- `replaceForIntegration(int $integrationId, array $mappings): void` — DELETE + INSERT (analogicznie do ShopproStatusMappingRepository)
- Wzorować na istniejącym ShopproStatusMappingRepository ale z odwróconymi kolumnami logicznymi (klucz = shoppro_status_code)
</action>
<verify>
- Migracja SQL parsuje się bez błędów: `php -r "echo 'OK';"` (manualna weryfikacja składni)
- Repository ma metody listByIntegration i replaceForIntegration
- UNIQUE na (integration_id, shoppro_status_code) zapobiega duplikatom
</verify>
<done>AC-1 satisfied: Tabela pull mappings z UNIQUE na shoppro side istnieje i ma repository</done>
</task>
<task type="auto">
<name>Task 2: UI sekcja pull mapping + controller + routing</name>
<files>resources/views/settings/shoppro.php, src/Modules/Settings/ShopproIntegrationsController.php, routes/web.php, resources/lang/pl.php</files>
<action>
1. W `ShopproIntegrationsController::show()`:
- Wstrzyknąć `ShopproPullStatusMappingRepository` przez konstruktor
- Załadować pull mappings: `$pullMappingIndex` = index po shoppro_status_code
- Przekazać do widoku: `'pullMappingIndex' => $pullMappingIndex`
2. Dodać nową metodę `savePullStatusMappings(Request $request): Response`:
- Analogicznie do `saveStatusMappings()` ale odwrócona logika
- Odczytuje `shoppro_status_code[]`, `orderpro_status_code[]`, `shoppro_status_name[]`
- Waliduje: shoppro_status_code nie może się powtarzać (UNIQUE)
- Zapisuje przez `ShopproPullStatusMappingRepository::replaceForIntegration()`
- Flash success/error, redirect do tab statuses
3. W `routes/web.php`:
- Dodać: `POST /settings/integrations/shoppro/statuses/save-pull` → `savePullStatusMappings`
4. W `resources/views/settings/shoppro.php` — pod istniejącym formularzem push (po linii ~264):
- Dodać nagłówek sekcji: "Mapowanie przy imporcie (shopPRO → orderPRO)"
- Opis: "Określ, jaki status orderPRO ma otrzymać zamówienie importowane z danym statusem shopPRO."
- Formularz z tabelą: lewa kolumna = status shopPRO (stały tekst + hidden input), prawa = dropdown z orderPRO statusami
- Action: `/settings/integrations/shoppro/statuses/save-pull`
- Iterować po `$shopproStatuses` (te same co w dropdownach push)
- Dla każdego shopPRO statusu: dropdown z orderPRO statusami, pre-selected z `$pullMappingIndex`
- Przycisk "Zapisz mapowanie importu"
5. W `resources/lang/pl.php` dodać klucze tłumaczeń:
- `settings.order_statuses.pull.title` → 'Mapowanie przy imporcie (shopPRO → orderPRO)'
- `settings.order_statuses.pull.description` → 'Określ, jaki status orderPRO ma otrzymać zamówienie importowane z danym statusem shopPRO.'
- `settings.order_statuses.pull.save` → 'Zapisz mapowanie importu'
- `settings.order_statuses.pull.saved` → 'Mapowanie importu zapisane.'
- `settings.order_statuses.pull.save_failed` → 'Błąd zapisu mapowania importu.'
</action>
<verify>
- Strona /settings/integrations/shoppro?tab=statuses wyświetla dwie sekcje mapowania
- Formularz pull ma shopPRO statusy po lewej, dropdown orderPRO po prawej
- Zapis działa i dane trafiają do tabeli order_status_pull_mappings
</verify>
<done>AC-2 satisfied: Sekcja pull mapping w UI z niezależnym zapisem</done>
</task>
<task type="auto">
<name>Task 3: buildStatusMap() z pull tabeli + naprawienie etykiet sekcji push</name>
<files>src/Modules/Settings/ShopproOrdersSyncService.php, src/Modules/Settings/ShopproIntegrationsController.php</files>
<action>
1. W `ShopproOrdersSyncService`:
- Wstrzyknąć `ShopproPullStatusMappingRepository` przez konstruktor
- Zmienić `buildStatusMap()` aby korzystała z pull tabeli:
```php
private function buildStatusMap(int $integrationId): array
{
$rows = $this->pullStatusMappings->listByIntegration($integrationId);
$map = [];
foreach ($rows as $row) {
$shopCode = strtolower(trim((string) ($row['shoppro_status_code'] ?? '')));
$orderCode = strtolower(trim((string) ($row['orderpro_status_code'] ?? '')));
if ($shopCode === '' || $orderCode === '') {
continue;
}
$map[$shopCode] = $orderCode; // No "first wins" — UNIQUE constraint guarantees one row per shopCode
}
return $map;
}
```
- Usunąć `if (!isset($map[$shopCode]))` guard — UNIQUE constraint na tabeli pull gwarantuje brak duplikatów
2. Zaktualizować `CronHandlerFactory` jeśli potrzebne — sprawdzić czy `ShopproOrdersSyncService` jest tam konstruowany i dodać nową zależność.
3. W istniejącej sekcji push w widoku: zmienić nagłówek sekcji z ogólnego "Mapowanie statusów" na "Wysyłka statusów (orderPRO → shopPRO)" żeby było jasne że to push direction.
Zaktualizować opis na: "Określ, jaki status shopPRO ma otrzymać zamówienie po zmianie statusu w orderPRO."
</action>
<verify>
- buildStatusMap() odczytuje z order_status_pull_mappings
- Przy pull mapping shopPRO "4" → "w_realizacji", import zamówienia ze statusem "4" daje external_status_id = "w_realizacji"
- Nie ma już "first wins" — każdy shopPRO code ma dokładnie jedno mapowanie
</verify>
<done>AC-3 satisfied: Import używa pull mappings z nowej tabeli. AC-4 będzie spełnione po następnym uruchomieniu crona.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- `order_status_mappings` — istniejąca tabela push mappings, nie modyfikować struktury
- `ShopproStatusMappingRepository` — istniejący repo push, nie modyfikować
- `ShopproStatusSyncService` (push direction) — nie zmieniać, push nadal korzysta ze starej tabeli
- `ShopproPaymentStatusSyncService` — nie zmieniać
- Allegro status mappings — nie dotyczy
## SCOPE LIMITS
- Nie naprawiamy ręcznie zamówień 211-214 — po deploy cron je zaktualizuje automatycznie
- Nie dodajemy pull mapping dla Allegro (osobna faza jeśli potrzebne)
- Nie zmieniamy logiki push mappings
- Nie dodajemy walidacji UI (JS) — duplikaty są blokowane przez UNIQUE constraint w DB
</boundaries>
<verification>
Before declaring plan complete:
- [ ] Migracja SQL tworzy tabelę i pre-populuje dane
- [ ] Repository CRUD działa (list, replace)
- [ ] UI wyświetla dwie sekcje: push i pull
- [ ] Zapis pull mappings trafia do nowej tabeli
- [ ] buildStatusMap() czyta z pull tabeli
- [ ] Brak regresji w push direction (ShopproStatusSyncService niezmodyfikowany)
- [ ] Tłumaczenia kompletne
- [ ] DOCS zaktualizowane
</verification>
<success_criteria>
- Nowa tabela order_status_pull_mappings istnieje i ma UNIQUE na (integration_id, shoppro_status_code)
- UI ma dwie odrębne sekcje mapowania (push i pull) z jasnymi etykietami kierunku
- Import zamówień z shopPRO korzysta wyłącznie z pull tabeli
- Zamówienia ze statusem shopPRO "4" trafiają jako "w_realizacji" (nie "do_odbioru")
- Push sync nie jest naruszony
</success_criteria>
<output>
After completion, create `.paul/phases/75-pull-status-mapping/75-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,142 @@
---
phase: 75-pull-status-mapping
plan: 01
subsystem: settings, orders
tags: [status-mapping, shoppro, pull, import, payment-transition]
requires:
- phase: 74-reverse-status-mapping
provides: push mapping with UNIQUE on orderpro_status_code
provides:
- dedicated pull status mapping table (order_status_pull_mappings)
- pull mapping UI section in shopPRO integration settings
- status protection on re-import (only nieoplacone→w_realizacji transition allowed)
affects: [shoppro-import, status-sync, automation]
tech-stack:
added: []
patterns: [separate push/pull mapping tables, payment-transition guard on re-import]
key-files:
created:
- database/migrations/20260407_000079_pull_status_mappings.sql
- src/Modules/Settings/ShopproPullStatusMappingRepository.php
modified:
- src/Modules/Settings/ShopproIntegrationsController.php
- src/Modules/Settings/ShopproOrdersSyncService.php
- src/Modules/Orders/OrderImportRepository.php
- src/Modules/Cron/CronHandlerFactory.php
- resources/views/settings/shoppro.php
- resources/lang/pl.php
- routes/web.php
key-decisions:
- "Separate pull table instead of is_pull_target flag — cleaner separation of concerns"
- "Status protection on re-import: only nieoplacone+paid=2 triggers status change"
- "Fallback to push table if pull repo not injected — backward compatibility"
patterns-established:
- "Pull mapping: UNIQUE on (integration_id, shoppro_status_code) — one orderPRO status per shopPRO code"
- "Re-import status guard: getCurrentStatus() check before updateOrder()"
duration: ~45min
started: 2026-04-07T12:00:00Z
completed: 2026-04-07T12:45:00Z
---
# Phase 75 Plan 01: Pull Status Mapping — Summary
**Rozdzielenie mapowania statusow shopPRO na push/pull + ochrona statusu przy re-imporcie (tylko nieoplacone→w_realizacji przy potwierdzeniu platnosci)**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~45min |
| Tasks | 3 planned + 1 extension (status protection) |
| Files modified | 12 |
| DB migration | Executed on remote |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Nowa tabela pull mappings | Pass | Tabela utworzona z UNIQUE, 18 rows pre-populated |
| AC-2: Sekcja pull mapping w UI | Pass | Dwie sekcje: push ("Wysylka statusow") + pull ("Mapowanie przy imporcie") |
| AC-3: Import uzywa pull mappings | Pass | buildStatusMap() czyta z pull tabeli, fallback na push |
| AC-4: Naprawa zamowien 211-214 | Pass | Statusy juz poprawione (w_realizacji) |
## Accomplishments
- Nowa tabela `order_status_pull_mappings` z UNIQUE na `(integration_id, shoppro_status_code)` — eliminuje bug "first wins" z Phase 74
- UI w Ustawienia > shopPRO > Statusy ma dwie sekcje: push (orderPRO→shopPRO) i pull (shopPRO→orderPRO) z jasnymi etykietami kierunku
- Ochrona statusu przy re-imporcie: `updateOrder()` nie nadpisuje `external_status_id` CHYBA ZE obecny status = `nieoplacone` i `payment_status = 2` (platnosc potwierdzona)
- Przy payment transition: importowane sa rowniez dane platnosci (`replacePayments`)
- Activity log rozroznia payment transition: "Platnosc potwierdzona z shopPRO — zmiana statusu na w realizacji"
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `database/migrations/20260407_000079_pull_status_mappings.sql` | Created | Nowa tabela + pre-populate z push mappings |
| `src/Modules/Settings/ShopproPullStatusMappingRepository.php` | Created | Repository CRUD dla pull mappings |
| `src/Modules/Settings/ShopproIntegrationsController.php` | Modified | Nowa zaleznosc, savePullStatusMappings(), buildPullMappingIndex() |
| `src/Modules/Settings/ShopproOrdersSyncService.php` | Modified | buildStatusMap() z pull tabeli, payment transition log |
| `src/Modules/Orders/OrderImportRepository.php` | Modified | Status protection + payment transition logic + getCurrentStatus() |
| `src/Modules/Cron/CronHandlerFactory.php` | Modified | Wstrzykniecie ShopproPullStatusMappingRepository |
| `resources/views/settings/shoppro.php` | Modified | Sekcja pull mapping, zmieniony tytul push |
| `resources/lang/pl.php` | Modified | Klucze pull.*, zmieniony tytul/opis push |
| `routes/web.php` | Modified | Nowa route POST .../statuses/save-pull |
| `DOCS/DB_SCHEMA.md` | Modified | Dokumentacja nowej tabeli |
| `DOCS/ARCHITECTURE.md` | Modified | Nowy endpoint save-pull |
| `DOCS/TECH_CHANGELOG.md` | Modified | Wpis Phase 75 |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Osobna tabela pull zamiast flagi is_pull_target | Czysta separacja push/pull, brak ryzyka kolizji | Dodatkowa tabela ale prosta logika |
| Status protection: tylko nieoplacone+paid=2 | User chce push-only z jednym wyjatkiem: platnosc | Re-import nie nadpisuje recznych zmian statusu |
| Fallback na push table jesli pull repo null | Backward compatibility dla kodu ktory nie wstrzykuje pull repo | Bezpieczna migracja |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Scope additions | 1 | Ochrona statusu przy re-imporcie (na zadanie usera) |
| Auto-fixed | 1 | Naprawa order 180 ze statusem "7" |
**Total impact:** Rozszerzenie scope o kluczowa logike biznesowa — status protection
### Scope Addition: Status Protection on Re-import
- **Requested by:** User during APPLY
- **Issue:** Re-import z shopPRO nadpisywal recznie zmienione statusy (np. order 180: "wyslane" → "7")
- **Fix:** `upsertOrderAggregate()` zachowuje current status chyba ze nieoplacone + payment confirmed
- **Files:** `src/Modules/Orders/OrderImportRepository.php`
### Auto-fixed: Order 180 status "7"
- **Found during:** Investigation
- **Issue:** Surowy kod shopPRO "7" (nieznany status) nadpisal "wyslane"
- **Fix:** Status juz poprawiony (wyslane). Nowa logika zapobiega powtorzeniu.
## Next Phase Readiness
**Ready:**
- Pull mapping w pelni funkcjonalne i konfigurowalne
- Status protection chroni reczne zmiany w orderPRO
- Push sync (ShopproStatusSyncService) niezmodyfikowany — dziala jak dotychczas
**Concerns:**
- shopPRO status "7" nie ma mapowania — user powinien zsynchronizowac statusy (przycisk w UI) lub dodac recznie
- Allegro mappings nie maja analogicznego pull — jesli potrzebne, osobna faza
**Blockers:**
- None — kod wymaga deploy na serwer (FTP)
---
*Phase: 75-pull-status-mapping, Plan: 01*
*Completed: 2026-04-07*

View File

@@ -0,0 +1,155 @@
---
phase: 76-shipment-receiver-fallback
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Modules/Shipments/ShipmentController.php
- src/Modules/Settings/ShopproOrderMapper.php
autonomous: true
---
<objective>
## Goal
Naprawic puste dane odbiorcy na stronie przygotowania przesylki (`/orders/{id}/shipment/prepare`) dla zamowien shopPRO, gdzie adres dostawy zawiera tylko nazwe metody dostawy bez danych adresowych.
## Purpose
Sprzedawca nie musi reczne przepisywac danych klienta do formularza przesylki — formularz automatycznie wypelnia sie danymi z adresu klienta gdy adres dostawy nie ma pelnych danych.
## Output
- Poprawiona metoda `buildReceiverAddress` w ShipmentController z fallbackami na dane klienta
- Poprawiona metoda `buildDeliveryAddress` w ShopproOrderMapper — rozroznienie label metody dostawy od nazwy odbiorcy
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@src/Modules/Shipments/ShipmentController.php
@src/Modules/Settings/ShopproOrderMapper.php
## Diagnostyka
Zamowienie 183 (shopPRO) — dane w `order_addresses`:
- customer: name="Paulina Smolinska", phone="505799865", email="paulinasmolinska2@wp.pl", street="Zamojskiego 80/46", city="Zuromin", zip="09-300"
- delivery: name="Kurier - przedplata: 0 zl", phone="505799865", email="...", street=NULL, city=NULL, zip=NULL
Problem: `buildReceiverAddress` uzywa delivery jako bazy, ale fallbacki dzialaja TYLKO dla name/phone/email — brak fallbacku na street/city/zip/country z customer address.
Dodatkowy problem: mapper zapisuje label metody dostawy jako `name` w delivery address.
</context>
<skills>
No specialized flows configured
</skills>
<acceptance_criteria>
## AC-1: Fallback danych adresowych na klienta
```gherkin
Given zamowienie shopPRO z adresem delivery bez street/city/zip
And adres customer ma pelne dane (name, street, city, zip)
When uzytkownik otwiera /orders/{id}/shipment/prepare
Then formularz "Adres odbiorcy" jest wypelniony danymi z adresu customer (imie, ulica, miasto, kod)
```
## AC-2: Delivery z pelnymi danymi — bez zmian
```gherkin
Given zamowienie z adresem delivery zawierajacym pelne dane (street, city, zip)
When uzytkownik otwiera /orders/{id}/shipment/prepare
Then formularz uzywa danych z adresu delivery (bez zmian wzgledem obecnego zachowania)
```
## AC-3: Nazwa odbiorcy — fallback na klienta gdy delivery name to label metody
```gherkin
Given zamowienie shopPRO z delivery name = "Kurier - przedplata: 0 zl"
And customer name = "Paulina Smolinska"
When uzytkownik otwiera /orders/{id}/shipment/prepare
Then pole "Imie i nazwisko" zawiera "Paulina Smolinska"
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Rozszerzyc fallbacki w buildReceiverAddress</name>
<files>src/Modules/Shipments/ShipmentController.php</files>
<action>
W metodzie `buildReceiverAddress` (linia 423) dodac fallbacki z customer na delivery dla pol adresowych:
- `street_name`: jezeli w delivery jest puste/null, uzyj z customer
- `street_number`: jezeli w delivery jest puste/null, uzyj z customer
- `city`: jezeli w delivery jest puste/null, uzyj z customer
- `zip_code`: jezeli w delivery jest puste/null, uzyj z customer
- `country`: jezeli w delivery jest puste/null, uzyj z customer
Dodatkowo zmienic warunek fallbacku `name`:
Obecny: fallback na customer name tylko gdy pickup point LUB delivery name jest puste.
Nowy: fallback na customer name rowniez gdy delivery name wyglada jak label metody dostawy
(nie ma adresu ulicy — tzn. jezeli delivery nie ma street_name, to name tez powinno byc z customer).
Logika: jezeli delivery nie ma street_name (jest null/pusty) i customer ma name, uzyj customer name.
To pokrywa zarowno przypadek pickup point, jak i "Kurier - przedplata" bez danych adresowych.
Wzorzec fallbacku (analogicznie do istniejacych phone/email):
```php
if (trim((string) ($result['street_name'] ?? '')) === '' && trim((string) ($customer['street_name'] ?? '')) !== '') {
$result['street_name'] = $customer['street_name'];
}
```
I tak samo dla street_number, city, zip_code, country.
Dla name — zmienic warunek z:
```php
if (($this->isPickupPointDelivery($delivery) || $deliveryName === '') && $customerName !== '') {
```
na:
```php
$deliveryHasAddress = trim((string) ($delivery['street_name'] ?? '')) !== '';
if ((!$deliveryHasAddress || $this->isPickupPointDelivery($delivery) || $deliveryName === '') && $customerName !== '') {
```
</action>
<verify>
Sprawdzic ze zamowienie 183 na /orders/183/shipment/prepare wyswietla dane klienta w formularzu odbiorcy.
Sprawdzic ze zamowienia z pelnym adresem delivery nadal dzialaja poprawnie.
</verify>
<done>AC-1, AC-2, AC-3 satisfied: formularz wypelnia sie danymi klienta gdy delivery nie ma danych adresowych</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- src/Modules/Orders/OrderImportRepository.php (logika importu)
- src/Modules/Orders/OrdersRepository.php (logika zapytan)
- resources/views/shipments/prepare.php (widok — formularz juz prawidlowo czyta z $receiver)
- database/migrations/* (schemat bazy)
## SCOPE LIMITS
- Nie zmieniamy sposobu importu adresow z shopPRO (ShopproOrderMapper::buildDeliveryAddress) — problem jest w prezentacji, nie w imporcie
- Nie zmieniamy struktury tabeli order_addresses
- Fix dotyczy tylko buildReceiverAddress w ShipmentController
</boundaries>
<verification>
Before declaring plan complete:
- [ ] Zamowienie 183: formularz na /orders/183/shipment/prepare wypelnia sie danymi klienta
- [ ] Zamowienie z pelnym adresem delivery: formularz uzywa danych delivery
- [ ] Zamowienie z pickup point (paczkomat): formularz uzywa name klienta i adres punktu
- [ ] Brak bledow PHP
- [ ] Docs zaktualizowane (ARCHITECTURE.md, TECH_CHANGELOG.md)
</verification>
<success_criteria>
- Formularz odbiorcy na stronie przygotowania przesylki jest wypelniony danymi klienta gdy delivery nie ma adresu
- Istniejace zamowienia z pelnym adresem delivery dzialaja bez zmian
- Brak regresji w tworzeniu przesylek
</success_criteria>
<output>
After completion, create `.paul/phases/76-shipment-receiver-fallback/76-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,112 @@
---
phase: 76-shipment-receiver-fallback
plan: 01
subsystem: shipments
tags: [address-fallback, shoppro, receiver-data]
requires:
- phase: none
provides: none
provides:
- Fallback danych odbiorcy z customer na delivery w formularzu przesylki
affects: []
tech-stack:
added: []
patterns: [address-field-fallback-loop]
key-files:
created: []
modified:
- src/Modules/Shipments/ShipmentController.php
key-decisions:
- "Fix w buildReceiverAddress zamiast w mapperze — problem prezentacyjny, nie importowy"
- "Foreach loop zamiast osobnych if-ow — czystszy kod, latwiejsze rozszerzenie"
patterns-established:
- "buildReceiverAddress fallback: delivery -> customer dla wszystkich pol adresowych"
duration: 10min
started: 2026-04-07T00:00:00Z
completed: 2026-04-07T00:10:00Z
---
# Phase 76 Plan 01: Shipment Receiver Fallback Summary
**Fallback danych odbiorcy z customer address gdy delivery address nie ma danych adresowych (ulica/miasto/kod)**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~10min |
| Tasks | 1 completed |
| Files modified | 3 (code + docs) |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Fallback danych adresowych na klienta | Pass | Loop fallback na 7 pol (phone, email, street_name, street_number, city, zip_code, country) |
| AC-2: Delivery z pelnymi danymi — bez zmian | Pass | Fallback uruchamia sie tylko gdy pole jest puste |
| AC-3: Nazwa odbiorcy — fallback gdy delivery nie ma ulicy | Pass | Warunek `!$deliveryHasAddress` pokrywa przypadek label metody dostawy |
## Accomplishments
- Formularz odbiorcy na `/orders/{id}/shipment/prepare` automatycznie wypelnia sie danymi klienta gdy delivery nie ma danych adresowych
- Uproszczono kod — foreach loop zamiast powtarzajacych sie if-ow dla phone/email, rozszerzony o street/city/zip/country
- Dodano warunek name fallback: gdy delivery nie ma ulicy, name tez jest pobierane z customer (pokrywa "Kurier - przedplata" jako delivery name)
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `src/Modules/Shipments/ShipmentController.php` | Modified | Rozszerzono `buildReceiverAddress` o fallbacki pol adresowych z customer |
| `DOCS/ARCHITECTURE.md` | Modified | Opis nowej logiki fallbacku w sekcji przeplywu tworzenia przesylki |
| `DOCS/TECH_CHANGELOG.md` | Modified | Wpis Phase 76 z opisem problemu i rozwiazania |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Fix w buildReceiverAddress, nie w mapperze | Problem jest prezentacyjny — dane w DB sa poprawne (customer ma pelne dane), mapper robi co moze z danymi API shopPRO | Brak zmian w logice importu |
| Foreach loop zamiast osobnych if-ow | Uproszczenie kodu, latwiejsze dodanie nowych pol w przyszlosci | Zamieniono 2 osobne if-y na 1 loop pokrywajacy 7 pol |
| ShopproOrderMapper.php bez zmian | Plan przewidywal potencjalna modyfikacje, ale fix w kontrolerze wystarczyl | Mniej zmian, mniejsze ryzyko regresji importu |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Scope reduction | 1 | Pozytywny — mniej zmian |
**Total impact:** Mniejszy zakres niz planowany — ShopproOrderMapper nie wymaga zmian
### Details
**1. ShopproOrderMapper.php bez zmian**
- **Plan:** files_modified zawieral ShopproOrderMapper.php
- **Rzeczywistosc:** Fix w buildReceiverAddress wystarczyl, mapper nie wymaga modyfikacji
- **Impact:** Pozytywny — mniej kodu do zmiany, zero ryzyka regresji importu
## Issues Encountered
None
## Next Phase Readiness
**Ready:**
- Formularz przesylki dziala poprawnie dla zamowien shopPRO z niekompletnymi adresami delivery
- Logika jest generyczna — dziala dla kazdego zrodla zamowien
**Concerns:**
- None
**Blockers:**
- None
---
*Phase: 76-shipment-receiver-fallback, Plan: 01*
*Completed: 2026-04-07*

View File

@@ -0,0 +1,157 @@
---
phase: 77-cod-amount-fix
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Core/Support/StringHelper.php
- src/Modules/Settings/ShopproOrderMapper.php
- resources/views/shipments/prepare.php
- resources/views/orders/show.php
- src/Modules/Orders/OrdersController.php
autonomous: true
---
<objective>
## Goal
Naprawić automatyczne uzupełnianie kwoty pobrania (COD) przy generowaniu przesyłki dla zamówień spoza Allegro (shopPRO).
## Purpose
Zamówienia shopPRO z płatnością za pobraniem nie mają auto-wypełnionej kwoty COD w formularzu przesyłki, ponieważ pole `external_payment_type_id` zawiera wartość z shopPRO API (np. `"cod"`, `"pobranie"`), a nie Allegro-specyficzne `"CASH_ON_DELIVERY"`.
## Output
- Centralna metoda `StringHelper::isCodPayment()` zastępuje wszystkie hardcoded sprawdzenia
- ShopproOrderMapper normalizuje typ płatności do `CASH_ON_DELIVERY` przy imporcie
- Kwota pobrania auto-wypełnia się poprawnie dla zamówień z każdego źródła
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@src/Core/Support/StringHelper.php
@src/Modules/Settings/ShopproOrderMapper.php
@resources/views/shipments/prepare.php
@resources/views/orders/show.php
@src/Modules/Orders/OrdersController.php
</context>
<skills>
No specialized flows configured — optional skills only.
</skills>
<acceptance_criteria>
## AC-1: COD auto-fill dla zamówień shopPRO
```gherkin
Given zamówienie shopPRO z payment_method "cod" lub "pobranie" lub "za pobraniem"
When użytkownik wchodzi na /orders/{id}/shipment/prepare
Then pole "Pobranie" zawiera kwotę total_with_tax zamówienia
And widoczny jest badge "ZA POBRANIEM"
```
## AC-2: COD auto-fill dla zamówień Allegro (regresja)
```gherkin
Given zamówienie Allegro z external_payment_type_id "CASH_ON_DELIVERY"
When użytkownik wchodzi na /orders/{id}/shipment/prepare
Then pole "Pobranie" zawiera kwotę total_with_tax zamówienia (bez zmian w zachowaniu)
```
## AC-3: Detekcja COD w widoku szczegółów zamówienia
```gherkin
Given zamówienie z dowolnym wariantem nazwy COD
When użytkownik przegląda szczegóły zamówienia
Then badge "Za pobraniem" wyświetla się poprawnie
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Centralna metoda isCodPayment w StringHelper + normalizacja w ShopproOrderMapper</name>
<files>src/Core/Support/StringHelper.php, src/Modules/Settings/ShopproOrderMapper.php</files>
<action>
1. W `StringHelper` dodaj statyczną metodę `isCodPayment(string $value): bool`:
- Normalizuj wartość: `strtoupper(trim($value))`
- Sprawdź czy pasuje do znanego zbioru: `CASH_ON_DELIVERY`, `COD`, `POBRANIE`, `ZA POBRANIEM`
- Zwróć true/false
2. W `ShopproOrderMapper::mapOrderAggregate()` (linia 139):
- Po odczytaniu `payment_method` ze ścieżek `['payment_method', 'payment.method', 'payments.method']`
- Jeśli odczytana wartość jest rozpoznawana jako COD (przez `StringHelper::isCodPayment()`), znormalizuj na `'CASH_ON_DELIVERY'`
- Dzięki temu nowe importy będą miały ujednolicony format
Nie zmieniaj kontraktu ShopproOrderMapper dla pozostałych pól.
</action>
<verify>
Przegląd kodu: metoda isCodPayment obsługuje min. 4 warianty; ShopproOrderMapper normalizuje COD.
</verify>
<done>AC-1 i AC-2 spełnione od strony danych — nowe importy mają ujednolicony format</done>
</task>
<task type="auto">
<name>Task 2: Zamiana hardcoded sprawdzeń na StringHelper::isCodPayment</name>
<files>resources/views/shipments/prepare.php, resources/views/orders/show.php, src/Modules/Orders/OrdersController.php</files>
<action>
1. `resources/views/shipments/prepare.php` linia 41:
- Zamień: `strtoupper(trim(...)) === 'CASH_ON_DELIVERY'`
- Na: `\App\Core\Support\StringHelper::isCodPayment((string) ($orderRow['external_payment_type_id'] ?? ''))`
2. `src/Modules/Orders/OrdersController.php` linia 343:
- Zamień: `$isCod = $paymentType === 'CASH_ON_DELIVERY'`
- Na: `$isCod = StringHelper::isCodPayment($paymentType)`
- Upewnij się że `use App\Core\Support\StringHelper;` jest w importach
3. `resources/views/orders/show.php`:
- Znajdź wszystkie porównania z `'CASH_ON_DELIVERY'` (linie ~222, 228, 578, 678)
- Zamień na `\App\Core\Support\StringHelper::isCodPayment(...)`
- Zachowaj mapę wyświetlania nazw (array z 'CASH_ON_DELIVERY' => 'Za pobraniem' itp.) — dodaj warianty COD do mapy
Nie zmieniaj logiki wyświetlania ani formatowania — tylko warunek detekcji COD.
</action>
<verify>
Grep po codebase: brak hardcoded `=== 'CASH_ON_DELIVERY'` w plikach PHP poza testami i DOCS.
Widok shipment prepare: zamówienie shopPRO z pobraniem ma auto-wypełnioną kwotę.
</verify>
<done>AC-1, AC-2, AC-3 spełnione — detekcja COD działa dla wszystkich źródeł zamówień</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- src/Modules/Settings/AllegroOrderImportService.php (Allegro import działa poprawnie)
- database/migrations/* (brak zmian schematu)
- src/Modules/Shipments/ShipmentController.php (logika tworzenia przesyłki nie wymaga zmian)
## SCOPE LIMITS
- Nie aktualizujemy istniejących danych w bazie (istniejące zamówienia shopPRO z "cod" zachowają starą wartość — helper obsłuży je w runtime)
- Nie dodajemy migracji
- Nie zmieniamy API ani endpointów
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `StringHelper::isCodPayment()` rozpoznaje: CASH_ON_DELIVERY, COD, POBRANIE, ZA POBRANIEM
- [ ] ShopproOrderMapper normalizuje COD na 'CASH_ON_DELIVERY' przy nowych importach
- [ ] Brak hardcoded `=== 'CASH_ON_DELIVERY'` w prepare.php, show.php, OrdersController.php
- [ ] Formularz przesyłki auto-wypełnia kwotę COD dla zamówień shopPRO
- [ ] Brak regresji: zamówienia Allegro nadal działają poprawnie
</verification>
<success_criteria>
- Wszystkie 2 taski ukończone
- Weryfikacja przeszła pomyślnie
- Brak błędów PHP na stronach przygotowania przesyłki i szczegółów zamówienia
</success_criteria>
<output>
After completion, create `.paul/phases/77-cod-amount-fix/77-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,127 @@
---
phase: 77-cod-amount-fix
plan: 01
subsystem: shipments
tags: [cod, payment, stringhelper, shoppro]
requires:
- phase: none
provides: n/a
provides:
- StringHelper::isCodPayment() — centralna detekcja platnosci COD
- Normalizacja COD w ShopproOrderMapper przy imporcie
affects: []
tech-stack:
added: []
patterns: [keyword-based COD detection via StringHelper]
key-files:
created: []
modified:
- src/Core/Support/StringHelper.php
- src/Modules/Settings/ShopproOrderMapper.php
- resources/views/shipments/prepare.php
- resources/views/orders/show.php
- src/Modules/Orders/OrdersController.php
key-decisions:
- "Keyword matching zamiast exact match — shopPRO wysyla pelne polskie nazwy metod platnosci"
- "Dwupoziomowa detekcja: exact match (COD_PAYMENT_TYPES) + keyword search (COD_PAYMENT_KEYWORDS)"
patterns-established:
- "StringHelper::isCodPayment() jako jedyne miejsce detekcji COD w codebase"
duration: 15min
started: 2026-04-07T00:00:00Z
completed: 2026-04-07T00:15:00Z
---
# Phase 77 Plan 01: COD Amount Fix Summary
**Centralna detekcja platnosci COD (StringHelper::isCodPayment) z keyword matching dla shopPRO wartosci typu "Platnosc przy odbiorze"**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~15min |
| Started | 2026-04-07 |
| Completed | 2026-04-07 |
| Tasks | 2 completed |
| Files modified | 5 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: COD auto-fill dla zamowien shopPRO | Pass | Testowane na zamowieniach 188/207 z wartoscia "Platnosc przy odbiorze" |
| AC-2: COD auto-fill dla zamowien Allegro (regresja) | Pass | CASH_ON_DELIVERY nadal rozpoznawane przez exact match |
| AC-3: Detekcja COD w widoku szczegulow zamowienia | Pass | Badge "Za pobraniem" wyswietla sie dla wszystkich wariantow COD |
## Accomplishments
- Centralna metoda `StringHelper::isCodPayment()` z dwupoziomowa detekcja: exact match na 4 warianty + keyword match na 3 frazy (PRZY ODBIORZE, POBRANIEM, POBRANIE)
- Normalizacja COD na `CASH_ON_DELIVERY` w `ShopproOrderMapper` przy nowych importach
- Eliminacja wszystkich hardcoded `=== 'CASH_ON_DELIVERY'` z kodu produkcyjnego (3 pliki)
- Rozszerzenie map etykiet platnosci w `show.php` o warianty COD
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `src/Core/Support/StringHelper.php` | Modified | Dodano COD_PAYMENT_TYPES, COD_PAYMENT_KEYWORDS, isCodPayment() |
| `src/Modules/Settings/ShopproOrderMapper.php` | Modified | normalizeCodPaymentType() + uzycie w mapOrderAggregate |
| `resources/views/shipments/prepare.php` | Modified | Zamiana hardcoded na StringHelper::isCodPayment() |
| `resources/views/orders/show.php` | Modified | Zamiana 2 sprawdzen + rozszerzenie map etykiet o warianty COD |
| `src/Modules/Orders/OrdersController.php` | Modified | Zamiana hardcoded na StringHelper::isCodPayment() |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Keyword matching zamiast samego exact match | shopPRO wysyla pelne polskie nazwy np. "Platnosc przy odbiorze" — nie da sie przewidziec wszystkich wariantow | Odpornosc na nowe warianty nazw COD |
| Dwupoziomowa detekcja (exact + keyword) | Exact match jest szybszy i pewniejszy dla znanych wartosci, keyword jako fallback | Brak false positives dla ONLINE/TRANSFER, elastycznosc dla COD |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 1 | Kluczowe — bez tego fix nie dzialal |
| Scope additions | 0 | - |
| Deferred | 0 | - |
**Total impact:** Konieczne rozszerzenie — plan zakladal 4 warianty exact match, rzeczywistosc wymagala keyword matching.
### Auto-fixed Issues
**1. Wartosc shopPRO to "Platnosc przy odbiorze" — nie ma w liscie exact match**
- **Found during:** Weryfikacja po APPLY (user report)
- **Issue:** Plan zakladal warianty "cod", "pobranie", "za pobraniem". Rzeczywista wartosc w DB to "Platnosc przy odbiorze"
- **Fix:** Dodano COD_PAYMENT_KEYWORDS z keyword matching (str_contains) jako drugi poziom detekcji
- **Files:** src/Core/Support/StringHelper.php, resources/views/orders/show.php
- **Verification:** 9/9 unit testow przeszlo, w tym "Platnosc przy odbiorze" => true
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| Poczatkowy fix nie dzialal — shopPRO wysyla "Platnosc przy odbiorze" nie "cod" | Sprawdzenie DB (orders WHERE id IN (188,207)), dodanie keyword matching |
## Next Phase Readiness
**Ready:**
- StringHelper::isCodPayment() gotowy do uzycia w kolejnych miejscach
- Nowe importy shopPRO beda mialy znormalizowane CASH_ON_DELIVERY
**Concerns:**
- Istniejace zamowienia w DB zachowuja oryginalna wartosc "Platnosc przy odbiorze" — helper obsluguje je w runtime, ale dane nie sa znormalizowane
**Blockers:**
- None
---
*Phase: 77-cod-amount-fix, Plan: 01*
*Completed: 2026-04-07*

View File

@@ -0,0 +1,109 @@
---
phase: 78-preset-auto-submit
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- resources/views/shipments/prepare.php
autonomous: true
---
<objective>
## Goal
Presety przesyłek po autofill automatycznie submitują formularz (kliknięcie "Utwórz przesyłkę").
## Purpose
Aktualnie użytkownik musi kliknąć preset, a potem ręcznie kliknąć "Utwórz przesyłkę". Skoro preset wypełnia wszystkie dane — submit powinien nastąpić automatycznie, oszczędzając jedno kliknięcie.
## Output
- Funkcja `applyPreset()` po wypełnieniu pól automatycznie submituje formularz
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/STATE.md
## Source Files
@resources/views/shipments/prepare.php (linie 957-993: applyPreset + setTimeout 200ms)
</context>
<skills>
No specialized flows configured — optional skills only.
</skills>
<acceptance_criteria>
## AC-1: Auto-submit po kliknięciu presetu
```gherkin
Given formularz przygotowania przesyłki z co najmniej jednym presetem
When użytkownik klika przycisk presetu
Then formularz wypełnia się danymi presetu
And formularz automatycznie się submituje (tworzenie przesyłki)
```
## AC-2: Brak regresji autofill
```gherkin
Given preset z zapisanymi danymi (carrier, wymiary, metoda dostawy)
When preset jest aplikowany
Then wszystkie pola formularza są poprawnie wypełnione przed submitem
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Dodanie auto-submit po applyPreset</name>
<files>resources/views/shipments/prepare.php</files>
<action>
W funkcji `applyPreset()` (linia ~957), wewnątrz istniejącego `setTimeout` (200ms) na końcu callbacku (po `selectDeliveryService(preset)`):
1. Dodaj drugi `setTimeout` (krótki, np. 100ms) po zakończeniu autofill, który:
- Znajduje formularz: `document.getElementById('shipment-form')` lub `document.querySelector('form[action*="shipment/create"]')`
- Wywołuje `form.submit()` lub klika przycisk submit
2. Formularz może nie mieć id — sprawdź czy `<form>` ma id. Jeśli nie, dodaj `id="shipment-form"` do tagu `<form>`.
Uwaga: `selectDeliveryService()` może mieć swój setTimeout — sprawdź czy submit nie nastąpi przed zakończeniem selekcji. Użyj wystarczającego opóźnienia (np. łącznie 400-500ms od kliknięcia presetu).
</action>
<verify>
Przegląd kodu: po applyPreset formularz submituje się automatycznie.
PHP lint: brak błędów składni.
</verify>
<done>AC-1 i AC-2 spełnione — preset autofill + auto-submit</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Logika autofill presetu (kolejność wypełniania pól, setTimeout 200ms)
- Backend ShipmentController (logika tworzenia przesyłki)
- API presetów (/api/shipment-presets/*)
## SCOPE LIMITS
- Tylko auto-submit po kliknięciu presetu
- Nie zmieniamy zachowania przycisku "Utwórz przesyłkę" ani ręcznego formularza
</boundaries>
<verification>
Before declaring plan complete:
- [ ] Kliknięcie presetu wypełnia formularz i submituje go
- [ ] Formularz ma id umożliwiające łatwe znalezienie w JS
- [ ] Brak błędów PHP syntax
- [ ] Ręczne wypełnienie formularza i kliknięcie "Utwórz przesyłkę" nadal działa
</verification>
<success_criteria>
- Task 1 ukończony
- Weryfikacja przeszła
- Brak błędów PHP
</success_criteria>
<output>
After completion, create `.paul/phases/78-preset-auto-submit/78-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,91 @@
---
phase: 78-preset-auto-submit
plan: 01
subsystem: shipments
tags: [presets, autofill, auto-submit, ux]
requires:
- phase: 23-shipment-presets-backend
provides: preset API i autofill
provides:
- Auto-submit formularza po kliknieciu presetu przesylki
affects: []
tech-stack:
added: []
patterns: []
key-files:
created: []
modified:
- resources/views/shipments/prepare.php
key-decisions:
- "500ms laczny delay (200ms autofill + 300ms submit) — wystarczajacy na selectDeliveryService"
patterns-established: []
duration: 5min
started: 2026-04-07T00:00:00Z
completed: 2026-04-07T00:05:00Z
---
# Phase 78 Plan 01: Preset Auto Submit Summary
**Presety przesylek automatycznie submituja formularz po autofill — jedno klikniecie zamiast dwoch**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~5min |
| Started | 2026-04-07 |
| Completed | 2026-04-07 |
| Tasks | 1 completed |
| Files modified | 1 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Auto-submit po kliknieciu presetu | Pass | form.submit() po 500ms od klikniecia |
| AC-2: Brak regresji autofill | Pass | Autofill bez zmian, submit nastepuje po nim |
## Accomplishments
- Dodano `id="shipment-form"` na formularz tworzenia przesylki
- `applyPreset()` po autofill (200ms) czeka 300ms i wywoluje `form.submit()`
- Jedno klikniecie presetu = wypelnienie + utworzenie przesylki
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `resources/views/shipments/prepare.php` | Modified | id na formularzu + auto-submit w applyPreset() |
## Decisions Made
None — plan wykonany zgodnie ze specyfikacja.
## Deviations from Plan
None — plan executed exactly as written.
## Issues Encountered
None.
## Next Phase Readiness
**Ready:**
- Preset flow kompletny: klik → autofill → submit
**Concerns:**
- None
**Blockers:**
- None
---
*Phase: 78-preset-auto-submit, Plan: 01*
*Completed: 2026-04-07*

View File

@@ -0,0 +1,167 @@
---
phase: 79-personalization-message-field
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Modules/Settings/ShopproOrderMapper.php
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
---
<objective>
## Goal
Dodanie pola `message` z API shopPRO do personalizacji produktow w zamowieniach. Aktualnie `extractPersonalization()` sprawdza tylko `attributes` i `custom_fields`, a shopPRO zwraca rowniez pole `message` z wiadomoscia personalizacji klienta (np. "Milenie na pamiatke I Komunii Swietej").
## Purpose
Klienci wpisuja wiadomosci personalizacji przy zamowieniach w shopPRO. Te dane sa kluczowe dla realizacji zamowien (np. grawerunki, dedykacje). Bez ich importu pracownik musi reczne sprawdzac dane w shopPRO.
## Output
- Zaktualizowany `extractPersonalization()` w ShopproOrderMapper — obsluguje pole `message`
- Istniejace zamowienia z `message` w payload_json — backfill personalizacji
- Zaktualizowana dokumentacja
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@src/Modules/Settings/ShopproOrderMapper.php (metoda extractPersonalization, linia ~590)
</context>
<skills>
No specialized flows required for this plan.
</skills>
<acceptance_criteria>
## AC-1: Pole message importowane do personalizacji
```gherkin
Given zamowienie shopPRO z pozycja majaca pole "message" w odpowiedzi API
When pozycja jest importowana/aktualizowana przez ShopproOrderMapper
Then wartosc pola "message" jest zapisana w kolumnie personalization tabeli order_items
```
## AC-2: Laczenie message z attributes i custom_fields
```gherkin
Given pozycja shopPRO majaca zarowno "attributes" jak i "message"
When extractPersonalization przetwarza dane
Then oba pola sa polaczone w personalization oddzielone nowa linia
And pole "message" jest poprzedzone etykieta "Wiadomosc:"
```
## AC-3: Backfill istniejacych zamowien
```gherkin
Given istniejace pozycje zamowien z polem "message" w payload_json ale pustym personalization
When uruchomiona jest migracja/skrypt backfill
Then kolumna personalization zostaje wypelniona danymi z payload_json.message
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Dodanie pola message do extractPersonalization</name>
<files>src/Modules/Settings/ShopproOrderMapper.php</files>
<action>
W metodzie `extractPersonalization()` (linia ~590):
1. Dodac pole `message` do listy sprawdzanych pol OSOBNO po petli attributes/custom_fields
2. Wartosc message powinna byc poprzedzona etykieta "Wiadomosc: " (z dwukropkiem i spacja)
3. Zachowac istniejaca logike czyszczenia HTML (strip_tags, html_entity_decode, trim)
4. Jesli message jest jedynym polem — zwrocic "Wiadomosc: {tresc}"
5. Jesli sa tez attributes/custom_fields — dodac message na koncu po nowej linii
Logika:
```
// Po istniejącej pętli attributes/custom_fields:
$message = $this->readPath($row, ['message']);
if ($message !== null && $message !== '' && $message !== false) {
$text = str_replace(['<br>', '<br/>', '<br />'], "\n", (string) $message);
$text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8');
$text = trim($text);
if ($text !== '') {
$parts[] = 'Wiadomość: ' . $text;
}
}
```
</action>
<verify>
Sprawdzic w kodzie ze extractPersonalization obsluguje 3 pola: attributes, custom_fields, message.
Zweryfikowac ze message jest poprzedzony etykieta "Wiadomosc:".
</verify>
<done>AC-1 i AC-2 satisfied: pole message jest importowane do personalizacji z etykieta</done>
</task>
<task type="auto">
<name>Task 2: Migracja backfill personalizacji z payload_json</name>
<files>database/migrations/20260407_000080_backfill_personalization_message.sql</files>
<action>
Utworzyc migracje SQL ktora:
1. Aktualizuje kolumne personalization dla pozycji majacych message w payload_json
2. Warunek: personalization IS NULL AND payload_json zawiera niepuste pole message
3. Uzyc JSON_UNQUOTE(JSON_EXTRACT(payload_json, '$.message')) do wyciagniecia wartosci
4. Ustawic personalization = CONCAT('Wiadomość: ', extracted_message)
5. Jesli personalization juz istnieje (nie NULL) — nie nadpisywac (dodac do WHERE)
Uwaga: Jezeli pozycja ma tez attributes/custom_fields w payload_json, sam SQL nie zbuduje pelnej personalizacji.
Dla prostoty: backfill dotyczy TYLKO pozycji z pustym personalization.
Pozycje z istniejacym personalization (z attributes/custom_fields) i brakujacym message — pomijamy
(przyszly re-import uzupelni je poprawnie dzieki Task 1).
</action>
<verify>
Uruchomic migracje na bazie i sprawdzic ze pozycje zamowienia #217 maja wypelniona personalizacje z polem Wiadomosc.
</verify>
<done>AC-3 satisfied: istniejace zamowienia maja uzupelniona personalizacje</done>
</task>
<task type="auto">
<name>Task 3: Aktualizacja dokumentacji</name>
<files>DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
1. W ARCHITECTURE.md — zaktualizowac opis ShopproOrderMapper::extractPersonalization o pole message
2. W TECH_CHANGELOG.md — dodac wpis o rozszerzeniu importu personalizacji o pole message
</action>
<verify>Sprawdzic ze dokumenty sa aktualne</verify>
<done>Dokumentacja zaktualizowana</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Logika importu zamowien (OrderImportRepository) — zmiana tylko w mapperze
- Widok show.php — juz obsluguje personalizacje (nl2br), nie wymaga zmian
- Struktura tabeli order_items — kolumna personalization juz istnieje
## SCOPE LIMITS
- Nie zmieniamy sposobu wyswietlania personalizacji w widoku (juz dziala)
- Nie dodajemy nowych kolumn do bazy
- Backfill tylko dla pozycji z pustym personalization (nie nadpisujemy istniejacych)
</boundaries>
<verification>
Before declaring plan complete:
- [ ] extractPersonalization obsluguje pola: attributes, custom_fields, message
- [ ] Pole message jest poprzedzone etykieta "Wiadomosc:"
- [ ] Migracja backfill wykonana pomyslnie
- [ ] Zamowienie #217 wyswietla personalizacje z wiadomosciami
- [ ] Dokumentacja zaktualizowana
</verification>
<success_criteria>
- Nowe zamowienia shopPRO z polem message importuja personalizacje
- Istniejace zamowienia z message w payload_json maja uzupelniona personalizacje
- Brak regresji w imporcie zamowien
</success_criteria>
<output>
After completion, create `.paul/phases/79-personalization-message-field/79-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,117 @@
---
phase: 79-personalization-message-field
plan: 01
subsystem: api
tags: [shoppro, import, personalization, order-notes]
requires:
- phase: 63-order-item-personalization
provides: extractPersonalization z attributes/custom_fields
provides:
- import pola message z API shopPRO do personalizacji pozycji zamowien
- import pola message z API shopPRO do notatek zamowienia (order_notes)
affects: []
tech-stack:
added: []
patterns: []
key-files:
created:
- database/migrations/20260407_000080_backfill_personalization_message.sql
modified:
- src/Modules/Settings/ShopproOrderMapper.php
- resources/views/orders/show.php
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
key-decisions:
- "Pole message na pozycji poprzedzone etykieta 'Wiadomosc:' dla odroznienia od attributes/custom_fields"
- "Pole message na poziomie zamowienia importowane do order_notes jako note_type=message"
- "Usunieto etykiete 'Personalizacja:' z widoku — kolor tla wystarczajacy"
patterns-established: []
duration: ~15min
started: 2026-04-07T00:00:00Z
completed: 2026-04-07T00:15:00Z
---
# Phase 79 Plan 01: Personalization Message Field Summary
**Import pola `message` z API shopPRO do personalizacji pozycji i notatek zamowienia + backfill istniejacych danych**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~15min |
| Tasks | 3 completed (plan) + 2 deviations |
| Files modified | 5 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Pole message importowane do personalizacji | Pass | extractPersonalization sprawdza attributes, custom_fields, message |
| AC-2: Laczenie message z attributes i custom_fields | Pass | message poprzedzony etykieta "Wiadomosc:" |
| AC-3: Backfill istniejacych zamowien | Pass | 21 pozycji + 70 notatek zamowien |
## Accomplishments
- `extractPersonalization()` rozszerzony o pole `message` z etykieta "Wiadomosc:"
- `mapNotes()` rozszerzony o pole `message` na poziomie zamowienia (wiadomosc klienta do sprzedawcy)
- Backfill: 21 pozycji zamowien uzupelnionych o personalizacje, 70 zamowien uzupelnionych o notatki
- Usunieto zbedna etykiete "Personalizacja:" z widoku zamowienia
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `src/Modules/Settings/ShopproOrderMapper.php` | Modified | Dodano pole message do extractPersonalization + mapNotes |
| `database/migrations/20260407_000080_backfill_personalization_message.sql` | Created | Backfill personalizacji i notatek z payload_json |
| `resources/views/orders/show.php` | Modified | Usunieto etykiete "Personalizacja:" |
| `DOCS/ARCHITECTURE.md` | Modified | Opis extractPersonalization z 3 polami |
| `DOCS/TECH_CHANGELOG.md` | Modified | Wpis Phase 79 |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Scope additions | 2 | Niezbedne uzupelnienia wykryte podczas UAT |
**Total impact:** Wiadomosc klienta importowana zarowno na poziomie pozycji jak i zamowienia.
### Scope Additions
**1. Import wiadomosci klienta do order_notes**
- **Found during:** UAT zamowienia #218
- **Issue:** Pole `message` na poziomie zamowienia (wiadomosc klienta do sprzedawcy) nie bylo importowane do sekcji "Wiadomosci i zalaczniki"
- **Fix:** Dodano `'message'` do listy kluczy w `mapNotes()` + backfill 70 zamowien
- **Files:** `src/Modules/Settings/ShopproOrderMapper.php`, migracja SQL
**2. Usuniecie etykiety "Personalizacja:"**
- **Found during:** UAT
- **Issue:** Etykieta zbedna — kolor tla wystarczajaco oznacza sekcje personalizacji
- **Fix:** Usunieto `<span class="item-personalization__label">` z widoku
- **Files:** `resources/views/orders/show.php`
## Issues Encountered
None
## Next Phase Readiness
**Ready:**
- Import shopPRO kompletny — wszystkie pola personalizacji i wiadomosci klienta sa importowane
- Backfill wykonany na produkcji
**Concerns:** None
**Blockers:** None
---
*Phase: 79-personalization-message-field, Plan: 01*
*Completed: 2026-04-07*

View File

@@ -0,0 +1,106 @@
---
phase: 80-status-change-reload
plan: 01
type: execute
wave: 1
depends_on: []
files_modified: [public/assets/js/modules/inline-status-change.js]
autonomous: true
---
<objective>
## Goal
Po zmianie statusu zamowienia inline na liscie zamowien (/orders/list), strona przeladowuje sie automatycznie, dzieki czemu zamowienie znika z aktualnego widoku filtrowanego po statusie.
## Purpose
Gdy uzytkownik filtruje zamowienia po statusie i zmienia status jednego z nich, zamowienie powinno zniknac z listy (bo juz nie pasuje do filtra). Obecnie badge aktualizuje sie w miejscu, ale zamowienie pozostaje na liscie co jest mylace.
## Output
Zmodyfikowany `public/assets/js/modules/inline-status-change.js` z przeladowaniem strony po udanej zmianie statusu.
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@public/assets/js/modules/inline-status-change.js
</context>
<acceptance_criteria>
## AC-1: Przeladowanie listy po zmianie statusu
```gherkin
Given uzytkownik jest na liscie zamowien z aktywnym filtrem statusu
When zmienia status zamowienia przez inline dropdown
Then strona przeladowuje sie po udanej odpowiedzi serwera
And zamowienie z nowym statusem nie pojawia sie na liscie (bo filtr go wyklucza)
```
## AC-2: Brak przeladowania przy bledzie
```gherkin
Given uzytkownik zmienia status zamowienia inline
When serwer zwraca blad (np. 500 lub validation error)
Then strona NIE przeladowuje sie
And badge wraca do poprzedniego statusu
And wyswietla sie komunikat bledu
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Dodanie location.reload() po udanej zmianie statusu</name>
<files>public/assets/js/modules/inline-status-change.js</files>
<action>
W funkcji `changeStatus()`, w bloku `.then(function (result) {...})`:
- Po linii 153-154 (po udanej aktualizacji badge'a), dodac `location.reload()`.
- Reload powinien nastapic TYLKO gdy `result.ok && result.data.success`.
- Blok error (linie 142-149) i `.catch()` (linie 156-162) pozostaja bez zmian — brak reloadu przy bledzie.
- Mozna usunac aktualizacje badge'a (linie 152-154) bo reload i tak odswieza strone, ale lepiej zostawic dla plynnosci UX — uzytkownik widzi natychmiastowa zmiane badge'a, potem reload.
</action>
<verify>
1. Otworz /orders/list z filtrem statusu (np. "Nowe")
2. Zmien status zamowienia na inny (np. "W realizacji")
3. Strona przeladowuje sie i zamowienie znika z listy
4. Zmien status bez filtra — strona tez sie przeladowuje (zamowienie pojawia sie z nowym statusem)
</verify>
<done>AC-1 i AC-2 spelnione: reload po sukcesie, brak reloadu przy bledzie</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- src/Modules/Orders/OrdersController.php (backend endpoint dziala poprawnie)
- resources/views/orders/list.php (konfiguracja JS jest OK)
- routes/web.php
## SCOPE LIMITS
- Nie dodawac AJAX-owego odswiezania listy (pelny reload jest prostszy i wystarczajacy)
- Nie zmieniac logiki dropdowna ani budowania badge'y
- Nie zmieniac obslugi bledow
</boundaries>
<verification>
Before declaring plan complete:
- [ ] Po zmianie statusu z filtrem — zamowienie znika z listy
- [ ] Po zmianie statusu bez filtra — zamowienie ma nowy status po reload
- [ ] Przy bledzie serwera — brak reloadu, badge wraca, komunikat bledu
- [ ] Dropdown dziala jak wczesniej (otwieranie, zamykanie, Escape)
</verification>
<success_criteria>
- Task 1 ukonczony
- Wszystkie verification checks przechodzą
- Brak regresji w inline status change
</success_criteria>
<output>
After completion, create `.paul/phases/80-status-change-reload/80-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,81 @@
---
phase: 80-status-change-reload
plan: 01
subsystem: ui
tags: [javascript, ajax, orders-list, inline-status]
requires:
- phase: 44-inline-status-change
provides: inline status change dropdown on orders list
provides:
- page reload after successful inline status change
affects: []
tech-stack:
added: []
patterns: []
key-files:
created: []
modified: [public/assets/js/modules/inline-status-change.js]
key-decisions: []
patterns-established: []
duration: 2min
started: 2026-04-07T00:00:00Z
completed: 2026-04-07T00:00:00Z
---
# Phase 80 Plan 01: Status Change Reload Summary
**Dodanie `location.reload()` po udanej zmianie statusu inline na liscie zamowien — zamowienie znika z filtrowanego widoku.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | 2min |
| Tasks | 1 completed |
| Files modified | 1 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Przeladowanie listy po zmianie statusu | Pass | `location.reload()` po sukcesie AJAX |
| AC-2: Brak przeladowania przy bledzie | Pass | Bloki error/catch bez zmian — revert badge + alert |
## Accomplishments
- Dodano `location.reload()` w `changeStatus()` po udanej odpowiedzi serwera (1 linia)
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `public/assets/js/modules/inline-status-change.js` | Modified | Dodano reload po udanej zmianie statusu |
## Deviations from Plan
None — plan executed exactly as written.
## Issues Encountered
None.
## Next Phase Readiness
**Ready:**
- Inline status change z reloadem dziala poprawnie
**Concerns:**
- None
**Blockers:**
- None
---
*Phase: 80-status-change-reload, Plan: 01*
*Completed: 2026-04-07*

View File

@@ -0,0 +1,207 @@
---
phase: 81-global-search-bar
plan: 01
type: execute
wave: 1
depends_on: []
files_modified: [src/Modules/Orders/OrdersRepository.php, src/Modules/Orders/OrdersController.php, routes/web.php, resources/views/layouts/app.php, public/assets/js/modules/global-search.js, resources/scss/components/_global-search.scss, resources/scss/app.scss]
autonomous: true
---
<objective>
## Goal
Globalna wyszukiwarka zamowien widoczna w topbarze na kazdej stronie orderPRO. Wyszukuje po: numerze zamowienia, nazwisku klienta, e-mailu, telefonie, nazwie produktu. Wyniki pojawiaja sie jako dropdown pod polem wyszukiwania. Klikniecie wyniku przenosi do /orders/{id}.
## Purpose
Szybki dostep do zamowienia z dowolnego miejsca w aplikacji — bez koniecznosci przechodzenia na liste zamowien i ustawiania filtrow. Analogicznie do wyszukiwarki w shopPRO.
## Output
- Nowy endpoint GET /api/orders/search?q=...&limit=10
- Pole wyszukiwania w topbarze (layout app.php)
- Modul JS global-search.js (debounced AJAX, dropdown wynikow, nawigacja klawiaturowa)
- Style SCSS dla komponentu wyszukiwarki
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@src/Modules/Orders/OrdersRepository.php (linie 91-106 — istniejacy search SQL)
@src/Modules/Orders/OrdersController.php (index() linie 44-71)
@routes/web.php (linie 408-413 — order routes)
@resources/views/layouts/app.php (linie 121-136 — topbar)
</context>
<acceptance_criteria>
## AC-1: Pole wyszukiwania widoczne w topbarze
```gherkin
Given uzytkownik jest zalogowany i na dowolnej stronie orderPRO
When strona sie laduje
Then w topbarze widoczne jest pole wyszukiwania z placeholderem "Szukaj zamowien..."
And pole jest miedzy hamburgerem a sekcja uzytkownika
```
## AC-2: Wyszukiwanie AJAX z debounce
```gherkin
Given uzytkownik wpisuje tekst w pole wyszukiwania (min. 2 znaki)
When minie 300ms od ostatniego znaku (debounce)
Then wykonywane jest zapytanie GET /api/orders/search?q=tekst&limit=10
And pod polem pojawia sie dropdown z wynikami
```
## AC-3: Wyniki wyszukiwania
```gherkin
Given backend znalazl pasujace zamowienia
When dropdown jest wyswietlany
Then kazdy wynik pokazuje: numer zamowienia, nazwe klienta, e-mail, telefon
And wyniki sa ograniczone do max 10
And jesli brak wynikow, wyswietla "Brak wynikow"
```
## AC-4: Nawigacja do zamowienia
```gherkin
Given dropdown z wynikami jest widoczny
When uzytkownik klika na wynik
Then zostaje przeniesiony do /orders/{id} (strona szczegulow zamowienia)
```
## AC-5: Nawigacja klawiaturowa
```gherkin
Given dropdown z wynikami jest widoczny
When uzytkownik uzywa strzalek gora/dol
Then podswietlony wynik sie zmienia
When uzytkownik naciska Enter na podswietlonym wyniku
Then zostaje przeniesiony do /orders/{id}
When uzytkownik naciska Escape
Then dropdown sie zamyka
```
## AC-6: Zamykanie dropdowna
```gherkin
Given dropdown z wynikami jest widoczny
When uzytkownik klika poza polem wyszukiwania i dropdownem
Then dropdown sie zamyka
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Backend — endpoint quickSearch</name>
<files>src/Modules/Orders/OrdersRepository.php, src/Modules/Orders/OrdersController.php, routes/web.php</files>
<action>
1. W `OrdersRepository.php` dodac metode `quickSearch(string $query, int $limit = 10): array`:
- SQL: SELECT o.id, o.source_order_id, o.external_order_id, a.name AS buyer_name, a.email AS buyer_email, a.phone AS buyer_phone
- FROM orders o LEFT JOIN order_addresses a ON a.order_id = o.id AND a.address_type = 'customer'
- WHERE (o.source_order_id LIKE :s OR o.external_order_id LIKE :s OR a.name LIKE :s OR a.email LIKE :s OR a.phone LIKE :s OR EXISTS (SELECT 1 FROM order_items oi WHERE oi.order_id = o.id AND oi.original_name LIKE :s))
- ORDER BY o.ordered_at DESC LIMIT :limit
- Uzyc prepared statements (Medoo raw lub PDO)
- Zwracac tablice z id, order_number (source_order_id lub external_order_id), buyer_name, buyer_email, buyer_phone
2. W `OrdersController.php` dodac metode `quickSearch(Request $request): Response`:
- Pobrac q = $request->input('q', '') — jesli pusty lub < 2 znaki, zwrocic pusty JSON []
- Pobrac limit = min((int)$request->input('limit', 10), 20)
- Wywolac $this->orders->quickSearch($q, $limit)
- Zwrocic Response::json(['results' => $results])
- Sprawdzic header X-Requested-With = XMLHttpRequest (opcjonalne, ale dobre praktyki)
3. W `routes/web.php` dodac route:
- $router->get('/api/orders/search', [$ordersController, 'quickSearch'], [$authMiddleware]);
- Dodac po istniejacych order routes (po linii ~413)
</action>
<verify>
Recznie wywolac GET /api/orders/search?q=test z przegladarki (zalogowany) — odpowiedz JSON z results array
</verify>
<done>AC-2 (backend), AC-3 (dane wynikow) spelnione</done>
</task>
<task type="auto">
<name>Task 2: Frontend — search bar w topbarze + JS + SCSS</name>
<files>resources/views/layouts/app.php, public/assets/js/modules/global-search.js, resources/scss/components/_global-search.scss, resources/scss/app.scss</files>
<action>
1. W `resources/views/layouts/app.php` w topbarze (miedzy hamburgerem a sekcja uzytkownika):
- Dodac div.global-search z:
- input type="text" z id="js-global-search" placeholder="Szukaj zamowien..." autocomplete="off"
- div.global-search__results (pusty, do wynikow)
- Na dole strony (przed zamknieciem body) dodac script include dla global-search.js
2. Stworzyc `public/assets/js/modules/global-search.js`:
- IIFE, 'use strict'
- Debounce 300ms na input event
- Min. 2 znaki do wyszukiwania
- Fetch GET /api/orders/search?q=...&limit=10 z header X-Requested-With: XMLHttpRequest
- Renderowanie wynikow w .global-search__results:
- Kazdy wynik: link <a href="/orders/{id}"> z numerem zamowienia, nazwiskiem, emailem, telefonem
- Jesli brak wynikow: div "Brak wynikow"
- Nawigacja klawiaturowa: ArrowDown/ArrowUp zmienia podswietlenie, Enter nawiguje, Escape zamyka
- Klik poza komponentem zamyka dropdown (document click listener)
- Przy pustym/krotkim query — zamknij dropdown
3. Stworzyc `resources/scss/components/_global-search.scss`:
- .global-search: flex: 1, max-width: 500px, position: relative, margin: 0 1rem
- .global-search input: szerokosc 100%, border-radius, padding, font-size
- .global-search__results: position absolute, top 100%, left 0, width 100%, background white, border, border-radius, box-shadow, max-height 400px, overflow-y auto, z-index 1000
- .global-search__item: padding, cursor pointer, border-bottom, hover/active background
- .global-search__item.is-highlighted: background podswietlenia
- .global-search__item-title: font-weight bold (numer zamowienia)
- .global-search__item-details: font-size mniejszy, color szary (nazwisko, email, telefon)
- .global-search__empty: padding, text-align center, color szary
- Responsywnosc: na mobile input moze byc mniejszy lub ikona lupa rozwijajaca pole
4. W `resources/scss/app.scss` dodac @import 'components/global-search'
5. Zbudowac SCSS do CSS (jesli istnieje build command)
</action>
<verify>
1. Otworz dowolna strone orderPRO — pole wyszukiwania widoczne w topbarze
2. Wpisz nazwe klienta — po 300ms dropdown z wynikami
3. Kliknij wynik — przeniesienie do /orders/{id}
4. Strzalki gora/dol + Enter — nawigacja klawiaturowa
5. Escape lub klik poza — zamkniecie dropdowna
</verify>
<done>AC-1, AC-2 (frontend), AC-3, AC-4, AC-5, AC-6 spelnione</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- src/Modules/Orders/OrdersRepository.php — istniejaca metoda paginate() i buildPaginateFilters() (nie modyfikowac)
- resources/views/orders/list.php — istniejaca wyszukiwarka na liscie zamowien
- public/assets/js/modules/inline-status-change.js
- database/migrations/*
## SCOPE LIMITS
- Wyszukiwanie TYLKO zamowien (nie produktow — nie ma jeszcze modulu produktow w orderPRO)
- Brak historii wyszukiwan
- Brak cache wynikow
- Nie zmieniac istniejacego wyszukiwania na liscie zamowien
</boundaries>
<verification>
Before declaring plan complete:
- [ ] GET /api/orders/search?q=test zwraca JSON z wynikami
- [ ] Pole wyszukiwania widoczne w topbarze na kazdej stronie
- [ ] Dropdown wynikow pojawia sie po wpisaniu 2+ znakow
- [ ] Klikniecie wyniku przenosi do /orders/{id}
- [ ] Nawigacja klawiaturowa (strzalki, Enter, Escape) dziala
- [ ] Klik poza zamyka dropdown
- [ ] Na mobile pole wyszukiwania nie psuje layoutu topbara
</verification>
<success_criteria>
- Oba taski ukonczone
- Wszystkie verification checks przechodzą
- Brak regresji w istniejacym UI
</success_criteria>
<output>
After completion, create `.paul/phases/81-global-search-bar/81-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,98 @@
---
phase: 81-global-search-bar
plan: 01
subsystem: ui
tags: [javascript, ajax, search, orders, topbar]
requires:
- phase: 44-inline-status-change
provides: orders list infrastructure
provides:
- global search bar in topbar
- GET /api/orders/search endpoint
- keyboard-navigable search dropdown
affects: []
tech-stack:
added: []
patterns: [debounced AJAX search with dropdown results]
key-files:
created: [public/assets/js/modules/global-search.js, resources/scss/modules/_global-search.scss]
modified: [src/Modules/Orders/OrdersRepository.php, src/Modules/Orders/OrdersController.php, routes/web.php, resources/views/layouts/app.php, resources/scss/app.scss, public/assets/css/app.css]
key-decisions: []
patterns-established:
- "Global search: debounced AJAX fetch to /api/orders/search with dropdown rendering"
duration: 5min
started: 2026-04-07T00:00:00Z
completed: 2026-04-07T00:00:00Z
---
# Phase 81 Plan 01: Global Search Bar Summary
**Globalna wyszukiwarka zamowien w topbarze — AJAX search z debounce, dropdown wynikow, nawigacja klawiaturowa, przeszukiwanie po numerze/nazwisku/email/telefonie/produkcie.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | 5min |
| Tasks | 2 completed |
| Files modified | 8 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Pole wyszukiwania w topbarze | Pass | Input miedzy hamburgerem a user info |
| AC-2: Wyszukiwanie AJAX z debounce | Pass | 300ms debounce, min 2 znaki |
| AC-3: Wyniki wyszukiwania | Pass | Numer, nazwisko, email, telefon; max 10; "Brak wynikow" |
| AC-4: Nawigacja do zamowienia | Pass | Klik na wynik → /orders/{id} |
| AC-5: Nawigacja klawiaturowa | Pass | ArrowUp/Down, Enter, Escape |
| AC-6: Zamykanie dropdowna | Pass | Klik poza zamyka |
## Accomplishments
- Nowy endpoint `GET /api/orders/search?q=...&limit=10` z prepared statements
- Metoda `quickSearch()` w OrdersRepository szuka po 6 polach (source_order_id, external_order_id, name, email, phone, product name)
- Modul JS global-search.js z debounce, dropdown, nawigacja klawiaturowa
- Responsywne style SCSS
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `src/Modules/Orders/OrdersRepository.php` | Modified | Nowa metoda `quickSearch()` |
| `src/Modules/Orders/OrdersController.php` | Modified | Nowa metoda `quickSearch()` |
| `routes/web.php` | Modified | Route GET /api/orders/search |
| `resources/views/layouts/app.php` | Modified | Pole wyszukiwania w topbarze + script include |
| `public/assets/js/modules/global-search.js` | Created | Modul JS (debounce, dropdown, klawiatura) |
| `resources/scss/modules/_global-search.scss` | Created | Style komponentu wyszukiwarki |
| `resources/scss/app.scss` | Modified | Import nowego modulu SCSS |
| `public/assets/css/app.css` | Modified | Zbudowany CSS |
## Deviations from Plan
None — plan executed exactly as written.
## Issues Encountered
None.
## Next Phase Readiness
**Ready:**
- Wyszukiwarka globalna dziala na kazdej stronie
**Concerns:**
- None
**Blockers:**
- None
---
*Phase: 81-global-search-bar, Plan: 01*
*Completed: 2026-04-07*

View File

@@ -0,0 +1,111 @@
---
phase: 82-product-title-tooltip
plan: 01
type: execute
wave: 1
depends_on: []
files_modified: [src/Modules/Orders/OrdersController.php]
autonomous: true
---
<objective>
## Goal
Dodanie natywnego tooltipa (atrybut `title`) do uciętych nazw produktów na liście zamówień, aby po najechaniu myszką wyświetlała się pełna nazwa.
## Purpose
Na liście zamówień nazwy produktów są obcinane przez `text-overflow: ellipsis`. Użytkownik nie ma możliwości zobaczenia pełnej nazwy bez wchodzenia w szczegóły zamówienia. Tooltip rozwiązuje ten problem minimalnym kosztem.
## Output
Zmodyfikowany `OrdersController::productsHtml()` — atrybut `title` na elemencie `.orders-product__name`.
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@src/Modules/Orders/OrdersController.php (metoda productsHtml(), linia 664)
</context>
<skills>
No specialized flows required.
</skills>
<acceptance_criteria>
## AC-1: Tooltip z pełną nazwą produktu
```gherkin
Given lista zamówień z produktem o długiej nazwie uciętej przez ellipsis
When użytkownik najedzie kursorem na uciętą nazwę produktu
Then pojawia się natywny tooltip przeglądarki z pełną nazwą produktu
```
## AC-2: Brak tooltipa dla pustych nazw
```gherkin
Given produkt bez nazwy (wyświetlany jako "-")
When użytkownik najedzie kursorem na "-"
Then nie pojawia się tooltip (brak atrybutu title lub pusty)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Dodanie atrybutu title do .orders-product__name</name>
<files>src/Modules/Orders/OrdersController.php</files>
<action>
W metodzie `productsHtml()`, linia 664, dodać atrybut `title` do `<div class="orders-product__name">`:
Zmienić:
```php
. '<div class="orders-product__name">' . htmlspecialchars($name !== '' ? $name : '-', ENT_QUOTES, 'UTF-8') . '</div>'
```
Na:
```php
. '<div class="orders-product__name"' . ($name !== '' ? ' title="' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '"' : '') . '>' . htmlspecialchars($name !== '' ? $name : '-', ENT_QUOTES, 'UTF-8') . '</div>'
```
- Atrybut `title` dodawany tylko gdy nazwa nie jest pusta
- Wartość `title` escapowana przez `htmlspecialchars` (XSS safety)
- Nie zmieniać żadnej innej logiki metody
</action>
<verify>Otworzyć /orders/list, najechać na uciętą nazwę produktu — powinien pojawić się tooltip z pełną nazwą</verify>
<done>AC-1 i AC-2 spełnione: tooltip pokazuje pełną nazwę; brak tooltipa dla pustych nazw</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- resources/scss/* (styl truncacji zostaje bez zmian)
- resources/views/orders/* (widoki bez zmian)
- Logika budowania `$itemsPreview` i reszta metody `productsHtml()`
## SCOPE LIMITS
- Tylko natywny tooltip HTML (`title`), bez custom JS tooltip library
- Tylko lista zamówień — nie strona szczegółów
</boundaries>
<verification>
Before declaring plan complete:
- [ ] Atrybut `title` obecny w renderowanym HTML produktów z długą nazwą
- [ ] Brak atrybutu `title` dla produktów bez nazwy
- [ ] Brak regresji — lista zamówień renderuje się poprawnie
- [ ] Wszystkie acceptance criteria spełnione
</verification>
<success_criteria>
- Tooltip z pełną nazwą produktu widoczny po hover na liście zamówień
- Brak zmian CSS ani JS
- Brak regresji w renderowaniu listy
</success_criteria>
<output>
After completion, create `.paul/phases/82-product-title-tooltip/82-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,84 @@
---
phase: 82-product-title-tooltip
plan: 01
subsystem: ui
tags: [tooltip, orders-list, ux]
requires: []
provides:
- Natywny tooltip z pelna nazwa produktu na liscie zamowien
affects: []
tech-stack:
added: []
patterns: []
key-files:
created: []
modified: [src/Modules/Orders/OrdersController.php]
key-decisions: []
patterns-established: []
duration: 2min
started: 2026-04-07T00:00:00Z
completed: 2026-04-07T00:00:00Z
---
# Phase 82 Plan 01: Product Title Tooltip Summary
**Dodano atrybut `title` do elementu `.orders-product__name` w metodzie `productsHtml()` — natywny tooltip przegladarki z pelna nazwa produktu na liscie zamowien.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~2min |
| Tasks | 1 completed |
| Files modified | 1 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Tooltip z pelna nazwa produktu | Pass | Atrybut `title` dodany z escapowana nazwa |
| AC-2: Brak tooltipa dla pustych nazw | Pass | Warunek `$name !== ''` pomija pusty title |
## Accomplishments
- Dodano atrybut `title` do `<div class="orders-product__name">` z pelna nazwa produktu (XSS-safe przez `htmlspecialchars`)
- Tooltip pojawia sie tylko gdy nazwa nie jest pusta
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `src/Modules/Orders/OrdersController.php` | Modified | Dodano atrybut `title` w metodzie `productsHtml()` linia 664 |
## Decisions Made
None - followed plan as specified
## Deviations from Plan
None - plan executed exactly as written
## Issues Encountered
None
## Next Phase Readiness
**Ready:**
- Tooltip dziala natywnie, zero zaleznosci JS/CSS
**Concerns:**
- None
**Blockers:**
- None
---
*Phase: 82-product-title-tooltip, Plan: 01*
*Completed: 2026-04-07*