mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 17:10:53 +00:00
[Admin] Added autocompletes to product images variant collection
This commit is contained in:
parent
0d75d45651
commit
ead270d7bd
25 changed files with 416 additions and 47 deletions
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 %}
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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()]);
|
||||||
|
|
|
||||||
|
|
@ -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 %}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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[]
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue