[Admin] Added autocompletes to product images variant collection

This commit is contained in:
Jan Goralski 2017-03-13 18:35:50 +01:00
parent 0d75d45651
commit ead270d7bd
No known key found for this signature in database
GPG key ID: 48749B194497003F
25 changed files with 416 additions and 47 deletions

View file

@ -0,0 +1,37 @@
@managing_product_variants
Feature: Product variants autocomplete
In order to get hints when looking for named product variants
As an Administrator
I want to get product variants according to my specified phrase
Background:
Given the store operates on a single channel in "United States"
And the store has a "Snake" configurable product
And this product has "Ouroboros", "Boomslang" and "Bimini" variants
And I am logged in as an administrator
@api
Scenario: Getting a hint when looking for product variants
When I look for a variant with "i" in descriptor within the "Snake" product
Then I should see 1 product variant on the list
And I should see the product variant named "Bimini" on the list
@api
Scenario: Getting a hint when looking for product variants
When I look for a variant with "bo" in descriptor within the "Snake" product
Then I should see 2 product variants on the list
And I should see the product variants named "Ouroboros" and "Boomslang" on the list
@api
Scenario: Getting a hint when looking for product variants
Given the product "Snake" also has a "Python" variant with code "TIMOR"
When I look for a variant with "ti" in descriptor within the "Snake" product
Then I should see 1 product variant on the list
And I should see the product variant named "Python" on the list
@api
Scenario: Getting a hint when looking for product variants
Given the product "Snake" also has a nameless variant with code "CERBERUS"
When I look for a variant with "cer" in descriptor within the "Snake" product
Then I should see 1 product variant on the list
And I should see the product variant labeled "Snake (CERBERUS)" on the list

View file

@ -1,33 +1,33 @@
@managing_taxons @managing_taxons
Feature: Taxons autocomplete Feature: Taxons autocomplete
In order to getting hint when looking for taxons In order to get hints when looking for taxons
As an Administrator As an Administrator
I want to get taxon I want to get taxons according to my specified phrase
Background: Background:
Given the store classifies its products as "T-Shirts", "Watches", "Belts" and "Wallets" Given the store classifies its products as "T-Shirts", "Watches", "Belts" and "Wallets"
And I am logged in as an administrator And I am logged in as an administrator
@api @api
Scenario: Getting hint when looking for taxons Scenario: Getting a hint when looking for taxons
When I look for a taxon with "b" in name When I look for a taxon with "b" in name
Then I should see 1 taxons on the list Then I should see 1 taxons on the list
And I should see the taxon named "Belts" in the list And I should see the taxon named "Belts" in the list
@api @api
Scenario: Getting hint when looking for taxons Scenario: Getting a hint when looking for taxons
When I look for a taxon with "shi" in name When I look for a taxon with "shi" in name
Then I should see 1 taxons on the list Then I should see 1 taxons on the list
And I should see the taxon named "T-Shirts" in the list And I should see the taxon named "T-Shirts" in the list
@api @api
Scenario: Getting hint when looking for taxons Scenario: Getting a hint when looking for taxons
When I look for a taxon with "e" in name When I look for a taxon with "e" in name
Then I should see 3 taxons on the list Then I should see 3 taxons on the list
And I should see the taxon named "Belts", "Wallets" and "Watches" in the list And I should see the taxon named "Belts", "Wallets" and "Watches" in the list
@api @api
Scenario: Getting taxon from its code Scenario: Getting a taxon from its code
When I want to get taxon with "belts" code When I want to get taxon with "belts" code
Then I should see 1 taxons on the list Then I should see 1 taxons on the list
And I should see the taxon named "Belts" in the list And I should see the taxon named "Belts" in the list

View file

