[Core] Channel pricings validation + cascade deletion

This commit is contained in:
Mateusz Zalewski 2016-11-23 15:20:40 +01:00 committed by Łukasz
parent 6691f5c2ac
commit c5908d44d9
12 changed files with 201 additions and 10 deletions

View file

@ -0,0 +1,40 @@
<?php
namespace Sylius\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20161123153400 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE sylius_channel_pricing DROP FOREIGN KEY FK_7801820C72F5A1AA');
$this->addSql('ALTER TABLE sylius_channel_pricing DROP FOREIGN KEY FK_7801820CA80EF684');
$this->addSql('ALTER TABLE sylius_channel_pricing ADD CONSTRAINT FK_7801820C72F5A1AA FOREIGN KEY (channel_id) REFERENCES sylius_channel (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE sylius_channel_pricing ADD CONSTRAINT FK_7801820CA80EF684 FOREIGN KEY (product_variant_id) REFERENCES sylius_product_variant (id) ON DELETE CASCADE');
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE sylius_channel_pricing DROP FOREIGN KEY FK_7801820CA80EF684');
$this->addSql('ALTER TABLE sylius_channel_pricing DROP FOREIGN KEY FK_7801820C72F5A1AA');
$this->addSql('ALTER TABLE sylius_channel_pricing ADD CONSTRAINT FK_7801820CA80EF684 FOREIGN KEY (product_variant_id) REFERENCES sylius_product_variant (id)');
$this->addSql('ALTER TABLE sylius_channel_pricing ADD CONSTRAINT FK_7801820C72F5A1AA FOREIGN KEY (channel_id) REFERENCES sylius_channel (id)');
}
}

View file

@ -60,7 +60,7 @@ Feature: Products validation
Then I should be notified that name is required
And product with code "BOARD_DICE_BREWING" should not be added
@ui @todo
@ui
Scenario: Adding a new simple product without specifying its price for every channel
Given the store operates on another channel named "Web-GB"
When I want to create a new simple product
@ -68,7 +68,7 @@ Feature: Products validation
And I set its price to $10.00 for "United States" channel
And I name it "Dice Brewing" in "English (United States)"
And I try to add it
Then I should be notified that price must be set for every channel
Then I should be notified that price must be defined for every channel
And product with code "BOARD_DICE_BREWING" should not be added
@ui

View file

@ -9,13 +9,14 @@ Feature: Removing a product's price after channel deletion
And the store has currency "GBP" with exchange rate 0.7
And the store operates on a channel named "Web-US" in "USD" currency
And the store operates on another channel named "Web-GB" in "GBP" currency
And the store has a product "Dice Brewing" priced at "$10.00" for "Web-US" channel and "£5.00" for "Web-GB" channel
And the store has a product "Dice Brewing" priced at "$10.00" in "Web-US" channel
And this product is also priced at "£5.00" in "Web-GB" channel
And I am logged in as an administrator
@ui @todo
@ui
Scenario: Removing a product's price after corresponding channel deletion
When I delete channel "Web-GB"
Then product "Dice Brewing" should have price "$10.00" for channel "Web-US"
When channel "Web-GB" has been deleted
Then product "Dice Brewing" should be priced at $10.00 for channel "Web-US"
And this product should no longer have price for channel "Web=GB"

View file

@ -155,6 +155,14 @@ final class ChannelContext implements Context
$this->changeChannelState($channel, false);
}
/**
* @When channel :channel has been deleted
*/
public function iChannelHasBeenDeleted(ChannelInterface $channel)
{
$this->channelRepository->remove($channel);
}
/**
* @Given /^(its) default tax zone is (zone "([^"]+)")$/
*/

View file

