# 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 */ public function getFulfillmentPolicies(): array { ... } /** @return list */ public function getPaymentPolicies(): array { ... } /** @return list */ 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 */ 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 Label => ID */ public function getFulfillmentChoices(): array { ... } /** @return array */ public function getPaymentChoices(): array { ... } /** @return array */ public function getReturnChoices(): array { ... } /** @return array 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` — 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 ```