@ -0,0 +1,103 @@
<?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\Context\Api\Admin;
use Behat\Behat\Context\Context;
use Sylius\Component\Core\Model\ProductInterface;
use Symfony\Component\BrowserKit\Client;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Webmozart\Assert\Assert;
/**
* @author Jan Góralski <jan.goralski@lakion.com>
*/
final class ManagingProductVariantsContext implements Context
{
/**
* @var Client
*/
private $client;
/**
* @var SessionInterface
*/
private $session;
/**
* @param Client $client
* @param SessionInterface $session
*/
public function __construct(Client $client, SessionInterface $session)
{
$this->client = $client;
$this->session = $session;
}
/**
* @When I look for a variant with :phrase in descriptor within the :product product
*/
public function iLookForVariantWithDescriptorWithinProduct($phrase, ProductInterface $product)
{
$this->client->getCookieJar()->set(new Cookie($this->session->getName(), $this->session->getId()));
$this->client->request(
'GET',
'/admin/ajax/product-variants/search',
['phrase' => $phrase, 'productCode' => $product->getCode()],
[],
['ACCEPT' => 'application/json']
);
}
/**
* @Then /^I should see (\d+) product variants? on the list$/
*/
public function iShouldSeeProductVariantsInTheList($number)
{
Assert::eq(count($this->getJSONResponse()), $number);
}
/**
* @Then I should see the product variant named :firstName on the list
* @Then I should see the product variants named :firstName and :secondName on the list
* @Then I should see the product variants named :firstName, :secondName and :thirdName on the list
* @Then I should see the product variants named :firstName, :secondName, :thirdName and :fourthName on the list
*/
public function iShouldSeeTheProductVariantNamedAnd(...$names)
{
$itemsNames = array_map(function ($item) {
return strstr($item['descriptor'], ' ', true);
}, $this->getJSONResponse());
Assert::allOneOf($itemsNames, $names);
}
/**
* @Then I should see the product variant labeled :label on the list
*/
public function iShouldSeeTheProductVariantLabeledAs($label)
{
$itemsLabels = array_map(function ($item) {
return $item['descriptor'];
}, $this->getJSONResponse());
Assert::oneOf($label, $itemsLabels, 'Expected "%s" to be on the list, found: %s.');
}
/**
* @return mixed
*/
private function getJSONResponse()
{
return json_decode($this->client->getResponse()->getContent(), true);
}
}

View file