@ -12,6 +12,7 @@
namespace Sylius\Behat\Context\Ui\Admin;
use Behat\Behat\Context\Context;
use Behat\Mink\Exception\ElementNotFoundException;
use Sylius\Behat\NotificationType;
use Sylius\Behat\Page\Admin\Crud\CreatePageInterface;
use Sylius\Behat\Page\Admin\Crud\UpdatePageInterface;
@ -910,6 +911,14 @@ final class ManagingProductsContext implements Context
$this->assertValidationMessage('code', 'Product code must be unique.');
}
/**
* @Then I should be notified that price must be defined for every channel
*/
public function iShouldBeNotifiedThatPriceMustBeDefinedForEveryChannel()
{
$this->assertValidationMessage('channel_pricings', 'You must define price for every channel.');
}
/**
* @Then they should have order like :firstProductName, :secondProductName and :thirdProductName
*/
@ -958,8 +967,8 @@ final class ManagingProductsContext implements Context
}
/**
* @Given /^(it|this product) should be priced at (?:|£|\$)([^"]+) for channel "([^"]+)"$/
* @Given /^(product "[^"]+") should be priced at (?:€|£|\$)([^"]+) for channel "([^"]+)"$/
* @Then /^(it|this product) should be priced at (?:|£|\$)([^"]+) for channel "([^"]+)"$/
* @Then /^(product "[^"]+") should be priced at (?:€|£|\$)([^"]+) for channel "([^"]+)"$/
*/
public function itShouldBePricedAtForChannel(ProductInterface $product, $price, $channelName)
{
@ -971,6 +980,24 @@ final class ManagingProductsContext implements Context
);
}
/**
* @Then /^(this product) should no longer have price for channel "([^"]+)"$/
*/
public function thisProductShouldNoLongerHavePriceForChannel(ProductInterface $product, $channelName)
{
$this->updateSimpleProductPage->open(['id' => $product->getId()]);
try {
$this->updateSimpleProductPage->getPriceForChannel($channelName);
} catch (ElementNotFoundException $exception) {
return;
}
throw new \Exception(
sprintf('Product "%s" should not have price defined for channel "%s".', $product->getName(), $channelName)
);
}
/**
* @param string $element
* @param string $value

View file

@ -226,6 +226,7 @@ class CreateSimpleProductPage extends BaseCreatePage implements CreateSimpleProd
'attribute_value' => '.attribute .label:contains("%attribute%") ~ input',
'attributes_choice' => '#sylius_product_attribute_choice',
'calculator' => '#sylius_calculator_container',
'channel_pricings' => '#sylius_product_variant_channelPricings',
'code' => '#sylius_product_code',
'form' => 'form[name="sylius_product"]',
'images' => '#sylius_product_images',

View file

@ -91,6 +91,7 @@ class ProductVariantTypeExtension extends AbstractTypeExtension
'label' => 'sylius.form.variant.price',
'allow_add' => false,
'allow_delete' => false,
'error_bubbling' => false,
])
->addEventSubscriber($this->channelPricingFormSubscriber)
;

View file

@ -24,10 +24,10 @@
<field name="price" column="price" type="integer"/>
<many-to-one field="productVariant" target-entity="Sylius\Component\Product\Model\ProductVariantInterface" inversed-by="channelPricings">
<join-column name="product_variant_id" referenced-column-name="id" nullable="false" />
<join-column name="product_variant_id" referenced-column-name="id" nullable="false" on-delete="CASCADE" />
</many-to-one>
<many-to-one field="channel" target-entity="Sylius\Component\Channel\Model\ChannelInterface">
<join-column name="channel_id" referenced-column-name="id" nullable="false" />
<join-column name="channel_id" referenced-column-name="id" nullable="false" on-delete="CASCADE" />
</many-to-one>
</mapped-superclass>

View file

@ -33,5 +33,9 @@
<argument type="service" id="sylius.availability_checker" />
<tag name="validator.constraint_validator" alias="sylius_cart_item_availability" />
</service>
<service id="sylius.validator.has_all_prices_defined" class="Sylius\Bundle\CoreBundle\Validator\Constraints\HasAllPricesDefinedValidator">
<argument type="service" id="sylius.repository.channel" />
<tag name="validator.constraint_validator" alias="sylius_has_all_prices_defined" />
</service>
</services>
</container>

View file

@ -52,5 +52,8 @@
<option name="groups">sylius</option>
</constraint>
</property>
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\HasAllPricesDefined">
<option name="groups">sylius</option>
</constraint>
</class>
</constraint-mapping>

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\CoreBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @author Mateusz Zalewski <mateusz.zalewski@lakion.com>
*/
final class HasAllPricesDefined extends Constraint
{
/**
* @var string
*/
private $message = 'You must define price for every channel.';
/**
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* {@inheritdoc}
*/
public function validatedBy()
{
return 'sylius_has_all_prices_defined';
}
/**
* {@inheritdoc}
*/
public function getTargets()
{
return Constraint::CLASS_CONSTRAINT;
}
}

View file

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\CoreBundle\Validator\Constraints;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Sylius\Component\Core\Model\ChannelPricingInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Webmozart\Assert\Assert;
/**
* @author Mateusz Zalewski <mateusz.zalewski@lakion.com>
*/
final class HasAllPricesDefinedValidator extends ConstraintValidator
{
/**
* @var ChannelRepositoryInterface
*/
private $channelRepository;
/**
* @param ChannelRepositoryInterface $channelRepository
*/
public function __construct(ChannelRepositoryInterface $channelRepository)
{
$this->channelRepository = $channelRepository;
}
/**
* {@inheritdoc}
*/
public function validate($productVariant, Constraint $constraint)
{
Assert::isInstanceOf($constraint, HasAllPricesDefined::class);
$enabledChannels = $this->channelRepository->findBy(['enabled' => true]);
foreach ($enabledChannels as $channel) {
/** @var ChannelPricingInterface $channelPricing */
$channelPricing = $productVariant->getChannelPricingForChannel($channel);
if (null === $channelPricing || null === $channelPricing->getPrice()) {
$this->context->addViolationAt('channelPricings', $constraint->getMessage());
return;
}
}
}
}