feat(127): erli integration foundation

Phase 127 complete:

- add global Erli settings schema and encrypted API key repository

- add real read-only Erli API connection test and settings UI

- expose Erli in integrations hub and update PAUL/docs state
This commit is contained in:
2026-05-15 23:26:44 +02:00
parent afdbc67887
commit d6b18a6438
18 changed files with 1281 additions and 28 deletions

View File

@@ -0,0 +1,259 @@
---
phase: 127-erli-integration-foundation
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- database/migrations/20260515_000114_create_erli_integration_settings.sql
- src/Modules/Settings/ErliIntegrationRepository.php
- src/Modules/Settings/ErliApiClient.php
- src/Modules/Settings/ErliIntegrationController.php
- src/Modules/Settings/IntegrationsHubController.php
- routes/web.php
- resources/views/settings/erli.php
- resources/lang/pl.php
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
delegation: auto
---
<objective>
## Goal
Add the foundation for a single global Erli marketplace integration: database settings, encrypted API key storage, API client, settings UI, real connection test, hub row, routes, and technical documentation.
## Purpose
Erli is the next sales channel in orderPRO. This plan creates the same kind of safe integration footing that Allegro, Fakturownia, HostedSMS and SMSPLANET already use, without starting order import, label generation, status sync or tracking yet.
## Output
- New `erli_integration_settings` table linked to one `integrations.type='erli'` row.
- New Settings module classes for Erli configuration and API connection testing.
- New `/settings/integrations/erli` screen with save/test actions.
- Erli visible in `/settings/integrations`.
- Documentation updated for schema, architecture and technical changelog.
</objective>
<context>
<clarifications>
- **Instancje** - Czy integracja Erli ma byc jedna globalna konfiguracja czy obslugiwac wiele kont/sklepow Erli?
-> Odpowiedz: Jedna globalna.
- **Sandbox** - Czy w fazie fundamentu dodac przelacznik srodowiska production/sandbox dla Erli?
-> Odpowiedz: Nie.
- **Test API** - Jak ma dzialac przycisk "Test polaczenia" w Phase 127?
-> Odpowiedz: Realnie ma dzialac.
</clarifications>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@.paul/codebase/architecture.md
@.paul/codebase/db_schema.md
## Required Project Docs
@DOCS/DB_SCHEMA.md
@DOCS/ARCHITECTURE.md
@DOCS/TECH_CHANGELOG.md
## Existing Patterns To Reuse
@src/Modules/Settings/FakturowniaIntegrationRepository.php
@src/Modules/Settings/FakturowniaIntegrationController.php
@src/Modules/Settings/FakturowniaApiClient.php
@src/Modules/Settings/SmsplanetIntegrationRepository.php
@src/Modules/Settings/SmsplanetIntegrationController.php
@src/Modules/Settings/IntegrationsHubController.php
@src/Modules/Settings/IntegrationsRepository.php
@resources/views/settings/fakturownia.php
@resources/views/settings/smsplanet.php
@database/migrations/20260512_000108_create_smsplanet_integration_settings.sql
@routes/web.php
@resources/lang/pl.php
## External API Reference
Erli API documentation: https://erli.pl/svc/shop-api/doc/
- REST API over HTTPS.
- Authorization uses Bearer API key in the `Authorization` header.
- Requests should include a meaningful `User-Agent`.
- Rate limiting can return HTTP 429.
- For this plan, use a safe read-only documented endpoint for connection testing, preferably the inbox endpoint (`/inbox`) with a small limit if supported by the docs.
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| /feature-dev | optional | New marketplace integration work | ○ |
| /code-review | optional | After implementation, before UNIFY | ○ |
| /frontend-design | optional | New settings UI screen | ○ |
| sonar-scanner | required | After APPLY, before UNIFY | ○ |
**BLOCKING:** `sonar-scanner` is required after APPLY and before UNIFY when available in PATH.
</skills>
<acceptance_criteria>
## AC-1: Schema And Single Integration Row
```gherkin
Given migrations are run
When the Erli foundation migration executes
Then the database contains exactly one global `erli_integration_settings` row linked to one `integrations.type='erli'` row
And the settings table stores the API key only in encrypted form
And the migration is idempotent when run again
```
## AC-2: Save Erli Configuration
```gherkin
Given an authenticated operator opens `/settings/integrations/erli`
When they enter an API key, optional seller/account label, and active flag, then submit the form with a valid CSRF token
Then the configuration is saved
And returning to the screen shows that the secret is saved without revealing the raw API key
And invalid or missing required values produce a Polish error flash
```
## AC-3: Real API Connection Test
```gherkin
Given a complete and active Erli configuration is saved
When the operator clicks "Test polaczenia"
Then orderPRO sends a real authenticated read-only request to the Erli API
And stores `last_test_status`, `last_test_http_code`, `last_test_message`, and `last_test_at` in `integrations`
And the UI shows a readable success or failure result
```
## AC-4: Integrations Hub Visibility
```gherkin
Given the operator opens `/settings/integrations`
When the hub renders integration rows
Then Erli appears with configured/missing secret status, active status, last test timestamp, and a configure link to `/settings/integrations/erli`
```
## AC-5: Documentation Updated
```gherkin
Given Phase 127 changes are implemented
When documentation is reviewed
Then `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md`, and `DOCS/TECH_CHANGELOG.md` describe the new Erli table, classes, routes, and decision boundaries
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Create Erli settings schema and repository</name>
<files>database/migrations/20260515_000114_create_erli_integration_settings.sql, src/Modules/Settings/ErliIntegrationRepository.php</files>
<action>
Create an idempotent migration for a single global Erli configuration:
- Insert or update base `integrations` row: `type='erli'`, `name='Erli'`, production API `base_url` from official docs, timeout around 15 seconds, active by default.
- Create `erli_integration_settings` with `id TINYINT UNSIGNED PRIMARY KEY` fixed at 1, nullable unique `integration_id` FK to `integrations(id)`, `api_key_encrypted TEXT NULL`, optional `account_label VARCHAR(128) NULL`, timestamps, and no sandbox/environment column.
- Insert row `id=1` linked to the Erli integration via `ON DUPLICATE KEY UPDATE`.
- Avoid `SELECT 1` no-op migrations; use real DDL/DML only, matching the project migration rule.
Create `ErliIntegrationRepository`:
- Mirror single-instance patterns from `FakturowniaIntegrationRepository` and `SmsplanetIntegrationRepository`.
- Use `IntegrationSecretCipher` for API key encryption/decryption.
- Expose `getSettings()`, `saveSettings(array $payload)`, `getCredentials(): ?array`, and private helpers for `ensureBaseIntegration()`, `ensureRow()`, validation and decryption.
- Preserve saved secret when the API key input is left empty on edit.
- Validate required API key on first save and active flag on the base integration.
- Use PDO prepared statements only.
</action>
<verify>`php -l src/Modules/Settings/ErliIntegrationRepository.php` and review migration SQL for idempotent `CREATE TABLE IF NOT EXISTS` + `ON DUPLICATE KEY UPDATE`</verify>
<done>AC-1 and AC-2 schema/repository portions satisfied</done>
</task>
<task type="auto">
<name>Task 2: Add Erli API client, controller, routes and settings view</name>
<files>src/Modules/Settings/ErliApiClient.php, src/Modules/Settings/ErliIntegrationController.php, routes/web.php, resources/views/settings/erli.php, resources/lang/pl.php</files>
<action>
Create `ErliApiClient`:
- Implement `testConnection(array $credentials): array{ok: bool, http_code: int, message: string}`.
- Send a real authenticated GET request to a read-only Erli endpoint from the official docs, preferably `/inbox` with a minimal limit when supported.
- Use `Authorization: Bearer {api_key}`, `Accept: application/json`, and `User-Agent: orderPRO/1.0 (+contact/orderpro)` style header.
- Use cURL with `SslCertificateResolver::resolve()`, timeouts, SSL verification, JSON/error parsing and no `curl_close()`.
- Treat 2xx as success, 401/403 as auth failure, 429 as rate-limit failure with readable Polish message, and other non-2xx as failure with a safe body snippet.
Create `ErliIntegrationController`:
- Actions: `index`, `save`, `test`.
- Use CSRF `_token`, `Flash`, `RedirectPathResolver`, and existing layout conventions.
- `test` must load saved credentials, call `ErliApiClient::testConnection()`, then write result via `IntegrationsRepository::updateTestResult()`.
Wire in `routes/web.php`:
- Instantiate repository/client/controller with existing `$app->db()` and integration secret.
- Register authenticated routes: `GET /settings/integrations/erli`, `POST /settings/integrations/erli/save`, `POST /settings/integrations/erli/test`.
Create `resources/views/settings/erli.php`:
- Compact settings page consistent with Fakturownia/SMSPLANET pages.
- Fields: account label (optional), API key password input with saved/missing hint, active checkbox.
- Test section with button/form and last test panel.
- Use reusable alert component includes; do not add inline CSS, native `alert()` or `confirm()`.
Add translation keys under settings for Erli provider labels, form fields, flash messages and hub provider name.
</action>
<verify>`php -l src/Modules/Settings/ErliApiClient.php`, `php -l src/Modules/Settings/ErliIntegrationController.php`, `php -l resources/views/settings/erli.php`, and `php -l routes/web.php`</verify>
<done>AC-2 and AC-3 satisfied: operator can save Erli config and run a real API test</done>
</task>
<task type="auto">
<name>Task 3: Add Erli to hub and update technical documentation</name>
<files>src/Modules/Settings/IntegrationsHubController.php, DOCS/DB_SCHEMA.md, DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
Update `IntegrationsHubController`:
- Add `ErliIntegrationRepository` constructor dependency.
- Add `buildErliRow()` mirroring Fakturownia/SMSPLANET single-instance hub rows.
- Include Erli in the rows array with configured/missing secret status, active status, last test timestamp, and configure URL `/settings/integrations/erli`.
- Keep constructor wiring in `routes/web.php` consistent with the new dependency from Task 2.
Update documentation:
- `DOCS/DB_SCHEMA.md`: add `erli_integration_settings` under Integrations with columns, FK, single-row constraint, and encrypted secret note.
- `DOCS/ARCHITECTURE.md`: add Erli foundation classes/routes to Settings and hub integration sections; explicitly state import/status/shipments are deferred to phases 128-131.
- `DOCS/TECH_CHANGELOG.md`: add a 2026-05-15 Phase 127 Plan 01 entry explaining what changed and why.
</action>
<verify>`php -l src/Modules/Settings/IntegrationsHubController.php` and text search confirms `Erli` appears in hub translations/docs</verify>
<done>AC-4 and AC-5 satisfied</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Do not modify Allegro, shopPRO, Fakturownia, HostedSMS or SMSPLANET behavior except for constructor wiring needed to add Erli to the hub.
- Do not change order import, `OrderImportRepository`, order list/detail behavior, shipment creation, tracking, status mapping, statistics aggregation, or automation execution in this plan.
- Do not introduce `DB_HOST_REMOTE` into runtime configuration.
- Do not store raw Erli API keys in plaintext, logs, flash messages, docs, or views.
## SCOPE LIMITS
- No order import from Erli in Phase 127; that starts in Phase 128.
- No Erli status mapping or status sync in Phase 127; that starts in Phase 129.
- No label generation or shipment integration in Phase 127; that starts in Phase 130.
- No tracking or automation hooks in Phase 127; that starts in Phase 131.
- No sandbox/environment switch per user clarification.
- No new frontend build requirement unless existing SCSS already requires it for changed shared styles; this plan should use existing compact form/card classes.
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `php -l` passes for all new/modified PHP files.
- [ ] Migration file is idempotent and uses FK to `integrations(id)`.
- [ ] Manual or local smoke: `/settings/integrations/erli` renders for an authenticated user.
- [ ] Manual or local smoke with real credentials: save config, run test, observe result in UI and `integrations.last_test_*`.
- [ ] `/settings/integrations` shows Erli row with configure link and status.
- [ ] `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md`, and `DOCS/TECH_CHANGELOG.md` updated.
- [ ] `sonar-scanner` run after APPLY if available; if not available, document the gap in SUMMARY.
- [ ] All acceptance criteria met.
</verification>
<success_criteria>
- Erli has one global encrypted configuration in DB.
- Operator can save Erli API key without exposing it after save.
- Test connection performs a real authenticated read-only Erli API request and persists the result.
- Erli appears in the integrations hub.
- Documentation reflects the new schema and architecture.
- No unrelated marketplace/order/shipment behavior changed.
</success_criteria>
<output>
After completion, create `.paul/phases/127-erli-integration-foundation/127-01-SUMMARY.md`.
</output>

View File

@@ -0,0 +1,167 @@
---
phase: 127-erli-integration-foundation
plan: 01
subsystem: settings, integrations, api, database
tags: [erli, marketplace, integration-settings, api-client, encrypted-secrets]
requires:
- phase: 113-fakturownia-integration-foundation
provides: integrations hub test-result pattern and encrypted integration settings
- phase: 117-smsplanet-integration-settings
provides: single global settings + real API test pattern
provides:
- single global Erli API configuration
- encrypted Erli Bearer API key storage
- real Erli connection test via GET /inbox
- Erli row in integrations hub
affects: [erli-orders-import, erli-status-sync, erli-shipments, erli-tracking]
tech-stack:
added: []
patterns: [single-global-marketplace-settings, encrypted-bearer-api-key, real-readonly-api-test]
key-files:
created:
- database/migrations/20260515_000114_create_erli_integration_settings.sql
- src/Modules/Settings/ErliIntegrationRepository.php
- src/Modules/Settings/ErliApiClient.php
- src/Modules/Settings/ErliIntegrationController.php
- resources/views/settings/erli.php
modified:
- src/Modules/Settings/IntegrationsHubController.php
- routes/web.php
- resources/lang/pl.php
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
- .paul/ROADMAP.md
- .paul/STATE.md
key-decisions:
- "Erli starts as one global configuration, not multi-account"
- "No sandbox/environment switch in Phase 127"
- "Connection test performs a real read-only Erli API request"
patterns-established:
- "Erli settings mirror Fakturownia/SMSPLANET single-row repository pattern"
- "Erli API test uses Authorization: Bearer plus User-Agent and stores result in integrations.last_test_*"
duration: ~20min
started: 2026-05-15T23:00:00+02:00
completed: 2026-05-15T23:19:00+02:00
---
# Phase 127 Plan 01: Erli Integration Foundation Summary
**Single global Erli marketplace configuration with encrypted API key storage, real read-only API test, settings UI, and integrations hub row.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~20min |
| Started | 2026-05-15T23:00:00+02:00 |
| Completed | 2026-05-15T23:19:00+02:00 |
| Tasks | 3 completed |
| Files created | 6 |
| Files modified | 8 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Schema And Single Integration Row | Pass | Migration creates `erli_integration_settings`, seeds/updates `integrations.type='erli'`, fixed row `id=1`, encrypted key column. |
| AC-2: Save Erli Configuration | Pass | `/settings/integrations/erli/save` validates CSRF, saves label/API key/active flag, preserves secret on empty input, shows saved/missing state. |
| AC-3: Real API Connection Test | Pass | `ErliApiClient::testConnection()` performs authenticated `GET /inbox`; controller stores `integrations.last_test_*`. Live test awaits real credentials. |
| AC-4: Integrations Hub Visibility | Pass | `IntegrationsHubController::buildErliRow()` adds Erli with configured/missing, active, last test and configure link. |
| AC-5: Documentation Updated | Pass | `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md`, `DOCS/TECH_CHANGELOG.md` updated. |
## Accomplishments
- Added global Erli settings table and repository with encrypted Bearer API key handling.
- Added real connection-test client for official Erli API using `GET https://erli.pl/svc/shop-api/inbox`.
- Added authenticated settings routes and compact UI under `/settings/integrations/erli`.
- Added Erli to the integrations hub.
- Documented schema, architecture and technical changelog.
## Task Commits
No per-task commits were created during APPLY. Phase commit is created during UNIFY transition.
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `database/migrations/20260515_000114_create_erli_integration_settings.sql` | Created | Global Erli config table and base integration seed. |
| `src/Modules/Settings/ErliIntegrationRepository.php` | Created | Settings persistence, secret encryption, active credentials. |
| `src/Modules/Settings/ErliApiClient.php` | Created | Real read-only Erli API connection test. |
| `src/Modules/Settings/ErliIntegrationController.php` | Created | Settings page, save action, test action. |
| `resources/views/settings/erli.php` | Created | Erli settings and test UI. |
| `routes/web.php` | Modified | Erli DI wiring and routes. |
| `resources/lang/pl.php` | Modified | Erli translations and hub provider label. |
| `src/Modules/Settings/IntegrationsHubController.php` | Modified | Erli row in integrations hub. |
| `DOCS/DB_SCHEMA.md` | Modified | Added `erli_integration_settings`. |
| `DOCS/ARCHITECTURE.md` | Modified | Added Erli foundation flow/classes. |
| `DOCS/TECH_CHANGELOG.md` | Modified | Added Phase 127 technical entry. |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Single global Erli configuration | User chose one global account; matches small single-instance integration pattern. | Future phases use one Erli integration id unless requirements change. |
| No sandbox switch | User explicitly declined sandbox/environment toggle. | UI/schema stay simpler; live testing uses production API credentials. |
| Real read-only connection test | User required a real API test. | Test uses `GET /inbox`, but does not import or mark messages read. |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Deferred | 2 | Environment/live resources needed |
| Scope additions | 0 | None |
| Auto-fixed | 0 | None |
### Deferred Items
- Run `php bin/migrate.php` when local MySQL/XAMPP is online.
- Save real Erli API key and perform manual `/settings/integrations/erli` connection test.
- `sonar-scanner` was not available in PATH, so scan was not run.
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| Live API verification needs real Erli credentials | Implemented real test path and documented manual follow-up. |
| SonarQube CLI missing from PATH | Documented as verification gap. |
## Verification Results
| Check | Result |
|-------|--------|
| `php -l src/Modules/Settings/ErliIntegrationRepository.php` | Pass |
| `php -l src/Modules/Settings/ErliApiClient.php` | Pass |
| `php -l src/Modules/Settings/ErliIntegrationController.php` | Pass |
| `php -l src/Modules/Settings/IntegrationsHubController.php` | Pass |
| `php -l resources/views/settings/erli.php` | Pass |
| `php -l routes/web.php` | Pass |
| `php -l resources/lang/pl.php` | Pass |
| `git diff --check` | Pass; only CRLF warnings from Git |
## Next Phase Readiness
**Ready:**
- Erli credentials can be stored and decrypted by future import/status/shipment services.
- Hub and last-test result contract is available for operator visibility.
- API client establishes header, timeout, SSL and error-handling pattern for future Erli calls.
**Concerns:**
- Real credentials and migration smoke remain manual follow-up.
- `/inbox` test endpoint checks authorization but does not verify order payload mapping yet.
**Blockers:**
- None for planning Phase 128.
---
*Phase: 127-erli-integration-foundation, Plan: 01*
*Completed: 2026-05-15*