feat(130): erli shipments and labels

This commit is contained in:
2026-05-16 00:58:14 +02:00
parent 4258751e80
commit 13f570e5af
21 changed files with 1451 additions and 58 deletions

View File

@@ -0,0 +1,255 @@
---
phase: 130-erli-shipments-labels
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Core/Constants/IntegrationSources.php
- src/Modules/Settings/ErliApiClient.php
- src/Modules/Settings/ErliDeliveryMappingController.php
- src/Modules/Settings/ErliIntegrationController.php
- src/Modules/Settings/CarrierDeliveryMethodMappingRepository.php
- src/Modules/Settings/ErliExternalShipmentService.php
- src/Modules/Shipments/ShipmentController.php
- routes/web.php
- resources/views/settings/erli.php
- resources/views/shipments/prepare.php
- resources/lang/pl.php
- tests/Unit/ErliExternalShipmentServiceTest.php
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
delegation: auto
---
<objective>
## Goal
Dodac obsluge przesylek dla zamowien Erli: mapowanie metod dostawy w osobnej zakladce ustawien, uzycie istniejacych providerow etykiet (InPost/Apaczka tam, gdzie sa dostepne), zapis paczek w `shipment_packages` oraz rejestracje numeru nadania w Erli przez natywny endpoint przesylek zewnetrznych.
## Purpose
Sprzedawca ma nadawac zamowienia Erli z orderPRO bez przelaczania sie do panelu marketplace, ale bez wymuszania nadawania "na umowie Erli". Natywne Erli w tym planie oznacza wykorzystanie oficjalnych slownikow/cennikow/shipping API tam, gdzie pasuja do wlasnych umow operatora.
## Output
Nowa zakladka dostaw w `/settings/integrations/erli`, rozszerzony Erli API client, flow tworzenia etykiety dla zmapowanych zamowien Erli oraz synchronizacja paczki zewnetrznej do Erli po uzyskaniu numeru przesylki.
</objective>
<context>
<clarifications>
- **Natywne Erli** - Czy probujemy najpierw API shipping Erli, czy od razu tylko istniejacy provider etykiet?
-> Odpowiedz: Trzeba sprobowac natywnego z erli.
- **Konfiguracja** - Gdzie operator ma mapowac metody dostawy Erli?
-> Odpowiedz: zakladka.
- **Umowa/cenniki** - Czy nadawac przez Erli na ich umowie, czy tylko to, co da sie spiac z wlasnymi providerami/cennikami?
-> Odpowiedz: Nie chce nadawac przez Erli na ich umowie, to co sie da. Tak jak allegro ma swoje cenniki.
</clarifications>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@.paul/codebase/architecture.md
@.paul/codebase/db_schema.md
@AGENTS.md
## Prior Work
@.paul/phases/127-erli-integration-foundation/127-01-SUMMARY.md
@.paul/phases/128-erli-orders-import/128-01-SUMMARY.md
@.paul/phases/129-erli-status-mapping-sync/129-01-SUMMARY.md
## External Contract
@https://erli.pl/svc/shop-api/doc/swagger.json
Notes from official swagger checked during planning:
- `GET /dictionaries/shippingMethods`, `GET /dictionaries/deliveryMethods`, `GET /dictionaries/deliveryVendors`, `GET /delivery/priceLists`, `GET /delivery/priceListsDetails` expose Erli shipping/delivery dictionaries and cenniki.
- `POST /shipping/external` creates external parcels with `orderId`, `vendor`, `status`, `trackingNumber`.
- `POST /shipping/parcels/` creates Erli parcels, but this path is treated as optional/discovery only because the user does not want to ship on Erli's carrier agreement.
- No label-download endpoint was found in the swagger path list; local label generation must continue through existing provider services unless APPLY finds a documented endpoint with compatible contract.
## Source Files
@src/Modules/Settings/ErliApiClient.php
@src/Modules/Settings/ErliIntegrationController.php
@src/Modules/Settings/CarrierDeliveryMethodMappingRepository.php
@src/Modules/Settings/AllegroDeliveryMappingController.php
@src/Modules/Shipments/ShipmentController.php
@src/Modules/Shipments/ShipmentProviderInterface.php
@src/Modules/Shipments/InpostShipmentService.php
@src/Modules/Shipments/ApaczkaShipmentService.php
@resources/views/settings/erli.php
@resources/views/shipments/prepare.php
@routes/web.php
@DOCS/DB_SCHEMA.md
@DOCS/ARCHITECTURE.md
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| `sonar-scanner` | required | Po APPLY, przed UNIFY | ○ |
| /feature-dev | optional | Przed implementacja integracji marketplace/shipping | ○ |
| /frontend-design | optional | Przy dodaniu zakladki UI w ustawieniach Erli | ○ |
| /code-review | optional | Po implementacji, przed UNIFY | ○ |
**BLOCKING:** Required `sonar-scanner` must be attempted before UNIFY. If CLI is still unavailable in PATH, document the gap in SUMMARY and STATE like Phase 128/129.
## Skill Invocation Checklist
- [ ] `sonar-scanner` attempted after APPLY
- [ ] Optional flows considered if implementation risk warrants them
</skills>
<acceptance_criteria>
## AC-1: Erli Shipping Contract Is Used Where Safe
```gherkin
Given Erli API credentials are configured
When orderPRO loads shipment configuration for Erli
Then it can fetch Erli shipping/delivery dictionaries, delivery vendors and price list data from the official Erli API
And it does not assume native Erli label download exists unless a documented endpoint is confirmed during implementation.
```
## AC-2: Erli Delivery Mapping Tab Exists
```gherkin
Given the operator opens /settings/integrations/erli
When they select the delivery/shipping tab
Then they see distinct Erli delivery methods from imported orders plus Erli dictionary context
And they can map each method to an available local label provider/service and Erli vendor with a CSRF-protected save form.
```
## AC-3: Erli Orders Preselect Shipment Provider
```gherkin
Given an Erli order has a saved delivery mapping
When the operator opens the shipment prepare page for that order
Then orderPRO preselects the mapped provider/service and shows a clear unmapped diagnostic if no mapping exists.
```
## AC-4: Labels Are Generated Without Erli Carrier Agreement
```gherkin
Given an Erli order is mapped to an existing local provider such as InPost or Apaczka
When the operator creates a shipment
Then the existing provider creates the local shipment package, stores it in shipment_packages and makes the label available for download/print queue
And the flow does not create Erli-contract parcels unless explicitly supported and selected in future work.
```
## AC-5: External Parcel Is Registered In Erli
```gherkin
Given a local Erli shipment package has a tracking number and a mapped Erli vendor
When the package creation/status check reaches a label-ready or sent state
Then orderPRO calls POST /shipping/external with order id, vendor, status and tracking number
And API failures are logged as non-critical local shipment warnings rather than losing the label.
```
## AC-6: Documentation And Verification Cover The Flow
```gherkin
Given the implementation is complete
When verification runs
Then PHP syntax checks, focused tests or documented PHPUnit gap, diff checks and documentation updates cover the new Erli shipment behavior.
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Extend Erli API and mapping repository for shipping</name>
<files>src/Core/Constants/IntegrationSources.php, src/Modules/Settings/ErliApiClient.php, src/Modules/Settings/CarrierDeliveryMethodMappingRepository.php, DOCS/DB_SCHEMA.md, DOCS/ARCHITECTURE.md</files>
<action>
Add `erli` as an explicit integration source constant/path wherever current generic delivery mappings normalize only `allegro` and `shoppro`.
Extend `CarrierDeliveryMethodMappingRepository` so `listMappings`, `findByOrderMethod`, `saveMappings`, `hasMappingsForSource` and `getDistinctOrderDeliveryMethods` work for `source_system='erli'` and global `source_integration_id=0`.
Extend `ErliApiClient` with small focused methods for shipping dictionaries and external parcels:
- `getShippingMethods()`
- `getDeliveryMethods()`
- `getDeliveryVendors()`
- `getPriceLists()` / `getPriceListsDetails()` if the response is needed for operator context
- `createExternalParcel(array $payload)`
Keep request/response handling consistent with existing Erli client methods and keep runtime on `DB_HOST`, never `DB_HOST_REMOTE`.
Do not add a new table unless APPLY proves existing `carrier_delivery_method_mappings` cannot represent the mapping. If a schema change becomes unavoidable, add a migration and update DB docs in the same task.
</action>
<verify>`C:\xampp\php\php.exe -l src/Modules/Settings/ErliApiClient.php` and `C:\xampp\php\php.exe -l src/Modules/Settings/CarrierDeliveryMethodMappingRepository.php`; repository can list/save/find Erli mappings without falling back to Allegro.</verify>
<done>AC-1 foundation complete and AC-2 persistence ready.</done>
</task>
<task type="auto">
<name>Task 2: Add Erli delivery mapping tab</name>
<files>src/Modules/Settings/ErliDeliveryMappingController.php, src/Modules/Settings/ErliIntegrationController.php, routes/web.php, resources/views/settings/erli.php, resources/lang/pl.php, DOCS/ARCHITECTURE.md</files>
<action>
Create or wire a focused Erli delivery mapping controller, following the clarity of `AllegroDeliveryMappingController` but without Allegro OAuth assumptions.
Load:
- distinct Erli delivery methods from imported orders,
- current `carrier_delivery_method_mappings` rows for `erli`,
- available local label provider services already supported by the shipment flow,
- Erli vendors/dictionaries for choosing the vendor sent to `/shipping/external`.
Add a new Erli settings tab (for example `delivery`) next to existing integration/status/settings tabs.
Saving must use POST + `_token`, `Flash`, bounded validation and redirect back to `/settings/integrations/erli?tab=delivery`.
Escape all view output with `$e()`, avoid inline CSS in the view, and do not add native `alert()`/`confirm()`.
</action>
<verify>`C:\xampp\php\php.exe -l src/Modules/Settings/ErliDeliveryMappingController.php`, `C:\xampp\php\php.exe -l resources/views/settings/erli.php`, and manual route smoke: opening `/settings/integrations/erli?tab=delivery` renders the tab even when Erli API metadata fetch fails.</verify>
<done>AC-2 satisfied; UI is consistent with Phase 129 tab pattern.</done>
</task>
<task type="auto">
<name>Task 3: Use mappings during shipment creation and sync external parcel to Erli</name>
<files>src/Modules/Shipments/ShipmentController.php, src/Modules/Settings/ErliExternalShipmentService.php, resources/views/shipments/prepare.php, routes/web.php, tests/Unit/ErliExternalShipmentServiceTest.php, DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
Update shipment prepare flow so Erli orders are eligible for `CarrierDeliveryMethodMappingRepository::findByOrderMethod('erli', 0, ...)` just like Allegro/shopPRO.
Ensure the mapped provider/service can preselect existing local provider forms. Preserve existing Allegro/shopPRO behavior.
Add an `ErliExternalShipmentService` that reads the local package/order context and calls `ErliApiClient::createExternalParcel()` with:
- Erli external order id,
- mapped Erli vendor,
- tracking number from `shipment_packages`,
- status matching Erli's accepted external parcel status contract.
Trigger this service after a local provider has produced a tracking number/label-ready package (for example from `checkStatus()` and/or label-ready creation path), and make the call idempotent enough for retries by treating duplicate/already-existing responses as non-fatal where Erli exposes that signal.
Log or flash bounded warnings for Erli sync failure but never discard the local label.
Add focused unit coverage for payload building and failure behavior. If PHPUnit remains unavailable, leave the test file and document the run gap.
</action>
<verify>`C:\xampp\php\php.exe -l src/Modules/Shipments/ShipmentController.php`; `C:\xampp\php\php.exe -l src/Modules/Settings/ErliExternalShipmentService.php`; if dependencies exist, run `vendor/bin/phpunit tests/Unit/ErliExternalShipmentServiceTest.php`; manual smoke on a mapped Erli order reaches local `shipment_packages` and attempts `/shipping/external` only after tracking exists.</verify>
<done>AC-3, AC-4, AC-5 and AC-6 satisfied.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Do not change unrelated `.vscode/ftp-kr.sync.cache.json`.
- Do not regress Allegro, shopPRO, InPost or Apaczka shipment behavior.
- Do not connect runtime application code to `DB_HOST_REMOTE`.
- Do not add inline CSS to `resources/views/...`.
- Do not add native `alert()` / `confirm()` in views.
## SCOPE LIMITS
- This plan does not implement Phase 131 carrier tracking polling, automation expansion or delivery-status cron beyond the immediate external parcel registration.
- This plan does not force creating shipments through Erli's own carrier agreement. `POST /shipping/parcels/` may be inspected during APPLY, but shipping via Erli-contract parcels is out of scope unless it is clearly just external/seller-contract compatible.
- This plan does not build product/stock sync or Erli offer management.
- This plan keeps Erli as one global integration unless a future phase explicitly introduces multi-account support.
- This plan should avoid new schema if existing generic mapping storage is enough.
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `C:\xampp\php\php.exe -l` for every changed PHP/view/test file.
- [ ] `git diff --check`.
- [ ] `vendor/bin/phpunit tests/Unit/ErliExternalShipmentServiceTest.php` if PHPUnit dependencies are available; otherwise document the environment gap.
- [ ] Attempt `sonar-scanner` after APPLY; if unavailable, document the gap in SUMMARY and STATE.
- [ ] Manual smoke: `/settings/integrations/erli?tab=delivery` renders, saves mapping with `_token`, and survives Erli API metadata errors.
- [ ] Manual smoke: a mapped Erli order preselects shipment provider/service on prepare page.
- [ ] Manual smoke: local provider label creation still stores `shipment_packages` and the Erli external parcel sync is attempted only with a tracking number.
- [ ] `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md`, `DOCS/TECH_CHANGELOG.md` updated for changed behavior and any schema decision.
- [ ] All acceptance criteria met.
</verification>
<success_criteria>
- Erli settings has a working delivery/shipping mapping tab.
- Erli delivery mappings are stored and used by shipment prepare.
- Erli orders can generate local labels through existing providers when mapped.
- Erli receives external parcel/tracking data through native shipping API when tracking exists.
- Native Erli-contract parcel creation is either explicitly not used or documented as future/manual decision.
- Tests/lints/docs and required skill audit are completed or gaps documented.
</success_criteria>
<output>
After completion, create `.paul/phases/130-erli-shipments-labels/130-01-SUMMARY.md`.
</output>

View File

@@ -0,0 +1,165 @@
---
phase: 130-erli-shipments-labels
plan: 01
subsystem: settings, integrations, shipments, database
tags: [erli, shipping, labels, delivery-mapping, external-parcels]
requires:
- phase: 127-erli-integration-foundation
provides: global Erli credentials and API client
- phase: 128-erli-orders-import
provides: Erli orders in common order model
- phase: 129-erli-status-mapping-sync
provides: tabbed Erli settings UI and outbound API pattern
provides:
- Erli delivery mapping tab
- Erli shipping dictionary and external parcel API methods
- Local label provider preselection for Erli orders
- External parcel registration in Erli after tracking number exists
affects: [phase-131-erli-tracking-automation, erli-settings, shipment-flow]
tech-stack:
added: []
patterns: [marketplace delivery mapping with source vendor, non-critical external parcel sync]
key-files:
created:
- database/migrations/20260516_000117_extend_delivery_mappings_for_erli_shipping.sql
- src/Modules/Settings/ErliDeliveryMappingController.php
- src/Modules/Settings/ErliExternalShipmentService.php
- tests/Unit/ErliExternalShipmentServiceTest.php
modified:
- src/Modules/Settings/ErliApiClient.php
- src/Modules/Settings/CarrierDeliveryMethodMappingRepository.php
- src/Modules/Settings/ErliIntegrationController.php
- src/Modules/Shipments/ShipmentController.php
- routes/web.php
- resources/views/settings/erli.php
- resources/views/shipments/prepare.php
- resources/lang/pl.php
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
key-decisions:
- "Erli labels stay on local providers; Erli receives external parcel/tracking through POST /shipping/external."
- "Erli vendor code is stored separately from local provider service in carrier_delivery_method_mappings.source_vendor_code."
patterns-established:
- "External marketplace shipment sync is non-critical and must not block local labels."
duration: ~20min
started: 2026-05-16T00:37:00+02:00
completed: 2026-05-16T00:51:00+02:00
---
# Phase 130-01 Summary: Erli Shipments + Labels
Phase 130 adds Erli delivery mappings, local label provider preselection and external parcel registration through the native Erli shipping API without using Erli carrier-contract label flow.
## Performance
| Metric | Result |
|--------|--------|
| Duration | ~20min |
| Started | 2026-05-16T00:37:00+02:00 |
| Completed | 2026-05-16T00:51:00+02:00 |
| Tasks | 3/3 completed |
| Files changed | 18 phase files, excluding unrelated `.vscode/ftp-kr.sync.cache.json` |
## Acceptance Criteria
| AC | Result | Notes |
|----|--------|-------|
| AC-1: Erli Shipping Contract Is Used Where Safe | Pass | `ErliApiClient` now supports shipping/delivery dictionaries, vendors, price lists and `POST /shipping/external`; no native label download endpoint is assumed. |
| AC-2: Erli Delivery Mapping Tab Exists | Pass | `/settings/integrations/erli?tab=delivery` has a CSRF-protected mapping tab with imported delivery methods, Erli vendor context and local provider service choices. |
| AC-3: Erli Orders Preselect Shipment Provider | Pass | Shipment prepare includes Erli in delivery mapping lookup and preselects mapped local provider/service. |
| AC-4: Labels Are Generated Without Erli Carrier Agreement | Pass | Labels remain provider-driven through local InPost/Apaczka/Allegro WZA flow and keep writing `shipment_packages`. |
| AC-5: External Parcel Is Registered In Erli | Pass with live smoke pending | `ErliExternalShipmentService` registers external parcels only after tracking exists and logs non-critical errors. |
| AC-6: Documentation And Verification Cover The Flow | Pass with env gaps | PHP lint and diff checks passed; PHPUnit and Sonar are unavailable in this environment. |
## Accomplishments
- Extended generic carrier delivery mappings with Erli-specific source service/vendor metadata.
- Added an Erli delivery tab that maps Erli delivery methods to local label providers and Erli vendor codes.
- Added native Erli shipping dictionary and external parcel client methods.
- Wired Erli shipment preparation into the existing local label flow.
- Added non-blocking external parcel sync to Erli after local tracking numbers become available.
- Updated database, architecture and technical changelog documentation.
## Task Results
| Task | Result | Commit |
|------|--------|--------|
| Task 1: Extend Erli API and mapping repository for shipping | Done | Phase commit |
| Task 2: Add Erli delivery mapping tab | Done | Phase commit |
| Task 3: Use mappings during shipment creation and sync external parcel to Erli | Done | Phase commit |
## Files Created
| File | Purpose |
|------|---------|
| `database/migrations/20260516_000117_extend_delivery_mappings_for_erli_shipping.sql` | Adds Erli source service/vendor metadata to delivery mappings. |
| `src/Modules/Settings/ErliDeliveryMappingController.php` | Loads and saves Erli delivery mapping tab data. |
| `src/Modules/Settings/ErliExternalShipmentService.php` | Registers local tracking numbers as Erli external parcels. |
| `tests/Unit/ErliExternalShipmentServiceTest.php` | Focused unit coverage for Erli external parcel sync behavior. |
## Files Modified
| File | Purpose |
|------|---------|
| `src/Modules/Settings/ErliApiClient.php` | Added shipping dictionaries, price lists and external parcel API calls. |
| `src/Modules/Settings/CarrierDeliveryMethodMappingRepository.php` | Added Erli source support and source vendor/service persistence. |
| `src/Modules/Settings/ErliIntegrationController.php` | Added delivery tab data and tab routing. |
| `src/Modules/Shipments/ShipmentController.php` | Uses Erli delivery mappings and triggers external parcel sync after tracking exists. |
| `routes/web.php` | Wires Erli delivery save route and external shipment service dependencies. |
| `resources/views/settings/erli.php` | Adds Erli delivery tab UI. |
| `resources/views/shipments/prepare.php` | Preselects mapped local provider/service for Erli shipments. |
| `resources/lang/pl.php` | Adds Polish labels for Erli delivery mapping UI. |
| `DOCS/DB_SCHEMA.md` | Documents mapping columns. |
| `DOCS/ARCHITECTURE.md` | Documents Erli shipment flow. |
| `DOCS/TECH_CHANGELOG.md` | Records Phase 130 technical changes. |
## Decisions Made
| Decision | Rationale |
|----------|-----------|
| Use local label providers for Erli labels | Operator does not want to ship on Erli's carrier agreement; existing provider labels are the source of truth. |
| Register Erli parcels through `POST /shipping/external` | Official Erli API supports external parcels with order id, vendor, status and tracking number. |
| Store Erli vendor separately from local provider service | Erli vendor and local provider/service are different contracts and should not overload the same field. |
| Make Erli external parcel sync non-critical | Local label generation must survive Erli API failures. |
## Deviations
| Type | Description | Impact |
|------|-------------|--------|
| Scope addition | Added a small migration because the existing mapping schema could not cleanly store Erli vendor separately from the local provider service. | Low risk, improves contract clarity. |
| Implementation detail | Native InPost service data is preferred for Erli/InPost mappings where available, with existing Allegro WZA filtered service list as fallback. | Keeps Erli local-provider flow independent from Allegro where possible. |
| Verification gap | PHPUnit binary is missing and `sonar-scanner` is not available in PATH. | Tests exist but could not be executed locally. |
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| Erli `deliveryVendors` dictionary can return scalar rows | Normalization now preserves scalar values as both id and name. |
| MySQL `CREATE INDEX IF NOT EXISTS` support is environment-sensitive | Migration avoids that syntax and only adds required columns. |
| External parcel duplicate behavior could not be live-tested | Service stores successful sync payload and treats sync failures as non-critical activity entries. |
## Verification
| Check | Result |
|-------|--------|
| PHP syntax lint for changed PHP/view/test files | Passed |
| `git diff --check -- . ':!.vscode/ftp-kr.sync.cache.json'` | Passed |
| `vendor/bin/phpunit tests/Unit/ErliExternalShipmentServiceTest.php` | Not run: `vendor/bin/phpunit` missing |
| `sonar-scanner --version` | Not run: command unavailable in PATH |
| Manual Erli delivery tab smoke | Pending operator after migration/live configuration |
| Manual Erli label + external parcel smoke | Pending operator after migration/live configuration |
## Next Phase Readiness
Phase 131 can build on:
- Erli delivery mappings with local provider and Erli vendor metadata.
- Local shipment packages with tracking numbers.
- Non-critical Erli external parcel sync payloads stored in `shipment_packages.payload_json`.
Remaining concerns for Phase 131:
- Live Erli credentials, migration execution and browser smoke are still pending operator environment.
- Delivery tracking automation should decide how to poll/update delivery status after the external parcel exists.
- Duplicate external parcel semantics should be confirmed against live Erli responses.
No blocker prevents planning Phase 131.