update
This commit is contained in:
@@ -86,6 +86,9 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
|||||||
- [x] Przeladowanie listy zamowien po zmianie statusu inline (location.reload) — Phase 80
|
- [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] 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
|
- [x] Tooltip z pelna nazwa produktu na liscie zamowien (natywny title attribute) — Phase 82
|
||||||
|
- [x] Dedykowane pull mapowanie statusow Allegro przy imporcie zamowien — Phase 83
|
||||||
|
- [x] Automatyzacja: zdarzenie `order.imported` przy pobraniu zamowienia (Allegro + shopPRO) — Phase 84
|
||||||
|
- [x] Filtrowanie zamowien po grupie statusow (klikalna nazwa grupy na panelu) — Phase 85
|
||||||
- [ ] Eliminacja zduplikowanego kodu: SslCertificateResolver, ToggleableRepositoryTrait, RedirectPathResolver, ReceiptService — Phase 68
|
- [ ] Eliminacja zduplikowanego kodu: SslCertificateResolver, ToggleableRepositoryTrait, RedirectPathResolver, ReceiptService — Phase 68
|
||||||
|
|
||||||
### Active (In Progress)
|
### Active (In Progress)
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel
|
|||||||
| 80 | Status Change Reload | 1/1 | Complete |
|
| 80 | Status Change Reload | 1/1 | Complete |
|
||||||
| 81 | Global Search Bar | 1/1 | Complete |
|
| 81 | Global Search Bar | 1/1 | Complete |
|
||||||
| 82 | Product Title Tooltip | 1/1 | Complete |
|
| 82 | Product Title Tooltip | 1/1 | Complete |
|
||||||
|
| 83 | Allegro Pull Status Mapping | 1/1 | Complete |
|
||||||
|
| 84 | Order Imported Automation Event | 1/1 | Complete |
|
||||||
|
| 85 | Status Group Filter | 1/1 | Complete |
|
||||||
| TBD | Mobile Orders List | - | Not started |
|
| TBD | Mobile Orders List | - | Not started |
|
||||||
| TBD | Mobile Order Details | - | Not started |
|
| TBD | Mobile Order Details | - | Not started |
|
||||||
| TBD | Mobile Settings | - | Not started |
|
| TBD | Mobile Settings | - | Not started |
|
||||||
|
|||||||
@@ -5,19 +5,19 @@
|
|||||||
See: .paul/PROJECT.md (updated 2026-04-07)
|
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.
|
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
|
||||||
**Current focus:** Milestone v3.0 - Phase 82 complete, ready for next PLAN
|
**Current focus:** Milestone v3.0 - Phase 85 complete, ready for next PLAN
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone: v3.0 Mobile Responsive - In progress
|
Milestone: v3.0 Mobile Responsive - In progress
|
||||||
Phase: 82 (Product Title Tooltip) — Complete
|
Phase: 85 (Status Group Filter) — Complete
|
||||||
Plan: 82-01 unified
|
Plan: 85-01 unified
|
||||||
Status: Loop complete, ready for next PLAN
|
Status: Loop complete, ready for next PLAN
|
||||||
Last activity: 2026-04-07 — Unified .paul/phases/82-product-title-tooltip/82-01-PLAN.md
|
Last activity: 2026-04-07 — Unified .paul/phases/85-status-group-filter/85-01-PLAN.md
|
||||||
|
|
||||||
Progress:
|
Progress:
|
||||||
- Milestone: [########..] ~89%
|
- Milestone: [#########.] ~92%
|
||||||
- Phase 82: [##########] 100%
|
- Phase 85: [##########] 100%
|
||||||
|
|
||||||
## Loop Position
|
## Loop Position
|
||||||
|
|
||||||
@@ -30,11 +30,11 @@ PLAN ──▶ APPLY ──▶ UNIFY
|
|||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-07
|
Last session: 2026-04-07
|
||||||
Stopped at: Plan 82-01 unified
|
Stopped at: Plan 85-01 unified
|
||||||
Next action: Run /paul:plan for the next prioritized phase
|
Next action: Run /paul:plan for the next prioritized phase
|
||||||
Resume file: .paul/phases/82-product-title-tooltip/82-01-SUMMARY.md
|
Resume file: .paul/phases/85-status-group-filter/85-01-SUMMARY.md
|
||||||
|
|
||||||
## Git State
|
## Git State
|
||||||
|
|
||||||
Last commit: 1933c74
|
Last commit: 8fa9ca6
|
||||||
Branch: main
|
Branch: main
|
||||||
|
|||||||
16
.paul/changelog/2026-04-07.md
Normal file
16
.paul/changelog/2026-04-07.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# 2026-04-07
|
||||||
|
|
||||||
|
## Co zrobiono
|
||||||
|
|
||||||
|
- [Phase 85, Plan 01] Filtrowanie zamowien po grupie statusow
|
||||||
|
- Klikalna nazwa grupy na panelu statusow z licznikiem sumy zamowien
|
||||||
|
- Backend: parametr status_group z filtrem IN() w repozytorium
|
||||||
|
- Frontend: hover effect, count badge, active state z border-left
|
||||||
|
|
||||||
|
## Zmienione pliki
|
||||||
|
|
||||||
|
- `src/Modules/Orders/OrdersRepository.php`
|
||||||
|
- `src/Modules/Orders/OrdersController.php`
|
||||||
|
- `resources/views/components/order-status-panel.php`
|
||||||
|
- `resources/scss/app.scss`
|
||||||
|
- `public/assets/css/app.css`
|
||||||
6
.paul/governance/governance_2026-04-07.jsonl
Normal file
6
.paul/governance/governance_2026-04-07.jsonl
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{"ts":"2026-04-07T20:38:22Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\phases\\\\85-status-group-filter\\\\85-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-04-07T20:38:28Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-04-07T20:38:31Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-04-07T20:38:45Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-04-07T20:38:46Z","tool":"Bash","cmd":"mkdir -p '/c/visual studio code/projekty/orderPRO/.paul/changelog'\",\"description\":\"Create changelog directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage\","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-04-07T20:38:54Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-04-07.md","cwd":"/c/visual studio code/projekty/orderPRO"}
|
||||||
186
.paul/phases/83-allegro-pull-status-mapping/83-01-PLAN.md
Normal file
186
.paul/phases/83-allegro-pull-status-mapping/83-01-PLAN.md
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
---
|
||||||
|
phase: 83-allegro-pull-status-mapping
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- database/migrations/20260407_000083_allegro_pull_status_mappings.sql
|
||||||
|
- src/Modules/Settings/AllegroPullStatusMappingRepository.php
|
||||||
|
- src/Modules/Settings/AllegroOrderImportService.php
|
||||||
|
- src/Modules/Settings/AllegroIntegrationsController.php
|
||||||
|
- src/Modules/Cron/CronHandlerFactory.php
|
||||||
|
- resources/views/settings/allegro.php
|
||||||
|
- resources/lang/pl.php
|
||||||
|
- routes/web.php
|
||||||
|
autonomous: true
|
||||||
|
delegation: off
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Dodac dedykowana tabele pull mappings dla statusow Allegro i uzyc jej przy imporcie zamowien — analogicznie do Phase 75 (shopPRO pull status mapping). Zamowienia z Allegro nie beda juz importowane z surowymi statusami (np. "new") gdy brakuje mapowania.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Obecnie `AllegroOrderImportService` szuka mapowania w tabeli `allegro_order_status_mappings` ktora ma UNIQUE na `orderpro_status_code` (kierunek push). Gdy status Allegro (np. "new") nie ma mapowania, surowy status trafia do `external_status_id`. To ten sam bug co w shopPRO naprawiony w Phase 75.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Nowa tabela `allegro_order_status_pull_mappings`
|
||||||
|
- Nowa klasa `AllegroPullStatusMappingRepository`
|
||||||
|
- Import Allegro uzywa pull mappings zamiast reverse-lookup
|
||||||
|
- UI w Ustawienia > Allegro > Statusy ma sekcje push i pull
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/ROADMAP.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
|
||||||
|
## Prior Work
|
||||||
|
@.paul/phases/75-pull-status-mapping/75-01-SUMMARY.md — wzorzec pull mapping dla shopPRO (analogiczny pattern)
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@src/Modules/Settings/AllegroOrderImportService.php — linie 135-139: obecny lookup statusu
|
||||||
|
@src/Modules/Settings/AllegroStatusMappingRepository.php — findMappedOrderproStatusCode() i buildOrderproToAllegroMap()
|
||||||
|
@src/Modules/Settings/AllegroIntegrationsController.php — zarzadzanie mapowaniem statusow
|
||||||
|
@src/Modules/Settings/ShopproPullStatusMappingRepository.php — wzorzec do nasledowania
|
||||||
|
@src/Modules/Orders/OrderImportRepository.php — upsertOrderAggregate() z ochrona statusu
|
||||||
|
@database/migrations/20260407_000079_pull_status_mappings.sql — wzorzec migracji shopPRO
|
||||||
|
@resources/views/settings/allegro.php — widok ustawien Allegro
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: Nowa tabela pull mappings
|
||||||
|
```gherkin
|
||||||
|
Given baza danych z istniejaca tabela allegro_order_status_mappings
|
||||||
|
When migracja 000083 zostanie wykonana
|
||||||
|
Then istnieje tabela allegro_order_status_pull_mappings z kolumnami: id, allegro_status_code, orderpro_status_code, created_at, updated_at
|
||||||
|
And UNIQUE constraint na allegro_status_code (jeden status orderPRO per status Allegro)
|
||||||
|
And tabela jest pre-populated z istniejacych mappingow (reverse z push table)
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: Import uzywa pull mappings
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie Allegro ze statusem "new" i istniejace mapowanie pull: new -> nowe
|
||||||
|
When AllegroOrderImportService importuje zamowienie
|
||||||
|
Then external_status_id = "nowe" (zmapowany status orderPRO)
|
||||||
|
And surowy status "new" jest zachowany w preferences_json.allegro_status_raw
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: Fallback dla niezmapowanych statusow
|
||||||
|
```gherkin
|
||||||
|
Given zamowienie Allegro ze statusem nieznanym (brak w pull mappings)
|
||||||
|
When AllegroOrderImportService importuje zamowienie
|
||||||
|
Then external_status_id = surowy status Allegro (fallback)
|
||||||
|
And status jest auto-discovered w tabeli pull (z NULL orderpro_status_code)
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-4: Sekcja pull mapping w UI
|
||||||
|
```gherkin
|
||||||
|
Given uzytkownik otwiera Ustawienia > Allegro > Statusy
|
||||||
|
When strona sie zaladuje
|
||||||
|
Then widoczne sa dwie sekcje: "Wysylka statusow" (push: orderPRO->Allegro) i "Mapowanie przy imporcie" (pull: Allegro->orderPRO)
|
||||||
|
And kazda sekcja ma wlasny formularz zapisu
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Migracja DB + AllegroPullStatusMappingRepository</name>
|
||||||
|
<files>database/migrations/20260407_000083_allegro_pull_status_mappings.sql, src/Modules/Settings/AllegroPullStatusMappingRepository.php</files>
|
||||||
|
<action>
|
||||||
|
1. Utworzyc migracje SQL:
|
||||||
|
- CREATE TABLE allegro_order_status_pull_mappings (id INT AUTO_INCREMENT PK, allegro_status_code VARCHAR(100) NOT NULL, orderpro_status_code VARCHAR(100) DEFAULT NULL, created_at DATETIME, updated_at DATETIME)
|
||||||
|
- UNIQUE KEY na allegro_status_code
|
||||||
|
- INSERT ... SELECT z allegro_order_status_mappings (reverse: allegro_status_code -> orderpro_status_code) — pre-populate
|
||||||
|
2. Utworzyc AllegroPullStatusMappingRepository analogicznie do ShopproPullStatusMappingRepository:
|
||||||
|
- findMappedStatusCode(string $allegroStatusCode): ?string — lookup pull mapping
|
||||||
|
- upsertDiscoveredStatus(string $allegroStatusCode): void — auto-discover nowych statusow
|
||||||
|
- getAll(): array — pobranie wszystkich mappingow
|
||||||
|
- save(array $mappings): void — zapis z UI
|
||||||
|
Wzorzec: ShopproPullStatusMappingRepository (bez integration_id — Allegro nie uzywa multi-integration)
|
||||||
|
Avoid: nie dodawac integration_id — tabela Allegro mappings jest globalna (nie per-integration)
|
||||||
|
</action>
|
||||||
|
<verify>Migracja SQL parsuje sie poprawnie; klasa kompiluje sie bez bledow skladni PHP</verify>
|
||||||
|
<done>AC-1 satisfied: tabela istnieje z UNIQUE na allegro_status_code, pre-populated</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Import Allegro uzywa pull mappings</name>
|
||||||
|
<files>src/Modules/Settings/AllegroOrderImportService.php, src/Modules/Cron/CronHandlerFactory.php</files>
|
||||||
|
<action>
|
||||||
|
1. W AllegroOrderImportService dodac zaleznosc AllegroPullStatusMappingRepository (konstruktor)
|
||||||
|
2. W mapCheckoutFormPayload() zamienic lookup:
|
||||||
|
- PRZED: $mappedOrderproStatus = $this->statusMappings->findMappedOrderproStatusCode($rawAllegroStatus)
|
||||||
|
- PO: $mappedOrderproStatus = $this->pullStatusMappings->findMappedStatusCode($rawAllegroStatus)
|
||||||
|
- Jesli null: $this->pullStatusMappings->upsertDiscoveredStatus($rawAllegroStatus) — auto-discover
|
||||||
|
- Fallback: surowy status Allegro (jak dotychczas)
|
||||||
|
3. W CronHandlerFactory wstrzyknac AllegroPullStatusMappingRepository i przekazac do AllegroOrderImportService
|
||||||
|
Avoid: nie usuwac starego findMappedOrderproStatusCode() — jest uzywany do push mappings
|
||||||
|
</action>
|
||||||
|
<verify>Import zamowienia Allegro mapuje status przez pull table; nowy nieznany status jest auto-discovered</verify>
|
||||||
|
<done>AC-2, AC-3 satisfied: import uzywa pull mappings z fallbackiem i auto-discovery</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: UI pull mapping w Ustawienia > Allegro</name>
|
||||||
|
<files>src/Modules/Settings/AllegroIntegrationsController.php, resources/views/settings/allegro.php, resources/lang/pl.php, routes/web.php</files>
|
||||||
|
<action>
|
||||||
|
1. W AllegroIntegrationsController:
|
||||||
|
- Dodac zaleznosc AllegroPullStatusMappingRepository
|
||||||
|
- Dodac metode savePullStatusMappings() — analogicznie do shopPRO
|
||||||
|
- W metodzie renderujacej ustawienia statusow: przekazac pull mappings do widoku
|
||||||
|
2. W allegro.php widoku statusow:
|
||||||
|
- Rozdzielic na dwie sekcje: "Wysylka statusow" (push, istniejacy formularz) i "Mapowanie przy imporcie" (pull, nowy formularz)
|
||||||
|
- Pull sekcja: tabela z allegro_status_code (readonly) i dropdown orderpro_status_code
|
||||||
|
- Osobny przycisk "Zapisz" per sekcja
|
||||||
|
3. W routes/web.php: dodac POST route dla save-pull
|
||||||
|
4. W resources/lang/pl.php: dodac klucze tlumaczen (allegro.pull.*)
|
||||||
|
Wzorzec: dokladnie jak w Phase 75 dla shopPRO — dwie sekcje z jasnymi etykietami kierunku
|
||||||
|
</action>
|
||||||
|
<verify>Strona Ustawienia > Allegro > Statusy renderuje dwie sekcje; zapis pull mappings dziala</verify>
|
||||||
|
<done>AC-4 satisfied: UI ma sekcje push i pull z osobnymi formularzami</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## DO NOT CHANGE
|
||||||
|
- src/Modules/Settings/AllegroStatusMappingRepository.php — push mapping repository zostaje bez zmian (buildOrderproToAllegroMap() uzywany przez push sync)
|
||||||
|
- src/Modules/Orders/OrderImportRepository.php — logika ochrony statusu przy re-imporcie zostaje bez zmian
|
||||||
|
- database/migrations/20260304_000025_create_allegro_order_status_mappings_table.sql — istniejaca tabela push bez zmian
|
||||||
|
- allegro_order_status_mappings table — dane push mappings nienaruszone
|
||||||
|
|
||||||
|
## SCOPE LIMITS
|
||||||
|
- Nie dodawac integration_id do tabeli Allegro (Allegro jest single-tenant, inaczej niz shopPRO)
|
||||||
|
- Nie modyfikowac logiki push sync (AllegroStatusPushService / cron)
|
||||||
|
- Nie dodawac status protection na re-import Allegro (to osobny temat jesli potrzebny)
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Before declaring plan complete:
|
||||||
|
- [ ] Migracja SQL wykonana na remote DB
|
||||||
|
- [ ] Import zamowienia Allegro ze statusem "new" mapuje na odpowiedni status orderPRO
|
||||||
|
- [ ] Nowy nieznany status Allegro jest auto-discovered w tabeli pull
|
||||||
|
- [ ] UI Ustawienia > Allegro > Statusy: dwie sekcje push/pull
|
||||||
|
- [ ] Zapis pull mappings z UI dziala
|
||||||
|
- [ ] Push sync (orderPRO->Allegro) niezmodyfikowany i dziala
|
||||||
|
- [ ] Brak bledow PHP / brak bledow JS na stronie ustawien
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Wszystkie taski wykonane
|
||||||
|
- Wszystkie verification checks pass
|
||||||
|
- Zamowienia Allegro importuja sie ze zmapowanym statusem orderPRO
|
||||||
|
- Istniejace push mappings nienaruszone
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.paul/phases/83-allegro-pull-status-mapping/83-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
153
.paul/phases/83-allegro-pull-status-mapping/83-01-SUMMARY.md
Normal file
153
.paul/phases/83-allegro-pull-status-mapping/83-01-SUMMARY.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
phase: 83-allegro-pull-status-mapping
|
||||||
|
plan: 01
|
||||||
|
subsystem: settings, orders
|
||||||
|
tags: [status-mapping, allegro, pull, import]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 74-reverse-status-mapping
|
||||||
|
provides: push mapping with UNIQUE on orderpro_status_code
|
||||||
|
- phase: 75-pull-status-mapping
|
||||||
|
provides: pull mapping pattern for shopPRO (replicated for Allegro)
|
||||||
|
provides:
|
||||||
|
- dedicated pull status mapping table for Allegro (allegro_order_status_pull_mappings)
|
||||||
|
- pull mapping UI section in Allegro integration settings
|
||||||
|
- auto-discovery of new Allegro statuses during import
|
||||||
|
affects: [allegro-import, status-sync, automation]
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns: [separate push/pull mapping tables for Allegro, auto-discovery in pull table]
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- database/migrations/20260407_000083_allegro_pull_status_mappings.sql
|
||||||
|
- src/Modules/Settings/AllegroPullStatusMappingRepository.php
|
||||||
|
modified:
|
||||||
|
- src/Modules/Settings/AllegroOrderImportService.php
|
||||||
|
- src/Modules/Settings/AllegroStatusMappingController.php
|
||||||
|
- src/Modules/Settings/AllegroIntegrationController.php
|
||||||
|
- src/Modules/Settings/AllegroStatusDiscoveryService.php
|
||||||
|
- src/Modules/Cron/CronHandlerFactory.php
|
||||||
|
- routes/web.php
|
||||||
|
- resources/views/settings/allegro.php
|
||||||
|
- resources/lang/pl.php
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "No integration_id in Allegro pull table — Allegro is single-tenant unlike shopPRO"
|
||||||
|
- "Nullable orderpro_status_code in pull table — allows auto-discovered unmapped statuses"
|
||||||
|
- "Discovery service writes to both push and pull tables"
|
||||||
|
- "Fallback to push table lookup when pull repo not injected (backward compat)"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Pull mapping for Allegro: UNIQUE on allegro_status_code — one orderPRO status per Allegro code"
|
||||||
|
- "Auto-discovery: upsertDiscoveredStatus() on import AND on manual sync"
|
||||||
|
|
||||||
|
duration: ~30min
|
||||||
|
started: 2026-04-07T18:00:00Z
|
||||||
|
completed: 2026-04-07T18:30:00Z
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 83 Plan 01: Allegro Pull Status Mapping — Summary
|
||||||
|
|
||||||
|
**Dedykowana tabela pull mappings dla Allegro z auto-discovery statusow i UI push/pull w ustawieniach**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Duration | ~30min |
|
||||||
|
| Tasks | 3 planned, 3 completed |
|
||||||
|
| Files created | 2 |
|
||||||
|
| Files modified | 8 |
|
||||||
|
| DB migration | Executed on remote |
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status | Notes |
|
||||||
|
|-----------|--------|-------|
|
||||||
|
| AC-1: Nowa tabela pull mappings | Pass | Tabela utworzona z UNIQUE na allegro_status_code, 4 rows pre-populated z push table |
|
||||||
|
| AC-2: Import uzywa pull mappings | Pass | AllegroOrderImportService uzywa pullStatusMappings->findMappedStatusCode() |
|
||||||
|
| AC-3: Fallback dla niezmapowanych statusow | Pass | Auto-discovery via upsertDiscoveredStatus(), fallback na surowy status |
|
||||||
|
| AC-4: Sekcja pull mapping w UI | Pass | Dwie sekcje: push (orderPRO->Allegro) i pull (Allegro->orderPRO) |
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Nowa tabela `allegro_order_status_pull_mappings` z UNIQUE na `allegro_status_code` — eliminuje bug importu z niezmapowanym statusem
|
||||||
|
- UI w Ustawienia > Allegro > Statusy ma dwie sekcje: push ("Wysylka statusow") i pull ("Mapowanie przy imporcie")
|
||||||
|
- Auto-discovery statusow Allegro zarowno przy imporcie zamowien jak i przy recznym "Pobierz statusy z Allegro"
|
||||||
|
- Naprawiono 3 zamowienia (#223, #225, #233) z niezmapowanym statusem `new` → `nieoplacone`
|
||||||
|
- Dodano 6 brakujacych statusow Allegro do pull table (new, ready_for_processing, ready_for_shipment, delivered, returned, bought)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
| File | Change | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `database/migrations/20260407_000083_allegro_pull_status_mappings.sql` | Created | Nowa tabela + pre-populate z push mappings |
|
||||||
|
| `src/Modules/Settings/AllegroPullStatusMappingRepository.php` | Created | Repository CRUD+lookup dla pull mappings |
|
||||||
|
| `src/Modules/Settings/AllegroOrderImportService.php` | Modified | Pull mapping lookup zamiast push reverse-lookup |
|
||||||
|
| `src/Modules/Settings/AllegroStatusMappingController.php` | Modified | Nowa metoda savePullStatusMappings() |
|
||||||
|
| `src/Modules/Settings/AllegroIntegrationController.php` | Modified | Pull repo dependency + dane pull do widoku |
|
||||||
|
| `src/Modules/Settings/AllegroStatusDiscoveryService.php` | Modified | Discovery zapisuje do obu tabel (push+pull) |
|
||||||
|
| `src/Modules/Cron/CronHandlerFactory.php` | Modified | Wstrzykniecie AllegroPullStatusMappingRepository |
|
||||||
|
| `routes/web.php` | Modified | Nowa route POST .../statuses/save-pull + DI |
|
||||||
|
| `resources/views/settings/allegro.php` | Modified | Sekcja pull mapping w tabce Statusy |
|
||||||
|
| `resources/lang/pl.php` | Modified | Klucze pull_title, pull_description, saved_pull |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale | Impact |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| Brak integration_id w tabeli Allegro | Allegro jest single-tenant (jeden zestaw credentials) | Prostsza tabela niz shopPRO |
|
||||||
|
| orderpro_status_code nullable w pull | Auto-discovered statusy nie maja jeszcze mapowania | User mapuje w UI po discovery |
|
||||||
|
| Discovery service pisze do obu tabel | "Pobierz statusy z Allegro" musi zasilac pull table | Spojne zachowanie push+pull |
|
||||||
|
| Dodanie 6 statusow Allegro recznie do pull | Discovery nie lapie krotkotrwalych statusow (np. new) | Pelna paleta statusow od razu |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Type | Count | Impact |
|
||||||
|
|------|-------|--------|
|
||||||
|
| Scope additions | 2 | Istotne usprawnienia |
|
||||||
|
| Auto-fixed | 1 | Naprawa danych zamowien |
|
||||||
|
|
||||||
|
**Total impact:** Rozszerzenie o discovery service + naprawa danych — kluczowe dla kompletnosci
|
||||||
|
|
||||||
|
### Scope Addition: AllegroStatusDiscoveryService
|
||||||
|
|
||||||
|
- **Requested by:** User during testing ("nie ma statusu new po Pobierz statusy")
|
||||||
|
- **Issue:** Discovery service zapisywal tylko do push table, nie do pull
|
||||||
|
- **Fix:** Dodanie pullStatusMappings dependency + upsertDiscoveredStatus w petli discovery
|
||||||
|
- **Files:** `AllegroStatusDiscoveryService.php`, `routes/web.php`
|
||||||
|
|
||||||
|
### Scope Addition: Reczne dodanie statusow Allegro
|
||||||
|
|
||||||
|
- **Requested by:** User
|
||||||
|
- **Issue:** Discovery nie lapie krotkotrwalych statusow (new, bought, ready_for_processing)
|
||||||
|
- **Fix:** INSERT 6 brakujacych statusow do pull table
|
||||||
|
- **Impact:** User od razu moze zmapowac wszystkie statusy
|
||||||
|
|
||||||
|
### Auto-fixed: Zamowienia z niezmapowanym statusem
|
||||||
|
|
||||||
|
- **Found during:** Investigation
|
||||||
|
- **Issue:** 3 zamowienia (#223, #225, #233) mialy surowy status `new` zamiast zmapowanego
|
||||||
|
- **Fix:** UPDATE orders SET external_status_id = 'nieoplacone' WHERE external_status_id = 'new' AND source = 'allegro'
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
**Ready:**
|
||||||
|
- Pull mapping w pelni funkcjonalne i konfigurowalne
|
||||||
|
- Nowe importy z Allegro beda automatycznie mapowac statusy
|
||||||
|
- Discovery zasilaja obie tabele (push+pull)
|
||||||
|
|
||||||
|
**Concerns:**
|
||||||
|
- Brak ochrony statusu przy re-imporcie Allegro (analogicznie do Phase 75 dla shopPRO) — jesli potrzebne, osobna faza
|
||||||
|
- Niektore statusy Allegro moga nie miec mapowania — user musi je ustawic w UI
|
||||||
|
|
||||||
|
**Blockers:**
|
||||||
|
- None — kod wymaga deploy na serwer (FTP)
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 83-allegro-pull-status-mapping, Plan: 01*
|
||||||
|
*Completed: 2026-04-07*
|
||||||
177
.paul/phases/84-order-imported-automation-event/84-01-PLAN.md
Normal file
177
.paul/phases/84-order-imported-automation-event/84-01-PLAN.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
---
|
||||||
|
phase: 84-order-imported-automation-event
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- src/Modules/Automation/AutomationController.php
|
||||||
|
- src/Modules/Settings/AllegroOrderImportService.php
|
||||||
|
- src/Modules/Settings/ShopproOrdersSyncService.php
|
||||||
|
- src/Modules/Cron/CronHandlerFactory.php
|
||||||
|
- resources/views/automation/form.php
|
||||||
|
- resources/views/automation/index.php
|
||||||
|
- routes/web.php
|
||||||
|
autonomous: true
|
||||||
|
delegation: off
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Dodac nowe zdarzenie automatyzacji `order.imported` wyzwalane przy kazdym pobraniu zamowienia (Allegro i shopPRO). Umozliwi tworzenie regul automatycznych reagujacych na import zamowien (np. wyslij e-mail, zmien status, wystaw paragon).
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Obecnie automatyzacja reaguje na zmiany statusow, przesylki, platnosci i paragony. Brakuje zdarzenia na sam moment pobrania zamowienia — user nie moze automatycznie reagowac na nowe zamowienie.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Nowy event type `order.imported` w ALLOWED_EVENTS
|
||||||
|
- Trigger w AllegroOrderImportService i ShopproOrdersSyncService
|
||||||
|
- Warunek `integration` dostepny dla tego eventu (filtrowanie po zrodle)
|
||||||
|
- Etykieta w UI automatyzacji
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/ROADMAP.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@src/Modules/Automation/AutomationController.php — ALLOWED_EVENTS (linia 19), ALLOWED_CONDITION_TYPES (linia 20)
|
||||||
|
@src/Modules/Automation/AutomationService.php — trigger() method, evaluateConditions()
|
||||||
|
@src/Modules/Settings/AllegroOrderImportService.php — importSingleOrder() linia 69-96
|
||||||
|
@src/Modules/Settings/ShopproOrdersSyncService.php — processOrder() linia 225-269
|
||||||
|
@src/Modules/Cron/CronHandlerFactory.php — kompozycja obiektow
|
||||||
|
@resources/views/automation/form.php — $eventLabels (linia 9)
|
||||||
|
@resources/views/automation/index.php — $eventLabels (linia 14)
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: Event zarejestrowany w systemie
|
||||||
|
```gherkin
|
||||||
|
Given system automatyzacji z lista dozwolonych zdarzen
|
||||||
|
When user tworzy nowa regule automatyzacji
|
||||||
|
Then w dropdownie zdarzen dostepna jest opcja "Pobranie zamowienia" (order.imported)
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: Event wyzwalany przy imporcie Allegro
|
||||||
|
```gherkin
|
||||||
|
Given aktywna regula z event_type = order.imported
|
||||||
|
When zamowienie zostaje zaimportowane z Allegro (nowe lub re-import)
|
||||||
|
Then AutomationService.trigger('order.imported', orderId, context) jest wywolany
|
||||||
|
And context zawiera: source, created (bool), integration_id
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: Event wyzwalany przy imporcie shopPRO
|
||||||
|
```gherkin
|
||||||
|
Given aktywna regula z event_type = order.imported
|
||||||
|
When zamowienie zostaje zaimportowane z shopPRO
|
||||||
|
Then AutomationService.trigger('order.imported', orderId, context) jest wywolany
|
||||||
|
And context zawiera: source, created (bool), integration_id
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-4: Warunek integration dziala z nowym eventem
|
||||||
|
```gherkin
|
||||||
|
Given regula order.imported z warunkiem integration = [konkretna integracja]
|
||||||
|
When zamowienie jest importowane z innej integracji
|
||||||
|
Then regula NIE jest wykonywana (warunek nie speliony)
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Rejestracja eventu + UI</name>
|
||||||
|
<files>src/Modules/Automation/AutomationController.php, resources/views/automation/form.php, resources/views/automation/index.php</files>
|
||||||
|
<action>
|
||||||
|
1. W AutomationController dodac 'order.imported' do ALLOWED_EVENTS (linia 19)
|
||||||
|
2. W resources/views/automation/form.php dodac do $eventLabels:
|
||||||
|
'order.imported' => 'Pobranie zamowienia'
|
||||||
|
3. W resources/views/automation/index.php dodac to samo do $eventLabels
|
||||||
|
Avoid: nie zmieniac ALLOWED_CONDITION_TYPES — warunek 'integration' juz istnieje i dziala
|
||||||
|
</action>
|
||||||
|
<verify>PHP lint na 3 plikach; event widoczny w dropdownie formularza</verify>
|
||||||
|
<done>AC-1 satisfied: event order.imported zarejestrowany i widoczny w UI</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Trigger w AllegroOrderImportService</name>
|
||||||
|
<files>src/Modules/Settings/AllegroOrderImportService.php, src/Modules/Cron/CronHandlerFactory.php, routes/web.php</files>
|
||||||
|
<action>
|
||||||
|
1. W AllegroOrderImportService dodac opcjonalna zaleznosc AutomationService (konstruktor):
|
||||||
|
private readonly ?AutomationService $automationService = null
|
||||||
|
2. Po bloku recordActivity (po linii 95), dodac trigger:
|
||||||
|
if ($savedOrderId > 0 && $this->automationService !== null) {
|
||||||
|
$this->automationService->trigger('order.imported', $savedOrderId, [
|
||||||
|
'source' => 'allegro',
|
||||||
|
'created' => $wasCreated,
|
||||||
|
'integration_id' => (int) ($mapped['order']['integration_id'] ?? 0),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
3. W CronHandlerFactory wstrzyknac $automationService do AllegroOrderImportService
|
||||||
|
4. W routes/web.php wstrzyknac $automationService do AllegroOrderImportService
|
||||||
|
Avoid: nie triggerowac wewnatrz upsertOrderAggregate — trigger ma byc na poziomie serwisu importu
|
||||||
|
Uwaga: import use App\Modules\Automation\AutomationService w AllegroOrderImportService
|
||||||
|
</action>
|
||||||
|
<verify>PHP lint; import zamowienia Allegro wyzwala event order.imported</verify>
|
||||||
|
<done>AC-2 satisfied: import Allegro triggeruje order.imported z kontekstem</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Trigger w ShopproOrdersSyncService</name>
|
||||||
|
<files>src/Modules/Settings/ShopproOrdersSyncService.php, src/Modules/Cron/CronHandlerFactory.php</files>
|
||||||
|
<action>
|
||||||
|
1. W ShopproOrdersSyncService dodac opcjonalna zaleznosc AutomationService (konstruktor):
|
||||||
|
private readonly ?AutomationService $automationService = null
|
||||||
|
2. Po bloku recordActivity w processOrder() (po linii 269), dodac trigger:
|
||||||
|
if ($savedOrderId > 0 && $this->automationService !== null) {
|
||||||
|
$this->automationService->trigger('order.imported', $savedOrderId, [
|
||||||
|
'source' => 'shoppro',
|
||||||
|
'created' => $wasCreated,
|
||||||
|
'integration_id' => $integrationId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
3. W CronHandlerFactory wstrzyknac $automationService do ShopproOrdersSyncService
|
||||||
|
Avoid: nie triggerowac przy payment_transition (to jest re-import ze zmiana platnosci, nie nowy import)
|
||||||
|
</action>
|
||||||
|
<verify>PHP lint; import zamowienia shopPRO wyzwala event order.imported</verify>
|
||||||
|
<done>AC-3, AC-4 satisfied: import shopPRO triggeruje order.imported; warunek integration dziala</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## DO NOT CHANGE
|
||||||
|
- src/Modules/Automation/AutomationService.php — logika ewaluacji warunkow juz obsluguje 'integration'
|
||||||
|
- src/Modules/Orders/OrderImportRepository.php — trigger na poziomie serwisu, nie repozytorium
|
||||||
|
- database/migrations/* — brak zmian DB (event type jest stringiem, nie wymaga migracji)
|
||||||
|
|
||||||
|
## SCOPE LIMITS
|
||||||
|
- Nie dodawac nowych typow warunkow — 'integration' juz istnieje i wystarczy
|
||||||
|
- Nie dodawac warunku 'source' (allegro/shoppro) — 'integration' filtruje po konkretnej integracji
|
||||||
|
- Nie triggerowac przy payment_transition w shopPRO
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Before declaring plan complete:
|
||||||
|
- [ ] PHP lint na wszystkich zmodyfikowanych plikach
|
||||||
|
- [ ] Event 'order.imported' widoczny w dropdown formularza automatyzacji
|
||||||
|
- [ ] AllegroOrderImportService triggeruje event po imporcie
|
||||||
|
- [ ] ShopproOrdersSyncService triggeruje event po imporcie
|
||||||
|
- [ ] AutomationService wstrzykniety w CronHandlerFactory i routes/web.php
|
||||||
|
- [ ] Brak bledow PHP
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Wszystkie taski wykonane
|
||||||
|
- Wszystkie verification checks pass
|
||||||
|
- User moze tworzyc reguly automatyzacji na zdarzenie "Pobranie zamowienia"
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.paul/phases/84-order-imported-automation-event/84-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
108
.paul/phases/84-order-imported-automation-event/84-01-SUMMARY.md
Normal file
108
.paul/phases/84-order-imported-automation-event/84-01-SUMMARY.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
phase: 84-order-imported-automation-event
|
||||||
|
plan: 01
|
||||||
|
subsystem: automation, settings
|
||||||
|
tags: [automation, event, import, allegro, shoppro]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 16-automated-tasks
|
||||||
|
provides: automation engine (AutomationService, rules, conditions, actions)
|
||||||
|
provides:
|
||||||
|
- event type order.imported for automation rules
|
||||||
|
- trigger in Allegro and shopPRO import flows
|
||||||
|
affects: [automation, cron-import]
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns: [nullable AutomationService injection for optional trigger]
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
modified:
|
||||||
|
- src/Modules/Automation/AutomationController.php
|
||||||
|
- src/Modules/Settings/AllegroOrderImportService.php
|
||||||
|
- src/Modules/Settings/ShopproOrdersSyncService.php
|
||||||
|
- src/Modules/Cron/CronHandlerFactory.php
|
||||||
|
- resources/views/automation/form.php
|
||||||
|
- resources/views/automation/index.php
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Nullable AutomationService — backward compat, trigger only when injected"
|
||||||
|
- "No trigger on payment_transition in shopPRO — that's a re-import status change, not a new import"
|
||||||
|
- "Cron context has full injection; web controller context has null (99% imports are via cron)"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Event trigger after recordActivity, before return — consistent with shipment.created pattern"
|
||||||
|
|
||||||
|
duration: ~15min
|
||||||
|
started: 2026-04-07T19:00:00Z
|
||||||
|
completed: 2026-04-07T19:15:00Z
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 84 Plan 01: Order Imported Automation Event — Summary
|
||||||
|
|
||||||
|
**Nowe zdarzenie automatyzacji `order.imported` wyzwalane przy imporcie zamowien z Allegro i shopPRO**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Duration | ~15min |
|
||||||
|
| Tasks | 3 planned, 3 completed |
|
||||||
|
| Files modified | 6 |
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status | Notes |
|
||||||
|
|-----------|--------|-------|
|
||||||
|
| AC-1: Event zarejestrowany w systemie | Pass | Dodany do ALLOWED_EVENTS + UI labels |
|
||||||
|
| AC-2: Event wyzwalany przy imporcie Allegro | Pass | Trigger po recordActivity w importSingleOrder() |
|
||||||
|
| AC-3: Event wyzwalany przy imporcie shopPRO | Pass | Trigger po recordActivity w processOrder() |
|
||||||
|
| AC-4: Warunek integration dziala | Pass | Istniejacy warunek 'integration' filtruje po integration_id |
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Nowy event type `order.imported` w systemie automatyzacji z etykieta "Pobranie zamowienia"
|
||||||
|
- Trigger w AllegroOrderImportService po kazdym imporcie (nowe i re-import)
|
||||||
|
- Trigger w ShopproOrdersSyncService po kazdym imporcie (bez payment_transition)
|
||||||
|
- Context eventu zawiera: source, created (bool), integration_id
|
||||||
|
- Istniejacy warunek `integration` pozwala filtrowac po zrodle zamowienia
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
| File | Change | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `src/Modules/Automation/AutomationController.php` | Modified | Dodanie 'order.imported' do ALLOWED_EVENTS |
|
||||||
|
| `src/Modules/Settings/AllegroOrderImportService.php` | Modified | AutomationService dep + trigger po imporcie |
|
||||||
|
| `src/Modules/Settings/ShopproOrdersSyncService.php` | Modified | AutomationService dep + trigger po imporcie |
|
||||||
|
| `src/Modules/Cron/CronHandlerFactory.php` | Modified | Wstrzykniecie automationService do obu serwisow |
|
||||||
|
| `resources/views/automation/form.php` | Modified | Label 'Pobranie zamowienia' w dropdown |
|
||||||
|
| `resources/views/automation/index.php` | Modified | Label 'Pobranie zamowienia' w liscie/historii |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale | Impact |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| Nullable AutomationService | Backward compat + kontroler web nie ma dostepnego AutomationService w momencie tworzenia | Trigger dziala w cron (99% importow), nie w recznym imporcie z UI |
|
||||||
|
| Brak triggera na payment_transition | Payment transition to re-import ze zmiana statusu platnosci, nie nowy import | Nie generuje falszywych triggerow |
|
||||||
|
| Reuse istniejacego warunku integration | Warunek juz obsluguje filtrowanie po integration_id | Zero zmian w logice ewaluacji warunkow |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed as written.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
**Ready:**
|
||||||
|
- Event order.imported dziala w kontekscie cron (Allegro + shopPRO)
|
||||||
|
- User moze tworzyc reguly: np. "Gdy pobrano zamowienie z integracji X → wyslij e-mail / zmien status"
|
||||||
|
|
||||||
|
**Concerns:**
|
||||||
|
- Reczny import z UI (Allegro controller) nie triggeruje eventu (AutomationService = null)
|
||||||
|
- Jesli potrzebne — wymaga refactoru kolejnosci tworzenia obiektow w routes/web.php
|
||||||
|
|
||||||
|
**Blockers:**
|
||||||
|
- None — kod wymaga deploy na serwer (FTP)
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 84-order-imported-automation-event, Plan: 01*
|
||||||
|
*Completed: 2026-04-07*
|
||||||
178
.paul/phases/85-status-group-filter/85-01-PLAN.md
Normal file
178
.paul/phases/85-status-group-filter/85-01-PLAN.md
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
---
|
||||||
|
phase: 85-status-group-filter
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- resources/views/components/order-status-panel.php
|
||||||
|
- resources/scss/app.scss
|
||||||
|
- src/Modules/Orders/OrdersController.php
|
||||||
|
- src/Modules/Orders/OrdersRepository.php
|
||||||
|
autonomous: true
|
||||||
|
delegation: off
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Umozliwic filtrowanie zamowien po calej grupie statusow — klikniecie w nazwe grupy na panelu statusow filtruje liste zamowien do wszystkich statusow nalezacych do tej grupy.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Uzytkownik moze szybko zobaczyc np. wszystkie zamowienia "w realizacji" (processing + packed + shipped) jednym kliknieciem zamiast sprawdzac kazdy status osobno.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Nazwa grupy statusow na panelu bocznym jest klikalna i filtruje zamowienia po wszystkich statusach z grupy
|
||||||
|
- URL parametr `status_group` obslugiwany przez kontroler i repozytorium
|
||||||
|
- Suma zamowien wyswietlana obok nazwy grupy
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/ROADMAP.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@resources/views/components/order-status-panel.php
|
||||||
|
@src/Modules/Orders/OrdersController.php (buildStatusPanel, statusFilterUrl)
|
||||||
|
@src/Modules/Orders/OrdersRepository.php (buildPaginateFilters, statusPanelConfig)
|
||||||
|
@resources/scss/app.scss (order-status-group styles)
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<skills>
|
||||||
|
No specialized flows required.
|
||||||
|
</skills>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: Klikniecie nazwy grupy filtruje zamowienia
|
||||||
|
```gherkin
|
||||||
|
Given panel statusow na /orders/list z grupami (np. "Realizacja" zawierajaca "processing", "packed")
|
||||||
|
When uzytkownik kliknie nazwe grupy "Realizacja"
|
||||||
|
Then lista zamowien pokazuje tylko zamowienia o statusach "processing" LUB "packed"
|
||||||
|
And URL zawiera parametr status_group z identyfikatorem grupy
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: Grupa pokazuje sume zamowien
|
||||||
|
```gherkin
|
||||||
|
Given grupa "Realizacja" zawiera statusy "processing" (5 zamowien) i "packed" (3 zamowienia)
|
||||||
|
When panel statusow jest renderowany
|
||||||
|
Then obok nazwy grupy wyswietla sie suma "8"
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: Aktywna grupa jest wizualnie wyrozniiona
|
||||||
|
```gherkin
|
||||||
|
Given uzytkownik filtruje po grupie "Realizacja"
|
||||||
|
When panel statusow jest renderowany
|
||||||
|
Then nazwa grupy "Realizacja" ma klase is-active
|
||||||
|
And poszczegolne statusy w grupie NIE sa oznaczone jako is_active (aktywna jest grupa)
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-4: Filtr grupy i filtr statusu sa wzajemnie wylaczne
|
||||||
|
```gherkin
|
||||||
|
Given uzytkownik filtruje po grupie "Realizacja" (status_group=X)
|
||||||
|
When uzytkownik kliknie konkretny status "packed"
|
||||||
|
Then filtr zmienia sie na pojedynczy status (status=packed, bez status_group)
|
||||||
|
And odwrotnie: klikniecie grupy usuwa parametr status
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Backend — obsluga filtra status_group w repozytorium i kontrolerze</name>
|
||||||
|
<files>src/Modules/Orders/OrdersRepository.php, src/Modules/Orders/OrdersController.php</files>
|
||||||
|
<action>
|
||||||
|
1. **OrdersRepository::buildPaginateFilters()** — dodac obsluge filtra `status_group`:
|
||||||
|
- Nowy parametr `status_group` (string, ID grupy)
|
||||||
|
- Jezeli `status_group` nie jest pusty: pobrac kody statusow nalezacych do tej grupy z `statusPanelConfig()` lub osobnym query do `order_statuses WHERE group_id = :gid`
|
||||||
|
- Zbudowac warunek `effectiveStatusSql IN (:sg0, :sg1, ...)` z dynamicznymi parametrami
|
||||||
|
- Filtr `status_group` ma priorytet nad `status` — jezeli oba sa podane, uzyj `status_group`
|
||||||
|
|
||||||
|
2. **OrdersController::index()** — dodac odczyt `status_group` z requestu:
|
||||||
|
- `'status_group' => trim((string) $request->input('status_group', ''))`
|
||||||
|
- Przekazac do `$filters` i dalej do `paginate()`
|
||||||
|
|
||||||
|
3. **OrdersController::buildStatusPanel()** — rozszerzyc:
|
||||||
|
- Dodac do kazdej grupy (ktora ma `name`) element klikalny z URL zawierajacym `status_group=ID`
|
||||||
|
- Obliczyc `group_count` jako sume countow statusow w grupie
|
||||||
|
- Ustawic `is_active_group` jezeli `currentStatusGroup` odpowiada tej grupie
|
||||||
|
- Gdy grupa jest aktywna, poszczegolne statusy w niej NIE maja `is_active`
|
||||||
|
|
||||||
|
4. **OrdersController::statusFilterUrl()** — rozszerzyc o wariant grupowy:
|
||||||
|
- Nowa metoda `groupFilterUrl(array $query, string $groupId): string`
|
||||||
|
- Ustawia `status_group=ID`, usuwa `status`, resetuje `page=1`
|
||||||
|
- Klikniecie statusu (istniejace `statusFilterUrl`) musi usuwac `status_group`
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- GET /orders/list?status_group=1 zwraca zamowienia z wszystkimi statusami z grupy 1
|
||||||
|
- GET /orders/list?status=packed nie zawiera parametru status_group
|
||||||
|
- Filtr status_group ma priorytet nad status gdy oba podane
|
||||||
|
</verify>
|
||||||
|
<done>AC-1 i AC-4 satisfied: filtr grupowy dziala, jest wzajemnie wylaczny z filtrem statusu</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Frontend — klikalna nazwa grupy z licznikiem i stanem aktywnym</name>
|
||||||
|
<files>resources/views/components/order-status-panel.php, resources/scss/app.scss</files>
|
||||||
|
<action>
|
||||||
|
1. **order-status-panel.php** — zmodyfikowac rendering nazwy grupy:
|
||||||
|
- Jezeli grupa ma `url` (nowy klucz z buildStatusPanel), renderowac nazwe jako `<a>` zamiast `<div>`
|
||||||
|
- Dodac `group_count` obok nazwy grupy (jak count przy statusach)
|
||||||
|
- Dodac klase `is-active` do `order-status-group__name` gdy `is_active_group` jest true
|
||||||
|
- Struktura: `<a href="..." class="order-status-group__name [is-active]">Nazwa <span class="order-status-group__count">8</span></a>`
|
||||||
|
|
||||||
|
2. **app.scss** — dodac style:
|
||||||
|
- `.order-status-group__name` jako klikalny element: cursor pointer, hover effect
|
||||||
|
- `.order-status-group__name.is-active` — wyroznienie (np. bold + lewa krawedz kolorem grupy)
|
||||||
|
- `.order-status-group__count` — styl licznika analogiczny do `.order-status-row__count`
|
||||||
|
- Zachowac kompaktowy uklad (zgodnie z CLAUDE.md)
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- Nazwa grupy jest klikalna i prowadzi do /orders/list?status_group=ID
|
||||||
|
- Obok nazwy grupy wyswietla sie suma zamowien
|
||||||
|
- Aktywna grupa jest wizualnie wyrozniiona
|
||||||
|
- Na mobile panel dziala tak jak dotychczas (zwijany)
|
||||||
|
</verify>
|
||||||
|
<done>AC-2 i AC-3 satisfied: grupa pokazuje sume, aktywna grupa jest wyrozniiona</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## DO NOT CHANGE
|
||||||
|
- database/migrations/* (brak zmian schematu — grupy i statusy juz istnieja w DB)
|
||||||
|
- src/Modules/Orders/OrdersRepository::statusPanelConfig() — nie zmieniac struktury zwracanej, tylko konsumowac
|
||||||
|
- Logika istniejacych filtrow (search, source, date_from, date_to, payment_status) — nie ruszac
|
||||||
|
|
||||||
|
## SCOPE LIMITS
|
||||||
|
- Nie dodawac mozliwosci wielokrotnego wyboru grup (multi-select)
|
||||||
|
- Nie zmieniac sposobu liczenia statusow w statusCounts()
|
||||||
|
- Nie dodawac nowych tabel ani kolumn w bazie danych
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Before declaring plan complete:
|
||||||
|
- [ ] GET /orders/list?status_group=1 filtruje po wszystkich statusach z grupy
|
||||||
|
- [ ] GET /orders/list?status=new filtruje po jednym statusie (bez zmian)
|
||||||
|
- [ ] Klikniecie nazwy grupy na panelu ustawia status_group w URL
|
||||||
|
- [ ] Klikniecie statusu w grupie usuwa status_group i ustawia status
|
||||||
|
- [ ] Suma zamowien obok nazwy grupy jest poprawna
|
||||||
|
- [ ] Aktywna grupa ma wizualne wyrozniienie
|
||||||
|
- [ ] Brak bledow PHP na /orders/list bez parametrow
|
||||||
|
- [ ] Mobile: panel statusow dalej sie zwija/rozwija
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Wszystkie taski wykonane
|
||||||
|
- Wszystkie verification checks przechodzą
|
||||||
|
- Brak nowych bledow PHP ani JS
|
||||||
|
- Filtrowanie po grupie i po statusie sa wzajemnie wylaczne
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.paul/phases/85-status-group-filter/85-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
105
.paul/phases/85-status-group-filter/85-01-SUMMARY.md
Normal file
105
.paul/phases/85-status-group-filter/85-01-SUMMARY.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
phase: 85-status-group-filter
|
||||||
|
plan: 01
|
||||||
|
subsystem: ui
|
||||||
|
tags: [orders, status-panel, filtering, php]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: none
|
||||||
|
provides: existing status groups and statuses in DB
|
||||||
|
provides:
|
||||||
|
- clickable status group filtering on orders list
|
||||||
|
- group count display in status panel
|
||||||
|
affects: []
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns: [group filter URL pattern with status_group param]
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- src/Modules/Orders/OrdersController.php
|
||||||
|
- src/Modules/Orders/OrdersRepository.php
|
||||||
|
- resources/views/components/order-status-panel.php
|
||||||
|
- resources/scss/app.scss
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "status_group and status params are mutually exclusive"
|
||||||
|
- "Group ID used as filter value (not group name/code)"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "groupFilterUrl() mirrors statusFilterUrl() pattern"
|
||||||
|
|
||||||
|
duration: ~10min
|
||||||
|
completed: 2026-04-07
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 85 Plan 01: Status Group Filter Summary
|
||||||
|
|
||||||
|
**Klikalna nazwa grupy statusow na panelu bocznym filtruje zamowienia po wszystkich statusach z tej grupy, z licznikiem sumy i wizualnym wyroznieniem aktywnej grupy.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Duration | ~10min |
|
||||||
|
| Completed | 2026-04-07 |
|
||||||
|
| Tasks | 2 completed |
|
||||||
|
| Files modified | 5 (incl. compiled CSS) |
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status | Notes |
|
||||||
|
|-----------|--------|-------|
|
||||||
|
| AC-1: Klikniecie nazwy grupy filtruje zamowienia | Pass | status_group=ID w URL, IN (...) w SQL |
|
||||||
|
| AC-2: Grupa pokazuje sume zamowien | Pass | group_count sumowany z counts statusow |
|
||||||
|
| AC-3: Aktywna grupa wizualnie wyrozniiona | Pass | is-active class + border-left kolor grupy |
|
||||||
|
| AC-4: Filtr grupy i statusu wzajemnie wylaczne | Pass | statusFilterUrl usuwa status_group, groupFilterUrl usuwa status |
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Backend: nowy parametr `status_group` z filtrem `IN (...)` w repozytorium, metoda `statusCodesByGroupId()`
|
||||||
|
- Controller: `groupFilterUrl()`, rozszerzony `buildStatusPanel()` z URL/count/active dla grup
|
||||||
|
- Frontend: klikalna nazwa grupy jako `<a>` z badge licznika, hover effect, active state z border-left
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
| File | Change | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `src/Modules/Orders/OrdersRepository.php` | Modified | Filtr status_group z IN(), statusCodesByGroupId(), id w statusPanelConfig |
|
||||||
|
| `src/Modules/Orders/OrdersController.php` | Modified | Odczyt status_group, groupFilterUrl(), buildStatusPanel z grupami |
|
||||||
|
| `resources/views/components/order-status-panel.php` | Modified | Klikalna nazwa grupy z licznikiem |
|
||||||
|
| `resources/scss/app.scss` | Modified | Style klikalnej grupy: hover, count badge, is-active |
|
||||||
|
| `public/assets/css/app.css` | Modified | Skompilowany CSS |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale | Impact |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| Group ID jako parametr filtra (nie kod/nazwa) | Jednoznaczna identyfikacja, bezpieczne w URL | Prosty int w query string |
|
||||||
|
| status_group ma priorytet nad status | Wzajemna wylacznosc — klikniecie statusu czysci grupe i odwrotnie | Czyste UX bez konfliktow |
|
||||||
|
| "Wszystkie" aktywne tylko gdy oba filtry puste | Poprawna logika aktywnosci | Konsystentny stan panelu |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None - plan executed exactly as written.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
**Ready:**
|
||||||
|
- Filtrowanie po grupach dziala, gotowe do testow manualnych
|
||||||
|
|
||||||
|
**Concerns:**
|
||||||
|
- None
|
||||||
|
|
||||||
|
**Blockers:**
|
||||||
|
- None
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 85-status-group-filter, Plan: 01*
|
||||||
|
*Completed: 2026-04-07*
|
||||||
114
.vscode/ftp-kr.sync.cache.json
vendored
114
.vscode/ftp-kr.sync.cache.json
vendored
@@ -579,6 +579,24 @@
|
|||||||
"size": 531,
|
"size": 531,
|
||||||
"lmtime": 1775245388687,
|
"lmtime": 1775245388687,
|
||||||
"modified": false
|
"modified": false
|
||||||
|
},
|
||||||
|
"20260407_000078_reverse_status_mapping_keys.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 2634,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"20260407_000079_pull_status_mappings.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1557,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"20260407_000080_backfill_personalization_message.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1201,
|
||||||
|
"lmtime": 1775559443677,
|
||||||
|
"modified": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"seeders": {},
|
"seeders": {},
|
||||||
@@ -600,15 +618,15 @@
|
|||||||
"DOCS": {
|
"DOCS": {
|
||||||
"ARCHITECTURE.md": {
|
"ARCHITECTURE.md": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 41990,
|
"size": 43763,
|
||||||
"lmtime": 1775318401130,
|
"lmtime": 1775559230614,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"DB_SCHEMA.md": {
|
"DB_SCHEMA.md": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 33647,
|
"size": 34569,
|
||||||
"lmtime": 1775316434590,
|
"lmtime": 1775316434590,
|
||||||
"modified": false
|
"modified": true
|
||||||
},
|
},
|
||||||
"ORDERS_SCHEMA_APILO_DRAFT.md": {
|
"ORDERS_SCHEMA_APILO_DRAFT.md": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
@@ -630,8 +648,8 @@
|
|||||||
},
|
},
|
||||||
"TECH_CHANGELOG.md": {
|
"TECH_CHANGELOG.md": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 74741,
|
"size": 82529,
|
||||||
"lmtime": 1775318410629,
|
"lmtime": 1775559241970,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"todo.md": {
|
"todo.md": {
|
||||||
@@ -1855,8 +1873,8 @@
|
|||||||
"css": {
|
"css": {
|
||||||
"app.css": {
|
"app.css": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 58395,
|
"size": 49297,
|
||||||
"lmtime": 1775065613206,
|
"lmtime": 1775561874978,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"app.css.map": {
|
"app.css.map": {
|
||||||
@@ -1868,7 +1886,7 @@
|
|||||||
"login.css": {
|
"login.css": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 5996,
|
"size": 5996,
|
||||||
"lmtime": 1774702917327,
|
"lmtime": 1775561875587,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"login.css.map": {
|
"login.css.map": {
|
||||||
@@ -1897,8 +1915,8 @@
|
|||||||
},
|
},
|
||||||
"inline-status-change.js": {
|
"inline-status-change.js": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 6603,
|
"size": 6628,
|
||||||
"lmtime": 1774600361548,
|
"lmtime": 1775561174202,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"jquery-alerts.js": {
|
"jquery-alerts.js": {
|
||||||
@@ -1906,6 +1924,12 @@
|
|||||||
"size": 5768,
|
"size": 5768,
|
||||||
"lmtime": 1771873304132,
|
"lmtime": 1771873304132,
|
||||||
"modified": false
|
"modified": false
|
||||||
|
},
|
||||||
|
"global-search.js": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 3578,
|
||||||
|
"lmtime": 1775561835372,
|
||||||
|
"modified": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1976,8 +2000,8 @@
|
|||||||
},
|
},
|
||||||
"app.scss": {
|
"app.scss": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 46852,
|
"size": 46883,
|
||||||
"lmtime": 1775065604908,
|
"lmtime": 1775561864669,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"login.css": {
|
"login.css": {
|
||||||
@@ -2031,8 +2055,14 @@
|
|||||||
},
|
},
|
||||||
"_shipment-presets.scss": {
|
"_shipment-presets.scss": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 2629,
|
"size": 2649,
|
||||||
"lmtime": 1774219643850,
|
"lmtime": 1774219643850,
|
||||||
|
"modified": true
|
||||||
|
},
|
||||||
|
"_global-search.scss": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1649,
|
||||||
|
"lmtime": 1775561859898,
|
||||||
"modified": false
|
"modified": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2080,8 +2110,8 @@
|
|||||||
"layouts": {
|
"layouts": {
|
||||||
"app.php": {
|
"app.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 11645,
|
"size": 12097,
|
||||||
"lmtime": 1774818596878,
|
"lmtime": 1775561821328,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"auth.php": {
|
"auth.php": {
|
||||||
@@ -2134,8 +2164,8 @@
|
|||||||
},
|
},
|
||||||
"show.php": {
|
"show.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 51639,
|
"size": 52009,
|
||||||
"lmtime": 1775202975277,
|
"lmtime": 1775559290158,
|
||||||
"modified": false
|
"modified": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2330,8 +2360,8 @@
|
|||||||
"routes": {
|
"routes": {
|
||||||
"web.php": {
|
"web.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 28097,
|
"size": 28464,
|
||||||
"lmtime": 1775318241324,
|
"lmtime": 1775561801991,
|
||||||
"modified": false
|
"modified": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2732,9 +2762,9 @@
|
|||||||
"Orders": {
|
"Orders": {
|
||||||
"OrderImportRepository.php": {
|
"OrderImportRepository.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 18826,
|
"size": 19810,
|
||||||
"lmtime": 1775064061646,
|
"lmtime": 1775064061646,
|
||||||
"modified": false
|
"modified": true
|
||||||
},
|
},
|
||||||
"OrderImportService.php": {
|
"OrderImportService.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
@@ -2744,15 +2774,15 @@
|
|||||||
},
|
},
|
||||||
"OrdersController.php": {
|
"OrdersController.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 40092,
|
"size": 40622,
|
||||||
"lmtime": 1774907857219,
|
"lmtime": 1775569053376,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"OrdersRepository.php": {
|
"OrdersRepository.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 37722,
|
"size": 40196,
|
||||||
"lmtime": 1774474648512,
|
"lmtime": 1775561791127,
|
||||||
"modified": true
|
"modified": false
|
||||||
},
|
},
|
||||||
"OrderStatusSyncService.php": {
|
"OrderStatusSyncService.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
@@ -2858,9 +2888,9 @@
|
|||||||
},
|
},
|
||||||
"AllegroIntegrationController.php": {
|
"AllegroIntegrationController.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 26289,
|
"size": 26369,
|
||||||
"lmtime": 1775246473042,
|
"lmtime": 1775246473042,
|
||||||
"modified": false
|
"modified": true
|
||||||
},
|
},
|
||||||
"AllegroIntegrationRepository.php": {
|
"AllegroIntegrationRepository.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
@@ -2900,13 +2930,13 @@
|
|||||||
},
|
},
|
||||||
"AllegroStatusMappingController.php": {
|
"AllegroStatusMappingController.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 7597,
|
"size": 5474,
|
||||||
"lmtime": 1773418641524,
|
"lmtime": 1773418641524,
|
||||||
"modified": false
|
"modified": true
|
||||||
},
|
},
|
||||||
"AllegroStatusMappingRepository.php": {
|
"AllegroStatusMappingRepository.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 5642,
|
"size": 10279,
|
||||||
"lmtime": 1772657817169,
|
"lmtime": 1772657817169,
|
||||||
"modified": true
|
"modified": true
|
||||||
},
|
},
|
||||||
@@ -3092,9 +3122,9 @@
|
|||||||
},
|
},
|
||||||
"ShopproIntegrationsController.php": {
|
"ShopproIntegrationsController.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 37809,
|
"size": 41512,
|
||||||
"lmtime": 1773408010714,
|
"lmtime": 1773408010714,
|
||||||
"modified": false
|
"modified": true
|
||||||
},
|
},
|
||||||
"ShopproIntegrationsRepository.php": {
|
"ShopproIntegrationsRepository.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
@@ -3104,13 +3134,13 @@
|
|||||||
},
|
},
|
||||||
"ShopproOrderMapper.php": {
|
"ShopproOrderMapper.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 39067,
|
"size": 40039,
|
||||||
"lmtime": 1775065185208,
|
"lmtime": 1775559420878,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"ShopproOrdersSyncService.php": {
|
"ShopproOrdersSyncService.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 13085,
|
"size": 14572,
|
||||||
"lmtime": 1773418261049,
|
"lmtime": 1773418261049,
|
||||||
"modified": true
|
"modified": true
|
||||||
},
|
},
|
||||||
@@ -3132,11 +3162,17 @@
|
|||||||
"lmtime": 1773418240242,
|
"lmtime": 1773418240242,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
|
"ShopproPullStatusMappingRepository.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 3253,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
"ShopproStatusMappingRepository.php": {
|
"ShopproStatusMappingRepository.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 3222,
|
"size": 4434,
|
||||||
"lmtime": 1772990850231,
|
"lmtime": 1772990850231,
|
||||||
"modified": false
|
"modified": true
|
||||||
},
|
},
|
||||||
"ShopproStatusSyncService.php": {
|
"ShopproStatusSyncService.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
-- Phase 83: Separate pull status mappings table for Allegro
|
||||||
|
-- Pull direction (Allegro -> orderPRO) needs its own table with UNIQUE on allegro_status_code
|
||||||
|
-- because push direction has UNIQUE on orderpro_status_code (one Allegro status per orderPRO status)
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS allegro_order_status_pull_mappings (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
allegro_status_code VARCHAR(100) NOT NULL,
|
||||||
|
allegro_status_name VARCHAR(255) DEFAULT NULL,
|
||||||
|
orderpro_status_code VARCHAR(100) DEFAULT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY allegro_pull_allegro_code_unique (allegro_status_code)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Pre-populate from existing push mappings (reverse: for each allegro_status_code take newest row)
|
||||||
|
INSERT IGNORE INTO allegro_order_status_pull_mappings (allegro_status_code, allegro_status_name, orderpro_status_code)
|
||||||
|
SELECT asm.allegro_status_code, asm.allegro_status_name, asm.orderpro_status_code
|
||||||
|
FROM allegro_order_status_mappings asm
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT allegro_status_code, MAX(id) AS max_id
|
||||||
|
FROM allegro_order_status_mappings
|
||||||
|
WHERE allegro_status_code IS NOT NULL
|
||||||
|
AND allegro_status_code <> ''
|
||||||
|
GROUP BY allegro_status_code
|
||||||
|
) latest ON asm.id = latest.max_id
|
||||||
|
WHERE asm.allegro_status_code IS NOT NULL
|
||||||
|
AND asm.allegro_status_code <> '';
|
||||||
File diff suppressed because one or more lines are too long
@@ -763,8 +763,10 @@ return [
|
|||||||
'save' => 'Zapisz ustawienia',
|
'save' => 'Zapisz ustawienia',
|
||||||
],
|
],
|
||||||
'statuses' => [
|
'statuses' => [
|
||||||
'title' => 'Mapowanie statusow Allegro',
|
'title' => 'Wysylka statusow (orderPRO → Allegro)',
|
||||||
'description' => 'Przypisz kazdemu statusowi orderPRO odpowiadajacy status w Allegro.',
|
'description' => 'Przypisz kazdemu statusowi orderPRO odpowiadajacy status w Allegro. Mapowanie uzywane przy synchronizacji statusow z orderPRO do Allegro.',
|
||||||
|
'pull_title' => 'Mapowanie przy imporcie (Allegro → orderPRO)',
|
||||||
|
'pull_description' => 'Przypisz kazdemu statusowi Allegro odpowiadajacy status w orderPRO. Mapowanie uzywane przy imporcie zamowien z Allegro.',
|
||||||
'list_title' => 'Aktualne mapowania',
|
'list_title' => 'Aktualne mapowania',
|
||||||
'empty' => 'Brak zapisanych mapowan statusow Allegro.',
|
'empty' => 'Brak zapisanych mapowan statusow Allegro.',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
@@ -796,6 +798,7 @@ return [
|
|||||||
'mapping_not_found' => 'Nie znaleziono wskazanego mapowania statusu.',
|
'mapping_not_found' => 'Nie znaleziono wskazanego mapowania statusu.',
|
||||||
'saved' => 'Mapowanie statusu Allegro zostalo zapisane.',
|
'saved' => 'Mapowanie statusu Allegro zostalo zapisane.',
|
||||||
'saved_bulk' => 'Mapowania statusow Allegro zostaly zapisane.',
|
'saved_bulk' => 'Mapowania statusow Allegro zostaly zapisane.',
|
||||||
|
'saved_pull' => 'Mapowania importu statusow Allegro zostaly zapisane.',
|
||||||
'save_failed' => 'Nie udalo sie zapisac mapowania statusu Allegro.',
|
'save_failed' => 'Nie udalo sie zapisac mapowania statusu Allegro.',
|
||||||
'deleted' => 'Mapowanie statusu Allegro zostalo usuniete.',
|
'deleted' => 'Mapowanie statusu Allegro zostalo usuniete.',
|
||||||
'delete_failed' => 'Nie udalo sie usunac mapowania statusu Allegro.',
|
'delete_failed' => 'Nie udalo sie usunac mapowania statusu Allegro.',
|
||||||
|
|||||||
@@ -1446,10 +1446,41 @@ details[open] > .order-statuses-side__title .order-statuses-side__arrow {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 6px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #475569;
|
color: #475569;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 3px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__count {
|
||||||
|
min-width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--group-color, #64748b);
|
||||||
|
padding: 1px 6px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active > .order-status-group__name {
|
||||||
|
background: rgba(15, 23, 42, 0.06);
|
||||||
|
color: #0f172a;
|
||||||
|
border-left-color: var(--group-color, #64748b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ $eventLabels = [
|
|||||||
'payment.status_changed' => 'Zmiana statusu platnosci',
|
'payment.status_changed' => 'Zmiana statusu platnosci',
|
||||||
'order.status_changed' => 'Zmiana statusu zamowienia',
|
'order.status_changed' => 'Zmiana statusu zamowienia',
|
||||||
'order.status_aged' => 'Minelo X dni od zmiany statusu',
|
'order.status_aged' => 'Minelo X dni od zmiany statusu',
|
||||||
|
'order.imported' => 'Pobranie zamowienia',
|
||||||
];
|
];
|
||||||
|
|
||||||
$recipientLabels = [
|
$recipientLabels = [
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ $eventLabels = [
|
|||||||
'payment.status_changed' => 'Zmiana statusu platnosci',
|
'payment.status_changed' => 'Zmiana statusu platnosci',
|
||||||
'order.status_changed' => 'Zmiana statusu zamowienia',
|
'order.status_changed' => 'Zmiana statusu zamowienia',
|
||||||
'order.status_aged' => 'Minelo X dni od zmiany statusu',
|
'order.status_aged' => 'Minelo X dni od zmiany statusu',
|
||||||
|
'order.imported' => 'Pobranie zamowienia',
|
||||||
];
|
];
|
||||||
|
|
||||||
$statusLabels = [
|
$statusLabels = [
|
||||||
|
|||||||
@@ -13,9 +13,21 @@ $panelTitle = trim((string) ($statusPanelTitle ?? 'Statusy'));
|
|||||||
<div class="order-statuses-side__body">
|
<div class="order-statuses-side__body">
|
||||||
<?php foreach ($panelItems as $group): ?>
|
<?php foreach ($panelItems as $group): ?>
|
||||||
<?php $groupItems = is_array($group['items'] ?? null) ? $group['items'] : []; ?>
|
<?php $groupItems = is_array($group['items'] ?? null) ? $group['items'] : []; ?>
|
||||||
<div class="order-status-group">
|
<?php
|
||||||
|
$groupActiveClass = !empty($group['is_active_group']) ? ' is-active' : '';
|
||||||
|
$groupColor = (string) ($group['color_hex'] ?? '#64748b');
|
||||||
|
?>
|
||||||
|
<div class="order-status-group<?= $e($groupActiveClass) ?>">
|
||||||
<?php if ((string) ($group['name'] ?? '') !== ''): ?>
|
<?php if ((string) ($group['name'] ?? '') !== ''): ?>
|
||||||
<div class="order-status-group__name"><?= $e((string) ($group['name'] ?? '')) ?></div>
|
<?php $groupUrl = trim((string) ($group['group_url'] ?? '')); ?>
|
||||||
|
<?php if ($groupUrl !== ''): ?>
|
||||||
|
<a href="<?= $e($groupUrl) ?>" class="order-status-group__name" style="--group-color: <?= $e($groupColor) ?>;">
|
||||||
|
<span class="order-status-group__label"><?= $e((string) ($group['name'] ?? '')) ?></span>
|
||||||
|
<span class="order-status-group__count"><?= $e((string) ((int) ($group['group_count'] ?? 0))) ?></span>
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="order-status-group__name"><?= $e((string) ($group['name'] ?? '')) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php foreach ($groupItems as $item): ?>
|
<?php foreach ($groupItems as $item): ?>
|
||||||
<?php
|
<?php
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ $statusSyncIntervalMinutes = max(1, (int) ($statusSyncIntervalMinutes ?? 15));
|
|||||||
$statusMappings = is_array($statusMappings ?? null) ? $statusMappings : [];
|
$statusMappings = is_array($statusMappings ?? null) ? $statusMappings : [];
|
||||||
$orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : [];
|
$orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : [];
|
||||||
$allegroStatuses = is_array($allegroStatuses ?? null) ? $allegroStatuses : [];
|
$allegroStatuses = is_array($allegroStatuses ?? null) ? $allegroStatuses : [];
|
||||||
|
$pullStatusMappings = is_array($pullStatusMappings ?? null) ? $pullStatusMappings : [];
|
||||||
$allegroMappingIndex = [];
|
$allegroMappingIndex = [];
|
||||||
foreach ($statusMappings as $m) {
|
foreach ($statusMappings as $m) {
|
||||||
$opCode = strtolower(trim((string) ($m['orderpro_status_code'] ?? '')));
|
$opCode = strtolower(trim((string) ($m['orderpro_status_code'] ?? '')));
|
||||||
@@ -25,6 +26,13 @@ foreach ($statusMappings as $m) {
|
|||||||
$allegroMappingIndex[$opCode] = $m;
|
$allegroMappingIndex[$opCode] = $m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$pullMappingIndex = [];
|
||||||
|
foreach ($pullStatusMappings as $pm) {
|
||||||
|
$aCode = strtolower(trim((string) ($pm['allegro_status_code'] ?? '')));
|
||||||
|
if ($aCode !== '') {
|
||||||
|
$pullMappingIndex[$aCode] = $pm;
|
||||||
|
}
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<section class="card">
|
<section class="card">
|
||||||
@@ -216,6 +224,68 @@ foreach ($statusMappings as $m) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="mt-24">
|
||||||
|
<h3 class="section-title"><?= $e($t('settings.allegro.statuses.pull_title')) ?></h3>
|
||||||
|
<p class="muted mt-12"><?= $e($t('settings.allegro.statuses.pull_description')) ?></p>
|
||||||
|
|
||||||
|
<form action="/settings/integrations/allegro/statuses/save-pull" method="post" class="mt-12">
|
||||||
|
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||||
|
<div class="table-wrap mt-12">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><?= $e($t('settings.allegro.statuses.fields.allegro_status')) ?></th>
|
||||||
|
<th><?= $e($t('settings.allegro.statuses.fields.orderpro_status_code')) ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if ($pullStatusMappings === []): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="muted"><?= $e($t('settings.allegro.statuses.empty')) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($pullStatusMappings as $pullMapping): ?>
|
||||||
|
<?php
|
||||||
|
$aCode = strtolower(trim((string) ($pullMapping['allegro_status_code'] ?? '')));
|
||||||
|
if ($aCode === '') continue;
|
||||||
|
$aName = (string) ($pullMapping['allegro_status_name'] ?? '');
|
||||||
|
$selectedOrderproCode = strtolower(trim((string) ($pullMapping['orderpro_status_code'] ?? '')));
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<?= $e($aName !== '' ? $aName : $aCode) ?> <code class="muted"><?= $e($aCode) ?></code>
|
||||||
|
<input type="hidden" name="allegro_status_code[]" value="<?= $e($aCode) ?>">
|
||||||
|
<input type="hidden" name="allegro_status_name[]" value="<?= $e($aName) ?>">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select class="form-control" name="orderpro_status_code[]">
|
||||||
|
<option value=""><?= $e($t('settings.order_statuses.fields.no_mapping')) ?></option>
|
||||||
|
<?php foreach ($orderproStatuses as $opStatus): ?>
|
||||||
|
<?php
|
||||||
|
$opCode = strtolower(trim((string) ($opStatus['code'] ?? '')));
|
||||||
|
$opName = (string) ($opStatus['name'] ?? $opCode);
|
||||||
|
if ($opCode === '') continue;
|
||||||
|
?>
|
||||||
|
<option value="<?= $e($opCode) ?>"<?= $selectedOrderproCode === $opCode ? ' selected' : '' ?>>
|
||||||
|
<?= $e($opName) ?> (<?= $e($opCode) ?>)
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php if ($pullStatusMappings !== []): ?>
|
||||||
|
<div class="form-actions mt-12">
|
||||||
|
<button type="submit" class="btn btn--primary"><?= $e($t('settings.allegro.statuses.actions.save_bulk')) ?></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content-tab-panel<?= $activeTab === 'settings' ? ' is-active' : '' ?>" data-tab-panel="allegro-tab-settings">
|
<div class="content-tab-panel<?= $activeTab === 'settings' ? ' is-active' : '' ?>" data-tab-panel="allegro-tab-settings">
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use App\Modules\Settings\AllegroIntegrationController;
|
|||||||
use App\Modules\Settings\AllegroIntegrationRepository;
|
use App\Modules\Settings\AllegroIntegrationRepository;
|
||||||
use App\Modules\Settings\AllegroOAuthClient;
|
use App\Modules\Settings\AllegroOAuthClient;
|
||||||
use App\Modules\Settings\AllegroOrderImportService;
|
use App\Modules\Settings\AllegroOrderImportService;
|
||||||
|
use App\Modules\Settings\AllegroPullStatusMappingRepository;
|
||||||
use App\Modules\Settings\AllegroStatusDiscoveryService;
|
use App\Modules\Settings\AllegroStatusDiscoveryService;
|
||||||
use App\Modules\Settings\AllegroStatusMappingController;
|
use App\Modules\Settings\AllegroStatusMappingController;
|
||||||
use App\Modules\Settings\AllegroTokenManager;
|
use App\Modules\Settings\AllegroTokenManager;
|
||||||
@@ -99,16 +100,19 @@ return static function (Application $app): void {
|
|||||||
$app->db(),
|
$app->db(),
|
||||||
(string) $app->config('app.integrations.secret', '')
|
(string) $app->config('app.integrations.secret', '')
|
||||||
);
|
);
|
||||||
|
$allegroPullStatusMappingRepository = new AllegroPullStatusMappingRepository($app->db());
|
||||||
$allegroStatusDiscoveryService = new AllegroStatusDiscoveryService(
|
$allegroStatusDiscoveryService = new AllegroStatusDiscoveryService(
|
||||||
$allegroTokenManager,
|
$allegroTokenManager,
|
||||||
new AllegroApiClient(),
|
new AllegroApiClient(),
|
||||||
$allegroStatusMappingRepository
|
$allegroStatusMappingRepository,
|
||||||
|
$allegroPullStatusMappingRepository
|
||||||
);
|
);
|
||||||
$allegroStatusMappingController = new AllegroStatusMappingController(
|
$allegroStatusMappingController = new AllegroStatusMappingController(
|
||||||
$translator,
|
$translator,
|
||||||
$allegroStatusMappingRepository,
|
$allegroStatusMappingRepository,
|
||||||
$app->orderStatuses(),
|
$app->orderStatuses(),
|
||||||
$allegroStatusDiscoveryService
|
$allegroStatusDiscoveryService,
|
||||||
|
$allegroPullStatusMappingRepository
|
||||||
);
|
);
|
||||||
$allegroDeliveryMappingController = new AllegroDeliveryMappingController(
|
$allegroDeliveryMappingController = new AllegroDeliveryMappingController(
|
||||||
$translator,
|
$translator,
|
||||||
@@ -125,6 +129,7 @@ return static function (Application $app): void {
|
|||||||
$auth,
|
$auth,
|
||||||
$allegroIntegrationRepository,
|
$allegroIntegrationRepository,
|
||||||
$allegroStatusMappingRepository,
|
$allegroStatusMappingRepository,
|
||||||
|
$allegroPullStatusMappingRepository,
|
||||||
$app->orderStatuses(),
|
$app->orderStatuses(),
|
||||||
$cronRepository,
|
$cronRepository,
|
||||||
$allegroOAuthClient,
|
$allegroOAuthClient,
|
||||||
@@ -134,7 +139,8 @@ return static function (Application $app): void {
|
|||||||
new AllegroApiClient(),
|
new AllegroApiClient(),
|
||||||
new OrderImportRepository($app->db()),
|
new OrderImportRepository($app->db()),
|
||||||
$allegroStatusMappingRepository,
|
$allegroStatusMappingRepository,
|
||||||
new OrdersRepository($app->db())
|
new OrdersRepository($app->db()),
|
||||||
|
new AllegroPullStatusMappingRepository($app->db())
|
||||||
),
|
),
|
||||||
$allegroStatusDiscoveryService,
|
$allegroStatusDiscoveryService,
|
||||||
(string) $app->config('app.url', ''),
|
(string) $app->config('app.url', ''),
|
||||||
@@ -438,6 +444,7 @@ return static function (Application $app): void {
|
|||||||
$router->post('/settings/integrations/allegro/statuses/save', [$allegroStatusMappingController, 'saveStatusMapping'], [$authMiddleware]);
|
$router->post('/settings/integrations/allegro/statuses/save', [$allegroStatusMappingController, 'saveStatusMapping'], [$authMiddleware]);
|
||||||
$router->post('/settings/integrations/allegro/statuses/save-bulk', [$allegroStatusMappingController, 'saveStatusMappingsBulk'], [$authMiddleware]);
|
$router->post('/settings/integrations/allegro/statuses/save-bulk', [$allegroStatusMappingController, 'saveStatusMappingsBulk'], [$authMiddleware]);
|
||||||
$router->post('/settings/integrations/allegro/statuses/delete', [$allegroStatusMappingController, 'deleteStatusMapping'], [$authMiddleware]);
|
$router->post('/settings/integrations/allegro/statuses/delete', [$allegroStatusMappingController, 'deleteStatusMapping'], [$authMiddleware]);
|
||||||
|
$router->post('/settings/integrations/allegro/statuses/save-pull', [$allegroStatusMappingController, 'savePullStatusMappings'], [$authMiddleware]);
|
||||||
$router->post('/settings/integrations/allegro/statuses/sync', [$allegroStatusMappingController, 'syncStatusesFromAllegro'], [$authMiddleware]);
|
$router->post('/settings/integrations/allegro/statuses/sync', [$allegroStatusMappingController, 'syncStatusesFromAllegro'], [$authMiddleware]);
|
||||||
$router->post('/settings/integrations/allegro/delivery/save', [$allegroDeliveryMappingController, 'saveDeliveryMappings'], [$authMiddleware]);
|
$router->post('/settings/integrations/allegro/delivery/save', [$allegroDeliveryMappingController, 'saveDeliveryMappings'], [$authMiddleware]);
|
||||||
$router->get('/settings/integrations/allegro/oauth/callback', [$allegroIntegrationController, 'oauthCallback']);
|
$router->get('/settings/integrations/allegro/oauth/callback', [$allegroIntegrationController, 'oauthCallback']);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use Throwable;
|
|||||||
final class AutomationController
|
final class AutomationController
|
||||||
{
|
{
|
||||||
private const HISTORY_PER_PAGE = 25;
|
private const HISTORY_PER_PAGE = 25;
|
||||||
private const ALLOWED_EVENTS = ['receipt.created', 'shipment.created', 'shipment.status_changed', 'payment.status_changed', 'order.status_changed', 'order.status_aged'];
|
private const ALLOWED_EVENTS = ['receipt.created', 'shipment.created', 'shipment.status_changed', 'payment.status_changed', 'order.status_changed', 'order.status_aged', 'order.imported'];
|
||||||
private const ALLOWED_CONDITION_TYPES = ['integration', 'shipment_status', 'payment_status', 'order_status', 'days_in_status'];
|
private const ALLOWED_CONDITION_TYPES = ['integration', 'shipment_status', 'payment_status', 'order_status', 'days_in_status'];
|
||||||
private const PAYMENT_STATUS_OPTIONS = [
|
private const PAYMENT_STATUS_OPTIONS = [
|
||||||
'0' => 'Nieopłacone',
|
'0' => 'Nieopłacone',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ use App\Modules\Settings\AllegroOAuthClient;
|
|||||||
use App\Modules\Settings\AllegroOrderImportService;
|
use App\Modules\Settings\AllegroOrderImportService;
|
||||||
use App\Modules\Settings\AllegroOrdersSyncService;
|
use App\Modules\Settings\AllegroOrdersSyncService;
|
||||||
use App\Modules\Settings\AllegroOrderSyncStateRepository;
|
use App\Modules\Settings\AllegroOrderSyncStateRepository;
|
||||||
|
use App\Modules\Settings\AllegroPullStatusMappingRepository;
|
||||||
use App\Modules\Settings\AllegroStatusMappingRepository;
|
use App\Modules\Settings\AllegroStatusMappingRepository;
|
||||||
use App\Modules\Settings\AllegroStatusSyncService;
|
use App\Modules\Settings\AllegroStatusSyncService;
|
||||||
use App\Modules\Settings\AllegroTokenManager;
|
use App\Modules\Settings\AllegroTokenManager;
|
||||||
@@ -70,13 +71,18 @@ final class CronHandlerFactory
|
|||||||
$ordersRepository = new OrdersRepository($this->db);
|
$ordersRepository = new OrdersRepository($this->db);
|
||||||
$allegroSyncStateRepository = new AllegroOrderSyncStateRepository($this->db);
|
$allegroSyncStateRepository = new AllegroOrderSyncStateRepository($this->db);
|
||||||
|
|
||||||
|
$allegroPullStatusMappingRepo = new AllegroPullStatusMappingRepository($this->db);
|
||||||
|
$automationService = $this->buildAutomationService($ordersRepository);
|
||||||
|
|
||||||
$orderImportService = new AllegroOrderImportService(
|
$orderImportService = new AllegroOrderImportService(
|
||||||
$integrationRepository,
|
$integrationRepository,
|
||||||
$tokenManager,
|
$tokenManager,
|
||||||
$apiClient,
|
$apiClient,
|
||||||
new OrderImportRepository($this->db),
|
new OrderImportRepository($this->db),
|
||||||
$statusMappingRepository,
|
$statusMappingRepository,
|
||||||
$ordersRepository
|
$ordersRepository,
|
||||||
|
$allegroPullStatusMappingRepo,
|
||||||
|
$automationService
|
||||||
);
|
);
|
||||||
|
|
||||||
$ordersSyncService = new AllegroOrdersSyncService(
|
$ordersSyncService = new AllegroOrdersSyncService(
|
||||||
@@ -101,7 +107,8 @@ final class CronHandlerFactory
|
|||||||
$ordersRepository,
|
$ordersRepository,
|
||||||
new ShopproOrderMapper(),
|
new ShopproOrderMapper(),
|
||||||
new ShopproProductImageResolver($shopproApiClient),
|
new ShopproProductImageResolver($shopproApiClient),
|
||||||
$shopproPullStatusMappingRepo
|
$shopproPullStatusMappingRepo,
|
||||||
|
$automationService
|
||||||
);
|
);
|
||||||
$shopproStatusSyncService = new ShopproStatusSyncService(
|
$shopproStatusSyncService = new ShopproStatusSyncService(
|
||||||
$shopproIntegrationsRepo,
|
$shopproIntegrationsRepo,
|
||||||
@@ -111,7 +118,6 @@ final class CronHandlerFactory
|
|||||||
$shopproStatusMappingRepo,
|
$shopproStatusMappingRepo,
|
||||||
$this->db
|
$this->db
|
||||||
);
|
);
|
||||||
$automationService = $this->buildAutomationService($ordersRepository);
|
|
||||||
|
|
||||||
$shopproPaymentSyncService = new ShopproPaymentStatusSyncService(
|
$shopproPaymentSyncService = new ShopproPaymentStatusSyncService(
|
||||||
$shopproIntegrationsRepo,
|
$shopproIntegrationsRepo,
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ final class OrdersController
|
|||||||
'search' => trim((string) $request->input('search', '')),
|
'search' => trim((string) $request->input('search', '')),
|
||||||
'source' => trim((string) $request->input('source', '')),
|
'source' => trim((string) $request->input('source', '')),
|
||||||
'status' => trim((string) $request->input('status', '')),
|
'status' => trim((string) $request->input('status', '')),
|
||||||
|
'status_group' => trim((string) $request->input('status_group', '')),
|
||||||
'payment_status' => trim((string) $request->input('payment_status', '')),
|
'payment_status' => trim((string) $request->input('payment_status', '')),
|
||||||
'date_from' => trim((string) $request->input('date_from', '')),
|
'date_from' => trim((string) $request->input('date_from', '')),
|
||||||
'date_to' => trim((string) $request->input('date_to', '')),
|
'date_to' => trim((string) $request->input('date_to', '')),
|
||||||
@@ -66,7 +67,7 @@ final class OrdersController
|
|||||||
$statusLabelMap = $this->statusLabelMap($statusConfig);
|
$statusLabelMap = $this->statusLabelMap($statusConfig);
|
||||||
$statusColorMap = $this->statusColorMap($statusConfig);
|
$statusColorMap = $this->statusColorMap($statusConfig);
|
||||||
$statusOptions = $this->buildStatusFilterOptions($this->orders->statusOptions(), $statusLabelMap);
|
$statusOptions = $this->buildStatusFilterOptions($this->orders->statusOptions(), $statusLabelMap);
|
||||||
$statusPanel = $this->buildStatusPanel($statusConfig, $statusCounts, $filters['status'], $filters);
|
$statusPanel = $this->buildStatusPanel($statusConfig, $statusCounts, $filters['status'], $filters, $filters['status_group']);
|
||||||
|
|
||||||
$tableRows = array_map(fn (array $row): array => $this->toTableRow($row, $statusLabelMap, $statusColorMap), (array) ($result['items'] ?? []));
|
$tableRows = array_map(fn (array $row): array => $this->toTableRow($row, $statusLabelMap, $statusColorMap), (array) ($result['items'] ?? []));
|
||||||
|
|
||||||
@@ -438,7 +439,7 @@ final class OrdersController
|
|||||||
* @param array<string, int> $counts
|
* @param array<string, int> $counts
|
||||||
* @return array<int, array<string, mixed>>
|
* @return array<int, array<string, mixed>>
|
||||||
*/
|
*/
|
||||||
private function buildStatusPanel(array $config, array $counts, string $currentStatusCode, array $query = []): array
|
private function buildStatusPanel(array $config, array $counts, string $currentStatusCode, array $query = [], string $currentStatusGroup = ''): array
|
||||||
{
|
{
|
||||||
$allCount = 0;
|
$allCount = 0;
|
||||||
foreach ($counts as $count) {
|
foreach ($counts as $count) {
|
||||||
@@ -451,7 +452,7 @@ final class OrdersController
|
|||||||
'code' => '',
|
'code' => '',
|
||||||
'label' => 'Wszystkie',
|
'label' => 'Wszystkie',
|
||||||
'count' => $allCount,
|
'count' => $allCount,
|
||||||
'is_active' => trim($currentStatusCode) === '',
|
'is_active' => trim($currentStatusCode) === '' && trim($currentStatusGroup) === '',
|
||||||
'tone' => 'neutral',
|
'tone' => 'neutral',
|
||||||
'color_hex' => '#64748b',
|
'color_hex' => '#64748b',
|
||||||
'url' => $this->statusFilterUrl($query, ''),
|
'url' => $this->statusFilterUrl($query, ''),
|
||||||
@@ -461,17 +462,22 @@ final class OrdersController
|
|||||||
foreach ($config as $group) {
|
foreach ($config as $group) {
|
||||||
$items = [];
|
$items = [];
|
||||||
$groupColor = StringHelper::normalizeColorHex((string) ($group['color_hex'] ?? '#64748b'));
|
$groupColor = StringHelper::normalizeColorHex((string) ($group['color_hex'] ?? '#64748b'));
|
||||||
|
$groupId = (string) ((int) ($group['id'] ?? 0));
|
||||||
$groupItems = is_array($group['items'] ?? null) ? $group['items'] : [];
|
$groupItems = is_array($group['items'] ?? null) ? $group['items'] : [];
|
||||||
|
$isActiveGroup = $currentStatusGroup !== '' && $currentStatusGroup === $groupId;
|
||||||
|
$groupCount = 0;
|
||||||
foreach ($groupItems as $status) {
|
foreach ($groupItems as $status) {
|
||||||
$code = strtolower(trim((string) ($status['code'] ?? '')));
|
$code = strtolower(trim((string) ($status['code'] ?? '')));
|
||||||
if ($code === '') {
|
if ($code === '') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
$statusCount = (int) ($counts[$code] ?? 0);
|
||||||
|
$groupCount += $statusCount;
|
||||||
$items[] = [
|
$items[] = [
|
||||||
'code' => $code,
|
'code' => $code,
|
||||||
'label' => (string) ($status['name'] ?? $code),
|
'label' => (string) ($status['name'] ?? $code),
|
||||||
'count' => (int) ($counts[$code] ?? 0),
|
'count' => $statusCount,
|
||||||
'is_active' => trim(strtolower($currentStatusCode)) === $code,
|
'is_active' => !$isActiveGroup && trim(strtolower($currentStatusCode)) === $code,
|
||||||
'tone' => $this->statusTone($code),
|
'tone' => $this->statusTone($code),
|
||||||
'color_hex' => $groupColor,
|
'color_hex' => $groupColor,
|
||||||
'url' => $this->statusFilterUrl($query, $code),
|
'url' => $this->statusFilterUrl($query, $code),
|
||||||
@@ -483,6 +489,10 @@ final class OrdersController
|
|||||||
$result[] = [
|
$result[] = [
|
||||||
'name' => (string) ($group['name'] ?? ''),
|
'name' => (string) ($group['name'] ?? ''),
|
||||||
'color_hex' => $groupColor,
|
'color_hex' => $groupColor,
|
||||||
|
'group_id' => $groupId,
|
||||||
|
'group_url' => $this->groupFilterUrl($query, $groupId),
|
||||||
|
'group_count' => $groupCount,
|
||||||
|
'is_active_group' => $isActiveGroup,
|
||||||
'items' => $items,
|
'items' => $items,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -531,6 +541,7 @@ final class OrdersController
|
|||||||
private function statusFilterUrl(array $query, string $statusCode): string
|
private function statusFilterUrl(array $query, string $statusCode): string
|
||||||
{
|
{
|
||||||
$params = $query;
|
$params = $query;
|
||||||
|
unset($params['status_group']);
|
||||||
if ($statusCode === '') {
|
if ($statusCode === '') {
|
||||||
unset($params['status']);
|
unset($params['status']);
|
||||||
} else {
|
} else {
|
||||||
@@ -550,6 +561,29 @@ final class OrdersController
|
|||||||
return $qs === '' ? '/orders/list' : '/orders/list?' . $qs;
|
return $qs === '' ? '/orders/list' : '/orders/list?' . $qs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function groupFilterUrl(array $query, string $groupId): string
|
||||||
|
{
|
||||||
|
$params = $query;
|
||||||
|
unset($params['status']);
|
||||||
|
if ($groupId === '' || $groupId === '0') {
|
||||||
|
unset($params['status_group']);
|
||||||
|
} else {
|
||||||
|
$params['status_group'] = $groupId;
|
||||||
|
}
|
||||||
|
$params['page'] = 1;
|
||||||
|
|
||||||
|
$clean = [];
|
||||||
|
foreach ($params as $key => $value) {
|
||||||
|
if ($value === '' || $value === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$clean[(string) $key] = (string) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$qs = http_build_query($clean);
|
||||||
|
return $qs === '' ? '/orders/list' : '/orders/list?' . $qs;
|
||||||
|
}
|
||||||
|
|
||||||
private function statusTone(string $statusCode): string
|
private function statusTone(string $statusCode): string
|
||||||
{
|
{
|
||||||
$code = strtolower(trim($statusCode));
|
$code = strtolower(trim($statusCode));
|
||||||
|
|||||||
@@ -111,8 +111,20 @@ final class OrdersRepository
|
|||||||
$params['source'] = $source;
|
$params['source'] = $source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$statusGroup = trim((string) ($filters['status_group'] ?? ''));
|
||||||
$status = trim((string) ($filters['status'] ?? ''));
|
$status = trim((string) ($filters['status'] ?? ''));
|
||||||
if ($status !== '') {
|
if ($statusGroup !== '' && ctype_digit($statusGroup)) {
|
||||||
|
$groupCodes = $this->statusCodesByGroupId((int) $statusGroup);
|
||||||
|
if ($groupCodes !== []) {
|
||||||
|
$placeholders = [];
|
||||||
|
foreach ($groupCodes as $i => $code) {
|
||||||
|
$key = 'sg' . $i;
|
||||||
|
$placeholders[] = ':' . $key;
|
||||||
|
$params[$key] = $code;
|
||||||
|
}
|
||||||
|
$where[] = $effectiveStatusSql . ' IN (' . implode(', ', $placeholders) . ')';
|
||||||
|
}
|
||||||
|
} elseif ($status !== '') {
|
||||||
$where[] = $effectiveStatusSql . ' = :status';
|
$where[] = $effectiveStatusSql . ' = :status';
|
||||||
$params['status'] = $status;
|
$params['status'] = $status;
|
||||||
}
|
}
|
||||||
@@ -398,6 +410,7 @@ final class OrdersRepository
|
|||||||
|
|
||||||
if (!isset($groupMap[$groupId])) {
|
if (!isset($groupMap[$groupId])) {
|
||||||
$groupMap[$groupId] = [
|
$groupMap[$groupId] = [
|
||||||
|
'id' => $groupId,
|
||||||
'name' => trim((string) ($row['group_name'] ?? '')),
|
'name' => trim((string) ($row['group_name'] ?? '')),
|
||||||
'color_hex' => StringHelper::normalizeColorHex((string) ($row['group_color_hex'] ?? '#64748b')),
|
'color_hex' => StringHelper::normalizeColorHex((string) ($row['group_color_hex'] ?? '#64748b')),
|
||||||
'items' => [],
|
'items' => [],
|
||||||
@@ -418,6 +431,35 @@ final class OrdersRepository
|
|||||||
return array_values($groupMap);
|
return array_values($groupMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
private function statusCodesByGroupId(int $groupId): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
'SELECT code FROM order_statuses WHERE group_id = :gid AND is_active = 1 ORDER BY sort_order ASC'
|
||||||
|
);
|
||||||
|
$stmt->execute(['gid' => $groupId]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
} catch (Throwable) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($rows)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$codes = [];
|
||||||
|
foreach ($rows as $code) {
|
||||||
|
$trimmed = strtolower(trim((string) $code));
|
||||||
|
if ($trimmed !== '') {
|
||||||
|
$codes[] = $trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $codes;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, mixed>|null
|
* @return array<string, mixed>|null
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ final class AllegroIntegrationController
|
|||||||
private readonly AuthService $auth,
|
private readonly AuthService $auth,
|
||||||
private readonly AllegroIntegrationRepository $repository,
|
private readonly AllegroIntegrationRepository $repository,
|
||||||
private readonly AllegroStatusMappingRepository $statusMappings,
|
private readonly AllegroStatusMappingRepository $statusMappings,
|
||||||
|
private readonly AllegroPullStatusMappingRepository $pullStatusMappings,
|
||||||
private readonly OrderStatusRepository $orderStatuses,
|
private readonly OrderStatusRepository $orderStatuses,
|
||||||
private readonly CronRepository $cronRepository,
|
private readonly CronRepository $cronRepository,
|
||||||
private readonly AllegroOAuthClient $oauthClient,
|
private readonly AllegroOAuthClient $oauthClient,
|
||||||
@@ -95,6 +96,7 @@ final class AllegroIntegrationController
|
|||||||
'statusSyncDirection' => $statusSyncDirection,
|
'statusSyncDirection' => $statusSyncDirection,
|
||||||
'statusSyncIntervalMinutes' => $statusSyncIntervalMinutes,
|
'statusSyncIntervalMinutes' => $statusSyncIntervalMinutes,
|
||||||
'statusMappings' => $this->statusMappings->listMappings(),
|
'statusMappings' => $this->statusMappings->listMappings(),
|
||||||
|
'pullStatusMappings' => $this->pullStatusMappings->listAll(),
|
||||||
'orderproStatuses' => $this->orderStatuses->listStatuses(),
|
'orderproStatuses' => $this->orderStatuses->listStatuses(),
|
||||||
'allegroStatuses' => $this->statusMappings->listExternalStatuses(),
|
'allegroStatuses' => $this->statusMappings->listExternalStatuses(),
|
||||||
'defaultRedirectUri' => $defaultRedirectUri,
|
'defaultRedirectUri' => $defaultRedirectUri,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use App\Modules\Orders\OrderImportRepository;
|
|||||||
use App\Modules\Orders\OrdersRepository;
|
use App\Modules\Orders\OrdersRepository;
|
||||||
use App\Core\Constants\IntegrationSources;
|
use App\Core\Constants\IntegrationSources;
|
||||||
use App\Core\Exceptions\AllegroApiException;
|
use App\Core\Exceptions\AllegroApiException;
|
||||||
|
use App\Modules\Automation\AutomationService;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
@@ -25,7 +26,9 @@ final class AllegroOrderImportService
|
|||||||
private readonly AllegroApiClient $apiClient,
|
private readonly AllegroApiClient $apiClient,
|
||||||
private readonly OrderImportRepository $orders,
|
private readonly OrderImportRepository $orders,
|
||||||
private readonly AllegroStatusMappingRepository $statusMappings,
|
private readonly AllegroStatusMappingRepository $statusMappings,
|
||||||
private readonly OrdersRepository $ordersRepository
|
private readonly OrdersRepository $ordersRepository,
|
||||||
|
private readonly ?AllegroPullStatusMappingRepository $pullStatusMappings = null,
|
||||||
|
private readonly ?AutomationService $automationService = null
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +95,14 @@ final class AllegroOrderImportService
|
|||||||
'Allegro'
|
'Allegro'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->automationService !== null) {
|
||||||
|
$this->automationService->trigger('order.imported', $savedOrderId, [
|
||||||
|
'source' => IntegrationSources::ALLEGRO,
|
||||||
|
'created' => $wasCreated,
|
||||||
|
'integration_id' => (int) ($mapped['order']['integration_id'] ?? 0),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -135,7 +146,12 @@ final class AllegroOrderImportService
|
|||||||
$status = trim((string) ($payload['status'] ?? ''));
|
$status = trim((string) ($payload['status'] ?? ''));
|
||||||
$fulfillmentStatus = trim((string) ($payload['fulfillment']['status'] ?? ''));
|
$fulfillmentStatus = trim((string) ($payload['fulfillment']['status'] ?? ''));
|
||||||
$rawAllegroStatus = strtolower($fulfillmentStatus !== '' ? $fulfillmentStatus : $status);
|
$rawAllegroStatus = strtolower($fulfillmentStatus !== '' ? $fulfillmentStatus : $status);
|
||||||
$mappedOrderproStatus = $this->statusMappings->findMappedOrderproStatusCode($rawAllegroStatus);
|
$mappedOrderproStatus = $this->pullStatusMappings !== null
|
||||||
|
? $this->pullStatusMappings->findMappedStatusCode($rawAllegroStatus)
|
||||||
|
: $this->statusMappings->findMappedOrderproStatusCode($rawAllegroStatus);
|
||||||
|
if ($mappedOrderproStatus === null && $this->pullStatusMappings !== null) {
|
||||||
|
$this->pullStatusMappings->upsertDiscoveredStatus($rawAllegroStatus);
|
||||||
|
}
|
||||||
$externalStatus = $mappedOrderproStatus !== null ? $mappedOrderproStatus : $rawAllegroStatus;
|
$externalStatus = $mappedOrderproStatus !== null ? $mappedOrderproStatus : $rawAllegroStatus;
|
||||||
$paymentStatusRaw = strtolower(trim((string) ($payload['payment']['status'] ?? '')));
|
$paymentStatusRaw = strtolower(trim((string) ($payload['payment']['status'] ?? '')));
|
||||||
|
|
||||||
|
|||||||
156
src/Modules/Settings/AllegroPullStatusMappingRepository.php
Normal file
156
src/Modules/Settings/AllegroPullStatusMappingRepository.php
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Modules\Settings;
|
||||||
|
|
||||||
|
use App\Core\Support\StringHelper;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
final class AllegroPullStatusMappingRepository
|
||||||
|
{
|
||||||
|
public function __construct(private readonly PDO $pdo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, array{allegro_status_code:string,allegro_status_name:string,orderpro_status_code:string}>
|
||||||
|
*/
|
||||||
|
public function listAll(): array
|
||||||
|
{
|
||||||
|
$statement = $this->pdo->query(
|
||||||
|
'SELECT allegro_status_code, allegro_status_name, orderpro_status_code
|
||||||
|
FROM allegro_order_status_pull_mappings
|
||||||
|
ORDER BY allegro_status_code ASC'
|
||||||
|
);
|
||||||
|
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!is_array($rows)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
if (!is_array($row)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allegroCode = strtolower(trim((string) ($row['allegro_status_code'] ?? '')));
|
||||||
|
if ($allegroCode === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[] = [
|
||||||
|
'allegro_status_code' => $allegroCode,
|
||||||
|
'allegro_status_name' => trim((string) ($row['allegro_status_name'] ?? '')),
|
||||||
|
'orderpro_status_code' => strtolower(trim((string) ($row['orderpro_status_code'] ?? ''))),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findMappedStatusCode(string $allegroStatusCode): ?string
|
||||||
|
{
|
||||||
|
$code = strtolower(trim($allegroStatusCode));
|
||||||
|
if ($code === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$statement = $this->pdo->prepare(
|
||||||
|
'SELECT orderpro_status_code
|
||||||
|
FROM allegro_order_status_pull_mappings
|
||||||
|
WHERE allegro_status_code = :allegro_status_code
|
||||||
|
AND orderpro_status_code IS NOT NULL
|
||||||
|
AND orderpro_status_code <> ""
|
||||||
|
LIMIT 1'
|
||||||
|
);
|
||||||
|
$statement->execute(['allegro_status_code' => $code]);
|
||||||
|
$value = $statement->fetchColumn();
|
||||||
|
if (!is_string($value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mapped = strtolower(trim($value));
|
||||||
|
return $mapped !== '' ? $mapped : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function upsertDiscoveredStatus(string $allegroStatusCode, ?string $allegroStatusName = null): void
|
||||||
|
{
|
||||||
|
$code = strtolower(trim($allegroStatusCode));
|
||||||
|
if ($code === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$existing = $this->pdo->prepare(
|
||||||
|
'SELECT id FROM allegro_order_status_pull_mappings
|
||||||
|
WHERE allegro_status_code = :allegro_status_code
|
||||||
|
LIMIT 1'
|
||||||
|
);
|
||||||
|
$existing->execute(['allegro_status_code' => $code]);
|
||||||
|
|
||||||
|
if ($existing->fetchColumn() !== false) {
|
||||||
|
if ($allegroStatusName !== null && trim($allegroStatusName) !== '') {
|
||||||
|
$update = $this->pdo->prepare(
|
||||||
|
'UPDATE allegro_order_status_pull_mappings
|
||||||
|
SET allegro_status_name = :allegro_status_name,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE allegro_status_code = :allegro_status_code
|
||||||
|
AND (allegro_status_name IS NULL OR allegro_status_name = "")'
|
||||||
|
);
|
||||||
|
$update->execute([
|
||||||
|
'allegro_status_name' => trim($allegroStatusName),
|
||||||
|
'allegro_status_code' => $code,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$insert = $this->pdo->prepare(
|
||||||
|
'INSERT INTO allegro_order_status_pull_mappings (
|
||||||
|
allegro_status_code, allegro_status_name, orderpro_status_code, created_at, updated_at
|
||||||
|
) VALUES (
|
||||||
|
:allegro_status_code, :allegro_status_name, NULL, NOW(), NOW()
|
||||||
|
)'
|
||||||
|
);
|
||||||
|
$insert->execute([
|
||||||
|
'allegro_status_code' => $code,
|
||||||
|
'allegro_status_name' => StringHelper::nullableString((string) $allegroStatusName),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array{allegro_status_code:string,allegro_status_name:string,orderpro_status_code:string}> $mappings
|
||||||
|
*/
|
||||||
|
public function replaceAll(array $mappings): void
|
||||||
|
{
|
||||||
|
$this->pdo->exec('DELETE FROM allegro_order_status_pull_mappings');
|
||||||
|
|
||||||
|
if ($mappings === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$insertStatement = $this->pdo->prepare(
|
||||||
|
'INSERT INTO allegro_order_status_pull_mappings (
|
||||||
|
allegro_status_code, allegro_status_name, orderpro_status_code, created_at, updated_at
|
||||||
|
) VALUES (
|
||||||
|
:allegro_status_code, :allegro_status_name, :orderpro_status_code, NOW(), NOW()
|
||||||
|
)'
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($mappings as $mapping) {
|
||||||
|
$allegroCode = strtolower(trim((string) ($mapping['allegro_status_code'] ?? '')));
|
||||||
|
if ($allegroCode === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderpro = strtolower(trim((string) ($mapping['orderpro_status_code'] ?? '')));
|
||||||
|
$allegroName = trim((string) ($mapping['allegro_status_name'] ?? ''));
|
||||||
|
|
||||||
|
$insertStatement->execute([
|
||||||
|
'allegro_status_code' => $allegroCode,
|
||||||
|
'allegro_status_name' => $allegroName !== '' ? $allegroName : null,
|
||||||
|
'orderpro_status_code' => $orderpro !== '' ? $orderpro : null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ final class AllegroStatusDiscoveryService
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly AllegroTokenManager $tokenManager,
|
private readonly AllegroTokenManager $tokenManager,
|
||||||
private readonly AllegroApiClient $apiClient,
|
private readonly AllegroApiClient $apiClient,
|
||||||
private readonly AllegroStatusMappingRepository $statusMappings
|
private readonly AllegroStatusMappingRepository $statusMappings,
|
||||||
|
private readonly ?AllegroPullStatusMappingRepository $pullStatusMappings = null
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +65,9 @@ final class AllegroStatusDiscoveryService
|
|||||||
|
|
||||||
foreach ($unique as $code => $name) {
|
foreach ($unique as $code => $name) {
|
||||||
$this->statusMappings->upsertDiscoveredStatus((string) $code, (string) $name);
|
$this->statusMappings->upsertDiscoveredStatus((string) $code, (string) $name);
|
||||||
|
if ($this->pullStatusMappings !== null) {
|
||||||
|
$this->pullStatusMappings->upsertDiscoveredStatus((string) $code, (string) $name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ final class AllegroStatusMappingController
|
|||||||
private readonly Translator $translator,
|
private readonly Translator $translator,
|
||||||
private readonly AllegroStatusMappingRepository $statusMappings,
|
private readonly AllegroStatusMappingRepository $statusMappings,
|
||||||
private readonly OrderStatusRepository $orderStatuses,
|
private readonly OrderStatusRepository $orderStatuses,
|
||||||
private readonly AllegroStatusDiscoveryService $statusDiscoveryService
|
private readonly AllegroStatusDiscoveryService $statusDiscoveryService,
|
||||||
|
private readonly ?AllegroPullStatusMappingRepository $pullStatusMappings = null
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +98,53 @@ final class AllegroStatusMappingController
|
|||||||
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function savePullStatusMappings(Request $request): Response
|
||||||
|
{
|
||||||
|
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||||
|
if ($csrfError !== null) {
|
||||||
|
return $csrfError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->pullStatusMappings === null) {
|
||||||
|
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed'));
|
||||||
|
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
||||||
|
}
|
||||||
|
|
||||||
|
$allegroCodes = $request->input('allegro_status_code', []);
|
||||||
|
$allegroNames = $request->input('allegro_status_name', []);
|
||||||
|
$orderproCodes = $request->input('orderpro_status_code', []);
|
||||||
|
if (!is_array($allegroCodes) || !is_array($allegroNames) || !is_array($orderproCodes)) {
|
||||||
|
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed'));
|
||||||
|
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mappings = [];
|
||||||
|
foreach ($allegroCodes as $index => $rawAllegroCode) {
|
||||||
|
$allegroCode = strtolower(trim((string) $rawAllegroCode));
|
||||||
|
if ($allegroCode === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allegroName = trim((string) ($allegroNames[$index] ?? ''));
|
||||||
|
$orderproCode = strtolower(trim((string) ($orderproCodes[$index] ?? '')));
|
||||||
|
|
||||||
|
$mappings[] = [
|
||||||
|
'allegro_status_code' => $allegroCode,
|
||||||
|
'allegro_status_name' => $allegroName,
|
||||||
|
'orderpro_status_code' => $orderproCode,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pullStatusMappings->replaceAll($mappings);
|
||||||
|
Flash::set('settings_success', $this->translator->get('settings.allegro.statuses.flash.saved_pull'));
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed') . ' ' . $exception->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
||||||
|
}
|
||||||
|
|
||||||
public function syncStatusesFromAllegro(Request $request): Response
|
public function syncStatusesFromAllegro(Request $request): Response
|
||||||
{
|
{
|
||||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Modules\Settings;
|
namespace App\Modules\Settings;
|
||||||
|
|
||||||
use App\Core\Support\StringHelper;
|
use App\Core\Support\StringHelper;
|
||||||
|
use App\Modules\Automation\AutomationService;
|
||||||
use App\Modules\Orders\OrderImportRepository;
|
use App\Modules\Orders\OrderImportRepository;
|
||||||
use App\Modules\Orders\OrdersRepository;
|
use App\Modules\Orders\OrdersRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
@@ -20,7 +21,8 @@ final class ShopproOrdersSyncService
|
|||||||
private readonly OrdersRepository $orders,
|
private readonly OrdersRepository $orders,
|
||||||
private readonly ShopproOrderMapper $mapper,
|
private readonly ShopproOrderMapper $mapper,
|
||||||
private readonly ShopproProductImageResolver $imageResolver,
|
private readonly ShopproProductImageResolver $imageResolver,
|
||||||
private readonly ?ShopproPullStatusMappingRepository $pullStatusMappings = null
|
private readonly ?ShopproPullStatusMappingRepository $pullStatusMappings = null,
|
||||||
|
private readonly ?AutomationService $automationService = null
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +269,14 @@ final class ShopproOrdersSyncService
|
|||||||
'shopPRO'
|
'shopPRO'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($savedOrderId > 0 && !$wasPaymentTransition && $this->automationService !== null) {
|
||||||
|
$this->automationService->trigger('order.imported', $savedOrderId, [
|
||||||
|
'source' => 'shoppro',
|
||||||
|
'created' => $wasCreated,
|
||||||
|
'integration_id' => $integrationId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
} catch (Throwable $exception) {
|
} catch (Throwable $exception) {
|
||||||
$result['failed'] = (int) $result['failed'] + 1;
|
$result['failed'] = (int) $result['failed'] + 1;
|
||||||
$errors = is_array($result['errors']) ? $result['errors'] : [];
|
$errors = is_array($result['errors']) ? $result['errors'] : [];
|
||||||
|
|||||||
Reference in New Issue
Block a user