mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 09:02:12 +00:00
Merge pull request #6713 from lchrusciel/product-variant-generation
[Product] Product variant generation
This commit is contained in:
commit
13c74f2ae2
29 changed files with 750 additions and 110 deletions
|
|
@ -0,0 +1,47 @@
|
|||
@managing_product_variants
|
||||
Feature: Generating product variants
|
||||
In order to sell different variations of a single product
|
||||
As an Administrator
|
||||
I want to generate all possible product variants
|
||||
|
||||
Background:
|
||||
Given the store is available in "English (United States)"
|
||||
And the store has a "Wyborowa Vodka" configurable product
|
||||
And this product has option "Taste" with values "Orange" and "Melon"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
Scenario: Generating a product variant for product without variants
|
||||
Given I want to generate new variants for this product
|
||||
When I specify that the 1st variant is identified by "WYBOROWA_ORANGE" code and costs "$90"
|
||||
And I specify that the 2nd variant is identified by "WYBOROWA_MELON" code and costs "$95"
|
||||
And I generate it
|
||||
Then I should be notified that it has been successfully generated
|
||||
And I should see 2 variants in the list
|
||||
|
||||
@ui
|
||||
Scenario: Generating the rest of product variants for product with at least one
|
||||
Given this product is available in "Melon" taste priced at "$95.00"
|
||||
And I want to generate new variants for this product
|
||||
When I specify that the 2nd variant is identified by "WYBOROWA_ORANGE" code and costs "$90"
|
||||
And I generate it
|
||||
Then I should be notified that it has been successfully generated
|
||||
And I should see 2 variants in the list
|
||||
|
||||
@ui
|
||||
Scenario: Generating the rest of product variants for product with at least one
|
||||
Given this product is available in "Orange" taste priced at "$90.00"
|
||||
And I want to generate new variants for this product
|
||||
When I specify that the 2nd variant is identified by "WYBOROWA_MELON" code and costs "$95"
|
||||
And I generate it
|
||||
Then I should be notified that it has been successfully generated
|
||||
And I should see 2 variants in the list
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Generating only a part of product variants
|
||||
Given I want to generate new variants for this product
|
||||
When I specify that the 1st variant is identified by "WYBOROWA_ORANGE" code and costs "$90"
|
||||
And I remove 2nd variant from the list
|
||||
And I generate it
|
||||
Then I should be notified that it has been successfully generated
|
||||
And I should see 1 variants in the list
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
@managing_product_variants
|
||||
Feature: Generating product variant generation
|
||||
In order to avoid making mistakes when generating variants
|
||||
As an Administrator
|
||||
I want to be prevented from generating it without specifying required fields
|
||||
|
||||
Background:
|
||||
Given the store is available in "English (United States)"
|
||||
And the store has a "Wyborowa Vodka" configurable product
|
||||
And this product has option "Taste" with values "Orange" and "Melon"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
Scenario: Generating a product variant without price
|
||||
Given I want to generate new variants for this product
|
||||
When I specify that the 1st variant is identified by "WYBOROWA_ORANGE" code
|
||||
And I try to generate it
|
||||
Then I should be notified that price is required for the 1st variant
|
||||
And I should see 0 variants in the list
|
||||
|
||||
@ui
|
||||
Scenario: Generating a product variant without code
|
||||
Given I want to generate new variants for this product
|
||||
When I specify that the 1st variant costs "$90"
|
||||
And I try to generate it
|
||||
Then I should be notified that code is required for the 1st variant
|
||||
And I should see 0 variants in the list
|
||||
|
|
@ -14,6 +14,7 @@ namespace Sylius\Behat\Context\Ui\Admin;
|
|||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\NotificationType;
|
||||
use Sylius\Behat\Page\Admin\ProductVariant\CreatePageInterface;
|
||||
use Sylius\Behat\Page\Admin\ProductVariant\GeneratePageInterface;
|
||||
use Sylius\Behat\Page\Admin\ProductVariant\IndexPageInterface;
|
||||
use Sylius\Behat\Page\Admin\ProductVariant\UpdatePageInterface;
|
||||
use Sylius\Behat\Service\NotificationCheckerInterface;
|
||||
|
|
@ -56,6 +57,11 @@ final class ManagingProductVariantsContext implements Context
|
|||
*/
|
||||
private $updatePage;
|
||||
|
||||
/**
|
||||
* @var GeneratePageInterface
|
||||
*/
|
||||
private $generatePage;
|
||||
|
||||
/**
|
||||
* @var CurrentPageResolverInterface
|
||||
*/
|
||||
|
|
@ -72,6 +78,7 @@ final class ManagingProductVariantsContext implements Context
|
|||
* @param CreatePageInterface $createPage
|
||||
* @param IndexPageInterface $indexPage
|
||||
* @param UpdatePageInterface $updatePage
|
||||
* @param GeneratePageInterface $generatePage
|
||||
* @param CurrentPageResolverInterface $currentPageResolver
|
||||
* @param NotificationCheckerInterface $notificationChecker
|
||||
*/
|
||||
|
|
@ -81,6 +88,7 @@ final class ManagingProductVariantsContext implements Context
|
|||
CreatePageInterface $createPage,
|
||||
IndexPageInterface $indexPage,
|
||||
UpdatePageInterface $updatePage,
|
||||
GeneratePageInterface $generatePage,
|
||||
CurrentPageResolverInterface $currentPageResolver,
|
||||
NotificationCheckerInterface $notificationChecker
|
||||
) {
|
||||
|
|
@ -89,6 +97,7 @@ final class ManagingProductVariantsContext implements Context
|
|||
$this->createPage = $createPage;
|
||||
$this->indexPage = $indexPage;
|
||||
$this->updatePage = $updatePage;
|
||||
$this->generatePage = $generatePage;
|
||||
$this->currentPageResolver = $currentPageResolver;
|
||||
$this->notificationChecker = $notificationChecker;
|
||||
}
|
||||
|
|
@ -209,7 +218,7 @@ final class ManagingProductVariantsContext implements Context
|
|||
* @Then I should see :numberOfProductVariants variants in the list
|
||||
* @Then I should see :numberOfProductVariants variant in the list
|
||||
*/
|
||||
public function iShouldSeeProductsInTheList($numberOfProductVariants)
|
||||
public function iShouldSeeProductVariantsInTheList($numberOfProductVariants)
|
||||
{
|
||||
$foundRows = $this->indexPage->countItems();
|
||||
|
||||
|
|
@ -291,6 +300,22 @@ final class ManagingProductVariantsContext implements Context
|
|||
$this->assertValidationMessage('price', 'Please enter the price.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^I should be notified that price is required for the (\d)(?:st|nd|rd|th) variant$/
|
||||
*/
|
||||
public function iShouldBeNotifiedThatPriceIsRequiredForVariant($position)
|
||||
{
|
||||
Assert::same($this->generatePage->getValidationMessage('price', $position), 'Please enter the price.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^I should be notified that code is required for the (\d)(?:st|nd|rd|th) variant$/
|
||||
*/
|
||||
public function iShouldBeNotifiedThatCodeIsRequiredForVariant($position)
|
||||
{
|
||||
Assert::same($this->generatePage->getValidationMessage('code', $position), 'Please enter variant code.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I save my changes
|
||||
* @When I try to save my changes
|
||||
|
|
@ -467,6 +492,64 @@ final class ManagingProductVariantsContext implements Context
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^I want to generate new variants for (this product)$/
|
||||
*/
|
||||
public function iWantToGenerateNewVariantsForThisProduct(ProductInterface $product)
|
||||
{
|
||||
$this->generatePage->open(['productId' => $product->getId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I generate it
|
||||
* @When I try to generate it
|
||||
*/
|
||||
public function iClickGenerate()
|
||||
{
|
||||
$this->generatePage->generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I specify that the (\d)(?:st|nd|rd|th) variant is identified by ("[^"]+") code and costs "(?:€|£|\$)([^"]+)"$/
|
||||
*/
|
||||
public function iSpecifyThereAreVariantsIdentifiedByCodeWithCost($nthVariant, $code, $price)
|
||||
{
|
||||
$this->generatePage->nameCode($nthVariant - 1, $code);
|
||||
$this->generatePage->specifyPrice($nthVariant - 1, $price);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I specify that the (\d)(?:st|nd|rd|th) variant is identified by ("[^"]+") code$/
|
||||
*/
|
||||
public function iSpecifyThereAreVariantsIdentifiedByCode($nthVariant, $code)
|
||||
{
|
||||
$this->generatePage->nameCode($nthVariant, $code - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I specify that the (\d)(?:st|nd|rd|th) variant costs "(?:€|£|\$)([^"]+)"$/
|
||||
*/
|
||||
public function iSpecifyThereAreVariantsWithCost($nthVariant, $price)
|
||||
{
|
||||
$this->generatePage->specifyPrice($nthVariant, $price - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I remove (\d)(?:st|nd|rd|th) variant from the list$/
|
||||
*/
|
||||
public function iRemoveVariantFromTheList($nthVariant)
|
||||
{
|
||||
$this->generatePage->removeVariant($nthVariant - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it has been successfully generated
|
||||
*/
|
||||
public function iShouldBeNotifiedThatItHasBeenSuccessfullyGenerated()
|
||||
{
|
||||
$this->notificationChecker->checkNotification('Success Product variants have been successfully generated.', NotificationType::success());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $element
|
||||
* @param $message
|
||||
|
|
|
|||
104
src/Sylius/Behat/Page/Admin/ProductVariant/GeneratePage.php
Normal file
104
src/Sylius/Behat/Page/Admin/ProductVariant/GeneratePage.php
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<?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\Behat\Page\Admin\ProductVariant;
|
||||
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Behat\Mink\Exception\ElementNotFoundException;
|
||||
use Sylius\Behat\Page\SymfonyPage;
|
||||
|
||||
/**
|
||||
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
|
||||
*/
|
||||
class GeneratePage extends SymfonyPage implements GeneratePageInterface
|
||||
{
|
||||
public function generate()
|
||||
{
|
||||
$this->getDocument()->pressButton('Generate');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function specifyPrice($nth, $price)
|
||||
{
|
||||
$this->getDocument()->fillField(sprintf('sylius_product_generate_variants_variants_%s_price', $nth), $price);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function nameCode($nth, $code)
|
||||
{
|
||||
$this->getDocument()->fillField(sprintf('sylius_product_generate_variants_variants_%s_code', $nth), $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeVariant($nth)
|
||||
{
|
||||
$item = $this->getDocument()->find('css', sprintf('div[data-form-collection-index="%s"]', $nth));
|
||||
|
||||
$item->clickLink('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteName()
|
||||
{
|
||||
return 'sylius_admin_product_variant_generate';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws ElementNotFoundException
|
||||
*/
|
||||
public function getValidationMessage($element, $position)
|
||||
{
|
||||
$foundElement = $this->getElement($element, ['%position%' => $position]);
|
||||
$validatedField = $this->getValidatedField($foundElement);
|
||||
if (null === $validatedField) {
|
||||
throw new ElementNotFoundException($this->getSession(), 'Element', 'css', $foundElement);
|
||||
}
|
||||
|
||||
return $validatedField->find('css', '.sylius-validation-error')->getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDefinedElements()
|
||||
{
|
||||
return array_merge(parent::getDefinedElements(), [
|
||||
'code' => '#sylius_product_generate_variants_variants_%position%_code',
|
||||
'price' => '#sylius_product_generate_variants_variants_%position%_price',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NodeElement $element
|
||||
*
|
||||
* @return NodeElement
|
||||
*
|
||||
* @throws ElementNotFoundException
|
||||
*/
|
||||
private function getValidatedField(NodeElement $element)
|
||||
{
|
||||
while (null !== $element && !$element->hasClass('field')) {
|
||||
$element = $element->getParent();
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?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\Behat\Page\Admin\ProductVariant;
|
||||
|
||||
use Sylius\Behat\Page\SymfonyPageInterface;
|
||||
|
||||
/**
|
||||
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
|
||||
*/
|
||||
interface GeneratePageInterface extends SymfonyPageInterface
|
||||
{
|
||||
public function generate();
|
||||
|
||||
/**
|
||||
* @param int $nth
|
||||
* @param int $price
|
||||
*/
|
||||
public function specifyPrice($nth, $price);
|
||||
|
||||
/**
|
||||
* @param int $nth
|
||||
* @param string $code
|
||||
*/
|
||||
public function nameCode($nth, $code);
|
||||
|
||||
/**
|
||||
* @param int $nth
|
||||
*/
|
||||
public function removeVariant($nth);
|
||||
|
||||
/**
|
||||
* @param string $element
|
||||
* @param int $position
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValidationMessage($element, $position);
|
||||
}
|
||||
|
|
@ -157,6 +157,7 @@
|
|||
<argument type="service" id="sylius.behat.page.admin.product_variant.create" />
|
||||
<argument type="service" id="sylius.behat.page.admin.product_variant.index" />
|
||||
<argument type="service" id="sylius.behat.page.admin.product_variant.update" />
|
||||
<argument type="service" id="sylius.behat.page.admin.product_variant.generate" />
|
||||
<argument type="service" id="sylius.behat.current_page_resolver" />
|
||||
<argument type="service" id="sylius.behat.notification_checker" />
|
||||
<tag name="fob.context_service" />
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
<parameters>
|
||||
<parameter key="sylius.behat.page.admin.product_variant.create.class">Sylius\Behat\Page\Admin\ProductVariant\CreatePage</parameter>
|
||||
<parameter key="sylius.behat.page.admin.product_variant.generate.class">Sylius\Behat\Page\Admin\ProductVariant\GeneratePage</parameter>
|
||||
<parameter key="sylius.behat.page.admin.product_variant.index.class">Sylius\Behat\Page\Admin\ProductVariant\IndexPage</parameter>
|
||||
<parameter key="sylius.behat.page.admin.product_variant.update.class">Sylius\Behat\Page\Admin\ProductVariant\UpdatePage</parameter>
|
||||
</parameters>
|
||||
|
||||
|
|
@ -21,7 +23,10 @@
|
|||
<service id="sylius.behat.page.admin.product_variant.create" class="%sylius.behat.page.admin.product_variant.create.class%" parent="sylius.behat.page.admin.crud.create" public="false">
|
||||
<argument type="string">product_variant</argument>
|
||||
</service>
|
||||
<service id="sylius.behat.page.admin.product_variant.index" class="Sylius\Behat\Page\Admin\ProductVariant\IndexPage" parent="sylius.behat.page.admin.crud.index" public="false">
|
||||
<service id="sylius.behat.page.admin.product_variant.generate" class="%sylius.behat.page.admin.product_variant.generate.class%" parent="sylius.behat.symfony_page" public="false">
|
||||
<argument type="string">product_variant</argument>
|
||||
</service>
|
||||
<service id="sylius.behat.page.admin.product_variant.index" class="%sylius.behat.page.admin.product_variant.index.class%" parent="sylius.behat.page.admin.crud.index" public="false">
|
||||
<argument type="string">product_variant</argument>
|
||||
</service>
|
||||
<service id="sylius.behat.page.admin.product_variant.update" class="%sylius.behat.page.admin.product_variant.update.class%" parent="sylius.behat.page.admin.crud.update" public="false">
|
||||
|
|
|
|||
|
|
@ -73,3 +73,21 @@ sylius_admin_product_variant_delete:
|
|||
_sylius:
|
||||
redirect: referer
|
||||
permission: true
|
||||
|
||||
sylius_admin_product_variant_generate:
|
||||
path: /generate
|
||||
methods: [GET, POST]
|
||||
defaults:
|
||||
_controller: sylius.controller.product:updateAction
|
||||
_sylius:
|
||||
template: "@SyliusAdmin/ProductVariant/generate.html.twig"
|
||||
section: admin
|
||||
redirect:
|
||||
route: sylius_admin_product_variant_index
|
||||
parameters: { productId: $productId }
|
||||
form:
|
||||
type: sylius_product_generate_variants
|
||||
repository:
|
||||
method: find
|
||||
arguments: [$productId]
|
||||
flash: sylius.product_variant.generation_completed
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
{% import '@SyliusAdmin/Macro/breadcrumb.html.twig' as breadcrumb %}
|
||||
|
||||
{% set breadcrumbs = [
|
||||
{ label: 'sylius.ui.administration'|trans, url: path('sylius_admin_dashboard') },
|
||||
{ label: 'sylius.ui.products'|trans, url: path('sylius_admin_product_index') },
|
||||
{ label: product.name, url: path('sylius_admin_product_update', {'id': product.id}) },
|
||||
{ label: 'sylius.ui.variants'|trans, url: path('sylius_admin_product_variant_index', {'productId': product.id}) },
|
||||
{ label: 'sylius.ui.generate'|trans }
|
||||
]
|
||||
%}
|
||||
|
||||
{{ breadcrumb.crumble(breadcrumbs) }}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<div class="ui grid">
|
||||
<div class="column">
|
||||
{{- form_row(form.variants) -}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{% extends '@SyliusAdmin/layout.html.twig' %}
|
||||
|
||||
{% import 'SyliusUiBundle:Macro:headers.html.twig' as headers %}
|
||||
|
||||
{% set header = 'sylius.ui.generate_variants' %}
|
||||
|
||||
{% block title %}{{ parent() }} {{ header|trans }}{% endblock %}
|
||||
|
||||
{% form_theme form 'SyliusUiBundle:Form:theme.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
{{ headers.default(header|trans, 'random', 'sylius.ui.manage_products'|trans) }}
|
||||
|
||||
{% include '@SyliusAdmin/ProductVariant/Generate/_breadcrumb.html.twig' %}
|
||||
|
||||
{{ form_start(form, {'action': path('sylius_admin_product_variant_generate', {'productId': product.id}), 'attr': {'class': 'ui loadable form', 'novalidate': 'novalidate'}}) }}
|
||||
<div class="ui segment">
|
||||
{% include '@SyliusAdmin/ProductVariant/Generate/_form.html.twig' %}
|
||||
<div class="ui basic segment">
|
||||
<div class="ui buttons">
|
||||
<button class="ui labeled icon primary button" type="submit"><i class="random icon"></i>{{- 'sylius.ui.generate'|trans -}}</button>
|
||||
<div class="or" data-text="{{ 'sylius.ui.or'|trans }}"></div>
|
||||
{% include '@SyliusUi/Form/Buttons/_cancel.html.twig' with {'path': path(configuration.getRouteName('index'), {'productId': product.id})} %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_row(form._token) }}
|
||||
{{ form_end(form, {'render_rest': false}) }}
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?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\Form\Type\Product;
|
||||
|
||||
use Sylius\Bundle\ProductBundle\Form\Type\ProductVariantGenerationType as BaseProductVariantGenerationType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
|
||||
*/
|
||||
class ProductVariantGenerationType extends BaseProductVariantGenerationType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder
|
||||
->add('price', 'sylius_money', [
|
||||
'label' => 'sylius.form.variant.price',
|
||||
])
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ sylius_product:
|
|||
repository: Sylius\Bundle\ProductBundle\Doctrine\ORM\ProductVariantRepository
|
||||
form:
|
||||
default: Sylius\Bundle\CoreBundle\Form\Type\Product\ProductVariantType
|
||||
generation: Sylius\Bundle\CoreBundle\Form\Type\Product\ProductVariantGenerationType
|
||||
product_option:
|
||||
classes:
|
||||
repository: Sylius\Bundle\ProductBundle\Doctrine\ORM\ProductOptionRepository
|
||||
|
|
|
|||
|
|
@ -16,3 +16,5 @@ sylius:
|
|||
failed: Your payment was declined.
|
||||
unknown: Unknown payment error.
|
||||
out_of_stock: There is only %quantity% %name% in the stock.
|
||||
product_variant:
|
||||
generation_completed: Product variants have been successfully generated.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use Sylius\Bundle\ProductBundle\Form\Type\ProductOptionValueTranslationType;
|
|||
use Sylius\Bundle\ProductBundle\Form\Type\ProductOptionValueType;
|
||||
use Sylius\Bundle\ProductBundle\Form\Type\ProductTranslationType;
|
||||
use Sylius\Bundle\ProductBundle\Form\Type\ProductType;
|
||||
use Sylius\Bundle\ProductBundle\Form\Type\ProductGenerateVariantsType;
|
||||
use Sylius\Bundle\ProductBundle\Form\Type\ProductVariantGenerationType;
|
||||
use Sylius\Bundle\ProductBundle\Form\Type\ProductVariantType;
|
||||
use Sylius\Bundle\ResourceBundle\Controller\ResourceController;
|
||||
|
|
@ -103,7 +104,7 @@ final class Configuration implements ConfigurationInterface
|
|||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('default')->defaultValue(ProductType::class)->cannotBeEmpty()->end()
|
||||
->scalarNode('variant_generation')->defaultValue(ProductVariantGenerationType::class)->cannotBeEmpty()->end()
|
||||
->scalarNode('generate_variants')->defaultValue(ProductGenerateVariantsType::class)->cannotBeEmpty()->end()
|
||||
->scalarNode('choice')->defaultValue(ResourceChoiceType::class)->cannotBeEmpty()->end()
|
||||
->scalarNode('from_identifier')->defaultValue(ResourceFromIdentifierType::class)->cannotBeEmpty()->end()
|
||||
->end()
|
||||
|
|
@ -118,7 +119,7 @@ final class Configuration implements ConfigurationInterface
|
|||
->defaultValue(['sylius'])
|
||||
->cannotBeEmpty()
|
||||
->end()
|
||||
->arrayNode('variant_generation')
|
||||
->arrayNode('generate_variants')
|
||||
->prototype('scalar')->end()
|
||||
->defaultValue(['sylius'])
|
||||
->cannotBeEmpty()
|
||||
|
|
@ -175,6 +176,7 @@ final class Configuration implements ConfigurationInterface
|
|||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('default')->defaultValue(ProductVariantType::class)->cannotBeEmpty()->end()
|
||||
->scalarNode('generation')->defaultValue(ProductVariantGenerationType::class)->cannotBeEmpty()->end()
|
||||
->scalarNode('from_identifier')->defaultValue(ResourceFromIdentifierType::class)->cannotBeEmpty()->end()
|
||||
->end()
|
||||
->end()
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ final class SyliusProductExtension extends AbstractResourceExtension implements
|
|||
|
||||
$loader->load('services.xml');
|
||||
|
||||
$formDefinition = $container->getDefinition('sylius.form.type.product_variant_generation');
|
||||
$formDefinition = $container->getDefinition('sylius.form.type.product_generate_variants');
|
||||
$formDefinition->addArgument($container->getDefinition('sylius.form.event_subscriber.product_variant_generator'));
|
||||
|
||||
$container->getDefinition('sylius.form.type.product')->addArgument(new Reference('sylius.product_variant_resolver.default'));
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use Symfony\Component\Form\FormFactoryInterface;
|
|||
|
||||
/**
|
||||
* @author Paweł Jędrzejewski <pawel@sylius.org>
|
||||
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
|
||||
*/
|
||||
final class BuildProductVariantFormSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
|
|
@ -28,11 +29,18 @@ final class BuildProductVariantFormSubscriber implements EventSubscriberInterfac
|
|||
private $factory;
|
||||
|
||||
/**
|
||||
* @param FormFactoryInterface $factory
|
||||
* @var bool
|
||||
*/
|
||||
public function __construct(FormFactoryInterface $factory)
|
||||
private $disabled;
|
||||
|
||||
/**
|
||||
* @param FormFactoryInterface $factory
|
||||
* @param bool $disabled
|
||||
*/
|
||||
public function __construct(FormFactoryInterface $factory, $disabled = false)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->disabled = $disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -65,6 +73,7 @@ final class BuildProductVariantFormSubscriber implements EventSubscriberInterfac
|
|||
}
|
||||
|
||||
$form->add($this->factory->createNamed('optionValues', 'sylius_product_option_value_collection', $productVariant->getOptionValues(), [
|
||||
'disabled' => $this->disabled,
|
||||
'options' => $product->getOptions(),
|
||||
'auto_initialize' => false,
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
<?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\ProductBundle\Form\Type;
|
||||
|
||||
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
|
||||
*/
|
||||
class ProductGenerateVariantsType extends AbstractResourceType
|
||||
{
|
||||
/**
|
||||
* @var EventSubscriberInterface
|
||||
*/
|
||||
private $generateProductVariantsSubscriber;
|
||||
|
||||
/**
|
||||
* @param string $dataClass
|
||||
* @param string[] $validationGroups
|
||||
* @param EventSubscriberInterface $generateProductVariants
|
||||
*/
|
||||
public function __construct($dataClass, $validationGroups, EventSubscriberInterface $generateProductVariants)
|
||||
{
|
||||
parent::__construct($dataClass, $validationGroups);
|
||||
|
||||
$this->generateProductVariantsSubscriber = $generateProductVariants;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('variants', 'collection', [
|
||||
'type' => 'sylius_product_variant_generation',
|
||||
'allow_add' => true,
|
||||
'allow_delete' => true,
|
||||
'by_reference' => false,
|
||||
])
|
||||
->addEventSubscriber($this->generateProductVariantsSubscriber);
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'sylius_product_generate_variants';
|
||||
}
|
||||
}
|
||||
|
|
@ -11,8 +11,9 @@
|
|||
|
||||
namespace Sylius\Bundle\ProductBundle\Form\Type;
|
||||
|
||||
use Sylius\Bundle\ResourceBundle\Form\EventSubscriber\AddCodeFormSubscriber;
|
||||
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Sylius\Bundle\ProductBundle\Form\EventSubscriber\BuildProductVariantFormSubscriber;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -20,37 +21,20 @@ use Symfony\Component\Form\FormBuilderInterface;
|
|||
*/
|
||||
class ProductVariantGenerationType extends AbstractResourceType
|
||||
{
|
||||
/**
|
||||
* @var EventSubscriberInterface
|
||||
*/
|
||||
private $generateProductVariantsSubscriber;
|
||||
|
||||
/**
|
||||
* @param string $dataClass
|
||||
* @param string[] $validationGroups
|
||||
* @param EventSubscriberInterface $generateProductVariants
|
||||
*/
|
||||
public function __construct($dataClass, $validationGroups, EventSubscriberInterface $generateProductVariants)
|
||||
{
|
||||
parent::__construct($dataClass, $validationGroups);
|
||||
|
||||
$this->generateProductVariantsSubscriber = $generateProductVariants;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('variants', 'collection', [
|
||||
'type' => 'sylius_product_variant',
|
||||
'allow_add' => true,
|
||||
'allow_delete' => true,
|
||||
'by_reference' => false,
|
||||
->add('name', 'text', [
|
||||
'required' => false,
|
||||
'label' => 'sylius.form.variant.name',
|
||||
])
|
||||
->addEventSubscriber($this->generateProductVariantsSubscriber);
|
||||
->addEventSubscriber(new AddCodeFormSubscriber())
|
||||
;
|
||||
|
||||
$builder->addEventSubscriber(new BuildProductVariantFormSubscriber($builder->getFormFactory(), true));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -24,12 +24,15 @@
|
|||
</service>
|
||||
|
||||
<service id="sylius.validator.product_variant_combination" class="Sylius\Bundle\ProductBundle\Validator\ProductVariantCombinationValidator">
|
||||
<argument type="service" id="sylius.checker.product_variants_parity" />
|
||||
<tag name="validator.constraint_validator" alias="sylius.validator.product_variant_combination" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.generator.product_variant" class="Sylius\Component\Product\Generator\ProductVariantGenerator">
|
||||
<argument type="service" id="sylius.factory.product_variant" />
|
||||
<argument type="service" id="sylius.checker.product_variants_parity" />
|
||||
</service>
|
||||
<service id="sylius.checker.product_variants_parity" class="Sylius\Component\Product\Checker\ProductVariantsParityChecker" />
|
||||
<service id="sylius.generator.slug" class="Sylius\Component\Product\Generator\SlugGenerator" />
|
||||
|
||||
<service id="sylius.form.type.sylius_product_associations" class="Sylius\Bundle\ProductBundle\Form\Type\ProductAssociationsType">
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<class name="Sylius\Component\Product\Model\ProductVariant">
|
||||
<property name="code">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.option.code.not_blank</option>
|
||||
<option name="message">sylius.variant.code.not_blank</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
</property>
|
||||
|
|
|
|||
|
|
@ -13,10 +13,9 @@ sylius:
|
|||
slug:
|
||||
not_blank: Please enter product slug.
|
||||
variant:
|
||||
sku:
|
||||
not_blank: Please enter variant SKU.
|
||||
unique: Variant SKU must be unique.
|
||||
combination: Variant with this option set already exists.
|
||||
code:
|
||||
not_blank: Please enter variant code.
|
||||
option:
|
||||
code:
|
||||
not_blank: Please enter option code.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace Sylius\Bundle\ProductBundle\Validator;
|
||||
|
||||
use Sylius\Component\Product\Model\ProductInterface;
|
||||
use Sylius\Component\Product\Checker\ProductVariantsParityCheckerInterface;
|
||||
use Sylius\Component\Product\Model\ProductVariantInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
|
@ -22,6 +22,19 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
|||
*/
|
||||
class ProductVariantCombinationValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* @var ProductVariantsParityCheckerInterface
|
||||
*/
|
||||
private $variantsParityChecker;
|
||||
|
||||
/**
|
||||
* @param ProductVariantsParityCheckerInterface $variantsParityChecker
|
||||
*/
|
||||
public function __construct(ProductVariantsParityCheckerInterface $variantsParityChecker)
|
||||
{
|
||||
$this->variantsParityChecker = $variantsParityChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
@ -36,37 +49,8 @@ class ProductVariantCombinationValidator extends ConstraintValidator
|
|||
return;
|
||||
}
|
||||
|
||||
if ($this->matches($value, $product)) {
|
||||
if ($this->variantsParityChecker->checkParity($value, $product)) {
|
||||
$this->context->addViolation($constraint->message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ProductVariantInterface $variant
|
||||
* @param ProductInterface $variable
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function matches(ProductVariantInterface $variant, ProductInterface $variable)
|
||||
{
|
||||
foreach ($variable->getVariants() as $existingVariant) {
|
||||
if ($variant === $existingVariant || !$variant->getOptionValues()->count()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matches = true;
|
||||
|
||||
foreach ($variant->getOptionValues() as $optionValue) {
|
||||
if (!$existingVariant->hasOptionValue($optionValue)) {
|
||||
$matches = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($matches) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ final class BuildProductVariantFormSubscriberSpec extends ObjectBehavior
|
|||
);
|
||||
}
|
||||
|
||||
function it_adds_options_on_pre_set_data_event(
|
||||
function it_adds_options_on_pre_set_data_event_with_configurable_options(
|
||||
FormEvent $event,
|
||||
FormFactoryInterface $factory,
|
||||
FormInterface $form,
|
||||
|
|
@ -66,6 +66,43 @@ final class BuildProductVariantFormSubscriberSpec extends ObjectBehavior
|
|||
[
|
||||
'options' => [$options],
|
||||
'auto_initialize' => false,
|
||||
'disabled' => false,
|
||||
]
|
||||
)->willReturn($optionsForm);
|
||||
|
||||
$form->add($optionsForm)->shouldBeCalled();
|
||||
|
||||
$this->preSetData($event);
|
||||
}
|
||||
|
||||
function it_adds_options_on_pre_set_data_event_without_configurable_options(
|
||||
FormEvent $event,
|
||||
FormFactoryInterface $factory,
|
||||
FormInterface $form,
|
||||
FormInterface $optionsForm,
|
||||
ProductInterface $variable,
|
||||
ProductOptionInterface $options,
|
||||
ProductOptionValueInterface $optionValue,
|
||||
ProductVariantInterface $variant
|
||||
) {
|
||||
$this->beConstructedWith($factory, true);
|
||||
|
||||
$event->getForm()->willReturn($form);
|
||||
$event->getData()->willReturn($variant);
|
||||
|
||||
$variant->getProduct()->willReturn($variable);
|
||||
$variant->getOptionValues()->willReturn([$optionValue]);
|
||||
$variable->getOptions()->willReturn([$options]);
|
||||
$variable->hasOptions()->willReturn(true);
|
||||
|
||||
$factory->createNamed(
|
||||
'optionValues',
|
||||
'sylius_product_option_value_collection',
|
||||
[$optionValue],
|
||||
[
|
||||
'options' => [$options],
|
||||
'auto_initialize' => false,
|
||||
'disabled' => true,
|
||||
]
|
||||
)->willReturn($optionsForm);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@
|
|||
|
||||
namespace spec\Sylius\Bundle\ProductBundle\Validator;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Bundle\ProductBundle\Validator\Constraint\ProductVariantCombination;
|
||||
use Sylius\Bundle\ProductBundle\Validator\ProductVariantCombinationValidator;
|
||||
use Sylius\Component\Product\Checker\ProductVariantsParityCheckerInterface;
|
||||
use Sylius\Component\Product\Model\ProductOptionValueInterface;
|
||||
use Sylius\Component\Product\Model\ProductInterface;
|
||||
use Sylius\Component\Product\Model\ProductVariantInterface;
|
||||
|
|
@ -24,8 +24,9 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||
|
||||
final class ProductVariantCombinationValidatorSpec extends ObjectBehavior
|
||||
{
|
||||
function let(ExecutionContextInterface $context)
|
||||
function let(ExecutionContextInterface $context, ProductVariantsParityCheckerInterface $variantsParityChecker)
|
||||
{
|
||||
$this->beConstructedWith($variantsParityChecker);
|
||||
$this->initialize($context);
|
||||
}
|
||||
|
||||
|
|
@ -39,10 +40,33 @@ final class ProductVariantCombinationValidatorSpec extends ObjectBehavior
|
|||
$this->shouldImplement(ConstraintValidator::class);
|
||||
}
|
||||
|
||||
function it_does_not_add_violation_if_variable_does_not_have_options(
|
||||
function it_does_not_add_violation_if_product_does_not_have_options(
|
||||
ExecutionContextInterface $context,
|
||||
ProductInterface $product,
|
||||
ProductVariantInterface $variant
|
||||
ProductVariantInterface $variant,
|
||||
ProductVariantsParityCheckerInterface $variantsParityChecker
|
||||
) {
|
||||
$constraint = new ProductVariantCombination([
|
||||
'message' => 'Variant with given options already exists',
|
||||
]);
|
||||
|
||||
$variant->getProduct()->willReturn($product);
|
||||
|
||||
$product->hasVariants()->willReturn(true);
|
||||
$product->hasOptions()->willReturn(false);
|
||||
|
||||
$variantsParityChecker->checkParity($variant, $product)->willReturn(false);
|
||||
|
||||
$context->addViolation(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->validate($variant, $constraint);
|
||||
}
|
||||
|
||||
function it_does_not_add_violation_if_product_does_not_have_variants(
|
||||
ExecutionContextInterface $context,
|
||||
ProductInterface $product,
|
||||
ProductVariantInterface $variant,
|
||||
ProductVariantsParityCheckerInterface $variantsParityChecker
|
||||
) {
|
||||
$constraint = new ProductVariantCombination([
|
||||
'message' => 'Variant with given options already exists',
|
||||
|
|
@ -55,48 +79,27 @@ final class ProductVariantCombinationValidatorSpec extends ObjectBehavior
|
|||
|
||||
$context->addViolation(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->validate($variant, $constraint);
|
||||
}
|
||||
|
||||
function it_does_not_add_violation_if_product_does_not_have_variants(
|
||||
ExecutionContextInterface $context,
|
||||
ProductInterface $variable,
|
||||
ProductVariantInterface $variant
|
||||
) {
|
||||
$constraint = new ProductVariantCombination([
|
||||
'message' => 'Variant with given options already exists',
|
||||
]);
|
||||
|
||||
$variant->getProduct()->willReturn($variable);
|
||||
|
||||
$variable->hasVariants()->willReturn(true);
|
||||
$variable->hasOptions()->willReturn(false);
|
||||
|
||||
$context->addViolation(Argument::any())->shouldNotBeCalled();
|
||||
$variantsParityChecker->checkParity($variant, $product)->willReturn(false);
|
||||
|
||||
$this->validate($variant, $constraint);
|
||||
}
|
||||
|
||||
function it_adds_violation_if_variant_with_given_same_options_already_exists(
|
||||
ExecutionContextInterface $context,
|
||||
ProductInterface $variable,
|
||||
ProductOptionValueInterface $optionValue,
|
||||
ProductVariantInterface $existingVariant,
|
||||
ProductVariantInterface $variant
|
||||
ProductInterface $product,
|
||||
ProductVariantInterface $variant,
|
||||
ProductVariantsParityCheckerInterface $variantsParityChecker
|
||||
) {
|
||||
$constraint = new ProductVariantCombination([
|
||||
'message' => 'Variant with given options already exists',
|
||||
]);
|
||||
|
||||
$variant->getProduct()->willReturn($variable);
|
||||
$variant->getProduct()->willReturn($product);
|
||||
|
||||
$variant->getOptionValues()->willReturn(new ArrayCollection([$optionValue->getWrappedObject()]));
|
||||
$product->hasVariants()->willReturn(true);
|
||||
$product->hasOptions()->willReturn(true);
|
||||
|
||||
$existingVariant->hasOptionValue($optionValue)->willReturn(true);
|
||||
|
||||
$variable->hasVariants()->willReturn(true);
|
||||
$variable->hasOptions()->willReturn(true);
|
||||
$variable->getVariants()->willReturn([$existingVariant]);
|
||||
$variantsParityChecker->checkParity($variant, $product)->willReturn(true);
|
||||
|
||||
$context->addViolation('Variant with given options already exists', Argument::any())->shouldBeCalled();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
<?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\Component\Product\Checker;
|
||||
|
||||
use Sylius\Component\Product\Model\ProductInterface;
|
||||
use Sylius\Component\Product\Model\ProductVariantInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/**
|
||||
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
|
||||
*/
|
||||
final class ProductVariantsParityChecker implements ProductVariantsParityCheckerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkParity(ProductVariantInterface $variant, ProductInterface $product)
|
||||
{
|
||||
foreach ($product->getVariants() as $existingVariant) {
|
||||
// This check is require, because this function has to look for any other different variant with same option values set
|
||||
if ($variant === $existingVariant || count($variant->getOptionValues()) !== count($product->getOptions())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->matchOptions($variant, $existingVariant)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ProductVariantInterface $variant
|
||||
* @param ProductVariantInterface $existingVariant
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function matchOptions(ProductVariantInterface $variant, ProductVariantInterface $existingVariant)
|
||||
{
|
||||
foreach ($variant->getOptionValues() as $option) {
|
||||
if (!$existingVariant->hasOptionValue($option)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?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\Component\Product\Checker;
|
||||
|
||||
use Sylius\Component\Product\Model\ProductInterface;
|
||||
use Sylius\Component\Product\Model\ProductVariantInterface;
|
||||
|
||||
/**
|
||||
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
|
||||
*/
|
||||
interface ProductVariantsParityCheckerInterface
|
||||
{
|
||||
/**
|
||||
* @param ProductVariantInterface $variant
|
||||
* @param ProductInterface $product
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkParity(ProductVariantInterface $variant, ProductInterface $product);
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
namespace Sylius\Component\Product\Generator;
|
||||
|
||||
use Sylius\Component\Product\Checker\ProductVariantsParityCheckerInterface;
|
||||
use Sylius\Component\Product\Factory\ProductVariantFactoryInterface;
|
||||
use Sylius\Component\Product\Model\ProductInterface;
|
||||
use Sylius\Component\Product\Model\ProductVariantInterface;
|
||||
|
|
@ -18,13 +19,14 @@ use Webmozart\Assert\Assert;
|
|||
|
||||
/**
|
||||
* @author Paweł Jędrzejewski <pawel@sylius.org>
|
||||
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
|
||||
*/
|
||||
final class ProductVariantGenerator implements ProductVariantGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var ProductVariantFactoryInterface
|
||||
*/
|
||||
protected $productVariantFactory;
|
||||
private $productVariantFactory;
|
||||
|
||||
/**
|
||||
* @var CartesianSetBuilder
|
||||
|
|
@ -32,12 +34,21 @@ final class ProductVariantGenerator implements ProductVariantGeneratorInterface
|
|||
private $setBuilder;
|
||||
|
||||
/**
|
||||
* @param ProductVariantFactoryInterface $productVariantFactory
|
||||
* @var ProductVariantsParityCheckerInterface
|
||||
*/
|
||||
public function __construct(ProductVariantFactoryInterface $productVariantFactory)
|
||||
{
|
||||
private $variantsParityChecker;
|
||||
|
||||
/**
|
||||
* @param ProductVariantFactoryInterface $productVariantFactory
|
||||
* @param ProductVariantsParityCheckerInterface $variantsParityChecker
|
||||
*/
|
||||
public function __construct(
|
||||
ProductVariantFactoryInterface $productVariantFactory,
|
||||
ProductVariantsParityCheckerInterface $variantsParityChecker
|
||||
) {
|
||||
$this->productVariantFactory = $productVariantFactory;
|
||||
$this->setBuilder = new CartesianSetBuilder();
|
||||
$this->variantsParityChecker = $variantsParityChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -61,7 +72,10 @@ final class ProductVariantGenerator implements ProductVariantGeneratorInterface
|
|||
|
||||
foreach ($permutations as $permutation) {
|
||||
$variant = $this->createVariant($product, $optionMap, $permutation);
|
||||
$product->addVariant($variant);
|
||||
|
||||
if (!$this->variantsParityChecker->checkParity($variant, $product)) {
|
||||
$product->addVariant($variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace spec\Sylius\Component\Product\Generator;
|
|||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Component\Product\Checker\ProductVariantsParityCheckerInterface;
|
||||
use Sylius\Component\Product\Factory\ProductVariantFactoryInterface;
|
||||
use Sylius\Component\Product\Generator\ProductVariantGenerator;
|
||||
use Sylius\Component\Product\Generator\ProductVariantGeneratorInterface;
|
||||
|
|
@ -26,9 +27,11 @@ use Sylius\Component\Product\Model\ProductVariantInterface;
|
|||
*/
|
||||
final class ProductVariantGeneratorSpec extends ObjectBehavior
|
||||
{
|
||||
function let(ProductVariantFactoryInterface $productVariantFactory)
|
||||
{
|
||||
$this->beConstructedWith($productVariantFactory);
|
||||
function let(
|
||||
ProductVariantFactoryInterface $productVariantFactory,
|
||||
ProductVariantsParityCheckerInterface $variantsParityChecker
|
||||
) {
|
||||
$this->beConstructedWith($productVariantFactory, $variantsParityChecker);
|
||||
}
|
||||
|
||||
function it_is_initializable()
|
||||
|
|
@ -55,7 +58,8 @@ final class ProductVariantGeneratorSpec extends ObjectBehavior
|
|||
ProductOptionValueInterface $redColor,
|
||||
ProductOptionValueInterface $whiteColor,
|
||||
ProductVariantFactoryInterface $productVariantFactory,
|
||||
ProductVariantInterface $permutationVariant
|
||||
ProductVariantInterface $permutationVariant,
|
||||
ProductVariantsParityCheckerInterface $variantsParityChecker
|
||||
) {
|
||||
$productVariable->hasOptions()->willReturn(true);
|
||||
|
||||
|
|
@ -67,6 +71,8 @@ final class ProductVariantGeneratorSpec extends ObjectBehavior
|
|||
$whiteColor->getId()->willReturn('white2');
|
||||
$redColor->getId()->willReturn('red3');
|
||||
|
||||
$variantsParityChecker->checkParity($permutationVariant, $productVariable)->willReturn(false);
|
||||
|
||||
$productVariantFactory->createForProduct($productVariable)->willReturn($permutationVariant);
|
||||
|
||||
$permutationVariant->addOptionValue(Argument::type(ProductOptionValueInterface::class))->shouldBeCalled();
|
||||
|
|
@ -75,6 +81,36 @@ final class ProductVariantGeneratorSpec extends ObjectBehavior
|
|||
$this->generate($productVariable);
|
||||
}
|
||||
|
||||
function it_does_not_generate_variant_if_given_variant_exists(
|
||||
ProductInterface $productVariable,
|
||||
ProductOptionInterface $colorOption,
|
||||
ProductOptionValueInterface $blackColor,
|
||||
ProductOptionValueInterface $redColor,
|
||||
ProductOptionValueInterface $whiteColor,
|
||||
ProductVariantFactoryInterface $productVariantFactory,
|
||||
ProductVariantInterface $permutationVariant,
|
||||
ProductVariantsParityCheckerInterface $variantsParityChecker
|
||||
) {
|
||||
$productVariable->hasOptions()->willReturn(true);
|
||||
|
||||
$productVariable->getOptions()->willReturn([$colorOption]);
|
||||
|
||||
$colorOption->getValues()->willReturn([$blackColor, $whiteColor, $redColor]);
|
||||
|
||||
$blackColor->getId()->willReturn('black1');
|
||||
$whiteColor->getId()->willReturn('white2');
|
||||
$redColor->getId()->willReturn('red3');
|
||||
|
||||
$variantsParityChecker->checkParity($permutationVariant, $productVariable)->willReturn(true);
|
||||
|
||||
$productVariantFactory->createForProduct($productVariable)->willReturn($permutationVariant);
|
||||
|
||||
$permutationVariant->addOptionValue(Argument::type(ProductOptionValueInterface::class))->shouldBeCalled();
|
||||
$productVariable->addVariant($permutationVariant)->shouldNotBeCalled();
|
||||
|
||||
$this->generate($productVariable);
|
||||
}
|
||||
|
||||
function it_generates_variants_for_every_possible_permutation_of_an_objects_options_and_option_values(
|
||||
ProductInterface $productVariable,
|
||||
ProductOptionInterface $colorOption,
|
||||
|
|
@ -86,7 +122,8 @@ final class ProductVariantGeneratorSpec extends ObjectBehavior
|
|||
ProductOptionValueInterface $smallSize,
|
||||
ProductOptionValueInterface $whiteColor,
|
||||
ProductVariantFactoryInterface $productVariantFactory,
|
||||
ProductVariantInterface $permutationVariant
|
||||
ProductVariantInterface $permutationVariant,
|
||||
ProductVariantsParityCheckerInterface $variantsParityChecker
|
||||
) {
|
||||
$productVariable->hasOptions()->willReturn(true);
|
||||
|
||||
|
|
@ -102,6 +139,8 @@ final class ProductVariantGeneratorSpec extends ObjectBehavior
|
|||
$mediumSize->getId()->willReturn('medium5');
|
||||
$largeSize->getId()->willReturn('large6');
|
||||
|
||||
$variantsParityChecker->checkParity($permutationVariant, $productVariable)->willReturn(false);
|
||||
|
||||
$productVariantFactory->createForProduct($productVariable)->willReturn($permutationVariant);
|
||||
|
||||
$permutationVariant->addOptionValue(Argument::type(ProductOptionValueInterface::class))->shouldBeCalled();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue