Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions config/admin/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,33 @@ services:
autowire: true
arguments:
$decorated: '@.inner'

PrestaShop\Module\APIResources\ApiPlatform\Processor\CombinationSuppliersUpdateProcessor:
autowire: true
public: true
tags:
- { name: 'api_platform.state_processor' }

PrestaShop\Module\APIResources\ApiPlatform\Processor\CombinationStockUpdateProcessor:
autowire: true
public: true
tags:
- { name: 'api_platform.state_processor' }

PrestaShop\Module\APIResources\ApiPlatform\Processor\GenerateProductCombinationsProcessor:
autowire: true
public: true
tags:
- { name: 'api_platform.state_processor' }

PrestaShop\Module\APIResources\ApiPlatform\Provider\SearchProductCombinationsProvider:
autowire: true
public: true
tags:
- { name: 'api_platform.state_provider' }

PrestaShop\Module\APIResources\ApiPlatform\Provider\GetEditableCombinationsListProvider:
autowire: true
public: true
tags:
- { name: 'api_platform.state_provider' }
125 changes: 125 additions & 0 deletions src/ApiPlatform/Processor/CombinationStockUpdateProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/

declare(strict_types=1);

namespace PrestaShop\Module\APIResources\ApiPlatform\Processor;

use ApiPlatform\Metadata\Operation;
use PrestaShop\PrestaShop\Adapter\Product\Combination\Repository\CombinationRepository;
use PrestaShop\PrestaShop\Adapter\Product\Stock\Repository\StockAvailableRepository;
use PrestaShop\PrestaShop\Core\CommandBus\CommandBusInterface;
use PrestaShop\PrestaShop\Core\Domain\Product\Combination\ValueObject\CombinationId;
use PrestaShop\PrestaShop\Core\Domain\Shop\ValueObject\ShopId;
use PrestaShopBundle\ApiPlatform\ContextParametersProvider;
use PrestaShopBundle\ApiPlatform\Exception\CQRSCommandNotFoundException;
use PrestaShopBundle\ApiPlatform\NormalizationMapper;
use PrestaShopBundle\ApiPlatform\Processor\CommandProcessor as BaseCommandProcessor;
use PrestaShopBundle\ApiPlatform\Serializer\CQRSApiSerializer;

/**
* After updating stock, return a minimal payload with current quantity and location
*/
class CombinationStockUpdateProcessor extends BaseCommandProcessor
{
/**
* Execute command then return minimal payload { combinationId, quantity, location } regardless of command result.
*/
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
{
$CQRSCommandClass = $this->getCQRSCommandClass($operation);
if (null === $CQRSCommandClass || !class_exists($CQRSCommandClass)) {
throw new CQRSCommandNotFoundException(sprintf('Resource %s has no CQRS command defined.', $operation->getClass()));
}

if ($data instanceof $CQRSCommandClass) {
$command = $data;
} else {
$normalizedApiResourceDTO = $this->domainSerializer->normalize($data, null, [NormalizationMapper::NORMALIZATION_MAPPING => $this->getApiResourceMapping($operation)]);
$commandParameters = array_merge($normalizedApiResourceDTO, $uriVariables, $this->contextParametersProvider->getContextParameters());
$command = $this->domainSerializer->denormalize($commandParameters, $CQRSCommandClass, null, [NormalizationMapper::NORMALIZATION_MAPPING => $this->getCQRSCommandMapping($operation)]);
}

// Run command (ignore result value)
$this->commandBus->handle($command);

// Build minimal response
return $this->denormalizeCommandResult(null, $operation, $uriVariables);
}

public function __construct(
CommandBusInterface $commandBus,
CQRSApiSerializer $domainSerializer,
ContextParametersProvider $contextParametersProvider,
StockAvailableRepository $stockAvailableRepository,
CombinationRepository $combinationRepository,
) {
parent::__construct($commandBus, $domainSerializer, $contextParametersProvider);
$this->stockAvailableRepository = $stockAvailableRepository;
$this->combinationRepository = $combinationRepository;
}

private StockAvailableRepository $stockAvailableRepository;
private CombinationRepository $combinationRepository;

protected function denormalizeCommandResult(mixed $commandResult, Operation $operation, array $uriVariables): mixed
{
// Execute the command as usual (already done upstream), then compute minimal payload
$combinationId = (int) ($uriVariables['combinationId'] ?? 0);
$contextParams = $this->contextParametersProvider->getContextParameters();
$shopId = (int) ($contextParams['shopId'] ?? 0);

$quantity = null;
$location = '';
try {
if ($combinationId > 0) {
if ($shopId > 0) {
$stockAvailable = $this->stockAvailableRepository->getForCombination(new CombinationId($combinationId), new ShopId($shopId));
$quantity = (int) $stockAvailable->quantity;
$location = (string) $stockAvailable->location;
} else {
// Fallback: find any shop stock for this combination
$productId = $this->combinationRepository->getProductId(new CombinationId($combinationId));
$stockIds = $this->stockAvailableRepository->getAllShopsStockIds($productId, new CombinationId($combinationId));
if (!empty($stockIds)) {
$stockAvailable = $this->stockAvailableRepository->get($stockIds[0]);
$quantity = (int) $stockAvailable->quantity;
$location = (string) $stockAvailable->location;
}
}
}
} catch (\Throwable $e) {
// Ignore and fallback to minimal payload
}

$class = $operation->getClass();
$dto = new $class();
// set public properties
$dto->combinationId = $combinationId;
if (property_exists($dto, 'quantity')) {
$dto->quantity = $quantity;
}
if (property_exists($dto, 'location')) {
$dto->location = $location;
}

return $dto;
}
}
63 changes: 63 additions & 0 deletions src/ApiPlatform/Processor/CombinationSuppliersUpdateProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/