@ -86,7 +86,7 @@ final class ManagingTaxonsContext implements Context
{ {
$response = json_decode($this->client->getResponse()->getContent(), true); $response = json_decode($this->client->getResponse()->getContent(), true);
Assert::eq($number, count($response)); Assert::eq(count($response), $number);
} }
/** /**

View file

@ -334,6 +334,50 @@ final class ProductContext implements Context
); );
} }
/**
* @Given /^the (product "[^"]+") has(?:| a| an) "([^"]+)" variant$/
* @Given /^(this product) has(?:| a| an) "([^"]+)" variant$/
* @Given /^(this product) has "([^"]+)", "([^"]+)" and "([^"]+)" variants$/
*/
public function theProductHasVariants(ProductInterface $product, ...$variantNames)
{
$channel = $this->sharedStorage->get('channel');
foreach ($variantNames as $name) {
$this->createProductVariant(
$product,
$name,
0,
StringInflector::nameToUppercaseCode($name),
$channel
);
}
}
/**
* @Given /^the (product "[^"]+")(?:| also) has a nameless variant with code "([^"]+)"$/
* @Given /^(this product)(?:| also) has a nameless variant with code "([^"]+)"$/
* @Given /^(it)(?:| also) has a nameless variant with code "([^"]+)"$/
*/
public function theProductHasNamelessVariantWithCode(ProductInterface $product, $variantCode)
{
$channel = $this->sharedStorage->get('channel');
$this->createProductVariant($product, null, 0, $variantCode, $channel);
}
/**
* @Given /^the (product "[^"]+")(?:| also) has(?:| a| an) "([^"]+)" variant with code "([^"]+)"$/
* @Given /^(this product)(?:| also) has(?:| a| an) "([^"]+)" variant with code "([^"]+)"$/
* @Given /^(it)(?:| also) has(?:| a| an) "([^"]+)" variant with code "([^"]+)"$/
*/
public function theProductHasVariantWithCode(ProductInterface $product, $variantName, $variantCode)
{
$channel = $this->sharedStorage->get('channel');
$this->createProductVariant($product, $variantName, 0, $variantCode, $channel);
}
/** /**
* @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+") which does not require shipping$/ * @Given /^(this product) has "([^"]+)" variant priced at ("[^"]+") which does not require shipping$/
*/ */
@ -447,6 +491,8 @@ final class ProductContext implements Context
*/ */
public function itComesInTheFollowingVariations(ProductInterface $product, TableNode $table) public function itComesInTheFollowingVariations(ProductInterface $product, TableNode $table)
{ {
$channel = $this->sharedStorage->get('channel');
foreach ($table->getHash() as $variantHash) { foreach ($table->getHash() as $variantHash) {
/** @var ProductVariantInterface $variant */ /** @var ProductVariantInterface $variant */
$variant = $this->productVariantFactory->createNew(); $variant = $this->productVariantFactory->createNew();
@ -455,8 +501,9 @@ final class ProductContext implements Context
$variant->setCode(StringInflector::nameToUppercaseCode($variantHash['name'])); $variant->setCode(StringInflector::nameToUppercaseCode($variantHash['name']));
$variant->addChannelPricing($this->createChannelPricingForChannel( $variant->addChannelPricing($this->createChannelPricingForChannel(
$this->getPriceFromString(str_replace(['$', '€', '£'], '', $variantHash['price'])), $this->getPriceFromString(str_replace(['$', '€', '£'], '', $variantHash['price'])),
$this->sharedStorage->get('channel') $channel
)); ));
$variant->setProduct($product); $variant->setProduct($product);
$product->addVariant($variant); $product->addVariant($variant);
} }

View file

@ -11,15 +11,17 @@
--> -->
<container xmlns="http://symfony.com/schema/dic/services" <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">
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">
<services> <services>
<service id="sylius.behat.context.api.admin.managing_taxons" class="Sylius\Behat\Context\Api\Admin\ManagingTaxonsContext"> <service id="sylius.behat.context.api.admin.managing_taxons" class="Sylius\Behat\Context\Api\Admin\ManagingTaxonsContext">
<argument type="service" id="__symfony__.test.client" /> <argument type="service" id="__symfony__.test.client" />
<argument type="service" id="__symfony__.session" /> <argument type="service" id="__symfony__.session" />
<tag name="fob.context_service" /> <tag name="fob.context_service" />
</service> </service>
<service id="sylius.behat.context.api.admin.managing_product_variants" class="Sylius\Behat\Context\Api\Admin\ManagingProductVariantsContext">
<argument type="service" id="__symfony__.test.client" />
<argument type="service" id="__symfony__.session" />
<tag name="fob.context_service" />
</service>
</services> </services>
</container> </container>

View file

@ -2,7 +2,8 @@
# (c) Paweł Jędrzejewski # (c) Paweł Jędrzejewski
imports: imports:
- suites/api/taxon/managing_taxon.yml - suites/api/taxon/managing_taxons.yml
- suites/api/product/managing_product_variants.yml
- suites/cli/installer.yml - suites/cli/installer.yml

View file

@ -0,0 +1,19 @@
# This file is part of the Sylius package.
# (c) Paweł Jędrzejewski
default:
suites:
api_managing_product_variants:
contexts_services:
- sylius.behat.context.hook.doctrine_orm
- sylius.behat.context.transform.product
- sylius.behat.context.transform.shared_storage
- sylius.behat.context.setup.channel
- sylius.behat.context.setup.product
- sylius.behat.context.setup.admin_security
- sylius.behat.context.api.admin.managing_product_variants
filters:
tags: "@managing_product_variants && @api"

View file

@ -3,3 +3,30 @@ sylius_admin_ajax_product_variants_update_position:
methods: [PUT] methods: [PUT]
defaults: defaults:
_controller: sylius.controller.product_variant:updatePositionsAction _controller: sylius.controller.product_variant:updatePositionsAction
sylius_admin_ajax_product_variants_by_phrase:
path: /search
methods: [GET]
defaults:
_controller: sylius.controller.product_variant:indexAction
_sylius:
serialization_groups: [Autocomplete]
repository:
method: findByPhraseAndProductCode
arguments:
phrase: $phrase
locale: expr:service('sylius.context.locale').getLocaleCode()
productCode: $productCode
sylius_admin_ajax_product_variants_by_code:
path: /
methods: [GET]
defaults:
_controller: sylius.controller.product_variant:indexAction
_sylius:
serialization_groups: [Autocomplete]
repository:
method: findByCodeAndProductCode
arguments:
code: $code
productCode: $productCode

View file

@ -48,6 +48,13 @@
}, 50); }, 50);
}); });
$(document).on('collection-form-add', function () {
$.each($('.sylius-autocomplete'), function (index, element) {
if ($._data($(element).get(0), 'events') == undefined) {
$(element).autoComplete();
}
});
});
$(document).on('collection-form-update', function () { $(document).on('collection-form-update', function () {
$.each($('.sylius-autocomplete'), function (index, element) { $.each($('.sylius-autocomplete'), function (index, element) {
if ($._data($(element).get(0), 'events') == undefined) { if ($._data($(element).get(0), 'events') == undefined) {

View file

@ -0,0 +1,23 @@
{% extends '@SyliusUi/Form/imagesTheme.html.twig' %}
{% block sylius_product_image_widget %}
{% spaceless %}
<div class="ui upload box segment">
{{ form_row(form.type) }}
<label for="{{ form.file.vars.id }}" class="ui icon labeled button"><i class="cloud upload icon"></i> {{ 'sylius.ui.choose_file'|trans }}</label>
{% if form.vars.value.path|default(null) is not null %}
<img class="ui small bordered image" src="{{ form.vars.value.path|imagine_filter('sylius_small') }}" alt="{{ form.vars.value.type }}" />
{% endif %}
<div class="ui hidden element">
{{ form_widget(form.file) }}
</div>
<div class="ui element">
{{- form_errors(form.file) -}}
</div>
{% if product.id is not null and 0 != product.variants|length and not product.simple %}
<br/>
{{ form_row(form.productVariants, {'remote_url': path('sylius_admin_ajax_product_variants_by_phrase', {'productCode': product.code}), 'remote_criteria_type': 'contains', 'remote_criteria_name': 'phrase', 'load_edit_url': path('sylius_admin_ajax_product_variants_by_code', {'productCode': product.code})}) }}
{% endif %}
</div>
{% endspaceless %}
{% endblock %}

View file

@ -1,4 +1,4 @@
{% form_theme form 'SyliusUiBundle:Form:imagesTheme.html.twig' %} {% form_theme form '@SyliusAdmin/Form/imagesTheme.html.twig' %}
<div class="ui tab" data-tab="media"> <div class="ui tab" data-tab="media">
<h3 class="ui dividing header">{{ 'sylius.ui.media'|trans }}</h3> <h3 class="ui dividing header">{{ 'sylius.ui.media'|trans }}</h3>

View file

@ -65,13 +65,8 @@ final class TaxonsToCodesTransformer implements DataTransformerInterface
return []; return [];
} }
$taxonCodes = []; return array_map(function (TaxonInterface $taxon) {
return $taxon->getCode();
/** @var TaxonInterface $taxon */ }, $taxons->toArray());
foreach ($taxons as $taxon) {
$taxonCodes[] = $taxon->getCode();
}
return $taxonCodes;
} }
} }

View file

@ -70,6 +70,7 @@ final class ProductTypeExtension extends AbstractTypeExtension
'allow_delete' => true, 'allow_delete' => true,
'by_reference' => false, 'by_reference' => false,
'label' => 'sylius.form.product.images', 'label' => 'sylius.form.product.images',
'block_name' => 'entry',
]) ])
; ;
} }

