diff --git a/src/Marello/Bundle/Magento2Bundle/Autocomplete/SalesChannelInGroupHandler.php b/src/Marello/Bundle/Magento2Bundle/Autocomplete/SalesChannelInGroupHandler.php new file mode 100644 index 000000000..835ef813f --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Autocomplete/SalesChannelInGroupHandler.php @@ -0,0 +1,58 @@ +entityRepository || !$this->idFieldName) { + throw new \RuntimeException('Search handler is not fully configured'); + } + } + + /** + * {@inheritdoc} + */ + protected function searchEntities($search, $firstResult, $maxResults) + { + $parts = explode(self::DELIMITER, $search); + if (3 !== count($parts)) { + return []; + } + + $searchTerm = $parts[0]; + $salesChannelGroupId = (int) $parts[1]; + $skippedSalesChannelIds = '' !== $parts[2] ? explode(',', $parts[2]) : []; + + $resultEntities = []; + if (0 !== $salesChannelGroupId) { + /** @var SalesChannelRepository $repository */ + $repository = $this->entityRepository; + $queryBuilder = $repository->getActiveSalesChannelBySearchTermLimitedWithGroupIdQB( + $searchTerm, + $salesChannelGroupId, + $skippedSalesChannelIds + ); + + $queryBuilder + ->setFirstResult($firstResult) + ->setMaxResults($maxResults); + + $resultEntities = $this->aclHelper->apply($queryBuilder->getQuery())->getResult(); + } + + return $resultEntities; + } +} diff --git a/src/Marello/Bundle/Magento2Bundle/Controller/IntegrationConfigController.php b/src/Marello/Bundle/Magento2Bundle/Controller/IntegrationConfigController.php index 3c955a209..2aaa6ecc5 100644 --- a/src/Marello/Bundle/Magento2Bundle/Controller/IntegrationConfigController.php +++ b/src/Marello/Bundle/Magento2Bundle/Controller/IntegrationConfigController.php @@ -43,7 +43,9 @@ public function checkAction( $transportEntity ?? new Magento2Transport() ); - $response = $this->getUpdateSuccessResponse($response); + if ($response['success']) { + $response = $this->getUpdateSuccessResponse($response); + } } catch (\Exception $e) { $response = $this->logErrorAndGetResponse($e); } diff --git a/src/Marello/Bundle/Magento2Bundle/DTO/WebsiteToSalesChannelMappingItemDTO.php b/src/Marello/Bundle/Magento2Bundle/DTO/WebsiteToSalesChannelMappingItemDTO.php new file mode 100644 index 000000000..9e6904d0e --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/DTO/WebsiteToSalesChannelMappingItemDTO.php @@ -0,0 +1,116 @@ + $notExistedKeys, + 'originalData' => $data + ] + ); + } + + $this->originData = $data; + $this->data = $data; + } + + /** + * @return int + */ + public function getOriginWebsiteId(): int + { + return $this->data['originWebsiteId']; + } + + /** + * @return string + */ + public function getWebsiteName(): string + { + return $this->data['websiteName']; + } + + /** + * @param string $websiteName + */ + public function setWebsiteName(string $websiteName): void + { + $this->data['websiteName'] = $websiteName; + } + + /** + * @return int + */ + public function getSalesChannelId(): int + { + return $this->data['salesChannelId']; + } + + /** + * @return string + */ + public function getSalesChannelName(): string + { + return $this->data['salesChannelName']; + } + + /** + * @param string $salesChannelName + */ + public function setSalesChannelName(string $salesChannelName): void + { + $this->data['salesChannelName'] = $salesChannelName; + } + + /** + * @return array + */ + public function getOriginData(): array + { + return $this->originData; + } + + /** + * @return array + */ + public function getData(): array + { + return $this->data; + } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + return $this->data; + } +} diff --git a/src/Marello/Bundle/Magento2Bundle/Entity/Magento2Transport.php b/src/Marello/Bundle/Magento2Bundle/Entity/Magento2Transport.php index cf3c9372e..903b8ee92 100644 --- a/src/Marello/Bundle/Magento2Bundle/Entity/Magento2Transport.php +++ b/src/Marello/Bundle/Magento2Bundle/Entity/Magento2Transport.php @@ -42,13 +42,8 @@ class Magento2Transport extends Transport /** * @var array - * [ - * [ - * 'website_code' => string , - * 'sales_chanel_code' => string - * ], - * ... - * ] + * + * The structure described in constant REQUIRED_KEYS of @see WebsiteToSalesChannelMappingItemDTO * * @ORM\Column(name="m2_websites_sales_channel_map", type="json", nullable=true) */ diff --git a/src/Marello/Bundle/Magento2Bundle/EventListener/Doctrine/SalesChannelReverseSyncListener.php b/src/Marello/Bundle/Magento2Bundle/EventListener/Doctrine/SalesChannelReverseSyncListener.php index 2bd76a45c..2102b4726 100644 --- a/src/Marello/Bundle/Magento2Bundle/EventListener/Doctrine/SalesChannelReverseSyncListener.php +++ b/src/Marello/Bundle/Magento2Bundle/EventListener/Doctrine/SalesChannelReverseSyncListener.php @@ -16,9 +16,6 @@ use Oro\Component\MessageQueue\Client\MessageProducerInterface; use Oro\Component\MessageQueue\Transport\Exception\Exception; -/** - * @todo Block changes currency and block changes in sales channel code - */ class SalesChannelReverseSyncListener { private const ACTIVE_PROPERTY_NAME = 'active'; diff --git a/src/Marello/Bundle/Magento2Bundle/EventListener/Doctrine/WebsiteSalesChannelReverseSyncListener.php b/src/Marello/Bundle/Magento2Bundle/EventListener/Doctrine/WebsiteSalesChannelReverseSyncListener.php index 0004b1be8..183919329 100644 --- a/src/Marello/Bundle/Magento2Bundle/EventListener/Doctrine/WebsiteSalesChannelReverseSyncListener.php +++ b/src/Marello/Bundle/Magento2Bundle/EventListener/Doctrine/WebsiteSalesChannelReverseSyncListener.php @@ -341,7 +341,7 @@ protected function loadCreatedWebsites(UnitOfWork $unitOfWork) protected function loadUpdatedWebsites(UnitOfWork $unitOfWork) { foreach ($unitOfWork->getScheduledEntityUpdates() as $entityUpdate) { - if ($entityUpdate instanceof Website && $this->isApplicableWebsite($entityUpdate)) { + if ($entityUpdate instanceof Website) { $changeSet = $unitOfWork->getEntityChangeSet($entityUpdate); if (!\array_key_exists(self::SALES_CHANNEL_FIELD_NAME, $changeSet)) { diff --git a/src/Marello/Bundle/Magento2Bundle/Form/Type/SalesChannelInGroupSelectType.php b/src/Marello/Bundle/Magento2Bundle/Form/Type/SalesChannelInGroupSelectType.php new file mode 100644 index 000000000..e02257e45 --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Form/Type/SalesChannelInGroupSelectType.php @@ -0,0 +1,66 @@ +setDefaults( + [ + 'componentName' => 'salesChannelInGroupSelectComponent', + 'autocomplete_alias' => 'magento2_saleschannels_in_group', + 'configs' => [ + 'allowClear' => false, + 'component' => 'autocomplete-magento2-sales-channel-in-group', + 'placeholder' => 'marello.sales.saleschannel.form.select_saleschannel', + 'result_template_twig' => 'MarelloMagento2Bundle:SalesChannel:Autocomplete/result.html.twig', + 'selection_template_twig' => 'MarelloMagento2Bundle:SalesChannel:Autocomplete/selection.html.twig' + ], + 'attr' => [ + 'data-role' => 'sales-channel-in-group-select' + ] + ] + ); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + parent::buildView($view, $form, $options); + + $vars = [ + 'attr' => [ + 'data-page-component-name' => $options['componentName'] + ] + ]; + + $view->vars = array_replace_recursive($view->vars, $vars); + } + + /** + * {@inheritdoc} + */ + public function getParent() + { + return OroJquerySelect2HiddenType::class; + } + + /** + * {@inheritdoc} + */ + public function getBlockPrefix() + { + return self::BLOCK_PREFIX; + } +} diff --git a/src/Marello/Bundle/Magento2Bundle/Form/Type/TransportCheckButtonType.php b/src/Marello/Bundle/Magento2Bundle/Form/Type/TransportCheckButtonType.php index d01f7233f..2f5abaac2 100644 --- a/src/Marello/Bundle/Magento2Bundle/Form/Type/TransportCheckButtonType.php +++ b/src/Marello/Bundle/Magento2Bundle/Form/Type/TransportCheckButtonType.php @@ -3,37 +3,44 @@ namespace Marello\Bundle\Magento2Bundle\Form\Type; use Symfony\Component\Form\Extension\Core\Type\ButtonType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; class TransportCheckButtonType extends ButtonType { - public const NAME = 'marello_magento2_transport_check_button'; + private const BLOCK_PREFIX = 'marello_magento2_transport_check_button'; /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getName() + public function getBlockPrefix() { - return $this->getBlockPrefix(); + return self::BLOCK_PREFIX; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getBlockPrefix() + public function configureOptions(OptionsResolver $resolver) { - return self::NAME; + parent::configureOptions($resolver); + + $resolver->setRequired(['selectorForFieldsRequiredReCheckConnection']); + $resolver->setDefaults(['attr' => ['class' => 'btn btn-primary']]); } /** * {@inheritDoc} */ - public function configureOptions(OptionsResolver $resolver) + public function buildView(FormView $view, FormInterface $form, array $options) { - parent::configureOptions($resolver); + parent::buildView($view, $form, $options); - $resolver->setRequired(['websiteToSalesChannelMappingSelector']); - $resolver->setRequired(['salesGroupSelector']); - $resolver->setDefaults(['attr' => ['class' => 'btn btn-primary']]); + $view->vars = \array_replace_recursive($view->vars, [ + 'component_options' => [ + 'selectorForFieldsRequiredReCheckConnection' => $options['selectorForFieldsRequiredReCheckConnection'] + ] + ]); } } diff --git a/src/Marello/Bundle/Magento2Bundle/Form/Type/TransportSettingFormType.php b/src/Marello/Bundle/Magento2Bundle/Form/Type/TransportSettingFormType.php index d585d957a..be276755f 100644 --- a/src/Marello/Bundle/Magento2Bundle/Form/Type/TransportSettingFormType.php +++ b/src/Marello/Bundle/Magento2Bundle/Form/Type/TransportSettingFormType.php @@ -3,23 +3,34 @@ namespace Marello\Bundle\Magento2Bundle\Form\Type; use Marello\Bundle\Magento2Bundle\Entity\Magento2Transport; -use Oro\Bundle\FormBundle\Form\DataTransformer\ArrayToJsonTransformer; use Oro\Bundle\FormBundle\Form\Type\OroDateType; +use Oro\Bundle\FormBundle\Form\Type\OroEncodedPlaceholderPasswordType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; -use Symfony\Component\Form\Extension\Core\Type\HiddenType; -use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Url; -use Oro\Bundle\FormBundle\Form\Type\OroEncodedPlaceholderPasswordType; class TransportSettingFormType extends AbstractType { + private const BLOCK_PREFIX = 'marello_magento2_transport_setting'; + public const ELEMENT_DATA_ROLE_WEBSITE_TO_CHANNEL_MAPPING = 'websiteToSalesChannelMapping'; public const ELEMENT_DATA_ROLE_SALES_CHANNEL_GROUP = 'salesChannelGroup'; + public const ELEMENT_DATA_ROLE_API_URL = 'apiUrl'; + public const ELEMENT_DATA_ROLE_API_TOKEN = 'apiToken'; + + /** + * {@inheritDoc} + */ + public function getBlockPrefix() + { + return self::BLOCK_PREFIX; + } + /** * {@inheritDoc} */ @@ -33,8 +44,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'required' => true, 'tooltip' => 'marello.magento2.transport_setting_form.api_url.tooltip', 'constraints' => [ - new Url() - ] + new Url(), + new NotBlank() + ], + 'attr' => ['data-role' => TransportSettingFormType::ELEMENT_DATA_ROLE_API_URL] ] ); @@ -44,7 +57,11 @@ public function buildForm(FormBuilderInterface $builder, array $options) [ 'label' => 'marello.magento2.transport_setting_form.api_token.label', 'required' => true, - 'tooltip' => 'marello.magento2.transport_setting_form.api_token.tooltip' + 'tooltip' => 'marello.magento2.transport_setting_form.api_token.tooltip', + 'constraints' => [ + new NotBlank() + ], + 'attr' => ['data-role' => TransportSettingFormType::ELEMENT_DATA_ROLE_API_TOKEN] ] ); @@ -55,7 +72,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'label' => 'marello.magento2.transport_setting_form.sync_start_date.label', 'required' => true, 'tooltip' => 'marello.magento2.transport_setting_form.sync_start_date.tooltip', - 'empty_data' => new \DateTime('2007-01-01', new \DateTimeZone('UTC')) + 'empty_data' => new \DateTime('2007-01-01', new \DateTimeZone('UTC')), + 'constraints' => [ + new NotBlank() + ] ] ); @@ -82,33 +102,42 @@ public function buildForm(FormBuilderInterface $builder, array $options) TransportCheckButtonType::class, [ 'label' => 'marello.magento2.connection_validation.button.text', - 'salesGroupSelector' => sprintf('[data-role="%s"]', self::ELEMENT_DATA_ROLE_SALES_CHANNEL_GROUP), - 'websiteToSalesChannelMappingSelector' => sprintf( - '[data-role="%s"]', self::ELEMENT_DATA_ROLE_WEBSITE_TO_CHANNEL_MAPPING - ) + 'selectorForFieldsRequiredReCheckConnection' => [ + sprintf('[data-role="%s"]', self::ELEMENT_DATA_ROLE_API_URL) + ] + ] + ); + + $builder->add( + 'websiteToSalesChannelMappingControls', + WebsiteToSalesChannelMappingControlsType::class, + [ + 'label' => 'Website To Sales Channel Mapping', + 'selectorSalesChannelGroup' => sprintf( + '[data-role="%s"]', + self::ELEMENT_DATA_ROLE_SALES_CHANNEL_GROUP + ), + 'selectorWebsiteToSalesChannelMapping' => sprintf( + '[data-role="%s"]', + self::ELEMENT_DATA_ROLE_WEBSITE_TO_CHANNEL_MAPPING + ), + 'mapped' => false ] ); $builder->add( - $builder - ->create( - 'websiteToSalesChannelMapping', - HiddenType::class, - [ - 'attr' => ['data-role' => self::ELEMENT_DATA_ROLE_WEBSITE_TO_CHANNEL_MAPPING], - 'data' => [ - [ - 'website_code' => 'MAG', - 'sales_chanel_code' => 'MAR', - ], - [ - 'website_code' => 'MAG_2', - 'sales_chanel_code' => 'MAR_2', - ] - ] - ] - ) - ->addViewTransformer(new ArrayToJsonTransformer()) + 'websiteToSalesChannelMapping', + WebsiteToSalesChannelMappingType::class, + [ + 'attr' => [ + 'data-role' => TransportSettingFormType::ELEMENT_DATA_ROLE_WEBSITE_TO_CHANNEL_MAPPING + ], + 'constraints' => [ + new NotBlank([ + 'message' => 'marello.magento2.validator.not_empty_website_to_sales_channel_mapping' + ]) + ] + ] ); } @@ -117,6 +146,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) */ public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefaults(['data_class' => Magento2Transport::class]); + $resolver->setDefaults([ + 'data_class' => Magento2Transport::class + ]); } } diff --git a/src/Marello/Bundle/Magento2Bundle/Form/Type/WebsiteSelectType.php b/src/Marello/Bundle/Magento2Bundle/Form/Type/WebsiteSelectType.php new file mode 100644 index 000000000..282d0094e --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Form/Type/WebsiteSelectType.php @@ -0,0 +1,69 @@ +translator = $translator; + } + + /** + * {@inheritDoc} + */ + public function getBlockPrefix() + { + return self::BLOCK_PREFIX; + } + + /** + * {@inheritDoc} + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'label' => 'marello.magento2.website_form.website.label', + 'attr' => [ + 'data-role' => 'website-select', + 'data-page-component-name' => 'websiteSelectComponent', + 'data-page-component-module' => 'marellomagento2/js/app/components/website-component' + ], + 'configs' => [ + 'placeholder' => 'marello.magento2.website_form.website.placeholder', + 'allowClear' => false, + ] + ]); + } + + /** + * {@inheritDoc} + */ + public function getParent() + { + return HiddenType::class; + } + + /** + * {@inheritDoc} + */ + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['configs'] = $options['configs']; + } +} diff --git a/src/Marello/Bundle/Magento2Bundle/Form/Type/WebsiteToSalesChannelMappingControlsType.php b/src/Marello/Bundle/Magento2Bundle/Form/Type/WebsiteToSalesChannelMappingControlsType.php new file mode 100644 index 000000000..f2de4468c --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Form/Type/WebsiteToSalesChannelMappingControlsType.php @@ -0,0 +1,56 @@ +add('salesChannel', SalesChannelInGroupSelectType::class); + $builder->add('website', WebsiteSelectType::class); + } + + /** + * {@inheritDoc} + */ + public function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + + $resolver->setRequired(['selectorSalesChannelGroup']); + $resolver->setRequired(['selectorWebsiteToSalesChannelMapping']); + } + + /** + * {@inheritDoc} + */ + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['component_options'] = \array_merge( + $view->vars['component_options'] ?? [], + [ + 'selectorSalesChannelGroup' => $options['selectorSalesChannelGroup'], + 'selectorWebsiteToSalesChannelMapping' => $options['selectorWebsiteToSalesChannelMapping'], + ] + ); + } +} diff --git a/src/Marello/Bundle/Magento2Bundle/Form/Type/WebsiteToSalesChannelMappingType.php b/src/Marello/Bundle/Magento2Bundle/Form/Type/WebsiteToSalesChannelMappingType.php new file mode 100644 index 000000000..b1797f8a7 --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Form/Type/WebsiteToSalesChannelMappingType.php @@ -0,0 +1,50 @@ +addModelTransformer(new ArrayToJsonTransformer()); + $builder->addViewTransformer(new ArrayToJsonTransformer()); + } + + /** + * {@inheritDoc} + */ + public function getParent() + { + return HiddenType::class; + } + + /** + * {@inheritDoc} + */ + public function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + } +} diff --git a/src/Marello/Bundle/Magento2Bundle/Handler/TransportHandler.php b/src/Marello/Bundle/Magento2Bundle/Handler/TransportHandler.php index 3724a9fd5..4387f28c1 100644 --- a/src/Marello/Bundle/Magento2Bundle/Handler/TransportHandler.php +++ b/src/Marello/Bundle/Magento2Bundle/Handler/TransportHandler.php @@ -66,6 +66,13 @@ public function getCheckResponse( $transportEntity ); + if (!$this->isValidTransportEntityToConnect($transportEntity)) { + return [ + 'success' => false, + 'message' => 'Form is invalid' + ]; + } + $transport->initWithExtraOptions( $transportEntity, MultiAttemptsConfigTrait::getMultiAttemptsDisabledConfig() @@ -77,6 +84,17 @@ public function getCheckResponse( ]; } + /** + * @todo Replace with validator + * + * @param Magento2Transport $transportEntity + * @return bool + */ + protected function isValidTransportEntityToConnect(Magento2Transport $transportEntity): bool + { + return $transportEntity->getApiUrl() && $transportEntity->getApiToken(); + } + /** * @param string $integrationType * @param string $transportType diff --git a/src/Marello/Bundle/Magento2Bundle/ImportExport/Converter/WebsiteDataConverter.php b/src/Marello/Bundle/Magento2Bundle/ImportExport/Converter/WebsiteDataConverter.php index 87c3b0979..b9d9f6ea7 100644 --- a/src/Marello/Bundle/Magento2Bundle/ImportExport/Converter/WebsiteDataConverter.php +++ b/src/Marello/Bundle/Magento2Bundle/ImportExport/Converter/WebsiteDataConverter.php @@ -6,8 +6,10 @@ class WebsiteDataConverter extends IntegrationAwareDataConverter { - /** @var string */ - public const SALES_CHANNEL_CODE_COLUMN_NAME = 'salesChannelGroupCode'; + public const ID_COLUMN_NAME = 'id'; + public const CODE_COLUMN_NAME = 'code'; + public const NAME_COLUMN_NAME = 'name'; + public const SALES_CHANNEL_CODE_COLUMN_ID = 'salesChannelId'; private const NAME_MAX_LENGTH = 255; private const CODE_MAX_LENGTH = 32; @@ -19,10 +21,10 @@ class WebsiteDataConverter extends IntegrationAwareDataConverter protected function getHeaderConversionRules() { return [ - 'id' => 'originId', - 'code' => 'code', - 'name' => 'name', - self::SALES_CHANNEL_CODE_COLUMN_NAME => 'salesChannel:code' + self::ID_COLUMN_NAME => 'originId', + self::CODE_COLUMN_NAME => 'code', + self::NAME_COLUMN_NAME => 'name', + self::SALES_CHANNEL_CODE_COLUMN_ID => 'salesChannel:id' ]; } diff --git a/src/Marello/Bundle/Magento2Bundle/ImportExport/Strategy/StoreMagento2ImportStrategy.php b/src/Marello/Bundle/Magento2Bundle/ImportExport/Strategy/StoreMagento2ImportStrategy.php index 82be63ad4..76410da8f 100644 --- a/src/Marello/Bundle/Magento2Bundle/ImportExport/Strategy/StoreMagento2ImportStrategy.php +++ b/src/Marello/Bundle/Magento2Bundle/ImportExport/Strategy/StoreMagento2ImportStrategy.php @@ -13,9 +13,6 @@ protected function findExistingEntity($entity, array $searchContext = []) { $existingEntity = null; - /** - * @todo Think about complex logic for guessing Localization, by part of formatting code - */ if ($entity instanceof Localization && null !== $entity->getFormattingCode()) { //for Localization we looking for the first localization by formattingCode return $this->databaseHelper->findOneBy( diff --git a/src/Marello/Bundle/Magento2Bundle/Migrations/Data/Magento2/ORM/LoadSalesChannelGroup.php b/src/Marello/Bundle/Magento2Bundle/Migrations/Data/Magento2/ORM/LoadSalesChannelGroup.php deleted file mode 100644 index 4c465396e..000000000 --- a/src/Marello/Bundle/Magento2Bundle/Migrations/Data/Magento2/ORM/LoadSalesChannelGroup.php +++ /dev/null @@ -1,7 +0,0 @@ -getRepository(Website::class)->findAll(); + + $websitePerSalesChannel = []; + /** @var $website */ + foreach ($websites as $website) { + if (!isset($websitePerSalesChannel[$website->getChannelId()])) { + $websitePerSalesChannel[$website->getChannelId()] = []; + } + + if (!$website->getSalesChannel()) { + return; + } + + $websitePerSalesChannel[$website->getChannelId()][] = (new WebsiteToSalesChannelMappingItemDTO( + [ + 'originWebsiteId' => $website->getOriginId(), + 'websiteName' => $website->getName(), + 'salesChannelId' => $website->getSalesChannel()->getId(), + 'salesChannelName' => $website->getSalesChannel()->getName() + ] + ))->getData(); + } + + foreach ($websitePerSalesChannel as $integrationId => $websiteToSalesChannelMappingData) { + $integration = $manager->getRepository(Channel::class)->find($integrationId); + $integration->getTransport()->setWebsiteToSalesChannelMapping( + $websiteToSalesChannelMappingData + ); + } + + $manager->flush(); + } +} diff --git a/src/Marello/Bundle/Magento2Bundle/Migrations/Schema/v1_1/MarelloMagento2Bundle.php b/src/Marello/Bundle/Magento2Bundle/Migrations/Schema/v1_1/MarelloMagento2Bundle.php index c9536c75b..c6729e5c3 100644 --- a/src/Marello/Bundle/Magento2Bundle/Migrations/Schema/v1_1/MarelloMagento2Bundle.php +++ b/src/Marello/Bundle/Magento2Bundle/Migrations/Schema/v1_1/MarelloMagento2Bundle.php @@ -6,6 +6,9 @@ use Oro\Bundle\MigrationBundle\Migration\Migration; use Oro\Bundle\MigrationBundle\Migration\QueryBag; +/** + * @todo Remove this in final version + */ class MarelloMagento2Bundle implements Migration { /** diff --git a/src/Marello/Bundle/Magento2Bundle/Model/Magento2TransportSettings.php b/src/Marello/Bundle/Magento2Bundle/Model/Magento2TransportSettings.php index 4d4b74038..5514b14f7 100644 --- a/src/Marello/Bundle/Magento2Bundle/Model/Magento2TransportSettings.php +++ b/src/Marello/Bundle/Magento2Bundle/Model/Magento2TransportSettings.php @@ -2,6 +2,7 @@ namespace Marello\Bundle\Magento2Bundle\Model; +use Marello\Bundle\Magento2Bundle\DTO\WebsiteToSalesChannelMappingItemDTO; use Symfony\Component\HttpFoundation\ParameterBag; class Magento2TransportSettings extends ParameterBag @@ -72,15 +73,16 @@ public function isDeleteRemoteDataOnDeletion(): bool } /** - * @param string $websiteCode - * @return string|null + * @param int $websiteId + * @return int|null */ - public function getSalesChannelCodeByWebsiteCode(string $websiteCode): ?string + public function getSalesChannelIdByWebsiteId(int $websiteId): ?int { $websiteToSalesChannelMapping = $this->get(self::WEBSITE_TO_SALES_CHANNEL_MAPPING_KEY, []); foreach ($websiteToSalesChannelMapping as $websiteToSalesChannelMappingItem) { - if ($websiteCode === $websiteToSalesChannelMappingItem['website_code']) { - return $websiteToSalesChannelMappingItem['sales_chanel_code']; + $mappingItem = new WebsiteToSalesChannelMappingItemDTO($websiteToSalesChannelMappingItem); + if ($mappingItem->getOriginWebsiteId() === $websiteId) { + return $mappingItem->getSalesChannelId(); } } diff --git a/src/Marello/Bundle/Magento2Bundle/Provider/WebsitesProvider.php b/src/Marello/Bundle/Magento2Bundle/Provider/WebsitesProvider.php index 92dd551ec..fb414bca6 100644 --- a/src/Marello/Bundle/Magento2Bundle/Provider/WebsitesProvider.php +++ b/src/Marello/Bundle/Magento2Bundle/Provider/WebsitesProvider.php @@ -2,6 +2,7 @@ namespace Marello\Bundle\Magento2Bundle\Provider; +use Marello\Bundle\Magento2Bundle\ImportExport\Converter\WebsiteDataConverter; use Marello\Bundle\Magento2Bundle\Transport\Magento2TransportInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -21,9 +22,19 @@ public function __construct(TranslatorInterface $translator) /** * @param Magento2TransportInterface $transport * @return array + * [ + * int => string + * ] */ public function getFormattedWebsites(Magento2TransportInterface $transport): array { - return iterator_to_array($transport->getWebsites()); + $websiteIdsWithNames = []; + foreach ($transport->getWebsites() as $website) { + $websiteId = $website[WebsiteDataConverter::ID_COLUMN_NAME]; + $websiteName = $website[WebsiteDataConverter::NAME_COLUMN_NAME]; + $websiteIdsWithNames[$websiteId] = $websiteName; + } + + return $websiteIdsWithNames; } } diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/config/batch_jobs.yml b/src/Marello/Bundle/Magento2Bundle/Resources/config/batch_jobs.yml index ec41c8f07..cf827b834 100644 --- a/src/Marello/Bundle/Magento2Bundle/Resources/config/batch_jobs.yml +++ b/src/Marello/Bundle/Magento2Bundle/Resources/config/batch_jobs.yml @@ -15,6 +15,7 @@ connector: parameters: batch_size: 25 # @todo Implement removing of abandoned website + # @todo Implement updating config WEBSITE_TO_SALES_CHANNEL_MAPPING_KEY marello_magento2_store_rest_import: title: "Store import from Magento 2" diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/config/jsmodules.yml b/src/Marello/Bundle/Magento2Bundle/Resources/config/jsmodules.yml index 8e8748e5a..79dc9115d 100644 --- a/src/Marello/Bundle/Magento2Bundle/Resources/config/jsmodules.yml +++ b/src/Marello/Bundle/Magento2Bundle/Resources/config/jsmodules.yml @@ -1,3 +1,10 @@ +aliases: + oro/select2-autocomplete-magento2-sales-channel-in-group-component$: marellomagento2/js/app/components/select2-autocomplete-sales-channel-in-group-component dynamic-imports: marellomagento2: + - marellomagento2/js/app/components/select2-autocomplete-sales-channel-in-group-component - marellomagento2/js/app/components/check-connection-component + - marellomagento2/js/app/components/website-component + - marellomagento2/js/app/components/website-to-sales-channel-controls-component + common: + - oro/select2-autocomplete-magento2-sales-channel-in-group-component diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/config/services.yml b/src/Marello/Bundle/Magento2Bundle/Resources/config/services.yml index e791146b0..1d509f144 100644 --- a/src/Marello/Bundle/Magento2Bundle/Resources/config/services.yml +++ b/src/Marello/Bundle/Magento2Bundle/Resources/config/services.yml @@ -5,6 +5,15 @@ services: tags: - { name: oro_form.autocomplete.search_handler, alias: magento2_group_saleschannels } + marello_magento2.saleschannels_in_group.form.autocomplete.search_handler: + class: 'Marello\Bundle\Magento2Bundle\Autocomplete\SalesChannelInGroupHandler' + parent: oro_form.autocomplete.search_handler + arguments: + - 'Marello\Bundle\SalesBundle\Entity\SalesChannel' + - ['name'] + tags: + - { name: oro_form.autocomplete.search_handler, alias: magento2_saleschannels_in_group, acl_resource: marello_product_view } + marello_magento2.transport.rest.request.request_factory: class: Marello\Bundle\Magento2Bundle\Transport\Rest\Request\RequestFactory @@ -153,6 +162,13 @@ services: tags: - { name: form.type_extension, extended_type: Oro\Bundle\IntegrationBundle\Form\Type\ChannelType } + marello_magento2.form.type.website_select: + class: Marello\Bundle\Magento2Bundle\Form\Type\WebsiteSelectType + arguments: + - '@translator' + tags: + - { name: form.type } + # Stack marello_magento2.stack.product_changes_by_channel: class: Marello\Bundle\Magento2Bundle\Stack\ProductChangesByChannelStack diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/check-connection-component.js b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/check-connection-component.js index 24056b516..e560ef897 100644 --- a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/check-connection-component.js +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/check-connection-component.js @@ -1,14 +1,24 @@ define(function(require) { 'use strict'; - var $ = require('jquery'); - var _ = require('underscore'); - var BaseComponent = require('oroui/js/app/components/base/component'); - var CheckConnectionView = require('../views/check-connection-view'); - var CheckConnectionModel = require('../models/check-connection-model'); - var CheckConnectionComponent; - - CheckConnectionComponent = BaseComponent.extend({ + const $ = require('jquery'); + const _ = require('underscore'); + const BaseComponent = require('oroui/js/app/components/base/component'); + const CheckConnectionView = require('../views/check-connection-view'); + const CheckConnectionModel = require('../models/check-connection-model'); + const WebsiteProvider = require('../provider/website-provider'); + const Utils = require('../utils/utils'); + const CheckConnectionComponent = BaseComponent.extend({ + /** + * @property {jQuery} + */ + $el: null, + + /** + * @property {CheckConnectionModel} + */ + model: null, + /** * @inheritDoc */ @@ -20,48 +30,86 @@ define(function(require) { * @param {Object} options */ initialize: function(options) { - var $form = $(options._sourceElement).closest(options.parentElementSelector); - var viewOptions = _.extend({ - model: new CheckConnectionModel({}), + CheckConnectionComponent.__super__.initialize.apply(this, arguments); + + this.model = new CheckConnectionModel({}); + this.$el = $(options._sourceElement); + + let viewOptions = this.getPreparedViewOptions(options); + this.view = new CheckConnectionView(viewOptions); + + WebsiteProvider.setSubProvider(this); + }, + + /** + * @param {Object} options + * @return {Object} + */ + getPreparedViewOptions: function(options) { + let $form = this.$el.closest(options.parentElementSelector); + let viewOptions = _.extend({ + model: this.model, el: $form, - checkButtonEl: $(options._sourceElement), - websiteListEl: $(options.websiteListSelector), - salesGroupEl: $(options.salesGroupSelector), - websiteToSalesChannelMappingEl: $(options.websiteToSalesChannelMappingSelector), - checkConnectionStatusEl: $form.find(options.checkConnectionStatusSelector), + $checkConnectionStatusEl: $form.find(options.checkConnectionStatusSelector), + selectorForFieldsRequiredReCheckConnection: options.selectorForFieldsRequiredReCheckConnection, transportEntityId: options.id }, options.viewOptions || {}); - var invalidOptionKeys = this.getInvalidJqueryOptionKeys(viewOptions, [ - 'el', 'salesGroupEl', 'websiteToSalesChannelMappingEl', 'checkButtonEl', 'checkConnectionStatusEl' + let invalidOptionKeys = Utils.getInvalidJqueryOptionKeys(viewOptions, [ + 'el', '$checkConnectionStatusEl' ]); if (invalidOptionKeys.length) { // unable to initialize - $(options._sourceElement).remove(); - throw new TypeError('Missing required form element (s): ' + invalidOptionKeys.join(',')); - } else { - this.view = new CheckConnectionView(viewOptions); + this.$el.remove(); + throw new TypeError( + 'Missing required form element(s): ' + invalidOptionKeys.join(', ') + ); } + + if (_.isUndefined(options.selectorForFieldsRequiredReCheckConnection)) { + this.$el.remove(); + throw new TypeError('Missing required option: selectorForFieldsRequiredReCheckConnection'); + } + + invalidOptionKeys = Utils.getInvalidJqueryOptionKeys( + options.selectorForFieldsRequiredReCheckConnection, + _.keys(options.selectorForFieldsRequiredReCheckConnection) + ); + + if (invalidOptionKeys.length) { + // unable to initialize + this.$el.remove(); + throw new TypeError( + 'Can\'t find element by key(s) in selectorForFieldsRequiredReCheckConnection: ' + + invalidOptionKeys.join(', ') + ); + } + + return viewOptions; }, /** - * @param {Object} options - * @param {array} elementKeys - * - * @return {array} + * @return {Promise} */ - getInvalidJqueryOptionKeys: function (options, elementKeys) { - var invalidKeys = _.filter(elementKeys, function (elementKey) { - /** - * @var {jQuery} element - */ - var element = options[elementKey]; - return !_.isUndefined(element) && !element.length; - }); - - return invalidKeys.length; + getWebsiteDTOsPromise: function () { + return this.view.getWebsiteDTOsPromise(); }, + + dispose: function() { + if (this.disposed) { + return; + } + + WebsiteProvider.cleanSubProvider(); + + const properties = ['$el', 'model']; + _.each(properties, _.bind(function (property) { + delete this[property]; + }, this)); + + CheckConnectionComponent.__super__.dispose.call(this); + } }); diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/select2-autocomplete-sales-channel-in-group-component.js b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/select2-autocomplete-sales-channel-in-group-component.js new file mode 100755 index 000000000..cf98f98ad --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/select2-autocomplete-sales-channel-in-group-component.js @@ -0,0 +1,131 @@ +define(function (require) { + 'use strict'; + + const _ = require('underscore'); + const __ = require('orotranslation/js/translator'); + const Select2AutocompleteComponent = require('oro/select2-autocomplete-component'); + const logger = require('oroui/js/tools/logger'); + const messenger = require('oroui/js/messenger'); + const Select2AutocompleteSalesChannelInGroupComponent = Select2AutocompleteComponent.extend({ + /** + * @property {jQuery} + */ + $el: null, + + /** + * @property {jQuery} + */ + $salesChannelGroupEl: null, + + /** + * @inheritDoc + */ + constructor: function Select2AutocompleteSalesChannelInGroupComponent(options) { + Select2AutocompleteSalesChannelInGroupComponent.__super__.constructor.call(this, options); + }, + + /** + * @inheritDoc + */ + initialize: function(options) { + Select2AutocompleteSalesChannelInGroupComponent.__super__.initialize.call(this, options); + this.$el = $(options._sourceElement); + }, + + /** + * @param {jQuery} $salesChannelGroupEl + */ + setSalesChannelGroupEl: function($salesChannelGroupEl) { + if (this.$salesChannelGroupEl) { + this.$salesChannelGroupEl.off( + 'change', + _.bind(this.handleSalesChannelGroupChange, this) + ); + } + + this.$salesChannelGroupEl = $salesChannelGroupEl; + this.$salesChannelGroupEl.on( + 'change', + _.bind(this.handleSalesChannelGroupChange, this) + ); + }, + + /** + * @param controlsComponent + */ + setWebsiteToSalesChannelControlsComponent: function(controlsComponent) { + this.controlsComponent = controlsComponent; + }, + + clearWebsiteToSalesChannelControlsComponent: function() { + this.controlsComponent = null; + }, + + handleSalesChannelGroupChange: function() { + this.clearData(); + }, + + clearData: function() { + this.$el.inputWidget('val', ''); + }, + + isValidData: function() { + let selectedData = this.$el.inputWidget('data'); + return !_.isEmpty(selectedData); + }, + + getData: function () { + let selectedData = this.$el.inputWidget('data'); + if (this.isValidData()) { + return { + 'id' : selectedData.id, + 'name': selectedData.name + } + } + + return {}; + }, + + makeQuery: function (query) { + if (null === this.controlsComponent) { + logger.warn( + 'Invalid configuration of autocompleteSalesChannelInGroup component. No controls component present.' + ); + + return query + ';0;'; + } + + let salesChannelGroupId = this.controlsComponent.getSalesChannelGroupId(); + if (!salesChannelGroupId) { + messenger.notificationFlashMessage( + 'error', + __('marello.magento2.mapping_form.no_sales_group_selected') + ); + } + + let usedSalesChannelIds = this.controlsComponent.getUsedSalesChannelIds(); + return query + ';' + salesChannelGroupId + ';' + usedSalesChannelIds.join(','); + }, + + dispose: function() { + if (this.disposed) { + return; + } + + this.$el.off(); + delete this.$el; + + if (this.$salesChannelGroupEl) { + this.$salesChannelGroupEl.off( + 'change', + _.bind(this.handleSalesChannelGroupChange, this) + ); + } + this.$salesChannelGroupEl = null; + + Select2AutocompleteSalesChannelInGroupComponent.__super__.dispose.call(this); + } + }); + + return Select2AutocompleteSalesChannelInGroupComponent; +}); diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/website-component.js b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/website-component.js new file mode 100644 index 000000000..419aa6487 --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/website-component.js @@ -0,0 +1,147 @@ +define(function (require) { + 'use strict'; + + require('jquery.select2'); + const $ = require('jquery'); + const _ = require('underscore'); + const BaseComponent = require('oroui/js/app/components/base/component'); + const logger = require('oroui/js/tools/logger'); + const WebsiteComponent = BaseComponent.extend({ + /** + * @property {jQuery} + */ + $el: null, + + /** + * @property {BaseComponent} + */ + controlsComponent: null, + + /** + * @inheritDoc + */ + constructor: function WebsiteComponent() { + WebsiteComponent.__super__.constructor.apply(this, arguments); + }, + + /** + * @param {Object} options + */ + initialize: function (options) { + WebsiteComponent.__super__.initialize.apply(this, arguments); + + this.$el = $(options._sourceElement); + let configs = options.configs || {}; + let self = this; + this.$el.select2( + _.extend(configs, { + query: function (query) { + if (null === this.controlsComponent) { + logger.warn( + 'Invalid configuration of website component. No controls component present.' + ); + + query.callback({results: []}); + return; + } + + let availableWebsiteDTOsPromise = self + .controlsComponent + .getAvailableWebsiteDTOsFilteredPromise(); + + availableWebsiteDTOsPromise.then(function (availableWebsiteDTOs) { + let resultWebsiteDTOs = availableWebsiteDTOs; + if(query.term) { + resultWebsiteDTOs = _.filter(availableWebsiteDTOs, function (websiteDTO) { + return self.matchWebsiteByTerm(query.term, websiteDTO.getName()); + }); + } + + let data = { + results: _.map(resultWebsiteDTOs, function (websiteDTO) { + return {id: websiteDTO.getId(), text: websiteDTO.getName()}; + }) + }; + + query.callback(data); + }); + } + }) + ); + }, + + /** + * @param {string} term + * @param {string} websiteName + * @return {boolean} + */ + matchWebsiteByTerm: function(term, websiteName) { + let match = websiteName.toUpperCase().indexOf(term.toUpperCase()); + + return match >= 0; + }, + + isValidData: function() { + let selectedData = this.$el.select2('data'); + return !_.isEmpty(selectedData); + }, + + getData: function () { + let selectedData = this.$el.select2('data'); + if (this.isValidData()) { + return { + 'id' : selectedData.id, + 'name': selectedData.text + } + } + + return {}; + }, + + clearData: function() { + this.$el.select2('val', ''); + }, + + /** + * @param controlsComponent + */ + setWebsiteToSalesChannelControlsComponent: function(controlsComponent) { + this.controlsComponent = controlsComponent; + }, + + clearWebsiteToSalesChannelControlsComponent: function() { + this.controlsComponent = null; + }, + + getSelect2Data: function() { + if (null === this.controlsComponent) { + logger.warn( + 'Invalid configuration of website component. No controls component present.' + ); + + return {results: []}; + } + + let availableWebsiteDTOs = this.controlsComponent.getAvailableWebsiteDTOs(); + return { + results: _.map(availableWebsiteDTOs, function (websiteDTO) { + return {id: websiteDTO.getId(), text: websiteDTO.getName()}; + }) + }; + }, + + dispose: function () { + if (this.disposed) { + return; + } + + this.clearWebsiteToSalesChannelControlsComponent(); + this.$el.off(); + delete this.$el; + + WebsiteComponent.__super__.dispose.call(this); + } + }); + + return WebsiteComponent; +}); diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/website-to-sales-channel-controls-component.js b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/website-to-sales-channel-controls-component.js new file mode 100644 index 000000000..c802c256b --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/components/website-to-sales-channel-controls-component.js @@ -0,0 +1,327 @@ +define(function(require) { + 'use strict'; + + require('oroui/js/items-manager/table'); + const $ = require('jquery'); + const _ = require('underscore'); + const BaseComponent = require('oroui/js/app/components/base/component'); + const BaseCollection = require('oroui/js/app/models/base/collection'); + const WebsiteProvider = require('../provider/website-provider'); + const Utils = require('../utils/utils'); + const logger = require('oroui/js/tools/logger'); + const WebsiteToSalesChannelGridView = require('../views/website-to-sales-channel-grid-view'); + const WebsiteToSalesChannelMappingModel = require('../models/website-to-sales-channel-mapping-model'); + const WebsiteToSalesChannelControlsComponent = BaseComponent.extend({ + /** + * @property {jQuery} + */ + $el: null, + + /** + * @property {jQuery} + */ + $salesChannelSelectEl: null, + + /** + * @property {jQuery} + */ + $websiteSelectEl: null, + + /** + * @property {jQuery} + */ + $addBtnEl: null, + + /** + * @property {jQuery} + */ + $salesChannelGroupEl: null, + + /** + * @property {jQuery} + */ + $websiteToSalesChannelMappingEl: null, + + /** + * @property {jQuery} + */ + $salesChannelToWebsiteMappingTableBodyEl: null, + + /** + * @property {jQuery} + */ + $noDataEl: null, + + /** + * @property {BaseComponent} + */ + websiteSelectComponent: null, + + /** + * @property {BaseComponent} + */ + salesChannelSelectComponent: null, + + /** + * @property {string} + */ + salesChannelSelectSelector: '[data-role="sales-channel-in-group-select"]', + + /** + * @property {string} + */ + websiteSelectSelector: '[data-role="website-select"]', + + /** + * @property {string} + */ + addBtnSelector: '[data-role="add-mapping-item"]', + + /** + * @property {string} + */ + salesChannelToWebsiteMappingTableBodySelector: '[data-role="sales-channel-to-website-mapping-table-body"]', + + /** + * @property {string} + */ + noDataSelector: '.no-data', + + /** + * @property {BaseCollection} + */ + collection: null, + + /** + * @property {WebsiteToSalesChannelGridView} + */ + view: null, + + /** + * @inheritDoc + */ + constructor: function WebsiteToSalesChannelControlsComponent() { + WebsiteToSalesChannelControlsComponent.__super__.constructor.apply(this, arguments); + }, + + /** + * @param {Object} options + */ + initialize: function(options) { + WebsiteToSalesChannelControlsComponent.__super__.initialize.apply(this, arguments); + + this._initElements(options); + this._initCollection(); + this._initView(); + + let websiteSelectComponentPromise = options._subPromises['websiteSelectComponent']; + this._prepareWebsiteSelectComponent(websiteSelectComponentPromise); + + let salesChannelSelectComponentPromise = options._subPromises['salesChannelInGroupSelectComponent']; + this._prepareSalesChannelSelectComponent(salesChannelSelectComponentPromise); + }, + + /** + * @param {Object} options + */ + _initElements: function(options) { + this.$el = $(options._sourceElement); + this.$salesChannelSelectEl = this.$el.find(this.salesChannelSelectSelector); + this.$websiteSelectEl = this.$el.find(this.websiteSelectSelector); + this.$addBtnEl = this.$el.find(this.addBtnSelector); + this.$noDataEl = this.$el.find(this.noDataSelector); + this.$salesChannelToWebsiteMappingTableBodyEl = this.$el.find( + this.salesChannelToWebsiteMappingTableBodySelector + ); + this.$salesChannelGroupEl = $(options.selectorSalesChannelGroup); + this.$websiteToSalesChannelMappingEl = $(options.selectorWebsiteToSalesChannelMapping); + + let elements = { + $addBtnEl: this.$addBtnEl, + $salesChannelSelectEl: this.$salesChannelGroupEl, + $websiteSelectEl: this.$websiteSelectEl, + $salesChannelGroupEl: this.$salesChannelGroupEl, + $noDataEl: this.$noDataEl, + $websiteToSalesChannelMappingEl: this.$websiteToSalesChannelMappingEl, + $salesChannelToWebsiteMappingTableBodyEl: this.$salesChannelToWebsiteMappingTableBodyEl + }; + + let invalidOptionKeys = Utils.getInvalidJqueryOptionKeys(elements, [ + '$addBtnEl', + '$salesChannelSelectEl', + '$websiteSelectEl', + '$salesChannelGroupEl', + '$noDataEl', + '$websiteToSalesChannelMappingEl', + '$salesChannelToWebsiteMappingTableBodyEl' + ]); + + if (invalidOptionKeys.length) { + // unable to initialize + this.$el.remove(); + throw new TypeError('Missing required form element (s): ' + invalidOptionKeys.join(', ')); + } + }, + + _initCollection: function() { + const WebsiteToSalesChannelMappingCollection = BaseCollection.extend({ + model: WebsiteToSalesChannelMappingModel + }); + + let websiteToSalesChannelMapping = JSON.parse(this.$websiteToSalesChannelMappingEl.val() || '[]'); + this.collection = new WebsiteToSalesChannelMappingCollection(websiteToSalesChannelMapping); + this.collection.on('add remove change reset', _.bind(this.updateHiddenField, this)); + }, + + _initView: function() { + this.view = new WebsiteToSalesChannelGridView({ + el: this.$el, + collection: this.collection, + $addBtnEl: this.$addBtnEl, + $noDataEl: this.$noDataEl, + $salesChannelSelectEl: this.$salesChannelSelectEl, + $websiteSelectEl: this.$websiteSelectEl, + $salesChannelGroupEl: this.$salesChannelGroupEl, + $salesChannelToWebsiteMappingTableBodyEl: this.$salesChannelToWebsiteMappingTableBodyEl, + getSelectedMappingDataCallback: _.bind(this.getSelectedMappingData, this), + clearMappingFieldsCallback: _.bind(this.clearMappingFields, this) + }); + }, + + /** + * @param {Promise} websiteSelectComponentPromise + */ + _prepareWebsiteSelectComponent: function(websiteSelectComponentPromise) { + let self = this; + websiteSelectComponentPromise.then(function (component) { + component.setWebsiteToSalesChannelControlsComponent(self); + self.websiteSelectComponent = component; + }); + }, + + /** + * @param {Promise} salesChannelSelectComponentPromise + */ + _prepareSalesChannelSelectComponent: function(salesChannelSelectComponentPromise) { + let self = this; + salesChannelSelectComponentPromise.then(function (component) { + component.setWebsiteToSalesChannelControlsComponent(self); + component.setSalesChannelGroupEl(self.$salesChannelGroupEl); + self.salesChannelSelectComponent = component; + }); + }, + + /** + * @return {WebsiteToSalesChannelMappingModel|boolean} + */ + getSelectedMappingData: function() { + if (!this.websiteSelectComponent || !this.salesChannelSelectComponent) { + return null; + } + + let websiteData = this.websiteSelectComponent.getData(); + let salesChannelData = this.salesChannelSelectComponent.getData(); + + let model = new WebsiteToSalesChannelMappingModel({ + originWebsiteId: websiteData.id, + websiteName: websiteData.name, + salesChannelId: salesChannelData.id, + salesChannelName: salesChannelData.name + }); + + if (model.isValid()) { + return model; + } + + logger.warn( + 'Got invalid data to initialize WebsiteToSalesChannelMappingModel.' + ); + + return null; + }, + + clearMappingFields: function() { + if (!this.websiteSelectComponent || !this.salesChannelSelectComponent) { + return null; + } + + this.websiteSelectComponent.clearData(); + this.salesChannelSelectComponent.clearData(); + }, + + updateHiddenField: function() { + let hiddenElValue = ''; + if (!this.collection.isEmpty()) { + hiddenElValue = JSON.stringify(this.collection); + } + this.$websiteToSalesChannelMappingEl.val(hiddenElValue); + }, + + getSalesChannelGroupId: function() { + let salesChannelGroupId = this.$salesChannelGroupEl.val(); + return _.isUndefined(salesChannelGroupId) ? 0 : salesChannelGroupId; + }, + + getUsedSalesChannelIds: function() { + return this.collection.map(function (websiteToSalesChannelMappingModel) { + return websiteToSalesChannelMappingModel.getSalesChannelId(); + }); + }, + + /** + * @return {Object} + */ + getAvailableWebsiteDTOsFilteredPromise: function() { + let deferredObject = $.Deferred(); + let websiteDTOsPromise = WebsiteProvider.getWebsiteDTOsPromise(); + websiteDTOsPromise.then(_.bind(function (websiteDTOs) { + let usedOriginWebsiteIds = this.getUsedOriginWebsiteIds(); + let filteredWebsiteDTOs = _.filter(websiteDTOs, function (websiteDTO) { + return -1 === _.indexOf(usedOriginWebsiteIds, websiteDTO.getId()); + }); + + deferredObject.resolve(filteredWebsiteDTOs); + }, this)); + + return deferredObject.promise(); + }, + + /** + * @return {array} + */ + getUsedOriginWebsiteIds: function() { + return this.collection.map(function (websiteToSalesChannelMappingModel) { + return websiteToSalesChannelMappingModel.getOriginWebsiteId(); + }); + }, + + dispose: function() { + if (this.disposed) { + return; + } + + this.collection.off('add remove change reset', _.bind(this.updateHiddenField, this)); + + const properties = [ + '$el', + '$addBtnEl', + '$salesChannelSelectEl', + '$websiteSelectEl', + '$noDataEl', + '$salesChannelGroupEl', + '$websiteToSalesChannelMappingEl', + '$salesChannelToWebsiteMappingTableBodyEl', + 'websiteSelectComponent', + 'salesChannelSelectComponent' + ]; + + _.each(properties, _.bind(function (property) { + delete this[property]; + }, this)); + + WebsiteToSalesChannelControlsComponent.__super__.dispose.call(this); + } + }); + + return WebsiteToSalesChannelControlsComponent; +}); diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/dto/website.js b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/dto/website.js new file mode 100644 index 000000000..fda6c2fcf --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/dto/website.js @@ -0,0 +1,18 @@ +define(function() { + 'use strict'; + + return class { + constructor(id, name) { + this.id = id; + this.name = name; + } + + getId() { + return this.id; + } + + getName() { + return this.name; + } + }; +}); diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/models/check-connection-model.js b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/models/check-connection-model.js index dbbb69e07..6ebf2ba6d 100644 --- a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/models/check-connection-model.js +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/models/check-connection-model.js @@ -1,12 +1,13 @@ define(function(require) { 'use strict'; - var CheckConnectionModel; - var BaseModel = require('oroui/js/app/models/base/model'); + const BaseModel = require('oroui/js/app/models/base/model'); + const _ = require('underscore'); - CheckConnectionModel = BaseModel.extend({ + const CheckConnectionModel = BaseModel.extend({ defaults: { - websites: {} + websiteDTOs: {}, + isRequiredToCheckConnection: true }, /** @@ -14,7 +15,44 @@ define(function(require) { */ constructor: function CheckConnectionModel() { CheckConnectionModel.__super__.constructor.apply(this, arguments); + }, + + setConnectionChecked: function() { + this.set('isRequiredToCheckConnection', false); + }, + + setConnectionRequiredToCheck: function() { + this.set('isRequiredToCheckConnection', true); + }, + + isConnectionRequiredToCheck: function() { + return this.get('isRequiredToCheckConnection'); + }, + + /** + * @param {object} websiteDTOs + * + * {: WebsiteDTO, ...} + */ + setWebsiteDTOs: function (websiteDTOs) { + this.set('websiteDTOs', websiteDTOs); + }, + + resetWebsiteDTOsField: function() { + this.set('websiteDTOs', this.defaults.websiteDTOs); + }, + + hasWebsites: function() { + return _.keys(this.get('websiteDTOs')).length > 1; + }, + + /** + * @return {object} + */ + getWebsiteDTOs: function () { + return this.get('websiteDTOs'); } }); + return CheckConnectionModel; }); diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/models/website-to-sales-channel-mapping-model.js b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/models/website-to-sales-channel-mapping-model.js new file mode 100644 index 000000000..89aab68a0 --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/models/website-to-sales-channel-mapping-model.js @@ -0,0 +1,53 @@ +define(function(require) { + 'use strict'; + + const BaseModel = require('oroui/js/app/models/base/model'); + const _ = require('underscore'); + const WebsiteToSalesChannelMappingModel = BaseModel.extend({ + defaults: { + originWebsiteId: null, + websiteName: null, + salesChannelId: null, + salesChannelName: null + }, + + /** + * @inheritDoc + */ + constructor: function WebsiteToSalesChannelMappingModel() { + WebsiteToSalesChannelMappingModel.__super__.constructor.apply(this, arguments); + }, + + getOriginWebsiteId: function() { + return this.get('originWebsiteId'); + }, + + getWebsiteName: function() { + return this.get('websiteName'); + }, + + getSalesChannelId: function() { + return this.get('salesChannelId'); + }, + + getSalesChannelName: function() { + return this.get('salesChannelName'); + }, + + isValid: function () { + return !_.isNull(this.get('originWebsiteId')) && !_.isNull(this.get('websiteName')) && + !_.isNull(this.get('salesChannelId')) && !_.isNull(this.get('salesChannelName')); + }, + + serialize: function () { + return { + originWebsiteId: this.get('originWebsiteId'), + websiteName: this.get('websiteName'), + salesChannelId: this.get('salesChannelId'), + salesChannelName: this.get('salesChannelName') + }; + } + }); + + return WebsiteToSalesChannelMappingModel; +}); diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/provider/website-provider.js b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/provider/website-provider.js new file mode 100644 index 000000000..67fe640da --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/provider/website-provider.js @@ -0,0 +1,60 @@ +define(function(require) { + 'use strict'; + + const _ = require('underscore'); + const logger = require('oroui/js/tools/logger'); + const $ = require('jquery'); + const WebsiteProvider = { + /** + * @property {object} + */ + subProviderComponent: null, + + /** + * @param {object} subProviderComponent + * @return {*|boolean} + */ + isValidSubProviderComponent: function(subProviderComponent) { + if (!_.isObject(subProviderComponent)) { + return false; + } + + return _.isFunction(subProviderComponent.getWebsiteDTOsPromise); + }, + + setSubProvider: function (subProviderComponent) { + if (!this.isValidSubProviderComponent(subProviderComponent)) { + logger.warn( + 'Invalid website sub-provider component given!', + {websiteSubProvider: subProviderComponent} + ); + } + + this.subProviderComponent = subProviderComponent; + }, + + cleanSubProvider: function() { + this.subProviderComponent = null; + }, + + /** + * @return {Promise} + */ + getWebsiteDTOsPromise: function () { + let deferredObject = $.Deferred(); + if (!this.isValidSubProviderComponent(this.subProviderComponent)) { + logger.warn( + 'Invalid website sub-provider component found!', + {websiteSubProvider: this.subProviderComponent} + ); + + deferredObject.resolve({}); + return deferredObject.promise(); + } + + return this.subProviderComponent.getWebsiteDTOsPromise(); + } + }; + + return WebsiteProvider; +}); diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/utils/utils.js b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/utils/utils.js new file mode 100644 index 000000000..f04e75576 --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/utils/utils.js @@ -0,0 +1,25 @@ +define(function() { + 'use strict'; + + const _ = require('underscore'); + const Utils = { + /** + * @param {Object} options + * @param {array} elementKeys + * + * @return {array} + */ + getInvalidJqueryOptionKeys: function (options, elementKeys) { + let invalidKeys = _.filter(elementKeys, function (elementKey) { + /** + * @var {jQuery} element + */ + return _.isUndefined(options[elementKey]) || 0 === options[elementKey].length; + }); + + return invalidKeys; + }, + }; + + return Utils; +}); diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/views/check-connection-view.js b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/views/check-connection-view.js index 6d4533f1f..e13a21dd6 100644 --- a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/views/check-connection-view.js +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/views/check-connection-view.js @@ -1,16 +1,16 @@ define(function(require) { 'use strict'; - var CheckConnectionView; - var $ = require('jquery'); - var _ = require('underscore'); - var __ = require('orotranslation/js/translator'); - var routing = require('routing'); - var mediator = require('oroui/js/mediator'); - var messenger = require('oroui/js/messenger'); - var BaseView = require('oroui/js/app/views/base/view'); - - CheckConnectionView = BaseView.extend({ + const $ = require('jquery'); + const _ = require('underscore'); + const __ = require('orotranslation/js/translator'); + const routing = require('routing'); + const mediator = require('oroui/js/mediator'); + const messenger = require('oroui/js/messenger'); + const BaseView = require('oroui/js/app/views/base/view'); + const logger = require('oroui/js/tools/logger'); + const WebsiteDTO = require('../dto/website'); + const CheckConnectionView = BaseView.extend({ /** * @property {string} */ @@ -31,22 +31,7 @@ define(function(require) { /** * @property {jQuery} */ - checkButtonEl: null, - - /** - * @property {jQuery} - */ - checkConnectionStatusEl: null, - - /** - * @property {jQuery} - */ - salesGroupEl: null, - - /** - * @property {jQuery} - */ - websiteToSalesChannelMappingEl: null, + $checkConnectionStatusEl: null, /** * @property {int} @@ -58,6 +43,11 @@ define(function(require) { */ jqXHR: null, + /** + * @property {jqXHR} + */ + websiteJqXHR: null, + /** * @inheritDoc */ @@ -69,21 +59,31 @@ define(function(require) { _.extend(this, _.pick( options, [ - 'checkButtonEl', - 'checkConnectionStatusEl', - 'salesGroupEl', - 'websiteToSalesChannelMappingEl', + '$checkConnectionStatusEl', 'transportEntityId' ] )); this.url = this.getResolvedUrl(); + this.attachAdditionalEvents(options); + }, + + attachAdditionalEvents: function(options) { + let selectorForFieldsRequiredReCheckConnection = options.selectorForFieldsRequiredReCheckConnection || []; + _.each(selectorForFieldsRequiredReCheckConnection, _.bind(function (selector) { + this.$el.on('change', selector, _.bind(this.resetToDefaultCheckConnectionData, this)); + }, this)); + }, + + resetToDefaultCheckConnectionData: function() { + this.model.setConnectionRequiredToCheck(); + this.model.resetWebsiteDTOsField(); }, /** * @returns {string} */ getResolvedUrl: function() { - var params = this.getIntegrationAndTransportTypeParams() || {}; + let params = this.getIntegrationAndTransportTypeParams() || {}; params = _.extend({ transportId: !_.isNull(this.transportEntityId) ? this.transportEntityId : 0 }, params); @@ -92,9 +92,9 @@ define(function(require) { }, getIntegrationAndTransportTypeParams: function() { - var params = {}; - var fields = this.$el.formToArray(); - var integrationType = _.first( + let params = {}; + let fields = this.$el.formToArray(); + let integrationType = _.first( _.filter(fields, function(field) { return field.name.indexOf('[type]') !== -1; }) @@ -107,13 +107,13 @@ define(function(require) { * In case we on edit page and field type is disabled * so we can't get it from element data array */ - var typeEl = this.$el.find('[name$="[type]"]').first(); + let typeEl = this.$el.find('[name$="[type]"]').first(); if (typeEl.length) { params.integrationType = typeEl.val(); } } - var transportType = _.first( + let transportType = _.first( _.filter(fields, function(field) { return field.name.indexOf('[transportType]') !== -1; }) @@ -123,7 +123,7 @@ define(function(require) { params.transportType = transportType.value; } - var requiredMissed = ['integrationType', 'transportType'].filter(function(option) { + let requiredMissed = ['integrationType', 'transportType'].filter(function(option) { return _.isUndefined(params[option]); }); @@ -139,7 +139,7 @@ define(function(require) { * @returns {Array} */ getDataForRequestFromFields: function(fields) { - var data = _.filter(fields, function(field) { + let data = _.filter(fields, function(field) { return field.name.indexOf('[transport]') !== -1; }); @@ -149,38 +149,95 @@ define(function(require) { }); }, + /** + * @return {Promise} + */ + getWebsiteDTOsPromise: function() { + let deferredObject = $.Deferred(); + let model = this.model; + // Get cached data in model in case if previous request was successful + if (false === model.isConnectionRequiredToCheck()) { + deferredObject.resolve(model.getWebsiteDTOs()); + return deferredObject; + } + + // Update data in model + let self = this; + let data = this.getDataForRequestFromFields(this.$el.formToArray()); + this.websiteJqXHR = $.ajax({ + type: 'POST', + url: this.url, + data: $.param(data), + success: this.saveResultSuccessHandler.bind(this), + error: this.errorHandler.bind(this), + complete: function() { + delete self.websiteJqXHR; + } + }); + + this.websiteJqXHR.always(function () { + if (!model.hasWebsites()) { + messenger.notificationFlashMessage( + 'error', + __('marello.magento2.connection.no_websites') + ); + } + + deferredObject.resolve(model.getWebsiteDTOs()); + }); + return deferredObject; + }, + onCheckConnection: function() { this.$el.validate(); - if (this.$el.valid()) { - this.checkConnection(); + this.processCheckConnection(false); } }, - checkConnection: function() { - var data = this.getDataForRequestFromFields(this.$el.formToArray()); + processCheckConnection: function() { + if (this.jqXHR) { + logger.warn( + 'Trying to check connection while another request on checking connection in progress!', + { + request: this.jqXHR + } + ); + + return false; + } + + let self = this; + let data = this.getDataForRequestFromFields(this.$el.formToArray()); this.jqXHR = $.ajax({ type: 'POST', url: this.url, data: $.param(data), beforeSend: this.beforeSend.bind(this), - success: this.successHandler.bind(this), + success: this.checkConnectionSuccessHandler.bind(this), + error: this.errorHandler.bind(this), complete: function() { mediator.execute('hideLoading'); + // Clear request object + delete self.jqXHR; } }); }, beforeSend: function () { - this.checkConnectionStatusEl.find('.alert').remove(); + this.$checkConnectionStatusEl.find('.alert').remove(); mediator.execute('showLoading'); }, + errorHandler: function() { + this.resetToDefaultCheckConnectionData(); + }, + /** - * @param {{success: bool, message: string}} response + * @param {{success: bool, message: string, websites: object}} response */ - successHandler: function(response) { - var type = 'error'; + checkConnectionSuccessHandler: function(response) { + let type = 'error'; if (response.success) { type = 'success'; } @@ -189,10 +246,28 @@ define(function(require) { type, response.message, { - container: this.checkConnectionStatusEl, + container: this.$checkConnectionStatusEl, delay: 0 } ); + + this.saveResultSuccessHandler(response); + }, + + /** + * @param {{success: bool, message: string, websites: object}} response + */ + saveResultSuccessHandler: function(response) { + if (response.success) { + this.model.setConnectionChecked(); + let websites = _.map(response.websites, function (name, id) { + return new WebsiteDTO(id, name); + }); + + this.model.setWebsiteDTOs(websites); + } else { + this.resetToDefaultCheckConnectionData(); + } }, dispose: function() { @@ -204,6 +279,15 @@ define(function(require) { this.jqXHR.abort(); } + if (this.websiteJqXHR) { + this.websiteJqXHR.abort(); + } + + const properties = ['jqXHR', 'websiteJqXHR', '$checkConnectionStatusEl']; + _.each(properties, _.bind(function (property) { + delete this[property]; + }, this)); + CheckConnectionView.__super__.dispose.call(this); } }); diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/views/templates/mapping-item.html b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/views/templates/mapping-item.html new file mode 100644 index 000000000..b55a29d7f --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/views/templates/mapping-item.html @@ -0,0 +1,12 @@ + + <%= originWebsiteId %> + <%= websiteName %> + <%= salesChannelId %> + <%= salesChannelName %> + + + + + + diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/views/website-to-sales-channel-grid-view.js b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/views/website-to-sales-channel-grid-view.js new file mode 100644 index 000000000..6abbbafc0 --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/public/js/app/views/website-to-sales-channel-grid-view.js @@ -0,0 +1,176 @@ +define(function(require) { + 'use strict'; + + require('oroui/js/items-manager/table'); + const _ = require('underscore'); + const __ = require('orotranslation/js/translator'); + const BaseView = require('oroui/js/app/views/base/view'); + const mappingItemTemplate = require('text-loader!./templates/mapping-item.html'); + const WebsiteToSalesChannelGridView = BaseView.extend({ + /** + * @type {function(Object)} + */ + itemTemplate: _.template(mappingItemTemplate), + + /** + * @property {jQuery} + */ + $salesChannelSelectEl: null, + + /** + * @property {jQuery} + */ + $websiteSelectEl: null, + + /** + * @property {jQuery} + */ + $addBtnEl: null, + + /** + * @property {jQuery} + */ + $noDataEl: null, + + /** + * @property {jQuery} + */ + $salesChannelGroupEl: null, + + /** + * @property {jQuery} + */ + $salesChannelToWebsiteMappingTableBodyEl: null, + + /** + * @property {CallableFunction} + */ + getSelectedMappingDataCallback: null, + + /** + * @property {CallableFunction} + */ + clearMappingFieldsCallback: null, + + /** + * @inheritDoc + */ + constructor: function WebsiteToSalesChannelGridView() { + WebsiteToSalesChannelGridView.__super__.constructor.apply(this, arguments); + }, + + initialize: function(options) { + _.extend(this, _.pick( + options, + [ + '$salesChannelSelectEl', + '$websiteSelectEl', + '$addBtnEl', + '$noDataEl', + '$salesChannelGroupEl', + '$salesChannelToWebsiteMappingTableBodyEl', + 'getSelectedMappingDataCallback', + 'clearMappingFieldsCallback' + ] + )); + + this.initTable(); + this.initEventListeners(); + this.updateVisibleStateOfTableMappingDataEl(); + this.collection.on('add remove change reset', _.bind(this.onCollectionChange, this)); + }, + + initTable: function() { + this.$salesChannelToWebsiteMappingTableBodyEl.itemsManagerTable({ + collection: this.collection, + itemTemplate: this.itemTemplate, + sorting: false, + itemRender: function itemRender(template, data) { + let context = _.extend({__: __}, data); + + return template(context); + }, + deleteHandler: _.partial(function(collection, model, data) { + collection.remove(model); + }, this.collection) + }); + }, + + initEventListeners: function() { + this.$addBtnEl.on('click', _.bind(this.onBtnAddClickHandler, this)); + this.$salesChannelGroupEl.on('change', _.bind(this.onSalesChannelGroupChange, this)) + }, + + onSalesChannelGroupChange: function() { + this.collection.reset([]); + }, + + onBtnAddClickHandler: function (e) { + e.preventDefault(); + this.validateMappingForm(); + + if (this.isValidMappingForm()) { + let model = this.getSelectedMappingDataCallback(); + if (!model) { + throw new Error('Invalid model data given for valid form !'); + } + + this.collection.add(model); + this.clearMappingFieldsCallback(); + } + }, + + validateMappingForm: function() { + this.$salesChannelSelectEl + .parents('.controls') + .toggleClass('validation-error', !this.$salesChannelSelectEl.val()); + + this.$websiteSelectEl + .parents('.controls') + .toggleClass('validation-error', !this.$websiteSelectEl.val()); + }, + + isValidMappingForm: function() { + return this.$salesChannelSelectEl.val() && this.$websiteSelectEl.val(); + }, + + onCollectionChange: function() { + this.updateVisibleStateOfTableMappingDataEl(); + }, + + updateVisibleStateOfTableMappingDataEl: function() { + if (this.collection.isEmpty()) { + this.$salesChannelToWebsiteMappingTableBodyEl.hide(); + this.$noDataEl.show(); + } else { + this.$noDataEl.hide(); + this.$salesChannelToWebsiteMappingTableBodyEl.show(); + } + }, + + dispose: function() { + if (this.disposed) { + return; + } + + this.$addBtnEl.off('click', _.bind(this.onBtnAddClickHandler, this)); + this.$salesChannelGroupEl.off('change', _.bind(this.onSalesChannelGroupChange, this)) + + const properties = [ + '$addBtnEl', + '$salesChannelSelectEl', + '$websiteSelectEl', + '$noDataEl', + '$salesChannelToWebsiteMappingTableBodyEl', + ]; + + _.each(properties, _.bind(function (property) { + delete this[property]; + }, this)); + + WebsiteToSalesChannelGridView.__super__.dispose.call(this); + } + }); + + return WebsiteToSalesChannelGridView; +}); diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/translations/jsmessages.en.yml b/src/Marello/Bundle/Magento2Bundle/Resources/translations/jsmessages.en.yml new file mode 100644 index 000000000..290e830f1 --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/translations/jsmessages.en.yml @@ -0,0 +1,8 @@ +marello: + magento2: + connection: + no_websites: 'No websites available, probably connection is invalid. Please check the credentials.' + mapping_grid: + delete_action: 'Delete' + mapping_form: + no_sales_group_selected: 'Please specify "Sales Group" before use "Sales Channel" select.' diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/translations/messages.en.yml b/src/Marello/Bundle/Magento2Bundle/Resources/translations/messages.en.yml index 39b16ec6a..6a8655cd9 100644 --- a/src/Marello/Bundle/Magento2Bundle/Resources/translations/messages.en.yml +++ b/src/Marello/Bundle/Magento2Bundle/Resources/translations/messages.en.yml @@ -16,6 +16,10 @@ marello: authorization_error.message: 'Authentication error has occurred. Please check that API token is valid.' connection_error.message: 'Connection error has occurred. Please, try again later.' not_valid_parameters.message: 'Parameters are not valid!' + website_form: + website: + label: Website + placeholder: Select Website transport_setting_form: api_url: label: API Url @@ -29,6 +33,18 @@ marello: label: Sync start date tooltip: This is the starting date for data synchronization. Only records updated after this date will be synchronized + mapping_control_form: + add_mapping_btn: + label: Add Mapping + mapping_grid: + columns: + sales_channel_id: 'Sales Channel ID' + sales_channel_name: 'Sales Channel Name' + original_website_id: 'Original Website ID' + website_name: 'Website Name' + actions: 'Actions' + no_data: 'Please configure at least one relation between Website and Sales Channel.' + connector: website.label: Website Connector store.label: Store Connector diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/translations/validators.en.yml b/src/Marello/Bundle/Magento2Bundle/Resources/translations/validators.en.yml new file mode 100644 index 000000000..17d0abd7c --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/translations/validators.en.yml @@ -0,0 +1,4 @@ +marello: + magento2: + validator: + not_empty_website_to_sales_channel_mapping: 'At least one relation between Website and Sales Channel must be configured.' diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/views/Form/fields.html.twig b/src/Marello/Bundle/Magento2Bundle/Resources/views/Form/fields.html.twig index d121e83b6..c05c23a74 100644 --- a/src/Marello/Bundle/Magento2Bundle/Resources/views/Form/fields.html.twig +++ b/src/Marello/Bundle/Magento2Bundle/Resources/views/Form/fields.html.twig @@ -1,5 +1,5 @@ {% block marello_magento2_transport_check_button_widget %} - {% set options = form.vars.options|default({})|merge({ + {% set component_options = component_options|default({})|merge({ id: form.parent.vars.value and form.parent.vars.value.id is defined ? form.parent.vars.value.id : null, parentElementSelector: 'form', checkConnectionStatusSelector: '.check-connection-messages' @@ -7,7 +7,7 @@ {% set attr = attr|merge({ 'data-role': 'check-connection-btn', 'data-page-component-module': 'marellomagento2/js/app/components/check-connection-component', - 'data-page-component-options': options|json_encode|raw, + 'data-page-component-options': component_options|json_encode, }) %}
@@ -17,8 +17,64 @@
-{% endblock marello_magento2_transport_check_button_widget %} +{% endblock %} {% block marello_magento2_transport_check_button_row %} {{ block('button_row') }} -{% endblock marello_magento2_transport_check_button_row %} +{% endblock %} + +{% block marello_magento2_website_to_sales_channel_controls_widget %} +
+ {{ block('form_widget') }} + + + + + + + + + + + + +
{{ 'marello.magento2.mapping_control_form.mapping_grid.columns.sales_channel_id'|trans }}{{ 'marello.magento2.mapping_control_form.mapping_grid.columns.sales_channel_name'|trans }}{{ 'marello.magento2.mapping_control_form.mapping_grid.columns.original_website_id'|trans }}{{ 'marello.magento2.mapping_control_form.mapping_grid.columns.website_name'|trans }}{{ 'marello.magento2.mapping_control_form.mapping_grid.columns.actions'|trans }}
+ +
+{% endblock %} + +{% block marello_magento2_website_select_widget %} + {% if configs.placeholder is defined %} + {% set configs = configs|merge({'placeholder': configs.placeholder|trans|escape }) %} + {% endif %} + + {% set component_options = component_options|default({})|merge({'configs': configs}) %} + + {% set attr = attr|default({})|merge({ + 'class': 'select2', + 'data-page-component-options': component_options|json_encode|raw + }) %} + + {{ block('form_widget') }} +{% endblock %} + +{% block marello_magento2_website_select_row %} + {{ block('form_row') }} +{% endblock %} + +{% block marello_magento2_website_to_sales_channel_widget %} + {# @todo: Move this scss file #} +
+ {{ block('hidden_widget') }} +
+{% endblock %} diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/views/SalesChannel/Autocomplete/result.html.twig b/src/Marello/Bundle/Magento2Bundle/Resources/views/SalesChannel/Autocomplete/result.html.twig new file mode 100644 index 000000000..73eb4324f --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/views/SalesChannel/Autocomplete/result.html.twig @@ -0,0 +1 @@ +<%= highlight(_.escape(name)) %> diff --git a/src/Marello/Bundle/Magento2Bundle/Resources/views/SalesChannel/Autocomplete/selection.html.twig b/src/Marello/Bundle/Magento2Bundle/Resources/views/SalesChannel/Autocomplete/selection.html.twig new file mode 100644 index 000000000..73eb4324f --- /dev/null +++ b/src/Marello/Bundle/Magento2Bundle/Resources/views/SalesChannel/Autocomplete/selection.html.twig @@ -0,0 +1 @@ +<%= highlight(_.escape(name)) %> diff --git a/src/Marello/Bundle/Magento2Bundle/Transport/Rest/Iterator/WebsiteIterator.php b/src/Marello/Bundle/Magento2Bundle/Transport/Rest/Iterator/WebsiteIterator.php index f72a3bcc1..1f58fe30a 100644 --- a/src/Marello/Bundle/Magento2Bundle/Transport/Rest/Iterator/WebsiteIterator.php +++ b/src/Marello/Bundle/Magento2Bundle/Transport/Rest/Iterator/WebsiteIterator.php @@ -35,18 +35,19 @@ public function __construct(array $data, Magento2TransportSettings $settingsBag) protected function getData(): array { $data = \array_filter($this->data, function (array $websiteData) { - return isset($websiteData['id']) && self::ADMIN_WEBSITE_ID !== $websiteData['id']; + return isset($websiteData[WebsiteDataConverter::ID_COLUMN_NAME]) && + self::ADMIN_WEBSITE_ID !== $websiteData[WebsiteDataConverter::ID_COLUMN_NAME]; }); return \array_map(function (array $websiteData) { - $websiteData[WebsiteDataConverter::SALES_CHANNEL_CODE_COLUMN_NAME] = null; - if (isset($websiteData['code'])) { - $salesChannelCode = $this->settingsBag->getSalesChannelCodeByWebsiteCode( - $websiteData['code'] + $websiteData[WebsiteDataConverter::SALES_CHANNEL_CODE_COLUMN_ID] = null; + if (isset($websiteData[WebsiteDataConverter::ID_COLUMN_NAME])) { + $salesChannelId = $this->settingsBag->getSalesChannelIdByWebsiteId( + $websiteData[WebsiteDataConverter::ID_COLUMN_NAME] ); - $websiteData[WebsiteDataConverter::SALES_CHANNEL_CODE_COLUMN_NAME] = $salesChannelCode; + $websiteData[WebsiteDataConverter::SALES_CHANNEL_CODE_COLUMN_ID] = $salesChannelId; } else { - $this->logNoSalesChannelForWebsiteFound($websiteData['code']); + $this->logInvalidWebsiteDataFound($websiteData); } return $websiteData; @@ -54,15 +55,13 @@ protected function getData(): array } /** - * @param string $code + * @param array $websiteData */ - protected function logNoSalesChannelForWebsiteFound(string $code): void + protected function logInvalidWebsiteDataFound(array $websiteData): void { $this->logger->warning( - sprintf( - '[Magento 2] Website with code "%s" has no attached Sales Channel.', - $code - ) + '[Magento 2] Website data dooesn\'t contains id column.', + $websiteData ); } }