declare(strict_types=1);

namespace PrestaShop\Module\APIResources\ApiPlatform\Processor;

use ApiPlatform\Metadata\Operation;
use PrestaShopBundle\ApiPlatform\NormalizationMapper;
use PrestaShopBundle\ApiPlatform\Processor\CommandProcessor as BaseCommandProcessor;

/**
* Custom processor for combination suppliers update that forces returning the fresh list by
* re-running the configured CQRSQuery using uriVariables (so combinationId is present).
*/
class CombinationSuppliersUpdateProcessor extends BaseCommandProcessor
{
protected function denormalizeCommandResult(mixed $commandResult, Operation $operation, array $uriVariables): mixed
{
// Ignore command result to ensure the subsequent Query gets proper identifiers from URI
$normalizedCommandResult = $uriVariables + $this->contextParametersProvider->getContextParameters();

$queryClass = $this->getCQRSQueryClass($operation);
if (!$queryClass) {
return $this->denormalizeApiPlatformDTO($normalizedCommandResult, $operation);
}

// Build and run CQRS query
$CQRSQuery = $this->domainSerializer->denormalize($normalizedCommandResult, $queryClass, null, [NormalizationMapper::NORMALIZATION_MAPPING => $this->getCQRSQueryMapping($operation)]);
$CQRSQueryResult = $this->commandBus->handle($CQRSQuery);

// Manually denormalize as a collection of ApiResource DTOs
$normalizedQueryResult = $this->domainSerializer->normalize($CQRSQueryResult, null, [NormalizationMapper::NORMALIZATION_MAPPING => $this->getCQRSQueryMapping($operation)]);
if (!\is_array($normalizedQueryResult)) {
return [];
}

$apiResourceClass = $operation->getClass();
$apiResourceMapping = $this->getApiResourceMapping($operation);
foreach ($normalizedQueryResult as $key => $item) {
$normalizedQueryResult[$key] = $this->domainSerializer->denormalize($item, $apiResourceClass, null, [NormalizationMapper::NORMALIZATION_MAPPING => $apiResourceMapping]);
}

return $normalizedQueryResult;
}
}
50 changes: 50 additions & 0 deletions src/ApiPlatform/Processor/GenerateProductCombinationsProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/

declare(strict_types=1);

namespace PrestaShop\Module\APIResources\ApiPlatform\Processor;

use ApiPlatform\Metadata\Operation;
use PrestaShopBundle\ApiPlatform\NormalizationMapper;
use PrestaShopBundle\ApiPlatform\Processor\CommandProcessor as BaseCommandProcessor;

/**
* Ensures uriVariables (productId) are available when building the return query
* after generating combinations, so the response includes the right productId.
*/
class GenerateProductCombinationsProcessor extends BaseCommandProcessor
{
/**
* {@inheritdoc}
*/
protected function denormalizeCommandResult(mixed $commandResult, Operation $operation, array $uriVariables): mixed
{
$queryClass = $this->getCQRSQueryClass($operation);
if (!$queryClass) {
return $this->domainSerializer->denormalize($commandResult, $operation->getClass(), null, [NormalizationMapper::NORMALIZATION_MAPPING => $this->getApiResourceMapping($operation)]);
}

// Merge uriVariables so [productId] mapping is always present
$normalizedCommandResult = array_merge($commandResult ?? [], $uriVariables, $this->contextParametersProvider->getContextParameters());

return $this->handleCQRSQueryAndReturnResult($queryClass, $normalizedCommandResult, $operation);
}
}
70 changes: 70 additions & 0 deletions src/ApiPlatform/Provider/GetEditableCombinationsListProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/

declare(strict_types=1);

namespace PrestaShop\Module\APIResources\ApiPlatform\Provider;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use PrestaShopBundle\ApiPlatform\Exception\CQRSQueryNotFoundException;
use PrestaShopBundle\ApiPlatform\NormalizationMapper;
use PrestaShopBundle\ApiPlatform\Provider\QueryProvider;
use Symfony\Component\Serializer\Exception\ExceptionInterface;

/**
* Custom provider that injects uri_variables into denormalization context
* so that ApiResourceMapping can copy productId from the route into the DTO.
*/
class GetEditableCombinationsListProvider extends QueryProvider implements ProviderInterface
{
/**
* @throws ExceptionInterface
*/
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array|object|null
{
$CQRSQueryClass = $this->getCQRSQueryClass($operation);
if (null === $CQRSQueryClass) {
throw new CQRSQueryNotFoundException(sprintf('Resource %s has no CQRS query defined.', $operation->getClass()));
}

$filters = $context['filters'] ?? [];
$queryParameters = array_merge($uriVariables, $filters, $this->contextParametersProvider->getContextParameters());

$CQRSQuery = $this->domainSerializer->denormalize($queryParameters, $CQRSQueryClass, null, [NormalizationMapper::NORMALIZATION_MAPPING => $this->getCQRSQueryMapping($operation)]);
$CQRSQueryResult = $this->queryBus->handle($CQRSQuery);
if (null === $CQRSQueryResult) {
return new ($operation->getClass())();
}

$normalizedQueryResult = $this->domainSerializer->normalize($CQRSQueryResult, null, [NormalizationMapper::NORMALIZATION_MAPPING => $this->getCQRSQueryMapping($operation)]);

return $this->domainSerializer->denormalize(
$normalizedQueryResult,
$operation->getClass(),
null,
[
NormalizationMapper::NORMALIZATION_MAPPING => $this->getApiResourceMapping($operation),
'uri_variables' => $uriVariables,
'operation' => $operation,
]
);
}
}
Loading
Loading