View file

@ -12,7 +12,7 @@
namespace Sylius\Bundle\CoreBundle\Form\Type\Product; namespace Sylius\Bundle\CoreBundle\Form\Type\Product;
use Sylius\Bundle\CoreBundle\Form\Type\ImageType; use Sylius\Bundle\CoreBundle\Form\Type\ImageType;
use Sylius\Bundle\ProductBundle\Form\Type\ProductVariantChoiceType; use Sylius\Bundle\ResourceBundle\Form\Type\ResourceAutocompleteChoiceType;
use Sylius\Component\Core\Model\ProductInterface; use Sylius\Component\Core\Model\ProductInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
@ -33,12 +33,13 @@ final class ProductImageType extends ImageType
if (isset($options['product']) && $options['product'] instanceof ProductInterface) { if (isset($options['product']) && $options['product'] instanceof ProductInterface) {
$builder $builder
->add('productVariants', ProductVariantChoiceType::class, [ ->add('productVariants', ResourceAutocompleteChoiceType::class, [
'label' => 'sylius.ui.product_variants', 'label' => 'sylius.ui.product_variants',
'multiple' => true, 'multiple' => true,
'expanded' => false,
'required' => false, 'required' => false,
'product' => $options['product'], 'choice_name' => 'descriptor',
'choice_value' => 'code',
'resource' => 'sylius.product_variant',
]) ])
; ;
} }

View file

