docs: add CLAUDE.md and Plan 7 (eBay admin navigation + business policies)
Plan 7 covers: ArticleTypePlatformConfig policy fields, EbayAccountApiClient, EbayPolicyProvider with live dropdown choices, EbayAdapter reading real config, and per-adapter admin navigation section. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
371213dbbb
commit
31c5116a1b
2 changed files with 375 additions and 0 deletions
107
CLAUDE.md
Normal file
107
CLAUDE.md
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
All PHP commands run inside Docker. The app container is named `app`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Unit tests
|
||||||
|
docker compose exec app php vendor/bin/phpunit tests/Unit/
|
||||||
|
|
||||||
|
# Single test file or method
|
||||||
|
docker compose exec app php vendor/bin/phpunit tests/Unit/Domain/Article/ArticleTest.php
|
||||||
|
docker compose exec app php vendor/bin/phpunit --filter testSomeMethod
|
||||||
|
|
||||||
|
# Integration tests (loads .env.local secrets automatically)
|
||||||
|
bin/test-integration
|
||||||
|
bin/test-integration tests/Integration/Channel/EbayAdapterTest.php
|
||||||
|
|
||||||
|
# PHPStan (level 9 — must be clean before committing)
|
||||||
|
docker compose exec app php vendor/bin/phpstan analyse
|
||||||
|
|
||||||
|
# CS Fixer (dry-run to check, no --dry-run to fix)
|
||||||
|
docker compose exec app php vendor/bin/php-cs-fixer fix --dry-run --diff
|
||||||
|
docker compose exec app php vendor/bin/php-cs-fixer fix
|
||||||
|
|
||||||
|
# Migrations
|
||||||
|
docker compose exec app php bin/console doctrine:migrations:migrate --no-interaction
|
||||||
|
docker compose exec app php bin/console doctrine:migrations:diff # generate from entity changes
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
docker compose exec app php bin/console cache:clear
|
||||||
|
|
||||||
|
# Create first user / API key
|
||||||
|
docker compose exec app php bin/console app:users:create
|
||||||
|
docker compose exec app php bin/console app:api-keys:create
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Hexagonal architecture: **Domain → Application → Infrastructure**. The boundary is enforced by convention and PHPStan.
|
||||||
|
|
||||||
|
- `src/Domain/` — pure PHP, zero framework imports. Entities, enums, value objects, repository *interfaces*. Doctrine attributes on entities are the pragmatic exception.
|
||||||
|
- `src/Application/` — use cases, message handlers, service interfaces (ports). Orchestrates domain via interfaces only.
|
||||||
|
- `src/Infrastructure/` — all framework/external-system code: Doctrine repositories, Symfony controllers, Messenger handlers, channel adapters, AI clients.
|
||||||
|
|
||||||
|
### Dependency Injection
|
||||||
|
|
||||||
|
Every Domain repository interface is aliased to its Doctrine implementation in `config/services.yaml`. All Application interfaces (ports) are aliased there too. When adding a new interface+implementation pair, add the alias manually — autowiring alone won't resolve interfaces.
|
||||||
|
|
||||||
|
Channel adapters are collected via `tagged_iterator app.channel_adapter` into `ChannelAdapterRegistry`. Tag new adapters in `services.yaml`.
|
||||||
|
|
||||||
|
### Routing (Symfony 8 gotcha)
|
||||||
|
|
||||||
|
`routing.controllers` auto-discovers all controllers. API controllers **must** declare the `/api` prefix in their class-level `#[Route]` attribute — a yaml `prefix:` on top of auto-discovery is silently ignored.
|
||||||
|
|
||||||
|
### AI Pipeline
|
||||||
|
|
||||||
|
The AI backend is `MistralClient` (vision: pixtral-12b, text: mistral-large). The interface is named `OllamaClientInterface` for historical reasons — the `services.yaml` alias points it to `MistralClient`. Web search uses `TavilyWebSearch` behind `WebSearchInterface`.
|
||||||
|
|
||||||
|
Pipeline A (photo) chains Messages sequentially — each handler dispatches the next:
|
||||||
|
`PhotoUpload → SpecsResearch → JsonCoding → Validation → DraftArticle → EbayText`
|
||||||
|
|
||||||
|
After vision, `findCompletedByModelNumber()` checks the DB for a cache hit and skips the remaining AI steps if found.
|
||||||
|
|
||||||
|
`PipelineJobFailureListener` catches `WorkerMessageFailedEvent` after all retries are exhausted and sets `AIPipelineJob.status = failed`.
|
||||||
|
|
||||||
|
### Messenger Transports
|
||||||
|
|
||||||
|
Three isolated Redis streams — a failing worker never blocks the others:
|
||||||
|
|
||||||
|
| Transport | Worker service | Retries | Delay |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `ai_pipeline` | `worker-ai` | 3 | 2 s ×2 |
|
||||||
|
| `orders` | `worker-orders` | 5 | 1 s ×2 |
|
||||||
|
| `channel_sync` | `worker-channel` | 5 | 2 s ×2, max 60 s |
|
||||||
|
|
||||||
|
Exhausted messages land in `failed` transport (persistent). Replay with `messenger:failed:retry`.
|
||||||
|
|
||||||
|
### eBay Integration
|
||||||
|
|
||||||
|
`EbayAdapter` implements `ChannelAdapterInterface`. It uses:
|
||||||
|
- `EbayInventoryApiClient` — inventory items, offers, publish/withdraw, stock updates, tracking
|
||||||
|
- `EbayFulfillmentApiClient` — order fetching
|
||||||
|
- `EbayOAuthClient` — Client-Credentials token with `cache.app` caching
|
||||||
|
- `EbayTaxonomyService` — category/aspect lookup, also cached
|
||||||
|
|
||||||
|
`ArticleTypePlatformConfig` holds per-ArticleType eBay settings (category ID, business policy IDs). `ArticleTypeEbayMapping` maps each eBay aspect name to either an `Article` field (`SOURCE_ARTICLE_FIELD`) or an `AttributeDefinition` (`SOURCE_ATTRIBUTE`). `EbayAdapter.buildAspects()` reads from this table.
|
||||||
|
|
||||||
|
### EasyAdmin (Admin Panel)
|
||||||
|
|
||||||
|
`DashboardController` carries `#[AdminDashboard]` and `configureMenuItems()`. All CRUD controllers in `src/Infrastructure/Http/Controller/Admin/` are auto-discovered. Menu items reference controller class names directly via `MenuItem::linkTo()`.
|
||||||
|
|
||||||
|
### PostgreSQL Schemas
|
||||||
|
|
||||||
|
Three schemas: `app` (all entities), `logs` (live log entries), `logs_archive` (rotated). `doctrine.yaml` sets `schema_filter` to exclude `logs_archive.*` and `app.inventory_seq` from migration diffs — never remove that filter.
|
||||||
|
|
||||||
|
### Auth
|
||||||
|
|
||||||
|
Browser login: form + optional TOTP (`scheb/two-factor-bundle`). API access: `X-Api-Key` header — stored as bcrypt hash with prefix for lookup. `PermissionVoter` checks `User.permissions` and `ApiKey.permissions` (both jsonb) uniformly. Permission constants live in `PermissionVoter`.
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
All PHP services (`app`, `worker-*`, `cron`) run as user `1000:1000` with `HOME=/tmp`. Never run commands as root inside the container — it creates root-owned files on the host.
|
||||||
|
|
||||||
|
`docker-compose.override.yml` exists only for local dev (exposes Postgres/Redis ports). Do not use it on the production server.
|
||||||
268
docs/superpowers/plans/2026-05-19-07-ebay-admin-policies.md
Normal file
268
docs/superpowers/plans/2026-05-19-07-ebay-admin-policies.md
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
# SuperSeller3000 — Plan 7: eBay Admin-Navigation & Business Policies
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Jeder Channel-Adapter bekommt einen eigenen Navigationsbereich im Admin-Panel. Für eBay werden alle Pflichtfelder für ein vollständiges Listing abgebildet: Die vier Business Policies (Fulfillment, Payment, Return, Merchant Location) werden direkt aus dem eBay-Account abgerufen und als Dropdown im Admin angeboten — keine manuelle ID-Eingabe.
|
||||||
|
|
||||||
|
**Auslöser:** `publishListing()` scheitert ohne `listingPolicies` + `merchantLocationKey` im Offer-Body. Außerdem ist `getCategoryId()` derzeit hardcoded auf `'177'` — der Adapter liest `ArticleTypePlatformConfig` noch gar nicht.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Was ein vollständiges eBay-Listing braucht
|
||||||
|
|
||||||
|
| Feld | Quelle | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| Titel, Beschreibung | `Article.ebayTitle/ebayDescription` | ✅ |
|
||||||
|
| Kategorie-ID | `ArticleTypePlatformConfig.categoryId` | ⚠️ vorhanden, aber Adapter liest es nicht |
|
||||||
|
| Condition, Fotos | `Article` | ✅ |
|
||||||
|
| Item Specifics (Aspects) | `ArticleTypeEbayMapping` | ✅ |
|
||||||
|
| Preis, Bestand | `Article.listingPrice/stock` | ✅ |
|
||||||
|
| **Fulfillment Policy ID** | eBay Account → `ArticleTypePlatformConfig` | ❌ fehlt |
|
||||||
|
| **Payment Policy ID** | eBay Account → `ArticleTypePlatformConfig` | ❌ fehlt |
|
||||||
|
| **Return Policy ID** | eBay Account → `ArticleTypePlatformConfig` | ❌ fehlt |
|
||||||
|
| **Merchant Location Key** | eBay Account → `ArticleTypePlatformConfig` | ❌ fehlt |
|
||||||
|
|
||||||
|
Business Policies werden einmalig im eBay Verkäuferkonto definiert und danach per ID referenziert. Der Admin ruft die Liste live ab und zeigt sie als Dropdown.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Schritte
|
||||||
|
|
||||||
|
### 1. `ArticleTypePlatformConfig` — 4 neue Felder
|
||||||
|
|
||||||
|
- [ ] In `src/Domain/Channel/ArticleTypePlatformConfig.php` vier nullable String-Felder ergänzen:
|
||||||
|
|
||||||
|
```php
|
||||||
|
#[ORM\Column(type: 'string', length: 100, nullable: true)]
|
||||||
|
private ?string $fulfillmentPolicyId = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 100, nullable: true)]
|
||||||
|
private ?string $paymentPolicyId = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 100, nullable: true)]
|
||||||
|
private ?string $returnPolicyId = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 100, nullable: true)]
|
||||||
|
private ?string $merchantLocationKey = null;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Getter + Setter für alle vier Felder
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. `ArticleTypePlatformConfigRepositoryInterface` — neue Query-Methode
|
||||||
|
|
||||||
|
- [ ] In `src/Domain/Channel/Repository/ArticleTypePlatformConfigRepositoryInterface.php` ergänzen:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function findByArticleTypeAndPlatformType(ArticleType $articleType, string $platformType): ?ArticleTypePlatformConfig;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Implementierung in `DoctrineArticleTypePlatformConfigRepository`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function findByArticleTypeAndPlatformType(ArticleType $articleType, string $platformType): ?ArticleTypePlatformConfig
|
||||||
|
{
|
||||||
|
return $this->em->createQuery('
|
||||||
|
SELECT c FROM App\Domain\Channel\ArticleTypePlatformConfig c
|
||||||
|
JOIN c.platform p
|
||||||
|
WHERE c.articleType = :articleType AND p.type = :platformType
|
||||||
|
')
|
||||||
|
->setParameter('articleType', $articleType)
|
||||||
|
->setParameter('platformType', $platformType)
|
||||||
|
->getOneOrNullResult();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Migration
|
||||||
|
|
||||||
|
- [ ] `docker compose exec app php bin/console doctrine:migrations:diff` ausführen
|
||||||
|
- [ ] Generierte Migration prüfen — erwartet: 4 `ADD COLUMN ... nullable` auf `app.article_type_platform_configs`
|
||||||
|
- [ ] Dateipfad-Konvention: `migrations/Version2026MMDD000001.php`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. `EbayAccountApiClient` (neu)
|
||||||
|
|
||||||
|
Neuer Client für die eBay Account API (`/sell/account/v1`). Gleiche Auth-Struktur wie `EbayInventoryApiClient`.
|
||||||
|
|
||||||
|
- [ ] `src/Infrastructure/Channel/Ebay/EbayAccountApiClient.php` erstellen:
|
||||||
|
|
||||||
|
```php
|
||||||
|
final class EbayAccountApiClient
|
||||||
|
{
|
||||||
|
private const ACCOUNT_BASE = '/sell/account/v1';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly HttpClientInterface $httpClient,
|
||||||
|
private readonly EbayOAuthClient $oauthClient,
|
||||||
|
private readonly string $apiBaseUrl,
|
||||||
|
private readonly string $marketplaceId,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/** @return list<array{fulfillmentPolicyId: string, name: string}> */
|
||||||
|
public function getFulfillmentPolicies(): array { ... }
|
||||||
|
|
||||||
|
/** @return list<array{paymentPolicyId: string, name: string}> */
|
||||||
|
public function getPaymentPolicies(): array { ... }
|
||||||
|
|
||||||
|
/** @return list<array{returnPolicyId: string, name: string}> */
|
||||||
|
public function getReturnPolicies(): array { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Alle drei rufen `GET {BASE}/{resource}?marketplace_id={marketplaceId}` auf.
|
||||||
|
Response-Key: `fulfillmentPolicies` / `paymentPolicies` / `returnPolicies`.
|
||||||
|
|
||||||
|
- [ ] `EbayInventoryApiClient` um `getLocations()` erweitern:
|
||||||
|
|
||||||
|
```php
|
||||||
|
/** @return list<array{merchantLocationKey: string, name: string}> */
|
||||||
|
public function getLocations(): array
|
||||||
|
{
|
||||||
|
// GET /sell/inventory/v1/location
|
||||||
|
$data = $this->request('GET', self::INVENTORY_BASE.'/location', []);
|
||||||
|
return $data['locations'] ?? [];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] `services.yaml`: `EbayAccountApiClient` registrieren (gleiche Argumente wie `EbayInventoryApiClient`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
App\Infrastructure\Channel\Ebay\EbayAccountApiClient:
|
||||||
|
arguments:
|
||||||
|
$apiBaseUrl: '%env(EBAY_API_BASE_URL)%'
|
||||||
|
$marketplaceId: '%env(EBAY_MARKETPLACE_ID)%'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. `EbayPolicyProvider` (neu)
|
||||||
|
|
||||||
|
Service, der Policy-Listen für EasyAdmin-Formulare aufbereitet und cached.
|
||||||
|
|
||||||
|
- [ ] `src/Infrastructure/Channel/Ebay/EbayPolicyProvider.php` erstellen:
|
||||||
|
|
||||||
|
```php
|
||||||
|
final class EbayPolicyProvider
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly EbayAccountApiClient $accountClient,
|
||||||
|
private readonly EbayInventoryApiClient $inventoryClient,
|
||||||
|
private readonly CacheInterface $cache,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/** @return array<string, string> Label => ID */
|
||||||
|
public function getFulfillmentChoices(): array { ... }
|
||||||
|
|
||||||
|
/** @return array<string, string> */
|
||||||
|
public function getPaymentChoices(): array { ... }
|
||||||
|
|
||||||
|
/** @return array<string, string> */
|
||||||
|
public function getReturnChoices(): array { ... }
|
||||||
|
|
||||||
|
/** @return array<string, string> Label => merchantLocationKey */
|
||||||
|
public function getLocationChoices(): array { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Label-Format: `"Policyname (ID)"` → Value: die ID
|
||||||
|
- Cache-Key pro Policy-Typ, TTL 300 s (`cache.app`)
|
||||||
|
- Bei Exception (API nicht erreichbar, fehlende Credentials): leeres Array zurückgeben — der CRUD-Controller behandelt das
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. `EbayAdapter` — Config wirklich lesen, Policies übergeben
|
||||||
|
|
||||||
|
- [ ] `EbayAdapter` bekommt `ArticleTypePlatformConfigRepositoryInterface` injiziert
|
||||||
|
- [ ] `publishListing()` lädt die Config über `findByArticleTypeAndPlatformType($article->getArticleType(), 'ebay')`
|
||||||
|
- [ ] Exception werfen wenn kein Config-Eintrag existiert: `"No eBay platform config for ArticleType {name}"`
|
||||||
|
- [ ] `createOffer()` body erweitern:
|
||||||
|
|
||||||
|
```php
|
||||||
|
'categoryId' => $config->getCategoryId(),
|
||||||
|
'listingPolicies' => array_filter([ // array_filter entfernt null-Werte
|
||||||
|
'fulfillmentPolicyId' => $config->getFulfillmentPolicyId(),
|
||||||
|
'paymentPolicyId' => $config->getPaymentPolicyId(),
|
||||||
|
'returnPolicyId' => $config->getReturnPolicyId(),
|
||||||
|
]),
|
||||||
|
...($config->getMerchantLocationKey() !== null ? [
|
||||||
|
'merchantLocationKey' => $config->getMerchantLocationKey(),
|
||||||
|
] : []),
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Hardcoded `return '177';` in `getCategoryId()` entfernen (Methode fällt weg, Config übernimmt)
|
||||||
|
- [ ] Bestehende Unit-Tests für `EbayAdapter` anpassen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. EasyAdmin CRUD — `EbayArticleTypePlatformConfigCrudController`
|
||||||
|
|
||||||
|
- [ ] `src/Infrastructure/Http/Controller/Admin/EbayArticleTypePlatformConfigCrudController.php` erstellen
|
||||||
|
- [ ] Entity: `ArticleTypePlatformConfig`
|
||||||
|
- [ ] `createIndexQueryBuilder()` überschreiben → filtert auf `platform.type = 'ebay'`
|
||||||
|
|
||||||
|
**`configureFields()`:**
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function configureFields(string $pageName): iterable
|
||||||
|
{
|
||||||
|
yield AssociationField::new('articleType', 'Artikel-Typ');
|
||||||
|
yield TextField::new('categoryId', 'eBay Kategorie-ID');
|
||||||
|
|
||||||
|
$choices = $this->tryGetChoices('fulfillment'); // siehe unten
|
||||||
|
yield $choices !== null
|
||||||
|
? ChoiceField::new('fulfillmentPolicyId', 'Versand-Policy')->setChoices($choices)
|
||||||
|
: TextField::new('fulfillmentPolicyId', 'Versand-Policy (ID)');
|
||||||
|
|
||||||
|
// analog für payment, return, merchantLocationKey
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `tryGetChoices(string $type): ?array<string,string>` — ruft `EbayPolicyProvider` auf, gibt `null` zurück bei leerer Liste oder Exception
|
||||||
|
- Wenn `null`: `TextField` statt `ChoiceField` + einmalige Flash-Warnung `"eBay API nicht erreichbar — ID manuell eingeben"`
|
||||||
|
- [ ] `services.yaml` — `EbayPolicyProvider` in den Controller injizieren
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Navigation — eBay-Bereich in `DashboardController`
|
||||||
|
|
||||||
|
- [ ] In `configureMenuItems()` einen eBay-Submenü-Block einfügen:
|
||||||
|
|
||||||
|
```php
|
||||||
|
yield MenuItem::subMenu('eBay', 'fa fa-store')->setSubItems([
|
||||||
|
MenuItem::linkTo(EbayArticleTypePlatformConfigCrudController::class, 'Kategorie & Policies', 'fa fa-sliders'),
|
||||||
|
MenuItem::linkToRoute('eBay Aspect Import', 'fa fa-download', 'admin_ebay_aspect_import'),
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Übersetzungsschlüssel in `messages.de.yaml` / `messages.en.yaml` ergänzen
|
||||||
|
- [ ] `ArticleTypePlatformConfig` aus dem allgemeinen Bereich entfernen, falls er dort noch auftaucht (war bisher nicht in der Nav)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nicht in diesem Plan
|
||||||
|
|
||||||
|
- Weitere Adapter-Sektionen (Amazon, Kaufland) — Struktur ist vorbereitet, wird angelegt wenn die Adapter existieren
|
||||||
|
- Sandbox-Policies abrufen — die Account-API ist auf dem echten eBay-Account. Für Tests: Policy-IDs aus Sandbox-Account manuell in `.env.local` setzen oder `EbayPolicyProvider` mocken
|
||||||
|
- Kategorie-ID als Typeahead (statt Text-Input) — ist ein separates Thema
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dateien die geändert werden
|
||||||
|
|
||||||
|
```
|
||||||
|
src/Domain/Channel/ArticleTypePlatformConfig.php ← 4 neue Felder
|
||||||
|
src/Domain/Channel/Repository/ArticleTypePlatformConfigRepositoryInterface.php ← neue Methode
|
||||||
|
src/Infrastructure/Persistence/Repository/DoctrineArticleTypePlatformConfigRepository.php ← Implementierung
|
||||||
|
src/Infrastructure/Channel/Ebay/EbayAccountApiClient.php ← NEU
|
||||||
|
src/Infrastructure/Channel/Ebay/EbayInventoryApiClient.php ← getLocations()
|
||||||
|
src/Infrastructure/Channel/Ebay/EbayPolicyProvider.php ← NEU
|
||||||
|
src/Infrastructure/Channel/Ebay/EbayAdapter.php ← Config lesen, Policies übergeben
|
||||||
|
src/Infrastructure/Http/Controller/Admin/EbayArticleTypePlatformConfigCrudController.php ← NEU
|
||||||
|
src/Infrastructure/Http/Controller/Admin/DashboardController.php ← Navigation
|
||||||
|
config/services.yaml ← neue Services
|
||||||
|
migrations/Version2026...php ← NEU
|
||||||
|
```
|
||||||
Loading…
Reference in a new issue