feat(128): erli orders import

Phase 128 complete:
- add Erli /inbox order import with safe mark-read ACK
- add cron/manual import controls and sync state tracking
- map Erli orders into orderPRO aggregates with mapper tests and docs
This commit is contained in:
2026-05-15 23:54:22 +02:00
parent 3ea8cdc941
commit 2565d9b754
23 changed files with 1989 additions and 35 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 127 shipped (Erli settings/API foundation); Phase 128 next |
| Last Updated | 2026-05-15 (Phase 127 closed) |
| 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) |
## Requirements
@@ -127,6 +127,7 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
- [x] Szablony SMS: CRUD w `/settings/sms-templates` (name + body + is_active), wspolny `SmsVariableResolver` wydzielony z Email\\VariableResolver (placeholdery `{{zamowienie.*|kupujacy.*|adres.*|firma.*|przesylka.*}}`), dropdown "Wybierz szablon" w zakladce SMS na `/orders/{id}` wstawia rozwiniete zmienne do textarea (z `OrderProAlerts.confirm` przy nadpisaniu); stopka SMSPLANET dalej doklejana wylacznie przez `SmsConversationService::buildFinalOutboundBody()` (Phase 122 contract preserved) — Phase 124
- [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
### Deferred
@@ -135,11 +136,10 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
### Active (In Progress)
- [ ] v3.8 Erli Marketplace Integration — Phase 127 shipped; Phase 128 next: pobieranie zamowien Erli przez cron/import reczny, mapper do wspolnego modelu orderPRO i state cursor.
- [ ] v3.8 Erli Marketplace Integration — Phase 129 next: mapowanie statusow pull/push Erli i synchronizacja statusow.
### Planned (Next)
- [ ] Erli status mapping + sync — Phase 129
- [ ] Erli shipments + labels — Phase 130
- [ ] Erli tracking + automation hooks — Phase 131
- [ ] Erli hardening, observability + docs — Phase 132
@@ -244,12 +244,15 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API
| `$messageHtml` w alert component musi być `unset()` po każdym include | PHP `include` widzi zmienne kontekstu z extracted scope; bez `unset` kolejny include w tym samym widoku falszywie wykrywa `isset($messageHtml)`. Pattern dla wszystkich miejsc używających `$messageHtml` (4 widoki: invoice_form, receipt-create, printing, statistics/orders) | 2026-05-12 | Active |
| Erli startuje jako jedna globalna konfiguracja bez sandbox switcha | Operator wybral prosty model pojedynczego konta; srodowisko testowe Erli wymaga osobnej domeny z BOK, wiec nie trafia do Phase 127 | 2026-05-15 | Active |
| Test Erli uzywa realnego read-only `GET /inbox` | Operator wymagal realnego testu API, ale fundament nie moze jeszcze importowac zamowien ani oznaczac inboxa jako przeczytanego | 2026-05-15 | Active |
| 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 |
## Success Metrics
| Metric | Target | Current | Status |
|--------|--------|---------|--------|
| Liczba zintegrowanych źródeŠzamówień | ≥3 | 2 aktywne importy + fundament Erli | In progress |
| Liczba zintegrowanych źródeŠzamówień | ≥3 | 3 zrodla importu (Allegro, shopPRO, Erli); Erli wymaga manualnego smoke po migracji | In progress |
| Generowanie etykiet | DziaĹa | InPost | In progress |
## Tech Stack
@@ -275,6 +278,6 @@ Quick Reference:
---
*PROJECT.md — Updated when requirements or context change*
*Last updated: 2026-05-15 after Phase 127 (Erli Integration Foundation) closure; v3.8 milestone in progress*
*Last updated: 2026-05-15 after Phase 128 (Erli Orders Import) closure; v3.8 milestone in progress*

View File

@@ -13,7 +13,7 @@ Pelna integracja z erli.pl wzorowana na istniejacej integracji Allegro: konfigur
| 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 | TBD | Not started |
| 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 |
| 130 | Erli Shipments + Labels | TBD | Not started |
| 131 | Erli Tracking + Automation Hooks | TBD | Not started |
@@ -27,7 +27,7 @@ Plans: 127-01 (complete)
### Phase 128: Erli Orders Import
Focus: Pobieranie nowych zamowien Erli przez cron i import reczny, mapper do wspolnego modelu orderPRO, state cursor, delta-only re-import, adresy/pozycje/platnosci/notatki oraz flaga faktury/NIP tam, gdzie API Erli daje dane firmowe.
Plans: TBD (defined during $paul-plan)
Plans: 128-01 (complete)
### Phase 129: Erli Status Mapping + Sync
@@ -553,4 +553,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
---
*Roadmap created: 2026-03-12*
*Last updated: 2026-05-15 - Phase 127 UNIFY closed*
*Last updated: 2026-05-15 - Phase 128 UNIFY closed*

View File

@@ -5,19 +5,19 @@
See: .paul/PROJECT.md (updated 2026-05-07)
**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 127 complete; Phase 128 ready to plan.
**Current focus:** v3.8 Erli Marketplace Integration - Phase 128 complete; Phase 129 ready to plan.
## Current Position
Milestone: v3.8 Erli Marketplace Integration
Phase: 128 of 132 (Erli Orders Import)
Phase: 129 of 132 (Erli Status Mapping + Sync)
Plan: Not started
Status: Ready to plan
Last activity: 2026-05-15 23:26 - Phase 127 complete; transitioned to Phase 128
Last activity: 2026-05-15 23:52 - Phase 128 complete; transitioned to Phase 129
Progress:
- Milestone v3.8: [##--------] ~16% (Phase 127 complete)
- Phase 128: [----------] 0% (not planned)
- Milestone v3.8: [####------] ~33% (Phases 127-128 complete)
- Phase 129: [----------] 0% (not planned)
## Loop Position
@@ -29,10 +29,10 @@ PLAN -> APPLY -> UNIFY
## Session Continuity
Last session: 2026-05-15 23:26
Stopped at: Phase 127 complete; Phase 128 ready to plan
Next action: $paul-plan for Phase 128 (Erli Orders Import)
Resume file: .paul/ROADMAP.md
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
## Pending parallel work
- None — Phase 118, 121, 122 wszystkie zacommitowane (8f14851, 360eef1).
@@ -66,6 +66,8 @@ Branch: main
- Phase 121 transition note (rozwiązane): commit 360eef1 obejmuje Phase 121 i Phase 122 razem; per-faza hunk-split nie wykonany ze względu na nakładkowe modyfikacje plików.
- Phase 126 follow-up: manual smoke `/orders/1090/invoice/create` (JDG, NIP 5170167517) -> "Imie i nazwisko"="JACEK PYZIAK", "Nazwa firmy"="Project-Pro Pyziak Jacek" niezmieniona; drugi smoke na zamowieniu spolki z aktywnym KRS; `curl /api/nip/lookup?nip=5170167517` -> `data.is_jdg=true`.
- 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.
## Deferred to Next Milestones
@@ -76,4 +78,4 @@ Branch: main
## Skill Requirements
- `sonar-scanner` required after APPLY; Phase 116, Phase 117, Phase 121 and Phase 122 gaps documented because CLI was not available in PATH.
- `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.

View File

@@ -4,6 +4,8 @@
- [Phase 127, Plan 01] Dodano fundament integracji Erli: globalna konfiguracja API, szyfrowany klucz, realny test polaczenia, widok ustawien i wiersz w hubie integracji.
- Utworzono plan i summary dla Phase 127 oraz przygotowano przejscie do Phase 128.
- [Phase 128, Plan 01] Wdrozono import zamowien Erli przez `/inbox`: cron, reczny import, mapper, sync service i bezpieczny ACK `/inbox/mark-read`.
- Dodano test mappera Erli oraz dokumentacje DB/architektury/changelogu dla importu zamowien.
## Zmienione pliki
@@ -22,3 +24,13 @@
- `DOCS/DB_SCHEMA.md`
- `DOCS/ARCHITECTURE.md`
- `DOCS/TECH_CHANGELOG.md`
- `.paul/phases/128-erli-orders-import/128-01-PLAN.md`
- `.paul/phases/128-erli-orders-import/128-01-SUMMARY.md`
- `database/migrations/20260515_000115_add_erli_orders_import_schedule.sql`
- `src/Core/Constants/IntegrationSources.php`
- `src/Modules/Cron/CronHandlerFactory.php`
- `src/Modules/Cron/ErliOrdersImportHandler.php`
- `src/Modules/Settings/ErliOrderMapper.php`
- `src/Modules/Settings/ErliOrderSyncStateRepository.php`
- `src/Modules/Settings/ErliOrdersSyncService.php`
- `tests/Unit/ErliOrderMapperTest.php`

View File

@@ -0,0 +1,311 @@
---
phase: 128-erli-orders-import
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- database/migrations/20260515_000115_add_erli_orders_import_schedule.sql
- src/Core/Constants/IntegrationSources.php
- src/Modules/Settings/ErliApiClient.php
- src/Modules/Settings/ErliIntegrationRepository.php
- src/Modules/Settings/ErliIntegrationController.php
- src/Modules/Settings/ErliOrderMapper.php
- src/Modules/Settings/ErliOrderSyncStateRepository.php
- src/Modules/Settings/ErliOrdersSyncService.php
- src/Modules/Cron/ErliOrdersImportHandler.php
- src/Modules/Cron/CronHandlerFactory.php
- routes/web.php
- resources/views/settings/erli.php
- resources/lang/pl.php
- tests/Unit/ErliOrderMapperTest.php
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
delegation: auto
---
<objective>
## Goal
Wdrozyc realny import zamowien Erli do orderPRO na bazie Erli `/inbox`: cron job, reczny import z ustawien, mapper payloadu zamowienia do wspolnego modelu `OrderImportRepository` oraz bezpieczne potwierdzanie przeczytania inboxa po udanym batchu.
## Purpose
Phase 127 dala konfiguracje i test API. Phase 128 ma sprawic, ze Erli zaczyna dostarczac realne zamowienia do listy orderPRO, z zachowaniem kontraktow delta-only re-import, `invoice_requested` i automatyzacji `order.imported` / `payment.status_changed`.
## Output
Nowe klasy importu Erli, cron schedule `erli_orders_import`, przycisk recznego importu w `/settings/integrations/erli`, testy mappera oraz dokumentacja techniczna.
</objective>
<context>
<clarifications>
- **Zrodlo importu** - Czy Phase 128 ma uzywac Erli `/inbox`, czy klasycznej listy zamowien po `updated`?
-> Odpowiedz: Wg rekomendacji; uzyc `/inbox` jako glownego zrodla.
- **ACK inboxa** - Czy po udanym przetworzeniu oznaczac wiadomosci Erli jako przeczytane?
-> Odpowiedz: Wg rekomendacji; oznaczac po udanym batchu, z notatka jezeli trzeba cos pozniej zrobic.
- **Reczny import** - Czy dodac reczna akcje importu w ustawieniach, czy tylko cron?
-> Odpowiedz: Obie.
</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
## Source Files
@src/Modules/Settings/ErliApiClient.php
@src/Modules/Settings/ErliIntegrationRepository.php
@src/Modules/Settings/ErliIntegrationController.php
@resources/views/settings/erli.php
@src/Modules/Settings/AllegroOrdersSyncService.php
@src/Modules/Settings/AllegroOrderImportService.php
@src/Modules/Settings/ShopproOrdersSyncService.php
@src/Modules/Settings/ShopproOrderMapper.php
@src/Modules/Settings/AllegroOrderSyncStateRepository.php
@src/Modules/Orders/OrderImportRepository.php
@src/Modules/Cron/CronHandlerFactory.php
@src/Modules/Cron/AllegroOrdersImportHandler.php
@src/Modules/Cron/ShopproOrdersImportHandler.php
@routes/web.php
@resources/lang/pl.php
@tests/Unit/OrderImportRepositoryTest.php
@tests/Unit/AllegroOrderImportServiceTest.php
## External API Notes
@https://erli.pl/svc/shop-api/doc/
- Erli API uses REST over HTTPS with `Authorization: Bearer ...`, `Accept: application/json` and a meaningful `User-Agent`.
- Orders and order changes are available through `/svc/shop-api/inbox`.
- One fetch returns up to 500 unread messages.
- Messages should be marked read only after processing, using the id of the newest/last message.
- Status basics: `pending` means unpaid PayU; `purchased` means paid PayU or COD; `cancelled` means cancelled.
- If the exact ACK endpoint/method is not recoverable from the public reference during APPLY, import must stay non-destructive, skip ACK, and SUMMARY must record the follow-up.
</context>
<skills>
## Required Skills / Tools (from SPECIAL-FLOWS.md)
| Skill / Tool | Priority | When to Invoke | Loaded? |
|--------------|----------|----------------|---------|
| `sonar-scanner` | required | After APPLY, before UNIFY | o |
## Optional Flows
- `/feature-dev` optional before implementation of this marketplace feature.
- `/code-review` optional after implementation, before UNIFY.
</skills>
<acceptance_criteria>
## AC-1: Import Configuration And Cron
```gherkin
Given Erli settings have a saved API key
When the operator enables Erli order import and saves the settings
Then `orders_fetch_enabled`, optional `orders_fetch_start_date`, cron interval, and `erli_orders_import` schedule are persisted
And the settings page offers a CSRF-protected "Importuj teraz" action.
```
## AC-2: Inbox Fetch And Safe Acknowledgement
```gherkin
Given Erli returns unread `/inbox` messages containing order events
When the cron or manual import processes the batch without per-order failures
Then every supported order event is imported or re-imported
And Erli inbox is marked read only up to the newest processed message id.
```
## AC-3: No Data Loss On Partial Failure
```gherkin
Given an Erli inbox batch contains at least one order that cannot be mapped or saved
When import finishes with failures
Then the sync state records the failure
And the inbox acknowledgement is not sent for that batch
And the result exposes processed/imported/failed/skipped counters plus sampled errors.
```
## AC-4: Order Aggregate Mapping
```gherkin
Given an Erli order payload contains buyer, delivery, payment, line items, totals and optional invoice/company data
When the mapper builds an order aggregate
Then `orders`, `order_addresses`, `order_items`, `order_payments`, `order_notes`, `order_status_history` receive orderPRO-compatible data
And new orders with invoice/company markers set `orders.invoice_requested=1`.
```
## AC-5: Existing Import Contracts Preserved
```gherkin
Given an Erli order already exists in orderPRO
When the same order is imported again from a changed inbox event
Then `OrderImportRepository::upsertOrderAggregate()` performs delta-only re-import
And local items/addresses/notes are not replaced on re-import
And payment transition can still trigger `payment.status_changed`.
```
## AC-6: Observability And Documentation
```gherkin
Given Phase 128 is complete
When maintainers read the docs or run tests
Then Erli import architecture, schema/schedule changes, verification gaps and manual smoke steps are documented
And mapper/unit checks cover the core Erli payload shapes.
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Add Erli import controls, schedule and entry points</name>
<files>
database/migrations/20260515_000115_add_erli_orders_import_schedule.sql,
src/Modules/Settings/ErliIntegrationRepository.php,
src/Modules/Settings/ErliIntegrationController.php,
resources/views/settings/erli.php,
resources/lang/pl.php,
routes/web.php,
src/Modules/Cron/ErliOrdersImportHandler.php,
src/Modules/Cron/CronHandlerFactory.php
</files>
<action>
Add an idempotent migration seeding `cron_schedules.job_type='erli_orders_import'` with a conservative default interval (5 minutes), disabled until the operator enables import.
Reuse existing `integrations.orders_fetch_enabled` and `integrations.orders_fetch_start_date`; do not add duplicate Erli-only columns for the same settings.
Extend Erli settings save/read to expose:
- import enabled checkbox,
- optional start date,
- order import interval minutes using `CronRepository::upsertSchedule`.
Add a POST `/settings/integrations/erli/import` action protected by CSRF that calls the Erli sync service with `ignore_orders_fetch_enabled=true` and small manual limits.
Wire `ErliOrdersImportHandler` into `CronHandlerFactory` as `erli_orders_import`.
Keep UI compact and reuse existing alert component; do not add inline CSS or native `alert()` / `confirm()`.
</action>
<verify>
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliIntegrationRepository.php`
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliIntegrationController.php`
`C:\xampp\php\php.exe -l src/Modules/Cron/ErliOrdersImportHandler.php`
`C:\xampp\php\php.exe -l src/Modules/Cron/CronHandlerFactory.php`
`C:\xampp\php\php.exe -l routes/web.php`
`C:\xampp\php\php.exe -l resources/views/settings/erli.php`
</verify>
<done>AC-1 satisfied: Erli import can be enabled, scheduled, and manually triggered from settings.</done>
</task>
<task type="auto">
<name>Task 2: Implement Erli inbox client, mapper and sync service</name>
<files>
src/Core/Constants/IntegrationSources.php,
src/Modules/Settings/ErliApiClient.php,
src/Modules/Settings/ErliOrderMapper.php,
src/Modules/Settings/ErliOrderSyncStateRepository.php,
src/Modules/Settings/ErliOrdersSyncService.php
</files>
<action>
Add `IntegrationSources::ERLI = 'erli'`.
Extend `ErliApiClient` with reusable JSON request helpers:
- `fetchInbox(base_url, api_key, timeout)` via `GET /inbox`,
- `ackInboxRead(base_url, api_key, timeout, latest_message_id)` after confirming the exact ACK method/path in Erli reference before coding,
- consistent handling for 401/403, 429, non-JSON bodies and cURL errors.
Build `ErliOrderMapper` that accepts supported inbox event payloads (`orderCreated`, `orderStatusChanged` and equivalent shape variants) and produces the aggregate arrays required by `OrderImportRepository::upsertOrderAggregate()`.
Mapping rules:
- source/integration: `source='erli'`, `external_platform_id='erli'`,
- status defaults: `pending -> nieoplacone`, `purchased -> nowe`, `cancelled -> anulowane`; richer pull/push mapping is deferred to Phase 129,
- payment status: `pending -> 0`, `purchased -> 2`, COD `purchased -> 2`, cancelled -> 0 unless payload clearly says paid/refunded,
- totals, currency and delivery price from payload when present,
- customer, delivery and invoice addresses from payload; company tax number/company name should set `invoice_detected=true`,
- items with source ids, names, quantity, gross price, SKU/EAN/image when present,
- buyer message/comment as order note when present,
- status history row with raw Erli status in payload/comment.
Build `ErliOrdersSyncService` that:
- reads active credentials from `ErliIntegrationRepository`,
- respects `orders_fetch_enabled` unless manual import overrides it,
- filters/skips messages older than `orders_fetch_start_date` where payload dates allow it,
- imports each supported order event through `OrderImportRepository`,
- records import activity with source `Erli`,
- sets `invoice_requested` only for newly created orders when mapper detects invoice/company data,
- triggers `order.imported` for created orders and `payment.status_changed` for re-import payment transitions,
- advances `integration_order_sync_state` on success and stores errors on failure,
- sends ACK only if the full batch had zero import failures and ACK endpoint was confirmed.
If the ACK endpoint cannot be confirmed in APPLY, implement the service with ACK disabled by default, return `acknowledged=false`, and add a clear follow-up in SUMMARY/STATE; do not guess a destructive endpoint.
</action>
<verify>
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliApiClient.php`
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliOrderMapper.php`
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliOrderSyncStateRepository.php`
`C:\xampp\php\php.exe -l src/Modules/Settings/ErliOrdersSyncService.php`
Static check: `rg -n "IntegrationSources::ERLI|erli_orders_import|ackInboxRead|order.imported|payment.status_changed" src routes`
</verify>
<done>AC-2, AC-3, AC-4 and AC-5 satisfied for the runtime import path.</done>
</task>
<task type="auto">
<name>Task 3: Add mapper tests and update technical docs</name>
<files>
tests/Unit/ErliOrderMapperTest.php,
DOCS/DB_SCHEMA.md,
DOCS/ARCHITECTURE.md,
DOCS/TECH_CHANGELOG.md
</files>
<action>
Add PHPUnit tests for `ErliOrderMapper` covering:
- paid/purchased order maps to an importable aggregate with source `erli`,
- pending order maps payment status 0 and status `nieoplacone`,
- cancelled order maps status `anulowane` and cancellation flag,
- invoice/company/tax id data sets `invoice_detected=true`,
- malformed/unsupported inbox messages are skipped or throw controlled mapper exceptions as designed.
Update DB docs with the new cron schedule and any sync-state usage/migration changes.
Update architecture docs with Erli import flow: settings/manual import/cron -> inbox client -> mapper -> `OrderImportRepository` -> automation.
Update technical changelog with Phase 128 scope, status defaults, ACK safety rule, manual verification steps and any deferred ACK follow-up if needed.
</action>
<verify>
`C:\xampp\php\php.exe -l tests/Unit/ErliOrderMapperTest.php`
If dependencies are installed: `vendor\bin\phpunit tests\Unit\ErliOrderMapperTest.php`
`git diff --check`
`sonar-scanner` after APPLY when available in PATH.
</verify>
<done>AC-6 satisfied: tests and documentation describe the new Erli import behavior and remaining live-smoke steps.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Do not change Allegro/shopPRO import behavior except for shared constants or wiring required by Erli.
- Do not weaken Phase 112/119 delta-only re-import protections in `OrderImportRepository`.
- Do not implement Erli status push, pull status mapping UI, label generation, shipment creation or tracking in this plan.
- Do not add a sandbox/environment switch; Phase 127 decision says one production/global config.
- Do not introduce native JS `alert()` / `confirm()` or CSS inside views.
- Do not use `DB_HOST_REMOTE` in runtime code.
## SCOPE LIMITS
- Phase 128 imports orders from Erli; Phase 129 owns configurable status mappings and status sync.
- Phase 130 owns shipments/labels.
- Phase 131 owns tracking/automation hooks beyond existing `order.imported` and `payment.status_changed`.
- Product catalog/stock sync is out of scope even though Erli inbox may include product sync messages.
- Live import verification requires real Erli credentials and local DB migration; if unavailable, record as manual follow-up.
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `C:\xampp\php\php.exe -l` passes for all created/modified PHP files.
- [ ] `vendor\bin\phpunit tests\Unit\ErliOrderMapperTest.php` passes when dependencies are installed.
- [ ] `git diff --check` passes.
- [ ] `sonar-scanner` run or documented as unavailable.
- [ ] Manual smoke documented: run `php bin/migrate.php`, enable Erli import, click "Importuj teraz", confirm Erli orders appear with `source='erli'`.
- [ ] If ACK endpoint was confirmed: successful import returns `acknowledged=true`; failure batch returns `acknowledged=false`.
- [ ] All acceptance criteria met or deferred with explicit reason in SUMMARY.
</verification>
<success_criteria>
- Erli orders can be imported by cron and manually from settings.
- Successful supported inbox events create/update orderPRO orders with addresses, items, payments, notes and status history.
- Re-import keeps existing delta-only protections.
- Inbox read acknowledgement is safe: only after all processed messages in the batch succeed, or explicitly disabled with follow-up if the ACK endpoint cannot be confirmed.
- Operator-visible result counters exist for manual import and cron payload result.
- Documentation and tests are updated.
</success_criteria>
<output>
After completion, create `.paul/phases/128-erli-orders-import/128-01-SUMMARY.md`.
</output>

View File

@@ -0,0 +1,177 @@
---
phase: 128-erli-orders-import
plan: 01
subsystem: settings, integrations, cron, api, database, testing
tags: [erli, marketplace, orders-import, inbox, cron, mapper, automation]
requires:
- phase: 127-erli-integration-foundation
provides: global Erli credentials, settings UI, API client base
- phase: 112-reimport-data-protection
provides: delta-only OrderImportRepository contract
- phase: 119-reimport-total-paid-protection
provides: total_paid protection on stable payment status
provides:
- Erli orders import via /inbox
- manual Erli import action
- erli_orders_import cron handler and schedule
- Erli order mapper to orderPRO aggregate
- safe inbox ACK after zero-failure batch
affects: [erli-status-sync, erli-shipments, erli-tracking, automations, statistics]
tech-stack:
added: []
patterns: [inbox-driven-marketplace-import, safe-ack-after-batch, source-specific-order-mapper]
key-files:
created:
- database/migrations/20260515_000115_add_erli_orders_import_schedule.sql
- src/Modules/Cron/ErliOrdersImportHandler.php
- src/Modules/Settings/ErliOrderMapper.php
- src/Modules/Settings/ErliOrderSyncStateRepository.php
- src/Modules/Settings/ErliOrdersSyncService.php
- tests/Unit/ErliOrderMapperTest.php
modified:
- src/Modules/Settings/ErliApiClient.php
- src/Modules/Settings/ErliIntegrationRepository.php
- src/Modules/Settings/ErliIntegrationController.php
- src/Modules/Cron/CronHandlerFactory.php
- resources/views/settings/erli.php
- routes/web.php
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
key-decisions:
- "Erli order import uses /inbox as primary event source."
- "POST /inbox/mark-read ACK runs only after a zero-failure batch."
- "Phase 128 uses fixed status defaults; configurable mappings are deferred to Phase 129."
patterns-established:
- "ErliOrdersSyncService is shared by cron and manual import."
- "ErliOrderMapper returns null for unsupported inbox messages."
duration: ~14min
started: 2026-05-15T23:32:00+02:00
completed: 2026-05-15T23:46:00+02:00
---
# Phase 128 Plan 01: Erli Orders Import Summary
Erli now imports order events from `/inbox` into the shared orderPRO order aggregate, with cron/manual entry points, state tracking, automation hooks, and safe Erli inbox acknowledgement after a clean batch.
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~14min |
| Started | 2026-05-15T23:32:00+02:00 |
| Completed | 2026-05-15T23:46:00+02:00 |
| Tasks | 6 acceptance areas completed |
| Files modified | 24 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Import configuration and schedule | Pass | Added `orders_fetch_enabled`, `orders_fetch_start_date`, interval UI, idempotent cron seed `erli_orders_import`, and manual import route. |
| AC-2: Fetch Erli inbox and ACK safely | Pass | `ErliApiClient::fetchInbox()` reads `/inbox`; `markInboxRead()` posts to `/inbox/mark-read` only after zero failures. Endpoint contract confirmed against official Erli swagger. |
| AC-3: Cursor/state and failure handling | Pass | `ErliOrderSyncStateRepository` records cursor, last run/success/error; failed batches mark failure and skip ACK. |
| AC-4: Map Erli order payload to orderPRO aggregate | Pass | Mapper covers source identifiers, status/payment defaults, customer/delivery/invoice addresses, items, payments, notes, status history, and invoice detection. |
| AC-5: Reuse shared order import and automations | Pass | Sync uses `OrderImportRepository`, preserves delta-only behavior, emits `order.imported` on create and `payment.status_changed` on payment transitions. |
| AC-6: Tests and documentation | Pass with environment gaps | Mapper unit test file added; docs updated. PHPUnit and Sonar CLI were unavailable in this checkout. |
## Accomplishments
- Added Erli order import service using the same orderPRO aggregate path as Allegro/shopPRO.
- Added cron composition and a manual "import now" action in Erli settings.
- Added import-state persistence so batches are observable and ACK is not sent on partial failure.
- Added Erli mapper coverage for common order, payment, invoice and cancellation cases.
- Updated technical docs for DB schema, architecture and changelog.
## Verification Results
| Check | Result |
|-------|--------|
| `php -l` on all changed PHP/view/lang/test files | Pass |
| Runtime mapper smoke via inline PHP | Pass: `MAPPER_SMOKE_OK` |
| `vendor/bin/phpunit tests/Unit/ErliOrderMapperTest.php` | Not run: `vendor/bin/phpunit` missing in checkout |
| `git diff --check` | Pass, with existing CRLF warnings only |
| `sonar-scanner` | Not run: CLI unavailable in PATH |
| Live migration + manual Erli import | Pending operator smoke on local/production DB |
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `database/migrations/20260515_000115_add_erli_orders_import_schedule.sql` | Created | Add sync-state fields and seed `erli_orders_import` cron schedule. |
| `src/Modules/Cron/ErliOrdersImportHandler.php` | Created | Cron handler for Erli import batches. |
| `src/Modules/Settings/ErliOrderMapper.php` | Created | Convert Erli inbox order messages to orderPRO aggregate. |
| `src/Modules/Settings/ErliOrderSyncStateRepository.php` | Created | Persist import cursor, success and error state. |
| `src/Modules/Settings/ErliOrdersSyncService.php` | Created | Coordinate fetch, map, upsert, automation and ACK. |
| `tests/Unit/ErliOrderMapperTest.php` | Created | Unit tests for mapper status/payment/invoice cases. |
| `src/Modules/Settings/ErliApiClient.php` | Modified | Add `/inbox` fetch and `/inbox/mark-read` ACK. |
| `src/Modules/Settings/ErliIntegrationRepository.php` | Modified | Store import settings and expose active integration credentials. |
| `src/Modules/Settings/ErliIntegrationController.php` | Modified | Save import settings and run manual import. |
| `src/Modules/Cron/CronHandlerFactory.php` | Modified | Register `erli_orders_import`. |
| `resources/views/settings/erli.php` | Modified | Add import controls and manual import button. |
| `routes/web.php` | Modified | Wire service construction and manual import route. |
| `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md`, `DOCS/TECH_CHANGELOG.md` | Modified | Document schema, flow and technical change. |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Use Erli `/inbox` as the primary import source | Inbox is event-driven and aligns with Erli's message processing model. | Phase 129+ can use the same event source for status-related updates. |
| ACK only after zero-failure batch | Prevents losing Erli messages when a partial batch fails locally. | Failed messages remain unread for retry. |
| Keep status mapping defaults fixed in Phase 128 | User chose recommendation; full configurable mapping belongs to Phase 129. | Import works now, status tuning remains explicit next scope. |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed/clarified | 1 | ACK endpoint confirmed and implemented during APPLY. |
| Scope additions | 0 | No extra product scope added. |
| Deferred | 3 | Environment/live verification only. |
### Auto-fixed Issues
**1. Erli ACK endpoint contract**
- **Found during:** API client implementation.
- **Issue:** Plan intentionally left ACK endpoint verification open.
- **Fix:** Confirmed official Erli swagger uses `POST /inbox/mark-read` with `lastMessageId` or `ids`; implemented `lastMessageId`.
- **Verification:** API client method and sync path reference checked; live ACK pending real credentials.
### Deferred Items
- Run `php bin/migrate.php` and enable Erli import in `/settings/integrations/erli`.
- Click `Importuj zamowienia teraz` and confirm `orders.source='erli'` plus no unread messages after clean ACK.
- Install/restore PHPUnit tooling and run `tests/Unit/ErliOrderMapperTest.php`; run Sonar when CLI is available.
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| `vendor/bin/phpunit` missing | Documented as verification gap; mapper smoke run with PHP runtime. |
| `sonar-scanner` missing in PATH | Documented as required-skill gap in STATE. |
| Live Erli import not executable without operator DB/API setup | Added explicit follow-up in STATE. |
## Skill Audit
| Expected | Invoked | Notes |
|----------|---------|-------|
| `sonar-scanner` | Gap | CLI unavailable in PATH; gap documented in STATE. |
## Next Phase Readiness
**Ready:**
- Phase 129 can add configurable Erli pull/push status mapping on top of imported Erli order status fields.
- Cron/manual import flow and state cursor are in place.
- Payment transition and first-import automation hooks are aligned with existing orderPRO contracts.
**Concerns:**
- Real inbox payload variance may require mapper additions after live smoke.
- PHPUnit and Sonar need environment repair for full verification.
**Blockers:**
- None for planning Phase 129.
---
*Phase: 128-erli-orders-import, Plan: 01*
*Completed: 2026-05-15*