Skip to content

Commit

Permalink
Merge pull request #4 from Setono/update-inventory
Browse files Browse the repository at this point in the history
Create more inventory handling
  • Loading branch information
loevgaard authored Jun 28, 2024
2 parents 1c5aac6 + d5e99b4 commit 03a28bb
Show file tree
Hide file tree
Showing 24 changed files with 759 additions and 46 deletions.
32 changes: 32 additions & 0 deletions src/Command/UpdateInventoryCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPeakPlugin\Command;

use Setono\SyliusPeakPlugin\Updater\InventoryUpdaterInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(
name: 'setono:sylius-peak-wms:update-inventory',
description: 'This will update the inventory for all product variants',
)]
final class UpdateInventoryCommand extends Command
{
public function __construct(private readonly InventoryUpdaterInterface $inventoryUpdater)
{
parent::__construct();
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
// todo allow the user to specify a product variant id to update
// todo allow the user to force the update of _ALL_ product variants regardless of the last update time
$this->inventoryUpdater->updateAll();

return 0;
}
}
15 changes: 15 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Setono\SyliusPeakPlugin\DependencyInjection;

use Setono\SyliusPeakPlugin\Model\InventoryUpdate;
use Setono\SyliusPeakPlugin\Model\RegisteredWebhooks;
use Setono\SyliusPeakPlugin\Model\UploadOrderRequest;
use Sylius\Component\Resource\Factory\Factory;
Expand Down Expand Up @@ -42,6 +43,20 @@ private function addResourcesSection(ArrayNodeDefinition $node): void
->arrayNode('resources')
->addDefaultsIfNotSet()
->children()
->arrayNode('inventory_update')
->addDefaultsIfNotSet()
->children()
->variableNode('options')->end()
->arrayNode('classes')
->addDefaultsIfNotSet()
->children()
->scalarNode('model')->defaultValue(InventoryUpdate::class)->cannotBeEmpty()->end()
->scalarNode('repository')->cannotBeEmpty()->end()
->scalarNode('factory')->defaultValue(Factory::class)->end()
->end()
->end()
->end()
->end()
->arrayNode('registered_webhooks')
->addDefaultsIfNotSet()
->children()
Expand Down
3 changes: 2 additions & 1 deletion src/DependencyInjection/SetonoSyliusPeakExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Setono\SyliusPeakPlugin\DataMapper\SalesOrderDataMapperInterface;
use Setono\SyliusPeakPlugin\WebhookHandler\WebhookHandlerInterface;
use Setono\SyliusPeakPlugin\Workflow\InventoryUpdateWorkflow;
use Setono\SyliusPeakPlugin\Workflow\UploadOrderRequestWorkflow;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Extension\AbstractResourceExtension;
use Sylius\Bundle\ResourceBundle\SyliusResourceBundle;
Expand Down Expand Up @@ -58,7 +59,7 @@ public function prepend(ContainerBuilder $container): void
],
],
],
'workflows' => UploadOrderRequestWorkflow::getConfig(),
'workflows' => UploadOrderRequestWorkflow::getConfig() + InventoryUpdateWorkflow::getConfig(),
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPeakPlugin\EventSubscriber\Workflow\InventoryUpdate;

use Setono\SyliusPeakPlugin\Model\InventoryUpdateInterface;
use Setono\SyliusPeakPlugin\Workflow\InventoryUpdateWorkflow;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\CompletedEvent;
use Webmozart\Assert\Assert;

final class CompleteSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
sprintf('workflow.%s.completed.%s', InventoryUpdateWorkflow::NAME, InventoryUpdateWorkflow::TRANSITION_COMPLETE) => 'set',
];
}

public function set(CompletedEvent $event): void
{
/** @var InventoryUpdateInterface|object $inventoryUpdate */
$inventoryUpdate = $event->getSubject();
Assert::isInstanceOf($inventoryUpdate, InventoryUpdateInterface::class);

$inventoryUpdate->setCompletedAt(new \DateTimeImmutable());

if (!$inventoryUpdate->hasErrors()) {
$inventoryUpdate->setNextUpdateThreshold($inventoryUpdate->getProcessingStartedAt());
}
}
}
30 changes: 30 additions & 0 deletions src/EventSubscriber/Workflow/InventoryUpdate/ProcessSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPeakPlugin\EventSubscriber\Workflow\InventoryUpdate;