@ -44,6 +44,9 @@
<parameter key="sylius.form.type.channel_pricing.validation_groups" type="collection"> <parameter key="sylius.form.type.channel_pricing.validation_groups" type="collection">
<parameter>sylius</parameter> <parameter>sylius</parameter>
</parameter> </parameter>
<parameter key="sylius.form.type.product_image.validation_groups" type="collection">
<parameter>sylius</parameter>
</parameter>
</parameters> </parameters>
<services> <services>
@ -131,6 +134,7 @@
<service id="sylius.form.type.product_image" class="Sylius\Bundle\CoreBundle\Form\Type\Product\ProductImageType"> <service id="sylius.form.type.product_image" class="Sylius\Bundle\CoreBundle\Form\Type\Product\ProductImageType">
<argument>%sylius.model.product_image.class%</argument> <argument>%sylius.model.product_image.class%</argument>
<argument>%sylius.form.type.product_image.validation_groups%</argument>
<tag name="form.type" /> <tag name="form.type" />
</service> </service>
<service id="sylius.form.type.product_taxon_choice" class="Sylius\Bundle\CoreBundle\Form\Type\ProductTaxonChoiceType"> <service id="sylius.form.type.product_taxon_choice" class="Sylius\Bundle\CoreBundle\Form\Type\ProductTaxonChoiceType">
@ -189,6 +193,9 @@
<service id="sylius.form.type.data_transformer.products_to_codes" class="Sylius\Bundle\CoreBundle\Form\DataTransformer\ProductsToCodesTransformer"> <service id="sylius.form.type.data_transformer.products_to_codes" class="Sylius\Bundle\CoreBundle\Form\DataTransformer\ProductsToCodesTransformer">
<argument type="service" id="sylius.repository.product" /> <argument type="service" id="sylius.repository.product" />
</service> </service>
<service id="sylius.form.type.data_transformer.product_variants_to_codes" class="Sylius\Bundle\CoreBundle\Form\DataTransformer\ProductVariantsToCodesTransformer">
<argument type="service" id="sylius.repository.product_variant" />
</service>
<service id="sylius.form.type.customer_guest" class="Sylius\Bundle\CoreBundle\Form\Type\Customer\CustomerGuestType"> <service id="sylius.form.type.customer_guest" class="Sylius\Bundle\CoreBundle\Form\Type\Customer\CustomerGuestType">
<argument>%sylius.model.customer.class%</argument> <argument>%sylius.model.customer.class%</argument>

View file

@ -12,9 +12,7 @@
namespace Sylius\Bundle\ProductBundle\Doctrine\ORM; namespace Sylius\Bundle\ProductBundle\Doctrine\ORM;
use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository; use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;
use Sylius\Component\Core\Model\Product;
use Sylius\Component\Product\Model\ProductInterface; use Sylius\Component\Product\Model\ProductInterface;
use Sylius\Component\Product\Model\ProductVariantInterface;
use Sylius\Component\Product\Repository\ProductVariantRepositoryInterface; use Sylius\Component\Product\Repository\ProductVariantRepositoryInterface;
/** /**
@ -92,7 +90,7 @@ class ProductVariantRepository extends EntityRepository implements ProductVarian
{ {
return $this->createQueryBuilder('o') return $this->createQueryBuilder('o')
->innerJoin('o.product', 'product') ->innerJoin('o.product', 'product')
->where('product.code = :productCode') ->andWhere('product.code = :productCode')
->andWhere('o.code = :code') ->andWhere('o.code = :code')
->setParameter('productCode', $productCode) ->setParameter('productCode', $productCode)
->setParameter('code', $code) ->setParameter('code', $code)
@ -101,13 +99,29 @@ class ProductVariantRepository extends EntityRepository implements ProductVarian
; ;
} }
/**
* {@inheritdoc}
*/
public function findByCodeAndProductCode($code, $productCode)
{
return $this->createQueryBuilder('o')
->innerJoin('o.product', 'product')
->andWhere('product.code = :productCode')
->andWhere('o.code = :code')
->setParameter('productCode', $productCode)
->setParameter('code', $code)
->getQuery()
->getResult()
;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function findOneByIdAndProductId($id, $productId) public function findOneByIdAndProductId($id, $productId)
{ {
return $this->createQueryBuilder('o') return $this->createQueryBuilder('o')
->where('o.product = :productId') ->andWhere('o.product = :productId')
->andWhere('o.id = :id') ->andWhere('o.id = :id')
->setParameter('productId', $productId) ->setParameter('productId', $productId)
->setParameter('id', $id) ->setParameter('id', $id)
@ -115,4 +129,27 @@ class ProductVariantRepository extends EntityRepository implements ProductVarian
->getOneOrNullResult() ->getOneOrNullResult()
; ;
} }
/**
* {@inheritdoc}
*/
public function findByPhraseAndProductCode($phrase, $locale, $productCode)
{
$expr = $this->getEntityManager()->getExpressionBuilder();
return $this->createQueryBuilder('o')
->innerJoin('o.translations', 'translation', 'WITH', 'translation.locale = :locale')
->innerJoin('o.product', 'product')
->andWhere('product.code = :productCode')
->andWhere($expr->orX(
'translation.name LIKE :phrase',
'o.code LIKE :phrase'
))
->setParameter('phrase', '%'.$phrase.'%')
->setParameter('locale', $locale)
->setParameter('productCode', $productCode)
->getQuery()
->getResult()
;
}
} }

