feat(129): erli status mapping sync

Phase 129 complete:
- Add Erli pull/push status mapping tables, seeds and repositories
- Wire Erli status sync cron for inbox pull and manual-only push
- Add tabbed Erli settings UI, tests and documentation

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-16 00:27:08 +02:00
parent c127ebf04d
commit 7972bb9fa4
28 changed files with 2021 additions and 57 deletions

View File

@@ -13,8 +13,8 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
| Attribute | Value |
|-----------|-------|
| Version | 3.8.0-dev |
| Status | v3.8 Erli Marketplace Integration in progress — Phase 128 shipped (Erli orders import); Phase 129 next |
| Last Updated | 2026-05-15 (Phase 128 closed) |
| Status | v3.8 Erli Marketplace Integration in progress — Phase 129 shipped (Erli status mappings/sync); Phase 130 next |
| Last Updated | 2026-05-16 (Phase 129 closed) |
## Requirements
@@ -128,6 +128,7 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
- [x] Bugfix detekcji faktury przy imporcie: shopPRO order z `firm_nip` ustawia `invoice_requested=1` (mapper jako jedyne zrodlo heurystyki, sync service propaguje `aggregate['invoice_detected']`); Allegro rozszerzony o `naturalPerson=false`/`address.taxId`/`companyName` (wczesniej tylko `invoice.required`); usunieta legacy kolumna `orders.is_invoice` (Phase 115 dryft) + backfill 7 zamowien — Phase 125
- [x] Fundament integracji Erli: pojedyncza globalna konfiguracja `/settings/integrations/erli`, szyfrowany Bearer API key, realny test `GET /svc/shop-api/inbox`, karta w hubie integracji oraz dokumentacja schematu/architektury — Phase 127
- [x] Import zamowien Erli: pobieranie `/inbox` przez cron i recznie, mapper do orderPRO, delta-only re-import, `invoice_requested` z danych firmowych/NIP, bezpieczny ACK `/inbox/mark-read` po bezblednym batchu — Phase 128
- [x] Mapowanie i synchronizacja statusow Erli: osobne pull/push mappings, discovery statusow z inboxa, reczny-only push `PATCH /orders/{id}/status`, cron `erli_status_sync` i zakladki w ustawieniach Erli — Phase 129
### Deferred
@@ -136,7 +137,7 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
### Active (In Progress)
- [ ] v3.8 Erli Marketplace Integration — Phase 129 next: mapowanie statusow pull/push Erli i synchronizacja statusow.
- [ ] v3.8 Erli Marketplace Integration — Phase 130 next: generowanie etykiet i obsluga przesylek Erli.
### Planned (Next)
@@ -247,6 +248,9 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API
| Erli import uzywa `/inbox` jako glownego zrodla zdarzen | Model inbox jest event-driven i pasuje do bezpiecznego przetwarzania batchy oraz przyszlych aktualizacji statusow | 2026-05-15 | Active |
| ACK Erli przez `POST /inbox/mark-read` tylko po bezblednym batchu | Zapobiega utracie zdarzen, gdy lokalny import czesciowo sie nie powiedzie | 2026-05-15 | Active |
| Phase 128 ma domyslne mapowania statusow, a UI mapowan dopiero Phase 129 | Import ma realnie dzialac teraz, a pelne strojenie pull/push statusow wymaga osobnej fazy | 2026-05-15 | Active |
| Push statusow Erli obejmuje tylko reczne zmiany orderPRO (`change_source='manual'`) | Chroni przed petlami po imporcie, automatyzacjach i systemowych zmianach statusu | 2026-05-16 | Active |
| Erli -> orderPRO status pull uzywa tego samego inbox + ACK flow co import zamowien | Jedno bezpieczne zrodlo zdarzen Erli; brak osobnego status endpointu do utrzymania | 2026-05-16 | Active |
| Erli settings korzysta z zakladek Integracja/Statusy/Ustawienia | Po dodaniu mapowan strona wymagala parytetu UX z Allegro/shopPRO | 2026-05-16 | Active |
## Success Metrics
@@ -278,6 +282,6 @@ Quick Reference:
---
*PROJECT.md — Updated when requirements or context change*
*Last updated: 2026-05-15 after Phase 128 (Erli Orders Import) closure; v3.8 milestone in progress*
*Last updated: 2026-05-16 after Phase 129 (Erli Status Mapping + Sync) closure; v3.8 milestone in progress*

View File

@@ -10,11 +10,13 @@ v3.8 Erli Marketplace Integration — In progress
Pelna integracja z erli.pl wzorowana na istniejacej integracji Allegro: konfiguracja konta/API, pobieranie zamowien, mapowanie i synchronizacja statusow, generowanie etykiet, tracking oraz wlaczenie Erli w istniejace przeplywy automatyzacji, statystyk i obslugi zamowien.
Progress: 3 of 6 phases complete (50%).
| Phase | Name | Plans | Status |
|-------|------|-------|--------|
| 127 | Erli Integration Foundation | 1/1 | Complete (2026-05-15; migration/manual Erli API smoke pending operator) |
| 128 | Erli Orders Import | 1/1 | Complete (2026-05-15; migration/manual Erli import smoke pending operator) |
| 129 | Erli Status Mapping + Sync | TBD | Not started |
| 129 | Erli Status Mapping + Sync | 1/1 | Complete (2026-05-16; migration/manual Erli status smoke pending operator) |
| 130 | Erli Shipments + Labels | TBD | Not started |
| 131 | Erli Tracking + Automation Hooks | TBD | Not started |
| 132 | Erli Hardening, Observability + Docs | TBD | Not started |
@@ -32,7 +34,7 @@ Plans: 128-01 (complete)
### Phase 129: Erli Status Mapping + Sync
Focus: Osobne mapowanie pull/push statusow Erli, auto-discovery nieznanych statusow, cron synchronizacji orderPRO -> Erli i ochrona lokalnych statusow przy re-imporcie analogicznie do Allegro/shopPRO.
Plans: TBD (defined during $paul-plan)
Plans: 129-01 (complete)
### Phase 130: Erli Shipments + Labels
@@ -553,4 +555,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
---
*Roadmap created: 2026-03-12*
*Last updated: 2026-05-15 - Phase 128 UNIFY closed*
*Last updated: 2026-05-16 - Phase 129 complete, ready for Phase 130 planning*

