Skip to content

Commit

Permalink
Handle webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
loevgaard committed Jun 19, 2024
1 parent 1298f82 commit d4ba10d
Show file tree
Hide file tree
Showing 28 changed files with 487 additions and 189 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
```

## Important

The plugin presumes that the `Product::$code` and `ProductVariant::$code` are the same the product id and variant id
respectively inside Peak WMS. Also, because Sylius _always_ has a product and a variant, it is presumed that Peak WMS
uses the same logic.

## Development

```shell
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"symfony/validator": "^5.4 || ^6.4",
"symfony/workflow": "^5.4 || ^6.4",
"twig/twig": "^2.16 || ^3.8",
"webmozart/assert": "^1.11"
"webmozart/assert": "^1.11",
"winzou/state-machine": "^0.4"
},
"require-dev": {
"api-platform/core": "^2.7.16",
Expand Down
37 changes: 37 additions & 0 deletions src/Controller/HandleWebhookControllerAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPeakWMSPlugin\Controller;

use Setono\PeakWMS\Parser\WebhookParser;
use Setono\PeakWMS\Parser\WebhookParserInterface;
use Setono\SyliusPeakWMSPlugin\WebhookHandler\WebhookHandlerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

final class HandleWebhookControllerAction
{
public function __construct(
private readonly WebhookParserInterface $webhookParser,
private readonly WebhookHandlerInterface $webhookHandler,
) {
}

public function __invoke(Request $request): JsonResponse
{
try {
$dataClass = WebhookParser::convertNameToDataClass($request->query->getInt('name'));

$data = $this->webhookParser->parse($request->getContent(), $dataClass);

$this->webhookHandler->handle($data);
} catch (\InvalidArgumentException $e) {
throw new BadRequestHttpException($e->getMessage());
}

return new JsonResponse(status: Response::HTTP_NO_CONTENT);
}
}
19 changes: 0 additions & 19 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
namespace Setono\SyliusPeakWMSPlugin\DependencyInjection;

use Setono\SyliusPeakWMSPlugin\Model\RegisteredWebhooks;
use Setono\SyliusPeakWMSPlugin\Model\RemoteEvent;
use Setono\SyliusPeakWMSPlugin\Model\UploadOrderRequest;
use Sylius\Bundle\ResourceBundle\Controller\ResourceController;
use Sylius\Bundle\ResourceBundle\Form\Type\DefaultResourceType;
use Sylius\Component\Resource\Factory\Factory;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
Expand Down Expand Up @@ -59,22 +56,6 @@ private function addResourcesSection(ArrayNodeDefinition $node): void
->end()
->end()
->end()
->arrayNode('remote_event')
->addDefaultsIfNotSet()
->children()
->variableNode('options')->end()
->arrayNode('classes')
->addDefaultsIfNotSet()
->children()
->scalarNode('model')->defaultValue(RemoteEvent::class)->cannotBeEmpty()->end()
->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end()
->scalarNode('repository')->cannotBeEmpty()->end()
->scalarNode('form')->defaultValue(DefaultResourceType::class)->end()
->scalarNode('factory')->defaultValue(Factory::class)->end()
->end()
->end()
->end()
->end()
->arrayNode('upload_order_request')
->addDefaultsIfNotSet()
->children()
Expand Down
6 changes: 6 additions & 0 deletions src/DependencyInjection/SetonoSyliusPeakWMSExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Setono\SyliusPeakWMSPlugin\DependencyInjection;

use Setono\SyliusPeakWMSPlugin\DataMapper\SalesOrderDataMapperInterface;
use Setono\SyliusPeakWMSPlugin\WebhookHandler\WebhookHandlerInterface;
use Setono\SyliusPeakWMSPlugin\Workflow\UploadOrderRequestWorkflow;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Extension\AbstractResourceExtension;
use Sylius\Bundle\ResourceBundle\SyliusResourceBundle;
Expand All @@ -30,6 +31,11 @@ public function load(array $configs, ContainerBuilder $container): void
->addTag('setono_sylius_peak_wms.sales_order_data_mapper')
;

$container
->registerForAutoconfiguration(WebhookHandlerInterface::class)
->addTag('setono_sylius_peak_wms.webhook_handler')
;

$container->setParameter('setono_sylius_peak_wms.api_key', $config['api_key']);

$this->registerResources(
Expand Down
13 changes: 13 additions & 0 deletions src/Exception/UnsupportedWebhookException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPeakWMSPlugin\Exception;

final class UnsupportedWebhookException extends \RuntimeException
{
public static function fromData(object $data): self
{
return new self('Unsupported webhook: ' . $data::class);
}
}
32 changes: 0 additions & 32 deletions src/Factory/RemoteEventFactory.php

This file was deleted.

18 changes: 0 additions & 18 deletions src/Factory/RemoteEventFactoryInterface.php

This file was deleted.

24 changes: 24 additions & 0 deletions src/Message/Command/UpdateInventory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPeakWMSPlugin\Message\Command;

use Sylius\Component\Core\Model\ProductVariantInterface;

/**
* Will update the inventory for a product variant
*/
final class UpdateInventory implements CommandInterface
{
public int $productVariant;

public function __construct(int|ProductVariantInterface $productVariant)
{
if ($productVariant instanceof ProductVariantInterface) {
$productVariant = (int) $productVariant->getId();
}

$this->productVariant = $productVariant;
}
}
61 changes: 61 additions & 0 deletions src/Message/CommandHandler/UpdateInventoryHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPeakWMSPlugin\Message\CommandHandler;

use Setono\PeakWMS\Client\ClientInterface;
use Setono\PeakWMS\DataTransferObject\Product\Product;
use Setono\SyliusPeakWMSPlugin\Message\Command\UpdateInventory;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;

final class UpdateInventoryHandler
{
public function __construct(
private readonly ClientInterface $client,
private readonly ProductVariantRepositoryInterface $productVariantRepository,
) {
}

public function __invoke(UpdateInventory $message): void
{
$productVariant = $this->productVariantRepository->find($message->productVariant);
if (!$productVariant instanceof ProductVariantInterface) {
throw new UnrecoverableMessageHandlingException(sprintf('Product variant with id %d not found', $message->productVariant));
}

$productCode = $productVariant->getProduct()?->getCode();
$variantCode = $productVariant->getCode();

if (null === $productCode || null === $variantCode) {
throw new UnrecoverableMessageHandlingException(sprintf('Product variant with id %d does not have a product code or variant code', $message->productVariant));
}

$collection = $this
->client
->product()
->getByProductId($productCode)
->filter(fn (Product $product) => $product->variantId === $variantCode)
;

if (count($collection) === 0) {
throw new UnrecoverableMessageHandlingException(sprintf('The product with id %s does not have a variant with id/code %s', $productCode, $variantCode));
}

if (count($collection) > 1) {
throw new UnrecoverableMessageHandlingException(sprintf('The product with id %s has multiple products with the same variant id/code', $productCode));
}

$peakProduct = $collection[0];

if (null === $peakProduct->availableToSell) {
throw new UnrecoverableMessageHandlingException(sprintf('The product with id %s and variant id/code %s does not have an availableToSell value', $productCode, $variantCode));
}

$productVariant->setOnHand($peakProduct->availableToSell);

$this->productVariantRepository->add($productVariant);
}
}
63 changes: 0 additions & 63 deletions src/Model/RemoteEvent.php

This file was deleted.

29 changes: 0 additions & 29 deletions src/Model/RemoteEventInterface.php

This file was deleted.

Loading

0 comments on commit d4ba10d

Please sign in to comment.