Some checks are pending
CI / test (push) Waiting to run
- ArticleTypePlatformConfig: fulfillmentPolicyId, paymentPolicyId, returnPolicyId, merchantLocationKey (all nullable) - EbayAccountApiClient: fetches Fulfillment/Payment/Return policies from eBay Account API (/sell/account/v1) - EbayInventoryApiClient: adds getLocations() - EbayPolicyProvider: aggregates choices with 5 min cache; returns empty array on API failure so the form degrades to TextField - EbayAdapter: reads real ArticleTypePlatformConfig (category ID no longer hardcoded), passes listingPolicies + merchantLocationKey into createOffer() when set - EbayArticleTypePlatformConfigCrudController: live policy dropdowns from EbayPolicyProvider; fallback to TextField with help text - DashboardController: eBay subMenu with Kategorie-Konfigurationen - 7 new unit tests for EbayAdapter policy scenarios Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
174 lines
5.8 KiB
PHP
174 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Infrastructure\Channel\Ebay;
|
|
|
|
use App\Application\Channel\ChannelAdapterInterface;
|
|
use App\Domain\Article\Article;
|
|
use App\Domain\Article\ArticleCondition;
|
|
use App\Domain\Article\ArticleTypeEbayMapping;
|
|
use App\Domain\Channel\Repository\ArticleTypePlatformConfigRepositoryInterface;
|
|
use App\Domain\Order\Order;
|
|
|
|
final class EbayAdapter implements ChannelAdapterInterface
|
|
{
|
|
public function __construct(
|
|
private readonly EbayInventoryApiClient $apiClient,
|
|
private readonly ArticleTypePlatformConfigRepositoryInterface $platformConfigRepository,
|
|
private readonly string $marketplaceId,
|
|
) {
|
|
}
|
|
|
|
public function getType(): string
|
|
{
|
|
return 'ebay';
|
|
}
|
|
|
|
public function publishListing(Article $article): string
|
|
{
|
|
$sku = $article->getSku();
|
|
$config = $this->platformConfigRepository->findByArticleTypeAndPlatformType(
|
|
$article->getArticleType(),
|
|
'ebay'
|
|
);
|
|
|
|
if (null === $config) {
|
|
throw new \RuntimeException(\sprintf('No eBay platform config found for ArticleType "%s"', $article->getArticleType()->getName()));
|
|
}
|
|
|
|
$this->apiClient->upsertInventoryItem($sku, [
|
|
'availability' => [
|
|
'shipToLocationAvailability' => [
|
|
'quantity' => $article->getStock(),
|
|
],
|
|
],
|
|
'condition' => $this->mapCondition($article->getCondition()),
|
|
'conditionDescription' => $article->getConditionNotes() ?? '',
|
|
'product' => [
|
|
'title' => $article->getEbayTitle() ?? $article->getSku(),
|
|
'description' => $article->getEbayDescription() ?? '',
|
|
'aspects' => $this->buildAspects($article),
|
|
],
|
|
]);
|
|
|
|
$listingPolicies = array_filter([
|
|
'fulfillmentPolicyId' => $config->getFulfillmentPolicyId(),
|
|
'paymentPolicyId' => $config->getPaymentPolicyId(),
|
|
'returnPolicyId' => $config->getReturnPolicyId(),
|
|
]);
|
|
|
|
$offerBody = [
|
|
'sku' => $sku,
|
|
'marketplaceId' => $this->marketplaceId,
|
|
'format' => 'FIXED_PRICE',
|
|
'availableQuantity' => $article->getStock(),
|
|
'categoryId' => $config->getCategoryId(),
|
|
'pricingSummary' => [
|
|
'price' => [
|
|
'currency' => 'EUR',
|
|
'value' => number_format((float) ($article->getListingPrice() ?? '0'), 2, '.', ''),
|
|
],
|
|
],
|
|
'listingDescription' => $article->getEbayDescription() ?? '',
|
|
];
|
|
|
|
if ([] !== $listingPolicies) {
|
|
$offerBody['listingPolicies'] = $listingPolicies;
|
|
}
|
|
|
|
if (null !== $config->getMerchantLocationKey()) {
|
|
$offerBody['merchantLocationKey'] = $config->getMerchantLocationKey();
|
|
}
|
|
|
|
$offerId = $this->apiClient->createOffer($offerBody);
|
|
|
|
return $this->apiClient->publishOffer($offerId);
|
|
}
|
|
|
|
public function updateStock(Article $article, int $stock): void
|
|
{
|
|
$this->apiClient->bulkUpdateInventoryItems([
|
|
'requests' => [
|
|
[
|
|
'sku' => $article->getSku(),
|
|
'shipToLocationAvailability' => [
|
|
'quantity' => $stock,
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function deactivateListing(Article $article): void
|
|
{
|
|
$listingId = $article->getEbayListingId();
|
|
if (null === $listingId) {
|
|
return;
|
|
}
|
|
|
|
$this->apiClient->withdrawOffer($listingId);
|
|
}
|
|
|
|
public function pushTracking(Order $order): void
|
|
{
|
|
if (null === $order->getTrackingNumber()) {
|
|
throw new \RuntimeException('Order has no tracking number');
|
|
}
|
|
|
|
$this->apiClient->addTrackingToOrder($order->getPlatformOrderId(), [
|
|
'lineItems' => [
|
|
['lineItemId' => $order->getPlatformOrderId(), 'quantity' => 1],
|
|
],
|
|
'shippingCarrierCode' => $order->getCarrier() ?? 'DHL',
|
|
'trackingNumber' => $order->getTrackingNumber(),
|
|
]);
|
|
}
|
|
|
|
private function mapCondition(ArticleCondition $condition): string
|
|
{
|
|
return match ($condition) {
|
|
ArticleCondition::New => 'NEW',
|
|
ArticleCondition::LikeNew => 'LIKE_NEW',
|
|
ArticleCondition::Good => 'GOOD',
|
|
ArticleCondition::Acceptable => 'ACCEPTABLE',
|
|
};
|
|
}
|
|
|
|
/** @return array<string, list<string>> */
|
|
private function buildAspects(Article $article): array
|
|
{
|
|
$aspects = [];
|
|
|
|
$valuesByDefId = [];
|
|
foreach ($article->getAttributeValues() as $value) {
|
|
$valuesByDefId[$value->getAttributeDefinition()->getId()->toRfc4122()] = $value->getValue();
|
|
}
|
|
|
|
foreach ($article->getArticleType()->getEbayMappings() as $mapping) {
|
|
$ebayName = $mapping->getEbayAspectName();
|
|
|
|
if (ArticleTypeEbayMapping::SOURCE_ARTICLE_FIELD === $mapping->getSourceType()) {
|
|
$getter = 'get'.ucfirst((string) $mapping->getArticleFieldKey());
|
|
if (!method_exists($article, $getter)) {
|
|
continue;
|
|
}
|
|
$fieldValue = $article->$getter();
|
|
if (null !== $fieldValue && '' !== (string) $fieldValue) {
|
|
$aspects[$ebayName] = [(string) $fieldValue];
|
|
}
|
|
} else {
|
|
$def = $mapping->getAttributeDefinition();
|
|
if (null === $def) {
|
|
continue;
|
|
}
|
|
$val = $valuesByDefId[$def->getId()->toRfc4122()] ?? null;
|
|
if (null !== $val) {
|
|
$aspects[$ebayName] = [$val];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $aspects;
|
|
}
|
|
}
|