mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 09:02:12 +00:00
Add possibility to remove option values from Product Option (#17048)
| Q | A |-----------------|----- | Branch? | 2.0 <!-- see the comment below --> | New feature? | a bit | BC breaks? | yes | License | MIT <!-- - Bug fixes must be submitted against the 1.13 branch - Features and deprecations must be submitted against the 1.14 branch - Features, removing deprecations and BC breaks must be submitted against the 2.0 branch - Make sure that the correct base branch is set To be sure you are not breaking any Backward Compatibilities, check the documentation: https://docs.sylius.com/en/latest/book/organization/backward-compatibility-promise.html --> 
This commit is contained in:
commit
3ec2396737
38 changed files with 677 additions and 54 deletions
|
|
@ -5,7 +5,7 @@ Feature: Managing option values of a product option
|
|||
I want to be able to edit a product option and its option values
|
||||
|
||||
Background:
|
||||
Given the store is available in "English (United States)"
|
||||
Given the store operates on a single channel in "United States"
|
||||
And the store has a product option "T-Shirt size" with a code "t_shirt_size"
|
||||
And this product option has the "S" option value with code "OV1"
|
||||
And this product option has also the "M" option value with code "OV2"
|
||||
|
|
@ -19,21 +19,34 @@ Feature: Managing option values of a product option
|
|||
Then I should be notified that it has been successfully edited
|
||||
And this product option should have the "L" option value
|
||||
|
||||
@api @todo-ui @javascript
|
||||
@api @ui @mink:chromedriver
|
||||
Scenario: Removing an option value from an existing product option
|
||||
Given this product option has also the "L" option value with code "OV3"
|
||||
When I want to modify the "T-Shirt size" product option
|
||||
And I delete the "L" option value of this product option
|
||||
And I delete the "OV3" option value of this product option
|
||||
And I save my changes
|
||||
Then I should be notified that it has been successfully edited
|
||||
And this product option should not have the "L" option value
|
||||
|
||||
@api @todo-ui @javascript
|
||||
@api @ui @mink:chromedriver
|
||||
Scenario: Removing and adding a new option value to an existing product option
|
||||
When I want to modify the "T-Shirt size" product option
|
||||
And I delete the "M" option value of this product option
|
||||
And I delete the "OV2" option value of this product option
|
||||
And I add the "L" option value identified by "OV3"
|
||||
And I save my changes
|
||||
Then I should be notified that it has been successfully edited
|
||||
And this product option should not have the "M" option value
|
||||
And this product option should have the "L" option value
|
||||
|
||||
@api @ui @mink:chromedriver
|
||||
Scenario: Removing product option value that is in use by product variant
|
||||
Given the store has a "Car" configurable product
|
||||
And this product has option "Model" with values "Sedan", "Kombi" and "Cabrio"
|
||||
And this product has "Car-Variant-1" variant priced at "$20.00" configured with "Sedan" option value
|
||||
And this product has "Car-Variant-2" variant priced at "$25.00" configured with "Kombi" option value
|
||||
And this product has "Car-Variant-3" variant priced at "$50.00" configured with "Cabrio" option value
|
||||
When I want to modify the "Model" product option
|
||||
And I delete the "Sedan" option value of this product option
|
||||
And I try to save my changes
|
||||
Then I should be notified that it is in use
|
||||
And product option "Model" should still have the "Sedan" option value
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Sylius\Behat\Context\Api\Admin;
|
||||
|
||||
use ApiPlatform\Api\IriConverterInterface;
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Client\ApiClientInterface;
|
||||
use Sylius\Behat\Client\ResponseCheckerInterface;
|
||||
|
|
@ -20,6 +21,7 @@ use Sylius\Behat\Context\Api\Admin\Helper\ValidationTrait;
|
|||
use Sylius\Behat\Context\Api\Resources;
|
||||
use Sylius\Behat\Service\SharedStorageInterface;
|
||||
use Sylius\Component\Product\Model\ProductOptionInterface;
|
||||
use Sylius\Component\Product\Model\ProductOptionValueInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class ManagingProductOptionsContext implements Context
|
||||
|
|
@ -30,6 +32,7 @@ final class ManagingProductOptionsContext implements Context
|
|||
private ApiClientInterface $client,
|
||||
private ResponseCheckerInterface $responseChecker,
|
||||
private SharedStorageInterface $sharedStorage,
|
||||
private IriConverterInterface $iriConverter,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -113,11 +116,12 @@ final class ManagingProductOptionsContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @When I delete the :value option value of this product option
|
||||
* @When I delete the :optionValue option value of this product option
|
||||
*/
|
||||
public function iDeleteTheOptionValueOfThisProductOption(string $value): void
|
||||
public function iDeleteTheOptionValueOfThisProductOption(ProductOptionValueInterface $optionValue): void
|
||||
{
|
||||
$this->client->removeSubResourceObject('values', $value, 'value');
|
||||
$optionValueIri = $this->iriConverter->getIriFromResource($optionValue);
|
||||
$this->client->removeSubResourceObject('values', $optionValueIri, 'value');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -228,6 +232,7 @@ final class ManagingProductOptionsContext implements Context
|
|||
|
||||
/**
|
||||
* @Then /^(product option "[^"]+") should have the "([^"]+)" option value$/
|
||||
* @Then /^(product option "[^"]+") should still have the "([^"]+)" option value$/
|
||||
* @Then /^(this product option) should have the "([^"]*)" option value$/
|
||||
*/
|
||||
public function productOptionShouldHaveTheOptionValue(
|
||||
|
|
@ -296,6 +301,17 @@ final class ManagingProductOptionsContext implements Context
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it is in use
|
||||
*/
|
||||
public function iShouldBeNotifiedThatItIsInUse(): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'Cannot delete, the product option value is in use.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it has been successfully created
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ final readonly class ManagingProductOptionsContext implements Context
|
|||
*/
|
||||
public function iDeleteTheOptionValueWithCodeAndValue(string $value): void
|
||||
{
|
||||
// TODO: Implement deleting option value
|
||||
$this->formElement->removeOptionValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -253,6 +253,7 @@ final readonly class ManagingProductOptionsContext implements Context
|
|||
/**
|
||||
* @Then /^(this product option) should have the "([^"]*)" option value$/
|
||||
* @Then /^(product option "[^"]+") should have the "([^"]*)" option value$/
|
||||
* @Then /^(product option "[^"]+") should still have the "([^"]*)" option value$/
|
||||
* @Then /^(this product option) should have the "([^"]*)" option value in ("([^"]+)" locale)$/
|
||||
*/
|
||||
public function thisProductOptionShouldHaveTheOptionValue(
|
||||
|
|
|
|||
|
|
@ -28,6 +28,17 @@ class FormElement extends BaseFormElement implements FormElementInterface
|
|||
$this->getElement('name', ['%locale_code%' => $localeCode])->setValue($name);
|
||||
}
|
||||
|
||||
public function removeOptionValue(string $optionValue): void
|
||||
{
|
||||
$optionValues = $this->getDocument()->findAll('css', '[data-test-option-value]');
|
||||
foreach ($optionValues as $optionValueElement) {
|
||||
if ($optionValueElement->has('css', sprintf('input[value="%s"]', $optionValue))) {
|
||||
$optionValueElement->find('css', '[data-test-delete-option-value]')->click();
|
||||
$this->waitForFormUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addOptionValue(string $code, string $localeCode, string $value): void
|
||||
{
|
||||
$this->getElement('add_option_value')->press();
|
||||
|
|
@ -56,6 +67,7 @@ class FormElement extends BaseFormElement implements FormElementInterface
|
|||
'add_option_value' => '[data-test-add-option-value]',
|
||||
'apply_to_all' => '[data-test-option-value="%value_code%"] [data-test-option-value-locale="%locale_code%"] [data-test-apply-to-all]',
|
||||
'code' => '[data-test-code]',
|
||||
'delete_option_value' => '[data-test-delete-option-value]',
|
||||
'form' => '[data-live-name-value="sylius_admin:product_option:form"]',
|
||||
'last_option_value' => '[data-test-option-values] [data-test-option-value]:last-child',
|
||||
'name' => '#sylius_admin_product_option_translations_%locale_code%_name',
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@
|
|||
<argument type="service" id="sylius.behat.api_platform_client.admin" />
|
||||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument type="service" id="sylius.behat.shared_storage" />
|
||||
<argument type="service" id="api_platform.iri_converter" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.api.admin.managing_products" class="Sylius\Behat\Context\Api\Admin\ManagingProductsContext">
|
||||
|
|
|
|||
|
|
@ -9,12 +9,16 @@ default:
|
|||
|
||||
- sylius.behat.context.transform.lexical
|
||||
- sylius.behat.context.transform.locale
|
||||
- sylius.behat.context.transform.product
|
||||
- sylius.behat.context.transform.product_option
|
||||
- sylius.behat.context.transform.product_option_value
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.locale
|
||||
- sylius.behat.context.setup.product_option
|
||||
- sylius.behat.context.setup.admin_api_security
|
||||
- sylius.behat.context.setup.product
|
||||
|
||||
- sylius.behat.context.api.admin.managing_product_options
|
||||
- sylius.behat.context.api.admin.response
|
||||
|
|
|
|||
|
|
@ -10,12 +10,16 @@ default:
|
|||
|
||||
- sylius.behat.context.transform.lexical
|
||||
- sylius.behat.context.transform.locale
|
||||
- sylius.behat.context.transform.product
|
||||
- sylius.behat.context.transform.product_option
|
||||
- sylius.behat.context.transform.product_option_value
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.locale
|
||||
- sylius.behat.context.setup.product_option
|
||||
- sylius.behat.context.setup.admin_security
|
||||
- sylius.behat.context.setup.product
|
||||
|
||||
- sylius.behat.context.ui.admin.managing_product_options
|
||||
- sylius.behat.context.ui.admin.notification
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\EventListener;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Provider\FlashBagProvider;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Sylius\Resource\ResourceActions;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
final readonly class ResourceDeleteExceptionListener
|
||||
{
|
||||
public function __construct(
|
||||
private UrlGeneratorInterface $router,
|
||||
private RequestStack $requestStack,
|
||||
) {
|
||||
}
|
||||
|
||||
public function onResourceDeleteException(ExceptionEvent $event): void
|
||||
{
|
||||
$exception = $event->getThrowable();
|
||||
|
||||
if (!$exception instanceof ResourceDeleteException) {
|
||||
return;
|
||||
}
|
||||
|
||||
FlashBagProvider::getFlashBag($this->requestStack)->add('error', [
|
||||
'message' => 'sylius.resource.delete_error',
|
||||
'parameters' => ['%resource%' => $exception->getResourceName()],
|
||||
]);
|
||||
|
||||
$eventRequest = $event->getRequest();
|
||||
$requestAttributes = $eventRequest->attributes;
|
||||
$originalRoute = $requestAttributes->get('_route', '');
|
||||
|
||||
$referrer = $eventRequest->headers->get('referer');
|
||||
if (null !== $referrer) {
|
||||
$event->setResponse(new RedirectResponse($referrer));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setResponse($this->createRedirectResponse($originalRoute, ResourceActions::INDEX));
|
||||
}
|
||||
|
||||
private function createRedirectResponse(string $originalRoute, string $targetAction): RedirectResponse
|
||||
{
|
||||
$redirectRoute = str_replace(ResourceActions::DELETE, $targetAction, $originalRoute);
|
||||
|
||||
return new RedirectResponse($this->router->generate($redirectRoute));
|
||||
}
|
||||
}
|
||||
|
|
@ -14,31 +14,12 @@ declare(strict_types=1);
|
|||
namespace Sylius\Bundle\AdminBundle\EventListener;
|
||||
|
||||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use Sylius\Bundle\CoreBundle\Provider\FlashBagProvider;
|
||||
use Sylius\Resource\ResourceActions;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
final readonly class ResourceDeleteSubscriber implements EventSubscriberInterface
|
||||
final readonly class ResourceDeleteListener
|
||||
{
|
||||
public function __construct(
|
||||
private UrlGeneratorInterface $router,
|
||||
private RequestStack $requestStack,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
KernelEvents::EXCEPTION => 'onResourceDelete',
|
||||
];
|
||||
}
|
||||
|
||||
public function onResourceDelete(ExceptionEvent $event): void
|
||||
{
|
||||
$exception = $event->getThrowable();
|
||||
|
|
@ -67,19 +48,7 @@ final readonly class ResourceDeleteSubscriber implements EventSubscriberInterfac
|
|||
return;
|
||||
}
|
||||
|
||||
FlashBagProvider::getFlashBag($this->requestStack)->add('error', [
|
||||
'message' => 'sylius.resource.delete_error',
|
||||
'parameters' => ['%resource%' => $resourceName],
|
||||
]);
|
||||
|
||||
$referrer = $eventRequest->headers->get('referer');
|
||||
if (null !== $referrer) {
|
||||
$event->setResponse(new RedirectResponse($referrer));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setResponse($this->createRedirectResponse($originalRoute, ResourceActions::INDEX));
|
||||
throw new ResourceDeleteException($resourceName);
|
||||
}
|
||||
|
||||
private function getResourceNameFromRoute(string $route): string
|
||||
|
|
@ -92,13 +61,6 @@ final readonly class ResourceDeleteSubscriber implements EventSubscriberInterfac
|
|||
return trim(implode(' ', $routeArrayWithoutPrefixes));
|
||||
}
|
||||
|
||||
private function createRedirectResponse(string $originalRoute, string $targetAction): RedirectResponse
|
||||
{
|
||||
$redirectRoute = str_replace(ResourceActions::DELETE, $targetAction, $originalRoute);
|
||||
|
||||
return new RedirectResponse($this->router->generate($redirectRoute));
|
||||
}
|
||||
|
||||
private function isMethodDelete(Request $request): bool
|
||||
{
|
||||
return Request::METHOD_DELETE === $request->getMethod();
|
||||
|
|
@ -109,6 +71,7 @@ final readonly class ResourceDeleteSubscriber implements EventSubscriberInterfac
|
|||
return str_starts_with($route, 'sylius');
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $syliusParameters */
|
||||
private function isAdminSection(array $syliusParameters): bool
|
||||
{
|
||||
return array_key_exists('section', $syliusParameters) && 'admin' === $syliusParameters['section'];
|
||||
|
|
@ -40,3 +40,5 @@ twig_hooks:
|
|||
template: '@SyliusAdmin/product_option/form/sections/values/value/code.html.twig'
|
||||
translations:
|
||||
template: '@SyliusAdmin/product_option/form/sections/values/value/translations.html.twig'
|
||||
button_delete:
|
||||
template: '@SyliusAdmin/product_option/form/sections/values/value/button_delete.html.twig'
|
||||
|
|
|
|||
|
|
@ -42,3 +42,5 @@ twig_hooks:
|
|||
template: '@SyliusAdmin/product_option/form/sections/values/value/code.html.twig'
|
||||
translations:
|
||||
template: '@SyliusAdmin/product_option/form/sections/values/value/translations.html.twig'
|
||||
button_delete:
|
||||
template: '@SyliusAdmin/product_option/form/sections/values/value/button_delete.html.twig'
|
||||
|
|
|
|||
|
|
@ -26,10 +26,14 @@
|
|||
<tag name="kernel.event_listener" event="sylius.locale.pre_delete" method="preDelete" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.event_subscriber.resource_delete" class="Sylius\Bundle\AdminBundle\EventListener\ResourceDeleteSubscriber">
|
||||
<service id="sylius.event_listener.resource_delete_exception" class="Sylius\Bundle\AdminBundle\EventListener\ResourceDeleteExceptionListener">
|
||||
<argument type="service" id="router" />
|
||||
<argument type="service" id="request_stack" />
|
||||
<tag name="kernel.event_subscriber" event="kernel.exception" />
|
||||
<tag name="kernel.event_listener" event="kernel.exception" method="onResourceDeleteException" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.event_listener.resource_delete" class="Sylius\Bundle\AdminBundle\EventListener\ResourceDeleteListener">
|
||||
<tag name="kernel.event_listener" event="kernel.exception" method="onResourceDelete" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.event_subscriber.admin_cache_control_subscriber" class="Sylius\Bundle\AdminBundle\EventListener\AdminSectionCacheControlSubscriber">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\AdminBundle\EventListener;
|
||||
|
||||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Symfony\Component\HttpFoundation\HeaderBag;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
final class ResourceDeleteExceptionListenerSpec extends ObjectBehavior
|
||||
{
|
||||
function let(UrlGeneratorInterface $router, RequestStack $requestStack): void
|
||||
{
|
||||
$this->beConstructedWith($router, $requestStack);
|
||||
}
|
||||
|
||||
function it_does_nothing_if_exception_is_not_resource_delete_exception(
|
||||
KernelInterface $kernel,
|
||||
ForeignKeyConstraintViolationException $exception,
|
||||
): void {
|
||||
$event = new ExceptionEvent($kernel->getWrappedObject(), new Request(), HttpKernelInterface::MAIN_REQUEST, $exception->getWrappedObject());
|
||||
|
||||
$this->onResourceDeleteException($event)->shouldReturn(null);
|
||||
}
|
||||
|
||||
function it_redirects_to_referer_if_present_and_adds_flash_message(
|
||||
KernelInterface $kernel,
|
||||
Request $request,
|
||||
RequestStack $requestStack,
|
||||
FlashBagInterface $flashBag,
|
||||
UrlGeneratorInterface $router,
|
||||
SessionInterface $session,
|
||||
): void {
|
||||
$request->attributes = new ParameterBag(['_route' => 'sylius_admin_product_delete']);
|
||||
$request->headers = new HeaderBag(['referer' => '/admin/product/index']);
|
||||
$exception = new ResourceDeleteException('Product');
|
||||
$event = new ExceptionEvent($kernel->getWrappedObject(), $request->getWrappedObject(), HttpKernelInterface::MAIN_REQUEST, $exception);
|
||||
|
||||
$requestStack->getSession()->willReturn($session);
|
||||
$session->getBag('flashes')->willReturn($flashBag);
|
||||
$flashBag->add('error', ['message' => 'sylius.resource.delete_error', 'parameters' => ['%resource%' => 'Product']])->shouldBeCalled();
|
||||
|
||||
$router->generate(Argument::cetera())->shouldNotBeCalled();
|
||||
|
||||
$this->onResourceDeleteException($event);
|
||||
}
|
||||
|
||||
function it_redirects_to_index_when_no_referer_present_and_adds_flash_message(
|
||||
KernelInterface $kernel,
|
||||
Request $request,
|
||||
RequestStack $requestStack,
|
||||
FlashBagInterface $flashBag,
|
||||
UrlGeneratorInterface $router,
|
||||
SessionInterface $session,
|
||||
): void {
|
||||
$request->attributes = new ParameterBag(['_route' => 'sylius_admin_product_delete']);
|
||||
$request->headers = new HeaderBag();
|
||||
$exception = new ResourceDeleteException('Product');
|
||||
$event = new ExceptionEvent($kernel->getWrappedObject(), $request->getWrappedObject(), HttpKernelInterface::MAIN_REQUEST, $exception);
|
||||
|
||||
$requestStack->getSession()->willReturn($session);
|
||||
$session->getBag('flashes')->willReturn($flashBag);
|
||||
$flashBag->add('error', ['message' => 'sylius.resource.delete_error', 'parameters' => ['%resource%' => 'Product']])->shouldBeCalled();
|
||||
|
||||
$router->generate('sylius_admin_product_index')->willReturn('/admin/product/index');
|
||||
|
||||
$this->onResourceDeleteException($event);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\AdminBundle\EventListener;
|
||||
|
||||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
final class ResourceDeleteListenerSpec extends ObjectBehavior
|
||||
{
|
||||
function it_does_nothing_if_exception_is_not_foreign_key_constraint(KernelInterface $kernel): void
|
||||
{
|
||||
$this->onResourceDelete(new ExceptionEvent($kernel->getWrappedObject(), new Request(), HttpKernelInterface::MAIN_REQUEST, new \Exception()))->shouldReturn(null);
|
||||
}
|
||||
|
||||
function it_does_nothing_if_request_is_not_main(KernelInterface $kernel, ForeignKeyConstraintViolationException $exception): void
|
||||
{
|
||||
$event = new ExceptionEvent($kernel->getWrappedObject(), new Request(), HttpKernelInterface::SUB_REQUEST, $exception->getWrappedObject());
|
||||
|
||||
$this->onResourceDelete($event)->shouldReturn(null);
|
||||
}
|
||||
|
||||
function it_should_throw_resource_delete_exception_if_all_conditions_are_met(KernelInterface $kernel, ForeignKeyConstraintViolationException $exception): void
|
||||
{
|
||||
$event = new ExceptionEvent($kernel->getWrappedObject(), new Request(), HttpKernelInterface::MAIN_REQUEST, $exception->getWrappedObject());
|
||||
|
||||
$this->onResourceDelete($event)->shouldThrow(ResourceDeleteException::class);
|
||||
}
|
||||
|
||||
function it_should_do_nothing_if_method_is_not_delete(KernelInterface $kernel, ForeignKeyConstraintViolationException $exception): void
|
||||
{
|
||||
$request = new Request([], [], ['_route' => 'sylius_admin_product_delete', '_sylius' => ['section' => 'admin']]);
|
||||
$request->setMethod('GET');
|
||||
|
||||
$event = new ExceptionEvent($kernel->getWrappedObject(), $request, HttpKernelInterface::MAIN_REQUEST, $exception->getWrappedObject());
|
||||
|
||||
$this->onResourceDelete($event)->shouldReturn(null);
|
||||
}
|
||||
|
||||
function it_should_do_nothing_if_route_is_not_sylius(KernelInterface $kernel, ForeignKeyConstraintViolationException $exception): void
|
||||
{
|
||||
$request = new Request([], [], ['_route' => 'non_sylius_route', '_sylius' => ['section' => 'admin']]);
|
||||
|
||||
$event = new ExceptionEvent($kernel->getWrappedObject(), $request, HttpKernelInterface::MAIN_REQUEST, $exception->getWrappedObject());
|
||||
|
||||
$this->onResourceDelete($event)->shouldReturn(null);
|
||||
}
|
||||
|
||||
function it_should_do_nothing_if_section_is_not_admin(KernelInterface $kernel, ForeignKeyConstraintViolationException $exception): void
|
||||
{
|
||||
$request = new Request([], [], ['_route' => 'sylius_admin_product_delete', '_sylius' => ['section' => 'shop']]);
|
||||
|
||||
$event = new ExceptionEvent($kernel->getWrappedObject(), $request, HttpKernelInterface::MAIN_REQUEST, $exception->getWrappedObject());
|
||||
|
||||
$this->onResourceDelete($event)->shouldReturn(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<div class="col text-end">
|
||||
{{ form_row(hookable_metadata.context.form.vars.button_delete, { attr: {class: 'btn btn-outline-danger', 'data-test-delete-option-value': '' }}) }}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Exception;
|
||||
|
||||
final class ProductOptionValueCannotBeRemoved extends \RuntimeException
|
||||
{
|
||||
public function __construct(
|
||||
string $message = 'Cannot delete, the product option value is in use.',
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null,
|
||||
) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
|
|
@ -96,6 +96,7 @@
|
|||
name="sylius_api_admin_product_option_put"
|
||||
class="ApiPlatform\Metadata\Put"
|
||||
uriTemplate="/admin/product-options/{code}"
|
||||
processor="sylius_api.state_processor.admin.product_option_value.persist"
|
||||
>
|
||||
<denormalizationContext>
|
||||
<values>
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ api_platform:
|
|||
Sylius\Bundle\ApiBundle\Exception\PaymentMethodCannotBeRemoved: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\ProductAttributeCannotBeRemoved: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\ProductCannotBeRemoved: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\ProductOptionValueCannotBeRemoved: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\ProductVariantCannotBeRemoved: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\PromotionCouponCannotBeRemoved: 422
|
||||
Sylius\Bundle\ApiBundle\Exception\ProvinceCannotBeRemoved: 422
|
||||
|
|
|
|||
|
|
@ -68,6 +68,11 @@
|
|||
<tag name="api_platform.state_processor" />
|
||||
</service>
|
||||
|
||||
<service id="sylius_api.state_processor.admin.product_option_value.persist" class="Sylius\Bundle\ApiBundle\StateProcessor\Admin\ProductOption\PersistProcessor">
|
||||
<argument type="service" id="api_platform.doctrine.orm.state.persist_processor" />
|
||||
<tag name="api_platform.state_processor" />
|
||||
</service>
|
||||
|
||||
<service id="sylius_api.state_processor.admin.product_attribute.remove" class="Sylius\Bundle\ApiBundle\StateProcessor\Admin\ProductAttribute\RemoveProcessor">
|
||||
<argument type="service" id="api_platform.doctrine.orm.state.remove_processor" />
|
||||
<tag name="api_platform.state_processor" />
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use ApiPlatform\Metadata\Operation;
|
|||
use ApiPlatform\State\ProcessorInterface;
|
||||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use Sylius\Bundle\ApiBundle\Exception\PaymentMethodCannotBeRemoved;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Sylius\Component\Core\Model\PaymentMethodInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use ApiPlatform\Metadata\Operation;
|
|||
use ApiPlatform\State\ProcessorInterface;
|
||||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use Sylius\Bundle\ApiBundle\Exception\ProductAttributeCannotBeRemoved;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Sylius\Component\Core\Model\ProductInterface;
|
||||
use Sylius\Component\Product\Model\ProductAttributeInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\StateProcessor\Admin\ProductOption;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use Sylius\Bundle\ApiBundle\Exception\ProductOptionValueCannotBeRemoved;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Sylius\Component\Product\Model\ProductOptionInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/** @implements ProcessorInterface<ProductOptionInterface> */
|
||||
final readonly class PersistProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ProcessorInterface $processor,
|
||||
) {
|
||||
}
|
||||
|
||||
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
|
||||
{
|
||||
Assert::isInstanceOf($data, ProductOptionInterface::class);
|
||||
Assert::isInstanceOf($operation, Put::class);
|
||||
|
||||
try {
|
||||
$this->processor->process($data, $operation, $uriVariables, $context);
|
||||
} catch (ResourceDeleteException) {
|
||||
throw new ProductOptionValueCannotBeRemoved();
|
||||
}
|
||||
|
||||
return $this->processor->process($data, $operation, $uriVariables, $context);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ use ApiPlatform\Metadata\Operation;
|
|||
use ApiPlatform\State\ProcessorInterface;
|
||||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use Sylius\Bundle\ApiBundle\Exception\ShippingMethodCannotBeRemoved;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Sylius\Component\Core\Model\ShippingMethodInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
|||
use Sylius\Bundle\ApiBundle\Exception\ZoneCannotBeRemoved;
|
||||
use Sylius\Component\Addressing\Checker\ZoneDeletionCheckerInterface;
|
||||
use Sylius\Component\Addressing\Model\ZoneInterface;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/** @implements ProcessorInterface<ZoneInterface> */
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ use ApiPlatform\State\ProcessorInterface;
|
|||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Exception\PaymentMethodCannotBeRemoved;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Sylius\Component\Core\Model\PaymentMethodInterface;
|
||||
use Sylius\Component\Core\Model\ShippingMethodInterface;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use ApiPlatform\State\ProcessorInterface;
|
|||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Exception\ProductAttributeCannotBeRemoved;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Sylius\Component\Product\Model\ProductAttributeInterface;
|
||||
|
||||
final class RemoveProcessorSpec extends ObjectBehavior
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\ApiBundle\StateProcessor\Admin\ProductOption;
|
||||
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Exception\ProductOptionValueCannotBeRemoved;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Sylius\Component\Core\Model\ShippingMethodInterface;
|
||||
use Sylius\Component\Product\Model\ProductOptionInterface;
|
||||
|
||||
final class PersistProcessorSpec extends ObjectBehavior
|
||||
{
|
||||
function let(ProcessorInterface $processor): void
|
||||
{
|
||||
$this->beConstructedWith($processor);
|
||||
}
|
||||
|
||||
function it_is_a_processor_interface(): void
|
||||
{
|
||||
$this->shouldImplement(ProcessorInterface::class);
|
||||
}
|
||||
|
||||
public function it_processes_remove_operation(
|
||||
ProcessorInterface $processor,
|
||||
ProductOptionInterface $productOption,
|
||||
): void {
|
||||
$operation = new Put();
|
||||
$processor->process($productOption, $operation, [], [])->shouldBeCalled();
|
||||
|
||||
$this->process($productOption, $operation);
|
||||
}
|
||||
|
||||
public function it_throws_an_exception_when_resource_delete_exception_occurs(
|
||||
ProcessorInterface $processor,
|
||||
ProductOptionInterface $productOption,
|
||||
): void {
|
||||
$operation = new Put();
|
||||
$processor->process($productOption, $operation, [], [])->willThrow(ResourceDeleteException::class);
|
||||
|
||||
$this->shouldThrow(ProductOptionValueCannotBeRemoved::class)->during('process', [$productOption, $operation]);
|
||||
}
|
||||
|
||||
public function it_throws_an_exception_if_operation_is_not_put(
|
||||
ShippingMethodInterface $shippingMethod,
|
||||
): void {
|
||||
$this->shouldThrow(\InvalidArgumentException::class)->during('process', [$shippingMethod, new Delete()]);
|
||||
}
|
||||
|
||||
public function it_throws_exception_if_data_is_not_product_option_interface(): void
|
||||
{
|
||||
$this->shouldThrow(\InvalidArgumentException::class)->during('process', [new \stdClass(), new Put()]);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ use ApiPlatform\State\ProcessorInterface;
|
|||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Exception\ShippingMethodCannotBeRemoved;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Sylius\Component\Core\Model\ShippingMethodInterface;
|
||||
|
||||
final class RemoveProcessorSpec extends ObjectBehavior
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use Prophecy\Argument;
|
|||
use Sylius\Bundle\ApiBundle\Exception\ZoneCannotBeRemoved;
|
||||
use Sylius\Component\Addressing\Checker\ZoneDeletionCheckerInterface;
|
||||
use Sylius\Component\Addressing\Model\ZoneInterface;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
|
||||
final class RemoveProcessorSpec extends ObjectBehavior
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Sylius\Bundle\CoreBundle\Doctrine\ORM;
|
||||
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Sylius\Bundle\ProductBundle\Doctrine\ORM\ProductVariantRepository as BaseProductVariantRepository;
|
||||
use Sylius\Component\Core\Model\CatalogPromotionInterface;
|
||||
|
|
@ -64,4 +65,14 @@ class ProductVariantRepository extends BaseProductVariantRepository implements P
|
|||
->setParameter('locale', $locale)
|
||||
;
|
||||
}
|
||||
|
||||
public function countByProductOptionValueId(mixed $id): int
|
||||
{
|
||||
return (int) $this->createQueryBuilder('o')
|
||||
->select('COUNT(o.id)')
|
||||
->innerJoin('o.optionValues', 'optionValue', Join::WITH, 'optionValue.id = :id')
|
||||
->setParameter('id', $id)
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\EventListener;
|
||||
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Sylius\Component\Core\Model\ProductVariantInterface;
|
||||
use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface;
|
||||
use Sylius\Component\Product\Model\ProductOptionValueInterface;
|
||||
|
||||
final class ProductOptionValueDeletionListener
|
||||
{
|
||||
/**
|
||||
* @param ProductVariantRepositoryInterface<ProductVariantInterface> $productVariantRepository
|
||||
*/
|
||||
public function __construct(
|
||||
private ProductVariantRepositoryInterface $productVariantRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function preRemove(ProductOptionValueInterface $optionValue): void
|
||||
{
|
||||
if ($this->productVariantRepository->countByProductOptionValueId($optionValue->getId()) > 0) {
|
||||
throw new ResourceDeleteException('product option value');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Sylius\Bundle\CoreBundle\Doctrine\Migrations\AbstractPostgreSQLMigration;
|
||||
|
||||
final class Version20240926122944 extends AbstractPostgreSQLMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Remove cascade onDelete from sylius_product_variant_option_value table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value DROP CONSTRAINT FK_76CDAFA13B69A9AF');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value DROP CONSTRAINT FK_76CDAFA1D957CA06');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value ADD CONSTRAINT FK_76CDAFA13B69A9AF FOREIGN KEY (variant_id) REFERENCES sylius_product_variant (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value ADD CONSTRAINT FK_76CDAFA1D957CA06 FOREIGN KEY (option_value_id) REFERENCES sylius_product_option_value (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value DROP CONSTRAINT fk_76cdafa13b69a9af');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value DROP CONSTRAINT fk_76cdafa1d957ca06');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value ADD CONSTRAINT fk_76cdafa13b69a9af FOREIGN KEY (variant_id) REFERENCES sylius_product_variant (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value ADD CONSTRAINT fk_76cdafa1d957ca06 FOREIGN KEY (option_value_id) REFERENCES sylius_product_option_value (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Sylius\Bundle\CoreBundle\Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20240927155535 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Remove cascade onDelete from sylius_product_variant_option_value table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value DROP FOREIGN KEY FK_76CDAFA13B69A9AF');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value DROP FOREIGN KEY FK_76CDAFA1D957CA06');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value ADD CONSTRAINT FK_76CDAFA13B69A9AF FOREIGN KEY (variant_id) REFERENCES sylius_product_variant (id)');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value ADD CONSTRAINT FK_76CDAFA1D957CA06 FOREIGN KEY (option_value_id) REFERENCES sylius_product_option_value (id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value DROP FOREIGN KEY FK_76CDAFA13B69A9AF');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value DROP FOREIGN KEY FK_76CDAFA1D957CA06');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value ADD CONSTRAINT FK_76CDAFA13B69A9AF FOREIGN KEY (variant_id) REFERENCES sylius_product_variant (id) ON UPDATE NO ACTION ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE sylius_product_variant_option_value ADD CONSTRAINT FK_76CDAFA1D957CA06 FOREIGN KEY (option_value_id) REFERENCES sylius_product_option_value (id) ON UPDATE NO ACTION ON DELETE CASCADE');
|
||||
}
|
||||
}
|
||||
|
|
@ -119,6 +119,11 @@
|
|||
<tag name="kernel.event_listener" event="sylius.product.pre_delete" method="protectFromRemovingProductInUseByPromotionRule" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.listener.product_option_value" class="Sylius\Bundle\CoreBundle\EventListener\ProductOptionValueDeletionListener">
|
||||
<argument type="service" id="sylius.repository.product_variant" />
|
||||
<tag name="doctrine.orm.entity_listener" event="preRemove" entity="%sylius.model.product_option_value.class%" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\CoreBundle\EventListener\PostgreSQLDefaultSchemaListener">
|
||||
<tag name="doctrine.event_listener" event="postGenerateSchema" method="postGenerateSchema" lazy="true" />
|
||||
</service>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\CoreBundle\EventListener;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Component\Core\Exception\ResourceDeleteException;
|
||||
use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface;
|
||||
use Sylius\Component\Product\Model\ProductOptionValueInterface;
|
||||
|
||||
final class ProductOptionValueDeletionListenerSpec extends ObjectBehavior
|
||||
{
|
||||
function let(ProductVariantRepositoryInterface $productVariantRepository): void
|
||||
{
|
||||
$this->beConstructedWith($productVariantRepository);
|
||||
}
|
||||
|
||||
function it_throws_resource_delete_exception_if_product_variants_exist_for_option_value(
|
||||
ProductVariantRepositoryInterface $productVariantRepository,
|
||||
ProductOptionValueInterface $optionValue,
|
||||
): void {
|
||||
$optionValue->getId()->willReturn(1);
|
||||
$productVariantRepository->countByProductOptionValueId(1)->willReturn(1);
|
||||
|
||||
$this->shouldThrow(ResourceDeleteException::class)->during('preRemove', [$optionValue]);
|
||||
}
|
||||
|
||||
function it_does_nothing_if_no_product_variants_exist_for_option_value(
|
||||
ProductVariantRepositoryInterface $productVariantRepository,
|
||||
ProductOptionValueInterface $optionValue,
|
||||
): void {
|
||||
$optionValue->getId()->willReturn(1);
|
||||
$productVariantRepository->countByProductOptionValueId(1)->willReturn(0);
|
||||
|
||||
$this->preRemove($optionValue)->shouldReturn(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,10 +44,10 @@
|
|||
|
||||
<join-table name="sylius_product_variant_option_value">
|
||||
<join-columns>
|
||||
<join-column name="variant_id" referenced-column-name="id" unique="false" nullable="false" on-delete="CASCADE" />
|
||||
<join-column name="variant_id" referenced-column-name="id" unique="false" nullable="false" />
|
||||
</join-columns>
|
||||
<inverse-join-columns>
|
||||
<join-column name="option_value_id" referenced-column-name="id" unique="false" nullable="false" on-delete="CASCADE" />
|
||||
<join-column name="option_value_id" referenced-column-name="id" unique="false" nullable="false" />
|
||||
</inverse-join-columns>
|
||||
</join-table>
|
||||
</many-to-many>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Component\Core\Exception;
|
||||
|
||||
class ResourceDeleteException extends \RuntimeException
|
||||
{
|
||||
public function __construct(
|
||||
private string $resourceName,
|
||||
string $message = '',
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null,
|
||||
) {
|
||||
if (empty($message)) {
|
||||
$message = sprintf('Cannot delete, the %s is in use.', $resourceName);
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
public function getResourceName(): string
|
||||
{
|
||||
return $this->resourceName;
|
||||
}
|
||||
}
|
||||
|
|
@ -34,4 +34,6 @@ interface ProductVariantRepositoryInterface extends BaseProductVariantRepository
|
|||
string $locale,
|
||||
CatalogPromotionInterface $catalogPromotion,
|
||||
): QueryBuilder;
|
||||
|
||||
public function countByProductOptionValueId(mixed $id): int;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue