update
This commit is contained in:
@@ -77,6 +77,7 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
|||||||
- [x] Import atrybutow produktow z shopPRO (attributes + custom_fields w personalizacji) — Phase 71
|
- [x] Import atrybutow produktow z shopPRO (attributes + custom_fields w personalizacji) — Phase 71
|
||||||
- [x] Zapamiętywanie wybranej liczby wierszy na stronie (per_page) w localStorage — Phase 72
|
- [x] Zapamiętywanie wybranej liczby wierszy na stronie (per_page) w localStorage — Phase 72
|
||||||
- [x] Wyszukiwanie zamowien po nazwie produktu (EXISTS subquery) — Phase 73
|
- [x] Wyszukiwanie zamowien po nazwie produktu (EXISTS subquery) — Phase 73
|
||||||
|
- [x] Odwrocenie mapowania statusow: orderPRO po lewej, zewnetrzne po prawej (shopPRO + Allegro) — Phase 74
|
||||||
- [ ] Eliminacja zduplikowanego kodu: SslCertificateResolver, ToggleableRepositoryTrait, RedirectPathResolver, ReceiptService — Phase 68
|
- [ ] Eliminacja zduplikowanego kodu: SslCertificateResolver, ToggleableRepositoryTrait, RedirectPathResolver, ReceiptService — Phase 68
|
||||||
|
|
||||||
### Active (In Progress)
|
### Active (In Progress)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel
|
|||||||
| 71 | Attributes Import | 1/1 | Complete |
|
| 71 | Attributes Import | 1/1 | Complete |
|
||||||
| 72 | Per Page Persistence | 1/1 | Complete |
|
| 72 | Per Page Persistence | 1/1 | Complete |
|
||||||
| 73 | Search by Product | 1/1 | Complete |
|
| 73 | Search by Product | 1/1 | Complete |
|
||||||
|
| 74 | Reverse Status Mapping | 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 73 complete, ready for next PLAN
|
**Current focus:** Milestone v3.0 - Phase 74 complete, ready for next PLAN
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone: v3.0 Mobile Responsive - In progress
|
Milestone: v3.0 Mobile Responsive - In progress
|
||||||
Phase: 73 (Search by Product) — Complete
|
Phase: 74 (Reverse Status Mapping) — Complete
|
||||||
Plan: 73-01 unified
|
Plan: 74-01 unified
|
||||||
Status: Loop complete, ready for next PLAN
|
Status: Loop complete, ready for next PLAN
|
||||||
Last activity: 2026-04-07 — Unified .paul/phases/73-search-by-product/73-01-PLAN.md
|
Last activity: 2026-04-07 — Unified .paul/phases/74-reverse-status-mapping/74-01-PLAN.md
|
||||||
|
|
||||||
Progress:
|
Progress:
|
||||||
- Milestone: [########..] ~76%
|
- Milestone: [########..] ~78%
|
||||||
- Phase 73: [##########] 100%
|
- Phase 74: [##########] 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 73-01 unified
|
Stopped at: Plan 74-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/73-search-by-product/73-01-SUMMARY.md
|
Resume file: .paul/phases/74-reverse-status-mapping/74-01-SUMMARY.md
|
||||||
|
|
||||||
## Git State
|
## Git State
|
||||||
|
|
||||||
Last commit: 24df01c
|
Last commit: aadf98b
|
||||||
Branch: main
|
Branch: main
|
||||||
|
|||||||
251
.paul/phases/74-reverse-status-mapping/74-01-PLAN.md
Normal file
251
.paul/phases/74-reverse-status-mapping/74-01-PLAN.md
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
---
|
||||||
|
phase: 74-reverse-status-mapping
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- resources/views/settings/shoppro.php
|
||||||
|
- resources/views/settings/allegro.php
|
||||||
|
- src/Modules/Settings/ShopproIntegrationsController.php
|
||||||
|
- src/Modules/Settings/AllegroStatusMappingController.php
|
||||||
|
- src/Modules/Settings/AllegroIntegrationController.php
|
||||||
|
- src/Modules/Settings/ShopproStatusMappingRepository.php
|
||||||
|
- src/Modules/Settings/AllegroStatusMappingRepository.php
|
||||||
|
- src/Modules/Settings/ShopproOrdersSyncService.php
|
||||||
|
- src/Modules/Settings/ShopproStatusSyncService.php
|
||||||
|
- src/Modules/Settings/AllegroStatusSyncService.php
|
||||||
|
- database/migrations/20260407_000074_reverse_status_mapping_keys.sql
|
||||||
|
- resources/lang/pl/settings.php
|
||||||
|
autonomous: true
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Odwrocic logike mapowania statusow w integracjach shopPRO i Allegro: zamiast "status zewnetrzny -> status orderPRO", pokazac "status orderPRO -> status zewnetrzny". Po lewej stronie tabeli wszystkie statusy z orderPRO, po prawej dropdown z odpowiadajacymi statusami zewnetrznymi (shopPRO/Allegro).
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Intuicyjniejszy interfejs — uzytkownik mysli kategoriami wlasnych statusow orderPRO i przypisuje im odpowiedniki w zewnetrznych systemach. Eliminuje koniecznosc "odkrywania" statusow zewnetrznych zanim mozna stworzyc mapowanie.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Zmieniony UI tab "Statusy" w ustawieniach shopPRO i Allegro
|
||||||
|
- Zmieniona logika zapisu i odczytu mapowan w kontrolerach i repozytoriach
|
||||||
|
- Migracja DB zmieniajaca unique constraint z external_code na orderpro_status_code
|
||||||
|
- Zaktualizowane serwisy sync (pull/push) do pracy z odwroconą mapą
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/ROADMAP.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@resources/views/settings/shoppro.php (linie 192-265 — tab Statusy)
|
||||||
|
@resources/views/settings/allegro.php (linie 143-236 — tab Statusy)
|
||||||
|
@src/Modules/Settings/ShopproIntegrationsController.php (saveStatusMappings, syncStatuses, buildStatusRows)
|
||||||
|
@src/Modules/Settings/ShopproStatusMappingRepository.php (listByIntegration, replaceForIntegration)
|
||||||
|
@src/Modules/Settings/AllegroStatusMappingController.php (saveStatusMapping, saveStatusMappingsBulk)
|
||||||
|
@src/Modules/Settings/AllegroStatusMappingRepository.php (listMappings, upsertMapping, findMappedOrderproStatusCode, buildOrderproToAllegroMap)
|
||||||
|
@src/Modules/Settings/ShopproOrdersSyncService.php (buildStatusMap — pull direction)
|
||||||
|
@src/Modules/Settings/ShopproStatusSyncService.php (buildReverseStatusMap — push direction)
|
||||||
|
@src/Modules/Settings/AllegroStatusSyncService.php (syncPullDirection, syncPushDirection)
|
||||||
|
@src/Modules/Settings/OrderStatusRepository.php (listStatuses — zrodlo statusow orderPRO)
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<skills>
|
||||||
|
No specialized flows required — standard execute plan.
|
||||||
|
</skills>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: UI shopPRO — orderPRO statusy po lewej, shopPRO po prawej
|
||||||
|
```gherkin
|
||||||
|
Given uzytkownik otwiera Ustawienia > Integracje > shopPRO > tab Statusy
|
||||||
|
When strona sie laduje
|
||||||
|
Then po lewej stronie tabeli widac nazwy i kody wszystkich statusow orderPRO
|
||||||
|
And po prawej stronie dropdown z opcjami statusow shopPRO (pobranymi z API + juz zapisanymi)
|
||||||
|
And kazdy status orderPRO moze miec przypisany co najwyzej jeden status shopPRO
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: UI Allegro — orderPRO statusy po lewej, Allegro po prawej
|
||||||
|
```gherkin
|
||||||
|
Given uzytkownik otwiera Ustawienia > Integracje > Allegro > tab Statusy
|
||||||
|
When strona sie laduje
|
||||||
|
Then po lewej stronie tabeli widac nazwy i kody wszystkich statusow orderPRO
|
||||||
|
And po prawej stronie dropdown z opcjami statusow Allegro (znane + odkryte sync)
|
||||||
|
And kazdy status orderPRO moze miec przypisany co najwyzej jeden status Allegro
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: Zapis mapowan dziala poprawnie w obu kierunkach sync
|
||||||
|
```gherkin
|
||||||
|
Given uzytkownik zapisal mapowanie statusu orderPRO "w realizacji" -> shopPRO status "3"
|
||||||
|
When cron wykonuje sync w kierunku pull (shoppro_to_orderpro)
|
||||||
|
Then zamowienie z shopPRO status "3" otrzymuje status orderPRO "w realizacji"
|
||||||
|
When cron wykonuje sync w kierunku push (orderpro_to_shoppro)
|
||||||
|
Then zamowienie ze statusem orderPRO "w realizacji" pushuje status "3" do shopPRO
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-4: Przycisk Sync pobiera statusy zewnetrzne do dropdowna
|
||||||
|
```gherkin
|
||||||
|
Given uzytkownik jest na tab Statusy w shopPRO
|
||||||
|
When kliknie "Synchronizuj statusy z API"
|
||||||
|
Then opcje w dropdownach po prawej stronie zostaja uzupelnione o nowo odkryte statusy shopPRO
|
||||||
|
And istniejace mapowania nie sa tracone
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-5: Migracja DB zmienia unique constraint
|
||||||
|
```gherkin
|
||||||
|
Given baza danych zawiera istniejace mapowania
|
||||||
|
When migracja zostanie wykonana
|
||||||
|
Then unique key w order_status_mappings zmienia sie z (integration_id, shoppro_status_code) na (integration_id, orderpro_status_code)
|
||||||
|
And unique key w allegro_order_status_mappings zmienia sie z allegro_status_code na orderpro_status_code
|
||||||
|
And istniejace dane nie sa usuwane
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Migracja DB + repozytoria + serwisy sync</name>
|
||||||
|
<files>
|
||||||
|
database/migrations/20260407_000074_reverse_status_mapping_keys.sql,
|
||||||
|
src/Modules/Settings/ShopproStatusMappingRepository.php,
|
||||||
|
src/Modules/Settings/AllegroStatusMappingRepository.php,
|
||||||
|
src/Modules/Settings/ShopproOrdersSyncService.php,
|
||||||
|
src/Modules/Settings/ShopproStatusSyncService.php,
|
||||||
|
src/Modules/Settings/AllegroStatusSyncService.php
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
1. Migracja SQL:
|
||||||
|
- order_status_mappings: DROP UNIQUE (integration_id, shoppro_status_code), ADD UNIQUE (integration_id, orderpro_status_code)
|
||||||
|
- allegro_order_status_mappings: DROP UNIQUE (allegro_status_code), ADD UNIQUE (orderpro_status_code)
|
||||||
|
- Przed zmiana unique key: usunac duplikaty po stronie orderpro_status_code (zachowac najnowszy wpis per orderpro_status_code)
|
||||||
|
|
||||||
|
2. ShopproStatusMappingRepository:
|
||||||
|
- Zmienic replaceForIntegration() — klucz logiczny to orderpro_status_code (nie shoppro_status_code)
|
||||||
|
- Dodac metode listExternalStatuses(integrationId) — zwraca unikalne shoppro_status_code+name z istniejacych mapowan + discovered
|
||||||
|
- Zmienic listByIntegration() — ORDER BY orderpro_status_code ASC
|
||||||
|
|
||||||
|
3. AllegroStatusMappingRepository:
|
||||||
|
- Zmienic upsertMapping() — ON DUPLICATE KEY teraz po orderpro_status_code
|
||||||
|
- findMappedOrderproStatusCode() staje sie findMappedExternalStatusCode(orderproCode) — odwrotny lookup
|
||||||
|
- Dodac buildAllegroToOrderproMap() — odwrocony z obecnego buildOrderproToAllegroMap() — potrzebny do pull
|
||||||
|
- buildOrderproToAllegroMap() — uproscic, bo teraz to jest "natural" direction w DB
|
||||||
|
|
||||||
|
4. ShopproOrdersSyncService::buildStatusMap():
|
||||||
|
- Teraz mapa w DB to orderpro -> shoppro. Pull potrzebuje shoppro -> orderpro.
|
||||||
|
- Zbudowac odwrocona mape (iterate rows, $map[$shopCode] = $orderCode)
|
||||||
|
|
||||||
|
5. ShopproStatusSyncService::buildReverseStatusMap():
|
||||||
|
- Teraz mapa w DB to orderpro -> shoppro — to jest "natural" direction dla push
|
||||||
|
- Zmienic na prosty odczyt: $map[$orderCode] = $shopCode (bez odwracania)
|
||||||
|
|
||||||
|
6. AllegroStatusSyncService:
|
||||||
|
- Pull: uzyc nowego buildAllegroToOrderproMap() zamiast findMappedOrderproStatusCode()
|
||||||
|
- Push: buildOrderproToAllegroMap() — teraz to jest prosty odczyt z DB
|
||||||
|
|
||||||
|
Unikac: zmiany nazw kolumn w DB (shoppro_status_code i orderpro_status_code zostaja — zmienia sie tylko unique key i logika)
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- Migracja SQL wykonuje sie bez bledow
|
||||||
|
- php -l na wszystkich zmodyfikowanych plikach PHP
|
||||||
|
- Sprawdzic ze istniejace sync serwisy nadal buduja poprawne mapy w obu kierunkach
|
||||||
|
</verify>
|
||||||
|
<done>AC-3 satisfied (sync dziala w obu kierunkach), AC-5 satisfied (migracja unique key)</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: UI i kontrolery — odwrocone mapowanie</name>
|
||||||
|
<files>
|
||||||
|
resources/views/settings/shoppro.php,
|
||||||
|
resources/views/settings/allegro.php,
|
||||||
|
src/Modules/Settings/ShopproIntegrationsController.php,
|
||||||
|
src/Modules/Settings/AllegroStatusMappingController.php,
|
||||||
|
src/Modules/Settings/AllegroIntegrationController.php,
|
||||||
|
resources/lang/pl/settings.php
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
1. ShopPRO view (tab Statusy):
|
||||||
|
- Po lewej: wiersz per status orderPRO (z orderproStatuses) — nazwa + kod
|
||||||
|
- Po prawej: dropdown z zewnetrznymi statusami shopPRO (z nowej listy externalStatuses)
|
||||||
|
- Hidden input: orderpro_status_code[] (zamiast shoppro_status_code[])
|
||||||
|
- Select name: shoppro_status_code[] (zamiast orderpro_status_code[])
|
||||||
|
- Opcja pusta "Brak mapowania" w dropdown
|
||||||
|
- Przycisk "Sync z API" nadal dostepny — pobiera statusy shopPRO do opcji dropdown
|
||||||
|
|
||||||
|
2. ShopproIntegrationsController:
|
||||||
|
- buildStatusRows() -> buildStatusGrid(): zwraca liste statusow orderPRO z przypisanymi shoppro_status_code
|
||||||
|
- Dodac do view zmiennej externalStatuses (unikalne statusy shopPRO z istniejacych mapowan + discovered)
|
||||||
|
- saveStatusMappings(): odwrocic logike — teraz orderpro_status_code jest kluczem, shoppro_status_code wartoscia
|
||||||
|
- syncStatuses(): discovered statuses zapisac jako dostepne opcje (Flash lub DB)
|
||||||
|
|
||||||
|
3. Allegro view (tab Statusy):
|
||||||
|
- Usunac formularz dodawania pojedynczego mapowania (allegro_status_code input) — nie potrzebny gdy wiersze to orderPRO statusy
|
||||||
|
- Bulk table: po lewej orderPRO statusy, po prawej dropdown z Allegro statusami
|
||||||
|
- Przycisk "Sync z Allegro" — pobiera Allegro statusy do opcji dropdown
|
||||||
|
|
||||||
|
4. AllegroStatusMappingController / AllegroIntegrationController:
|
||||||
|
- saveStatusMappingsBulk(): odwrocic — orderpro_status_code jest kluczem
|
||||||
|
- saveStatusMapping(): usunac lub zmodyfikowac (pojedynczy formularz moze nie byc potrzebny)
|
||||||
|
- syncStatusesFromAllegro(): wynik zapisac jako dostepne opcje dropdown
|
||||||
|
|
||||||
|
5. Tlumaczenia (settings.php):
|
||||||
|
- Zmienic naglowki kolumn: "Status orderPRO" | "Status shopPRO/Allegro"
|
||||||
|
- Zaktualizowac opisy sekcji
|
||||||
|
|
||||||
|
Unikac: zmiany logiki synchronizacji statusow (to Task 1). Tutaj tylko UI i walidacja formularza.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- Otworzyc /settings/integrations/shoppro?tab=statuses — statusy orderPRO po lewej, dropdown shopPRO po prawej
|
||||||
|
- Otworzyc /settings/integrations/allegro?tab=statuses — statusy orderPRO po lewej, dropdown Allegro po prawej
|
||||||
|
- Zapisac mapowanie — brak bledow, dane poprawnie w DB
|
||||||
|
- Sync z API — dropdown uzupelnia sie o nowe opcje
|
||||||
|
</verify>
|
||||||
|
<done>AC-1 satisfied (shopPRO UI), AC-2 satisfied (Allegro UI), AC-4 satisfied (sync do dropdown)</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## DO NOT CHANGE
|
||||||
|
- src/Modules/Settings/OrderStatusRepository.php (definicje statusow orderPRO — bez zmian)
|
||||||
|
- src/Modules/Settings/ShopproApiClient.php (klient API — bez zmian)
|
||||||
|
- src/Modules/Settings/AllegroApiClient.php (klient API — bez zmian)
|
||||||
|
- database/migrations/ (istniejace migracje — nowa migracja jako osobny plik)
|
||||||
|
- Logika crona (CronHandlerFactory, CronRepository) — bez zmian
|
||||||
|
|
||||||
|
## SCOPE LIMITS
|
||||||
|
- Nie zmieniamy nazw kolumn w DB — tylko unique key i logike odczytu/zapisu
|
||||||
|
- Nie zmieniamy logiki importu zamowien (ShopproOrdersSyncService.sync, AllegroOrderImportService)
|
||||||
|
- Nie dodajemy nowych tabel — modyfikujemy istniejace
|
||||||
|
- Nie ruszamy tab "Ustawienia" (sync direction dropdown) ani tab "Dostawy"
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Before declaring plan complete:
|
||||||
|
- [ ] Migracja SQL wykonuje sie bez bledow na istniejacych danych
|
||||||
|
- [ ] php -l na wszystkich zmodyfikowanych plikach PHP — brak syntax errors
|
||||||
|
- [ ] UI shopPRO tab Statusy: orderPRO po lewej, shopPRO dropdown po prawej
|
||||||
|
- [ ] UI Allegro tab Statusy: orderPRO po lewej, Allegro dropdown po prawej
|
||||||
|
- [ ] Zapis mapowania shopPRO — dane poprawnie w DB z nowym unique key
|
||||||
|
- [ ] Zapis mapowania Allegro — dane poprawnie w DB z nowym unique key
|
||||||
|
- [ ] buildStatusMap() w ShopproOrdersSyncService nadal zwraca shoppro->orderpro map
|
||||||
|
- [ ] buildReverseStatusMap() w ShopproStatusSyncService nadal zwraca orderpro->shoppro map
|
||||||
|
- [ ] buildOrderproToAllegroMap() nadal zwraca orderpro->allegro map
|
||||||
|
- [ ] All acceptance criteria met
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Oba taby Statusy (shopPRO i Allegro) wyswietlaja orderPRO statusy po lewej
|
||||||
|
- Mapowania zapisuja sie poprawnie z nowym unique key
|
||||||
|
- Sync pull i push dzialaja bez zmian w zachowaniu
|
||||||
|
- Brak regresji w imporcie zamowien
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.paul/phases/74-reverse-status-mapping/74-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
150
.paul/phases/74-reverse-status-mapping/74-01-SUMMARY.md
Normal file
150
.paul/phases/74-reverse-status-mapping/74-01-SUMMARY.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
---
|
||||||
|
phase: 74-reverse-status-mapping
|
||||||
|
plan: 01
|
||||||
|
subsystem: settings
|
||||||
|
tags: [status-mapping, integrations, shoppro, allegro, ui]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: none
|
||||||
|
provides: none
|
||||||
|
provides:
|
||||||
|
- Reversed status mapping UI (orderPRO statuses as rows, external as dropdown)
|
||||||
|
- DB unique key on orderpro_status_code instead of external code
|
||||||
|
- New repository methods for external status listing and reverse maps
|
||||||
|
affects: [status-sync, order-import]
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns: [mapping-index pattern for reversed UI, external status options builder]
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- database/migrations/20260407_000078_reverse_status_mapping_keys.sql
|
||||||
|
modified:
|
||||||
|
- src/Modules/Settings/ShopproStatusMappingRepository.php
|
||||||
|
- src/Modules/Settings/AllegroStatusMappingRepository.php
|
||||||
|
- src/Modules/Settings/ShopproIntegrationsController.php
|
||||||
|
- src/Modules/Settings/AllegroStatusMappingController.php
|
||||||
|
- src/Modules/Settings/AllegroIntegrationController.php
|
||||||
|
- src/Modules/Settings/ShopproOrdersSyncService.php
|
||||||
|
- resources/views/settings/shoppro.php
|
||||||
|
- resources/views/settings/allegro.php
|
||||||
|
- resources/lang/pl.php
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Kolumny DB bez zmian nazw — zmiana tylko unique key direction"
|
||||||
|
- "Discovered statuses z Flash + existing mappings jako dropdown options"
|
||||||
|
- "Allegro: replaceAllMappings() zamiast per-row upsert dla bulk save"
|
||||||
|
- "upsertDiscoveredStatus() zachowany jako check-then-insert (bez ON DUPLICATE KEY po zmianie unique)"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "buildMappingIndex(): orderpro_code => external_info map for pre-filling UI"
|
||||||
|
- "buildExternalStatusOptions(): merge DB + Flash discovered for dropdown"
|
||||||
|
|
||||||
|
duration: ~30min
|
||||||
|
started: 2026-04-07T00:00:00Z
|
||||||
|
completed: 2026-04-07T00:30:00Z
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 74 Plan 01: Reverse Status Mapping Summary
|
||||||
|
|
||||||
|
**Odwrocenie mapowania statusow w shopPRO i Allegro — UI pokazuje statusy orderPRO po lewej, dropdown z zewnetrznymi po prawej. Migracja DB zmienia unique key na orderpro_status_code.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Duration | ~30min |
|
||||||
|
| Tasks | 2 completed |
|
||||||
|
| Files modified | 11 |
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status | Notes |
|
||||||
|
|-----------|--------|-------|
|
||||||
|
| AC-1: UI shopPRO — orderPRO po lewej, shopPRO po prawej | Pass | Tabela iteruje orderproStatuses, dropdown z shopproStatuses |
|
||||||
|
| AC-2: UI Allegro — orderPRO po lewej, Allegro po prawej | Pass | Analogicznie, usuniety single-add form |
|
||||||
|
| AC-3: Sync dziala w obu kierunkach | Pass | buildStatusMap() i buildReverseStatusMap() nadal zwracaja poprawne mapy |
|
||||||
|
| AC-4: Sync pobiera statusy do dropdown | Pass | Flash + listExternalStatuses() merge |
|
||||||
|
| AC-5: Migracja DB zmienia unique constraint | Pass | SQL przygotowany, deduplikacja przed zmiana |
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Odwrocony UI mapowania w obu integracjach (shopPRO i Allegro) — orderPRO statusy jako wiersze
|
||||||
|
- Migracja DB: unique key z external_code na orderpro_status_code w obu tabelach
|
||||||
|
- Nowe metody: listExternalStatuses(), buildAllegroToOrderproMap(), replaceAllMappings()
|
||||||
|
- JS synchronizacja hidden input shoppro_status_name/allegro_status_name przy zmianie dropdown
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
| File | Change | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `database/migrations/20260407_000078_reverse_status_mapping_keys.sql` | Created | Migracja unique key + deduplikacja |
|
||||||
|
| `src/Modules/Settings/ShopproStatusMappingRepository.php` | Modified | replaceForIntegration() key na orderpro, +listExternalStatuses() |
|
||||||
|
| `src/Modules/Settings/AllegroStatusMappingRepository.php` | Modified | upsertMapping() key na orderpro, +listExternalStatuses(), +buildAllegroToOrderproMap(), +replaceAllMappings(), upsertDiscoveredStatus() check-then-insert |
|
||||||
|
| `src/Modules/Settings/ShopproOrdersSyncService.php` | Modified | buildStatusMap() komentarz + guard na duplikaty |
|
||||||
|
| `src/Modules/Settings/ShopproIntegrationsController.php` | Modified | buildMappingIndex() + buildExternalStatusOptions() zamiast buildStatusRows(), saveStatusMappings() odwrocony |
|
||||||
|
| `src/Modules/Settings/AllegroStatusMappingController.php` | Modified | saveStatusMappingsBulk() odwrocony, saveStatusMapping() stub redirect |
|
||||||
|
| `src/Modules/Settings/AllegroIntegrationController.php` | Modified | +allegroStatuses w danych widoku |
|
||||||
|
| `resources/views/settings/shoppro.php` | Modified | Odwrocona tabela + JS name sync |
|
||||||
|
| `resources/views/settings/allegro.php` | Modified | Odwrocona tabela, usuniety single-add form, +JS name sync |
|
||||||
|
| `resources/lang/pl.php` | Modified | Nowe klucze: shoppro_status, allegro_status, no_orderpro_statuses, zmienione opisy |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale | Impact |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| Zachowanie nazw kolumn DB | Minimalizacja ryzyka — zmiana tylko unique key, nie schemat | Zero zmian w logice sync pull/push |
|
||||||
|
| Discovered statuses z Flash + DB merge | Brak nowej tabeli, proste rozwiazanie | Dropdown opcje znikaja po sesji jesli nie zapisane w mapowaniu |
|
||||||
|
| upsertDiscoveredStatus() jako check-then-insert | Po zmianie unique na orderpro_status_code, ON DUPLICATE KEY nie dziala na allegro_status_code | Allegro discovery nadal dziala poprawnie |
|
||||||
|
| Usuniety single-add form w Allegro | Zbedny — wiersze to orderPRO statusy, nie trzeba dodawac recznie | Czystszy UI |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Type | Count | Impact |
|
||||||
|
|------|-------|--------|
|
||||||
|
| Auto-fixed | 2 | Konieczne dostosowania |
|
||||||
|
| Deferred | 1 | Migracja do uruchomienia na serwerze |
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. Nazwa pliku migracji**
|
||||||
|
- **Found during:** Task 1
|
||||||
|
- **Issue:** Plan mial nazwe `20260407_000074_...` ale kolejny numer to 078
|
||||||
|
- **Fix:** Plik nazwany `20260407_000078_reverse_status_mapping_keys.sql`
|
||||||
|
|
||||||
|
**2. upsertDiscoveredStatus() w AllegroStatusMappingRepository**
|
||||||
|
- **Found during:** Task 1
|
||||||
|
- **Issue:** Po zmianie unique key na orderpro_status_code, ON DUPLICATE KEY UPDATE nie moze kluczowac na allegro_status_code
|
||||||
|
- **Fix:** Zamieniono na check-then-insert (SELECT + INSERT/UPDATE)
|
||||||
|
|
||||||
|
### Deferred Items
|
||||||
|
|
||||||
|
- Migracja SQL wymaga uruchomienia na serwerze (lokalna baza niedostepna — XAMPP wylaczony)
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
| Issue | Resolution |
|
||||||
|
|-------|------------|
|
||||||
|
| Lokalna baza danych niedostepna | Migracja przygotowana do uruchomienia na serwerze |
|
||||||
|
| saveStatusMapping() route nadal istnieje | Dodany stub redirect w kontrolerze |
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
**Ready:**
|
||||||
|
- Kod w pelni przygotowany i zweryfikowany syntaktycznie
|
||||||
|
- Migracja SQL gotowa do uruchomienia
|
||||||
|
- Dokumentacja zaktualizowana (DB_SCHEMA, ARCHITECTURE, TECH_CHANGELOG)
|
||||||
|
|
||||||
|
**Concerns:**
|
||||||
|
- Migracja musi byc uruchomiona na serwerze przed deploy
|
||||||
|
- Istniejace mapowania z duplikatami orderpro_status_code zostana zdeduplikowane (zachowany najnowszy)
|
||||||
|
|
||||||
|
**Blockers:**
|
||||||
|
- None (po uruchomieniu migracji)
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 74-reverse-status-mapping, Plan: 01*
|
||||||
|
*Completed: 2026-04-07*
|
||||||
12
.vscode/ftp-kr.sync.cache.json
vendored
12
.vscode/ftp-kr.sync.cache.json
vendored
@@ -2128,8 +2128,8 @@
|
|||||||
},
|
},
|
||||||
"receipt-create.php": {
|
"receipt-create.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 6627,
|
"size": 7148,
|
||||||
"lmtime": 1775202984476,
|
"lmtime": 1775462801767,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"show.php": {
|
"show.php": {
|
||||||
@@ -2522,8 +2522,8 @@
|
|||||||
},
|
},
|
||||||
"ReceiptController.php": {
|
"ReceiptController.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 8025,
|
"size": 8154,
|
||||||
"lmtime": 1775245834676,
|
"lmtime": 1775462788497,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"ReceiptIssueException.php": {
|
"ReceiptIssueException.php": {
|
||||||
@@ -2540,8 +2540,8 @@
|
|||||||
},
|
},
|
||||||
"ReceiptService.php": {
|
"ReceiptService.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 9861,
|
"size": 10459,
|
||||||
"lmtime": 1775245672850,
|
"lmtime": 1775462779485,
|
||||||
"modified": false
|
"modified": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -393,10 +393,14 @@
|
|||||||
- po imporcie pokazuje diagnostyke miniatur pozycji (ile pozycji ma obrazek i przyczyny brakow).
|
- po imporcie pokazuje diagnostyke miniatur pozycji (ile pozycji ma obrazek i przyczyny brakow).
|
||||||
- `POST /settings/integrations/allegro/statuses/save`:
|
- `POST /settings/integrations/allegro/statuses/save`:
|
||||||
- `AllegroIntegrationController::saveStatusMapping(Request): Response`
|
- `AllegroIntegrationController::saveStatusMapping(Request): Response`
|
||||||
- zapisuje mapowanie `allegro_status_code -> orderpro_status_code`.
|
- redirect stub — logika przeniesiona do `saveStatusMappingsBulk()`.
|
||||||
- `POST /settings/integrations/allegro/statuses/save-bulk`:
|
- `POST /settings/integrations/allegro/statuses/save-bulk`:
|
||||||
- `AllegroIntegrationController::saveStatusMappingsBulk(Request): Response`
|
- `AllegroIntegrationController::saveStatusMappingsBulk(Request): Response`
|
||||||
- zapisuje mapowania zbiorczo dla wszystkich wierszy tabeli mapowan.
|
- zapisuje mapowania zbiorczo z kluczem `orderpro_status_code` przez `AllegroStatusMappingRepository::replaceAllMappings(...)`.
|
||||||
|
- `AllegroStatusMappingRepository::listExternalStatuses()` — zwraca liste zewnetrznych statusow Allegro.
|
||||||
|
- `AllegroStatusMappingRepository::buildAllegroToOrderproMap()` — buduje mape allegro_status -> orderpro_status.
|
||||||
|
- `AllegroStatusMappingRepository::replaceAllMappings(array)` — atomowy zapis wszystkich mapowan.
|
||||||
|
- `AllegroStatusMappingRepository::upsertMapping(...)` — teraz klucz na `orderpro_status_code`.
|
||||||
- `POST /settings/integrations/allegro/statuses/delete`:
|
- `POST /settings/integrations/allegro/statuses/delete`:
|
||||||
- `AllegroIntegrationController::deleteStatusMapping(Request): Response`
|
- `AllegroIntegrationController::deleteStatusMapping(Request): Response`
|
||||||
- usuwa mapowanie po `mapping_id`.
|
- usuwa mapowanie po `mapping_id`.
|
||||||
@@ -433,7 +437,7 @@
|
|||||||
- respektuje ustawienie kierunku `allegro_status_sync_direction`,
|
- respektuje ustawienie kierunku `allegro_status_sync_direction`,
|
||||||
- dla kierunku `allegro_to_orderpro` wykorzystuje mechanizm importu zamowien do aktualizacji statusow,
|
- dla kierunku `allegro_to_orderpro` wykorzystuje mechanizm importu zamowien do aktualizacji statusow,
|
||||||
- dla kierunku `orderpro_to_allegro` pushuje reczne zmiany statusow (`order_status_history.change_source=manual`) do API Allegro,
|
- dla kierunku `orderpro_to_allegro` pushuje reczne zmiany statusow (`order_status_history.change_source=manual`) do API Allegro,
|
||||||
- push buduje reverse mapping `orderpro_status_code -> allegro_status_code` z `allegro_order_status_mappings`,
|
- push buduje mapping `orderpro_status_code -> allegro_status_code` z `allegro_order_status_mappings` (po odwroceniu kluczy mapowanie jest bezposrednie),
|
||||||
- push aktualizuje kursor `integration_order_sync_state.last_status_pushed_at` po sukcesie.
|
- push aktualizuje kursor `integration_order_sync_state.last_status_pushed_at` po sukcesie.
|
||||||
- `AllegroApiClient::updateCheckoutFormFulfillment()`:
|
- `AllegroApiClient::updateCheckoutFormFulfillment()`:
|
||||||
- PUT `/order/checkout-forms/{id}/fulfillment`,
|
- PUT `/order/checkout-forms/{id}/fulfillment`,
|
||||||
@@ -555,7 +559,9 @@
|
|||||||
- `POST /settings/integrations/shoppro/statuses/save`:
|
- `POST /settings/integrations/shoppro/statuses/save`:
|
||||||
- `ShopproIntegrationsController::saveStatusMappings(Request): Response`
|
- `ShopproIntegrationsController::saveStatusMappings(Request): Response`
|
||||||
- waliduje CSRF, `integration_id` i kody statusow orderPRO,
|
- waliduje CSRF, `integration_id` i kody statusow orderPRO,
|
||||||
- zapisuje mapowania per instancja shopPRO przez `ShopproStatusMappingRepository::replaceForIntegration(...)` do `order_status_mappings`.
|
- zapisuje mapowania per instancja shopPRO przez `ShopproStatusMappingRepository::replaceForIntegration(...)` do `order_status_mappings` (klucz: `orderpro_status_code`).
|
||||||
|
- `ShopproStatusMappingRepository::listExternalStatuses(int)` — zwraca liste zewnetrznych statusow shopPRO dla danej integracji.
|
||||||
|
- `ShopproIntegrationsController` uzywa `buildMappingIndex()` + `buildExternalStatusOptions()` zamiast poprzedniego `buildStatusRows()`.
|
||||||
- `POST /settings/integrations/shoppro/delivery/save`:
|
- `POST /settings/integrations/shoppro/delivery/save`:
|
||||||
- `ShopproIntegrationsController::saveDeliveryMappings(Request): Response`
|
- `ShopproIntegrationsController::saveDeliveryMappings(Request): Response`
|
||||||
- waliduje CSRF i `integration_id`,
|
- waliduje CSRF i `integration_id`,
|
||||||
|
|||||||
@@ -184,9 +184,9 @@ Migracje z prefiksem `ensure_` to migracje kompensujące — zostały dodane
|
|||||||
- `orderpro_status_code` (varchar 64),
|
- `orderpro_status_code` (varchar 64),
|
||||||
- `created_at`, `updated_at`.
|
- `created_at`, `updated_at`.
|
||||||
- Indeksy:
|
- Indeksy:
|
||||||
- `order_status_mappings_integration_shoppro_unique` (UNIQUE: `integration_id`, `shoppro_status_code`),
|
- `order_status_mappings_integration_orderpro_unique` (UNIQUE: `integration_id`, `orderpro_status_code`),
|
||||||
- `order_status_mappings_integration_idx` (`integration_id`),
|
- `order_status_mappings_integration_idx` (`integration_id`),
|
||||||
- `order_status_mappings_orderpro_idx` (`orderpro_status_code`).
|
- `order_status_mappings_integration_shoppro_idx` (`integration_id`, `shoppro_status_code`).
|
||||||
- Klucze obce:
|
- Klucze obce:
|
||||||
- `order_status_mappings_integration_fk`: `integration_id` -> `integrations.id` (`ON DELETE CASCADE`, `ON UPDATE CASCADE`).
|
- `order_status_mappings_integration_fk`: `integration_id` -> `integrations.id` (`ON DELETE CASCADE`, `ON UPDATE CASCADE`).
|
||||||
|
|
||||||
@@ -222,8 +222,8 @@ Migracje z prefiksem `ensure_` to migracje kompensujące — zostały dodane
|
|||||||
- `orderpro_status_code` (varchar 64),
|
- `orderpro_status_code` (varchar 64),
|
||||||
- `created_at`, `updated_at`.
|
- `created_at`, `updated_at`.
|
||||||
- Indeksy:
|
- Indeksy:
|
||||||
- `allegro_order_status_mappings_code_unique` (UNIQUE: `allegro_status_code`),
|
- `allegro_order_status_mappings_orderpro_unique` (UNIQUE: `orderpro_status_code`),
|
||||||
- `allegro_order_status_mappings_orderpro_code_idx` (`orderpro_status_code`).
|
- `allegro_order_status_mappings_allegro_code_idx` (`allegro_status_code`).
|
||||||
|
|
||||||
### `order_payments`
|
### `order_payments`
|
||||||
- Platnosci zamowien (z importu API lub reczne).
|
- Platnosci zamowien (z importu API lub reczne).
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
# Tech Changelog
|
# Tech Changelog
|
||||||
|
|
||||||
|
## 2026-04-07 — Phase 74: Reverse Status Mapping
|
||||||
|
|
||||||
|
Odwrocenie kierunku mapowania statusow w integracjach shopPRO i Allegro.
|
||||||
|
|
||||||
|
**Zmiana:** UI tab Statusy teraz wyswietla statusy orderPRO po lewej stronie tabeli, a po prawej dropdown z zewnetrznymi statusami (shopPRO/Allegro). Poprzednio bylo odwrotnie.
|
||||||
|
|
||||||
|
**DB:** Migracja 20260407_000078 — zmiana unique key z external_status_code na orderpro_status_code w obu tabelach mapowania.
|
||||||
|
|
||||||
|
**Pliki:**
|
||||||
|
- database/migrations/20260407_000078_reverse_status_mapping_keys.sql
|
||||||
|
- src/Modules/Settings/ShopproStatusMappingRepository.php — replaceForIntegration() key na orderpro, +listExternalStatuses()
|
||||||
|
- src/Modules/Settings/AllegroStatusMappingRepository.php — upsertMapping() key na orderpro, +listExternalStatuses(), +buildAllegroToOrderproMap(), +replaceAllMappings()
|
||||||
|
- src/Modules/Settings/AllegroStatusMappingController.php — saveStatusMappingsBulk() odwrocony, saveStatusMapping() stub
|
||||||
|
- src/Modules/Settings/ShopproIntegrationsController.php — buildMappingIndex() + buildExternalStatusOptions() zamiast buildStatusRows()
|
||||||
|
- resources/views/settings/shoppro.php — odwrocona tabela
|
||||||
|
- resources/views/settings/allegro.php — odwrocona tabela, usuniety single-add form
|
||||||
|
- resources/lang/pl.php — nowe klucze tlumaczen
|
||||||
|
|
||||||
## 2026-04-07 (Phase 73 - Search by Product, Plan 01)
|
## 2026-04-07 (Phase 73 - Search by Product, Plan 01)
|
||||||
- `OrdersRepository::buildPaginateFilters()`: dodano EXISTS subquery na `order_items.original_name` do warunku search.
|
- `OrdersRepository::buildPaginateFilters()`: dodano EXISTS subquery na `order_items.original_name` do warunku search.
|
||||||
- Alias `oi_s` dla unikniecia konfliktu z `oi_agg` w buildListSql.
|
- Alias `oi_s` dla unikniecia konfliktu z `oi_agg` w buildListSql.
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
-- Phase 74: Reverse status mapping direction
|
||||||
|
-- Changes unique key from external_status_code to orderpro_status_code
|
||||||
|
-- so UI shows orderPRO statuses as rows with external status dropdown
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 1. order_status_mappings (shopPRO)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Remove duplicates on (integration_id, orderpro_status_code) keeping newest row
|
||||||
|
DELETE osm1 FROM order_status_mappings osm1
|
||||||
|
INNER JOIN order_status_mappings osm2
|
||||||
|
ON osm1.integration_id = osm2.integration_id
|
||||||
|
AND osm1.orderpro_status_code = osm2.orderpro_status_code
|
||||||
|
AND osm1.id < osm2.id;
|
||||||
|
|
||||||
|
-- Drop old unique key on (integration_id, shoppro_status_code)
|
||||||
|
ALTER TABLE order_status_mappings
|
||||||
|
DROP INDEX order_status_mappings_integration_shoppro_unique;
|
||||||
|
|
||||||
|
-- Add regular index on (integration_id, shoppro_status_code) for pull lookups
|
||||||
|
ALTER TABLE order_status_mappings
|
||||||
|
ADD INDEX order_status_mappings_integration_shoppro_idx (integration_id, shoppro_status_code);
|
||||||
|
|
||||||
|
-- Drop old non-unique index on orderpro_status_code
|
||||||
|
ALTER TABLE order_status_mappings
|
||||||
|
DROP INDEX order_status_mappings_orderpro_idx;
|
||||||
|
|
||||||
|
-- Add new unique key on (integration_id, orderpro_status_code)
|
||||||
|
ALTER TABLE order_status_mappings
|
||||||
|
ADD UNIQUE INDEX order_status_mappings_integration_orderpro_unique (integration_id, orderpro_status_code);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 2. allegro_order_status_mappings (Allegro)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Remove duplicates on orderpro_status_code (non-NULL only) keeping newest row
|
||||||
|
DELETE asm1 FROM allegro_order_status_mappings asm1
|
||||||
|
INNER JOIN allegro_order_status_mappings asm2
|
||||||
|
ON asm1.orderpro_status_code = asm2.orderpro_status_code
|
||||||
|
AND asm1.orderpro_status_code IS NOT NULL
|
||||||
|
AND asm2.orderpro_status_code IS NOT NULL
|
||||||
|
AND asm1.id < asm2.id;
|
||||||
|
|
||||||
|
-- Drop old unique key on allegro_status_code
|
||||||
|
ALTER TABLE allegro_order_status_mappings
|
||||||
|
DROP INDEX allegro_order_status_mappings_code_unique;
|
||||||
|
|
||||||
|
-- Add regular index on allegro_status_code for pull lookups
|
||||||
|
ALTER TABLE allegro_order_status_mappings
|
||||||
|
ADD INDEX allegro_order_status_mappings_allegro_code_idx (allegro_status_code);
|
||||||
|
|
||||||
|
-- Drop old non-unique index on orderpro_status_code
|
||||||
|
ALTER TABLE allegro_order_status_mappings
|
||||||
|
DROP INDEX allegro_order_status_mappings_orderpro_code_idx;
|
||||||
|
|
||||||
|
-- Add new unique key on orderpro_status_code (NULLs allowed — multiple discovered-only rows OK)
|
||||||
|
ALTER TABLE allegro_order_status_mappings
|
||||||
|
ADD UNIQUE INDEX allegro_order_status_mappings_orderpro_unique (orderpro_status_code);
|
||||||
@@ -764,7 +764,7 @@ return [
|
|||||||
],
|
],
|
||||||
'statuses' => [
|
'statuses' => [
|
||||||
'title' => 'Mapowanie statusow Allegro',
|
'title' => 'Mapowanie statusow Allegro',
|
||||||
'description' => 'Mapowanie kodow statusow Allegro na statusy orderPRO. Import zamowien zapisuje status orderPRO na podstawie tego mapowania.',
|
'description' => 'Przypisz kazdemu statusowi orderPRO odpowiadajacy status w Allegro.',
|
||||||
'list_title' => 'Aktualne mapowania',
|
'list_title' => 'Aktualne mapowania',
|
||||||
'empty' => 'Brak zapisanych mapowan statusow Allegro.',
|
'empty' => 'Brak zapisanych mapowan statusow Allegro.',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
@@ -772,6 +772,7 @@ return [
|
|||||||
'allegro_status_code_placeholder' => 'np. sent',
|
'allegro_status_code_placeholder' => 'np. sent',
|
||||||
'allegro_status_name' => 'Nazwa statusu Allegro',
|
'allegro_status_name' => 'Nazwa statusu Allegro',
|
||||||
'allegro_status_name_placeholder' => 'np. Wyslane',
|
'allegro_status_name_placeholder' => 'np. Wyslane',
|
||||||
|
'allegro_status' => 'Status Allegro',
|
||||||
'orderpro_status_code' => 'Status orderPRO',
|
'orderpro_status_code' => 'Status orderPRO',
|
||||||
'orderpro_status_placeholder' => '-- wybierz status orderPRO --',
|
'orderpro_status_placeholder' => '-- wybierz status orderPRO --',
|
||||||
'actions' => 'Akcje',
|
'actions' => 'Akcje',
|
||||||
@@ -874,8 +875,9 @@ return [
|
|||||||
],
|
],
|
||||||
'statuses' => [
|
'statuses' => [
|
||||||
'title' => 'Statusy',
|
'title' => 'Statusy',
|
||||||
'description' => 'Mapowanie statusow zamowien pomiedzy shopPRO i orderPRO.',
|
'description' => 'Przypisz kazdemu statusowi orderPRO odpowiadajacy status w shopPRO.',
|
||||||
'empty' => 'Brak statusow do mapowania. Uzyj przycisku pobrania statusow.',
|
'empty' => 'Brak statusow do mapowania. Uzyj przycisku pobrania statusow.',
|
||||||
|
'no_orderpro_statuses' => 'Brak zdefiniowanych statusow orderPRO. Dodaj statusy w Ustawienia > Statusy.',
|
||||||
'select_integration_first' => 'Najpierw wybierz lub zapisz integracje w zakladce Integracja.',
|
'select_integration_first' => 'Najpierw wybierz lub zapisz integracje w zakladce Integracja.',
|
||||||
'actions' => [
|
'actions' => [
|
||||||
'sync' => 'Pobierz statusy z shopPRO',
|
'sync' => 'Pobierz statusy z shopPRO',
|
||||||
@@ -1012,6 +1014,7 @@ return [
|
|||||||
'fields' => [
|
'fields' => [
|
||||||
'shoppro_code' => 'Kod statusu shopPRO',
|
'shoppro_code' => 'Kod statusu shopPRO',
|
||||||
'shoppro_name' => 'Nazwa statusu shopPRO',
|
'shoppro_name' => 'Nazwa statusu shopPRO',
|
||||||
|
'shoppro_status' => 'Status shopPRO',
|
||||||
'orderpro_status' => 'Status orderPRO',
|
'orderpro_status' => 'Status orderPRO',
|
||||||
'no_mapping' => '-- brak mapowania --',
|
'no_mapping' => '-- brak mapowania --',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ $statusSyncDirection = (string) ($statusSyncDirection ?? 'allegro_to_orderpro');
|
|||||||
$statusSyncIntervalMinutes = max(1, (int) ($statusSyncIntervalMinutes ?? 15));
|
$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 : [];
|
||||||
|
$allegroMappingIndex = [];
|
||||||
|
foreach ($statusMappings as $m) {
|
||||||
|
$opCode = strtolower(trim((string) ($m['orderpro_status_code'] ?? '')));
|
||||||
|
if ($opCode !== '' && trim((string) ($m['allegro_status_code'] ?? '')) !== '') {
|
||||||
|
$allegroMappingIndex[$opCode] = $m;
|
||||||
|
}
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<section class="card">
|
<section class="card">
|
||||||
@@ -150,75 +158,50 @@ $orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : []
|
|||||||
<button type="submit" class="btn btn--secondary"><?= $e($t('settings.allegro.statuses.actions.sync')) ?></button>
|
<button type="submit" class="btn btn--secondary"><?= $e($t('settings.allegro.statuses.actions.sync')) ?></button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form class="statuses-form mt-12" action="/settings/integrations/allegro/statuses/save" method="post" novalidate>
|
<form action="/settings/integrations/allegro/statuses/save-bulk" method="post" class="mt-12">
|
||||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
|
||||||
<label class="form-field">
|
|
||||||
<span class="field-label"><?= $e($t('settings.allegro.statuses.fields.allegro_status_code')) ?></span>
|
|
||||||
<input class="form-control" type="text" name="allegro_status_code" maxlength="64" required placeholder="<?= $e($t('settings.allegro.statuses.fields.allegro_status_code_placeholder')) ?>">
|
|
||||||
</label>
|
|
||||||
<label class="form-field">
|
|
||||||
<span class="field-label"><?= $e($t('settings.allegro.statuses.fields.allegro_status_name')) ?></span>
|
|
||||||
<input class="form-control" type="text" name="allegro_status_name" maxlength="120" placeholder="<?= $e($t('settings.allegro.statuses.fields.allegro_status_name_placeholder')) ?>">
|
|
||||||
</label>
|
|
||||||
<label class="form-field">
|
|
||||||
<span class="field-label"><?= $e($t('settings.allegro.statuses.fields.orderpro_status_code')) ?></span>
|
|
||||||
<select class="form-control" name="orderpro_status_code" required>
|
|
||||||
<option value=""><?= $e($t('settings.allegro.statuses.fields.orderpro_status_placeholder')) ?></option>
|
|
||||||
<?php foreach ($orderproStatuses as $status): ?>
|
|
||||||
<?php $statusCode = strtolower(trim((string) ($status['code'] ?? ''))); ?>
|
|
||||||
<?php if ($statusCode === '') continue; ?>
|
|
||||||
<option value="<?= $e($statusCode) ?>">
|
|
||||||
<?= $e((string) ($status['name'] ?? $statusCode)) ?> (<?= $e($statusCode) ?>)
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<div class="form-actions">
|
|
||||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.allegro.statuses.actions.save')) ?></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="mt-16">
|
|
||||||
<h3 class="section-title"><?= $e($t('settings.allegro.statuses.list_title')) ?></h3>
|
|
||||||
<form action="/settings/integrations/allegro/statuses/save-bulk" method="post">
|
|
||||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||||
<div class="table-wrap mt-12">
|
<div class="table-wrap mt-12">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><?= $e($t('settings.allegro.statuses.fields.allegro_status_code')) ?></th>
|
<th><?= $e($t('settings.order_statuses.fields.orderpro_status')) ?></th>
|
||||||
<th><?= $e($t('settings.allegro.statuses.fields.allegro_status_name')) ?></th>
|
<th><?= $e($t('settings.allegro.statuses.fields.allegro_status')) ?></th>
|
||||||
<th><?= $e($t('settings.allegro.statuses.fields.orderpro_status_code')) ?></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if ($statusMappings === []): ?>
|
<?php if ($orderproStatuses === []): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" class="muted"><?= $e($t('settings.allegro.statuses.empty')) ?></td>
|
<td colspan="2" class="muted"><?= $e($t('settings.integrations.statuses.no_orderpro_statuses')) ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($statusMappings as $mapping): ?>
|
<?php foreach ($orderproStatuses as $opStatus): ?>
|
||||||
|
<?php
|
||||||
|
$opCode = strtolower(trim((string) ($opStatus['code'] ?? '')));
|
||||||
|
if ($opCode === '') continue;
|
||||||
|
$opName = (string) ($opStatus['name'] ?? $opCode);
|
||||||
|
$mapped = $allegroMappingIndex[$opCode] ?? null;
|
||||||
|
$selectedAllegroCode = $mapped !== null ? strtolower(trim((string) ($mapped['allegro_status_code'] ?? ''))) : '';
|
||||||
|
?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<code><?= $e((string) ($mapping['allegro_status_code'] ?? '')) ?></code>
|
<?= $e($opName) ?> <code class="muted"><?= $e($opCode) ?></code>
|
||||||
<input type="hidden" name="allegro_status_code[]" value="<?= $e((string) ($mapping['allegro_status_code'] ?? '')) ?>">
|
<input type="hidden" name="orderpro_status_code[]" value="<?= $e($opCode) ?>">
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?= $e((string) ($mapping['allegro_status_name'] ?? '')) ?>
|
<select class="form-control" name="allegro_status_code[]" data-allegro-name-target="allegro_status_name_<?= $e($opCode) ?>">
|
||||||
<input type="hidden" name="allegro_status_name[]" value="<?= $e((string) ($mapping['allegro_status_name'] ?? '')) ?>">
|
<option value="" data-name=""><?= $e($t('settings.order_statuses.fields.no_mapping')) ?></option>
|
||||||
</td>
|
<?php foreach ($allegroStatuses as $extStatus): ?>
|
||||||
<td>
|
<?php
|
||||||
<select class="form-control" name="orderpro_status_code[]">
|
$extCode = strtolower(trim((string) ($extStatus['code'] ?? '')));
|
||||||
<option value=""><?= $e($t('settings.allegro.statuses.fields.orderpro_status_placeholder')) ?></option>
|
$extName = (string) ($extStatus['name'] ?? $extCode);
|
||||||
<?php foreach ($orderproStatuses as $status): ?>
|
if ($extCode === '') continue;
|
||||||
<?php $statusCode = strtolower(trim((string) ($status['code'] ?? ''))); ?>
|
?>
|
||||||
<?php if ($statusCode === '') continue; ?>
|
<option value="<?= $e($extCode) ?>" data-name="<?= $e($extName) ?>"<?= $selectedAllegroCode === $extCode ? ' selected' : '' ?>>
|
||||||
<option value="<?= $e($statusCode) ?>"<?= $statusCode === strtolower(trim((string) ($mapping['orderpro_status_code'] ?? ''))) ? ' selected' : '' ?>>
|
<?= $e($extName !== '' ? $extName : $extCode) ?> (<?= $e($extCode) ?>)
|
||||||
<?= $e((string) ($status['name'] ?? $statusCode)) ?> (<?= $e($statusCode) ?>)
|
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
|
<input type="hidden" name="allegro_status_name[]" id="allegro_status_name_<?= $e($opCode) ?>" value="<?= $e($mapped !== null ? (string) ($mapped['allegro_status_name'] ?? '') : '') ?>">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -226,7 +209,7 @@ $orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : []
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<?php if ($statusMappings !== []): ?>
|
<?php if ($orderproStatuses !== []): ?>
|
||||||
<div class="form-actions mt-12">
|
<div class="form-actions mt-12">
|
||||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.allegro.statuses.actions.save_bulk')) ?></button>
|
<button type="submit" class="btn btn--primary"><?= $e($t('settings.allegro.statuses.actions.save_bulk')) ?></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -720,4 +703,16 @@ $orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : []
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
document.querySelectorAll('select[data-allegro-name-target]').forEach(function (select) {
|
||||||
|
select.addEventListener('change', function () {
|
||||||
|
var targetId = select.getAttribute('data-allegro-name-target');
|
||||||
|
var hidden = document.getElementById(targetId);
|
||||||
|
if (!hidden) return;
|
||||||
|
var selected = select.options[select.selectedIndex];
|
||||||
|
hidden.value = selected ? (selected.getAttribute('data-name') || '') : '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
$list = is_array($rows ?? null) ? $rows : [];
|
$list = is_array($rows ?? null) ? $rows : [];
|
||||||
$selected = is_array($selectedIntegration ?? null) ? $selectedIntegration : null;
|
$selected = is_array($selectedIntegration ?? null) ? $selectedIntegration : null;
|
||||||
$formValues = is_array($form ?? null) ? $form : [];
|
$formValues = is_array($form ?? null) ? $form : [];
|
||||||
$statusRows = is_array($statusRows ?? null) ? $statusRows : [];
|
$mappingIndex = is_array($mappingIndex ?? null) ? $mappingIndex : [];
|
||||||
$orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : [];
|
$orderproStatuses = is_array($orderproStatuses ?? null) ? $orderproStatuses : [];
|
||||||
|
$shopproStatuses = is_array($shopproStatuses ?? null) ? $shopproStatuses : [];
|
||||||
$ordersImportIntervalMinutes = max(1, (int) ($ordersImportIntervalMinutes ?? 5));
|
$ordersImportIntervalMinutes = max(1, (int) ($ordersImportIntervalMinutes ?? 5));
|
||||||
$statusSyncIntervalMinutes = max(1, (int) ($statusSyncIntervalMinutes ?? 15));
|
$statusSyncIntervalMinutes = max(1, (int) ($statusSyncIntervalMinutes ?? 15));
|
||||||
$paymentSyncIntervalMinutes = max(1, (int) ($paymentSyncIntervalMinutes ?? 10));
|
$paymentSyncIntervalMinutes = max(1, (int) ($paymentSyncIntervalMinutes ?? 10));
|
||||||
@@ -210,43 +211,44 @@ foreach ($dmMappings as $dm) {
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><?= $e($t('settings.order_statuses.fields.shoppro_code')) ?></th>
|
|
||||||
<th><?= $e($t('settings.order_statuses.fields.shoppro_name')) ?></th>
|
|
||||||
<th><?= $e($t('settings.order_statuses.fields.orderpro_status')) ?></th>
|
<th><?= $e($t('settings.order_statuses.fields.orderpro_status')) ?></th>
|
||||||
|
<th><?= $e($t('settings.order_statuses.fields.shoppro_status')) ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if ($statusRows === []): ?>
|
<?php if ($orderproStatuses === []): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" class="muted"><?= $e($t('settings.integrations.statuses.empty')) ?></td>
|
<td colspan="2" class="muted"><?= $e($t('settings.integrations.statuses.no_orderpro_statuses')) ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($statusRows as $row): ?>
|
<?php foreach ($orderproStatuses as $opStatus): ?>
|
||||||
<?php
|
<?php
|
||||||
$shopCode = (string) ($row['shoppro_status_code'] ?? '');
|
$opCode = strtolower(trim((string) ($opStatus['code'] ?? '')));
|
||||||
$shopName = (string) ($row['shoppro_status_name'] ?? '');
|
if ($opCode === '') continue;
|
||||||
$selectedOrderpro = strtolower(trim((string) ($row['orderpro_status_code'] ?? '')));
|
$opName = (string) ($opStatus['name'] ?? $opCode);
|
||||||
|
$mapped = $mappingIndex[$opCode] ?? null;
|
||||||
|
$selectedShopCode = $mapped !== null ? (string) ($mapped['shoppro_status_code'] ?? '') : '';
|
||||||
?>
|
?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<code><?= $e($shopCode) ?></code>
|
<?= $e($opName) ?> <code class="muted"><?= $e($opCode) ?></code>
|
||||||
<input type="hidden" name="shoppro_status_code[]" value="<?= $e($shopCode) ?>">
|
<input type="hidden" name="orderpro_status_code[]" value="<?= $e($opCode) ?>">
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?= $e($shopName) ?>
|
<select class="form-control" name="shoppro_status_code[]" data-shoppro-name-target="shoppro_status_name_<?= $e($opCode) ?>">
|
||||||
<input type="hidden" name="shoppro_status_name[]" value="<?= $e($shopName) ?>">
|
<option value="" data-name=""><?= $e($t('settings.order_statuses.fields.no_mapping')) ?></option>
|
||||||
</td>
|
<?php foreach ($shopproStatuses as $extStatus): ?>
|
||||||
<td>
|
<?php
|
||||||
<select class="form-control" name="orderpro_status_code[]">
|
$extCode = (string) ($extStatus['code'] ?? '');
|
||||||
<option value=""><?= $e($t('settings.order_statuses.fields.no_mapping')) ?></option>
|
$extName = (string) ($extStatus['name'] ?? $extCode);
|
||||||
<?php foreach ($orderproStatuses as $status): ?>
|
if ($extCode === '') continue;
|
||||||
<?php $statusCode = strtolower(trim((string) ($status['code'] ?? ''))); ?>
|
?>
|
||||||
<?php if ($statusCode === '') continue; ?>
|
<option value="<?= $e($extCode) ?>" data-name="<?= $e($extName) ?>"<?= $selectedShopCode === $extCode ? ' selected' : '' ?>>
|
||||||
<option value="<?= $e($statusCode) ?>"<?= $selectedOrderpro === $statusCode ? ' selected' : '' ?>>
|
<?= $e($extName) ?> (<?= $e($extCode) ?>)
|
||||||
<?= $e((string) ($status['name'] ?? $statusCode)) ?> (<?= $e($statusCode) ?>)
|
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
|
<input type="hidden" name="shoppro_status_name[]" id="shoppro_status_name_<?= $e($opCode) ?>" value="<?= $e($mapped !== null ? (string) ($mapped['shoppro_status_name'] ?? '') : '') ?>">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -254,7 +256,7 @@ foreach ($dmMappings as $dm) {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<?php if ($statusRows !== []): ?>
|
<?php if ($orderproStatuses !== []): ?>
|
||||||
<div class="form-actions mt-12">
|
<div class="form-actions mt-12">
|
||||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.order_statuses.actions.save')) ?></button>
|
<button type="submit" class="btn btn--primary"><?= $e($t('settings.order_statuses.actions.save')) ?></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -740,4 +742,16 @@ foreach ($dmMappings as $dm) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
document.querySelectorAll('select[data-shoppro-name-target]').forEach(function (select) {
|
||||||
|
select.addEventListener('change', function () {
|
||||||
|
var targetId = select.getAttribute('data-shoppro-name-target');
|
||||||
|
var hidden = document.getElementById(targetId);
|
||||||
|
if (!hidden) return;
|
||||||
|
var selected = select.options[select.selectedIndex];
|
||||||
|
hidden.value = selected ? (selected.getAttribute('data-name') || '') : '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ final class AllegroIntegrationController
|
|||||||
'statusSyncIntervalMinutes' => $statusSyncIntervalMinutes,
|
'statusSyncIntervalMinutes' => $statusSyncIntervalMinutes,
|
||||||
'statusMappings' => $this->statusMappings->listMappings(),
|
'statusMappings' => $this->statusMappings->listMappings(),
|
||||||
'orderproStatuses' => $this->orderStatuses->listStatuses(),
|
'orderproStatuses' => $this->orderStatuses->listStatuses(),
|
||||||
|
'allegroStatuses' => $this->statusMappings->listExternalStatuses(),
|
||||||
'defaultRedirectUri' => $defaultRedirectUri,
|
'defaultRedirectUri' => $defaultRedirectUri,
|
||||||
'errorMessage' => (string) Flash::get('settings_error', ''),
|
'errorMessage' => (string) Flash::get('settings_error', ''),
|
||||||
'successMessage' => (string) Flash::get('settings_success', ''),
|
'successMessage' => (string) Flash::get('settings_success', ''),
|
||||||
|
|||||||
@@ -23,37 +23,6 @@ final class AllegroStatusMappingController
|
|||||||
|
|
||||||
public function saveStatusMapping(Request $request): Response
|
public function saveStatusMapping(Request $request): Response
|
||||||
{
|
{
|
||||||
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
|
|
||||||
if ($csrfError !== null) {
|
|
||||||
return $csrfError;
|
|
||||||
}
|
|
||||||
|
|
||||||
$allegroStatusCode = strtolower(trim((string) $request->input('allegro_status_code', '')));
|
|
||||||
$orderproStatusCode = strtolower(trim((string) $request->input('orderpro_status_code', '')));
|
|
||||||
$allegroStatusName = trim((string) $request->input('allegro_status_name', ''));
|
|
||||||
|
|
||||||
if ($allegroStatusCode === '') {
|
|
||||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.allegro_status_required'));
|
|
||||||
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($orderproStatusCode === '') {
|
|
||||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.orderpro_status_required'));
|
|
||||||
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->orderStatusCodeExists($orderproStatusCode)) {
|
|
||||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.orderpro_status_not_found'));
|
|
||||||
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->statusMappings->upsertMapping($allegroStatusCode, $allegroStatusName !== '' ? $allegroStatusName : null, $orderproStatusCode);
|
|
||||||
Flash::set('settings_success', $this->translator->get('settings.allegro.statuses.flash.saved'));
|
|
||||||
} 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);
|
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,37 +33,39 @@ final class AllegroStatusMappingController
|
|||||||
return $csrfError;
|
return $csrfError;
|
||||||
}
|
}
|
||||||
|
|
||||||
$codes = $request->input('allegro_status_code', []);
|
$orderproCodes = $request->input('orderpro_status_code', []);
|
||||||
$names = $request->input('allegro_status_name', []);
|
$allegroCodes = $request->input('allegro_status_code', []);
|
||||||
$selectedOrderproCodes = $request->input('orderpro_status_code', []);
|
$allegroNames = $request->input('allegro_status_name', []);
|
||||||
if (!is_array($codes) || !is_array($names) || !is_array($selectedOrderproCodes)) {
|
if (!is_array($orderproCodes) || !is_array($allegroCodes) || !is_array($allegroNames)) {
|
||||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed'));
|
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed'));
|
||||||
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
$allowedOrderpro = $this->resolveAllowedOrderproStatusCodes();
|
||||||
foreach ($codes as $index => $rawCode) {
|
$mappings = [];
|
||||||
$allegroStatusCode = strtolower(trim((string) $rawCode));
|
|
||||||
if ($allegroStatusCode === '') {
|
foreach ($orderproCodes as $index => $rawOrderproCode) {
|
||||||
|
$orderproCode = strtolower(trim((string) $rawOrderproCode));
|
||||||
|
if ($orderproCode === '' || !isset($allowedOrderpro[$orderproCode])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$allegroStatusName = trim((string) ($names[$index] ?? ''));
|
$allegroCode = strtolower(trim((string) ($allegroCodes[$index] ?? '')));
|
||||||
$orderproStatusCodeRaw = strtolower(trim((string) ($selectedOrderproCodes[$index] ?? '')));
|
if ($allegroCode === '') {
|
||||||
$orderproStatusCode = $orderproStatusCodeRaw !== '' ? $orderproStatusCodeRaw : null;
|
continue;
|
||||||
|
|
||||||
if ($orderproStatusCode !== null && !$this->orderStatusCodeExists($orderproStatusCode)) {
|
|
||||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.orderpro_status_not_found'));
|
|
||||||
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->statusMappings->upsertMapping(
|
$allegroName = trim((string) ($allegroNames[$index] ?? ''));
|
||||||
$allegroStatusCode,
|
|
||||||
$allegroStatusName !== '' ? $allegroStatusName : null,
|
$mappings[] = [
|
||||||
$orderproStatusCode
|
'orderpro_status_code' => $orderproCode,
|
||||||
);
|
'allegro_status_code' => $allegroCode,
|
||||||
|
'allegro_status_name' => $allegroName,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->statusMappings->replaceAllMappings($mappings);
|
||||||
Flash::set('settings_success', $this->translator->get('settings.allegro.statuses.flash.saved_bulk'));
|
Flash::set('settings_success', $this->translator->get('settings.allegro.statuses.flash.saved_bulk'));
|
||||||
} catch (Throwable $exception) {
|
} catch (Throwable $exception) {
|
||||||
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed') . ' ' . $exception->getMessage());
|
Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed') . ' ' . $exception->getMessage());
|
||||||
@@ -149,21 +120,20 @@ final class AllegroStatusMappingController
|
|||||||
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
return Response::redirect(RedirectPaths::ALLEGRO_STATUSES_TAB);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function orderStatusCodeExists(string $code): bool
|
/**
|
||||||
|
* @return array<string, true>
|
||||||
|
*/
|
||||||
|
private function resolveAllowedOrderproStatusCodes(): array
|
||||||
{
|
{
|
||||||
$needle = strtolower(trim($code));
|
$allowed = [];
|
||||||
if ($needle === '') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->orderStatuses->listStatuses() as $row) {
|
foreach ($this->orderStatuses->listStatuses() as $row) {
|
||||||
$statusCode = strtolower(trim((string) ($row['code'] ?? '')));
|
$code = strtolower(trim((string) ($row['code'] ?? '')));
|
||||||
if ($statusCode === $needle) {
|
if ($code !== '') {
|
||||||
return true;
|
$allowed[$code] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return $allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function validateCsrf(string $token): ?Response
|
private function validateCsrf(string $token): ?Response
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ final class AllegroStatusMappingRepository
|
|||||||
$statement = $this->pdo->query(
|
$statement = $this->pdo->query(
|
||||||
'SELECT id, allegro_status_code, allegro_status_name, orderpro_status_code, created_at, updated_at
|
'SELECT id, allegro_status_code, allegro_status_name, orderpro_status_code, created_at, updated_at
|
||||||
FROM allegro_order_status_mappings
|
FROM allegro_order_status_mappings
|
||||||
ORDER BY allegro_status_code ASC'
|
ORDER BY orderpro_status_code ASC, allegro_status_code ASC'
|
||||||
);
|
);
|
||||||
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||||
if (!is_array($rows)) {
|
if (!is_array($rows)) {
|
||||||
@@ -39,29 +39,67 @@ final class AllegroStatusMappingRepository
|
|||||||
}, $rows);
|
}, $rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function upsertMapping(string $allegroStatusCode, ?string $allegroStatusName, ?string $orderproStatusCode): void
|
/**
|
||||||
|
* @return array<int, array{code:string,name:string}>
|
||||||
|
*/
|
||||||
|
public function listExternalStatuses(): array
|
||||||
{
|
{
|
||||||
$code = strtolower(trim($allegroStatusCode));
|
$statement = $this->pdo->query(
|
||||||
$orderproCode = $orderproStatusCode !== null ? strtolower(trim($orderproStatusCode)) : null;
|
'SELECT DISTINCT allegro_status_code, allegro_status_name
|
||||||
|
FROM allegro_order_status_mappings
|
||||||
|
WHERE allegro_status_code IS NOT NULL
|
||||||
|
AND allegro_status_code <> ""
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = strtolower(trim((string) ($row['allegro_status_code'] ?? '')));
|
||||||
if ($code === '') {
|
if ($code === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[] = [
|
||||||
|
'code' => $code,
|
||||||
|
'name' => trim((string) ($row['allegro_status_name'] ?? $code)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function upsertMapping(string $orderproStatusCode, ?string $allegroStatusCode, ?string $allegroStatusName): void
|
||||||
|
{
|
||||||
|
$orderproCode = strtolower(trim($orderproStatusCode));
|
||||||
|
if ($orderproCode === '') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$allegroCode = $allegroStatusCode !== null ? strtolower(trim($allegroStatusCode)) : null;
|
||||||
|
|
||||||
$statement = $this->pdo->prepare(
|
$statement = $this->pdo->prepare(
|
||||||
'INSERT INTO allegro_order_status_mappings (
|
'INSERT INTO allegro_order_status_mappings (
|
||||||
allegro_status_code, allegro_status_name, orderpro_status_code, created_at, updated_at
|
orderpro_status_code, allegro_status_code, allegro_status_name, created_at, updated_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
:allegro_status_code, :allegro_status_name, :orderpro_status_code, NOW(), NOW()
|
:orderpro_status_code, :allegro_status_code, :allegro_status_name, NOW(), NOW()
|
||||||
)
|
)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
|
allegro_status_code = VALUES(allegro_status_code),
|
||||||
allegro_status_name = VALUES(allegro_status_name),
|
allegro_status_name = VALUES(allegro_status_name),
|
||||||
orderpro_status_code = VALUES(orderpro_status_code),
|
|
||||||
updated_at = VALUES(updated_at)'
|
updated_at = VALUES(updated_at)'
|
||||||
);
|
);
|
||||||
$statement->execute([
|
$statement->execute([
|
||||||
'allegro_status_code' => $code,
|
'orderpro_status_code' => $orderproCode,
|
||||||
|
'allegro_status_code' => $allegroCode !== null && $allegroCode !== '' ? $allegroCode : null,
|
||||||
'allegro_status_name' => StringHelper::nullableString((string) $allegroStatusName),
|
'allegro_status_name' => StringHelper::nullableString((string) $allegroStatusName),
|
||||||
'orderpro_status_code' => $orderproCode !== null && $orderproCode !== '' ? $orderproCode : null,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,20 +110,41 @@ final class AllegroStatusMappingRepository
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$statement = $this->pdo->prepare(
|
$existing = $this->pdo->prepare(
|
||||||
|
'SELECT id FROM allegro_order_status_mappings
|
||||||
|
WHERE allegro_status_code = :allegro_status_code
|
||||||
|
LIMIT 1'
|
||||||
|
);
|
||||||
|
$existing->execute(['allegro_status_code' => $code]);
|
||||||
|
|
||||||
|
if ($existing->fetchColumn() !== false) {
|
||||||
|
$update = $this->pdo->prepare(
|
||||||
|
'UPDATE allegro_order_status_mappings
|
||||||
|
SET allegro_status_name = CASE
|
||||||
|
WHEN :allegro_status_name IS NULL OR :allegro_status_name2 = "" THEN allegro_status_name
|
||||||
|
ELSE :allegro_status_name3
|
||||||
|
END,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE allegro_status_code = :allegro_status_code'
|
||||||
|
);
|
||||||
|
$name = StringHelper::nullableString((string) $allegroStatusName);
|
||||||
|
$update->execute([
|
||||||
|
'allegro_status_name' => $name,
|
||||||
|
'allegro_status_name2' => (string) $name,
|
||||||
|
'allegro_status_name3' => $name,
|
||||||
|
'allegro_status_code' => $code,
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$insert = $this->pdo->prepare(
|
||||||
'INSERT INTO allegro_order_status_mappings (
|
'INSERT INTO allegro_order_status_mappings (
|
||||||
allegro_status_code, allegro_status_name, orderpro_status_code, created_at, updated_at
|
allegro_status_code, allegro_status_name, orderpro_status_code, created_at, updated_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
:allegro_status_code, :allegro_status_name, NULL, NOW(), NOW()
|
:allegro_status_code, :allegro_status_name, NULL, NOW(), NOW()
|
||||||
)
|
)'
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
allegro_status_name = CASE
|
|
||||||
WHEN VALUES(allegro_status_name) IS NULL OR VALUES(allegro_status_name) = "" THEN allegro_status_name
|
|
||||||
ELSE VALUES(allegro_status_name)
|
|
||||||
END,
|
|
||||||
updated_at = VALUES(updated_at)'
|
|
||||||
);
|
);
|
||||||
$statement->execute([
|
$insert->execute([
|
||||||
'allegro_status_code' => $code,
|
'allegro_status_code' => $code,
|
||||||
'allegro_status_name' => StringHelper::nullableString((string) $allegroStatusName),
|
'allegro_status_name' => StringHelper::nullableString((string) $allegroStatusName),
|
||||||
]);
|
]);
|
||||||
@@ -112,6 +171,8 @@ final class AllegroStatusMappingRepository
|
|||||||
'SELECT orderpro_status_code
|
'SELECT orderpro_status_code
|
||||||
FROM allegro_order_status_mappings
|
FROM allegro_order_status_mappings
|
||||||
WHERE allegro_status_code = :allegro_status_code
|
WHERE allegro_status_code = :allegro_status_code
|
||||||
|
AND orderpro_status_code IS NOT NULL
|
||||||
|
AND orderpro_status_code <> ""
|
||||||
LIMIT 1'
|
LIMIT 1'
|
||||||
);
|
);
|
||||||
$statement->execute(['allegro_status_code' => $code]);
|
$statement->execute(['allegro_status_code' => $code]);
|
||||||
@@ -130,10 +191,12 @@ final class AllegroStatusMappingRepository
|
|||||||
public function buildOrderproToAllegroMap(): array
|
public function buildOrderproToAllegroMap(): array
|
||||||
{
|
{
|
||||||
$statement = $this->pdo->query(
|
$statement = $this->pdo->query(
|
||||||
'SELECT allegro_status_code, orderpro_status_code
|
'SELECT orderpro_status_code, allegro_status_code
|
||||||
FROM allegro_order_status_mappings
|
FROM allegro_order_status_mappings
|
||||||
WHERE orderpro_status_code IS NOT NULL
|
WHERE orderpro_status_code IS NOT NULL
|
||||||
AND orderpro_status_code <> ""
|
AND orderpro_status_code <> ""
|
||||||
|
AND allegro_status_code IS NOT NULL
|
||||||
|
AND allegro_status_code <> ""
|
||||||
ORDER BY id ASC'
|
ORDER BY id ASC'
|
||||||
);
|
);
|
||||||
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@@ -157,4 +220,74 @@ final class AllegroStatusMappingRepository
|
|||||||
return $map;
|
return $map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string> allegro_status_code => orderpro_status_code
|
||||||
|
*/
|
||||||
|
public function buildAllegroToOrderproMap(): array
|
||||||
|
{
|
||||||
|
$statement = $this->pdo->query(
|
||||||
|
'SELECT allegro_status_code, orderpro_status_code
|
||||||
|
FROM allegro_order_status_mappings
|
||||||
|
WHERE allegro_status_code IS NOT NULL
|
||||||
|
AND allegro_status_code <> ""
|
||||||
|
AND orderpro_status_code IS NOT NULL
|
||||||
|
AND orderpro_status_code <> ""
|
||||||
|
ORDER BY id ASC'
|
||||||
|
);
|
||||||
|
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
if (!is_array($rows)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$allegroCode = strtolower(trim((string) ($row['allegro_status_code'] ?? '')));
|
||||||
|
$orderproCode = strtolower(trim((string) ($row['orderpro_status_code'] ?? '')));
|
||||||
|
if ($allegroCode === '' || $orderproCode === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($map[$allegroCode])) {
|
||||||
|
$map[$allegroCode] = $orderproCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array{orderpro_status_code:string,allegro_status_code:string,allegro_status_name:string}> $mappings
|
||||||
|
*/
|
||||||
|
public function replaceAllMappings(array $mappings): void
|
||||||
|
{
|
||||||
|
$this->pdo->exec(
|
||||||
|
'DELETE FROM allegro_order_status_mappings WHERE orderpro_status_code IS NOT NULL AND orderpro_status_code <> ""'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($mappings === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$insertStatement = $this->pdo->prepare(
|
||||||
|
'INSERT INTO allegro_order_status_mappings (
|
||||||
|
orderpro_status_code, allegro_status_code, allegro_status_name, created_at, updated_at
|
||||||
|
) VALUES (
|
||||||
|
:orderpro_status_code, :allegro_status_code, :allegro_status_name, NOW(), NOW()
|
||||||
|
)'
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($mappings as $mapping) {
|
||||||
|
$orderproCode = strtolower(trim((string) ($mapping['orderpro_status_code'] ?? '')));
|
||||||
|
$allegroCode = strtolower(trim((string) ($mapping['allegro_status_code'] ?? '')));
|
||||||
|
if ($orderproCode === '' || $allegroCode === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$insertStatement->execute([
|
||||||
|
'orderpro_status_code' => $orderproCode,
|
||||||
|
'allegro_status_code' => $allegroCode,
|
||||||
|
'allegro_status_name' => trim((string) ($mapping['allegro_status_name'] ?? '')) ?: null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,9 +66,13 @@ final class ShopproIntegrationsController
|
|||||||
$this->ensureStatusSyncScheduleExists();
|
$this->ensureStatusSyncScheduleExists();
|
||||||
$this->ensurePaymentSyncScheduleExists();
|
$this->ensurePaymentSyncScheduleExists();
|
||||||
$activeTab = $this->resolveTab((string) $request->input('tab', 'integration'));
|
$activeTab = $this->resolveTab((string) $request->input('tab', 'integration'));
|
||||||
|
$integrationId = $selectedIntegration !== null ? (int) ($selectedIntegration['id'] ?? 0) : 0;
|
||||||
$discoveredStatuses = $this->readDiscoveredStatuses();
|
$discoveredStatuses = $this->readDiscoveredStatuses();
|
||||||
$statusRows = $selectedIntegration !== null
|
$mappingIndex = $integrationId > 0
|
||||||
? $this->buildStatusRows((int) ($selectedIntegration['id'] ?? 0), $discoveredStatuses)
|
? $this->buildMappingIndex($integrationId)
|
||||||
|
: [];
|
||||||
|
$shopproStatuses = $integrationId > 0
|
||||||
|
? $this->buildExternalStatusOptions($integrationId, $discoveredStatuses)
|
||||||
: [];
|
: [];
|
||||||
$deliveryServicesData = $activeTab === 'delivery'
|
$deliveryServicesData = $activeTab === 'delivery'
|
||||||
? $this->loadDeliveryServices()
|
? $this->loadDeliveryServices()
|
||||||
@@ -93,8 +97,9 @@ final class ShopproIntegrationsController
|
|||||||
'ordersImportIntervalMinutes' => $this->currentImportIntervalMinutes(),
|
'ordersImportIntervalMinutes' => $this->currentImportIntervalMinutes(),
|
||||||
'statusSyncIntervalMinutes' => $this->currentStatusSyncIntervalMinutes(),
|
'statusSyncIntervalMinutes' => $this->currentStatusSyncIntervalMinutes(),
|
||||||
'paymentSyncIntervalMinutes' => $this->currentPaymentSyncIntervalMinutes(),
|
'paymentSyncIntervalMinutes' => $this->currentPaymentSyncIntervalMinutes(),
|
||||||
'statusRows' => $statusRows,
|
'mappingIndex' => $mappingIndex,
|
||||||
'orderproStatuses' => $this->orderStatuses->listStatuses(),
|
'orderproStatuses' => $this->orderStatuses->listStatuses(),
|
||||||
|
'shopproStatuses' => $shopproStatuses,
|
||||||
'deliveryMappings' => $deliveryMappings,
|
'deliveryMappings' => $deliveryMappings,
|
||||||
'orderDeliveryMethods' => $orderDeliveryMethods,
|
'orderDeliveryMethods' => $orderDeliveryMethods,
|
||||||
'allegroDeliveryServices' => $deliveryServicesData[0],
|
'allegroDeliveryServices' => $deliveryServicesData[0],
|
||||||
@@ -224,38 +229,34 @@ final class ShopproIntegrationsController
|
|||||||
return $accessError;
|
return $accessError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$orderCodes = $request->input('orderpro_status_code', []);
|
||||||
$shopCodes = $request->input('shoppro_status_code', []);
|
$shopCodes = $request->input('shoppro_status_code', []);
|
||||||
$shopNames = $request->input('shoppro_status_name', []);
|
$shopNames = $request->input('shoppro_status_name', []);
|
||||||
$orderCodes = $request->input('orderpro_status_code', []);
|
if (!is_array($orderCodes) || !is_array($shopCodes) || !is_array($shopNames)) {
|
||||||
if (!is_array($shopCodes) || !is_array($shopNames) || !is_array($orderCodes)) {
|
|
||||||
Flash::set('settings_error', $this->translator->get('settings.integrations.statuses.flash.invalid_payload'));
|
Flash::set('settings_error', $this->translator->get('settings.integrations.statuses.flash.invalid_payload'));
|
||||||
return Response::redirect($redirectTo);
|
return Response::redirect($redirectTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
$allowedOrderpro = $this->resolveAllowedOrderproStatusCodes();
|
$allowedOrderpro = $this->resolveAllowedOrderproStatusCodes();
|
||||||
$rowsCount = min(count($shopCodes), count($shopNames), count($orderCodes));
|
$rowsCount = min(count($orderCodes), count($shopCodes), count($shopNames));
|
||||||
$mappings = [];
|
$mappings = [];
|
||||||
for ($index = 0; $index < $rowsCount; $index++) {
|
for ($index = 0; $index < $rowsCount; $index++) {
|
||||||
|
$orderCode = strtolower(trim((string) ($orderCodes[$index] ?? '')));
|
||||||
$shopCode = trim((string) ($shopCodes[$index] ?? ''));
|
$shopCode = trim((string) ($shopCodes[$index] ?? ''));
|
||||||
$shopName = trim((string) ($shopNames[$index] ?? ''));
|
$shopName = trim((string) ($shopNames[$index] ?? ''));
|
||||||
$orderCode = strtolower(trim((string) ($orderCodes[$index] ?? '')));
|
|
||||||
|
if ($orderCode === '' || !isset($allowedOrderpro[$orderCode])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($shopCode === '') {
|
if ($shopCode === '') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($orderCode === '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($allowedOrderpro[$orderCode])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$mappings[] = [
|
$mappings[] = [
|
||||||
|
'orderpro_status_code' => $orderCode,
|
||||||
'shoppro_status_code' => $shopCode,
|
'shoppro_status_code' => $shopCode,
|
||||||
'shoppro_status_name' => $shopName,
|
'shoppro_status_name' => $shopName,
|
||||||
'orderpro_status_code' => $orderCode,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,55 +495,68 @@ final class ShopproIntegrationsController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, array{shoppro_status_code:string,shoppro_status_name:string,orderpro_status_code:string}>
|
* @return array<string, array{shoppro_status_code:string,shoppro_status_name:string}>
|
||||||
*/
|
*/
|
||||||
private function buildStatusRows(int $integrationId, array $discoveredStatuses): array
|
private function buildMappingIndex(int $integrationId): array
|
||||||
{
|
{
|
||||||
$mappedRows = $this->statusMappings->listByIntegration($integrationId);
|
$rows = $this->statusMappings->listByIntegration($integrationId);
|
||||||
$result = [];
|
$index = [];
|
||||||
|
|
||||||
foreach ($mappedRows as $row) {
|
foreach ($rows as $row) {
|
||||||
$code = trim((string) ($row['shoppro_status_code'] ?? ''));
|
$orderproCode = strtolower(trim((string) ($row['orderpro_status_code'] ?? '')));
|
||||||
if ($code === '') {
|
if ($orderproCode === '') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$key = mb_strtolower($code);
|
$index[$orderproCode] = [
|
||||||
$result[$key] = [
|
'shoppro_status_code' => trim((string) ($row['shoppro_status_code'] ?? '')),
|
||||||
'shoppro_status_code' => $code,
|
|
||||||
'shoppro_status_name' => trim((string) ($row['shoppro_status_name'] ?? '')),
|
'shoppro_status_name' => trim((string) ($row['shoppro_status_name'] ?? '')),
|
||||||
'orderpro_status_code' => strtolower(trim((string) ($row['orderpro_status_code'] ?? ''))),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array{code:string,name:string}> $discoveredStatuses
|
||||||
|
* @return array<int, array{code:string,name:string}>
|
||||||
|
*/
|
||||||
|
private function buildExternalStatusOptions(int $integrationId, array $discoveredStatuses): array
|
||||||
|
{
|
||||||
|
$existing = $this->statusMappings->listExternalStatuses($integrationId);
|
||||||
|
$seen = [];
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($existing as $status) {
|
||||||
|
$code = strtolower(trim((string) ($status['code'] ?? '')));
|
||||||
|
if ($code === '' || isset($seen[$code])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$seen[$code] = true;
|
||||||
|
$result[] = $status;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($discoveredStatuses as $status) {
|
foreach ($discoveredStatuses as $status) {
|
||||||
if (!is_array($status)) {
|
if (!is_array($status)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
$code = strtolower(trim((string) ($status['code'] ?? '')));
|
||||||
$code = trim((string) ($status['code'] ?? ''));
|
if ($code === '' || isset($seen[$code])) {
|
||||||
if ($code === '') {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
$seen[$code] = true;
|
||||||
$key = mb_strtolower($code);
|
$result[] = [
|
||||||
if (!isset($result[$key])) {
|
'code' => $code,
|
||||||
$result[$key] = [
|
'name' => trim((string) ($status['name'] ?? $code)),
|
||||||
'shoppro_status_code' => $code,
|
|
||||||
'shoppro_status_name' => trim((string) ($status['name'] ?? '')),
|
|
||||||
'orderpro_status_code' => '',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
uasort($result, static function (array $left, array $right): int {
|
usort($result, static fn (array $a, array $b): int => strcmp(
|
||||||
return strcmp(
|
strtolower((string) ($a['code'] ?? '')),
|
||||||
strtolower((string) ($left['shoppro_status_code'] ?? '')),
|
strtolower((string) ($b['code'] ?? ''))
|
||||||
strtolower((string) ($right['shoppro_status_code'] ?? ''))
|
));
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return array_values($result);
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ final class ShopproOrdersSyncService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, string>
|
* @return array<string, string> shoppro_status_code => orderpro_status_code (reverse of DB direction)
|
||||||
*/
|
*/
|
||||||
private function buildStatusMap(int $integrationId): array
|
private function buildStatusMap(int $integrationId): array
|
||||||
{
|
{
|
||||||
@@ -309,8 +309,10 @@ final class ShopproOrdersSyncService
|
|||||||
if ($shopCode === '' || $orderCode === '') {
|
if ($shopCode === '' || $orderCode === '') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!isset($map[$shopCode])) {
|
||||||
$map[$shopCode] = $orderCode;
|
$map[$shopCode] = $orderCode;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $map;
|
return $map;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ final class ShopproStatusMappingRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<int, array{shoppro_status_code:string,shoppro_status_name:string,orderpro_status_code:string}>
|
* @return array<int, array{orderpro_status_code:string,shoppro_status_code:string,shoppro_status_name:string}>
|
||||||
*/
|
*/
|
||||||
public function listByIntegration(int $integrationId): array
|
public function listByIntegration(int $integrationId): array
|
||||||
{
|
{
|
||||||
@@ -21,9 +21,53 @@ final class ShopproStatusMappingRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
$statement = $this->pdo->prepare(
|
$statement = $this->pdo->prepare(
|
||||||
'SELECT shoppro_status_code, shoppro_status_name, orderpro_status_code
|
'SELECT orderpro_status_code, shoppro_status_code, shoppro_status_name
|
||||||
FROM order_status_mappings
|
FROM order_status_mappings
|
||||||
WHERE integration_id = :integration_id
|
WHERE integration_id = :integration_id
|
||||||
|
ORDER BY orderpro_status_code ASC'
|
||||||
|
);
|
||||||
|
$statement->execute(['integration_id' => $integrationId]);
|
||||||
|
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!is_array($rows)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
if (!is_array($row)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderproCode = strtolower(trim((string) ($row['orderpro_status_code'] ?? '')));
|
||||||
|
if ($orderproCode === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[] = [
|
||||||
|
'orderpro_status_code' => $orderproCode,
|
||||||
|
'shoppro_status_code' => trim((string) ($row['shoppro_status_code'] ?? '')),
|
||||||
|
'shoppro_status_name' => trim((string) ($row['shoppro_status_name'] ?? '')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, array{code:string,name:string}>
|
||||||
|
*/
|
||||||
|
public function listExternalStatuses(int $integrationId): array
|
||||||
|
{
|
||||||
|
if ($integrationId <= 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$statement = $this->pdo->prepare(
|
||||||
|
'SELECT DISTINCT shoppro_status_code, shoppro_status_name
|
||||||
|
FROM order_status_mappings
|
||||||
|
WHERE integration_id = :integration_id
|
||||||
|
AND shoppro_status_code <> ""
|
||||||
ORDER BY shoppro_status_code ASC'
|
ORDER BY shoppro_status_code ASC'
|
||||||
);
|
);
|
||||||
$statement->execute(['integration_id' => $integrationId]);
|
$statement->execute(['integration_id' => $integrationId]);
|
||||||
@@ -39,15 +83,14 @@ final class ShopproStatusMappingRepository
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$shopproCode = trim((string) ($row['shoppro_status_code'] ?? ''));
|
$code = trim((string) ($row['shoppro_status_code'] ?? ''));
|
||||||
if ($shopproCode === '') {
|
if ($code === '') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result[] = [
|
$result[] = [
|
||||||
'shoppro_status_code' => $shopproCode,
|
'code' => $code,
|
||||||
'shoppro_status_name' => trim((string) ($row['shoppro_status_name'] ?? '')),
|
'name' => trim((string) ($row['shoppro_status_name'] ?? $code)),
|
||||||
'orderpro_status_code' => trim((string) ($row['orderpro_status_code'] ?? '')),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +98,7 @@ final class ShopproStatusMappingRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int, array{shoppro_status_code:string,shoppro_status_name:string,orderpro_status_code:string}> $mappings
|
* @param array<int, array{orderpro_status_code:string,shoppro_status_code:string,shoppro_status_name:string}> $mappings
|
||||||
*/
|
*/
|
||||||
public function replaceForIntegration(int $integrationId, array $mappings): void
|
public function replaceForIntegration(int $integrationId, array $mappings): void
|
||||||
{
|
{
|
||||||
@@ -74,25 +117,25 @@ final class ShopproStatusMappingRepository
|
|||||||
|
|
||||||
$insertStatement = $this->pdo->prepare(
|
$insertStatement = $this->pdo->prepare(
|
||||||
'INSERT INTO order_status_mappings (
|
'INSERT INTO order_status_mappings (
|
||||||
integration_id, shoppro_status_code, shoppro_status_name, orderpro_status_code, created_at, updated_at
|
integration_id, orderpro_status_code, shoppro_status_code, shoppro_status_name, created_at, updated_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
:integration_id, :shoppro_status_code, :shoppro_status_name, :orderpro_status_code, NOW(), NOW()
|
:integration_id, :orderpro_status_code, :shoppro_status_code, :shoppro_status_name, NOW(), NOW()
|
||||||
)'
|
)'
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($mappings as $mapping) {
|
foreach ($mappings as $mapping) {
|
||||||
|
$orderproCode = strtolower(trim((string) ($mapping['orderpro_status_code'] ?? '')));
|
||||||
$shopproCode = trim((string) ($mapping['shoppro_status_code'] ?? ''));
|
$shopproCode = trim((string) ($mapping['shoppro_status_code'] ?? ''));
|
||||||
$orderproCode = trim((string) ($mapping['orderpro_status_code'] ?? ''));
|
if ($orderproCode === '' || $shopproCode === '') {
|
||||||
if ($shopproCode === '' || $orderproCode === '') {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$shopproName = trim((string) ($mapping['shoppro_status_name'] ?? ''));
|
$shopproName = trim((string) ($mapping['shoppro_status_name'] ?? ''));
|
||||||
$insertStatement->execute([
|
$insertStatement->execute([
|
||||||
'integration_id' => $integrationId,
|
'integration_id' => $integrationId,
|
||||||
|
'orderpro_status_code' => $orderproCode,
|
||||||
'shoppro_status_code' => $shopproCode,
|
'shoppro_status_code' => $shopproCode,
|
||||||
'shoppro_status_name' => $shopproName !== '' ? $shopproName : null,
|
'shoppro_status_name' => $shopproName !== '' ? $shopproName : null,
|
||||||
'orderpro_status_code' => $orderproCode,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user