View File

@@ -2,22 +2,22 @@
## Project Reference
See: .paul/PROJECT.md (updated 2026-05-07)
See: .paul/PROJECT.md (updated 2026-05-16)
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
**Current focus:** v3.8 Erli Marketplace Integration - Phase 128 complete; Phase 129 ready to plan.
**Current focus:** v3.8 Erli Marketplace Integration - Phase 130 ready to plan.
## Current Position
Milestone: v3.8 Erli Marketplace Integration
Phase: 129 of 132 (Erli Status Mapping + Sync)
Phase: 130 of 132 (Erli Shipments + Labels) - Not started
Plan: Not started
Status: Ready to plan
Last activity: 2026-05-15 23:52 - Phase 128 complete; transitioned to Phase 129
Last activity: 2026-05-16 00:23 - Phase 129 complete, transitioned to Phase 130
Progress:
- Milestone v3.8: [####------] ~33% (Phases 127-128 complete)
- Phase 129: [----------] 0% (not planned)
- Milestone v3.8: [#####-----] 50% (Phases 127-129 complete; Phase 130 next)
- Phase 130: [----------] 0% (not planned)
## Loop Position
@@ -29,20 +29,26 @@ PLAN -> APPLY -> UNIFY
## Session Continuity
Last session: 2026-05-15 23:52
Stopped at: Phase 128 complete
Next action: $paul-plan for Phase 129 (Erli Status Mapping + Sync)
Resume file: .paul/phases/128-erli-orders-import/128-01-SUMMARY.md
Last session: 2026-05-16 00:23
Stopped at: Phase 129 complete, ready to plan Phase 130
Next action: $paul-plan for Phase 130 (Erli Shipments + Labels)
Resume file: .paul/ROADMAP.md
## Pending parallel work
- None — Phase 118, 121, 122 wszystkie zacommitowane (8f14851, 360eef1).
## Git State
Last phase commit: 2565d9b feat(128): erli orders import
Previous: d6b18a6 feat(127): erli integration foundation
Last phase commit: b371420 feat(129): erli status mapping sync
Previous: 2565d9b feat(128): erli orders import
Branch: main
### Skill Audit (Phase 129)
| Expected | Invoked | Notes |
|----------|---------|-------|
| `sonar-scanner` | gap documented | Attempted before UNIFY; CLI is not available in PATH. |
## Pending Actions
- Manualne testy AC-1..AC-7 dla Phase 112 na zywej bazie (XAMPP online).
@@ -68,6 +74,8 @@ Branch: main
- Phase 127 follow-up: uruchom `php bin/migrate.php` gdy lokalny MySQL/XAMPP jest online, zapisz prawdziwy klucz Erli w `/settings/integrations/erli`, wykonaj realny test polaczenia i potwierdz wpis w hubie integracji.
- Phase 128 follow-up: uruchom `php bin/migrate.php`, wlacz import Erli w `/settings/integrations/erli`, kliknij `Importuj zamowienia teraz`, potwierdz `orders.source='erli'` i sprawdz, ze przy bezblednym batchu inbox ACK `POST /inbox/mark-read` nie zostawia nieprzeczytanych zdarzen.
- Phase 128 verification gap: `vendor/bin/phpunit` nie istnieje w checkoutcie, wiec test `tests/Unit/ErliOrderMapperTest.php` nie zostal uruchomiony przez PHPUnit; wykonano `php -l` i runtime smoke mappera.
- Phase 129 follow-up: uruchom `php bin/migrate.php`, sprawdz `/settings/integrations/erli` mapowania pull/push i zakladki, zapisz mapowania, ustaw `orderPRO -> Erli`, zmien recznie status zamowienia Erli i uruchom cron `erli_status_sync`.
- Phase 129 verification gap: `vendor/bin/phpunit` nie istnieje w checkoutcie, a globalny XAMPP PHPUnit jest niekompatybilny z PHP (`each()` removed), wiec testy `ErliOrderMapperTest` i `ErliStatusSyncServiceTest` nie zostaly uruchomione przez PHPUnit; wykonano `php -l`, runtime smoke mappera i `git diff --check`.
## Deferred to Next Milestones
@@ -78,4 +86,4 @@ Branch: main
## Skill Requirements
- `sonar-scanner` required after APPLY; Phase 116, Phase 117, Phase 121, Phase 122 and Phase 128 gaps documented because CLI was not available in PATH.
- `sonar-scanner` required after APPLY; Phase 116, Phase 117, Phase 121, Phase 122, Phase 128 and Phase 129 gaps documented because CLI was not available in PATH.

View File

@@ -0,0 +1,40 @@
# 2026-05-16
## Co zrobiono
- [Phase 129, Plan 01] Wdrozono mapowanie i synchronizacje statusow Erli w obu kierunkach: Erli -> orderPRO przez inbox oraz orderPRO -> Erli przez `PATCH /orders/{id}/status`.
- Dodano tabele pull/push mapowan statusow Erli, seed statusow, kursor `last_status_pushed_at`, ustawienia `erli_status_sync_*` i cron `erli_status_sync`.
- Dodano repozytoria mapowan, `ErliStatusSyncService`, `ErliStatusSyncHandler`, discovery nieznanych statusow Erli i testy jednostkowe dla mappera/status sync.
- Ujednolicono `/settings/integrations/erli` z innymi integracjami przez zakladki Integracja, Statusy i Ustawienia.
- Udokumentowano gapy srodowiskowe: brak `vendor/bin/phpunit`, globalny XAMPP PHPUnit niekompatybilny z PHP, brak `sonar-scanner` w PATH.
## Zmienione pliki
- `.paul/phases/129-erli-status-mapping-sync/129-01-PLAN.md`
- `.paul/phases/129-erli-status-mapping-sync/129-01-SUMMARY.md`
- `.paul/ROADMAP.md`
- `.paul/STATE.md`
- `.paul/PROJECT.md`
- `.paul/codebase/architecture.md`
- `.paul/codebase/db_schema.md`
- `.paul/codebase/tech_changelog.md`
- `.paul/changelog/2026-05-16.md`
- `database/migrations/20260515_000116_add_erli_status_mapping_sync.sql`
- `src/Modules/Settings/ErliApiClient.php`
- `src/Modules/Settings/ErliIntegrationController.php`
- `src/Modules/Settings/ErliOrderMapper.php`
- `src/Modules/Settings/ErliOrderSyncStateRepository.php`
- `src/Modules/Settings/ErliOrdersSyncService.php`
- `src/Modules/Settings/ErliPullStatusMappingRepository.php`
- `src/Modules/Settings/ErliStatusMappingRepository.php`
- `src/Modules/Settings/ErliStatusSyncService.php`
- `src/Modules/Cron/ErliStatusSyncHandler.php`
- `src/Modules/Cron/CronHandlerFactory.php`
- `routes/web.php`
- `resources/views/settings/erli.php`
- `resources/lang/pl.php`
- `tests/Unit/ErliOrderMapperTest.php`
- `tests/Unit/ErliStatusSyncServiceTest.php`
- `DOCS/DB_SCHEMA.md`
- `DOCS/ARCHITECTURE.md`
- `DOCS/TECH_CHANGELOG.md`

View File

@@ -69,7 +69,7 @@ HTTP Request
1. **Import** — Cron handler → API client → `OrderImportService``OrdersRepository::insertOrder()``AutomationService::executeForNewOrder()`
2. **Re-import (Phase 111 + 112 + 119)**`OrderImportRepository::upsertOrderAggregate` wykrywa tranzycje `payment_status` z 0/1 na 2 i zwraca `payment_transition=true`. `AllegroOrderImportService` i `ShopproOrdersSyncService` na tej fladze emituja `payment.status_changed`, co przez chain reguly automatyzacji #7 zmienia `status_code` na `w_realizacji`. Logika preservacji `status_code` z Phase 62 pozostaje rozdzielona (`statusOverwriteAllowed` = `currentStatus='nieoplacone' && newPaymentStatus===2`). **Phase 112-01 (delta-only re-import):** przy `created=false` repo nie wywoluje `replaceAddresses/replaceItems/replaceNotes/replaceShipments/replaceStatusHistory``order_items.id` i flagi lokalne (np. `project_generated` z Phase 97) pozostaja stabilne. `updateOrderDelta()` aktualizuje wylacznie `status_code` (warunkowo, z propagacja anulowania), `payment_status`, `total_paid`, `is_canceled_by_buyer`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`. Anulowanie ze zrodla (`is_canceled_by_buyer=1` lub zmapowany pull `status_code='anulowane'`) nadpisuje preservacje statusu. Identical-payload guard (`normalizePayloadJson`) pomija UPDATE gdy znormalizowany payload nie rozni sie od DB i brak innych tranzycji. **Phase 119-01 (total_paid protection):** gdy `paymentStatusUnchanged=true` (`oldPaymentStatus === newPaymentStatus`), `updateOrderDelta()` nie dolacza `total_paid` do UPDATE — chroni reczne korekty kwoty (np. zwroty czesciowe). `is_canceled_by_buyer` jest pomijane analogicznie, chyba ze `cancelledBySource=true` (cancel propagation ze zrodla zawsze wymusza wpis flagi). Pozostale pola (`status_code`, `payment_status`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`) zachowuja niezmieniony kontrakt z Phase 112-01.
3. **Status update**`OrdersController::updateStatus()``OrdersRepository::updateStatus()` → automation check
4. **Status sync** — Cron → `AllegroStatusSyncService` / `ShopproStatusSyncService` → carrier API
4. **Status sync** — Cron → `AllegroStatusSyncService` / `ShopproStatusSyncService` / `ErliStatusSyncService` → marketplace API
### Statistics Summary
1. **Request**`/statistics/summary``OrdersStatisticsController::summary()`
@@ -97,6 +97,7 @@ HTTP Request
|---------|------|
| `AllegroOrdersImportHandler` | Fetch new Allegro orders |
| `AllegroStatusSyncHandler` | Push status changes to Allegro |
| `ErliStatusSyncHandler` | Pull Erli status events via inbox or push manual local status changes to Erli |
| `AllegroTokenRefreshHandler` | OAuth token refresh (24h expiry) |
| `ShopproOrdersImportHandler` | Fetch new shopPRO orders |
| `ShopproStatusSyncHandler` | Push status to shopPRO |

View File

@@ -370,6 +370,7 @@ UNIQUE: `(order_id, source_payment_id)`
UNIQUE: `(integration_id, shoppro_status_code)`
**integration_order_sync_state** — Track order fetch progress per integration
- Phase 129 adds `last_status_pushed_at` for Erli manual status push cursor.
**integration_order_status_sync_state** — Track status sync progress per integration and direction
@@ -517,6 +518,14 @@ UNIQUE: `(type, name)`
| `created_at` | DATETIME | |
| `updated_at` | DATETIME | |
**erli_order_status_mappings** — orderPRO status → Erli status used for status push
- Seeded official Erli statuses: `created`, `canceled`, `readyToProcess`, `inProgress`, `sent`, `readyToPickup`, `received`, `returned`, `returningToSender`, `unknown`.
- `orderpro_status_code` is nullable; unmapped status rows are skipped by push sync.
**erli_order_status_pull_mappings** — Erli status → orderPRO status used during inbox import
- Seeded defaults: `pending -> nieoplacone`, `purchased -> nowe`, `cancelled -> anulowane`.
- Unknown Erli statuses are discovered from `/inbox` and inserted with nullable mapping.
**allegro_delivery_method_mappings** — Map order delivery method strings to Allegro services
| Column | Type | Notes |
|--------|------|-------|

View File

@@ -1,5 +1,16 @@
# Technical Changelog
## 2026-05-16 - Phase 129 Plan 01: Erli Status Mapping + Sync
**Co zrobiono:**
- Dodano mapowania statusow Erli pull/push, `ErliStatusSyncService`, `ErliStatusSyncHandler`, endpoint `PATCH /orders/{id}/status` w kliencie API oraz UI mapowan w `/settings/integrations/erli`.
- Import Erli odkrywa surowe statusy z inboxa i uzywa `erli_order_status_pull_mappings`; push obejmuje tylko reczne zmiany statusu z `order_status_history.change_source='manual'`.
**Dlaczego:**
- Phase 128 miala domyslne statusy; Phase 129 daje operatorowi kontrolowane mapowanie i bezpieczny push bez petli automatyzacji.
---
## 2026-05-13 - Phase 126 Plan 01: Invoice GUS Field Mapping Fix (KRS heuristic)
**Co zrobiono:**

View File

@@ -0,0 +1,304 @@
---
phase: 129-erli-status-mapping-sync
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- database/migrations/20260515_000116_add_erli_status_mapping_sync.sql
- src/Modules/Settings/ErliApiClient.php
- src/Modules/Settings/ErliIntegrationController.php
- src/Modules/Settings/ErliIntegrationRepository.php
- src/Modules/Settings/ErliOrderMapper.php
- src/Modules/Settings/ErliOrdersSyncService.php
- src/Modules/Settings/ErliStatusMappingRepository.php
- src/Modules/Settings/ErliPullStatusMappingRepository.php
- src/Modules/Settings/ErliStatusSyncService.php
- src/Modules/Cron/ErliStatusSyncHandler.php
- src/Modules/Cron/CronHandlerFactory.php
- routes/web.php
- resources/views/settings/erli.php
- resources/lang/pl.php
- tests/Unit/ErliOrderMapperTest.php
- tests/Unit/ErliStatusSyncServiceTest.php
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
delegation: auto
---
<objective>
## Goal
Wdrozyc mapowanie i synchronizacje statusow Erli w obu kierunkach: Erli -> orderPRO przy imporcie `/inbox` oraz orderPRO -> Erli przez cron push na `PATCH /orders/{id}/status`.
## Purpose
Phase 128 importuje zamowienia Erli, ale statusy sa jeszcze mapowane sztywnymi defaultami. Phase 129 ma dac operatorowi kontrolowane mapowania pull/push, bezpieczne odkrywanie nowych statusow z inboxa oraz automatyczny push recznych zmian statusu z orderPRO do Erli.
## Output
Nowe tabele mapowan statusow Erli, UI w ustawieniach Erli, endpointy zapisu mapowan, serwis synchronizacji statusow, handler crona `erli_status_sync`, rozszerzony mapper importu oraz testy i dokumentacja.
</objective>
<context>
<clarifications>
- **Zakres** — Czy Phase 129 ma objac od razu oba kierunki synchronizacji statusow Erli?
-> Odpowiedz: Tak, oba kierunki.
- **Statusy** — Jak traktowac liste statusow Erli w UI mapowan?
-> Odpowiedz: Seed + discovery.
- **Push** — Ktore lokalne zmiany statusu orderPRO maja byc wysylane do Erli?
-> Odpowiedz: Tylko reczne zmiany statusu (`order_status_history.change_source='manual'`).
</clarifications>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@.paul/codebase/architecture.md
@.paul/codebase/db_schema.md
@DOCS/DB_SCHEMA.md
@DOCS/ARCHITECTURE.md
## Prior Work
@.paul/phases/127-erli-integration-foundation/127-01-SUMMARY.md
@.paul/phases/128-erli-orders-import/128-01-SUMMARY.md
## External API Context
- Official Erli docs checked 2026-05-15: `https://erli.pl/svc/shop-api/doc/`
- Relevant Erli contract:
- `/inbox` contains new orders and order status changes.
- Base order status values documented for import: `pending`, `purchased`, `cancelled`.
- Swagger exposes `PATCH /orders/{id}/status` with body `{"status": "created|canceled|readyToProcess|inProgress|sent|readyToPickup|received|returned|returningToSender|unknown"}`.
- Erli docs state that order update for Erli shipments should not send `trackingNumber`; shipment/tracking remains Phase 130-131.
## Source Files
@src/Modules/Settings/ErliApiClient.php
@src/Modules/Settings/ErliIntegrationRepository.php
@src/Modules/Settings/ErliIntegrationController.php
@src/Modules/Settings/ErliOrderMapper.php
@src/Modules/Settings/ErliOrdersSyncService.php
@src/Modules/Settings/AllegroStatusMappingRepository.php
@src/Modules/Settings/AllegroPullStatusMappingRepository.php
@src/Modules/Settings/AllegroStatusMappingController.php
@src/Modules/Settings/AllegroStatusSyncService.php
@src/Modules/Settings/ShopproStatusSyncService.php
@src/Modules/Settings/OrderStatusRepository.php
@src/Modules/Cron/CronHandlerFactory.php
@src/Modules/Cron/AllegroStatusSyncHandler.php
@resources/views/settings/erli.php
@resources/views/settings/allegro.php
@routes/web.php
@resources/lang/pl.php
@tests/Unit/AllegroStatusSyncServiceTest.php
@tests/Unit/ErliOrderMapperTest.php
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| `sonar-scanner` | required | After APPLY, before UNIFY | ○ |
| `/feature-dev` | optional | New marketplace integration feature | ○ |
| `/code-review` | optional | Before UNIFY if broad risks remain | ○ |
| `/frontend-design` | optional | If Erli settings UI needs significant redesign | ○ |
**BLOCKING:** Required skills MUST be attempted before UNIFY. If `sonar-scanner` is unavailable in PATH, document the gap in SUMMARY and STATE as in Phase 128.
## Skill Invocation Checklist
- [ ] `sonar-scanner` run or documented unavailable
</skills>
<acceptance_criteria>
## AC-1: Erli status mapping schema and seeds
```gherkin
Given Phase 129 migration is applied
When the database is inspected
Then Erli has separate pull and push mapping tables, seeded with documented Erli statuses, and `erli_status_sync` exists as a disabled-by-default or settings-controlled cron schedule
```
## AC-2: Erli settings UI exposes status mappings
```gherkin
Given an operator opens `/settings/integrations/erli`
When they review the settings page
Then they can configure Erli -> orderPRO pull mappings, orderPRO -> Erli push mappings, status sync direction, and status sync interval without editing code
```
## AC-3: Import uses configurable pull mappings with discovery
```gherkin
Given an Erli inbox message contains status `pending`, `purchased`, `cancelled`, or a new status
When `ErliOrdersSyncService` imports the message
Then `ErliOrderMapper` uses the configured pull mapping when present, falls back to safe defaults when absent, and stores unknown Erli statuses for later mapping
```
## AC-4: Cron pushes only manual orderPRO status changes to Erli
```gherkin
Given an Erli order has a manual status change in `order_status_history`
When the `erli_status_sync` cron handler runs in orderPRO -> Erli direction
Then the service maps the current orderPRO status to an Erli status and calls `PATCH /orders/{id}/status` only for mapped manual changes after the last pushed cursor
```
## AC-5: Push is safe and observable
```gherkin
Given Erli credentials are missing, mappings are incomplete, or Erli API returns an error
When status sync runs
Then the result reports pushed/skipped/failed counts, records bounded errors, and advances the push cursor only for successfully processed change timestamps
```
## AC-6: Documentation and tests cover status behavior
```gherkin
Given Phase 129 implementation is complete
When verification runs
Then mapper/status-sync unit tests, PHP lint, docs, and PAUL summary describe the new status mapping and sync behavior
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Add Erli status mapping persistence, seeds, API client method</name>
<files>
database/migrations/20260515_000116_add_erli_status_mapping_sync.sql,
src/Modules/Settings/ErliApiClient.php,
src/Modules/Settings/ErliIntegrationRepository.php,
src/Modules/Settings/ErliStatusMappingRepository.php,
src/Modules/Settings/ErliPullStatusMappingRepository.php,
DOCS/DB_SCHEMA.md
</files>
<action>
Create the persistence foundation:
- Add `erli_order_status_mappings` for push mappings (`orderpro_status_code` -> `erli_status_code`, name, timestamps).
- Add `erli_order_status_pull_mappings` for pull mappings (`erli_status_code` -> `orderpro_status_code`, name, timestamps).
- Seed documented pull statuses: `pending -> nieoplacone`, `purchased -> nowe`, `cancelled -> anulowane`.
- Seed documented push status options from official swagger: `created`, `canceled`, `readyToProcess`, `inProgress`, `sent`, `readyToPickup`, `received`, `returned`, `returningToSender`, `unknown`. Keep orderPRO mappings nullable where no safe default exists.
- Seed `app_settings` keys `erli_status_sync_direction=erli_to_orderpro` and `erli_status_sync_interval_minutes=15`.
- Seed `cron_schedules.job_type='erli_status_sync'` idempotently; prefer disabled until operator enables/import settings are confirmed, unless existing local pattern makes enabled safer.
- Add repositories mirroring Allegro style: list, replaceAll, find mapped status, upsertDiscoveredStatus, build reverse map.
- Add `ErliApiClient::updateOrderStatus(array $credentials, string $orderId, string $erliStatus): array` calling `PATCH /orders/{id}/status` with JSON `{status: ...}`.
- Extend repository credentials/settings only as needed for status sync settings; do not introduce per-account Erli complexity.
- Update DB schema docs.
</action>
<verify>
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliApiClient.php`
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliStatusMappingRepository.php`
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliPullStatusMappingRepository.php`
Inspect migration for idempotent DDL/seed and no raw runtime use of `DB_HOST_REMOTE`.
</verify>
<done>AC-1 foundation is implemented and AC-5 has API error result shape available.</done>
</task>
<task type="auto">
<name>Task 2: Add Erli settings UI and routes for pull/push mappings</name>
<files>
src/Modules/Settings/ErliIntegrationController.php,
routes/web.php,
resources/views/settings/erli.php,
resources/lang/pl.php,
DOCS/ARCHITECTURE.md
</files>
<action>
Extend the Erli settings page without creating inline CSS or native alert/confirm:
- Inject `OrderStatusRepository`, `ErliStatusMappingRepository`, and `ErliPullStatusMappingRepository`.
- Pass orderPRO statuses, Erli push statuses, Erli pull mappings, current sync direction and interval to the view.
- Add POST handlers for saving pull mappings and push mappings with CSRF validation.
- Add status sync direction and interval fields to the existing Erli settings form, validating direction against `erli_to_orderpro` / `orderpro_to_erli` and interval 1-1440.
- Add routes under `/settings/integrations/erli/statuses/save-pull` and `/settings/integrations/erli/statuses/save-push` (or one clear equivalent route if controller shape is cleaner).
- Render compact mapping tables similar to Allegro, but keep Erli page simple; do not build a landing/marketing page.
- Add Polish translations for labels, hints, empty states, validation and flash messages.
- Preserve existing test/import flash behavior from Phase 128.
</action>
<verify>
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliIntegrationController.php`
`C:\xampp\php\php.exe -l routes/web.php`
`C:\xampp\php\php.exe -l resources/views/settings/erli.php`
`C:\xampp\php\php.exe -l resources/lang/pl.php`
Manual inspect that forms include `_token` and use escaped output via `$e()`.
</verify>
<done>AC-2 is satisfied and existing Phase 127/128 settings actions remain reachable.</done>
</task>
<task type="auto">
<name>Task 3: Wire pull discovery and push cron sync</name>
<files>
src/Modules/Settings/ErliOrderMapper.php,
src/Modules/Settings/ErliOrdersSyncService.php,
src/Modules/Settings/ErliStatusSyncService.php,
src/Modules/Cron/ErliStatusSyncHandler.php,
src/Modules/Cron/CronHandlerFactory.php,
tests/Unit/ErliOrderMapperTest.php,
tests/Unit/ErliStatusSyncServiceTest.php,
DOCS/ARCHITECTURE.md,
DOCS/TECH_CHANGELOG.md
</files>
<action>
Implement runtime behavior:
- Let `ErliOrderMapper` accept an optional pull mapping dependency or mapping array while preserving the existing no-dependency unit-test usage.
- During import, discover any raw Erli order status from inbox and upsert it into the pull mapping repository before/while mapping.
- Use configured pull mapping when available; otherwise keep Phase 128 defaults (`pending`, `purchased`, `cancelled`) and safe fallback.
- Add `ErliStatusSyncService` similar to Allegro/shopPRO:
- Direction `erli_to_orderpro`: run Erli inbox import with `ignore_orders_fetch_enabled=true` so status-change messages are pulled via the same safe ACK path.
- Direction `orderpro_to_erli`: find only Erli orders with `order_status_history.change_source='manual'` newer than `last_status_pushed_at` for the active Erli integration.
- Use push mapping to call `ErliApiClient::updateOrderStatus()`.
- Count `pushed`, `skipped`, `failed`; keep bounded errors; advance cursor only to latest successfully processed change timestamp.
- Add `ErliStatusSyncHandler` and register `erli_status_sync` in `CronHandlerFactory`.
- Add/update unit tests for pull mapping override, unknown status discovery behavior, skipped unmapped push, successful push, and failed push cursor behavior.
- Update architecture/changelog docs.
</action>
<verify>
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliOrderMapper.php`
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliOrdersSyncService.php`
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliStatusSyncService.php`
`C:\xampp\php\php.exe -l src/Modules/Cron/ErliStatusSyncHandler.php`
`C:\xampp\php\php.exe -l src/Modules/Cron/CronHandlerFactory.php`
`C:\xampp\php\php.exe -l tests/Unit/ErliStatusSyncServiceTest.php`
If available: `vendor/bin/phpunit tests/Unit/ErliOrderMapperTest.php tests/Unit/ErliStatusSyncServiceTest.php`
Required skill: `sonar-scanner` or document unavailable.
</verify>
<done>AC-3, AC-4, AC-5 and AC-6 are satisfied.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Do not change shipment/label generation flows; Erli shipment creation belongs to Phase 130.
- Do not send Erli `trackingNumber` in status sync; official docs say shipment number is handled after shipment generation.
- Do not broaden push to automation/system/import changes; Phase 129 push covers only manual `order_status_history` rows.
- Do not alter Allegro/shopPRO mapping semantics except for reading patterns.
- Do not use `DB_HOST_REMOTE` in runtime code.
- Do not add native `alert()`/`confirm()` or inline CSS in Erli settings.
## SCOPE LIMITS
- No Erli webhook registration in this plan; inbox/cron remains the source.
- No product stock adjustment on Erli cancellation; document if needed for a later products/inventory phase.
- No manual live Erli API call is required during APPLY; live smoke is an operator follow-up unless credentials and DB are ready.
- No redesign of the whole integrations UI; keep changes scoped to Erli settings.
</boundaries>
<verification>
Before declaring plan complete:
- [ ] PHP lint passes for all changed PHP/view/lang/test files.
- [ ] Migration is idempotent and documented in `DOCS/DB_SCHEMA.md`.
- [ ] Erli settings page renders pull/push mapping forms with CSRF and escaped values.
- [ ] Mapper tests cover configured mapping fallback and unknown status handling.
- [ ] Status sync tests cover successful push, skipped unmapped push, failed push, and cursor behavior.
- [ ] `vendor/bin/phpunit tests/Unit/ErliOrderMapperTest.php tests/Unit/ErliStatusSyncServiceTest.php` run if available; otherwise gap documented.
- [ ] `sonar-scanner` attempted per SPECIAL-FLOWS; unavailable CLI documented as gap.
- [ ] `git diff --check` passes.
- [ ] All acceptance criteria met.
</verification>
<success_criteria>
- Erli status pull/push mappings can be configured from UI.
- Erli import uses configured pull mapping and discovers unknown raw statuses.
- Erli status cron can push only manual orderPRO status changes to Erli using `PATCH /orders/{id}/status`.
- Failures are observable and do not incorrectly advance push cursor.
- Docs and tests reflect the new behavior.
</success_criteria>
<output>
After completion, create `.paul/phases/129-erli-status-mapping-sync/129-01-SUMMARY.md`.
</output>

View File

@@ -0,0 +1,198 @@
---
phase: 129-erli-status-mapping-sync
plan: 01
subsystem: settings, integrations, cron, database
tags: [erli, status-mapping, status-sync, cron, marketplace]
requires:
- phase: 127-erli-integration-foundation
provides: global Erli credentials, API client, settings page and hub row
- phase: 128-erli-orders-import
provides: Erli inbox import, order mapper, sync state and safe ACK flow
provides:
- Configurable Erli -> orderPRO pull status mappings with discovery
- Configurable orderPRO -> Erli push status mappings
- Cron-driven Erli status sync in pull or push direction
- Tabbed Erli settings UI consistent with other integrations
affects: [phase-130-erli-shipments-labels, phase-131-erli-tracking-automation, erli-settings]
tech-stack:
added: []
patterns: [separate pull/push marketplace status mappings, manual-only push cursor, tabbed integration settings]
key-files:
created:
- database/migrations/20260515_000116_add_erli_status_mapping_sync.sql
- src/Modules/Settings/ErliStatusMappingRepository.php
- src/Modules/Settings/ErliPullStatusMappingRepository.php
- src/Modules/Settings/ErliStatusSyncService.php
- src/Modules/Cron/ErliStatusSyncHandler.php
- tests/Unit/ErliStatusSyncServiceTest.php
modified:
- src/Modules/Settings/ErliApiClient.php
- src/Modules/Settings/ErliIntegrationController.php
- src/Modules/Settings/ErliOrderMapper.php
- src/Modules/Settings/ErliOrdersSyncService.php
- src/Modules/Settings/ErliOrderSyncStateRepository.php
- src/Modules/Cron/CronHandlerFactory.php
- routes/web.php
- resources/views/settings/erli.php
- resources/lang/pl.php
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
key-decisions:
- "Push to Erli uses only manual orderPRO status changes from order_status_history.change_source='manual'."
- "Pull status changes reuse the existing Erli inbox import and ACK flow instead of a separate status endpoint."
- "Erli settings use the same tabbed UI pattern as Allegro/shopPRO."
patterns-established:
- "Erli has separate pull and push mapping repositories/tables to avoid mixing import and outbound sync semantics."
- "Unknown Erli statuses discovered in inbox are stored for later operator mapping."
duration: 35min
started: 2026-05-16T00:00:00+02:00
completed: 2026-05-16T00:23:35+02:00
---
# Phase 129 Plan 01: Erli Status Mapping + Sync Summary
Erli now supports configurable pull/push status mapping, status discovery from inbox, and cron-based status synchronization with a tabbed settings UI.
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~35min |
| Started | 2026-05-16T00:00:00+02:00 |
| Completed | 2026-05-16T00:23:35+02:00 |
| Tasks | 3 completed |
| Files modified | 25 phase files, excluding unrelated `.vscode/ftp-kr.sync.cache.json` |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Erli status mapping schema and seeds | Pass | Migration adds pull/push mapping tables, `integration_order_sync_state.last_status_pushed_at`, app settings, seed status options, and disabled `erli_status_sync` schedule. |
| AC-2: Erli settings UI exposes status mappings | Pass | `/settings/integrations/erli` exposes tabbed panels for integration, statuses and settings; operators can save pull/push mappings, direction and interval with CSRF-protected forms. |
| AC-3: Import uses configurable pull mappings with discovery | Pass | `ErliOrderMapper` uses configured pull mappings with safe fallbacks; `ErliOrdersSyncService` stores newly seen raw Erli statuses for later mapping. |
| AC-4: Cron pushes only manual orderPRO status changes to Erli | Pass | `ErliStatusSyncService` selects only Erli orders with manual status history newer than `last_status_pushed_at` and calls `PATCH /orders/{id}/status` only when mapped. |
| AC-5: Push is safe and observable | Pass | Sync result reports pushed/skipped/failed and bounded errors; cursor advances only through successfully processed timestamps. |
| AC-6: Documentation and tests cover status behavior | Pass with env gaps | PHP lint and diff checks passed; PHPUnit and Sonar are documented gaps because local CLIs are unavailable/broken. Docs were updated. |
## Accomplishments
- Added Erli status persistence for both directions with seed statuses from the documented Erli status contract.
- Extended Erli API client with `PATCH /orders/{id}/status`.
- Added repositories and controller endpoints for saving pull and push mappings.
- Reused the existing inbox import path for Erli -> orderPRO status pulls.
- Added `ErliStatusSyncService` and cron handler for configurable pull/push sync.
- Updated Erli settings UI to match other integrations with tabs.
- Added mapper and status sync unit test coverage files.
## Task Commits
No per-task commits were created during APPLY. Phase transition creates the scoped phase commit.
| Task | Commit | Type | Description |
|------|--------|------|-------------|
| Task 1: Persistence/API foundation | pending phase commit | feat | Mapping tables, seeds, repositories and API status update method. |
| Task 2: Settings UI/routes | pending phase commit | feat | Erli settings status controls, mapping forms, routes and translations. |
| Task 3: Runtime sync/tests/docs | pending phase commit | feat/test/docs | Discovery, pull/push sync service, cron handler, tests and docs. |
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `database/migrations/20260515_000116_add_erli_status_mapping_sync.sql` | Created | Status mapping tables, sync cursor, seeds and cron/app settings. |
| `src/Modules/Settings/ErliStatusMappingRepository.php` | Created | Push mapping persistence for orderPRO -> Erli. |
| `src/Modules/Settings/ErliPullStatusMappingRepository.php` | Created | Pull mapping persistence and discovery for Erli -> orderPRO. |
| `src/Modules/Settings/ErliStatusSyncService.php` | Created | Pull/push status sync orchestration and result counters. |
| `src/Modules/Cron/ErliStatusSyncHandler.php` | Created | Cron entrypoint for `erli_status_sync`. |
| `tests/Unit/ErliStatusSyncServiceTest.php` | Created | Unit tests for push success, skipped unmapped changes and failure cursor behavior. |
| `src/Modules/Settings/ErliApiClient.php` | Modified | Added authenticated Erli status update request. |
| `src/Modules/Settings/ErliIntegrationController.php` | Modified | Added tab state, status settings and mapping save handlers. |
| `src/Modules/Settings/ErliOrderMapper.php` | Modified | Added configurable pull mapping with safe fallback. |
| `src/Modules/Settings/ErliOrdersSyncService.php` | Modified | Added raw Erli status discovery during inbox import. |
| `src/Modules/Settings/ErliOrderSyncStateRepository.php` | Modified | Added push cursor support. |
| `src/Modules/Cron/CronHandlerFactory.php` | Modified | Registered `erli_status_sync`. |
| `routes/web.php` | Modified | Wired repositories, status sync service and mapping routes. |
| `resources/views/settings/erli.php` | Modified | Added tabbed UI and pull/push mapping forms. |
| `resources/lang/pl.php` | Modified | Added Erli status mapping/sync/tabs translations. |
| `DOCS/DB_SCHEMA.md` | Modified | Documented new tables/settings/cursor. |
| `DOCS/ARCHITECTURE.md` | Modified | Documented Erli status sync flow and tabbed settings. |
| `DOCS/TECH_CHANGELOG.md` | Modified | Logged Phase 129 and Erli settings tabs fix. |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Push only manual status changes | Prevents loops from imports, automation and system updates. | Operator intent is explicit; automated changes stay local unless manually changed. |
| Pull uses inbox import | Erli status events arrive through inbox and the Phase 128 ACK flow is already safe. | One source of truth for Erli event processing. |
| Separate pull/push mapping tables | Import statuses and outbound Erli status values have different semantics. | Safer UI and easier future extension. |
| Unknown pull statuses are discovered | Erli can introduce or send statuses not present in seed data. | Operator can map new statuses without code changes. |
| Erli settings use tabs | Page grew after Phase 129 and needed parity with Allegro/shopPRO. | Better scanability, same interaction model as other integrations. |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 1 | UI parity fix, no backend contract change. |
| Scope additions | 1 | Tabbed Erli settings page added during final verification. |
| Deferred | 2 | Environment-dependent verification remains operator follow-up. |
**Total impact:** Positive UI consistency improvement; no expansion into shipments/labels/tracking.
### Auto-fixed Issues
**1. Erli settings were missing tabs**
- **Found during:** Post-APPLY manual inspection.
- **Issue:** Erli settings displayed all integration/status/import/test sections in one vertical page, unlike Allegro/shopPRO.
- **Fix:** Added active `tab` handling, `return_to` for forms and standard `content-tabs-nav` / `content-tab-panel` markup.
- **Files:** `resources/views/settings/erli.php`, `src/Modules/Settings/ErliIntegrationController.php`, `resources/lang/pl.php`, docs.
- **Verification:** PHP lint for view/controller/lang and `git diff --check`.
### Deferred Items
- Phase 129 follow-up: run `php bin/migrate.php`, verify `/settings/integrations/erli` mappings, set `orderPRO -> Erli`, manually change an Erli order status and run `erli_status_sync`.
- Phase 129 verification gap: `vendor/bin/phpunit` is absent in this checkout and global XAMPP PHPUnit is incompatible with the current PHP, so PHPUnit tests were not executed.
- Phase 129 skill gap: `sonar-scanner` is not available in PATH, so Sonar scan could not run.
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| `vendor/bin/phpunit` missing | Documented as verification gap; PHP lint and diff checks were run. |
| Global XAMPP `phpunit` crashes on removed PHP `each()` | Documented as verification gap. |
| `sonar-scanner` unavailable | Documented in SUMMARY and STATE skill audit. |
## Verification Results
| Check | Result |
|-------|--------|
| `php -l resources/views/settings/erli.php` | Pass |
| `php -l src/Modules/Settings/ErliIntegrationController.php` | Pass |
| `php -l resources/lang/pl.php` | Pass |
| Phase APPLY PHP lints for changed PHP/view/test files | Pass |
| `git diff --check` | Pass |
| `vendor/bin/phpunit tests/Unit/ErliOrderMapperTest.php tests/Unit/ErliStatusSyncServiceTest.php` | Not run: `vendor/bin/phpunit` missing |
| `phpunit --version` | Failed: old XAMPP PHPUnit uses removed `each()` |
| `sonar-scanner --version` | Failed: command not found |
Skill audit: required `sonar-scanner` was attempted and documented unavailable.
## Next Phase Readiness
**Ready:**
- Erli status mapping and sync foundation is in place for later shipment/tracking work.
- Erli settings now has the tab structure needed to host future shipment/label settings.
- Push sync cursor and manual-change filtering provide a safe outbound pattern for future Erli API writes.
**Concerns:**
- Live Erli behavior still needs migration and production credential smoke testing.
- PHPUnit dev dependencies are missing locally, so unit tests need a `composer install` or CI run.
- Sonar scan still depends on installing/configuring `sonar-scanner` in PATH.
**Blockers:**
- None for planning Phase 130. Manual smoke remains required before production confidence.
---
*Phase: 129-erli-status-mapping-sync, Plan: 01*
*Completed: 2026-05-16*