Files
orderPRO/.paul/phases/129-erli-status-mapping-sync/129-01-PLAN.md
Jacek Pyziak 7972bb9fa4 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>
2026-05-16 00:27:28 +02:00

305 lines
15 KiB
Markdown

---
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>