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