View file

@ -6,11 +6,11 @@ Sylius\Component\Product\Model\ProductVariant:
expose: true expose: true
type: integer type: integer
xml_attribute: true xml_attribute: true
groups: [Default, Detailed, DetailedCart] groups: [Default, Detailed, DetailedCart, Autocomplete]
code: code:
expose: true expose: true
type: string type: string
groups: [Default, Detailed, DetailedCart] groups: [Default, Detailed, DetailedCart, Autocomplete]
position: position:
expose: true expose: true
type: integer type: integer
@ -23,6 +23,10 @@ Sylius\Component\Product\Model\ProductVariant:
expose: true expose: true
type: array type: array
groups: [Default, Detailed, DetailedCart] groups: [Default, Detailed, DetailedCart]
virtual_properties:
getDescriptor:
serialized_name: descriptor
groups: [Autocomplete]
relations: relations:
- rel: self - rel: self
href: href:

View file

@ -11,6 +11,7 @@
namespace Sylius\Bundle\ResourceBundle\Form\DataTransformer; namespace Sylius\Bundle\ResourceBundle\Form\DataTransformer;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\TransformationFailedException;
@ -38,6 +39,10 @@ final class RecursiveTransformer implements DataTransformerInterface
*/ */
public function transform($values) public function transform($values)
{ {
if (null === $values) {
return new ArrayCollection();
}
$this->assertTransformationValueType($values, Collection::class); $this->assertTransformationValueType($values, Collection::class);
return $values->map(function ($value) { return $values->map(function ($value) {
@ -50,6 +55,10 @@ final class RecursiveTransformer implements DataTransformerInterface
*/ */
public function reverseTransform($values) public function reverseTransform($values)
{ {
if (null === $values) {
return new ArrayCollection();
}
$this->assertTransformationValueType($values, Collection::class); $this->assertTransformationValueType($values, Collection::class);
return $values->map(function ($value) { return $values->map(function ($value) {

View file

@ -13,6 +13,7 @@ namespace spec\Sylius\Bundle\ResourceBundle\Form\DataTransformer;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use PhpSpec\ObjectBehavior; use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Bundle\ResourceBundle\Form\DataTransformer\RecursiveTransformer; use Sylius\Bundle\ResourceBundle\Form\DataTransformer\RecursiveTransformer;
use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\TransformationFailedException;
@ -37,6 +38,20 @@ final class RecursiveTransformerSpec extends ObjectBehavior
$this->shouldImplement(DataTransformerInterface::class); $this->shouldImplement(DataTransformerInterface::class);
} }
function it_returns_an_empty_array_collection_when_transforming_a_null(
DataTransformerInterface $decoratedTransformer
) {
$this->transform(null)->shouldBeLike(new ArrayCollection());
$decoratedTransformer->transform(Argument::any())->shouldNotBeCalled();
}
function it_returns_an_empty_array_collection_when_reverse_transforming_a_null(
DataTransformerInterface $decoratedTransformer
) {
$this->reverseTransform(null)->shouldBeLike(new ArrayCollection());
$decoratedTransformer->reverseTransform(Argument::any())->shouldNotBeCalled();
}
function it_transforms_recursively_using_configured_transformer(DataTransformerInterface $decoratedTransformer) function it_transforms_recursively_using_configured_transformer(DataTransformerInterface $decoratedTransformer)
{ {
$decoratedTransformer->transform('ABC')->willReturn('abc'); $decoratedTransformer->transform('ABC')->willReturn('abc');
@ -55,7 +70,7 @@ final class RecursiveTransformerSpec extends ObjectBehavior
$this->reverseTransform(new ArrayCollection(['abc', 'cde', 'fgh']))->shouldBeLike(new ArrayCollection(['ABC', 'CDE', 'FGH'])); $this->reverseTransform(new ArrayCollection(['abc', 'cde', 'fgh']))->shouldBeLike(new ArrayCollection(['ABC', 'CDE', 'FGH']));
} }
function it_throws_transformation_failed_exception_if_transform_argument_is_not_collection() function it_throws_transformation_failed_exception_if_transform_argument_is_not_collection_or_null()
{ {
$this->shouldThrow(TransformationFailedException::class)->during('transform', [new \stdClass()]); $this->shouldThrow(TransformationFailedException::class)->during('transform', [new \stdClass()]);
$this->shouldThrow(TransformationFailedException::class)->during('reverseTransform', [new \stdClass()]); $this->shouldThrow(TransformationFailedException::class)->during('reverseTransform', [new \stdClass()]);

View file

@ -193,20 +193,22 @@
{%- endblock sylius_translations_row %} {%- endblock sylius_translations_row %}
{% block sylius_resource_autocomplete_choice_row %} {% block sylius_resource_autocomplete_choice_row %}
<div class="{% if required %}required {% endif %}field{% if (not compound or force_error|default(false)) and not valid %} error{% endif %}">
{{- form_label(form) -}} {{- form_label(form) -}}
<div <div
class="sylius-autocomplete ui fluid search selection dropdown {% if multiple %}multiple{% endif %}" class="sylius-autocomplete ui fluid search selection dropdown {% if multiple %}multiple{% endif %}"
data-url="{{ remote_url }}" data-url="{{ remote_url }}"
data-choice-name="{{ choice_name }}" data-choice-name="{{ choice_name }}"
data-choice-value="{{ choice_value }}" data-choice-value="{{ choice_value }}"
data-criteria-type="{{ remote_criteria_type }}" data-criteria-type="{{ remote_criteria_type }}"
data-criteria-name="{{ remote_criteria_name }}" data-criteria-name="{{ remote_criteria_name }}"
data-load-edit-url="{{ load_edit_url }}" data-load-edit-url="{{ load_edit_url }}"
> >
{{ form_widget(form, {'attr': {'class' : 'autocomplete'}}) }} {{- form_row(form, {'attr': {'class' : 'autocomplete'}}) -}}
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
<div class="default text">{% if placeholder is defined %} {{ placeholder|trans }} {% endif %}</div> <div class="default text">{% if placeholder is defined %} {{ placeholder|trans }} {% endif %}</div>
<div class="menu"></div> <div class="menu"></div>
</div>
{{- form_errors(form) -}}
</div> </div>
{{ form_errors(form) }}
{% endblock %} {% endblock %}

View file

@ -110,6 +110,16 @@ class ProductVariant implements ProductVariantInterface
$this->getTranslation()->setName($name); $this->getTranslation()->setName($name);
} }
/**
* {@inheritdoc}
*/
public function getDescriptor()
{
$name = empty($this->getName()) ? $this->getProduct()->getName() : $this->getName();
return trim(sprintf('%s (%s)', $name, $this->code));
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View file

@ -36,6 +36,11 @@ interface ProductVariantInterface extends
*/ */
public function setName($name); public function setName($name);
/**
* @return string
*/
public function getDescriptor();
/** /**
* @return Collection|ProductOptionValueInterface[] * @return Collection|ProductOptionValueInterface[]
*/ */

View file

@ -54,6 +54,14 @@ interface ProductVariantRepositoryInterface extends RepositoryInterface
*/ */
public function findOneByCodeAndProductCode($code, $productCode); public function findOneByCodeAndProductCode($code, $productCode);
/**
* @param string|array $code
* @param string $productCode
*
* @return ProductVariantInterface[]
*/
public function findByCodeAndProductCode($code, $productCode);
/** /**
* @param mixed $id * @param mixed $id
* @param mixed $productId * @param mixed $productId
@ -61,4 +69,13 @@ interface ProductVariantRepositoryInterface extends RepositoryInterface
* @return ProductVariantInterface|null * @return ProductVariantInterface|null
*/ */
public function findOneByIdAndProductId($id, $productId); public function findOneByIdAndProductId($id, $productId);
/**
* @param string $phrase
* @param string $locale
* @param string $productCode
*
* @return ProductVariantInterface[]
*/
public function findByPhraseAndProductCode($phrase, $locale, $productCode);
} }