use Setono\SyliusPeakPlugin\Model\InventoryUpdateInterface;
use Setono\SyliusPeakPlugin\Workflow\InventoryUpdateWorkflow;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\CompletedEvent;
use Webmozart\Assert\Assert;

final class ProcessSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
sprintf('workflow.%s.completed.%s', InventoryUpdateWorkflow::NAME, InventoryUpdateWorkflow::TRANSITION_PROCESS) => 'set',
];
}

public function set(CompletedEvent $event): void
{
/** @var InventoryUpdateInterface|object $inventoryUpdate */
$inventoryUpdate = $event->getSubject();
Assert::isInstanceOf($inventoryUpdate, InventoryUpdateInterface::class);

$inventoryUpdate->setProcessingStartedAt(new \DateTimeImmutable());
}
}
33 changes: 33 additions & 0 deletions src/EventSubscriber/Workflow/InventoryUpdate/ResetSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPeakPlugin\EventSubscriber\Workflow\InventoryUpdate;

use Setono\SyliusPeakPlugin\Model\InventoryUpdateInterface;
use Setono\SyliusPeakPlugin\Workflow\InventoryUpdateWorkflow;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\CompletedEvent;
use Webmozart\Assert\Assert;

final class ResetSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
sprintf('workflow.%s.completed.%s', InventoryUpdateWorkflow::NAME, InventoryUpdateWorkflow::TRANSITION_RESET) => 'reset',
];
}

public function reset(CompletedEvent $event): void
{
/** @var InventoryUpdateInterface|object $inventoryUpdate */
$inventoryUpdate = $event->getSubject();
Assert::isInstanceOf($inventoryUpdate, InventoryUpdateInterface::class);

$inventoryUpdate->setCompletedAt(null);
$inventoryUpdate->setProcessingStartedAt(null);
$inventoryUpdate->setWarnings(null);
$inventoryUpdate->setErrors(null);
}
}
21 changes: 15 additions & 6 deletions src/Message/Command/UpdateInventory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,28 @@

use Sylius\Component\Core\Model\ProductVariantInterface;

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

public function __construct(int|ProductVariantInterface $productVariant)
private function __construct()
{
}

public static function for(int|ProductVariantInterface $productVariant): self
{
if ($productVariant instanceof ProductVariantInterface) {
$productVariant = (int) $productVariant->getId();
}

$this->productVariant = $productVariant;
$command = new self();
$command->productVariant = $productVariant;

return $command;
}

public static function forAll(): self
{
return new self();
}
}
43 changes: 9 additions & 34 deletions src/Message/CommandHandler/UpdateInventoryHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,33 @@

namespace Setono\SyliusPeakPlugin\Message\CommandHandler;

use Setono\PeakWMS\Client\ClientInterface;
use Setono\PeakWMS\DataTransferObject\Product\Product;
use Setono\SyliusPeakPlugin\Message\Command\UpdateInventory;
use Setono\SyliusPeakPlugin\Updater\InventoryUpdaterInterface;
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,
private readonly InventoryUpdaterInterface $inventoryUpdater,
) {
}

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 (null === $message->productVariant) {
$this->inventoryUpdater->updateAll();

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

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 = $this->productVariantRepository->find($message->productVariant);
if (!$productVariant instanceof ProductVariantInterface) {
throw new UnrecoverableMessageHandlingException(sprintf('Product variant with id %d does not exist', $message->productVariant));
}

$productVariant->setOnHand($peakProduct->availableToSell + (int) $productVariant->getOnHold());

$this->productVariantRepository->add($productVariant);
$this->inventoryUpdater->update($productVariant);
}
}
Loading

0 comments on commit 03a28bb

Please sign in to comment.